[
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n*               text=auto\n\n# Java sources\n*.java          text diff=java eol=lf\n*.gradle        text diff=java eol=lf\n\n# These files are text and should be normalized (Convert crlf => lf)\n*.css           text diff=css eol=lf\n*.html          text diff=html eol=lf\n*.md            text diff=markdown eol=lf\n*.js            text eol=lf\n*.csv           text eol=lf\n*.json          text eol=lf\n*.properties    text eol=lf\n*.svg           text eol=lf\n*.xml           text eol=lf\n*.yaml          text eol=lf\n*.yml           text eol=lf\n*.toml          text eol=lf\n*.lang          text eol=lf\n\n# These files are binary and should be left untouched\n*.png           binary\n*.gif           binary\n*.jpg           binary\n*.jpeg          binary\n\n# Common build-tool wrapper scripts\nmvnw            text eol=lf\ngradlew         text eol=lf\n*.sh            text eol=lf\n*.bat           text eol=crlf\n*.cmd           text eol=crlf"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to notify developers of unintended behavior\n\n---\n\n**Describe the bug**\n\n> A clear and concise description of what the bug is.\n\n**To Reproduce**\n\nSteps to reproduce the behavior:\n1. Open sample '...' _(provide sample download if possible)_\n2. Navigate to '....'\n3. Do something '....'\n4. See error\n\n**Exception**\n\nIf applicable, add the exception/stacktrace.\n```\nstacktrace goes here\n```\n\n**Screenshots**\n\nIf applicable, add screenshots to help explain your problem.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest a feature to improve Recaf\n\n---\n\n## Feature_Name\n\nDescription of the feature"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/other.md",
    "content": "---\nname: Other\nabout: If it isn't a bug or a feature request, choose this\n\n---\n\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: CI/CD\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n  workflow_dispatch:\n    inputs:\n      is-a-release:\n        description: Publish release? (Only works on master, and for untagged versions)\n        type: boolean\n\npermissions:\n  contents: write\n\njobs:\n  test:\n    name: Run test suite\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ ubuntu-latest ]\n        java-version: [ 22 ]\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n      - name: Setup JDK\n        uses: actions/setup-java@v3\n        with:\n          distribution: temurin\n          java-version: 22\n          check-latest: true\n      # The project version extract NEEDS to have the gradle wrapper already downloaded.\n      # So we have a dummy step here just to initialize it.\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v4\n      # Run the tests and upload results/coverage\n      - name: Run tests\n        run: ./gradlew test\n      - name: Upload test coverage to CodeCov.io\n        uses: codecov/codecov-action@v3\n        env:\n          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n      - name: Upload test artifacts\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: test-artifacts\n          retention-days: 21\n          path: |\n            **/TEST-*\n            **/hs_err_pid*\n      # Build the distribution jar and upload it, without bundling JavaFX in the jar\n      - name: Create distribution jar\n        run: ./gradlew assemble -x compileTestJava -Dskip_jfx_bundle=true\n      - name: Upload distribution jar\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: snapshot-build\n          retention-days: 30\n          path: |\n            recaf-ui/build/libs/recaf-ui-*-all.jar\n\n  # Publishes the test results from prior task work\n  publish-test-results:\n    name: Publish tests results\n    needs: test\n    if: always()\n    runs-on: ubuntu-latest\n    permissions:\n      checks: write\n      pull-requests: write\n    steps:\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: test-artifacts\n      - name: Publish test results\n        uses: EnricoMi/publish-unit-test-result-action@v2\n        with:\n          check_name: Unit test results\n          files: |\n            **/*.xml\n\n  # Builds the projects and attempts to publish a release if the current project version\n  # does not match any existing tags in the repository.\n  build-and-release:\n    name: Publish release\n    needs: test\n    if: inputs.is-a-release && github.repository == 'Col-E/Recaf' && github.ref == 'refs/heads/master'\n    strategy:\n      fail-fast: false\n    runs-on: ubuntu-latest\n    timeout-minutes: 20\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n        with:\n          fetch-depth: 0 # Required depth for JReleaser\n      - name: Setup Java JDK\n        uses: actions/setup-java@v3\n        with:\n          distribution: temurin\n          java-version: 22\n      # The project version extract NEEDS to have the gradle wrapper already downloaded.\n      # So we have a dummy step here just to initialize it.\n      - name: Setup Gradle\n        uses: gradle/actions/setup-gradle@v4\n      # Set environment variable for the project version: \"var_to_set=$(command_to_run)\" >> sink\n      #  - For maven:  echo \"PROJECT_VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)\" >> $GITHUB_ENV\n      #  - For gradle: echo \"PROJECT_VERSION=$(./gradlew properties | grep -Po '(?<=version: ).*')\" >> $GITHUB_ENV\n      - name: Extract project version to environment variable\n        run: echo \"PROJECT_VERSION=$(./gradlew properties | grep -Po '(?<=version\\W ).*')\" >> $GITHUB_ENV\n      # Check if a tag exists that matches the current project version.\n      # Write the existence state to the step output 'tagExists'.\n      - name: Check the package version has corresponding Git tag\n        id: tagged\n        shell: bash\n        run: |\n          git show-ref --tags --verify --quiet -- \"refs/tags/${{ env.PROJECT_VERSION }}\" && echo \"tagExists=1\" >> $GITHUB_OUTPUT || echo \"tagExists=0\" >> $GITHUB_OUTPUT\n          git show-ref --tags --verify --quiet -- \"refs/tags/${{ env.PROJECT_VERSION }}\" && echo \"Tag for current version exists\" || echo \"Tag for current version does not exist\"\n      # If the tag could not be fetched, show a message and abort the job.\n      # The wonky if logic is a workaround for: https://github.com/actions/runner/issues/1173\n      - name: Abort if tag exists, or existence check fails\n        if: ${{ false && steps.tagged.outputs.tagExists }}\n        run: |\n          echo \"Output of 'tagged' step: ${{ steps.tagged.outputs.tagExists }}\"\n          echo \"Failed to check if tag exists.\"\n          echo \"PROJECT_VERSION: ${{ env.PROJECT_VERSION }}\"\n          echo \"Tags $(git tag | wc -l):\"\n          git tag\n          git show-ref --tags --verify -- \"refs/tags/${{ env.PROJECT_VERSION }}\"\n          exit 1\n      # Run build to generate the release artifacts.\n      # Tag does not exist AND trigger was manual. Deploy release artifacts!\n      - name: Build release artifacts\n        run: ./gradlew publish # TODO: Publish all modules' artifacts into a single directory\n      # Make release with JReleaser, only running when the project version does not exist as a tag on the repository.\n      - name: Publish release\n        uses: jreleaser/release-action@v2\n        with:\n          arguments: full-release\n        env:\n          JRELEASER_PROJECT_VERSION: ${{ env.PROJECT_VERSION }}\n          JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          JRELEASER_GPG_PASSPHRASE: ${{ secrets.JRELEASER_GPG_PASSPHRASE }}\n          JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.JRELEASER_GPG_PUBLIC_KEY }}\n          JRELEASER_GPG_SECRET_KEY: ${{ secrets.JRELEASER_GPG_SECRET_KEY }}\n          JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.JRELEASER_MAVENCENTRAL_USERNAME }}\n          JRELEASER_MAVENCENTRAL_TOKEN: ${{ secrets.JRELEASER_MAVENCENTRAL_TOKEN }}\n      # Upload JRelease debug log\n      - name: JReleaser output\n        uses: actions/upload-artifact@v4\n        if: always()\n        with:\n          name: jreleaser-release\n          path: |\n            out/jreleaser/trace.log\n            out/jreleaser/output.properties"
  },
  {
    "path": ".gitignore",
    "content": "# IntelliJ\nout/\n.idea/\n.idea_modules/\n*.iws\n*.iml\n\n# Eclipse\n.settings/\n.classpath\n.checkstyle\n.project\n\n# Gradle\ntarget/\nbuild/\ngenerated/\ngradle-app.setting\n.gradle\n.gradletasknamecache\n!gradle-wrapper.jar\n**/build/\n\n# Misc\nhs_err_pid*\n*.log\n*.ctxt\ntemp/\n\n# IntelliJ\nlib/"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Recaf\n\nThe following is a series of guidelines for contributing to Recaf. They're not _\"rules\"_ per say, rather they're more like goals to strive towards. Regardless of how closely you adhere to the following guidelines I really appreciate you taking the time to contribute, it means a lot :+1:\n\n**Table of Contents**\n\n- [What if I am not a programmer?](#what-if-i-am-not-a-programmer)\n- [What should I know before I get started?](#what-should-i-know-before-getting-started)\n- [Is there a todo list?](#is-there-a-to-do-list)\n- [Reporting Bugs](#reporting-bugs)\n- [Suggesting Features](#suggesting-features)\n- [Coding Guidelines](#coding-guidelines)\n- [Pull Requests](#pull-requests)\n\n**TLDR?**\n\n- Follow the code style.\n- Document and comment your code.\n- Make sure the tests pass after making changes.\n- Translations and feature ideas are appreciated too.\n\n**Questions?**\n\nYou can DM `invokecoley` on discord, or join the [Recaf discord](https://discord.gg/Bya5HaA).\n\n## What if I am not a programmer?\n\n[There is plenty to contribute that isn't based in code.](https://www.youtube.com/watch?v=GAqfMNB-YBU&t=603)\n\nFor example, you can contribute ideas, add translations, or write documentation:\n\n- [Documentation source](https://github.com/Col-E/recaf-site)\n- [Documentation site](https://recaf.coley.software/)\n\n## What should I know before getting started?\n\nIt depends on what changes you are making. For instance, changing the user-interface requires very minimal or no reverse-engineering prior knowledge. If you do need JVM reversal knowledge to work on a feature you can check the [primer guide](PRIMER.md) which points to several good resources and outlines key details. For a general understanding of how Recaf works and how to begin creating contributions you can read our [getting started](https://recaf.coley.software/dev/getting-started.html) page as well.\n\n## Is there a to-do list?\n\nUnfortunately the to-do list is scattered around a few places. We're working on eventually consolidating everything into one place.\n\n## Reporting Bugs\n\nWhen creating an issue select the `Bug report` button.\nThis will provide a template that you can fill in the details for your bug.\nPlease include as much information as possible.\nThis can include:\n\n- Clear and descriptive title\n- Log files\n- Steps to reproduce the bug\n- An explanation of what you _\\*expected\\*_ to happen\n- The file being analyzed _(Do not share anything you do not own the rights to)_\n\n## Suggesting Features\n\nWhen creating an issue select the `Feature request` button.\nThis will provide a template that you can fill in the details for your feature idea.\nBe as descriptive as possible with your idea.\n\n**Note**: Not all ideas may be within Recaf's scope. In these cases the feature should be implemented as a script or plugin.\n\n## Coding Guidelines\n\n**Style**: IDE code formatting rules can be found in the [`/setup` directory](setup/).\n\n**Commits**: Try and keep commits small and focused on one thing at a time.\n\n## Pull Requests\n\nWhen creating a pull request please consider the following when filling in the template:\n\n- Clear and descriptive title\n- A clear description of what changes are included in the pull\n\nGithub's PR system will validate that your changes compile and pass the unit tests as well."
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017-2025 Matthew Coley\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "PRIMER.md",
    "content": "# What should I know before getting started?\n\n## JVM / Class file format\n\n### General concepts\n\nA basic understanding of the JVM / class file format is _highly_ reccomended before contributing. \nHere are some articles that should bring you up to speed:\n\n- [JVM Architecture 101: Get to Know Your Virtual Machine](https://blog.overops.com/jvm-architecture-101-get-to-know-your-virtual-machine/)\n- [JVM Internals](https://blog.jamesdbloom.com/JVMInternals.html)\n- [Java Code To Byte Code](https://blog.jamesdbloom.com/JavaCodeToByteCode_PartOne.html)\n\n### Terminology\n\n**Qualified name**: Package separators using the `.` character. \nThese are names used by runtime functions like `Class.forName(name)`.\n\nFor example: \n\n- `java.lang.String`\n- `com.example.MyClass.InnerClass`\n\n**Internal name**: Package separators using the `/` character. \nInner classes specified with the `$` character. \nThese are names how classes are specified internally in the class file.\n\nFor example: \n\n- `java/lang/String`\n- `com/example/MyClass$InnerClass`\n\nPrimitives *(Not the boxed types)* use single characters:\n\n| Primitive | Internal |\n|-----------|----------|\n| `long`    | `J`      |\n| `int`     | `I`      |\n| `short`   | `S`      |\n| `byte`    | `B`      |\n| `boolean` | `Z`      |\n| `float`   | `F`      |\n| `double`  | `D`      |\n| `void`    | `V`      |\n\n**Descriptor**: Used to describe field and method types. \nThese are essentially the same as internal names, but class names are wrapped in a prefix (`L`) and suffix character (`;`).\n\nFor example: \n\n * `Ljava/lang/String;`\n * `I` _(primitives stay the same)_\n \nMethod descriptors are formatted like so:\n \n * `double method(int i, String s)` = `(ILjava/lang/String;)D`\n * `void method()` = `()V`\n \nArrays are prefixed with a `[` for each level of the array.\n\n * `int[]` = `[I`\n * `String[][]` = `[[Ljava/lang/String;`\n \n### Quirks\n\n**Wide types**: `double` and `long` typed variables take up two slots _(On the stack and in the local variable table)_.\nFor example, declaring two doubles in a static method will use slots 0, then 2. \nSlots 0-3 are all in-use. \n\n**Lambdas**: The content of a lambda is defined in compiler-generated hidden methods and are invoked with `INVOKEDYNAMIC`. \nDecompilers will in-line the code so that it looks more similar to the source representation."
  },
  {
    "path": "PULL_REQUEST_TEMPLATE.md",
    "content": "## What's new\n\n* Summary of additions\n\n## What's fixed\n\n* Summary of bugs fixed\n"
  },
  {
    "path": "README.md",
    "content": "# Recaf [![Discord](https://dcbadge.limes.pink/api/server/https://discord.gg/Bya5HaA?style=flat)](https://discord.gg/Bya5HaA) [![codecov](https://codecov.io/gh/Col-E/Recaf/graph/badge.svg?token=N8GslpI1lL)](https://codecov.io/gh/Col-E/Recaf)  ![downloads](https://img.shields.io/github/downloads/Col-E/Recaf/total.svg) [![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](CONTRIBUTING.md)\n\n![Recaf 4x UI](recaf.png)\n\nAn easy to use modern Java bytecode editor that abstracts away the complexities of Java programs.\n\n## Download\n\n- [Launcher](https://github.com/Col-E/Recaf-Launcher)\n  - Usage & instructions found on the launcher repo\n- [Snapshot releases](https://github.com/Col-E/Recaf-Launcher/blob/master/MANUAL.md)\n  - See [CI actions](https://github.com/Col-E/Recaf/actions/workflows/build.yml) for release artifacts\n- [Independent releases](https://github.com/Col-E/Recaf/releases) _(None for 4X currently)_\n\n## Features\n\n- Edit Java bytecode with ease from a high or low level _(minus the annoying parts)_\n    - Editor features within Recaf abstract away complex details of compiled Java applications like:\n        - The constant pool\n        - Stack frame calculation\n        - Using wide instructions when needed\n        - And more!\n- Easy to use navigable interface with context-sensitive actions\n- Support for standard Java _and_ Android applications\n- Multiple decompilers to switch between, with all of their parameters made fully configurable\n- Built in compiler to allow recompiling decompiled classes, even if some referenced classes are missing *(When supported, support may vary depending on code complexity and obfuscation)*\n- A bytecode assembler with a simple syntax, and supporting tooling\n    - See the state of local variables and stack values at any point in methods\n    - Access variables by names instead of indices for clearer disassembled code\n    - Convert snippets of Java source code to bytecode sequences automatically\n- Searching for a variety of different content: Strings/numeric constants, classes and member references, instruction patterns\n- Tools for deobfuscating obfuscated code\n    - Specially crafted class files with the intent of crashing reverse engineering tools are automatically patched when opened in Recaf\n    - Specially crafted jar/zip files are read as the JVM does, bypassing sneaky tricks that can trick reverse engineering tools into showing the wrong data\n    - Support for automatically renaming obfuscated classes and their members\n    - Support for manually renaming classes and their members *_(And exporting these mappings to a variety of mapping formats for use in other tools)_*\n    - Bytecode transformers for simplifying common obfuscation strategies\n- Attach to running Java process with instrumentation capabilities\n- And much more\n\nA complete list of features can be found in the [user documentation](https://recaf.coley.software/user/index.html).\n\n## Scripting & Plugins\n\nRecaf exposes almost all of its functionality through modular API's. Automating behaviors can be done easily with scripts, or with plugins for more complex situations. Additional features can also be added via plugins, which can register hooks in API's that offer them.\n\nTo create your own script or plugin, see the [developer documentation](https://recaf.coley.software/dev/index.html), specifically the _\"plugins & scripts\"_ section.\n\n## Command Line\n\nRecaf can run as a command line application, which can be especially useful when paired with scripts provided at startup. You can see all the current launch arguments by passing `--help` as an application argument.\n\n## Development Setup\n\nClone the repository via `git clone https://github.com/Col-E/Recaf.git`\n\nOpen the project in an IDE or generate the build with gradle.\n\n**IDE**:\n1. Import the project from the `build.gradle` file\n2. Create a run configuration with the main class `software.coley.recaf.Main`\n\n**Without IDE**:\n1. Run `gradlew build`\n    - Output will be located at: `recaf-ui/build/libs/recaf-ui-{VERSION}-all.jar`\n"
  },
  {
    "path": "build.gradle",
    "content": "plugins {\n    alias(libs.plugins.benmanes.versions) apply false\n    alias(libs.plugins.coverage.report.aggregator)\n    alias(libs.plugins.checker.processor) apply false\n}\n\nallprojects {\n    group = 'software.coley'\n    version = '4.0.0-SNAPSHOT'\n}\n\nsubprojects {\n    apply plugin: 'java'\n    apply plugin: 'jacoco'\n    apply plugin: 'maven-publish'\n    apply plugin: 'com.github.ben-manes.versions'\n\n    repositories {\n        mavenLocal()\n        mavenCentral()\n        google()\n        maven {\n            url = 'https://jitpack.io'\n            name = 'JitPack'\n        }\n    }\n\n    // ======================= DEPENDENCIES ========================\n    dependencies {\n        // Enforce jakarta annotations everywhere as the standard for Nullable/Nonnull\n        implementation(libs.jakarta.annotation)\n\n        // Local libraries for internal use only\n        //  (none of the types from these libraries should be part of a public API)\n        implementation fileTree(dir: \"$rootProject.projectDir/libs\", include: ['*.jar'])\n    }\n\n    configurations.configureEach {\n        // Annoying annotations that replace desired tab completions.\n        exclude group: 'org.checkerframework'\n\n        // Other annotations we don't use which are transitive deps of deps\n        exclude group: 'com.google.code.findbugs'\n        exclude group: 'com.google.errorprone'\n        exclude group: 'com.google.j2objc'\n        exclude group: 'org.jetbrains', module: 'annotations'\n\n        // Used by ANTLR runtime, has a lot of IL8N related files which we don't use.\n        // Removing this dependency doesn't inhibit the behavior of libraries using the\n        // runtime in practice though.\n        exclude group: 'com.ibm.icu'\n    }\n\n    // ========================== COMPILE ==========================\n\n    // https://docs.gradle.org/current/userguide/toolchains.html\n    // gradlew -q javaToolchains - see the list of detected toolchains.\n    java {\n        toolchain {\n            languageVersion = JavaLanguageVersion.of(System.getenv('TARGET_VERSION') ?: '22')\n        }\n    }\n\n    // Append options for unchecked/deprecation\n    tasks.withType(JavaCompile).configureEach {\n        options.compilerArgs << '-Xlint:unchecked' << '-Xlint:deprecation' << '-g' << '-parameters'\n        options.encoding = 'UTF-8'\n        options.incremental = true\n    }\n\n    // Enable automatic generation of null checks on annotated methods\n    afterEvaluate { Project p ->\n        p.plugins.apply('gov.tak.gradle.plugins.checker-processor')\n    }\n\n    // ========================== TESTING ==========================\n\n    // All modules should have the same test framework setup.\n    test {\n        useJUnitPlatform()\n\n        // Required for Mockito in newer JDK's which disable useful features by default for 'integrity' reasons.\n        jvmArgs '-XX:+EnableDynamicAgentLoading'\n\n        systemProperty 'junit.jupiter.execution.parallel.enabled', true\n        systemProperty 'junit.jupiter.execution.parallel.mode.default', 'concurrent'\n\n        testLogging {\n            showStandardStreams = true\n            events \"passed\", \"skipped\", \"failed\"\n        }\n    }\n\n    // All modules with Java components should share the same test dependencies.\n    plugins.withType(JavaPlugin).configureEach {\n        dependencies {\n            testImplementation(libs.junit.api)\n            testImplementation(libs.junit.params)\n            testImplementation(libs.mockito)\n            testImplementation(libs.assertj)\n            testRuntimeOnly(libs.junit.engine)\n            testRuntimeOnly(libs.junit.launcher)\n        }\n    }\n\n    // Need to tell any test-fixture-plugin to include dependencies\n    // in its own configuration. Otherwise it can get confused.\n    plugins.withType(JavaTestFixturesPlugin).configureEach {\n        dependencies {\n            testFixturesApi(libs.junit.api)\n            testFixturesApi(libs.junit.params)\n            testFixturesApi(libs.mockito)\n        }\n    }\n\n    // Configure report outputs, and jacoco packages to target.\n    tasks.withType(Test).configureEach {\n        reports.html.required = false\n        reports.junitXml.required = true\n\n        // We want to cover all recaf classes, but not the test classes themselves (or auto-gen classes).\n        // The exclusion list is applied after the inclusion list, so this ends up working out.\n        jacoco {\n            includes = ['software/coley/recaf/**']\n            excludes = ['software/coley/recaf/**Test**', 'software/coley/recaf/test/**', '**/**WeldClientProxy']\n        }\n    }\n\n    // Setup artifact publishing to maven local\n    publishing {\n        publications {\n            mavenJava(MavenPublication) {\n                from components.java\n            }\n        }\n        repositories {\n            mavenLocal()\n        }\n    }\n}\n\n// Always emit HTML & XML aggregate reports\njacocoAggregation {\n    outputHtml = true\n    outputXml = true\n}\n\n// Build aggregate report for test coverage when subproject 'test' tasks complete.\n// But only do so when the 'test' tasks have executed.\n// You can skip tests by specifying '-x test' in your gradle task arguments.\ntasks.register('test') {\n    dependsOn(subprojects.test)\n    doLast {\n        if (subprojects.test.stream().anyMatch(Task::getDidWork))\n            buildJacocoAggregate.execute()\n    }\n}\n\ntasks.register('build') {\n    // Build will run tests, unless skipped by '-x test'.\n    // Even if skipped, this will still lead to the subproject build tasks being executed, such as:\n    //  - recaf-ui:shadowJar\n    dependsOn(tasks.named('test'))\n}"
  },
  {
    "path": "codecov.yml",
    "content": "coverage:\n  precision: 2\n  round: down\n  status:\n    project:\n      default:\n        informational: true\n    patch:\n      default:\n        informational: true\n\nignore:\n  - \"**/src/test/\"\n  - \"**/src/testFixtures\""
  },
  {
    "path": "docs/README.md",
    "content": "# Github pages directory\n\nGithub pages requires this directory be named `docs`. \nIf you were looking for documentation go here:\n\n- [User documentation](https://recaf.coley.software/user/index.html)\n- [Developer documentation](https://recaf.coley.software/dev/index.html)"
  },
  {
    "path": "docs/index.html",
    "content": "<html lang=\"en\">\n<head>\n    <title>Recaf - The modern bytecode editor</title>\n\t<meta charset=\"UTF-8\">\n\t<meta name=\"description\" content=\"Recaf - The modern bytecode editor\">\n\t<meta name=\"keywords\" content=\"Recaf, Java, Bytecode, Modern, Editor, Decompiler, Decompilation, Compiler, Deobfuscation, Deobfuscator\">\n    <meta http-equiv=\"Refresh\" content=\"0; url='https://recaf.coley.software/'\"/>\n    <style>\nhtml {\n\tbackground: rgb(33, 33, 34);\n\tcolor: rgb(210, 212, 214);\n\tfont-family: Arial, Helvetica, sans-serif;\n}\n\n.block {\n\tmargin: auto;\n\twidth: 270;\n\tbackground: rgb(40, 40, 42);\n\tpadding: 30px;\n}\n    </style>\n    <script type=\"text/javascript\">\n    \twindow.location.href = \"https://recaf.coley.software/\"\n    </script>\n</head>\n<body>\n<div class=\"block\">\n    <h1>Redirecting to <code>recaf.coley.software</code></h1>\n</div>\n</body>\n</html>"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nacc-agent = \"1.0.4\"\nassertj = \"3.27.3\"\nasm = \"9.9\"\natlantafx = \"2.1.0\"\nbinary-resources = \"31.3.0-alpha01.8\"\ncafedude = \"2.6.5\"\ncdi-api = \"4.1.0\"\ncdi-impl = \"6.0.2.Final\"\ncfr = \"0.152\"\ndex-translator = \"1.1.1\"\ndiffutils = \"4.16\"\ndocking = \"0.15.1\"\ndowngrader = \"1.3.3\"\nextra-collections = \"1.7.0\"\nextra-observables = \"1.3.0\"\ngson = \"2.13.1\"\nikonli = \"12.4.0\"\nimage-io-ext = { strictly = \"3.0.2\" } # newer release breaks ico plugin\nimage-io-ext-ico = \"3.0.2\"\ninstrument-server = \"1.5.0\"\njackson = \"2.18.2\"\njakarta-annotation = \"3.0.0\"\njasm = \"aacebfa8b0\"\njelf = \"0.10.0\"\njlinker = \"1.0.7\" # We could update, but I don't feel like rewriting the callgraph\njphantom = \"1.4.4\"\njunit = \"5.13.4\"\njunit-launch = \"1.13.4\"\njsvg = \"2.0.0\"\nlljzip = \"2.8.1\"\nlogback-classic = { strictly = \"1.4.11\" } # newer releases break in jar releases\nmapping-io = \"0.7.0\"\nmockito = \"5.19.0\"\nnatural-order = \"1.1\"\npicocli = \"4.7.7\"\npe = \"2.2.3\"\nprocyon = \"0.6.0\"\nreactfx = { strictly = \"2.0-M5\" } # won't get updates, dead\nregex = \"0.1.19\"\n# richtextfx = \"0.11.3\"\nrichtextfx = \"cbdcbd1440\"\ntreemapfx = \"1.1.0\"\nvineflower = \"1.11.2\"\nsourcesolver = \"1.1.9\"\n# Plugins\nbenmanes-versions = \"0.52.0\"\ncoverage-report-aggregator = \"1.3.2\"\nchecker-processor = \"2.0.4\"\njavafx-plugin = \"0.1.0\"\nshadow = \"9.1.0\"\npeterabeles-gversion = \"1.10.3\"\n\n[libraries]\nacc-agent = { module = \"software.coley:javafx-access-agent\", version.ref = \"acc-agent\" }\n\nassertj = { module = \"org.assertj:assertj-core\", version.ref = \"assertj\" }\n\nasm-core = { module = \"org.ow2.asm:asm\", version.ref = \"asm\" }\nasm-analysis = { module = \"org.ow2.asm:asm-analysis\", version.ref = \"asm\" }\nasm-commons = { module = \"org.ow2.asm:asm-commons\", version.ref = \"asm\" }\nasm-tree = { module = \"org.ow2.asm:asm-tree\", version.ref = \"asm\" }\nasm-util = { module = \"org.ow2.asm:asm-util\", version.ref = \"asm\" }\n\natlantafx = { module = \"io.github.mkpaz:atlantafx-base\", version.ref = \"atlantafx\" }\n\n# Use our fork of Android's release with fixes and less transitive dependencies\nbinary-resources = { module = \"com.github.Col-E:binary-resources\", version.ref = \"binary-resources\" }\n\ncafedude = { module = \"software.coley:cafedude-core\", version.ref = \"cafedude\" }\n\ncdi-api = { module = \"jakarta.enterprise:jakarta.enterprise.cdi-api\", version.ref = \"cdi-api\" }\ncdi-impl = { module = \"org.jboss.weld.se:weld-se-core\", version.ref = \"cdi-impl\" }\n\ncfr = { module = \"org.benf:cfr\", version.ref = \"cfr\" }\n\ndex-translator = { module = \"software.coley:dex-translator\", version.ref = \"dex-translator\" }\n\ndiffutils = { module = \"io.github.java-diff-utils:java-diff-utils\", version.ref = \"diffutils\" }\n\ndocking = { module = \"software.coley:bento-fx\", version.ref = \"docking\" }\n\ndowngrader-core = { module = \"xyz.wagyourtail.jvmdowngrader:jvmdowngrader\", version.ref = \"downgrader\" }\ndowngrader-impls = { module = \"xyz.wagyourtail.jvmdowngrader:jvmdowngrader-java-api\", version.ref = \"downgrader\" }\n\nextra-collections = { module = \"software.coley:extra-collections\", version.ref = \"extra-collections\" }\nextra-observables = { module = \"software.coley:extra-observables\", version.ref = \"extra-observables\" }\n\ngson = { module = \"com.google.code.gson:gson\", version.ref = \"gson\" }\n\nikonli-javafx = { module = \"org.kordamp.ikonli:ikonli-javafx\", version.ref = \"ikonli\" }\nikonli-pack = { module = \"org.kordamp.ikonli:ikonli-carbonicons-pack\", version.ref = \"ikonli\" }\n\nimage-io-ext = { module = \"com.twelvemonkeys.imageio:imageio-core\", version.ref = \"image-io-ext\" }\nimage-io-ext-ico = { module = \"com.twelvemonkeys.imageio:imageio-ico\", version.ref = \"image-io-ext-ico\" }\n\ninstrument-server = { module = \"software.coley:instrumentation-server\", version.ref = \"instrument-server\" }\n\njackson = { module = \"com.fasterxml.jackson.dataformat:jackson-dataformat-xml\", version.ref = \"jackson\" }\n\njakarta-annotation = { module = \"jakarta.annotation:jakarta.annotation-api\", version.ref = \"jakarta-annotation\" }\n\njasm-composistion-jvm = { module = \"com.github.jumanji144.Jasm:jasm-composition-jvm\", version.ref = \"jasm\" }\njasm-core = { module = \"com.github.jumanji144.Jasm:jasm-core\", version.ref = \"jasm\" }\n\njelf = { module = \"net.fornwall:jelf\", version.ref = \"jelf\" }\n\njlinker = { module = \"com.github.xxDark:jlinker\", version.ref = \"jlinker\" }\n\njphantom = { module = \"com.github.Col-E:jphantom\", version.ref = \"jphantom\" }\n\njunit-api = { module = \"org.junit.jupiter:junit-jupiter-api\", version.ref = \"junit\" }\njunit-engine = { module = \"org.junit.jupiter:junit-jupiter-engine\", version.ref = \"junit\" }\njunit-params = { module = \"org.junit.jupiter:junit-jupiter-params\", version.ref = \"junit\" }\njunit-launcher = { module = \"org.junit.platform:junit-platform-launcher\", version.ref = \"junit-launch\" }\n\njsvg = { module = \"com.github.weisj:jsvg\", version.ref = \"jsvg\" }\n\nlljzip = { module = \"software.coley:lljzip\", version.ref = \"lljzip\" }\n\nlogback-classic = { module = \"ch.qos.logback:logback-classic\", version.ref = \"logback-classic\" }\n\nmapping-io = { module = \"net.fabricmc:mapping-io\", version.ref = \"mapping-io\" }\n\nmockito = { module = \"org.mockito:mockito-core\", version.ref = \"mockito\" }\n\nnatural-order = { module = \"net.grey-panther:natural-comparator\", version.ref = \"natural-order\" }\n\npicocli = { module = \"info.picocli:picocli\", version.ref = \"picocli\" }\n\npe = { module = \"com.github.cademtz:JavaPeParser\", version.ref = \"pe\" }\n\nprocyon = { module = \"org.bitbucket.mstrobel:procyon-compilertools\", version.ref = \"procyon\" }\n\nreactfx = { module = \"org.reactfx:reactfx\", version.ref = \"reactfx\" }\n\nregex = { module = \"com.github.tommyettinger:regexodus\", version.ref = \"regex\" }\n\n# richtextfx = { module = \"org.fxmisc.richtext:richtextfx\", version.ref = \"richtextfx\" }\nrichtextfx = { module = \"com.github.Col-E:RichTextFX\", version.ref = \"richtextfx\" }\n\ntreemapfx = { module = \"software.coley:treemap-fx\", version.ref = \"treemapfx\" }\n\nvineflower = { module = \"org.vineflower:vineflower\", version.ref = \"vineflower\" }\n\nsourcesolver = { module = \"software.coley:source-solver\", version.ref = \"sourcesolver\" }\n\n[bundles]\nasm = [\n    \"asm-core\",\n    \"asm-analysis\",\n    \"asm-commons\",\n    \"asm-tree\",\n    \"asm-util\",\n]\njasm = [\n    \"jasm-core\",\n    \"jasm-composistion-jvm\",\n]\ndowngrader = [\n    \"downgrader-core\",\n    \"downgrader-impls\"\n]\ncdi = [\n    \"cdi-api\",\n    \"cdi-impl\",\n]\nlogging = [\n    \"logback-classic\"\n]\nikonli = [\n    \"ikonli-javafx\",\n    \"ikonli-pack\"\n]\nimage-io = [\n    \"image-io-ext\",\n    \"image-io-ext-ico\"\n]\n\n[plugins]\nbenmanes-versions = { id = \"com.github.ben-manes.versions\", version.ref = \"benmanes-versions\" }\ncoverage-report-aggregator = { id = \"gov.tak.gradle.plugins.coverage-report-aggregator\", version.ref = \"coverage-report-aggregator\" }\nchecker-processor = { id = \"gov.tak.gradle.plugins.checker-processor\", version.ref = \"checker-processor\" }\njavafx = { id = \"org.openjfx.javafxplugin\", version.ref = \"javafx-plugin\" }\nshadow = { id = \"com.gradleup.shadow\", version.ref = \"shadow\" }\npeterabeles-gversion = { id = \"com.peterabeles.gversion\", version.ref = \"peterabeles-gversion\" }\n"
  },
  {
    "path": "gradle/tasks.gradle",
    "content": "import java.nio.file.Files\nimport java.nio.file.StandardCopyOption\n\n// Copies all project dependencies (including transitive) to the local Maven repository.\n// This is useful for offline development, or when IntelliJ fails to resolve sources automatically.\n// Yes, this is AI slop, but it works. Good lord, I could not bring myself to write this by hand.\ntasks.register('cacheToMavenLocal') {\n    description = 'Copies all project dependencies (including transitive) to the local Maven repository'\n    group = 'dependencies'\n\n    doLast {\n        def processedComponents = new HashSet()\n        def allComponentIds = new HashSet()\n        def allComponents = []\n        def componentRepositories = [:]  // Map component to repository URL\n\n        // Build a list of repository URLs from the project's configured repositories\n        def repositoryUrls = []\n        project.repositories.each { repo ->\n            if (repo.hasProperty('url') && repo.name != 'MavenLocal') {\n                def repoUrl = repo.url.toString()\n                if (!repoUrl.endsWith('/')) {\n                    repoUrl += '/'\n                }\n                repositoryUrls.add([name: repo.name, url: repoUrl])\n                println \"Found repository: ${repo.name} -> ${repoUrl}\"\n            }\n        }\n\n        // First pass: collect all component IDs and determine their origin repositories\n        configurations.each { configuration ->\n            if (configuration.canBeResolved) {\n                try {\n                    configuration.incoming.resolutionResult.allComponents.each { component ->\n                        if (component.moduleVersion) {\n                            allComponentIds.add(component.id)\n                            allComponents.add(component)\n\n                            def moduleVersion = component.moduleVersion\n                            def componentKey = \"${moduleVersion.group}:${moduleVersion.name}:${moduleVersion.version}\".toString()\n\n                            // Try to determine repository by checking which one has the artifact\n                            if (!componentRepositories.containsKey(componentKey)) {\n                                def group = moduleVersion.group\n                                def name = moduleVersion.name\n                                def version = moduleVersion.version\n                                def groupPath = group.replace('.', '/')\n                                def pomFileName = \"${name}-${version}.pom\"\n\n                                // First check local Maven repository - if it's there, we can skip remote checks\n                                def localMavenRepo = new File(System.getProperty('user.home'), '.m2/repository')\n                                def localArtifactDir = new File(localMavenRepo, \"${groupPath}/${name}/${version}\")\n                                def localPomFile = new File(localArtifactDir, pomFileName)\n                                if (localPomFile.exists()) {\n                                    processedComponents.add(componentKey)\n                                    componentRepositories.put(componentKey, [name: 'MavenLocal', url: null])\n                                    println \"Detected ${componentKey} from MavenLocal (will skip)\"\n                                    return\n                                } else {\n                                    localArtifactDir.mkdirs()\n                                }\n\n                                // Check each repository to see which one has this artifact\n                                for (repo in repositoryUrls) {\n                                    def repoUrl = repo.url\n                                    def pomUrl = \"${repoUrl}${groupPath}/${name}/${version}/${pomFileName}\"\n\n                                    try {\n                                        def url = new URL(pomUrl)\n                                        def connection = (HttpURLConnection) url.openConnection()\n                                        connection.setRequestMethod(\"GET\")\n                                        connection.setConnectTimeout(2000)\n                                        connection.setReadTimeout(2000)\n                                        connection.setInstanceFollowRedirects(true)\n\n                                        def responseCode = connection.getResponseCode()\n                                        if (responseCode == 200 || responseCode == 301 || responseCode == 302) {\n                                            componentRepositories.put(componentKey, repo)\n                                            println \"Detected ${componentKey} from repository: ${repoUrl}\"\n\n                                            // Save the pom file\n                                            if (!localPomFile.exists() || localPomFile.length() != connection.getContentLengthLong()) {\n                                                println \"Copying POM for ${componentKey} to local Maven repository\"\n                                                localPomFile.withOutputStream { out ->\n                                                    out << connection.getInputStream()\n                                                }\n                                            } else {\n                                                println \"Skipping POM for ${componentKey} - already exists\"\n                                            }\n\n                                            break\n                                        }\n                                    } catch (Exception ignored) {\n                                        // This repository doesn't have it, try next one\n                                    }\n                                }\n\n                                // If we couldn't determine the repo, default to the first one (usually Maven Central)\n                                if (!componentRepositories.containsKey(componentKey) && !repositoryUrls.isEmpty()) {\n                                    println \"Could not detect repository for ${componentKey}\"\n                                }\n                            } else {\n                                println \"Already detected repository for ${componentKey}, skipping detection\"\n                            }\n                        }\n                    }\n                } catch (Exception e) {\n                    println \"Could not resolve configuration ${configuration.name}: ${e.message}\"\n                }\n            }\n        }\n\n        println \"Found ${allComponentIds.size()} unique components to process\"\n\n        // Second pass: process all artifacts\n        configurations.each { configuration ->\n            if (configuration.canBeResolved) {\n                try {\n                    // Use resolvedConfiguration to get all dependencies including transitive ones\n                    configuration.resolvedConfiguration.resolvedArtifacts.each { artifact ->\n                        def id = artifact.moduleVersion.id\n                        def componentKey = \"${id.group}:${id.name}:${id.version}\".toString()\n\n                        // Skip if we've already processed this component\n                        if (!processedComponents.add(componentKey))\n                            return\n\n                        def group = id.group\n                        def name = id.name\n                        def version = id.version\n\n                        // Define the local Maven repository path\n                        def localMavenRepo = new File(System.getProperty('user.home'), '.m2/repository')\n                        def groupPath = group.replace('.', '/')\n                        def artifactDir = new File(localMavenRepo, \"${groupPath}/${name}/${version}\")\n\n                        // Create directory structure\n                        artifactDir.mkdirs()\n\n                        // Copy the main artifact\n                        def artifactFile = artifact.file\n                        def fileName = artifactFile.name\n                        def targetFile = new File(artifactDir, fileName)\n\n                        if (!targetFile.exists() || targetFile.length() != artifactFile.length()) {\n                            println \"Copying ${group}:${name}:${version} (${fileName}) to local Maven repository\"\n                            Files.copy(\n                                    artifactFile.toPath(),\n                                    targetFile.toPath(),\n                                    StandardCopyOption.REPLACE_EXISTING\n                            )\n                        } else {\n                            println \"Skipping ${group}:${name}:${version} (${fileName}) - already exists\"\n                        }\n                    }\n                } catch (Exception e) {\n                    println \"Could not resolve configuration ${configuration.name}: ${e.message}\"\n                }\n            }\n        }\n\n        // Third pass: batch download sources and javadoc for all components\n        println \"\\nResolving sources and javadoc...\"\n\n        def resolvedSources = new HashSet()\n\n        try {\n            def sourcesQuery = dependencies.createArtifactResolutionQuery()\n                    .forComponents(allComponentIds)\n                    .withArtifacts(JvmLibrary, SourcesArtifact)\n                    .execute()\n\n            sourcesQuery.resolvedComponents.each { componentResult ->\n                def id = componentResult.id\n                // Extract module info - handle different ComponentIdentifier types\n                def moduleId = null\n                if (id.hasProperty('moduleIdentifier')) {\n                    moduleId = id.moduleIdentifier\n                } else if (id.hasProperty('module')) {\n                    moduleId = id.module\n                }\n\n                if (moduleId) {\n                    def group = moduleId.group\n                    def name = moduleId.name\n                    def version = id.hasProperty('version') ? id.version : ''\n\n                    def localMavenRepo = new File(System.getProperty('user.home'), '.m2/repository')\n                    def groupPath = group.replace('.', '/')\n                    def artifactDir = new File(localMavenRepo, \"${groupPath}/${name}/${version}\")\n                    artifactDir.mkdirs()\n\n                    def sourceArtifacts = componentResult.getArtifacts(SourcesArtifact)\n                    sourceArtifacts.each { artifactResult ->\n                        if (artifactResult instanceof ResolvedArtifactResult) {\n                            def componentKey = \"${group}:${name}:${version}\".toString()\n                            resolvedSources.add(componentKey)\n\n                            def sourceFile = artifactResult.file\n                            def sourceFileName = \"${name}-${version}-sources.jar\"\n                            def targetFile = new File(artifactDir, sourceFileName)\n                            if (!targetFile.exists() || targetFile.length() != sourceFile.length()) {\n                                println \"Copying ${group}:${name}:${version} sources to local Maven repository\"\n                                Files.copy(\n                                        sourceFile.toPath(),\n                                        targetFile.toPath(),\n                                        StandardCopyOption.REPLACE_EXISTING\n                                )\n                            } else {\n                                println \"Skipping ${group}:${name}:${version} sources - already exists\"\n                            }\n                        }\n                    }\n                }\n            }\n        } catch (Exception ex) {\n            println \"Exception while resolving sources: ${ex.message}\"\n        }\n\n        // Fallback: Try to download sources from the detected repository for components that weren't resolved\n        println \"\\nAttempting direct download for unresolved sources...\"\n        allComponents.each { component ->\n            def moduleVersion = component.moduleVersion\n            if (moduleVersion) {\n                def group = moduleVersion.group\n                def name = moduleVersion.name\n                def version = moduleVersion.version\n                def componentKey = \"${group}:${name}:${version}\".toString()\n\n                if (!resolvedSources.contains(componentKey)) {\n                    def localMavenRepo = new File(System.getProperty('user.home'), '.m2/repository')\n                    def groupPath = group.replace('.', '/')\n                    def artifactDir = new File(localMavenRepo, \"${groupPath}/${name}/${version}\")\n                    artifactDir.mkdirs()\n\n                    def sourceFileName = \"${name}-${version}-sources.jar\"\n                    def targetFile = new File(artifactDir, sourceFileName)\n\n                    if (!targetFile.exists()) {\n                        // Get the repository this component came from\n                        def repo = componentRepositories.get(componentKey)\n\n                        if (repo) {\n                            def repoUrl = repo.url\n                            def artifactUrl = \"${repoUrl}${groupPath}/${name}/${version}/${sourceFileName}\"\n\n                            try {\n                                println \"Attempting to download ${componentKey} sources from ${repoUrl}...\"\n                                def url = new URL(artifactUrl)\n                                def connection = (HttpURLConnection) url.openConnection()\n                                connection.setRequestMethod(\"GET\")\n                                connection.setConnectTimeout(5000)\n                                connection.setReadTimeout(30000)\n                                connection.setInstanceFollowRedirects(true)\n\n                                def responseCode = connection.getResponseCode()\n                                if (responseCode == 200) {\n                                    targetFile.withOutputStream { out ->\n                                        out << connection.getInputStream()\n                                    }\n                                    println \"Downloaded ${componentKey} sources from ${repoUrl}\"\n                                    resolvedSources.add(componentKey)\n                                } else if (responseCode == 301 || responseCode == 302) {\n                                    // Handle redirects\n                                    def newUrl = connection.getHeaderField(\"Location\")\n                                    println \"Redirected to ${newUrl}\"\n                                    def redirectConnection = (HttpURLConnection) new URL(newUrl).openConnection()\n                                    redirectConnection.setRequestMethod(\"GET\")\n                                    redirectConnection.setConnectTimeout(5000)\n                                    redirectConnection.setReadTimeout(30000)\n\n                                    if (redirectConnection.getResponseCode() == 200) {\n                                        targetFile.withOutputStream { out ->\n                                            out << redirectConnection.getInputStream()\n                                        }\n                                        println \"Downloaded ${componentKey} sources from ${repo.name} (after redirect)\"\n                                        resolvedSources.add(componentKey)\n                                    } else {\n                                        println \"Sources not available for ${componentKey} at ${repo.name} (HTTP ${redirectConnection.getResponseCode()})\"\n                                    }\n                                } else {\n                                    println \"Sources not available for ${componentKey} at ${repo.name} (HTTP ${responseCode})\"\n                                }\n                            } catch (Exception e) {\n                                println \"Could not download sources for ${componentKey} from ${repo.name}: ${e.message}\"\n                            }\n                        } else {\n                            println \"Could not determine repository for ${componentKey}, skipping sources download\"\n                        }\n                    } else {\n                        println \"Skipping ${componentKey} sources - already exists\"\n                        resolvedSources.add(componentKey)\n                    }\n                }\n            }\n        }\n\n        def resolvedJavadoc = new HashSet()\n\n        try {\n            def javadocQuery = dependencies.createArtifactResolutionQuery()\n                    .forComponents(allComponentIds)\n                    .withArtifacts(JvmLibrary, JavadocArtifact)\n                    .execute()\n\n            javadocQuery.resolvedComponents.each { componentResult ->\n                def id = componentResult.id\n                // Extract module info - handle different ComponentIdentifier types\n                def moduleId = null\n                if (id.hasProperty('moduleIdentifier')) {\n                    moduleId = id.moduleIdentifier\n                } else if (id.hasProperty('module')) {\n                    moduleId = id.module\n                }\n\n                if (moduleId) {\n                    def group = moduleId.group\n                    def name = moduleId.name\n                    def version = id.hasProperty('version') ? id.version : ''\n\n                    def localMavenRepo = new File(System.getProperty('user.home'), '.m2/repository')\n                    def groupPath = group.replace('.', '/')\n                    def artifactDir = new File(localMavenRepo, \"${groupPath}/${name}/${version}\")\n                    artifactDir.mkdirs()\n\n                    def javadocArtifacts = componentResult.getArtifacts(JavadocArtifact)\n                    javadocArtifacts.each { artifactResult ->\n                        if (artifactResult instanceof ResolvedArtifactResult) {\n                            def componentKey = \"${group}:${name}:${version}\".toString()\n                            resolvedJavadoc.add(componentKey)\n\n                            def javadocFile = artifactResult.file\n                            def javadocFileName = \"${name}-${version}-javadoc.jar\"\n                            def targetFile = new File(artifactDir, javadocFileName)\n                            if (!targetFile.exists() || targetFile.length() != javadocFile.length()) {\n                                println \"Copying ${group}:${name}:${version} javadoc to local Maven repository\"\n                                Files.copy(\n                                        javadocFile.toPath(),\n                                        targetFile.toPath(),\n                                        StandardCopyOption.REPLACE_EXISTING\n                                )\n                            } else {\n                                println \"Skipping ${group}:${name}:${version} javadoc - already exists\"\n                            }\n                        }\n                    }\n                }\n            }\n        } catch (Exception ex) {\n            println \"Exception while resolving javadoc: ${ex.message}\"\n        }\n\n        // Fallback: Try to download javadoc from the detected repository for components that weren't resolved\n        println \"\\nAttempting direct download for unresolved javadoc...\"\n        allComponents.each { component ->\n            def moduleVersion = component.moduleVersion\n            if (moduleVersion) {\n                def group = moduleVersion.group\n                def name = moduleVersion.name\n                def version = moduleVersion.version\n                def componentKey = \"${group}:${name}:${version}\".toString()\n\n                if (!resolvedJavadoc.contains(componentKey)) {\n                    def localMavenRepo = new File(System.getProperty('user.home'), '.m2/repository')\n                    def groupPath = group.replace('.', '/')\n                    def artifactDir = new File(localMavenRepo, \"${groupPath}/${name}/${version}\")\n                    artifactDir.mkdirs()\n\n                    def javadocFileName = \"${name}-${version}-javadoc.jar\"\n                    def targetFile = new File(artifactDir, javadocFileName)\n\n                    if (!targetFile.exists()) {\n                        // Get the repository this component came from\n                        def repo = componentRepositories.get(componentKey)\n\n                        if (repo) {\n                            def repoUrl = repo.url\n                            def artifactUrl = \"${repoUrl}${groupPath}/${name}/${version}/${javadocFileName}\"\n\n                            try {\n                                println \"Attempting to download ${componentKey} javadoc from ${repo.name}...\"\n                                def url = new URL(artifactUrl)\n                                def connection = (HttpURLConnection) url.openConnection()\n                                connection.setRequestMethod(\"GET\")\n                                connection.setConnectTimeout(5000)\n                                connection.setReadTimeout(30000)\n                                connection.setInstanceFollowRedirects(true)\n\n                                def responseCode = connection.getResponseCode()\n                                if (responseCode == 200) {\n                                    targetFile.withOutputStream { out ->\n                                        out << connection.getInputStream()\n                                    }\n                                    println \"Downloaded ${componentKey} javadoc from ${repo.name}\"\n                                    resolvedJavadoc.add(componentKey)\n                                } else if (responseCode == 301 || responseCode == 302) {\n                                    // Handle redirects\n                                    def newUrl = connection.getHeaderField(\"Location\")\n                                    println \"Redirected to ${newUrl}\"\n                                    def redirectConnection = (HttpURLConnection) new URL(newUrl).openConnection()\n                                    redirectConnection.setRequestMethod(\"GET\")\n                                    redirectConnection.setConnectTimeout(5000)\n                                    redirectConnection.setReadTimeout(30000)\n\n                                    if (redirectConnection.getResponseCode() == 200) {\n                                        targetFile.withOutputStream { out ->\n                                            out << redirectConnection.getInputStream()\n                                        }\n                                        println \"Downloaded ${componentKey} javadoc from ${repo.name} (after redirect)\"\n                                        resolvedJavadoc.add(componentKey)\n                                    } else {\n                                        println \"Javadoc not available for ${componentKey} at ${repo.name} (HTTP ${redirectConnection.getResponseCode()})\"\n                                    }\n                                } else {\n                                    println \"Javadoc not available for ${componentKey} at ${repo.name} (HTTP ${responseCode})\"\n                                }\n                            } catch (Exception e) {\n                                println \"Could not download javadoc for ${componentKey} from ${repo.name}: ${e.message}\"\n                            }\n                        } else {\n                            println \"Could not determine repository for ${componentKey}, skipping javadoc download\"\n                        }\n                    } else {\n                        println \"Skipping ${componentKey} javadoc - already exists\"\n                        resolvedJavadoc.add(componentKey)\n                    }\n                }\n            }\n        }\n\n        println \"\\nAll dependencies (including transitive) and sources have been copied to the local Maven repository\"\n        println \"Total unique components processed: ${processedComponents.size()}\"\n        println \"Sources resolved: ${resolvedSources.size()}\"\n        println \"Javadoc resolved: ${resolvedJavadoc.size()}\"\n    }\n}"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.2.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "org.gradle.caching=true"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=${0##*/}\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "jitpack.yml",
    "content": "jdk:\n  - openjdk22\nbefore_install:\n  - sdk install java 22.0.1-zulu\n  - sdk use java 22.0.1-zulu\n"
  },
  {
    "path": "libs/README.md",
    "content": "# Provided libraries\n\nIn some circumstances, libraries are not hosted on standard Maven hosts.\n\n## Kotlin Metadata\n\nGenerally provided in a much larger dependency, this is just the portion pertaining to reading Kotlin Metadata.\n\nThis minified dependency was provided by `SuperCoder79`"
  },
  {
    "path": "recaf-core/build.gradle",
    "content": "import java.nio.file.Files\n\nplugins {\n    alias(libs.plugins.peterabeles.gversion)\n}\n\napply plugin: 'java-library'\napply plugin: 'java-test-fixtures'\n\ndependencies {\n    api(libs.bundles.asm)\n    api(libs.binary.resources)\n    api(libs.cafedude)\n    api(libs.bundles.cdi)\n    api(libs.cfr)\n    api(libs.dex.translator) {\n        exclude group: 'com.android.tools'\n    }\n    api(libs.diffutils)\n    api(libs.bundles.downgrader) {\n        exclude group: 'org.ow2.asm'\n    }\n    api(libs.extra.collections)\n    api(libs.extra.observables)\n    api(libs.gson) // required by R8 (from dex-translator)\n    api(libs.instrument.server)\n    api(libs.jelf)\n    api(libs.jphantom)\n    api(libs.lljzip)\n    api(libs.bundles.logging)\n    api(libs.mapping.io)\n    api(libs.natural.order)\n    api(libs.pe)\n    api(libs.picocli)\n    api(libs.procyon)\n    api(libs.jlinker)\n    api(libs.regex)\n    api(libs.bundles.jasm)\n    api(libs.vineflower)\n    api(libs.sourcesolver)\n\n    // We use Jackson as a test dependency so that we can convert Android API XML models into JSON.\n    // This way, we only need to include GSON in the final Recaf build.\n    testImplementation(libs.jackson)\n}\n\n// Force generation of gversion data class when the version information is not up-to-date\ntasks.register('conditionalBuildConfigUpdate') {\n    if (!isBuildConfigUpToDate()) {\n        finalizedBy createVersionFile\n    }\n}\n\nproject.compileJava.dependsOn('conditionalBuildConfigUpdate')\n\ngversion {\n    srcDir = \"src/generated/java/\"\n    classPackage = \"software.coley.recaf\"\n    className = \"RecafBuildConfig\"\n    dateFormat = \"yyyy MM/dd HH:mm\"\n    debug = true\n    language = \"java\"\n    explicitType = false\n    annotate = false\n}\n\nsourceSets {\n    // Need to add the generated class to the source set\n    main {\n        java {\n            srcDirs 'src/generated/java', 'src/main/java'\n        }\n    }\n}\n\nclean {\n    // Delete the generated gversion class when cleaning\n    delete 'src/generated'\n}\n\nprivate boolean isBuildConfigUpToDate() {\n    File buildConfigPath = project.file(gversion.srcDir +\n            gversion.classPackage.replace('.', '/') + '/' +\n            gversion.className + \".java\")\n    if (buildConfigPath.exists()) {\n        String text = Files.readString(buildConfigPath.toPath())\n        if (text.contains('VERSION = \"' + project.version + '\"'))\n            return true\n    }\n    return false\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/Bootstrap.java",
    "content": "package software.coley.recaf;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.inject.se.SeContainer;\nimport org.jboss.weld.environment.se.Weld;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.cdi.EagerInitializationExtension;\n\nimport java.util.function.Consumer;\n\nimport static software.coley.recaf.RecafBuildConfig.*;\n\n/**\n * Handles creation of Recaf instance.\n *\n * @author Matt Coley\n */\npublic class Bootstrap {\n\tprivate static final Logger logger = Logging.get(Bootstrap.class);\n\tprivate static Recaf instance;\n\tprivate static Consumer<Weld> weldConsumer;\n\n\t/**\n\t * @return Recaf instance.\n\t */\n\t@Nonnull\n\tpublic static Recaf get() {\n\t\tif (instance == null) {\n\t\t\tString fmt = \"\"\"\n\t\t\t\t\tInitializing Recaf {}\n\t\t\t\t\t - Build rev:  {}\n\t\t\t\t\t - Build date: {}\n\t\t\t\t\t - Build hash: {}\"\"\";\n\t\t\tlogger.info(fmt, VERSION, GIT_REVISION, GIT_DATE, GIT_SHA);\n\t\t\tlong then = System.currentTimeMillis();\n\n\t\t\t// Create the Recaf container\n\t\t\ttry {\n\t\t\t\tSeContainer container = createContainer();\n\t\t\t\tinstance = new Recaf(container);\n\t\t\t\tlogger.info(\"Recaf CDI container created in {}ms\", System.currentTimeMillis() - then);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Failed to create Recaf CDI container\", t);\n\t\t\t\tExitDebugLoggingHook.exit(ExitCodes.ERR_CDI_INIT_FAILURE);\n\t\t\t}\n\t\t}\n\t\treturn instance;\n\t}\n\n\t/**\n\t * Must be called before invoking {@link #get()}.\n\t *\n\t * @param consumer\n\t * \t\tConsumer to operate on the CDI container producing {@link Weld} instance.\n\t */\n\tpublic static void setWeldConsumer(@Nullable Consumer<Weld> consumer) {\n\t\tweldConsumer = consumer;\n\t}\n\n\t@Nonnull\n\tprivate static SeContainer createContainer() {\n\t\tlogger.info(\"Creating Recaf CDI container...\");\n\t\tWeld weld = new Weld(\"recaf\");\n\t\tweld.setClassLoader(Bootstrap.class.getClassLoader());\n\n\t\t// Setup custom interceptors & extensions\n\t\tlogger.info(\"CDI: Adding interceptors & extensions\");\n\t\tweld.addExtension(EagerInitializationExtension.getInstance());\n\n\t\t// Setup bean discovery\n\t\tlogger.info(\"CDI: Registering bean packages\");\n\t\tweld.addPackage(true, Recaf.class);\n\n\t\t// Handle user-defined action\n\t\tif (weldConsumer != null) {\n\t\t\tlogger.info(\"CDI: Running user-defined Consumer<Weld>\");\n\t\t\tweldConsumer.accept(weld);\n\t\t\tweldConsumer = null;\n\t\t}\n\n\t\tlogger.info(\"CDI: Initializing...\");\n\t\treturn weld.initialize();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/ExitCodes.java",
    "content": "package software.coley.recaf;\n\n/**\n * Exit codes for Recaf calling {@link System#exit(int)}.\n *\n * @author Matt Coley\n */\npublic class ExitCodes {\n\tpublic static final int SUCCESS = 0;\n\tpublic static final int ERR_FX_UNKNOWN = 100;\n\tpublic static final int ERR_FX_CLASS_NOT_FOUND = 101;\n\tpublic static final int ERR_FX_NO_SUCH_METHOD = 102;\n\tpublic static final int ERR_FX_INVOKE_TARGET = 103;\n\tpublic static final int ERR_FX_ACCESS_TARGET = 104;\n\tpublic static final int ERR_FX_OLD_VERSION = 105;\n\tpublic static final int ERR_FX_UNKNOWN_VERSION = 106;\n\tpublic static final int INTELLIJ_TERMINATION = 130;\n\tpublic static final int ERR_CDI_INIT_FAILURE = 150;\n\tpublic static final int ERR_NOT_A_JDK = 160;\n\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/ExitDebugLoggingHook.java",
    "content": "package software.coley.recaf;\n\nimport com.google.common.hash.Hasher;\nimport com.google.common.hash.Hashing;\nimport jakarta.annotation.Nonnull;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.file.RecafDirectoriesConfig;\nimport software.coley.recaf.util.JavaVersion;\nimport software.coley.recaf.util.ReflectUtil;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodType;\nimport java.lang.module.ModuleReference;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.net.URI;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * A hook registered to {@code java.lang.Shutdown} and not {@link Runtime#addShutdownHook(Thread)}.\n * This allows us to use the {@link StackWalker} API to detect the exit code from {@link System#exit(int)}\n *\n * @author Matt Coley\n */\npublic class ExitDebugLoggingHook {\n\tprivate static final int UNKNOWN_CODE = -1337420;\n\tprivate static final Logger logger = Logging.get(ExitDebugLoggingHook.class);\n\tprivate static int exitCode = UNKNOWN_CODE;\n\tprivate static boolean printConfigs;\n\tprivate static Thread mainThread;\n\tprivate static MethodHandles.Lookup lookup;\n\n\t/**\n\t * Register the shutdown hook.\n\t */\n\tpublic static void register() {\n\t\tlookup = ReflectUtil.lookup();\n\t\ttry {\n\t\t\t// We use this instead of the Runtime shutdown hook thread because this will run on the same thread\n\t\t\t// as the call to System.exit(int)\n\t\t\tClass<?> shutdown = lookup.findClass(\"java.lang.Shutdown\");\n\t\t\tMethodHandle add = lookup.findStatic(shutdown, \"add\", MethodType.methodType(void.class, int.class, boolean.class, Runnable.class));\n\t\t\tadd.invoke(9, false, (Runnable) ExitDebugLoggingHook::run);\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Failed to add exit-hooking debug dumping shutdown hook\", t);\n\n\t\t\t// Use fallback shutdown hook which checks for manual exit codes being set.\n\t\t\tRuntime.getRuntime().addShutdownHook(new Thread(() -> {\n\t\t\t\tif (exitCode != UNKNOWN_CODE)\n\t\t\t\t\thandle(exitCode);\n\t\t\t}));\n\t\t}\n\t}\n\n\tprivate static void run() {\n\t\t// If we've set the exit code manually, use that.\n\t\tif (exitCode != UNKNOWN_CODE) {\n\t\t\thandle(exitCode);\n\t\t\treturn;\n\t\t}\n\n\t\t// We didn't do the exit, so lets try and see if we can figure out who did it.\n\t\ttry {\n\t\t\tAtomicBoolean visited = new AtomicBoolean();\n\t\t\tClass<?> shutdown = lookup.findClass(\"java.lang.Shutdown\");\n\t\t\tClass<?> stackFrameClass = Class.forName(\"java.lang.LiveStackFrame\");\n\t\t\tMethodHandle getLocals = lookup.findVirtual(stackFrameClass, \"getLocals\", MethodType.methodType(Object[].class))\n\t\t\t\t\t.asType(MethodType.methodType(Object[].class, Object.class));\n\t\t\tMethod getStackWalker = stackFrameClass.getDeclaredMethod(\"getStackWalker\", Set.class);\n\t\t\tgetStackWalker.setAccessible(true);\n\t\t\tStackWalker stackWalker = (StackWalker) getStackWalker.invoke(null, Set.of(StackWalker.Option.RETAIN_CLASS_REFERENCE, StackWalker.Option.SHOW_HIDDEN_FRAMES));\n\t\t\tstackWalker.forEach(frame -> {\n\t\t\t\ttry {\n\t\t\t\t\tif (!visited.get() && frame.getDeclaringClass() == shutdown && frame.getMethodName().equals(\"exit\")) {\n\t\t\t\t\t\tObject[] locals = (Object[]) getLocals.invoke(frame);\n\t\t\t\t\t\tString local0 = locals[0].toString().replaceAll(\"\\\\D+\", \"\");\n\t\t\t\t\t\tint exit = (int) Long.parseLong(local0); // Need to parse as long, cast to int\n\t\t\t\t\t\thandle(exit);\n\t\t\t\t\t\tvisited.set(true);\n\t\t\t\t\t}\n\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\tthrow new IllegalStateException(t);\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (Throwable t) {\n\t\t\t// If this cursed abomination breaks, we want to know about it\n\t\t\tlogger.error(\"\"\"\n\t\t\t\t\tFailed to detect application exit code.\n\n\t\t\t\t\tPlease report that the exit debugger has failed.\n\n\t\t\t\t\t  https://github.com/Col-E/Recaf/issues/new?labels=bug&title=Error%20Debugger%20Hook%20fails%20on%20Java%20{V}\n\t\t\t\t\t\"\"\".replace(\"{V}\", String.valueOf(JavaVersion.get())), t);\n\t\t}\n\t}\n\n\tprivate static void handle(int code) {\n\t\t// Skip on successful closure\n\t\tif (code == ExitCodes.SUCCESS || code == ExitCodes.INTELLIJ_TERMINATION)\n\t\t\treturn;\n\n\t\tif (code == UNKNOWN_CODE)\n\t\t\tSystem.out.println(\"Exit code: <error>\");\n\t\telse\n\t\t\tSystem.out.println(\"Exit code: \" + code);\n\n\t\tSystem.out.println(\"Java\");\n\t\tSystem.out.println(\" - Version (Runtime): \" + System.getProperty(\"java.runtime.version\", \"<unknown>\"));\n\t\tSystem.out.println(\" - Version (Raw):     \" + JavaVersion.get());\n\t\tSystem.out.println(\" - Vendor:            \" + System.getProperty(\"java.vm.vendor\", \"<unknown>\"));\n\t\tSystem.out.println(\" - Home:              \" + System.getProperty(\"java.home\", \"<unknown>\"));\n\n\t\tSystem.out.println(\"JavaFX\");\n\t\tSystem.out.println(\" - Version (Runtime): \" + System.getProperty(\"javafx.runtime.version\", \"<uninitialized>\"));\n\t\tSystem.out.println(\" - Version (Raw):     \" + System.getProperty(\"javafx.version\", \"<uninitialized>\"));\n\t\t{\n\t\t\tClassLoader loader = ExitDebugLoggingHook.class.getClassLoader();\n\t\t\tString javafxClass = \"javafx/beans/Observable.class\";\n\t\t\ttry {\n\t\t\t\tIterator<URL> iterator = loader.getResources(javafxClass).asIterator();\n\t\t\t\tif (!iterator.hasNext()) {\n\t\t\t\t\tSystem.out.println(\" - Location: not found\");\n\t\t\t\t} else {\n\t\t\t\t\tURL url = iterator.next();\n\t\t\t\t\tif (!iterator.hasNext()) {\n\t\t\t\t\t\tSystem.out.println(\" - Location:          \" + url);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tSystem.out.println(\" - Location (likely): \" + url);\n\t\t\t\t\t\tdo {\n\t\t\t\t\t\t\tSystem.out.println(\" - Location (seen):   \" + url);\n\t\t\t\t\t\t} while (iterator.hasNext());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (Exception ex) {\n\t\t\t\tSystem.out.println(\" - Location:   <error>\");\n\t\t\t}\n\t\t}\n\n\t\tSystem.out.println(\"Operating System\");\n\t\tSystem.out.println(\" - Name:           \" + System.getProperty(\"os.name\"));\n\t\tSystem.out.println(\" - Version:        \" + System.getProperty(\"os.version\"));\n\t\tSystem.out.println(\" - Architecture:   \" + System.getProperty(\"os.arch\"));\n\t\tSystem.out.println(\" - Processors:     \" + Runtime.getRuntime().availableProcessors());\n\t\tSystem.out.println(\" - Path Separator: \" + File.pathSeparator);\n\t\tSystem.out.println(\"Recaf\");\n\t\tSystem.out.println(\" - Version:    \" + RecafBuildConfig.VERSION);\n\t\tSystem.out.println(\" - Build hash: \" + RecafBuildConfig.GIT_SHA);\n\t\tSystem.out.println(\" - Build date: \" + RecafBuildConfig.GIT_DATE);\n\n\t\tString command = System.getProperty(\"sun.java.command\", \"\");\n\t\tif (command != null) {\n\t\t\tSystem.out.println(\"Launch\");\n\t\t\tSystem.out.println(\" - Args: \" + command);\n\t\t}\n\n\t\tString[] classPath = System.getProperty(\"java.class.path\").split(File.pathSeparator);\n\t\tSystem.out.println(\"Classpath:\");\n\t\tfor (String pathEntry : classPath) {\n\t\t\tFile file = new File(pathEntry);\n\t\t\tif (file.isFile()) {\n\t\t\t\tSystem.out.println(\" - File: \" + pathEntry);\n\t\t\t\ttry {\n\t\t\t\t\tSystem.out.println(\"   - SHA1: \" + createSha1(file));\n\t\t\t\t} catch (Exception ex) {\n\t\t\t\t\tSystem.out.println(\"   - SHA1: <error>\");\n\t\t\t\t}\n\t\t\t} else if (file.isDirectory()) {\n\t\t\t\tSystem.out.println(\" - Directory: \" + pathEntry);\n\t\t\t}\n\t\t}\n\n\t\tSystem.out.println(\"Boot class loader:\");\n\t\tdumpBootstrapClassLoader();\n\t\tSystem.out.println(\"Platform class loader:\");\n\t\tdumpBuiltinClassLoader(ClassLoader.getPlatformClassLoader());\n\n\t\tPath root = RecafDirectoriesConfig.createBaseDirectory().resolve(\"config\");\n\t\tif (printConfigs && Files.isDirectory(root)) {\n\t\t\tSystem.out.println(\"Configs\");\n\t\t\ttry (Stream<Path> stream = Files.walk(root)) {\n\t\t\t\tstream.filter(path -> path.getFileName().toString().endsWith(\"-config.json\")).forEach(path -> {\n\t\t\t\t\tString configName = path.getFileName().toString();\n\n\t\t\t\t\t// Skip certain configs\n\t\t\t\t\tif (configName.contains(\"service.ui.bind-\"))\n\t\t\t\t\t\treturn;\n\t\t\t\t\tif (configName.contains(\"service.ui.snippets-config\"))\n\t\t\t\t\t\treturn;\n\t\t\t\t\tif (configName.contains(\"service.io.recent-workspaces-config\"))\n\t\t\t\t\t\treturn;\n\t\t\t\t\tif (configName.contains(\"service.decompile.impl.decompiler-\"))\n\t\t\t\t\t\treturn;\n\n\t\t\t\t\tSystem.out.println(\" - \" + configName + \":\");\n\t\t\t\t\ttry {\n\t\t\t\t\t\tString indent = \"    \";\n\t\t\t\t\t\tString configJson = Files.readString(path);\n\t\t\t\t\t\tString indented = indent + Arrays.stream(configJson.split(\"\\n\"))\n\t\t\t\t\t\t\t\t.collect(Collectors.joining(\"\\n\" + indent));\n\t\t\t\t\t\tSystem.out.println(indented);\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tSystem.out.println(\" - <error>\");\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} catch (Exception ex) {\n\t\t\t\tSystem.out.println(\" - <error>\");\n\t\t\t}\n\t\t}\n\n\t\tSystem.out.println(\"Threads\");\n\t\tThread.getAllStackTraces().forEach((thread, trace) -> {\n\t\t\tSystem.out.println(\" - \" + thread.getName() + \" [\" + thread.getState().name() + \"]\");\n\t\t\tfor (StackTraceElement element : trace) {\n\t\t\t\tSystem.out.println(\"   - \" + element);\n\t\t\t}\n\t\t});\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static void dumpBuiltinClassLoader(ClassLoader loader) {\n\t\ttry {\n\t\t\tClass<?> c = Class.forName(\"jdk.internal.loader.BuiltinClassLoader\");\n\t\t\tField nameToModuleField = c.getDeclaredField(\"nameToModule\");\n\t\t\tnameToModuleField.setAccessible(true);\n\t\t\tMap<String, ModuleReference> mdouleMap = (Map<String, ModuleReference>) nameToModuleField.get(loader);\n\t\t\tfor (Map.Entry<String, ModuleReference> e : mdouleMap.entrySet()) {\n\t\t\t\tModuleReference moduleReference = e.getValue();\n\t\t\t\tSystem.out.printf(\"%s located at %s%n\", moduleReference.descriptor().toNameAndVersion(), moduleReference.location()\n\t\t\t\t\t\t.map(URI::toString)\n\t\t\t\t\t\t.orElse(\"Unknown\"));\n\t\t\t}\n\t\t} catch (Exception ex) {\n\t\t\tSystem.out.printf(\"dumpBuiltinClassLoader(%s) - <error>%n\", loader);\n\t\t}\n\t}\n\n\tprivate static void dumpBootstrapClassLoader() {\n\t\ttry {\n\t\t\tClass<?> c = Class.forName(\"jdk.internal.loader.ClassLoaders\");\n\t\t\tMethod bootLoaderMethod = c.getDeclaredMethod(\"bootLoader\");\n\t\t\tbootLoaderMethod.setAccessible(true);\n\t\t\tdumpBuiltinClassLoader((ClassLoader) bootLoaderMethod.invoke(null));\n\t\t} catch (Exception ex) {\n\t\t\tSystem.out.println(\"dumpBootstrapClassLoader - <error>\");\n\t\t}\n\t}\n\n\t@Nonnull\n\t@SuppressWarnings(\"all\")\n\tprivate static String createSha1(@Nonnull File file) throws Exception {\n\t\tlong length = file.length();\n\t\tif (length > Integer.MAX_VALUE)\n\t\t\tthrow new IOException(\"File too large to hash\");\n\t\tHasher hasher = Hashing.sha1().newHasher((int) length);\n\t\ttry (InputStream fis = new FileInputStream(file)) {\n\t\t\tint n = 0;\n\t\t\tbyte[] buffer = new byte[8192];\n\t\t\twhile (n != -1) {\n\t\t\t\tn = fis.read(buffer);\n\t\t\t\tif (n > 0)\n\t\t\t\t\thasher.putBytes(buffer, 0, n);\n\t\t\t}\n\t\t\treturn hasher.hash().toString();\n\t\t}\n\t}\n\n\tpublic static void exit(int exitCode) {\n\t\tExitDebugLoggingHook.exitCode = exitCode;\n\t\tSystem.exit(exitCode);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/Recaf.java",
    "content": "package software.coley.recaf;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.enterprise.inject.se.SeContainer;\n\nimport java.lang.annotation.Annotation;\nimport java.util.Locale;\n\n/**\n * Recaf application instance.\n *\n * @author Matt Coley\n */\npublic class Recaf {\n\tprivate static final Annotation[] NO_QUALIFIERS = new Annotation[0];\n\tprivate final SeContainer container;\n\n\t/**\n\t * @param container\n\t * \t\tContainer instance for bean management.\n\t */\n\tpublic Recaf(@Nonnull SeContainer container) {\n\t\tthis.container = container;\n\t}\n\n\t/**\n\t * @return The CDI container.\n\t */\n\tpublic SeContainer getContainer() {\n\t\treturn container;\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tType to get an {@link Instance} of.\n\t * @param <T>\n\t * \t\tInstance type.\n\t *\n\t * @return Instance accessor for bean of the given type.\n\t */\n\t@Nonnull\n\tpublic <T> Instance<T> instance(@Nonnull Class<T> type) {\n\t\treturn instance(type, NO_QUALIFIERS);\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tType to get an {@link Instance} of.\n\t * @param qualifiers\n\t * \t\tQualifiers to narrow down an option if multiple candidate instances exist for the type.\n\t * @param <T>\n\t * \t\tInstance type.\n\t *\n\t * @return Instance accessor for bean of the given type.\n\t */\n\t@Nonnull\n\tpublic <T> Instance<T> instance(@Nonnull Class<T> type, Annotation... qualifiers) {\n\t\treturn container.select(type, qualifiers);\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tType to get instance of.\n\t * @param <T>\n\t * \t\tInstance type.\n\t *\n\t * @return Instance of type <i>(Wrapped in a proxy)</i>.\n\t */\n\t@Nonnull\n\tpublic <T> T get(@Nonnull Class<T> type) {\n\t\treturn get(type, NO_QUALIFIERS);\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tType to get instance of.\n\t * @param qualifiers\n\t * \t\tQualifiers to narrow down an option if multiple candidate instances exist for the type.\n\t * @param <T>\n\t * \t\tInstance type.\n\t *\n\t * @return Instance of type <i>(Wrapped in a proxy)</i>.\n\t */\n\t@Nonnull\n\tpublic <T> T get(@Nonnull Class<T> type, Annotation... qualifiers) {\n\t\treturn instance(type, qualifiers).get();\n\t}\n\n\tstatic {\n\t\t// Enforce US locale just in case we have some string formatting susceptible to this \"feature\":\n\t\t//  https://mattryall.net/blog/the-infamous-turkish-locale-bug\n\t\tLocale.setDefault(Locale.US);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/RecafConstants.java",
    "content": "package software.coley.recaf;\n\nimport org.objectweb.asm.Opcodes;\n\n/**\n * Common constants.\n *\n * @author Matt Coley\n */\npublic final class RecafConstants {\n\tprivate RecafConstants() {\n\t}\n\n\t/**\n\t * @return Current ASM version.\n\t */\n\tpublic static int getAsmVersion() {\n\t\treturn Opcodes.ASM9;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/analytics/SystemInformation.java",
    "content": "package software.coley.recaf.analytics;\n\nimport jakarta.annotation.Nullable;\nimport org.slf4j.Logger;\n\nimport java.io.StringWriter;\nimport java.util.Map;\nimport java.util.TreeMap;\n\n/**\n * Common properties pulled at runtime from {@link System#getProperty(String)}\n * that are useful for tracking and logging.\n *\n * @author Matt Coley\n */\npublic class SystemInformation {\n\tprivate static final String KEY_OS_NAME = \"os.name\";\n\tprivate static final String KEY_OS_ARCH = \"os.arch\";\n\tprivate static final String KEY_OS_VERSION = \"os.version\";\n\tprivate static final String KEY_OS_ARCH_BITS = \"os.bitness\";\n\tprivate static final String KEY_OS_PROCESSORS = \"os.processors\";\n\tprivate static final String KEY_JAVA_VERSION = \"java.version\";\n\tprivate static final String KEY_JAVA_VM_NAME = \"java.vm.version\";\n\tprivate static final String KEY_JAVA_VM_VENDOR = \"java.vm.vendor\";\n\tprivate static final String KEY_JAVA_HOME = \"java.home\";\n\tpublic static final String OS_NAME = System.getProperty(KEY_OS_NAME);\n\tpublic static final String OS_ARCH = System.getProperty(KEY_OS_ARCH);\n\tpublic static final int OS_ARCH_BITS = determineBitness();\n\tpublic static final String OS_VERSION = System.getProperty(KEY_OS_VERSION);\n\tpublic static final String JAVA_VERSION = System.getProperty(KEY_JAVA_VERSION);\n\tpublic static final String JAVA_VM_NAME = System.getProperty(KEY_JAVA_VM_NAME);\n\tpublic static final String JAVA_VM_VENDOR = System.getProperty(KEY_JAVA_VM_VENDOR);\n\tpublic static final String JAVA_HOME = System.getProperty(KEY_JAVA_HOME);\n\tprivate static final Map<String, String> ALL_PROPERTIES = new TreeMap<>() {\n\t\t{\n\t\t\tput(KEY_OS_NAME, OS_NAME);\n\t\t\tput(KEY_OS_ARCH, OS_ARCH);\n\t\t\tput(KEY_OS_ARCH_BITS, String.valueOf(OS_ARCH_BITS));\n\t\t\tput(KEY_OS_PROCESSORS, String.valueOf(Runtime.getRuntime().availableProcessors()));\n\t\t\tput(KEY_OS_VERSION, OS_VERSION);\n\t\t\tput(KEY_JAVA_VERSION, JAVA_VERSION);\n\t\t\tput(KEY_JAVA_VM_NAME, JAVA_VM_NAME);\n\t\t\tput(KEY_JAVA_VM_VENDOR, JAVA_VM_VENDOR);\n\t\t\tput(KEY_JAVA_HOME, JAVA_HOME);\n\t\t}\n\t};\n\n\n\t/**\n\t * @return {@code 64} or {@code 32} dependent on the {@link #OS_ARCH}.\n\t */\n\tprivate static int determineBitness() {\n\t\t// Parse from specification value, which is commonly defined.\n\t\tString bitness = System.getProperty(\"sun.arch.data.model\", \"\");\n\t\tif (bitness.matches(\"[0-9]{2}\"))\n\t\t\treturn Integer.parseInt(bitness, 10);\n\t\t// Parse from IBM value, used on IBM releases.\n\t\tbitness = System.getProperty(\"com.ibm.vm.bitmode\", \"\");\n\t\tif (bitness.matches(\"[0-9]{2}\"))\n\t\t\treturn Integer.parseInt(bitness, 10);\n\t\treturn OS_ARCH.contains(\"64\") ? 64 : 32;\n\t}\n\n\t/**\n\t * Dump all properties into the given logger.\n\t *\n\t * @param logger\n\t * \t\tLogger to dump into.\n\t */\n\tpublic static void dump(@Nullable Logger logger) {\n\t\tif (logger != null)\n\t\t\tALL_PROPERTIES.forEach((key, value) ->\n\t\t\t\t\tlogger.debug(\"{} = {}\", key, value));\n\t}\n\n\t/**\n\t * Dump all properties into the given writer.\n\t *\n\t * @param writer\n\t * \t\tWriter to dump into.\n\t */\n\tpublic static void dump(@Nullable StringWriter writer) {\n\t\tif (writer != null)\n\t\t\tALL_PROPERTIES.forEach((key, value) ->\n\t\t\t\t\twriter.append(String.format(\"%s = %s\\n\", key, value)));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/analytics/logging/DebuggingLogger.java",
    "content": "package software.coley.recaf.analytics.logging;\n\nimport jakarta.annotation.Nonnull;\nimport org.slf4j.Logger;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\n\nimport java.util.function.Consumer;\n\n/**\n * Used for verbose logging that we normally would not want to capture due to excessiveness.\n * But in the case where we want to enable it for local testing, its available.\n *\n * @author Matt Coley\n */\n@ExcludeFromJacocoGeneratedReport(justification = \"Logging not relevant for test coverage\")\npublic interface DebuggingLogger extends Logger {\n\tboolean DEBUG = System.getenv(\"RECAF_DEBUG\") != null;\n\n\t/**\n\t * Only do the given action when manual debugging is enabled.\n\t *\n\t * @param action\n\t * \t\tCall onto self.\n\t */\n\tdefault void debugging(@Nonnull Consumer<DebuggingLogger> action) {\n\t\tif (DEBUG)\n\t\t\taction.accept(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/analytics/logging/InterceptingLogger.java",
    "content": "package software.coley.recaf.analytics.logging;\n\nimport jakarta.annotation.Nonnull;\nimport org.slf4j.Logger;\nimport org.slf4j.Marker;\nimport org.slf4j.event.Level;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\n\nimport java.util.regex.Matcher;\n\n/**\n * A forwarding logger that lets us intercept compiled messages.\n *\n * @author Matt Coley\n */\n@ExcludeFromJacocoGeneratedReport(justification = \"Logging not relevant for test coverage\")\npublic abstract class InterceptingLogger implements DebuggingLogger {\n\tprivate final Logger backing;\n\n\t/**\n\t * @param backing\n\t * \t\tBacking logger to send info to.\n\t */\n\tprotected InterceptingLogger(@Nonnull Logger backing) {\n\t\tthis.backing = backing;\n\t}\n\n\t/**\n\t * Intercept logging.\n\t *\n\t * @param level\n\t * \t\tLevel logged.\n\t * @param message\n\t * \t\tMessage logged.\n\t */\n\tpublic abstract void intercept(Level level, String message);\n\n\t/**\n\t * Intercept throwable logging.\n\t *\n\t * @param level\n\t * \t\tLevel logged.\n\t * @param message\n\t * \t\tMessage logged.\n\t * @param t\n\t * \t\tItem thrown.\n\t */\n\tpublic abstract void intercept(Level level, String message, Throwable t);\n\n\t@Override\n\tpublic String getName() {\n\t\treturn backing.getName();\n\t}\n\n\t@Override\n\tpublic boolean isTraceEnabled() {\n\t\treturn backing.isTraceEnabled();\n\t}\n\n\t@Override\n\tpublic boolean isTraceEnabled(Marker marker) {\n\t\treturn backing.isTraceEnabled(marker);\n\t}\n\n\t@Override\n\tpublic void trace(String msg) {\n\t\tbacking.trace(msg);\n\t\tif (isTraceEnabled()) intercept(Level.TRACE, msg);\n\t}\n\n\t@Override\n\tpublic void trace(String format, Object arg) {\n\t\ttrace(format, new Object[]{arg});\n\t}\n\n\t@Override\n\tpublic void trace(String format, Object arg1, Object arg2) {\n\t\ttrace(format, new Object[]{arg1, arg2});\n\t}\n\n\t@Override\n\tpublic void trace(String format, Object... arguments) {\n\t\tbacking.trace(format, arguments);\n\t\tif (isTraceEnabled()) intercept(Level.TRACE, compile(format, arguments));\n\t}\n\n\t@Override\n\tpublic void trace(String msg, Throwable t) {\n\t\tbacking.trace(msg, t);\n\t\tif (isTraceEnabled()) intercept(Level.TRACE, msg, t);\n\t}\n\n\t@Override\n\tpublic void trace(Marker marker, String msg) {\n\t\tbacking.trace(marker, msg);\n\t\tif (isTraceEnabled()) intercept(Level.TRACE, msg);\n\t}\n\n\t@Override\n\tpublic void trace(Marker marker, String format, Object arg) {\n\t\ttrace(marker, format, new Object[]{arg});\n\t}\n\n\t@Override\n\tpublic void trace(Marker marker, String format, Object arg1, Object arg2) {\n\t\ttrace(marker, format, new Object[]{arg1, arg2});\n\t}\n\n\t@Override\n\tpublic void trace(Marker marker, String format, Object... arguments) {\n\t\tbacking.trace(marker, format, arguments);\n\t\tif (isTraceEnabled()) intercept(Level.TRACE, compile(format, arguments));\n\t}\n\n\t@Override\n\tpublic void trace(Marker marker, String msg, Throwable t) {\n\t\tbacking.trace(marker, msg, t);\n\t\tif (isTraceEnabled()) intercept(Level.TRACE, msg, t);\n\t}\n\n\t@Override\n\tpublic boolean isDebugEnabled() {\n\t\treturn backing.isDebugEnabled();\n\t}\n\n\t@Override\n\tpublic boolean isDebugEnabled(Marker marker) {\n\t\treturn backing.isDebugEnabled(marker);\n\t}\n\n\t@Override\n\tpublic void debug(String msg) {\n\t\tbacking.debug(msg);\n\t\tif (isDebugEnabled()) intercept(Level.DEBUG, msg);\n\t}\n\n\t@Override\n\tpublic void debug(String format, Object arg) {\n\t\tdebug(format, new Object[]{arg});\n\t}\n\n\t@Override\n\tpublic void debug(String format, Object arg1, Object arg2) {\n\t\tdebug(format, new Object[]{arg1, arg2});\n\t}\n\n\t@Override\n\tpublic void debug(String format, Object... arguments) {\n\t\tbacking.debug(format, arguments);\n\t\tif (isDebugEnabled()) intercept(Level.DEBUG, compile(format, arguments));\n\t}\n\n\t@Override\n\tpublic void debug(String msg, Throwable t) {\n\t\tbacking.debug(msg, t);\n\t\tif (isDebugEnabled()) intercept(Level.DEBUG, msg, t);\n\t}\n\n\t@Override\n\tpublic void debug(Marker marker, String msg) {\n\t\tbacking.debug(marker, msg);\n\t\tif (isDebugEnabled()) intercept(Level.DEBUG, msg);\n\t}\n\n\t@Override\n\tpublic void debug(Marker marker, String format, Object arg) {\n\t\tdebug(marker, format, new Object[]{arg});\n\t}\n\n\t@Override\n\tpublic void debug(Marker marker, String format, Object arg1, Object arg2) {\n\t\tdebug(marker, format, new Object[]{arg1, arg2});\n\t}\n\n\t@Override\n\tpublic void debug(Marker marker, String format, Object... arguments) {\n\t\tbacking.debug(marker, format, arguments);\n\t\tif (isDebugEnabled()) intercept(Level.DEBUG, compile(format, arguments));\n\t}\n\n\t@Override\n\tpublic void debug(Marker marker, String msg, Throwable t) {\n\t\tbacking.debug(marker, msg, t);\n\t\tif (isDebugEnabled()) intercept(Level.DEBUG, msg, t);\n\t}\n\n\t@Override\n\tpublic boolean isInfoEnabled() {\n\t\treturn backing.isInfoEnabled();\n\t}\n\n\t@Override\n\tpublic boolean isInfoEnabled(Marker marker) {\n\t\treturn backing.isInfoEnabled(marker);\n\t}\n\n\t@Override\n\tpublic void info(String msg) {\n\t\tbacking.info(msg);\n\t\tif (isInfoEnabled()) intercept(Level.INFO, msg);\n\t}\n\n\t@Override\n\tpublic void info(String format, Object arg) {\n\t\tinfo(format, new Object[]{arg});\n\t}\n\n\t@Override\n\tpublic void info(String format, Object arg1, Object arg2) {\n\t\tinfo(format, new Object[]{arg1, arg2});\n\t}\n\n\t@Override\n\tpublic void info(String format, Object... arguments) {\n\t\tbacking.info(format, arguments);\n\t\tif (isInfoEnabled()) intercept(Level.INFO, compile(format, arguments));\n\t}\n\n\t@Override\n\tpublic void info(String msg, Throwable t) {\n\t\tbacking.info(msg, t);\n\t\tif (isInfoEnabled()) intercept(Level.INFO, msg, t);\n\t}\n\n\t@Override\n\tpublic void info(Marker marker, String msg) {\n\t\tbacking.info(marker, msg);\n\t\tif (isInfoEnabled()) intercept(Level.INFO, msg);\n\t}\n\n\t@Override\n\tpublic void info(Marker marker, String format, Object arg) {\n\t\tinfo(marker, format, new Object[]{arg});\n\t}\n\n\t@Override\n\tpublic void info(Marker marker, String format, Object arg1, Object arg2) {\n\t\tinfo(marker, format, new Object[]{arg1, arg2});\n\t}\n\n\t@Override\n\tpublic void info(Marker marker, String format, Object... arguments) {\n\t\tbacking.info(marker, format, arguments);\n\t\tif (isInfoEnabled()) intercept(Level.INFO, compile(format, arguments));\n\t}\n\n\t@Override\n\tpublic void info(Marker marker, String msg, Throwable t) {\n\t\tbacking.info(marker, msg, t);\n\t\tif (isInfoEnabled()) intercept(Level.INFO, msg, t);\n\t}\n\n\t@Override\n\tpublic boolean isWarnEnabled() {\n\t\treturn backing.isWarnEnabled();\n\t}\n\n\t@Override\n\tpublic boolean isWarnEnabled(Marker marker) {\n\t\treturn backing.isWarnEnabled(marker);\n\t}\n\n\t@Override\n\tpublic void warn(String msg) {\n\t\tbacking.warn(msg);\n\t\tif (isWarnEnabled()) intercept(Level.WARN, msg);\n\t}\n\n\t@Override\n\tpublic void warn(String format, Object arg) {\n\t\twarn(format, new Object[]{arg});\n\t}\n\n\t@Override\n\tpublic void warn(String format, Object arg1, Object arg2) {\n\t\twarn(format, new Object[]{arg1, arg2});\n\t}\n\n\t@Override\n\tpublic void warn(String format, Object... arguments) {\n\t\tbacking.warn(format, arguments);\n\t\tif (isWarnEnabled()) intercept(Level.WARN, compile(format, arguments));\n\t}\n\n\t@Override\n\tpublic void warn(String msg, Throwable t) {\n\t\tbacking.warn(msg, t);\n\t\tif (isWarnEnabled()) intercept(Level.WARN, msg, t);\n\t}\n\n\t@Override\n\tpublic void warn(Marker marker, String msg) {\n\t\tbacking.warn(marker, msg);\n\t\tif (isWarnEnabled()) intercept(Level.WARN, msg);\n\t}\n\n\t@Override\n\tpublic void warn(Marker marker, String format, Object arg) {\n\t\twarn(marker, format, new Object[]{arg});\n\t}\n\n\t@Override\n\tpublic void warn(Marker marker, String format, Object arg1, Object arg2) {\n\t\twarn(marker, format, new Object[]{arg1, arg2});\n\t}\n\n\t@Override\n\tpublic void warn(Marker marker, String format, Object... arguments) {\n\t\tbacking.warn(marker, format, arguments);\n\t\tif (isWarnEnabled()) intercept(Level.WARN, compile(format, arguments));\n\t}\n\n\t@Override\n\tpublic void warn(Marker marker, String msg, Throwable t) {\n\t\tbacking.warn(marker, msg, t);\n\t\tif (isWarnEnabled()) intercept(Level.WARN, msg, t);\n\t}\n\n\t@Override\n\tpublic boolean isErrorEnabled() {\n\t\treturn backing.isErrorEnabled();\n\t}\n\n\t@Override\n\tpublic boolean isErrorEnabled(Marker marker) {\n\t\treturn backing.isErrorEnabled(marker);\n\t}\n\n\t@Override\n\tpublic void error(String msg) {\n\t\tbacking.error(msg);\n\t\tif (isErrorEnabled()) intercept(Level.ERROR, msg);\n\t}\n\n\t@Override\n\tpublic void error(String format, Object arg) {\n\t\terror(format, new Object[]{arg});\n\t}\n\n\t@Override\n\tpublic void error(String format, Object arg1, Object arg2) {\n\t\terror(format, new Object[]{arg1, arg2});\n\t}\n\n\t@Override\n\tpublic void error(String format, Object... arguments) {\n\t\tbacking.error(format, arguments);\n\t\tif (isErrorEnabled()) intercept(Level.ERROR, compile(format, arguments));\n\t}\n\n\t@Override\n\tpublic void error(String msg, Throwable t) {\n\t\tbacking.error(msg, t);\n\t\tif (isErrorEnabled()) intercept(Level.ERROR, msg, t);\n\t}\n\n\t@Override\n\tpublic void error(Marker marker, String msg) {\n\t\tbacking.error(marker, msg);\n\t\tif (isErrorEnabled()) intercept(Level.ERROR, msg);\n\t}\n\n\t@Override\n\tpublic void error(Marker marker, String format, Object arg) {\n\t\terror(marker, format, new Object[]{arg});\n\t}\n\n\t@Override\n\tpublic void error(Marker marker, String format, Object arg1, Object arg2) {\n\t\terror(marker, format, new Object[]{arg1, arg2});\n\t}\n\n\t@Override\n\tpublic void error(Marker marker, String format, Object... arguments) {\n\t\tbacking.error(marker, format, arguments);\n\t\tif (isErrorEnabled()) intercept(Level.ERROR, compile(format, arguments));\n\t}\n\n\t@Override\n\tpublic void error(Marker marker, String msg, Throwable t) {\n\t\tbacking.error(marker, msg, t);\n\t\tif (isErrorEnabled()) intercept(Level.ERROR, msg, t);\n\t}\n\n\tprivate static String compile(String message, Object[] arguments) {\n\t\tint i = 0;\n\t\twhile (message.contains(\"{}\")) {\n\t\t\t// Failsafe, shouldn't occur if logging is written correctly\n\t\t\tif (i == arguments.length)\n\t\t\t\treturn message;\n\t\t\t// Replace arg in pattern\n\t\t\tObject arg = arguments[i];\n\t\t\tString argStr = arg == null ? \"null\" : arg.toString();\n\t\t\tmessage = message.replaceFirst(\"\\\\{}\", Matcher.quoteReplacement(argStr));\n\t\t\ti++;\n\t\t}\n\t\treturn message;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/analytics/logging/LogConsumer.java",
    "content": "package software.coley.recaf.analytics.logging;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.slf4j.event.Level;\n\n/**\n * Triple-argument consumer for taking in log messages.\n *\n * @param <T>\n * \t\tLog content.\n *\n * @author Matt Coley\n */\npublic interface LogConsumer<T> {\n\t/**\n\t * @param loggerName\n\t * \t\tName of logger message applies to.\n\t * @param level\n\t * \t\tLog level of message.\n\t * @param messageContent\n\t * \t\tContent of message.\n\t */\n\tvoid accept(@Nonnull String loggerName, @Nonnull Level level, @Nullable T messageContent);\n\n\t/**\n\t * @param loggerName\n\t * \t\tName of logger message applies to.\n\t * @param level\n\t * \t\tLog level of message.\n\t * @param messageContent\n\t * \t\tContent of message.\n\t * @param throwable\n\t * \t\tAssociated thrown exception.\n\t */\n\tvoid accept(@Nonnull String loggerName, @Nonnull Level level, @Nullable T messageContent, @Nullable Throwable throwable);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/analytics/logging/Logging.java",
    "content": "package software.coley.recaf.analytics.logging;\n\nimport ch.qos.logback.classic.LoggerContext;\nimport ch.qos.logback.classic.encoder.PatternLayoutEncoder;\nimport ch.qos.logback.core.FileAppender;\nimport jakarta.annotation.Nonnull;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.slf4j.event.Level;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\n\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NavigableSet;\nimport java.util.TreeSet;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\nimport static org.slf4j.LoggerFactory.getLogger;\n\n/**\n * {@link LoggerFactory} wrapper that lets us intercept all logged messages.\n *\n * @author Matt Coley\n */\n@ExcludeFromJacocoGeneratedReport(justification = \"Logging not relevant for test coverage\")\npublic class Logging {\n\tprivate static final Map<String, DebuggingLogger> loggers = new ConcurrentHashMap<>();\n\tprivate static final NavigableSet<String> loggerKeys = Collections.synchronizedNavigableSet(new TreeSet<>());\n\tprivate static final List<LogConsumer<String>> logConsumers = new CopyOnWriteArrayList<>();\n\tprivate static Level interceptLevel = Level.INFO;\n\n\t/**\n\t * @return Set of the current logger keys.\n\t */\n\t@Nonnull\n\tpublic static NavigableSet<String> loggerKeys() {\n\t\t// We track the keys in this separate set so that we can retrieve them\n\t\t// in sorted order without needing to wrap in 'new TreeSet' every time.\n\t\treturn Collections.unmodifiableNavigableSet(loggerKeys);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tLogger name.\n\t *\n\t * @return Logger associated with name.\n\t */\n\t@Nonnull\n\tpublic static DebuggingLogger get(@Nonnull String name) {\n\t\treturn loggers.computeIfAbsent(name, k -> intercept(k, getLogger(k)));\n\t}\n\n\t/**\n\t * @param cls\n\t * \t\tLogger class key.\n\t *\n\t * @return Logger associated with class.\n\t */\n\t@Nonnull\n\tpublic static DebuggingLogger get(@Nonnull Class<?> cls) {\n\t\treturn loggers.computeIfAbsent(cls.getName(), k -> intercept(k, getLogger(k)));\n\t}\n\n\t/**\n\t * @param consumer\n\t * \t\tNew log message consumer.\n\t */\n\tpublic static void addLogConsumer(@Nonnull LogConsumer<String> consumer) {\n\t\tlogConsumers.add(consumer);\n\t}\n\n\t/**\n\t * @param consumer\n\t * \t\tLog message consumer to remove.\n\t */\n\tpublic static void removeLogConsumer(@Nonnull LogConsumer<String> consumer) {\n\t\tlogConsumers.remove(consumer);\n\t}\n\n\t/**\n\t * Sets the target level for log interception. This affects what messages {@link LogConsumer}s receive.\n\t *\n\t * @param level\n\t * \t\tNew target level.\n\t */\n\tpublic static void setInterceptLevel(@Nonnull Level level) {\n\t\tinterceptLevel = level;\n\t}\n\n\t/**\n\t * Registers a file appender for all log calls.\n\t *\n\t * @param path\n\t * \t\tPath to file to append to.\n\t */\n\t@SuppressWarnings({\"unchecked\", \"rawtypes\"})\n\tpublic static void addFileAppender(@Nonnull Path path) {\n\t\t// We do it this way so the file path can be set at runtime.\n\t\tLoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();\n\t\tFileAppender fileAppender = new FileAppender<>();\n\t\tfileAppender.addFilter(new RecafLoggingFilter(ch.qos.logback.classic.Level.ALL));\n\t\tfileAppender.setFile(path.toString());\n\t\tfileAppender.setContext(loggerContext);\n\t\tfileAppender.setPrudent(true);\n\t\tfileAppender.setAppend(true);\n\t\tfileAppender.setImmediateFlush(true);\n\n\t\t// Pattern\n\t\tPatternLayoutEncoder encoder = new PatternLayoutEncoder();\n\t\tencoder.setContext(loggerContext);\n\t\tencoder.setPattern(\"%d{HH:mm:ss.SSS} [%logger{0}/%thread] %-5level: %msg%n\");\n\t\tencoder.start();\n\t\tfileAppender.setEncoder(encoder);\n\n\t\t// Start file appender\n\t\tfileAppender.start();\n\n\t\t// Create logger\n\t\tch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger)\n\t\t\t\tLoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);\n\t\tlogbackLogger.addAppender(fileAppender);\n\t\tlogbackLogger.setAdditive(false);\n\t}\n\n\t@Nonnull\n\tprivate static DebuggingLogger intercept(@Nonnull String name, @Nonnull Logger logger) {\n\t\tloggerKeys.add(name);\n\t\treturn new InterceptingLogger(logger) {\n\t\t\t@Override\n\t\t\tpublic void intercept(@Nonnull Level level, String message) {\n\t\t\t\tif (interceptLevel.toInt() <= level.toInt())\n\t\t\t\t\tlogConsumers.forEach(consumer -> consumer.accept(name, level, message));\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void intercept(@Nonnull Level level, String message, Throwable t) {\n\t\t\t\tif (interceptLevel.toInt() <= level.toInt())\n\t\t\t\t\tlogConsumers.forEach(consumer -> consumer.accept(name, level, message, t));\n\t\t\t}\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/analytics/logging/RecafLoggingFilter.java",
    "content": "package software.coley.recaf.analytics.logging;\n\nimport ch.qos.logback.classic.Level;\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport ch.qos.logback.core.filter.Filter;\nimport ch.qos.logback.core.spi.FilterReply;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\n\nimport java.util.Objects;\nimport java.util.function.Supplier;\n\n/**\n * Logging filter impl that only allows Recaf logger calls.\n *\n * @author Matt Coley\n */\n@ExcludeFromJacocoGeneratedReport(justification = \"Logging not relevant for test coverage\")\npublic class RecafLoggingFilter extends Filter<ILoggingEvent> {\n\t/** Shared default level - used by auto-created instances of this filter. */\n\tpublic static Level defaultLevel = Level.TRACE;\n\t/** Instance supplier of the logging level for this filter. */\n\tprivate final Supplier<Level> instanceLevel;\n\n\t/**\n\t * No-args constructor for auto-created instances.\n\t * Will delegate the level to {@link #defaultLevel}.\n\t */\n\tpublic RecafLoggingFilter() {\n\t\tinstanceLevel = () -> defaultLevel;\n\t}\n\n\t/**\n\t * Constructor for intentionally made use cases which\n\t * want to control the logging level of output.\n\t *\n\t * @param level\n\t * \t\tLevel for this filter instance.\n\t */\n\tpublic RecafLoggingFilter(@Nullable Level level) {\n\t\tinstanceLevel = () -> Objects.requireNonNullElse(level, Level.TRACE);\n\t}\n\n\t@Override\n\tpublic FilterReply decide(@Nonnull ILoggingEvent event) {\n\t\tLevel level = event.getLevel();\n\t\tif (instanceLevel.get().isGreaterOrEqual(level))\n\t\t\treturn FilterReply.DENY;\n\t\tString loggerName = event.getLoggerName();\n\t\tif (loggerName.startsWith(\"software.coley.\") || Logging.loggerKeys().contains(loggerName))\n\t\t\treturn FilterReply.ACCEPT;\n\t\treturn FilterReply.DENY;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/behavior/Closing.java",
    "content": "package software.coley.recaf.behavior;\n\n/**\n * Type is closable.\n *\n * @author Matt Coley\n */\npublic interface Closing {\n\t/**\n\t * Called to close the item.\n\t */\n\tvoid close();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/behavior/PriorityKeys.java",
    "content": "package software.coley.recaf.behavior;\n\n/**\n * Default keys for {@link PrioritySortable} implementations.\n * <p>\n * Searching for usages of these keys will show what listeners/classes fire in which order.\n *\n * @author Matt Coley\n */\npublic final class PriorityKeys {\n\tpublic static final int EARLIEST = -1000;\n\tpublic static final int EARLIER = -100;\n\tpublic static final int EARLY = -10;\n\tpublic static final int DEFAULT = 0;\n\tpublic static final int LATE = 10;\n\tpublic static final int LATER = 100;\n\tpublic static final int LATEST = 1000;\n\n\tprivate PriorityKeys() {}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/behavior/PrioritySortable.java",
    "content": "package software.coley.recaf.behavior;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.collections.Lists;\n\nimport java.util.List;\n\n/**\n * Priority sortable item.\n *\n * @author Matt Coley\n */\npublic interface PrioritySortable extends Comparable<PrioritySortable> {\n\t/**\n\t * @return This item's priority value.\n\t * Negative values have higher priority.\n\t * Positive values have lower priority.\n\t *\n\t * @see PriorityKeys\n\t */\n\tdefault int getPriority() {\n\t\t// Everything will default to '0' and the order of items is not guaranteed.\n\t\t//\n\t\t// The idea is that most things do not need a guaranteed run order, but for the few edge cases that do\n\t\t// those specific cases will use higher/lower values to be moved to the front/end of the sorted list.\n\t\treturn PriorityKeys.DEFAULT;\n\t}\n\n\t@Override\n\tdefault int compareTo(@Nonnull PrioritySortable o) {\n\t\treturn Integer.compare(getPriority(), o.getPriority());\n\t}\n\n\t/**\n\t * Add a sortable item to a sortable list.\n\t *\n\t * @param items\n\t * \t\tList to add to.\n\t * @param item\n\t * \t\tItem to add.\n\t * @param <T>\n\t * \t\tChild priority-sortable type.\n\t *\n\t * @return {@code true} on add. {@code false} on failure <i>(Insertion index cannot be computed)</i>,\n\t */\n\tstatic <T extends PrioritySortable> boolean add(@Nonnull List<T> items, @Nonnull T item) {\n\t\treturn Lists.sortedInsert(PrioritySortable::compareTo, items, item);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/cdi/EagerInitialization.java",
    "content": "package software.coley.recaf.cdi;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.event.Observes;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Applied to beans to enable eager initialization, which is to say they and their dependencies get created as soon as\n * possible depending on the {@link #value() value of the intended} {@link InitializationStage}. This will result in\n * the bean's {@code @Inject} annotated constructor being called.\n * <p>\n * Alternatively, you could also observe the events {@link InitializationEvent} or {@link UiInitializationEvent}\n * in a method with {@link Observes}. This would allow you to separate the initialization logic from the constructor\n * and have it reside in a separate method.\n * <p>\n * <b>NOTE:</b> Beans are not eagerly initialized while in a test environment.\n *\n * @author Matt Coley\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.TYPE)\npublic @interface EagerInitialization {\n\t/**\n\t * Determines when to run early initialization.\n\t * <br>\n\t * Changing this value is mostly applicable to {@link ApplicationScoped} beans.\n\t * Having the value set to {@link InitializationStage#IMMEDIATE} will result in the bean and <i>all</i> of\n\t * its dependencies being created as soon as the application begins. For beans dealing with UI capabilities this\n\t * will likely lead to problems. For those situations, use {@link InitializationStage#AFTER_UI_INIT} to delay\n\t * initialization until after the UI has been populated.\n\t *\n\t * @return When the initialization should occur.\n\t */\n\tInitializationStage value() default InitializationStage.IMMEDIATE;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/cdi/EagerInitializationExtension.java",
    "content": "package software.coley.recaf.cdi;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.event.Observes;\nimport jakarta.enterprise.inject.spi.Annotated;\nimport jakarta.enterprise.inject.spi.Bean;\nimport jakarta.enterprise.inject.spi.BeanManager;\nimport jakarta.enterprise.inject.spi.Extension;\nimport jakarta.enterprise.inject.spi.ProcessBean;\nimport jakarta.inject.Inject;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Extension to force creation of {@link EagerInitialization} annotated beans without the need to\n * {@link Inject} and reference them externally.\n *\n * @author Matt Coley\n */\npublic class EagerInitializationExtension implements Extension {\n\tprivate static final EagerInitializationExtension INSTANCE = new EagerInitializationExtension();\n\tprivate static final List<Bean<?>> applicationScopedEagerBeans = new ArrayList<>();\n\tprivate static final List<Bean<?>> applicationScopedEagerBeansForUi = new ArrayList<>();\n\tprivate static BeanManager beanManager;\n\n\tprivate EagerInitializationExtension() {\n\t}\n\n\t/**\n\t * @return Extension singleton.\n\t */\n\t@Nonnull\n\tpublic static EagerInitializationExtension getInstance() {\n\t\treturn INSTANCE;\n\t}\n\n\t/**\n\t * @return Application scoped {@link EagerInitialization} beans.\n\t */\n\t@Nonnull\n\tpublic static List<Bean<?>> getApplicationScopedEagerBeans() {\n\t\treturn applicationScopedEagerBeans;\n\t}\n\n\t/**\n\t * @return Application scoped {@link EagerInitialization} beans which will wait for the UI to be initialized before being initialized.\n\t */\n\t@Nonnull\n\tpublic static List<Bean<?>> getApplicationScopedEagerBeansForUi() {\n\t\treturn applicationScopedEagerBeansForUi;\n\t}\n\n\t/**\n\t * Called when a bean is discovered and processed.\n\t * We will record eager beans here so that we can initialize them later.\n\t *\n\t * @param event\n\t * \t\tCDI bean process event.\n\t */\n\tpublic void onProcessBean(@Observes ProcessBean<?> event) {\n\t\tAnnotated annotated = event.getAnnotated();\n\t\tEagerInitialization eager = annotated.getAnnotation(EagerInitialization.class);\n\t\tif (eager != null && annotated.isAnnotationPresent(ApplicationScoped.class)) {\n\t\t\tif (eager.value() == InitializationStage.IMMEDIATE)\n\t\t\t\tapplicationScopedEagerBeans.add(event.getBean());\n\t\t\telse if (eager.value() == InitializationStage.AFTER_UI_INIT)\n\t\t\t\tapplicationScopedEagerBeansForUi.add(event.getBean());\n\t\t}\n\t}\n\n\t/**\n\t * Called when Recaf initializes the CDI container, and after plugins are loaded.\n\t *\n\t * @param event\n\t * \t\tRecaf initialization event.\n\t * @param beanManager\n\t * \t\tCDI bean manager.\n\t */\n\tpublic void onInitialize(@Observes InitializationEvent event, @Nonnull BeanManager beanManager) {\n\t\tEagerInitializationExtension.beanManager = beanManager;\n\t\tfor (Bean<?> bean : applicationScopedEagerBeans)\n\t\t\tcreate(bean);\n\t}\n\n\t/**\n\t * Called when the UI is populated.\n\t * This obviously means that this only gets called when running from the UI module.\n\t *\n\t * @param event\n\t * \t\tUI initialization event.\n\t * @param beanManager\n\t * \t\tCDI bean manager.\n\t */\n\tpublic void onUiInitialize(@Observes UiInitializationEvent event, @Nonnull BeanManager beanManager) {\n\t\tEagerInitializationExtension.beanManager = beanManager;\n\t\tfor (Bean<?> bean : applicationScopedEagerBeansForUi)\n\t\t\tcreate(bean);\n\t}\n\n\tstatic void create(@Nonnull Bean<?> bean) {\n\t\t// NOTE: Calling toString() triggers the bean's proxy to the real implementation to initialize it.\n\t\t// We have a null check here because under some test environments this may trigger without being set (see above)\n\t\tif (beanManager != null)\n\t\t\tbeanManager.getReference(bean, bean.getBeanClass(), beanManager.createCreationalContext(bean)).toString();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/cdi/InitializationEvent.java",
    "content": "package software.coley.recaf.cdi;\n\n/**\n * Empty type, used to notify CDI consumers observing this type that the application has been launched.\n *\n * @author Matt Coley\n * @see UiInitializationEvent Alternative which waits for the UI to initialize.\n */\npublic class InitializationEvent {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/cdi/InitializationStage.java",
    "content": "package software.coley.recaf.cdi;\n\n/**\n * Initialization stage for {@link EagerInitialization#value()}.\n *\n * @author Matt Coley\n * @see EagerInitialization\n */\npublic enum InitializationStage {\n\t/**\n\t * Occurs as soon as possible.\n\t */\n\tIMMEDIATE,\n\t/**\n\t * Occurs after the UI is initialized.\n\t */\n\tAFTER_UI_INIT\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/cdi/UiInitializationEvent.java",
    "content": "package software.coley.recaf.cdi;\n\n/**\n * Empty type, used to notify CDI consumers observing this type that the UI has been populated.\n *\n * @author Matt Coley\n * @see InitializationEvent Alternative which runs before the UI is initialized.\n */\npublic class UiInitializationEvent {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/config/BasicCollectionConfigValue.java",
    "content": "package software.coley.recaf.config;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.observables.ObservableCollection;\n\nimport java.util.Collection;\n\n/**\n * Basic implementation of {@link ConfigCollectionValue}.\n *\n * @param <T>\n * \t\tValue type.\n * @param <C>\n * \t\tCollection type.\n *\n * @author Matt Coley\n */\npublic class BasicCollectionConfigValue<T, C extends Collection<T>> implements ConfigCollectionValue<T, C> {\n\tprivate final String key;\n\tprivate final Class<C> collectionType;\n\tprivate final Class<T> itemType;\n\tprivate final ObservableCollection<T, C> observable;\n\tprivate final boolean hidden;\n\n\t/**\n\t * @param key\n\t * \t\tValue key.\n\t * @param type\n\t * \t\tValue type class.\n\t * @param observable\n\t * \t\tObservable of value.\n\t */\n\t@SuppressWarnings(\"rawtypes\")\n\tpublic BasicCollectionConfigValue(@Nonnull String key,\n\t                                  @Nonnull Class<? extends Collection> type,\n\t                                  @Nonnull Class<T> itemType,\n\t                                  @Nonnull ObservableCollection<T, C> observable) {\n\t\tthis(key, type, itemType, observable, false);\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tValue key.\n\t * @param type\n\t * \t\tValue type class.\n\t * @param observable\n\t * \t\tObservable of value.\n\t * @param hidden\n\t * \t\tHidden flag.\n\t */\n\t@SuppressWarnings({\"rawtypes\", \"unchecked\"})\n\tpublic BasicCollectionConfigValue(@Nonnull String key,\n\t                                  @Nonnull Class<? extends Collection> type,\n\t                                  @Nonnull Class<T> itemType,\n\t                                  @Nonnull ObservableCollection<T, C> observable,\n\t                                  boolean hidden) {\n\t\tthis.key = key;\n\t\tthis.collectionType = (Class<C>) type;\n\t\tthis.itemType = itemType;\n\t\tthis.observable = observable;\n\t\tthis.hidden = hidden;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getId() {\n\t\treturn key;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Class<C> getType() {\n\t\treturn collectionType;\n\t}\n\n\t@Override\n\tpublic Class<T> getItemType() {\n\t\treturn itemType;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ObservableCollection<T, C> getObservable() {\n\t\treturn observable;\n\t}\n\n\t@Override\n\tpublic boolean isHidden() {\n\t\treturn hidden;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tBasicCollectionConfigValue<?, ?> other = (BasicCollectionConfigValue<?, ?>) o;\n\n\t\tif (!key.equals(other.key)) return false;\n\t\tif (!collectionType.equals(other.collectionType)) return false;\n\t\tif (!itemType.equals(other.itemType)) return false;\n\t\treturn observable.equals(other.observable);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = key.hashCode();\n\t\tresult = 31 * result + collectionType.hashCode();\n\t\tresult = 31 * result + itemType.hashCode();\n\t\tresult = 31 * result + observable.hashCode();\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"BasicCollectionConfigValue{\" +\n\t\t\t\t\"key='\" + key + '\\'' +\n\t\t\t\t\", collectionType=\" + collectionType +\n\t\t\t\t\", itemType=\" + itemType +\n\t\t\t\t'}';\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/config/BasicConfigContainer.java",
    "content": "package software.coley.recaf.config;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Map;\nimport java.util.TreeMap;\n\n/**\n * Basic implementation of {@link ConfigContainer}\n *\n * @author Matt Coley\n */\npublic class BasicConfigContainer implements ConfigContainer {\n\tprivate final Map<String, ConfigValue<?>> configMap = new TreeMap<>();\n\tprivate final String group;\n\tprivate final String id;\n\n\t/**\n\t * @param group\n\t * \t\tContainer group.\n\t * @param id\n\t * \t\tContainer ID.\n\t */\n\tpublic BasicConfigContainer(@Nonnull String group, @Nonnull String id) {\n\t\tthis.group = group;\n\t\tthis.id = id;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to add.\n\t */\n\tprotected void addValue(@Nonnull ConfigValue<?> value) {\n\t\tconfigMap.put(value.getId(), value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getGroup() {\n\t\treturn group;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getId() {\n\t\treturn id;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, ConfigValue<?>> getValues() {\n\t\treturn configMap;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tBasicConfigContainer that = (BasicConfigContainer) o;\n\n\t\tif (!configMap.equals(that.configMap)) return false;\n\t\treturn id.equals(that.id);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = configMap.hashCode();\n\t\tresult = 31 * result + id.hashCode();\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/config/BasicConfigValue.java",
    "content": "package software.coley.recaf.config;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.observables.Observable;\n\n/**\n * Basic implementation of {@link ConfigValue}.\n *\n * @param <T>\n * \t\tValue type.\n *\n * @author Matt Coley\n */\npublic class BasicConfigValue<T> implements ConfigValue<T> {\n\tprivate final String key;\n\tprivate final Class<T> type;\n\tprivate final Observable<T> observable;\n\tprivate final boolean hidden;\n\n\t/**\n\t * @param key\n\t * \t\tValue key.\n\t * @param type\n\t * \t\tValue type class.\n\t * @param observable\n\t * \t\tObservable of value.\n\t */\n\tpublic BasicConfigValue(String key, Class<T> type, Observable<T> observable) {\n\t\tthis(key, type, observable, false);\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tValue key.\n\t * @param type\n\t * \t\tValue type class.\n\t * @param observable\n\t * \t\tObservable of value.\n\t * @param hidden\n\t * \t\tHidden flag.\n\t */\n\tpublic BasicConfigValue(String key, Class<T> type, Observable<T> observable, boolean hidden) {\n\t\tthis.key = key;\n\t\tthis.type = type;\n\t\tthis.observable = observable;\n\t\tthis.hidden = hidden;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getId() {\n\t\treturn key;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Class<T> getType() {\n\t\treturn type;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Observable<T> getObservable() {\n\t\treturn observable;\n\t}\n\n\t@Override\n\tpublic boolean isHidden() {\n\t\treturn hidden;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tBasicConfigValue<?> other = (BasicConfigValue<?>) o;\n\n\t\tif (!key.equals(other.key)) return false;\n\t\treturn type.equals(other.type);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = key.hashCode();\n\t\tresult = 31 * result + type.hashCode();\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"BasicConfigValue{\" +\n\t\t\t\t\"key='\" + key + '\\'' +\n\t\t\t\t\", type=\" + type +\n\t\t\t\t'}';\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/config/BasicMapConfigValue.java",
    "content": "package software.coley.recaf.config;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.observables.ObservableMap;\n\nimport java.util.Map;\n\n/**\n * Basic implementation of {@link ConfigMapValue}.\n *\n * @param <K>\n * \t\tMap key type.\n * @param <V>\n * \t\tMap value type.\n * @param <M>\n * \t\tMap type.\n *\n * @author Matt Coley\n */\npublic class BasicMapConfigValue<K, V, M extends Map<K, V>> implements ConfigMapValue<K, V, M> {\n\tprivate final String key;\n\tprivate final Class<K> keyType;\n\tprivate final Class<V> valueType;\n\tprivate final Class<M> mapType;\n\tprivate final ObservableMap<K, V, M> observable;\n\tprivate final boolean hidden;\n\n\t/**\n\t * @param key\n\t * \t\tValue key.\n\t * @param type\n\t * \t\tValue type class.\n\t * @param observable\n\t * \t\tObservable of value.\n\t */\n\t@SuppressWarnings(\"rawtypes\")\n\tpublic BasicMapConfigValue(String key,\n\t                           Class<? extends Map> type,\n\t                           Class<K> keyType,\n\t                           Class<V> valueType,\n\t                           ObservableMap<K, V, M> observable) {\n\t\tthis(key, type, keyType, valueType, observable, false);\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tValue key.\n\t * @param type\n\t * \t\tValue type class.\n\t * @param observable\n\t * \t\tObservable of value.\n\t * @param hidden\n\t * \t\tHidden flag.\n\t */\n\t@SuppressWarnings({\"rawtypes\", \"unchecked\"})\n\tpublic BasicMapConfigValue(String key,\n\t                           Class<? extends Map> type,\n\t                           Class<K> keyType,\n\t                           Class<V> valueType,\n\t                           ObservableMap<K, V, M> observable,\n\t                           boolean hidden) {\n\t\tthis.key = key;\n\t\tthis.mapType = (Class<M>) type;\n\t\tthis.keyType = keyType;\n\t\tthis.valueType = valueType;\n\t\tthis.observable = observable;\n\t\tthis.hidden = hidden;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getId() {\n\t\treturn key;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Class<M> getType() {\n\t\treturn mapType;\n\t}\n\n\t@Override\n\tpublic Class<K> getKeyType() {\n\t\treturn keyType;\n\t}\n\n\t@Override\n\tpublic Class<V> getValueType() {\n\t\treturn valueType;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ObservableMap<K, V, M> getObservable() {\n\t\treturn observable;\n\t}\n\n\t@Override\n\tpublic boolean isHidden() {\n\t\treturn hidden;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tBasicMapConfigValue<?, ?, ?> that = (BasicMapConfigValue<?, ?, ?>) o;\n\n\t\tif (hidden != that.hidden) return false;\n\t\tif (!key.equals(that.key)) return false;\n\t\tif (!keyType.equals(that.keyType)) return false;\n\t\tif (!valueType.equals(that.valueType)) return false;\n\t\tif (!mapType.equals(that.mapType)) return false;\n\t\treturn observable.equals(that.observable);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = key.hashCode();\n\t\tresult = 31 * result + keyType.hashCode();\n\t\tresult = 31 * result + valueType.hashCode();\n\t\tresult = 31 * result + mapType.hashCode();\n\t\tresult = 31 * result + observable.hashCode();\n\t\tresult = 31 * result + (hidden ? 1 : 0);\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"BasicMapConfigValue{\" +\n\t\t\t\t\"key='\" + key + '\\'' +\n\t\t\t\t\", keyType=\" + keyType +\n\t\t\t\t\", valueType=\" + valueType +\n\t\t\t\t\", mapType=\" + mapType +\n\t\t\t\t\", observable=\" + observable +\n\t\t\t\t\", hidden=\" + hidden +\n\t\t\t\t'}';\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/config/ConfigCollectionValue.java",
    "content": "package software.coley.recaf.config;\n\nimport java.util.Collection;\n\n/**\n * An option stored in a {@link ConfigContainer} object representing a collection.\n *\n * @param <T>\n * \t\tCollection value type.\n * @param <C>\n * \t\tCollection type.\n *\n * @author Matt Coley\n */\npublic interface ConfigCollectionValue<T, C extends Collection<T>> extends ConfigValue<C> {\n\t/**\n\t * @return Collection type.\n\t */\n\tClass<T> getItemType();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/config/ConfigContainer.java",
    "content": "package software.coley.recaf.config;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\n\nimport java.util.Map;\n\nimport static software.coley.recaf.config.ConfigGroups.PACKAGE_SPLIT;\n\n/**\n * Configurable object. Implementations will almost always want to use {@link ApplicationScoped} for their scope.\n * This is so that the config container is shared between <i>all</i> instances of the item it is a config for.\n *\n * @author Matt Coley.\n * @see ConfigValue Values within this container.\n * @see ConfigGroups Values for {@link #getGroup()}.\n */\npublic interface ConfigContainer {\n\tString CONFIG_SUFFIX = \"-config\";\n\n\t/**\n\t * @return Group ID the container belongs to.\n\t *\n\t * @see ConfigGroups For constant values.\n\t */\n\t@Nonnull\n\tString getGroup();\n\n\t/**\n\t * The unique ID of this container should be <i>globally</i> unique.\n\t * The {@link #getGroup() group} does not act as an identifier prefix.\n\t *\n\t * @return Unique ID of this container.\n\t */\n\t@Nonnull\n\tString getId();\n\n\t/**\n\t * @return Combined {@link #getGroup()} and {@link #getId()}.\n\t */\n\t@Nonnull\n\tdefault String getGroupAndId() {\n\t\treturn getGroup() + PACKAGE_SPLIT + getId();\n\t}\n\n\t/**\n\t * @return Values in the container.\n\t */\n\t@Nonnull\n\tMap<String, ConfigValue<?>> getValues();\n\n\t/**\n\t * @param value\n\t * \t\tValue to get path of.\n\t *\n\t * @return Full path, scoped to this container.\n\t */\n\t@Nonnull\n\tdefault String getScopedId(ConfigValue<?> value) {\n\t\treturn getGroupAndId() + PACKAGE_SPLIT + value.getId();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/config/ConfigGroups.java",
    "content": "package software.coley.recaf.config;\n\nimport software.coley.recaf.services.Service;\n\n/**\n * Constants for {@link ConfigContainer#getGroup()}.\n *\n * @author Matt Coley\n */\npublic final class ConfigGroups {\n\t/**\n\t * Used to split group into sections.\n\t */\n\tpublic static final String PACKAGE_SPLIT = \".\";\n\t/**\n\t * Group base for {@link Service} classes.\n\t */\n\tpublic static final String SERVICE = \"service\";\n\t/**\n\t * Group for analyzing components.\n\t */\n\tpublic static final String SERVICE_ANALYSIS = SERVICE + PACKAGE_SPLIT + \"analysis\";\n\t/**\n\t * Group for assembler components.\n\t */\n\tpublic static final String SERVICE_ASSEMBLER = SERVICE + PACKAGE_SPLIT + \"assembler\";\n\t/**\n\t * Group for compiler components.\n\t */\n\tpublic static final String SERVICE_COMPILE = SERVICE + PACKAGE_SPLIT + \"compile\";\n\t/**\n\t * Group for debug/attach components.\n\t */\n\tpublic static final String SERVICE_DEBUG = SERVICE + PACKAGE_SPLIT + \"debug\";\n\t/**\n\t * Group for decompilation components.\n\t */\n\tpublic static final String SERVICE_DECOMPILE = SERVICE + PACKAGE_SPLIT + \"decompile\";\n\t/**\n\t * Group for specific decompiler components.\n\t */\n\tpublic static final String SERVICE_DECOMPILE_IMPL = SERVICE_DECOMPILE + PACKAGE_SPLIT + \"impl\";\n\t/**\n\t * Group for IO components.\n\t */\n\tpublic static final String SERVICE_IO = SERVICE + PACKAGE_SPLIT + \"io\";\n\t/**\n\t * Group for mapping components.\n\t */\n\tpublic static final String SERVICE_MAPPING = SERVICE + PACKAGE_SPLIT + \"mapping\";\n\t/**\n\t * Group for plugin components.\n\t */\n\tpublic static final String SERVICE_PLUGIN = SERVICE + PACKAGE_SPLIT + \"plugin\";\n\t/**\n\t * Group for transformation components.\n\t */\n\tpublic static final String SERVICE_TRANSFORM = SERVICE + PACKAGE_SPLIT + \"transform\";\n\t/**\n\t * Group base for UI classes.\n\t */\n\tpublic static final String SERVICE_UI = SERVICE + PACKAGE_SPLIT + \"ui\";\n\t/**\n\t * Group for 3rd party.\n\t * <br>\n\t * Plugin registering new {@link ConfigContainer} instances should use this as the {@link ConfigContainer#getGroup()}.\n\t * This group is given special treatment in the UI.\n\t */\n\tpublic static final String EXTERNAL = \"external\";\n\n\tprivate ConfigGroups() {\n\t}\n\n\t/**\n\t * @param container\n\t * \t\tContainer to get packages from.\n\t *\n\t * @return Group packages.\n\t */\n\tpublic static String[] getGroupPackages(ConfigContainer container) {\n\t\treturn container.getGroup().split('\\\\' + PACKAGE_SPLIT);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/config/ConfigMapValue.java",
    "content": "package software.coley.recaf.config;\n\nimport java.util.Map;\n\n/**\n * An option stored in a {@link ConfigContainer} object representing a map.\n *\n * @param <K>\n * \t\tMap key type.\n * @param <V>\n * \t\tMap value type.\n * @param <M>\n * \t\tMap type.\n *\n * @author Matt Coley\n */\npublic interface ConfigMapValue<K, V, M extends Map<K, V>> extends ConfigValue<M> {\n\t/**\n\t * @return Map key type.\n\t */\n\tClass<K> getKeyType();\n\n\t/**\n\t * @return Map value type.\n\t */\n\tClass<V> getValueType();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/config/ConfigPersistence.java",
    "content": "package software.coley.recaf.config;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Outline of persistence for {@link ConfigContainer}\n *\n * @author Matt Coley\n */\npublic interface ConfigPersistence {\n\t/**\n\t * @param container\n\t * \t\tContainer with values to save to persistent medium.\n\t */\n\tvoid save(@Nonnull ConfigContainer container);\n\n\t/**\n\t * @param container\n\t * \t\tContainer with values to load from persistent medium.\n\t */\n\tvoid load(@Nonnull ConfigContainer container);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/config/ConfigValue.java",
    "content": "package software.coley.recaf.config;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.observables.Observable;\n\n/**\n * An option stored in a {@link ConfigContainer} object.\n *\n * @param <T>\n * \t\tValue type.\n *\n * @author Matt Coley\n */\npublic interface ConfigValue<T> {\n\t/**\n\t * @return Unique ID of this value.\n\t */\n\t@Nonnull\n\tString getId();\n\n\t/**\n\t * @return Value type class.\n\t */\n\t@Nonnull\n\tClass<T> getType();\n\n\t/**\n\t * @return Observable of value.\n\t */\n\t@Nonnull\n\tObservable<T> getObservable();\n\n\t/**\n\t * @param value\n\t * \t\tValue to set.\n\t */\n\tdefault void setValue(@Nonnull T value) {\n\t\tgetObservable().setValue(value);\n\t}\n\n\t/**\n\t * @return Current value.\n\t */\n\t@Nonnull\n\tdefault T getValue() {\n\t\treturn getObservable().getValue();\n\t}\n\n\t/**\n\t * @return {@code true} for hidden values not to be shown to users <i>(Strictly in the UI)</i>.\n\t */\n\tdefault boolean isHidden() {\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/config/RestoreAwareConfigContainer.java",
    "content": "package software.coley.recaf.config;\n\nimport software.coley.recaf.services.config.ConfigManager;\n\n/**\n * Outline of a config container that is aware of when its contents are restored.\n *\n * @author Matt Coley\n */\npublic interface RestoreAwareConfigContainer extends ConfigContainer {\n\t/**\n\t * Called when this container has its contents restored via {@link ConfigManager}.\n\t */\n\tdefault void onRestore() {}\n\n\t/**\n\t * Called when this container was checked for contents via {@link ConfigManager} for restoration,\n\t * but no local files were found and thus there was nothing to restore.\n\t */\n\tdefault void onNoRestore() {}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/Accessed.java",
    "content": "package software.coley.recaf.info;\n\nimport software.coley.recaf.info.member.ClassMember;\n\nimport static java.lang.reflect.Modifier.*;\n\n/**\n * Outline of a class or member with access modifiers.\n *\n * @author Matt Coley\n * @see ClassMember\n * @see ClassInfo\n */\npublic interface Accessed {\n\t/**\n\t * @return Access modifiers.\n\t */\n\tint getAccess();\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code public}.\n\t */\n\tdefault boolean hasPublicModifier() {\n\t\treturn hasModifierMask(PUBLIC);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code protected}.\n\t */\n\tdefault boolean hasProtectedModifier() {\n\t\treturn hasModifierMask(PROTECTED);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code private}.\n\t */\n\tdefault boolean hasPrivateModifier() {\n\t\treturn hasModifierMask(PRIVATE);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains none of:\n\t * {@code public}, {@code protected}, or {@code private}.\n\t */\n\tdefault boolean hasPackagePrivateModifier() {\n\t\treturn hasNoneOfMask(PRIVATE | PROTECTED | PUBLIC);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code static}.\n\t */\n\tdefault boolean hasStaticModifier() {\n\t\treturn hasModifierMask(STATIC);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code final}.\n\t */\n\tdefault boolean hasFinalModifier() {\n\t\treturn hasModifierMask(FINAL);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code synchronized}.\n\t */\n\tdefault boolean hasSynchronizedModifier() {\n\t\treturn hasModifierMask(SYNCHRONIZED);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code volatile}.\n\t */\n\tdefault boolean hasVolatileModifier() {\n\t\treturn hasModifierMask(VOLATILE);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code transient}.\n\t */\n\tdefault boolean hasTransientModifier() {\n\t\treturn hasModifierMask(TRANSIENT);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code native}.\n\t */\n\tdefault boolean hasNativeModifier() {\n\t\treturn hasModifierMask(NATIVE);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code enum}.\n\t */\n\tdefault boolean hasEnumModifier() {\n\t\treturn hasModifierMask(0x00004000);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code annotation}.\n\t */\n\tdefault boolean hasAnnotationModifier() {\n\t\treturn hasModifierMask(0x00002000);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code interface}.\n\t */\n\tdefault boolean hasInterfaceModifier() {\n\t\treturn hasModifierMask(INTERFACE);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code module}.\n\t */\n\tdefault boolean hasModuleModifier() {\n\t\treturn hasModifierMask(0x8000);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code abstract}.\n\t */\n\tdefault boolean hasAbstractModifier() {\n\t\treturn hasModifierMask(ABSTRACT);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code strictfp}.\n\t */\n\tdefault boolean hasStrictFpModifier() {\n\t\treturn hasModifierMask(STRICT);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code varargs}.\n\t */\n\tdefault boolean hasVarargsModifier() {\n\t\treturn hasModifierMask(0x00000080);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code bridge}.\n\t */\n\tdefault boolean hasBridgeModifier() {\n\t\treturn hasModifierMask(0x00000040);\n\t}\n\n\t/**\n\t * @return {@code true} when this item's access modifiers contains {@code synthetic}.\n\t */\n\tdefault boolean hasSyntheticModifier() {\n\t\treturn hasModifierMask(0x00001000);\n\t}\n\n\t/**\n\t * @return {@code true} when {@link #hasSyntheticModifier()} or {@link #hasBridgeModifier()} are {@code true}.\n\t */\n\tdefault boolean isCompilerGenerated() {\n\t\treturn hasBridgeModifier() || hasSyntheticModifier();\n\t}\n\n\t/**\n\t * @param mask\n\t * \t\tMask to check.\n\t *\n\t * @return {@code true} if the access modifiers of this item match the given mask.\n\t */\n\tdefault boolean hasModifierMask(int mask) {\n\t\treturn (getAccess() & mask) == mask;\n\t}\n\n\t/**\n\t * @param modifiers\n\t * \t\tModifiers to check.\n\t *\n\t * @return {@code true} if the access modifiers of this item contain all the given modifiers.\n\t */\n\tdefault boolean hasAllModifiers(int... modifiers) {\n\t\tfor (int modifier : modifiers)\n\t\t\tif (!hasModifierMask(modifier))\n\t\t\t\treturn false;\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param modifiers\n\t * \t\tModifiers to check.\n\t *\n\t * @return {@code true} if the access modifiers of this item contain any the given modifiers.\n\t */\n\tdefault boolean hasAnyModifiers(int... modifiers) {\n\t\tfor (int modifier : modifiers)\n\t\t\tif (hasModifierMask(modifier))\n\t\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param mask\n\t * \t\tMask to check.\n\t *\n\t * @return {@code true} if the access modifiers of this item do not match the mask.\n\t */\n\tdefault boolean hasNoneOfMask(int mask) {\n\t\treturn (getAccess() & mask) == 0;\n\t}\n\n\t/**\n\t * @param modifiers\n\t * \t\tModifiers to check.\n\t *\n\t * @return {@code true} if the access modifiers of this item contain none the given modifiers.\n\t */\n\tdefault boolean hasNoneOfModifiers(int... modifiers) {\n\t\tfor (int modifier : modifiers)\n\t\t\tif (!hasNoneOfMask(modifier))\n\t\t\t\treturn false;\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/AndroidChunkFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceFile;\nimport jakarta.annotation.Nonnull;\n\n/**\n * Outline of files that utilize the Android chunk format.\n *\n * @author Matt Coley\n * @see BinaryXmlFileInfo For {@code AndroidManifest.xml} contents.\n * @see ArscFileInfo For {@code resources.arsc} contents.\n */\npublic interface AndroidChunkFileInfo extends FileInfo {\n\t/**\n\t * @return Model representation of the chunk file.\n\t */\n\t@Nonnull\n\tBinaryResourceFile getChunkModel();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/AndroidClassInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.builder.AndroidClassInfoBuilder;\n\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\n\n/**\n * Outline of an Android class.\n *\n * @author Matt Coley\n */\npublic interface AndroidClassInfo extends ClassInfo {\n\t/**\n\t * @return {@code true} when {@link #asJvmClass()} can act as a mapping operation.\n\t * {@code false} when mapping to JVM classes is unsupported.\n\t */\n\tdefault boolean canMapToJvmClass() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @return New builder wrapping this class information.\n\t */\n\t@Nonnull\n\tdefault AndroidClassInfoBuilder toAndroidBuilder() {\n\t\treturn new AndroidClassInfoBuilder(this);\n\t}\n\n\t@Override\n\tdefault void acceptIfJvmClass(@Nonnull Consumer<JvmClassInfo> action) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tdefault void acceptIfAndroidClass(@Nonnull Consumer<AndroidClassInfo> action) {\n\t\taction.accept(this);\n\t}\n\n\t@Override\n\tdefault boolean testIfJvmClass(@Nonnull Predicate<JvmClassInfo> predicate) {\n\t\treturn false;\n\t}\n\n\t@Override\n\tdefault boolean testIfAndroidClass(@Nonnull Predicate<AndroidClassInfo> predicate) {\n\t\treturn predicate.test(this);\n\t}\n\n\t@Nonnull\n\t@Override\n\tdefault JvmClassInfo asJvmClass() {\n\t\tthrow new IllegalStateException(\"Android class cannot be cast to JVM class\");\n\t}\n\n\t@Nonnull\n\t@Override\n\tdefault AndroidClassInfo asAndroidClass() {\n\t\treturn this;\n\t}\n\n\t@Override\n\tdefault boolean isJvmClass() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tdefault boolean isAndroidClass() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/ApkFileInfo.java",
    "content": "package software.coley.recaf.info;\n\n/**\n * Outline of an APK Android file container.\n *\n * @author Matt Coley\n */\npublic interface ApkFileInfo extends ZipFileInfo {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/ArscFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.android.xml.AndroidResourceProvider;\n\n/**\n * Outline of a ARSC file, used by Android APK's.\n *\n * @author Matt Coley\n */\npublic interface ArscFileInfo extends AndroidChunkFileInfo {\n\t/**\n\t * Standard name of ARSC resource file in APK files.\n\t */\n\tString NAME = \"resources.arsc\";\n\n\t/**\n\t * @return Resource information extracted from the ARSC file contents.\n\t */\n\t@Nonnull\n\tAndroidResourceProvider getResourceInfo();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/AudioFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Outline of an audio file.\n *\n * @author Matt Coley\n */\npublic interface AudioFileInfo extends FileInfo {\n\t@Nonnull\n\t@Override\n\tdefault AudioFileInfo asAudioFile() {\n\t\treturn this;\n\t}\n\n\t@Override\n\tdefault boolean isAudioFile() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicAndroidChunkFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceFile;\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.builder.ChunkFileInfoBuilder;\n\n/**\n * Common implementation for {@link BasicBinaryXmlFileInfo} and {@link BasicArscFileInfo}.\n *\n * @author Matt Coley\n */\npublic class BasicAndroidChunkFileInfo extends BasicFileInfo implements AndroidChunkFileInfo {\n\tprivate BinaryResourceFile resourceFile;\n\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicAndroidChunkFileInfo(ChunkFileInfoBuilder<?> builder) {\n\t\tsuper(builder);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BinaryResourceFile getChunkModel() {\n\t\tif (resourceFile == null)\n\t\t\tresourceFile = new BinaryResourceFile(getRawContent());\n\t\treturn resourceFile;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicAndroidClassInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport com.android.tools.r8.graph.DexProgramClass;\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.ClassReader;\nimport software.coley.dextranslator.Options;\nimport software.coley.dextranslator.ir.ConversionException;\nimport software.coley.dextranslator.model.ApplicationData;\nimport software.coley.recaf.info.builder.AndroidClassInfoBuilder;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\n\nimport java.io.IOException;\nimport java.util.Collections;\n\n/**\n * Basic Android class info implementation.\n *\n * @author Matt Coley\n */\npublic class BasicAndroidClassInfo extends BasicClassInfo implements AndroidClassInfo {\n\tprivate final DexProgramClass dexClass;\n\tprivate JvmClassInfo converted;\n\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull info from.\n\t */\n\tpublic BasicAndroidClassInfo(@Nonnull AndroidClassInfoBuilder builder) {\n\t\tsuper(builder);\n\t\tdexClass = builder.getDexClass();\n\t}\n\n\t@Override\n\tpublic boolean canMapToJvmClass() {\n\t\t// See below\n\t\treturn true;\n\t}\n\n\t/**\n\t * @return Translation into JVM class.\n\t */\n\t@Nonnull\n\t@Override\n\tpublic JvmClassInfo asJvmClass() {\n\t\tif (converted == null) {\n\t\t\t// This is an expensive operation, so it's best to make other thread access wait until it is done and\n\t\t\t// then use the singular return value.\n\t\t\tsynchronized (this) {\n\t\t\t\t// If there are 2+ requests here, and we let one through to completion, when the others are let in\n\t\t\t\t// this value should be computed then.\n\t\t\t\tif (converted != null)\n\t\t\t\t\treturn converted;\n\t\t\t\ttry {\n\t\t\t\t\tString name = getName();\n\t\t\t\t\tApplicationData data = ApplicationData.fromProgramClasses(Collections.singleton(dexClass));\n\t\t\t\t\tdata.setOperationOptionsProvider(() -> new Options()\n\t\t\t\t\t\t\t.enableLoadStoreOptimization()\n\t\t\t\t\t\t\t.setLenient(true)\n\t\t\t\t\t\t\t.setReplaceInvalidMethodBodies(true));\n\t\t\t\t\tbyte[] convertedBytecode = data.exportToJvmClass(name);\n\t\t\t\t\tif (convertedBytecode == null)\n\t\t\t\t\t\tthrow new IllegalStateException(\"Failed to convert Dalvik model of \" + name + \" to JVM bytecode, \" +\n\t\t\t\t\t\t\t\t\"conversion results did not include type name.\");\n\t\t\t\t\tClassReader reader = new ClassReader(convertedBytecode);\n\t\t\t\t\tconverted = new JvmClassInfoBuilder(reader).build();\n\t\t\t\t} catch (ConversionException | IOException ex) {\n\t\t\t\t\tthrow new IllegalStateException(ex);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn converted;\n\t}\n\n\t/**\n\t * @return Backing program class node.\n\t */\n\t@Nonnull\n\tpublic DexProgramClass getDexClass() {\n\t\treturn dexClass;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"Android class: \" + getName();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicApkFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport software.coley.recaf.info.builder.ZipFileInfoBuilder;\n\n/**\n * Basic implementation of an Android APK file info.\n *\n * @author Matt Coley\n */\npublic class BasicApkFileInfo extends BasicZipFileInfo implements ApkFileInfo {\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicApkFileInfo(ZipFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicArscFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport org.slf4j.Logger;\nimport software.coley.android.xml.AndroidResourceProvider;\nimport software.coley.android.xml.NoopAndroidResourceProvider;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.builder.ArscFileInfoBuilder;\nimport software.coley.recaf.util.android.AndroidRes;\n\n/**\n * Basic implementation of ARSC file info.\n *\n * @author Matt Coley\n */\npublic class BasicArscFileInfo extends BasicAndroidChunkFileInfo implements ArscFileInfo {\n\tprivate static final Logger logger = Logging.get(BasicArscFileInfo.class);\n\tprivate AndroidResourceProvider res;\n\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicArscFileInfo(ArscFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic AndroidResourceProvider getResourceInfo() {\n\t\tif (res == null)\n\t\t\ttry {\n\t\t\t\tres = AndroidRes.fromArsc(getChunkModel());\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Failed to decode '{}', will use an empty model instead\", getName(), t);\n\t\t\t\tres = NoopAndroidResourceProvider.INSTANCE;\n\t\t\t}\n\t\treturn res;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicAudioFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport software.coley.recaf.info.builder.AudioFileInfoBuilder;\n\n/**\n * Basic implementation of an audio file info.\n *\n * @author Matt Coley\n */\npublic class BasicAudioFileInfo extends BasicFileInfo implements AudioFileInfo {\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicAudioFileInfo(AudioFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicBinaryXmlFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport software.coley.recaf.info.builder.BinaryXmlFileInfoBuilder;\n\n/**\n * Basic implementation of binary XML file info.\n *\n * @author Matt Coley\n */\npublic class BasicBinaryXmlFileInfo extends BasicAndroidChunkFileInfo implements BinaryXmlFileInfo {\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicBinaryXmlFileInfo(BinaryXmlFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicClassInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.annotation.TypeAnnotationInfo;\nimport software.coley.recaf.info.builder.AbstractClassInfoBuilder;\nimport software.coley.recaf.info.member.BasicMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.info.properties.Property;\nimport software.coley.recaf.info.properties.PropertyContainer;\nimport software.coley.recaf.util.Types;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.stream.Stream;\n\n/**\n * Basic implementation of class info.\n *\n * @author Matt Coley\n * @see BasicJvmClassInfo\n * @see BasicAndroidClassInfo\n */\npublic abstract class BasicClassInfo implements ClassInfo {\n\tprivate static final int SIGS_VALID = 1;\n\tprivate static final int SIGS_INVALID = 0;\n\tprivate static final int SIGS_UNKNOWN = -1;\n\tprivate final PropertyContainer properties;\n\tprivate final String name;\n\tprivate final String superName;\n\tprivate final List<String> interfaces;\n\tprivate final int access;\n\tprivate final String signature;\n\tprivate final String sourceFileName;\n\tprivate final List<AnnotationInfo> annotations;\n\tprivate final List<TypeAnnotationInfo> typeAnnotations;\n\tprivate final String outerClassName;\n\tprivate final String outerMethodName;\n\tprivate final String outerMethodDescriptor;\n\tprivate final List<InnerClassInfo> innerClasses;\n\tprivate final List<FieldMember> fields;\n\tprivate final List<MethodMember> methods;\n\tprivate List<String> breadcrumbs;\n\tprivate int sigCheck = SIGS_UNKNOWN;\n\n\tprotected BasicClassInfo(@Nonnull AbstractClassInfoBuilder<?> builder) {\n\t\tthis(builder.getName(),\n\t\t\t\tbuilder.getSuperName(),\n\t\t\t\tbuilder.getInterfaces(),\n\t\t\t\tbuilder.getAccess(),\n\t\t\t\tbuilder.getSignature(),\n\t\t\t\tbuilder.getSourceFileName(),\n\t\t\t\tbuilder.getAnnotations(),\n\t\t\t\tbuilder.getTypeAnnotations(),\n\t\t\t\tbuilder.getOuterClassName(),\n\t\t\t\tbuilder.getOuterMethodName(),\n\t\t\t\tbuilder.getOuterMethodDescriptor(),\n\t\t\t\tbuilder.getInnerClasses(),\n\t\t\t\tbuilder.getFields(),\n\t\t\t\tbuilder.getMethods(),\n\t\t\t\tbuilder.getPropertyContainer());\n\t}\n\n\tprotected BasicClassInfo(@Nonnull String name, String superName, @Nonnull List<String> interfaces, int access,\n\t                         String signature, String sourceFileName,\n\t                         @Nonnull List<AnnotationInfo> annotations,\n\t                         @Nonnull List<TypeAnnotationInfo> typeAnnotations,\n\t                         String outerClassName, String outerMethodName,\n\t                         String outerMethodDescriptor,\n\t                         @Nonnull List<InnerClassInfo> innerClasses,\n\t                         @Nonnull List<FieldMember> fields, @Nonnull List<MethodMember> methods,\n\t                         @Nonnull PropertyContainer properties) {\n\t\tthis.name = name;\n\t\tthis.superName = superName;\n\t\tthis.interfaces = interfaces;\n\t\tthis.access = access;\n\t\tthis.signature = signature;\n\t\tthis.sourceFileName = sourceFileName;\n\t\tthis.annotations = annotations;\n\t\tthis.typeAnnotations = typeAnnotations;\n\t\tthis.outerClassName = outerClassName;\n\t\tthis.outerMethodName = outerMethodName;\n\t\tthis.outerMethodDescriptor = outerMethodDescriptor;\n\t\tthis.innerClasses = innerClasses;\n\t\tthis.fields = fields;\n\t\tthis.methods = methods;\n\t\tthis.properties = properties;\n\t\t// Link fields/methods to self\n\t\tStream.concat(fields.stream(), methods.stream())\n\t\t\t\t.filter(member -> member instanceof BasicMember)\n\t\t\t\t.map(member -> (BasicMember) member)\n\t\t\t\t.forEach(member -> member.setDeclaringClass(this));\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t@Override\n\tpublic String getSuperName() {\n\t\treturn superName;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<String> getInterfaces() {\n\t\treturn interfaces;\n\t}\n\n\t@Override\n\tpublic int getAccess() {\n\t\treturn access;\n\t}\n\n\t@Override\n\tpublic String getSignature() {\n\t\treturn signature;\n\t}\n\n\t@Override\n\tpublic String getSourceFileName() {\n\t\treturn sourceFileName;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<AnnotationInfo> getAnnotations() {\n\t\treturn annotations;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<TypeAnnotationInfo> getTypeAnnotations() {\n\t\treturn typeAnnotations;\n\t}\n\n\t@Override\n\tpublic String getOuterClassName() {\n\t\treturn outerClassName;\n\t}\n\n\t@Override\n\tpublic String getOuterMethodName() {\n\t\treturn outerMethodName;\n\t}\n\n\t@Override\n\tpublic String getOuterMethodDescriptor() {\n\t\treturn outerMethodDescriptor;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<String> getOuterClassBreadcrumbs() {\n\t\tif (breadcrumbs == null) {\n\t\t\tString currentOuter = getOuterClassName();\n\t\t\tif (currentOuter == null)\n\t\t\t\treturn breadcrumbs = Collections.emptyList();\n\n\t\t\tint maxOuterDepth = 10;\n\t\t\tList<String> list = new ArrayList<>();\n\t\t\tint counter = 0;\n\t\t\twhile (currentOuter != null) {\n\t\t\t\tif (++counter > maxOuterDepth) {\n\t\t\t\t\tlist.clear(); // assuming some obfuscator is at work, so breadcrumbs might be invalid.\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tlist.addFirst(currentOuter);\n\t\t\t\tString targetOuter = currentOuter;\n\t\t\t\tcurrentOuter = innerClasses.stream()\n\t\t\t\t\t\t.filter(i -> i.getInnerClassName().equals(targetOuter) && i.getOuterClassName() != null)\n\t\t\t\t\t\t.map(InnerClassInfo::getOuterClassName)\n\t\t\t\t\t\t.findFirst().orElse(null);\n\t\t\t}\n\t\t\tbreadcrumbs = Collections.unmodifiableList(list);\n\t\t}\n\t\treturn breadcrumbs;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<InnerClassInfo> getInnerClasses() {\n\t\treturn innerClasses;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<FieldMember> getFields() {\n\t\treturn fields;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<MethodMember> getMethods() {\n\t\treturn methods;\n\t}\n\n\t@Override\n\tpublic <V> void setProperty(Property<V> property) {\n\t\tproperties.setProperty(property);\n\t}\n\n\t@Override\n\tpublic void removeProperty(String key) {\n\t\tproperties.removeProperty(key);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, Property<?>> getProperties() {\n\t\treturn properties.getProperties();\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null) return false;\n\n\t\tif (o instanceof ClassInfo other) {\n\t\t\t// NOTE: Do NOT consider the properties since contents of the map can point back to this instance\n\t\t\t//       or our containing resource, causing a cycle.\n\t\t\tif (access != other.getAccess()) return false;\n\t\t\tif (!name.equals(other.getName())) return false;\n\t\t\tif (!Objects.equals(superName, other.getSuperName())) return false;\n\t\t\tif (!interfaces.equals(other.getInterfaces())) return false;\n\t\t\tif (!Objects.equals(signature, other.getSignature())) return false;\n\t\t\tif (!Objects.equals(sourceFileName, other.getSourceFileName())) return false;\n\t\t\tif (!annotations.equals(other.getAnnotations())) return false;\n\t\t\tif (!typeAnnotations.equals(other.getTypeAnnotations())) return false;\n\t\t\tif (!Objects.equals(outerClassName, other.getOuterClassName())) return false;\n\t\t\tif (!Objects.equals(outerMethodName, other.getOuterMethodName())) return false;\n\t\t\tif (!Objects.equals(outerMethodDescriptor, other.getOuterMethodDescriptor())) return false;\n\t\t\tif (!innerClasses.equals(other.getInnerClasses())) return false;\n\t\t\tif (!fields.equals(other.getFields())) return false;\n\t\t\treturn methods.equals(other.getMethods());\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\t// NOTE: Do NOT consider the properties since contents of the map can point back to this instance\n\t\t//       or our containing resource, causing a cycle.\n\t\tint result = name.hashCode();\n\t\tresult = 31 * result + (superName != null ? superName.hashCode() : 0);\n\t\tresult = 31 * result + interfaces.hashCode();\n\t\tresult = 31 * result + access;\n\t\tresult = 31 * result + (signature != null ? signature.hashCode() : 0);\n\t\tresult = 31 * result + (sourceFileName != null ? sourceFileName.hashCode() : 0);\n\t\tresult = 31 * result + annotations.hashCode();\n\t\tresult = 31 * result + typeAnnotations.hashCode();\n\t\tresult = 31 * result + (outerClassName != null ? outerClassName.hashCode() : 0);\n\t\tresult = 31 * result + (outerMethodName != null ? outerMethodName.hashCode() : 0);\n\t\tresult = 31 * result + (outerMethodDescriptor != null ? outerMethodDescriptor.hashCode() : 0);\n\t\tresult = 31 * result + innerClasses.hashCode();\n\t\tresult = 31 * result + fields.hashCode();\n\t\tresult = 31 * result + methods.hashCode();\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicDexFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport software.coley.recaf.info.builder.DexFileInfoBuilder;\n\n/**\n * Basic implementation of an Android DEX file info.\n *\n * @author Matt Coley\n */\npublic class BasicDexFileInfo extends BasicFileInfo implements DexFileInfo {\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicDexFileInfo(DexFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.builder.FileInfoBuilder;\nimport software.coley.recaf.info.properties.Property;\nimport software.coley.recaf.info.properties.PropertyContainer;\n\nimport java.util.Arrays;\nimport java.util.Map;\n\n/**\n * Basic implementation of file info.\n *\n * @author Matt Coley\n */\npublic class BasicFileInfo implements FileInfo {\n\tprivate final PropertyContainer properties;\n\tprivate final String name;\n\tprivate final byte[] rawContent;\n\n\tpublic BasicFileInfo(@Nonnull FileInfoBuilder<?> builder) {\n\t\tthis(builder.getName(),\n\t\t\t\tbuilder.getRawContent(),\n\t\t\t\tbuilder.getProperties());\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tFile name/path.\n\t * @param rawContent\n\t * \t\tRaw contents of file.\n\t * @param properties\n\t * \t\tAssorted properties.\n\t */\n\tpublic BasicFileInfo(@Nonnull String name, @Nonnull byte[] rawContent, @Nonnull PropertyContainer properties) {\n\t\tthis.name = name;\n\t\tthis.rawContent = rawContent;\n\t\tthis.properties = properties;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] getRawContent() {\n\t\treturn rawContent;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null) return false;\n\t\tif (o instanceof FileInfo other) {\n\t\t\tif (!name.equals(other.getName())) return false;\n\t\t\treturn Arrays.equals(rawContent, other.getRawContent());\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = name.hashCode();\n\t\tresult = 31 * result + Arrays.hashCode(rawContent);\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic <V> void setProperty(Property<V> property) {\n\t\tproperties.setProperty(property);\n\t}\n\n\t@Override\n\tpublic void removeProperty(String key) {\n\t\tproperties.removeProperty(key);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, Property<?>> getProperties() {\n\t\treturn properties.getProperties();\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn name;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicImageFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport software.coley.recaf.info.builder.ImageFileInfoBuilder;\n\n/**\n * Basic implementation of an image file info.\n *\n * @author Matt Coley\n */\npublic class BasicImageFileInfo extends BasicFileInfo implements ImageFileInfo {\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicImageFileInfo(ImageFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicInnerClassInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Objects;\n\n/**\n * Basic implementation of inner class info.\n *\n * @author Matt Coley\n */\npublic class BasicInnerClassInfo implements InnerClassInfo {\n\tprivate final String outerDeclaringClassName; // Recaf specific, not modeling class spec\n\tprivate final String innerClassName;\n\tprivate final String outerClassName;\n\tprivate final String innerName;\n\tprivate final int access;\n\tprivate String simpleName;\n\n\t/**\n\t * @param outerDeclaringClassName\n\t * \t\tDeclaring class name,\n\t * @param innerClassName\n\t * \t\tInner name.\n\t * @param outerClassName\n\t * \t\tOuter name.\n\t * @param innerName\n\t * \t\tLocal inner name.\n\t * @param access\n\t * \t\tInner class flags originally declared.\n\t */\n\tpublic BasicInnerClassInfo(String outerDeclaringClassName, String innerClassName,\n\t                           String outerClassName, String innerName, int access) {\n\t\tthis.outerDeclaringClassName = outerDeclaringClassName;\n\t\tthis.innerClassName = innerClassName;\n\t\tthis.outerClassName = outerClassName;\n\t\tthis.innerName = innerName;\n\t\tthis.access = access;\n\t}\n\n\t@Override\n\tpublic int getAccess() {\n\t\treturn access;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getOuterDeclaringClassName() {\n\t\treturn outerDeclaringClassName;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getInnerClassName() {\n\t\treturn innerClassName;\n\t}\n\n\t@Override\n\tpublic String getOuterClassName() {\n\t\treturn outerClassName;\n\t}\n\n\t@Override\n\tpublic String getInnerName() {\n\t\treturn innerName;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getSimpleName() {\n\t\t// Cache simple name computation\n\t\tif (simpleName == null) simpleName = InnerClassInfo.super.getSimpleName();\n\t\treturn simpleName;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null) return false;\n\n\t\tif (o instanceof InnerClassInfo inner) {\n\t\t\tif (access != inner.getAccess()) return false;\n\t\t\tif (!innerClassName.equals(inner.getInnerClassName())) return false;\n\t\t\tif (!Objects.equals(outerClassName, inner.getOuterClassName())) return false;\n\t\t\treturn Objects.equals(innerName, inner.getInnerName());\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = innerClassName.hashCode();\n\t\tresult = 31 * result + (outerClassName != null ? outerClassName.hashCode() : 0);\n\t\tresult = 31 * result + (innerName != null ? innerName.hashCode() : 0);\n\t\tresult = 31 * result + access;\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"Inner class: \" + getSimpleName();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicJModFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport software.coley.recaf.info.builder.JModFileInfoBuilder;\n\n/**\n * Basic implementation of JMod file info.\n *\n * @author Matt Coley\n */\npublic class BasicJModFileInfo extends BasicZipFileInfo implements JModFileInfo {\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicJModFileInfo(JModFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicJarFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport software.coley.recaf.info.builder.JarFileInfoBuilder;\n\n/**\n * Basic implementation of JAR file info.\n *\n * @author Matt Coley\n */\npublic class BasicJarFileInfo extends BasicZipFileInfo implements JarFileInfo {\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicJarFileInfo(JarFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicJvmClassInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.ClassReader;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\n\nimport java.util.Arrays;\n\n/**\n * Basic JVM class info implementation.\n *\n * @author Matt Coley\n */\npublic class BasicJvmClassInfo extends BasicClassInfo implements JvmClassInfo {\n\tprivate final byte[] bytecode;\n\tprivate final int version;\n\tprivate ClassReader reader;\n\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull info from.\n\t */\n\tpublic BasicJvmClassInfo(@Nonnull JvmClassInfoBuilder builder) {\n\t\tsuper(builder);\n\t\tthis.bytecode = builder.getBytecode();\n\t\tthis.version = builder.getVersion();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] getBytecode() {\n\t\treturn bytecode;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassReader getClassReader() {\n\t\tif (reader == null)\n\t\t\treader = new ClassReader(bytecode);\n\t\treturn reader;\n\t}\n\n\t@Override\n\tpublic int getVersion() {\n\t\treturn version;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null) return false;\n\n\t\tif (o instanceof JvmClassInfo other) {\n\t\t\tif (version != other.getVersion()) return false;\n\t\t\treturn Arrays.equals(bytecode, other.getBytecode());\n\t\t} else if (!super.equals(o)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = super.hashCode();\n\t\tresult = 31 * result + Arrays.hashCode(bytecode);\n\t\tresult = 31 * result + version;\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"JVM class: \" + getName();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicModulesFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport software.coley.recaf.info.builder.ModulesFileInfoBuilder;\n\n/**\n * Basic implementation of Modules file info.\n *\n * @author Matt Coley\n */\npublic class BasicModulesFileInfo extends BasicFileInfo implements ModulesFileInfo {\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicModulesFileInfo(ModulesFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicNativeLibraryFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport software.coley.recaf.info.builder.NativeLibraryFileInfoBuilder;\n\n/**\n * Basic implementation of a native-library file info.\n *\n * @author Matt Coley\n */\npublic class BasicNativeLibraryFileInfo extends BasicFileInfo implements NativeLibraryFileInfo {\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicNativeLibraryFileInfo(NativeLibraryFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicTextFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.builder.TextFileInfoBuilder;\n\nimport java.nio.charset.Charset;\n\n/**\n * Basic implementation of text file info.\n *\n * @author Matt Coley\n */\npublic class BasicTextFileInfo extends BasicFileInfo implements TextFileInfo {\n\tprivate final Charset charset;\n\tprivate final String text;\n\tprivate String[] lines;\n\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicTextFileInfo(@Nonnull TextFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t\ttext = builder.getText();\n\t\tcharset = builder.getCharset();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getText() {\n\t\treturn text;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String[] getTextLines() {\n\t\tif (lines == null)\n\t\t\tlines = getText().lines().toArray(String[]::new);\n\t\treturn lines;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Charset getCharset() {\n\t\treturn charset;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicVideoFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport software.coley.recaf.info.builder.VideoFileInfoBuilder;\n\n/**\n * Basic implementation of a video file info.\n *\n * @author Matt Coley\n */\npublic class BasicVideoFileInfo extends BasicFileInfo implements VideoFileInfo {\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicVideoFileInfo(VideoFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicWarFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport software.coley.recaf.info.builder.WarFileInfoBuilder;\n\n/**\n * Basic implementation of WAR file info.\n *\n * @author Matt Coley\n */\npublic class BasicWarFileInfo extends BasicZipFileInfo implements WarFileInfo {\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicWarFileInfo(WarFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BasicZipFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport software.coley.recaf.info.builder.ZipFileInfoBuilder;\n\n/**\n * Basic implementation of ZIP file info.\n *\n * @author Matt Coley\n */\npublic class BasicZipFileInfo extends BasicFileInfo implements ZipFileInfo {\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicZipFileInfo(ZipFileInfoBuilder builder) {\n\t\tsuper(builder);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/BinaryXmlFileInfo.java",
    "content": "package software.coley.recaf.info;\n\n/**\n * Outline of a binary XML file, used by Android APK's.\n *\n * @author Matt Coley\n */\npublic interface BinaryXmlFileInfo extends AndroidChunkFileInfo {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/ClassInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.visitors.IllegalSignatureRemovingVisitor;\n\nimport java.util.List;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.stream.Stream;\n\n/**\n * Outline of a class.\n *\n * @author Matt Coley\n * @see JvmClassInfo For JVM classes.\n * @see AndroidClassInfo For Android classes.\n */\npublic interface ClassInfo extends Info, Annotated, Accessed {\n\t/**\n\t * @return Name of the source file the class was compiled from.\n\t * May be {@code null} when there is no debug data attached to the class.\n\t */\n\t@Nullable\n\tString getSourceFileName();\n\n\t/**\n\t * @return List of implemented interfaces.\n\t */\n\t@Nonnull\n\tList<String> getInterfaces();\n\n\t/**\n\t * @return Super-name of the class.\n\t * May be {@code null} for {@link java.lang.annotation.Annotation} and {@code module-info} classes.\n\t */\n\t@Nullable\n\tString getSuperName();\n\n\t/**\n\t * @return Package the class resides in.\n\t * May be {@code null} for classes in the default package.\n\t */\n\t@Nullable\n\tdefault String getPackageName() {\n\t\tString className = getName();\n\t\tint packageIndex = className.lastIndexOf('/');\n\t\tif (packageIndex <= 0) return null;\n\t\treturn className.substring(0, packageIndex);\n\t}\n\n\t/**\n\t * @return {@code true} when the class name has no package.\n\t */\n\tdefault boolean isInDefaultPackage() {\n\t\treturn getPackageName() == null;\n\t}\n\n\t/**\n\t * @return Stream of all parent types, where the {@link #getSuperName()} is first if present,\n\t * followed by any {@link #getInterfaces()}.\n\t */\n\t@Nonnull\n\tdefault Stream<String> parentTypesStream() {\n\t\treturn Stream.concat(\n\t\t\t\tStream.ofNullable(getSuperName()),\n\t\t\t\tgetInterfaces().stream()\n\t\t);\n\t}\n\n\t/**\n\t * @return Signature containing generic information. May be {@code null}.\n\t */\n\t@Nullable\n\tString getSignature();\n\n\t/**\n\t * @return Name of outer class that this is declared in, if this is an inner class.\n\t * {@code null} when this class is not an inner class.\n\t */\n\t@Nullable\n\tString getOuterClassName();\n\n\t/**\n\t * @return Name of the outer method that this is declared in, as an anonymous inner class.\n\t * {@code null} when this class is not an inner anonymous class.\n\t *\n\t * @see #getOuterMethodDescriptor() Descriptor of outer method\n\t */\n\t@Nullable\n\tString getOuterMethodName();\n\n\t/**\n\t * @return Descriptor of the outer method that this is declared in, as an anonymous inner class.\n\t * {@code null} when this class is not an inner anonymous class.\n\t *\n\t * @see #getOuterMethodName() Name of outer method.\n\t */\n\t@Nullable\n\tString getOuterMethodDescriptor();\n\n\t/**\n\t * Breadcrumbs of the outer class.\n\t * This List <strong>MUST</strong> be sorted in order of the outermost first.\n\t * The last element is the outer of the class itself.\n\t * <br>\n\t * For an example, if our class is 'C' then this list will be {@code [foo/A, foo/A$B]}:\n\t * <pre>{@code\n\t * package foo;\n\t *\n\t * class A {\n\t *     class B {\n\t *         class C {}  // This class\n\t *     }\n\t * }\n\t * }</pre>\n\t *\n\t * @return Breadcrumbs of the outer class.\n\t */\n\t@Nonnull\n\tList<String> getOuterClassBreadcrumbs();\n\n\t/**\n\t * @return List of declared inner classes.\n\t */\n\t@Nonnull\n\tList<InnerClassInfo> getInnerClasses();\n\n\t/**\n\t * Gets a named inner class by the local name.\n\t * Given the following example, you would pass {@code \"Bar\"} or even {@code \"FizzBuzz\"}:\n\t * <pre>{@code\n\t * class Foo {\n\t *     class Bar {\n\t *         class FizzBuzz {}\n\t *     }\n\t * }\n\t * }</pre>\n\t * Because anonymous inner classes do not have a name declared, they cannot be yielded here.\n\t *\n\t * @param innerName\n\t * \t\tLocal name of inner class.\n\t *\n\t * @return Inner class of the matching name, or {@code null} if no such inner exists.\n\t */\n\t@Nullable\n\tdefault InnerClassInfo getInnerClassByInnerName(@Nonnull String innerName) {\n\t\tfor (InnerClassInfo innerClass : getInnerClasses())\n\t\t\tif (innerName.equals(innerClass.getInnerName()))\n\t\t\t\treturn innerClass;\n\t\treturn null;\n\t}\n\n\t/**\n\t * @return {@code true} when this class is an inner class of another class.\n\t */\n\tdefault boolean isInnerClass() {\n\t\treturn getOuterClassName() != null || getOuterMethodName() != null;\n\t}\n\n\t/**\n\t * @param className\n\t * \t\tName of a supposed outer class.\n\t *\n\t * @return {@code true} if this class is an inner class of the given outer class.\n\t */\n\tdefault boolean isInnerClassOf(@Nonnull String className) {\n\t\t// If we don't start with that class name, we can't possibly be an inner class.\n\t\tif (!getName().startsWith(className + \"$\"))\n\t\t\treturn false;\n\t\treturn getOuterClassBreadcrumbs().contains(className);\n\t}\n\n\t/**\n\t * @return {@code true} when this class is an anonymous inner class of another class.\n\t */\n\tdefault boolean isAnonymousInnerClass() {\n\t\t// Check if the 'full' name of the inner 'InnerClassName' is the current class (entry representing ourselves)\n\t\t// Then if the 'OuterClassName' is null, this means our class does not expose a name because it is anonymous.\n\t\treturn getInnerClasses().stream()\n\t\t\t\t.anyMatch(inner -> inner.getInnerClassName().equals(getName()) && inner.getOuterClassName() == null);\n\t}\n\n\t/**\n\t * @return List of declared fields.\n\t */\n\t@Nonnull\n\tList<FieldMember> getFields();\n\n\t/**\n\t * @return List of declared methods.\n\t */\n\t@Nonnull\n\tList<MethodMember> getMethods();\n\n\t/**\n\t * @return Stream of declared fields.\n\t */\n\t@Nonnull\n\tdefault Stream<FieldMember> fieldStream() {\n\t\treturn Stream.of(this).flatMap(self -> self.getFields().stream());\n\t}\n\n\t/**\n\t * @return Stream of declared methods.\n\t */\n\t@Nonnull\n\tdefault Stream<MethodMember> methodStream() {\n\t\treturn Stream.of(this).flatMap(self -> self.getMethods().stream());\n\t}\n\n\t/**\n\t * @return Stream of declared fields and methods.\n\t */\n\t@Nonnull\n\tdefault Stream<ClassMember> fieldAndMethodStream() {\n\t\treturn Stream.concat(fieldStream(), methodStream());\n\t}\n\n\t/**\n\t * Do note that there can be multiple fields with one name if there are different descriptors for each.\n\t * To differentiate properly, please use {@link #getDeclaredField(String, String)}.\n\t *\n\t * @param name\n\t * \t\tField name.\n\t *\n\t * @return First matching field definition, or {@code null} if none were found.\n\t */\n\t@Nullable\n\tdefault FieldMember getFirstDeclaredFieldByName(@Nonnull String name) {\n\t\treturn fieldStream()\n\t\t\t\t.filter(f -> f.getName().equals(name))\n\t\t\t\t.findFirst().orElse(null);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tField name.\n\t * @param descriptor\n\t * \t\tField descriptor.\n\t *\n\t * @return Field matching definition, or {@code null} if none were found.\n\t */\n\t@Nullable\n\tdefault FieldMember getDeclaredField(@Nonnull String name, @Nonnull String descriptor) {\n\t\treturn fieldStream()\n\t\t\t\t.filter(f -> f.getName().equals(name) && f.getDescriptor().equals(descriptor))\n\t\t\t\t.findFirst().orElse(null);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tMethod name.\n\t * @param descriptor\n\t * \t\tMethod descriptor.\n\t *\n\t * @return Method matching definition, or {@code null} if none were found.\n\t */\n\t@Nullable\n\tdefault MethodMember getDeclaredMethod(@Nonnull String name, @Nonnull String descriptor) {\n\t\treturn methodStream()\n\t\t\t\t.filter(m -> m.getName().equals(name) && m.getDescriptor().equals(descriptor))\n\t\t\t\t.findFirst().orElse(null);\n\t}\n\n\t/**\n\t * Do note that there can be multiple methods with one name if there are different method descriptors for each.\n\t * To differentiate properly, please use {@link #getDeclaredMethod(String, String)}.\n\t *\n\t * @param name\n\t * \t\tMethod name.\n\t *\n\t * @return First matching method definition, or {@code null} if none were found.\n\t */\n\t@Nullable\n\tdefault MethodMember getFirstDeclaredMethodByName(@Nonnull String name) {\n\t\treturn methodStream()\n\t\t\t\t.filter(m -> m.getName().equals(name))\n\t\t\t\t.findFirst().orElse(null);\n\t}\n\n\t/**\n\t * @param action\n\t * \t\tAction to run if this is a JVM class.\n\t */\n\tvoid acceptIfJvmClass(@Nonnull Consumer<JvmClassInfo> action);\n\n\t/**\n\t * @param action\n\t * \t\tAction to run if this is an Android class.\n\t */\n\tvoid acceptIfAndroidClass(@Nonnull Consumer<AndroidClassInfo> action);\n\n\t/**\n\t * @param action\n\t * \t\tAction to run.\n\t */\n\tdefault void acceptClass(@Nonnull Consumer<ClassInfo> action) {\n\t\taction.accept(this);\n\t}\n\n\t/**\n\t * @param predicate\n\t * \t\tPredicate to run if this is a JVM class.\n\t *\n\t * @return {@code true} when the predicate passes.\n\t * {@code false} when it does not, or the class is not a JVM class.\n\t */\n\tboolean testIfJvmClass(@Nonnull Predicate<JvmClassInfo> predicate);\n\n\t/**\n\t * @param predicate\n\t * \t\tPredicate to run if this is an Android class.\n\t *\n\t * @return {@code true} when the predicate passes.\n\t * {@code false} when it does not, or the class is not an Android class.\n\t */\n\tboolean testIfAndroidClass(@Nonnull Predicate<AndroidClassInfo> predicate);\n\n\t/**\n\t * @param predicate\n\t * \t\tPredicate to run.\n\t *\n\t * @return Predicate evaluation.\n\t */\n\tdefault boolean testClass(@Nonnull Predicate<ClassInfo> predicate) {\n\t\treturn predicate.test(this);\n\t}\n\n\t/**\n\t * @param function\n\t * \t\tMapping function.\n\t * @param <R>\n\t * \t\tFunction return type.\n\t *\n\t * @return Mapped value.\n\t */\n\tdefault <R> R mapClass(@Nonnull Function<ClassInfo, R> function) {\n\t\treturn function.apply(this);\n\t}\n\n\t@Nonnull\n\t@Override\n\tdefault ClassInfo asClass() {\n\t\treturn this;\n\t}\n\n\t@Nonnull\n\t@Override\n\tdefault FileInfo asFile() {\n\t\tthrow new IllegalStateException(\"Class cannot be cast to generic file\");\n\t}\n\n\t/**\n\t * @return Self cast to JVM class.\n\t */\n\t@Nonnull\n\tJvmClassInfo asJvmClass();\n\n\t/**\n\t * @return Self cast to Android class.\n\t */\n\t@Nonnull\n\tAndroidClassInfo asAndroidClass();\n\n\t@Override\n\tdefault boolean isClass() {\n\t\treturn true;\n\t}\n\n\t@Override\n\tdefault boolean isFile() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @return {@code true} if self is a JVM class.\n\t */\n\tboolean isJvmClass();\n\n\t/**\n\t * @return {@code true} if self is an Android class.\n\t */\n\tboolean isAndroidClass();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/DexFileInfo.java",
    "content": "package software.coley.recaf.info;\n\n/**\n * Outline of an Android dex file.\n *\n * @author Matt Coley\n */\npublic interface DexFileInfo extends FileInfo {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/FileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.builder.FileInfoBuilder;\n\n/**\n * Outline of a file.\n *\n * @author Matt Coley\n */\npublic interface FileInfo extends Info {\n\t/**\n\t * @return New builder wrapping this file information.\n\t */\n\t@Nonnull\n\tdefault FileInfoBuilder<?> toFileBuilder() {\n\t\treturn FileInfoBuilder.forFile(this);\n\t}\n\n\t/**\n\t * @return Raw bytes of file content.\n\t */\n\t@Nonnull\n\tbyte[] getRawContent();\n\n\t/**\n\t * @return File extension of {@link #getName() the file name}, if any exists.\n\t */\n\t@Nullable\n\tdefault String getFileExtension() {\n\t\tString fileName = getName();\n\t\tint i = fileName.indexOf('.') + 1;\n\t\tif (i > 0)\n\t\t\treturn fileName.toLowerCase().substring(i);\n\t\treturn null;\n\t}\n\n\t/**\n\t * @return Directory the file resides in.\n\t * May be {@code null} for files in the root directory.\n\t */\n\t@Nullable\n\tdefault String getDirectoryName() {\n\t\tString fileName = getName();\n\t\tint directoryIndex = fileName.lastIndexOf('/');\n\t\tif (directoryIndex <= 0) return null;\n\t\treturn fileName.substring(0, directoryIndex);\n\t}\n\n\t@Nonnull\n\t@Override\n\tdefault ClassInfo asClass() {\n\t\tthrow new IllegalStateException(\"File cannot be cast to generic class\");\n\t}\n\n\t@Nonnull\n\t@Override\n\tdefault FileInfo asFile() {\n\t\treturn this;\n\t}\n\n\t/**\n\t * @return Self cast to text file.\n\t */\n\t@Nonnull\n\tdefault TextFileInfo asTextFile() {\n\t\tthrow new IllegalStateException(\"Non-text file cannot be cast to text file\");\n\t}\n\n\t/**\n\t * @return Self cast to image file.\n\t */\n\t@Nonnull\n\tdefault ImageFileInfo asImageFile() {\n\t\tthrow new IllegalStateException(\"Non-image file cannot be cast to image file\");\n\t}\n\n\t/**\n\t * @return Self cast to audio file.\n\t */\n\t@Nonnull\n\tdefault AudioFileInfo asAudioFile() {\n\t\tthrow new IllegalStateException(\"Non-audio file cannot be cast to audio file\");\n\t}\n\n\t/**\n\t * @return Self cast to video file.\n\t */\n\t@Nonnull\n\tdefault VideoFileInfo asVideoFile() {\n\t\tthrow new IllegalStateException(\"Non-video file cannot be cast to video file\");\n\t}\n\n\t/**\n\t * @return Self cast to video file.\n\t */\n\t@Nonnull\n\tdefault NativeLibraryFileInfo asNativeLibraryFile() {\n\t\tthrow new IllegalStateException(\"Non-native-library file cannot be cast to native-library file\");\n\t}\n\n\t/**\n\t * @return Self cast to zip file.\n\t */\n\t@Nonnull\n\tdefault ZipFileInfo asZipFile() {\n\t\tthrow new IllegalStateException(\"Non-zip file cannot be cast to zip file\");\n\t}\n\n\t@Override\n\tdefault boolean isClass() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tdefault boolean isFile() {\n\t\treturn true;\n\t}\n\n\t/**\n\t * @return {@code true} if self is a text file.\n\t */\n\tdefault boolean isTextFile() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @return {@code true} if self is an image file.\n\t */\n\tdefault boolean isImageFile() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @return {@code true} if self is an audio file.\n\t */\n\tdefault boolean isAudioFile() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @return {@code true} if self is a video file.\n\t */\n\tdefault boolean isVideoFile() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @return {@code true} if self is a native-library file.\n\t */\n\tdefault boolean isNativeLibraryFile() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @return {@code true} if self is a zip file.\n\t */\n\tdefault boolean isZipFile() {\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/ImageFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Outline of an image file.\n *\n * @author Matt Coley\n */\npublic interface ImageFileInfo extends FileInfo {\n\t@Nonnull\n\t@Override\n\tdefault ImageFileInfo asImageFile() {\n\t\treturn this;\n\t}\n\n\t@Override\n\tdefault boolean isImageFile() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/Info.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.properties.PropertyContainer;\n\n/**\n * Outline of all info types.\n *\n * @author Matt Coley\n */\npublic interface Info extends Named, PropertyContainer {\n\t/**\n\t * @return Self cast to general class.\n\t */\n\t@Nonnull\n\tClassInfo asClass();\n\n\t/**\n\t * @return Self cast to general file.\n\t */\n\t@Nonnull\n\tFileInfo asFile();\n\n\t/**\n\t * @return {@code true} if self is a general class.\n\t */\n\tboolean isClass();\n\n\t/**\n\t * @return {@code true} if self is a general file.\n\t */\n\tboolean isFile();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/InnerClassInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\n/**\n * Outline of an inner class for a declaring {@link ClassInfo}.\n * <br>\n * <b>Important note:</b> Oracle's Java Virtual Machine implementation\n * does not check the consistency of an InnerClasses attribute against a class\n * file representing a class or interface referenced by the attribute.\n *\n * @author Matt Coley\n * @author Amejonah\n */\npublic interface InnerClassInfo extends Accessed, Named {\n\t@Nonnull\n\t@Override\n\tdefault String getName() {\n\t\treturn getInnerClassName();\n\t}\n\n\t/**\n\t * @return The name of the outer declaring class for this inner class.\n\t */\n\t@Nonnull\n\tString getOuterDeclaringClassName();\n\n\t/**\n\t * Given the following example, the inner name is {@code Apple$Worm}.\n\t * <pre>\n\t * class Apple {\n\t *     class Worm {}\n\t * }\n\t * </pre>\n\t *\n\t * @return The internal name of an inner class.\n\t */\n\t@Nonnull\n\tString getInnerClassName();\n\n\t/**\n\t * Given the following example, the inner name is {@code Apple}.\n\t * <pre>\n\t * class Apple {\n\t *     class Worm {}\n\t * }\n\t * </pre>\n\t *\n\t * @return The internal name of the class to which the inner class belongs.\n\t * May be {@code null} for anonymous classes.\n\t */\n\t@Nullable\n\tString getOuterClassName();\n\n\t/**\n\t * Given the following example, the inner name is {@code Worm}.\n\t * <pre>\n\t * class Apple {\n\t *     class Worm {}\n\t * }\n\t * </pre>\n\t *\n\t * @return The <i>(simple)</i> name of the inner class inside its enclosing class.\n\t * May be {@code null} for anonymous inner classes.\n\t */\n\t@Nullable\n\tString getInnerName();\n\n\t/**\n\t * There are some wierd cases where there can be inner-class entries of classes defined by other classes.\n\t * You can use this to filter those cases out.\n\t *\n\t * @return {@code true} when this inner-class entry denotes an inner-class\n\t * reference to a class defined in another class.\n\t */\n\tdefault boolean isExternalReference() {\n\t\treturn !getInnerClassName().startsWith(getOuterDeclaringClassName());\n\t}\n\n\t/**\n\t * @return The access modifiers of the inner class as originally declared in the enclosing class.\n\t */\n\tdefault int getInnerAccess() {\n\t\treturn getAccess();\n\t}\n\n\t/**\n\t * @return Either {@link #getInnerName()} if not {@code null},\n\t * otherwise the last \"part\" (after last $ or /) of {@link #getOuterClassName()}.\n\t */\n\t@Nonnull\n\tdefault String getSimpleName() {\n\t\t// Check for inner name\n\t\tString innerName = getInnerName();\n\t\tif (innerName != null) return innerName;\n\n\t\t// Substring from outer class prefix\n\t\tString outerDeclaringClass = getOuterDeclaringClassName();\n\t\tString outerName = getOuterClassName();\n\t\tif (outerName != null) {\n\t\t\tint outerDeclaringLength = outerDeclaringClass.length();\n\t\t\tint lastIndex = 0;\n\t\t\tint endIndex = Math.min(outerDeclaringLength, outerName.length());\n\t\t\tfor (; lastIndex < endIndex; lastIndex++) {\n\t\t\t\tif (outerDeclaringClass.charAt(lastIndex) != outerName.charAt(lastIndex)) break;\n\t\t\t}\n\n\t\t\t// Edge case handling with outer name\n\t\t\tif (lastIndex == 0)\n\t\t\t\treturn outerName;\n\t\t\telse if (outerName.startsWith(\"$\", lastIndex))\n\t\t\t\tlastIndex++;\n\t\t\treturn outerName.substring(lastIndex);\n\t\t}\n\n\t\t// Class entry is for anonymous class.\n\t\tString innerClassName = getInnerClassName();\n\t\treturn \"Anonymous '\" + innerClassName.substring(innerClassName.lastIndexOf('$') + 1) + \"'\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/JModFileInfo.java",
    "content": "package software.coley.recaf.info;\n\n/**\n * Outline of a JVM JMod file.\n * These files are found at {@code %JAVA%/jmods/}.\n *\n * @author Matt Coley\n */\npublic interface JModFileInfo extends ZipFileInfo {\n\t/**\n\t * Classes in the JMod archive are prefixed with this path.\n\t */\n\tString CLASSES_PREFIX = \"classes/\";\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/JarFileInfo.java",
    "content": "package software.coley.recaf.info;\n\n/**\n * Outline of a JAR file container.\n *\n * @author Matt Coley\n */\npublic interface JarFileInfo extends ZipFileInfo {\n\t/**\n\t * Multi-release JARs prefix class names with this, plus the target version.\n\t * For example: Multiple versions of {@code foo/Bar.class}\n\t * <ul>\n\t *     <li>{@code foo/Bar.class}</li>\n\t *     <li>{@code META-INF/versions/9/foo/Bar.class}</li>\n\t *     <li>{@code META-INF/versions/11/foo/Bar.class}</li>\n\t * </ul>\n\t * The first item is used for Java 8.<br>\n\t * The second item for Java 9 and 10.<br>\n\t * The third item for Java 11+.\n\t */\n\tString MULTI_RELEASE_PREFIX = \"META-INF/versions/\";\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/JvmClassInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.Type;\nimport software.coley.cafedude.classfile.ConstantPoolConstants;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.info.properties.builtin.ReferencedClassesProperty;\nimport software.coley.recaf.info.properties.builtin.StringDefinitionsProperty;\nimport software.coley.recaf.util.JavaVersion;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.visitors.TypeVisitor;\n\nimport java.util.*;\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\n\n/**\n * Outline of a JVM class.\n *\n * @author Matt Coley\n */\npublic interface JvmClassInfo extends ClassInfo {\n\t/**\n\t * Denotes the base version offset.\n\t * <ul>\n\t *     <li>For version 1 of you would use {@code BASE_VERSION + 1}.</li>\n\t *     <li>For version 2 of you would use {@code BASE_VERSION + 2}.</li>\n\t *     <li>...</li>\n\t *     <li>For version N of you would use {@code BASE_VERSION + N}.</li>\n\t * </ul>\n\t */\n\tint BASE_VERSION = 44;\n\n\t/**\n\t * @return New builder wrapping this class information.\n\t */\n\t@Nonnull\n\tdefault JvmClassInfoBuilder toJvmClassBuilder() {\n\t\treturn new JvmClassInfoBuilder(this);\n\t}\n\n\t/**\n\t * @return Java class file version.\n\t */\n\tint getVersion();\n\n\t/**\n\t * @return Bytecode of class.\n\t */\n\t@Nonnull\n\tbyte[] getBytecode();\n\n\t/**\n\t * @return Class reader of {@link #getBytecode()}.\n\t */\n\t@Nonnull\n\tClassReader getClassReader();\n\n\t/**\n\t * @return Default flags to use with {@link #getClassReader()}\n\t */\n\tdefault int getClassReaderFlags() {\n\t\t// There are some old classes with stack-frame data.\n\t\t// These aren't strictly required on old classes, and ASM dies when re-writing them (\n\t\t// - MethodWriter.visitFrame checks for pre Java 6 and throws\n\t\treturn getVersion() <= Opcodes.V1_5 ?\n\t\t\t\tClassReader.SKIP_FRAMES : 0;\n\t}\n\n\t/**\n\t * @return Set of all classes referenced in the constant pool.\n\t */\n\t@Nonnull\n\tdefault NavigableSet<String> getReferencedClasses() {\n\t\tNavigableSet<String> classes = ReferencedClassesProperty.get(this);\n\t\tif (classes != null)\n\t\t\treturn classes;\n\n\t\tSet<String> classNames = new HashSet<>();\n\t\tClassReader reader = getClassReader();\n\n\t\t// Iterate over pool entries. Supe fast way to discover most of the referenced types.\n\t\tint itemCount = reader.getItemCount();\n\t\tchar[] buffer = new char[reader.getMaxStringLength()];\n\t\tfor (int i = 1; i < itemCount; i++) {\n\t\t\tint offset = reader.getItem(i);\n\t\t\tif (offset >= 10) {\n\t\t\t\ttry {\n\t\t\t\t\tint itemTag = reader.readByte(offset - 1);\n\t\t\t\t\tif (itemTag == ConstantPoolConstants.CLASS) {\n\t\t\t\t\t\tString className = reader.readUTF8(offset, buffer);\n\t\t\t\t\t\tif (className.isEmpty())\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\taddName(className, classNames);\n\t\t\t\t\t} else if (itemTag == ConstantPoolConstants.NAME_TYPE) {\n\t\t\t\t\t\tString desc = reader.readUTF8(offset + 2, buffer);\n\t\t\t\t\t\tif (desc.isEmpty())\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\tif (desc.charAt(0) == '(') {\n\t\t\t\t\t\t\taddMethodType(Type.getMethodType(desc), classNames);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tType type = Type.getType(desc);\n\t\t\t\t\t\t\taddType(type, classNames);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (itemTag == ConstantPoolConstants.METHOD_TYPE) {\n\t\t\t\t\t\tString methodDesc = reader.readUTF8(offset, buffer);\n\t\t\t\t\t\tif (methodDesc.isEmpty() || methodDesc.charAt(0) != '(')\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\taddMethodType(Type.getMethodType(methodDesc), classNames);\n\t\t\t\t\t}\n\t\t\t\t} catch (Throwable ignored) {\n\t\t\t\t\t// Exists only to catch situations where obfuscators put unused junk pool entries\n\t\t\t\t\t// with malformed descriptors, which cause ASM's type parser to crash.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// In some cases like interface classes, there may be UTF8 pool entries outlining method descriptors which\n\t\t// are not directly linked in NameType or MethodType pool entries. We need to iterate over fields and methods\n\t\t// to get the descriptors in these cases.\n\t\treader.accept(new TypeVisitor(t -> {\n\t\t\tif (t.getSort() == Type.METHOD)\n\t\t\t\taddMethodType(t, classNames);\n\t\t\telse\n\t\t\t\taddType(t, classNames);\n\t\t}), ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE);\n\n\t\treturn ReferencedClassesProperty.set(this, classNames);\n\t}\n\n\tprivate static void addMethodType(@Nonnull Type methodType, @Nonnull Set<String> classNames) {\n\t\tfor (Type argumentType : methodType.getArgumentTypes())\n\t\t\taddType(argumentType, classNames);\n\t\tType returnType = methodType.getReturnType();\n\t\taddType(returnType, classNames);\n\t}\n\n\tprivate static void addType(@Nonnull Type type, @Nonnull Set<String> classNames) {\n\t\tif (type.getSort() == Type.ARRAY)\n\t\t\ttype = type.getElementType();\n\t\tif (!Types.isPrimitive(type))\n\t\t\taddName(type.getInternalName(), classNames);\n\t}\n\n\tprivate static void addName(@Nonnull String className, @Nonnull Set<String> classNames) {\n\t\tif (className.isEmpty())\n\t\t\treturn;\n\t\tif (className.indexOf(0) == '[' || className.charAt(className.length() - 1) == ';')\n\t\t\taddType(Type.getType(className), classNames);\n\t\telse if (className.indexOf(0) == '(')\n\t\t\taddMethodType(Type.getMethodType(className), classNames);\n\t\telse\n\t\t\tclassNames.add(className);\n\t}\n\n\t/**\n\t * @return Set of all string constants listed in the constant pool.\n\t */\n\t@Nonnull\n\tdefault Set<String> getStringConstants() {\n\t\tSortedSet<String> strings = StringDefinitionsProperty.get(this);\n\t\tif (strings != null)\n\t\t\treturn strings;\n\n\t\tSet<String> stringSet = new HashSet<>();\n\t\tClassReader reader = getClassReader();\n\t\tint itemCount = reader.getItemCount();\n\t\tchar[] buffer = new char[reader.getMaxStringLength()];\n\t\tfor (int i = 1; i < itemCount; i++) {\n\t\t\tint offset = reader.getItem(i);\n\t\t\tif (offset >= 10) {\n\t\t\t\tint itemTag = reader.readByte(offset - 1);\n\t\t\t\tif (itemTag == ConstantPoolConstants.STRING) {\n\t\t\t\t\tString string = reader.readUTF8(offset, buffer);\n\t\t\t\t\tstringSet.add(string);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tStringDefinitionsProperty.set(this, stringSet);\n\t\treturn stringSet;\n\t}\n\n\t@Override\n\tdefault void acceptIfJvmClass(@Nonnull Consumer<JvmClassInfo> action) {\n\t\taction.accept(this);\n\t}\n\n\t@Override\n\tdefault void acceptIfAndroidClass(@Nonnull Consumer<AndroidClassInfo> action) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tdefault boolean testIfJvmClass(@Nonnull Predicate<JvmClassInfo> predicate) {\n\t\treturn predicate.test(this);\n\t}\n\n\t@Override\n\tdefault boolean testIfAndroidClass(@Nonnull Predicate<AndroidClassInfo> predicate) {\n\t\treturn false;\n\t}\n\n\t@Nonnull\n\t@Override\n\tdefault JvmClassInfo asJvmClass() {\n\t\treturn this;\n\t}\n\n\t@Nonnull\n\t@Override\n\tdefault AndroidClassInfo asAndroidClass() {\n\t\tthrow new IllegalStateException(\"JVM class cannot be cast to Android class\");\n\t}\n\n\t@Override\n\tdefault boolean isJvmClass() {\n\t\treturn true;\n\t}\n\n\t@Override\n\tdefault boolean isAndroidClass() {\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/ModulesFileInfo.java",
    "content": "package software.coley.recaf.info;\n\n/**\n * Outline of a JVM modules file.\n * This file is found at {@code %JAVA%/lib/modules}.\n *\n * @author Matt Coley\n */\npublic interface ModulesFileInfo extends FileInfo {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/Named.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.Comparator;\nimport java.util.Objects;\n\n/**\n * Outline of a type that can be identified by name.\n *\n * @author Matt Coley\n */\npublic interface Named {\n\t/**\n\t * Comparator for {@link String} items.\n\t * First compares case-insensitively with natural ordering, then falls back to case-sensitive comparison.\n\t */\n\tComparator<String> STRING_COMPARATOR = (a, b) -> {\n\t\t// Fallback to natural string comparison, first case-insensitive but then case-sensitive to differentiate.\n\t\tint cmp = CaseInsensitiveSimpleNaturalComparator.getInstance().compare(a, b);\n\t\tif (cmp == 0)\n\t\t\tcmp = a.compareTo(b);\n\t\treturn cmp;\n\t};\n\n\t/**\n\t * Comparator for {@link Named} items.\n\t * First compares case-insensitively with natural ordering, then falls back to case-sensitive comparison.\n\t *\n\t * @see #STRING_COMPARATOR\n\t */\n\tComparator<Named> NAMED_COMPARATOR = (o1, o2) -> {\n\t\tString a = o1.getName();\n\t\tString b = o2.getName();\n\t\treturn STRING_COMPARATOR.compare(a, b);\n\t};\n\n\t/**\n\t * Comparator for {@link String} items whose content represent file paths.\n\t */\n\t@SuppressWarnings(\"StringEquality\")\n\tComparator<String> STRING_PATH_COMPARATOR = (a, b) -> {\n\t\t// Get parent directory path for each item.\n\t\tString directoryPathA = StringUtil.cutOffAtLast(a, '/');\n\t\tString directoryPathB = StringUtil.cutOffAtLast(b, '/');\n\t\tif (!Objects.equals(directoryPathA, directoryPathB)) {\n\t\t\t// The directory path is the input path (same reference) if there is no '/'.\n\t\t\t// We always want root paths to be shown first since we group them in a container directory anyways.\n\t\t\tif (directoryPathA == a && directoryPathB != b)\n\t\t\t\treturn -1;\n\t\t\tif (directoryPathA != a && directoryPathB == b)\n\t\t\t\treturn 1;\n\n\t\t\t// We want subdirectories to be shown first over files in the directory.\n\t\t\t// The top-level directory being an empty string is an edge case.\n\t\t\tif (directoryPathA.isEmpty())\n\t\t\t\treturn -1;\n\t\t\telse if (directoryPathB.isEmpty())\n\t\t\t\treturn 1;\n\n\t\t\t// If both paths have the same number of separators, then we can do a normal string comparison on the directory paths.\n\t\t\t// If they have different numbers of separators, then we want to check if one is a parent of the other (or vice versa).\n\t\t\tint sectionCountA = StringUtil.count('/', directoryPathA);\n\t\t\tint sectionCountB = StringUtil.count('/', directoryPathB);\n\t\t\tif (sectionCountA == sectionCountB) {\n\t\t\t\t// Compare directories as paths.\n\t\t\t\tint cmp = STRING_COMPARATOR.compare(directoryPathA, directoryPathB);\n\t\t\t\tif (cmp != 0)\n\t\t\t\t\treturn cmp;\n\t\t\t} else {\n\t\t\t\t// Check for parent-child relationship between the directory paths. The parent directory should be shown first.\n\t\t\t\tif (directoryPathB.startsWith(directoryPathA))\n\t\t\t\t\treturn 1;\n\t\t\t\telse if (directoryPathA.startsWith(directoryPathB))\n\t\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\n\t\treturn STRING_COMPARATOR.compare(a, b);\n\t};\n\n\t/**\n\t * Comparator for {@link Named} items whose names represent file paths.\n\t *\n\t * @see #STRING_PATH_COMPARATOR\n\t */\n\tComparator<Named> NAMED_PATH_COMPARATOR = (o1, o2) -> {\n\t\tString a = o1.getName();\n\t\tString b = o2.getName();\n\t\treturn STRING_PATH_COMPARATOR.compare(a, b);\n\t};\n\n\t/**\n\t * @return Identifying name.\n\t */\n\t@Nonnull\n\tString getName();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/NativeLibraryFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Outline of a native library or application file.\n *\n * @author Matt Coley\n */\npublic interface NativeLibraryFileInfo extends FileInfo {\n\t@Nonnull\n\t@Override\n\tdefault NativeLibraryFileInfo asNativeLibraryFile() {\n\t\treturn this;\n\t}\n\n\t@Override\n\tdefault boolean isNativeLibraryFile() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/StubClassInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.annotation.TypeAnnotationInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.info.properties.Property;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\nimport software.coley.recaf.util.JavaVersion;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\n\n/**\n * Stub implementation of {@link ClassInfo}.\n *\n * @author Matt Coley\n */\n@ExcludeFromJacocoGeneratedReport(justification = \"Stub/placeholder type\")\npublic class StubClassInfo implements ClassInfo {\n\tprivate final String name;\n\tprivate final List<FieldMember> fields;\n\tprivate final List<MethodMember> methods;\n\n\t/**\n\t * @param name\n\t * \t\tClass name.\n\t */\n\tpublic StubClassInfo(@Nonnull String name) {\n\t\tthis(name, Collections.emptyList(), Collections.emptyList());\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tClass name.\n\t * @param fields\n\t * \t\tFields to include.\n\t * @param methods\n\t * \t\tMethods to include.\n\t */\n\tpublic StubClassInfo(@Nonnull String name,\n\t                     @Nonnull List<FieldMember> fields,\n\t                     @Nonnull List<MethodMember> methods) {\n\t\tthis.name = name;\n\t\tthis.fields = fields;\n\t\tthis.methods = methods;\n\t}\n\n\t@Override\n\tpublic int getAccess() {\n\t\treturn 0;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getSourceFileName() {\n\t\treturn null;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<String> getInterfaces() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getSuperName() {\n\t\treturn \"java/lang/Object\";\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getSignature() {\n\t\treturn null;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getOuterClassName() {\n\t\treturn null;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getOuterMethodName() {\n\t\treturn null;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getOuterMethodDescriptor() {\n\t\treturn null;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<String> getOuterClassBreadcrumbs() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<InnerClassInfo> getInnerClasses() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<FieldMember> getFields() {\n\t\treturn fields;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<MethodMember> getMethods() {\n\t\treturn methods;\n\t}\n\n\t@Override\n\tpublic void acceptIfJvmClass(@Nonnull Consumer<JvmClassInfo> action) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void acceptIfAndroidClass(@Nonnull Consumer<AndroidClassInfo> action) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic boolean testIfJvmClass(@Nonnull Predicate<JvmClassInfo> predicate) {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean testIfAndroidClass(@Nonnull Predicate<AndroidClassInfo> predicate) {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean isJvmClass() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean isAndroidClass() {\n\t\treturn false;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<AnnotationInfo> getAnnotations() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<TypeAnnotationInfo> getTypeAnnotations() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic <V> void setProperty(Property<V> property) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void removeProperty(String key) {\n\t\t// no-op\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, Property<?>> getProperties() {\n\t\treturn Collections.emptyMap();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic JvmClassInfo asJvmClass() {\n\t\treturn new Jvm(name);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic AndroidClassInfo asAndroidClass() {\n\t\treturn new Android(name);\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tStubClassInfo that = (StubClassInfo) o;\n\n\t\treturn name.equals(that.name);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn name.hashCode();\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn name;\n\t}\n\n\tprivate static class Android extends StubClassInfo implements AndroidClassInfo {\n\t\tpublic Android(@Nonnull String name) {\n\t\t\tsuper(name);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic AndroidClassInfo asAndroidClass() {\n\t\t\treturn this;\n\t\t}\n\t}\n\n\tprivate static class Jvm extends StubClassInfo implements JvmClassInfo {\n\t\tpublic Jvm(@Nonnull String name) {\n\t\t\tsuper(name);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic JvmClassInfo asJvmClass() {\n\t\t\treturn this;\n\t\t}\n\n\t\t@Override\n\t\tpublic int getVersion() {\n\t\t\treturn JavaVersion.VERSION_OFFSET + JavaVersion.get();\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic byte[] getBytecode() {\n\t\t\treturn new byte[0];\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic ClassReader getClassReader() {\n\t\t\tthrow new IllegalStateException(\"Cannot read from stub class!\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/StubFieldMember.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\n\n/**\n * Stub implementation of {@link FieldMember}.\n *\n * @author Matt Coley\n */\n@ExcludeFromJacocoGeneratedReport(justification = \"Stub/placeholder type\")\npublic class StubFieldMember extends StubMember implements FieldMember {\n\t/**\n\t * @param name\n\t * \t\tField name.\n\t * @param desc\n\t * \t\tField descriptor.\n\t * @param access\n\t * \t\tField access flags.\n\t */\n\tpublic StubFieldMember(@Nonnull String name, @Nonnull String desc, int access) {\n\t\tsuper(name, desc, access);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic Object getDefaultValue() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn getDescriptor() + ' ' + getName();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/StubFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.properties.Property;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collections;\nimport java.util.Map;\n\n/**\n * Stub implementation of {@link FileInfo}.\n *\n * @author Matt Coley\n */\n@ExcludeFromJacocoGeneratedReport(justification = \"Stub/placeholder type\")\npublic class StubFileInfo implements FileInfo {\n\tprivate final String name;\n\n\t/**\n\t * @param name\n\t * \t\tFile name.\n\t */\n\tpublic StubFileInfo(@Nonnull String name) {\n\t\tthis.name = name;\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tText to assign.\n\t *\n\t * @return This file, with text content.\n\t */\n\t@Nonnull\n\tpublic TextFileInfo withText(@Nonnull String text) {\n\t\treturn withText(StandardCharsets.ISO_8859_1, text);\n\t}\n\n\t/**\n\t * @param charset\n\t * \t\tCharset of text.\n\t * @param text\n\t * \t\tText to assign.\n\t *\n\t * @return This file, with text content.\n\t */\n\t@Nonnull\n\tpublic TextFileInfo withText(@Nonnull Charset charset, @Nonnull String text) {\n\t\treturn new StubTextFileInfo(name, charset, text);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] getRawContent() {\n\t\treturn new byte[0];\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t@Override\n\tpublic <V> void setProperty(Property<V> property) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void removeProperty(String key) {\n\t\t// no-op\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, Property<?>> getProperties() {\n\t\treturn Collections.emptyMap();\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null) return false;\n\t\tif (o instanceof FileInfo other) {\n\t\t\treturn name.equals(other.getName());\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn name.hashCode();\n\t}\n\n\tprivate static class StubTextFileInfo extends StubFileInfo implements TextFileInfo {\n\t\tprivate final String text;\n\t\tprivate final Charset charset;\n\n\t\t/**\n\t\t * @param name\n\t\t * \t\tFile name.\n\t\t * @param charset\n\t\t * \t\tCharset of text.\n\t\t * @param text\n\t\t * \t\tText content.\n\t\t */\n\t\tpublic StubTextFileInfo(@Nonnull String name, @Nonnull Charset charset, @Nonnull String text) {\n\t\t\tsuper(name);\n\n\t\t\tthis.text = text;\n\t\t\tthis.charset = charset;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String getText() {\n\t\t\treturn text;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String[] getTextLines() {\n\t\t\treturn StringUtil.splitNewline(text);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Charset getCharset() {\n\t\t\treturn charset;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean equals(Object o) {\n\t\t\tif (this == o) return true;\n\t\t\tif (o == null) return false;\n\t\t\tif (!super.equals(o)) return false;\n\t\t\tif (o instanceof TextFileInfo other) {\n\t\t\t\treturn (text.equals(other.getText()));\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\t@Override\n\t\tpublic int hashCode() {\n\t\t\tint result = super.hashCode();\n\t\t\tresult = 31 * result + text.hashCode();\n\t\t\treturn result;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn getName() + \" : <\" +  text + \">\";\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/StubMember.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.annotation.TypeAnnotationInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.properties.Property;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Stub implementation of {@link ClassMember}.\n *\n * @author Matt Coley\n */\n@ExcludeFromJacocoGeneratedReport(justification = \"Stub/placeholder type\")\npublic abstract class StubMember implements ClassMember {\n\tprivate final String name;\n\tprivate final String desc;\n\tprivate final int access;\n\n\t/**\n\t * @param name Member name.\n\t * @param desc Member descriptor.\n\t * @param access Member access flags.\n\t */\n\tpublic StubMember(@Nonnull String name, @Nonnull String desc, int access) {\n\t\tthis.name = name;\n\t\tthis.desc = desc;\n\t\tthis.access = access;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getDescriptor() {\n\t\treturn desc;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getSignature() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic int getAccess() {\n\t\treturn access;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<AnnotationInfo> getAnnotations() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<TypeAnnotationInfo> getTypeAnnotations() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic <V> void setProperty(Property<V> property) {}\n\n\t@Override\n\tpublic void removeProperty(String key) {}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, Property<?>> getProperties() {\n\t\treturn Collections.emptyMap();\n\t}\n\n\t@Override\n\tpublic final boolean equals(Object o) {\n\t\tif (!(o instanceof StubMember that)) return false;\n\n\t\treturn access == that.access\n\t\t\t\t&& name.equals(that.name)\n\t\t\t\t&& desc.equals(that.desc);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = name.hashCode();\n\t\tresult = 31 * result + desc.hashCode();\n\t\tresult = 31 * result + access;\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/StubMethodMember.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.annotation.AnnotationElement;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\n\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Stub implementation of {@link MethodMember}.\n *\n * @author Matt Coley\n */\n@ExcludeFromJacocoGeneratedReport(justification = \"Stub/placeholder type\")\npublic class StubMethodMember extends StubMember implements MethodMember {\n\t/**\n\t * @param name\n\t * \t\tMethod name.\n\t * @param desc\n\t * \t\tMethod descriptor.\n\t * @param access\n\t * \t\tMethod access flags.\n\t */\n\tpublic StubMethodMember(@Nonnull String name, @Nonnull String desc, int access) {\n\t\tsuper(name, desc, access);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<String> getThrownTypes() {\n\t\treturn  Collections.emptyList();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<LocalVariable> getLocalVariables() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic AnnotationElement getAnnotationDefault() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn getName() + getDescriptor();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/TextFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.builder.TextFileInfoBuilder;\n\nimport java.nio.charset.Charset;\n\n/**\n * Outline of a text file.\n *\n * @author Matt Coley\n */\npublic interface TextFileInfo extends FileInfo {\n\t/**\n\t * @return New builder wrapping this file information.\n\t */\n\t@Nonnull\n\tdefault TextFileInfoBuilder toTextBuilder() {\n\t\treturn new TextFileInfoBuilder(this);\n\t}\n\n\t/**\n\t * @return The {@link #getRawContent()} as text.\n\t */\n\t@Nonnull\n\tString getText();\n\n\t/**\n\t * @return The {@link #getText() text content} split into lines.\n\t */\n\t@Nonnull\n\tString[] getTextLines();\n\n\t/**\n\t * @return The charset used to encode {@link #getText() the text content}.\n\t */\n\t@Nonnull\n\tCharset getCharset();\n\n\t@Nonnull\n\t@Override\n\tdefault TextFileInfo asTextFile() {\n\t\treturn this;\n\t}\n\n\t@Override\n\tdefault boolean isTextFile() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/VideoFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Outline of a video file.\n *\n * @author Matt Coley\n */\npublic interface VideoFileInfo extends FileInfo {\n\t@Nonnull\n\t@Override\n\tdefault VideoFileInfo asVideoFile() {\n\t\treturn this;\n\t}\n\n\t@Override\n\tdefault boolean isVideoFile() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/WarFileInfo.java",
    "content": "package software.coley.recaf.info;\n\n/**\n * Outline of a WAR file container.\n *\n * @author Matt Coley\n */\npublic interface WarFileInfo extends ZipFileInfo {\n\t/**\n\t * WAR files prefix their class names with this.\n\t */\n\tString WAR_CLASS_PREFIX = \"WEB-INF/classes/\";\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/ZipFileInfo.java",
    "content": "package software.coley.recaf.info;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Outline of a ZIP file container.\n *\n * @author Matt Coley\n * @see JarFileInfo\n * @see WarFileInfo\n * @see JModFileInfo\n * @see ApkFileInfo\n */\npublic interface ZipFileInfo extends FileInfo {\n\t@Nonnull\n\t@Override\n\tdefault ZipFileInfo asZipFile() {\n\t\treturn this;\n\t}\n\n\t@Override\n\tdefault boolean isZipFile() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/annotation/Annotated.java",
    "content": "package software.coley.recaf.info.annotation;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\n\nimport java.util.List;\nimport java.util.stream.Stream;\n\n/**\n * Outline of an annotated class or member.\n *\n * @author Matt Coley\n * @see ClassInfo\n * @see ClassMember\n */\npublic interface Annotated {\n\t/**\n\t * @return List of declared annotations.\n\t */\n\t@Nonnull\n\tList<AnnotationInfo> getAnnotations();\n\n\t/**\n\t * @return List of type annotations.\n\t */\n\t@Nonnull\n\tList<TypeAnnotationInfo> getTypeAnnotations();\n\n\t/**\n\t * @return Stream of declared annotations.\n\t */\n\t@Nonnull\n\tdefault Stream<AnnotationInfo> annotationStream() {\n\t\treturn Stream.of(this).flatMap(self -> self.getAnnotations().stream());\n\t}\n\n\t/**\n\t * @return Stream of type annotations.\n\t */\n\t@Nonnull\n\tdefault Stream<AnnotationInfo> typeAnnotationStream() {\n\t\treturn Stream.of(this).flatMap(self -> self.getAnnotations().stream());\n\t}\n\n\t/**\n\t * @return Stream of both normal and type anotations.\n\t */\n\t@Nonnull\n\tdefault Stream<AnnotationInfo> allAnnotationsStream() {\n\t\treturn Stream.concat(annotationStream(), typeAnnotationStream());\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/annotation/AnnotationArrayReference.java",
    "content": "package software.coley.recaf.info.annotation;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.List;\n\n/**\n * Outline of a potential value for {@link AnnotationElement#getElementValue()}.\n *\n * @author Matt Coley\n */\npublic interface AnnotationArrayReference {\n\t/**\n\t * @return List of values.\n\t */\n\t@Nonnull\n\tList<Object> getValues();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/annotation/AnnotationElement.java",
    "content": "package software.coley.recaf.info.annotation;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\n\nimport java.util.List;\n\n/**\n * Outline of an annotation member.\n *\n * @author Matt Coley\n */\npublic interface AnnotationElement {\n\t/**\n\t * @return Element name.\n\t */\n\t@Nonnull\n\tString getElementName();\n\n\t/**\n\t * @return Element value. Can be a primitive, {@link String}, a {@link AnnotationInfo},\n\t * a {@link AnnotationEnumReference}, a {@link Type}, or a {@link List} of any of the prior values.\n\t */\n\t@Nonnull\n\tObject getElementValue();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/annotation/AnnotationEnumReference.java",
    "content": "package software.coley.recaf.info.annotation;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Outline of an enum reference for {@link AnnotationElement#getElementValue()}.\n *\n * @author Matt Coley\n */\npublic interface AnnotationEnumReference {\n\t/**\n\t * @return Descriptor of enum value.\n\t */\n\t@Nonnull\n\tString getDescriptor();\n\n\t/**\n\t * @return Enum value name.\n\t */\n\t@Nonnull\n\tString getValue();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/annotation/AnnotationInfo.java",
    "content": "package software.coley.recaf.info.annotation;\n\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.TypePath;\n\nimport java.util.Map;\n\n/**\n * Outline of annotation data.\n *\n * @author Matt Coley\n */\npublic interface AnnotationInfo extends Annotated {\n\t/**\n\t * @param typeRef\n\t * \t\tConstant denoting where the annotation is applied.\n\t * @param typePath\n\t * \t\tPath to a type argument.\n\t *\n\t * @return Type annotation from this annotation.\n\t */\n\t@Nonnull\n\tTypeAnnotationInfo withTypeInfo(int typeRef, @Nullable TypePath typePath);\n\n\t/**\n\t * @return {@code true} if the annotation is visible at runtime.\n\t */\n\tboolean isVisible();\n\n\t/**\n\t * @return Annotation descriptor.\n\t */\n\t@Nonnull\n\tString getDescriptor();\n\n\t/**\n\t * @return Annotation elements.\n\t */\n\t@Nonnull\n\tMap<String, AnnotationElement> getElements();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/annotation/BasicAnnotationArrayReference.java",
    "content": "package software.coley.recaf.info.annotation;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Basic implementation of an annotation array reference in an element.\n *\n * @author Matt Coley\n */\npublic class BasicAnnotationArrayReference implements AnnotationArrayReference {\n\tprivate final List<Object> values;\n\n\t/**\n\t * @param values\n\t * \t\tArray values.\n\t */\n\tpublic BasicAnnotationArrayReference(List<Object> values) {\n\t\tthis.values = values;\n\t}\n\n\t@Nonnull\n\tpublic List<Object> getValues() {\n\t\treturn values;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tBasicAnnotationArrayReference that = (BasicAnnotationArrayReference) o;\n\n\t\treturn values.equals(that.values);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn values.hashCode();\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"[\" + values.stream()\n\t\t\t\t.map(Object::toString)\n\t\t\t\t.collect(Collectors.joining(\", \")) + \"]\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/annotation/BasicAnnotationElement.java",
    "content": "package software.coley.recaf.info.annotation;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Objects;\n\n/**\n * Basic implementation of annotation elements.\n *\n * @author Matt Coley\n */\npublic class BasicAnnotationElement implements AnnotationElement {\n\tprivate final String name;\n\tprivate final Object value;\n\n\t/**\n\t * @param name\n\t * \t\tElement name.\n\t * @param value\n\t * \t\tElement value.\n\t */\n\tpublic BasicAnnotationElement(String name, Object value) {\n\t\tthis.name = name;\n\t\tthis.value = value;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getElementName() {\n\t\treturn name;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Object getElementValue() {\n\t\treturn value;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tBasicAnnotationElement element = (BasicAnnotationElement) o;\n\n\t\tif (!name.equals(element.name)) return false;\n\t\treturn Objects.equals(value, element.value);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = name.hashCode();\n\t\tresult = 31 * result + (value != null ? value.hashCode() : 0);\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"BasicAnnotationElement{\" +\n\t\t\t\t\"name='\" + name + '\\'' +\n\t\t\t\t\", value=\" + value +\n\t\t\t\t'}';\n\t}\n\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/annotation/BasicAnnotationEnumReference.java",
    "content": "package software.coley.recaf.info.annotation;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Basic implementation of an annotation array reference in an element.\n *\n * @author Matt Coley\n */\npublic class BasicAnnotationEnumReference implements AnnotationEnumReference {\n\tprivate final String descriptor;\n\tprivate final String value;\n\n\t/**\n\t * @param descriptor\n\t * \t\tEnum descriptor.\n\t * @param value\n\t * \t\tEnum name.\n\t */\n\tpublic BasicAnnotationEnumReference(String descriptor, String value) {\n\t\tthis.descriptor = descriptor;\n\t\tthis.value = value;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getDescriptor() {\n\t\treturn descriptor;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getValue() {\n\t\treturn value;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tBasicAnnotationEnumReference enumValue = (BasicAnnotationEnumReference) o;\n\n\t\tif (!descriptor.equals(enumValue.descriptor)) return false;\n\t\treturn value.equals(enumValue.value);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = descriptor.hashCode();\n\t\tresult = 31 * result + value.hashCode();\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn descriptor + \":\" + value;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/annotation/BasicAnnotationInfo.java",
    "content": "package software.coley.recaf.info.annotation;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.TypePath;\n\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Basic implementation of annotation info.\n *\n * @author Matt Coley\n */\npublic class BasicAnnotationInfo implements AnnotationInfo {\n\tprivate final Map<String, AnnotationElement> elements = new LinkedHashMap<>(); // preserve order on iter\n\tprivate final List<AnnotationInfo> annotations = new ArrayList<>();\n\tprivate final List<TypeAnnotationInfo> typeAnnotations = new ArrayList<>();\n\tprivate final boolean visible;\n\tprivate final String descriptor;\n\n\t/**\n\t * @param visible\n\t * \t\tAnnotation runtime visibility.\n\t * @param descriptor\n\t * \t\tAnnotation descriptor.\n\t */\n\tpublic BasicAnnotationInfo(boolean visible, @Nonnull String descriptor) {\n\t\tthis.visible = visible;\n\t\tthis.descriptor = descriptor;\n\t}\n\n\t/**\n\t * For internal use when populating the model.\n\t * Adding an element here does not change the bytecode of the class.\n\t *\n\t * @param element\n\t * \t\tElement to add.\n\t */\n\tpublic void addElement(@Nonnull AnnotationElement element) {\n\t\telements.put(element.getElementName(), element);\n\t}\n\n\t/**\n\t * For internal use when populating the model.\n\t * Adding an annotation here does not change the bytecode of the class.\n\t *\n\t * @param annotation\n\t * \t\tAnnotation to add.\n\t */\n\tpublic void addAnnotation(@Nonnull AnnotationInfo annotation) {\n\t\tannotations.add(annotation);\n\t}\n\n\t/**\n\t * For internal use when populating the model.\n\t * Adding an annotation here does not change the bytecode of the class.\n\t *\n\t * @param typeAnnotation\n\t * \t\tAnnotation to add.\n\t */\n\tpublic void addTypeAnnotation(@Nonnull TypeAnnotationInfo typeAnnotation) {\n\t\ttypeAnnotations.add(typeAnnotation);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicTypeAnnotationInfo withTypeInfo(int typeRef, @Nullable TypePath typePath) {\n\t\treturn new BasicTypeAnnotationInfo(typeRef, typePath, isVisible(), getDescriptor());\n\t}\n\n\t@Override\n\tpublic boolean isVisible() {\n\t\treturn visible;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getDescriptor() {\n\t\treturn descriptor;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, AnnotationElement> getElements() {\n\t\treturn elements;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<AnnotationInfo> getAnnotations() {\n\t\treturn annotations;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<TypeAnnotationInfo> getTypeAnnotations() {\n\t\treturn typeAnnotations;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tBasicAnnotationInfo annotation = (BasicAnnotationInfo) o;\n\n\t\tif (visible != annotation.visible) return false;\n\t\tif (!elements.equals(annotation.elements)) return false;\n\t\treturn descriptor.equals(annotation.descriptor);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = elements.hashCode();\n\t\tresult = 31 * result + (visible ? 1 : 0);\n\t\tresult = 31 * result + descriptor.hashCode();\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"BasicAnnotationInfo{\" +\n\t\t\t\t\" visible=\" + visible +\n\t\t\t\t\", descriptor='\" + descriptor + '\\'' +\n\t\t\t\t\", elements=\" + elements +\n\t\t\t\t'}';\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/annotation/BasicTypeAnnotationInfo.java",
    "content": "package software.coley.recaf.info.annotation;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.TypePath;\n\nimport java.util.Objects;\n\n/**\n * Basic implementation of type annotation info.\n *\n * @author Matt Coley\n */\npublic class BasicTypeAnnotationInfo extends BasicAnnotationInfo implements TypeAnnotationInfo {\n\tprivate final int typeRef;\n\tprivate final TypePath typePath;\n\n\t/**\n\t * @param typeRef\n\t * \t\tConstant denoting where the annotation is applied.\n\t * @param typePath\n\t * \t\tPath to a type argument.\n\t * \t\tMay be {@code null} if no path is required.\n\t * @param visible\n\t * \t\tAnnotation runtime visibility.\n\t * @param descriptor\n\t * \t\tAnnotation descriptor.\n\t */\n\tpublic BasicTypeAnnotationInfo(int typeRef, @Nullable TypePath typePath,\n\t\t\t\t\t\t\t\t   boolean visible, @Nonnull String descriptor) {\n\t\tsuper(visible, descriptor);\n\t\tthis.typeRef = typeRef;\n\t\tthis.typePath = typePath;\n\t}\n\n\t@Override\n\tpublic int getTypeRef() {\n\t\treturn typeRef;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic TypePath getTypePath() {\n\t\treturn typePath;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\t\tif (!super.equals(o)) return false;\n\n\t\tBasicTypeAnnotationInfo that = (BasicTypeAnnotationInfo) o;\n\n\t\tif (typeRef != that.typeRef) return false;\n\t\treturn Objects.equals(typePath, that.typePath);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = super.hashCode();\n\t\tresult = 31 * result + typeRef;\n\t\tresult = 31 * result + (typePath != null ? typePath.hashCode() : 0);\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/annotation/TypeAnnotationInfo.java",
    "content": "package software.coley.recaf.info.annotation;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.TypePath;\nimport org.objectweb.asm.TypeReference;\n\n/**\n * Outline of type annotation data.\n *\n * @author Matt Coley\n */\npublic interface TypeAnnotationInfo extends AnnotationInfo {\n\t/**\n\t * @return Constant denoting where the annotation is applied.\n\t *\n\t * @see TypeReference For return values.\n\t */\n\tint getTypeRef();\n\n\t/**\n\t * @return Path to a type argument. May be {@code null} if no path is required.\n\t */\n\t@Nullable\n\tTypePath getTypePath();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/AbstractClassInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport software.coley.recaf.info.Accessed;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.annotation.TypeAnnotationInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.info.properties.BasicPropertyContainer;\nimport software.coley.recaf.info.properties.PropertyContainer;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * Common builder info for {@link ClassInfo}.\n *\n * @param <B>\n * \t\tSelf type. Exists so implementations don't get stunted in their chaining.\n *\n * @author Matt Coley\n * @see JvmClassInfoBuilder For {@link software.coley.recaf.info.JvmClassInfo}\n * @see AndroidClassInfoBuilder For {@link software.coley.recaf.info.AndroidClassInfo}\n */\npublic abstract class AbstractClassInfoBuilder<B extends AbstractClassInfoBuilder<?>> {\n\tprivate String name;\n\tprivate String superName = \"java/lang/Object\";\n\tprivate List<String> interfaces = Collections.emptyList();\n\tprivate final AccessImpl access = new AccessImpl();\n\tprivate String signature;\n\tprivate String sourceFileName;\n\tprivate List<AnnotationInfo> annotations = Collections.emptyList();\n\tprivate List<TypeAnnotationInfo> typeAnnotations = Collections.emptyList();\n\tprivate String outerClassName;\n\tprivate String outerMethodName;\n\tprivate String outerMethodDescriptor;\n\tprivate List<InnerClassInfo> innerClasses = Collections.emptyList();\n\tprivate List<FieldMember> fields = Collections.emptyList();\n\tprivate List<MethodMember> methods = Collections.emptyList();\n\tprivate PropertyContainer propertyContainer = new BasicPropertyContainer();\n\n\tprotected AbstractClassInfoBuilder() {\n\t\t// default\n\t}\n\n\tprotected AbstractClassInfoBuilder(ClassInfo classInfo) {\n\t\t// copy state\n\t\twithName(classInfo.getName());\n\t\twithSuperName(classInfo.getSuperName());\n\t\twithInterfaces(classInfo.getInterfaces());\n\t\twithAccess(classInfo.getAccess());\n\t\twithSignature(classInfo.getSignature());\n\t\twithSourceFileName(classInfo.getSourceFileName());\n\t\twithAnnotations(classInfo.getAnnotations());\n\t\twithTypeAnnotations(classInfo.getTypeAnnotations());\n\t\twithOuterClassName(classInfo.getOuterClassName());\n\t\twithOuterMethodName(classInfo.getOuterMethodName());\n\t\twithOuterMethodDescriptor(classInfo.getOuterMethodDescriptor());\n\t\twithInnerClasses(classInfo.getInnerClasses());\n\t\twithFields(classInfo.getFields());\n\t\twithMethods(classInfo.getMethods());\n\t\twithPropertyContainer(new BasicPropertyContainer(classInfo.getPersistentProperties()));\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static <B extends AbstractClassInfoBuilder<?>> B forClass(ClassInfo info) {\n\t\tif (info.isJvmClass()) {\n\t\t\treturn (B) new JvmClassInfoBuilder(info.asJvmClass());\n\t\t} else if (info.isAndroidClass()) {\n\t\t\treturn (B) new AndroidClassInfoBuilder(info.asAndroidClass());\n\t\t}\n\t\tthrow new IllegalStateException(\"Unsupported class info type: \" + info);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withName(String name) {\n\t\tthis.name = name;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withSuperName(String superName) {\n\t\tthis.superName = superName;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withInterfaces(List<String> interfaces) {\n\t\tif (interfaces == null)\n\t\t\tthis.interfaces = Collections.emptyList();\n\t\telse\n\t\t\tthis.interfaces = interfaces;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withAccess(int access) {\n\t\tthis.access.value = access;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withSignature(String signature) {\n\t\tthis.signature = signature;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withSourceFileName(String sourceFileName) {\n\t\tthis.sourceFileName = sourceFileName;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withAnnotations(List<AnnotationInfo> annotations) {\n\t\tif (annotations == null)\n\t\t\tthis.annotations = Collections.emptyList();\n\t\telse\n\t\t\tthis.annotations = annotations;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withTypeAnnotations(List<TypeAnnotationInfo> typeAnnotations) {\n\t\tthis.typeAnnotations = Objects.requireNonNullElse(typeAnnotations, Collections.emptyList());\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withOuterClassName(String outerClassName) {\n\t\tthis.outerClassName = outerClassName;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withOuterMethodName(String outerMethodName) {\n\t\tthis.outerMethodName = outerMethodName;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withOuterMethodDescriptor(String outerMethodDescriptor) {\n\t\tthis.outerMethodDescriptor = outerMethodDescriptor;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withInnerClasses(List<InnerClassInfo> innerClasses) {\n\t\tif (innerClasses == null)\n\t\t\tthis.innerClasses = Collections.emptyList();\n\t\telse\n\t\t\tthis.innerClasses = innerClasses;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withFields(List<FieldMember> fields) {\n\t\tif (fields == null)\n\t\t\tthis.fields = Collections.emptyList();\n\t\telse\n\t\t\tthis.fields = fields;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withMethods(List<MethodMember> methods) {\n\t\tif (methods == null)\n\t\t\tthis.methods = Collections.emptyList();\n\t\telse\n\t\t\tthis.methods = methods;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withPropertyContainer(PropertyContainer propertyContainer) {\n\t\tthis.propertyContainer = Objects.requireNonNullElseGet(propertyContainer, BasicPropertyContainer::new);\n\t\treturn (B) this;\n\t}\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic String getSuperName() {\n\t\treturn superName;\n\t}\n\n\tpublic List<String> getInterfaces() {\n\t\treturn interfaces;\n\t}\n\n\tpublic int getAccess() {\n\t\treturn access.value;\n\t}\n\n\tpublic String getSignature() {\n\t\treturn signature;\n\t}\n\n\tpublic String getSourceFileName() {\n\t\treturn sourceFileName;\n\t}\n\n\tpublic List<AnnotationInfo> getAnnotations() {\n\t\treturn annotations;\n\t}\n\n\tpublic List<TypeAnnotationInfo> getTypeAnnotations() {\n\t\treturn typeAnnotations;\n\t}\n\n\tpublic String getOuterClassName() {\n\t\treturn outerClassName;\n\t}\n\n\tpublic String getOuterMethodName() {\n\t\treturn outerMethodName;\n\t}\n\n\tpublic String getOuterMethodDescriptor() {\n\t\treturn outerMethodDescriptor;\n\t}\n\n\tpublic List<InnerClassInfo> getInnerClasses() {\n\t\treturn innerClasses;\n\t}\n\n\tpublic List<FieldMember> getFields() {\n\t\treturn fields;\n\t}\n\n\tpublic List<MethodMember> getMethods() {\n\t\treturn methods;\n\t}\n\n\tpublic PropertyContainer getPropertyContainer() {\n\t\treturn propertyContainer;\n\t}\n\n\tpublic abstract ClassInfo build();\n\n\tprotected void verify() {\n\t\tif (name == null)\n\t\t\tthrow new IllegalArgumentException(\"Name required\");\n\t\tif (superName == null && !access.hasModuleModifier() && !access.hasAnnotationModifier() && !name.equals(\"java/lang/Object\"))\n\t\t\tthrow new IllegalArgumentException(\"Super-name required\");\n\t}\n\n\tstatic class AccessImpl implements Accessed {\n\t\tprivate int value;\n\n\t\t@Override\n\t\tpublic int getAccess() {\n\t\t\treturn value;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/AndroidClassInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport com.android.tools.r8.graph.*;\nimport com.google.common.collect.Streams;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.BasicAndroidClassInfo;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.annotation.BasicAnnotationElement;\nimport software.coley.recaf.info.annotation.BasicAnnotationInfo;\nimport software.coley.recaf.info.member.*;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static software.coley.recaf.util.NumberUtil.isNonZero;\n\n/**\n * Builder for {@link AndroidClassInfo}.\n *\n * @author Matt Coley\n */\npublic class AndroidClassInfoBuilder extends AbstractClassInfoBuilder<AndroidClassInfoBuilder> {\n\tprivate DexProgramClass dexClass;\n\n\t/**\n\t * Create empty builder.\n\t */\n\tpublic AndroidClassInfoBuilder() {\n\t\tsuper();\n\t}\n\n\t/**\n\t * Create a builder with data pulled from the given class.\n\t *\n\t * @param classInfo\n\t * \t\tClass to pull data from.\n\t */\n\tpublic AndroidClassInfoBuilder(AndroidClassInfo classInfo) {\n\t\tsuper(classInfo);\n\t}\n\n\t/**\n\t * @return Wrapped dex class, if any. Used as a source for information when adapted from.\n\t *\n\t * @see #adaptFrom(DexProgramClass) Where this value is set.\n\t */\n\tpublic DexProgramClass getDexClass() {\n\t\treturn dexClass;\n\t}\n\n\t@Override\n\tpublic AndroidClassInfo build() {\n\t\tverify();\n\t\treturn new BasicAndroidClassInfo(this);\n\t}\n\n\t/**\n\t * Copies over values by pulling values from the contents of the given class model.\n\t *\n\t * @param dexClass\n\t * \t\tD8 Class structure to pull data from.\n\t *\n\t * @return Builder.\n\t */\n\t@Nonnull\n\tpublic AndroidClassInfoBuilder adaptFrom(@Nonnull DexProgramClass dexClass) {\n\t\tthis.dexClass = dexClass;\n\t\twithName(dexClass.getTypeName().replace('.', '/'));\n\t\twithSuperName(dexClass.getSuperType().getTypeName().replace('.', '/'));\n\t\twithInterfaces(dexClass.getInterfaces().stream().map(i -> i.getTypeName().replace('.', '/')).toList());\n\t\twithAccess(dexClass.getAccessFlags().getAsCfAccessFlags());\n\t\twithSourceFileName(dexClass.getSourceFile() == null ? null : dexClass.getSourceFile().toString());\n\t\twithAnnotations(mapAnnos(dexClass.annotations()));\n\t\twithFields(mapFields(dexClass.fields()));\n\t\twithMethods(mapMethods(dexClass.methods()));\n\t\twithSignature(dexClass.getClassSignature().toString());\n\t\tInnerClassAttribute innerClasses = dexClass.getInnerClassAttributeForThisClass();\n\t\tif (innerClasses != null) {\n\t\t\tDexType outerType = innerClasses.getOuter();\n\t\t\tif (outerType != null) {\n\t\t\t\twithOuterClassName(outerType.getTypeName().replace('.', '/'));\n\t\t\t}\n\t\t}\n\t\tif (dexClass.hasEnclosingMethodAttribute()) {\n\t\t\tDexMethod enclosingMethod = dexClass.getEnclosingMethodAttribute().getEnclosingMethod();\n\t\t\tif (enclosingMethod != null) {\n\t\t\t\twithOuterMethodName(enclosingMethod.getName().toString());\n\t\t\t\twithOuterMethodDescriptor(enclosingMethod.getProto().toDescriptorString());\n\t\t\t\twithOuterClassName(enclosingMethod.getHolderType().getTypeName().replace('.', '/'));\n\t\t\t}\n\t\t}\n\t\treturn this;\n\t}\n\n\t@Nonnull\n\tprivate List<FieldMember> mapFields(Iterable<DexEncodedField> fields) {\n\t\tif (fields == null) return Collections.emptyList();\n\t\treturn Streams.stream(fields)\n\t\t\t\t.map(f -> {\n\t\t\t\t\tString name = f.getName().toString();\n\t\t\t\t\tString desc = f.getType().toDescriptorString();\n\t\t\t\t\tString sig = f.getGenericSignature().toString();\n\t\t\t\t\tint access = f.accessFlags.getAsCfAccessFlags();\n\t\t\t\t\tObject value = unbox(f.getStaticValue());\n\t\t\t\t\tBasicFieldMember field = new BasicFieldMember(name, desc, sig, access, value);\n\t\t\t\t\tfor (AnnotationInfo anno : mapAnnos(f.annotations())) field.addAnnotation(anno);\n\t\t\t\t\treturn field;\n\t\t\t\t})\n\t\t\t\t.collect(Collectors.toList());\n\t}\n\n\t@Nonnull\n\tprivate List<MethodMember> mapMethods(@Nullable Iterable<DexEncodedMethod> methods) {\n\t\tif (methods == null) return Collections.emptyList();\n\t\treturn Streams.stream(methods)\n\t\t\t\t.map(m -> {\n\t\t\t\t\tString name = m.getName().toString();\n\t\t\t\t\tString desc = m.getProto().toDescriptorString();\n\t\t\t\t\tString sig = m.getSignature().toString();\n\t\t\t\t\tint access = m.getAccessFlags().getAsCfAccessFlags();\n\t\t\t\t\tList<String> thrownTypes = Collections.emptyList();\n\t\t\t\t\tBasicMethodMember method = new BasicMethodMember(name, desc, sig, access, thrownTypes);\n\t\t\t\t\tfor (AnnotationInfo anno : mapAnnos(m.annotations())) method.addAnnotation(anno);\n\t\t\t\t\treturn method;\n\t\t\t\t})\n\t\t\t\t.collect(Collectors.toList());\n\t}\n\n\t@Nonnull\n\tprivate static List<AnnotationInfo> mapAnnos(@Nullable DexAnnotationSet anns) {\n\t\tif (anns == null) return Collections.emptyList();\n\t\treturn anns.stream()\n\t\t\t\t.map(AndroidClassInfoBuilder::mapAnno)\n\t\t\t\t.collect(Collectors.toList());\n\t}\n\n\t@Nonnull\n\tprivate static BasicAnnotationInfo mapAnno(@Nonnull DexAnnotation anno) {\n\t\tBasicAnnotationInfo info = new BasicAnnotationInfo(isNonZero(anno.getVisibility()),\n\t\t\t\tanno.getAnnotationType().getTypeName().replace('.', '/'));\n\t\tanno.annotation.forEachElement(element -> {\n\t\t\tString name = element.getName().toString();\n\t\t\tObject unbox = unbox(element.getValue());\n\t\t\tinfo.addElement(new BasicAnnotationElement(name, unbox));\n\t\t});\n\t\treturn info;\n\t}\n\n\t@Nonnull\n\tprivate static BasicAnnotationInfo mapAnno(@Nonnull DexEncodedAnnotation anno) {\n\t\tBasicAnnotationInfo info = new BasicAnnotationInfo(true, anno.type.getTypeName().replace('.', '/'));\n\t\tfor (DexAnnotationElement element : anno.elements) {\n\t\t\tString name = element.getName().toString();\n\t\t\tDexValue value = element.getValue();\n\t\t\tObject unbox = unbox(value);\n\t\t\tinfo.addElement(new BasicAnnotationElement(name, unbox));\n\t\t}\n\t\treturn info;\n\t}\n\n\tprivate static Object unbox(DexValue value) {\n\t\tif (value instanceof DexValue.DexValueString dexString) {\n\t\t\treturn dexString.toString();\n\t\t} else if (value instanceof DexValue.DexValueBoolean dexBoolean) {\n\t\t\treturn dexBoolean.getValue();\n\t\t} else if (value instanceof DexValue.DexValueByte dexByte) {\n\t\t\treturn dexByte.getValue();\n\t\t} else if (value instanceof DexValue.DexValueChar dexChar) {\n\t\t\treturn dexChar.getValue();\n\t\t} else if (value instanceof DexValue.DexValueShort dexShort) {\n\t\t\treturn dexShort.getValue();\n\t\t} else if (value instanceof DexValue.DexValueInt dexInt) {\n\t\t\treturn dexInt.getValue();\n\t\t} else if (value instanceof DexValue.DexValueFloat dexFloat) {\n\t\t\treturn dexFloat.getValue();\n\t\t} else if (value instanceof DexValue.DexValueLong dexLong) {\n\t\t\treturn dexLong.getValue();\n\t\t} else if (value instanceof DexValue.DexValueDouble dexFloat) {\n\t\t\treturn dexFloat.getValue();\n\t\t} else if (value instanceof DexValue.DexValueAnnotation dexAnnotation) {\n\t\t\treturn mapAnno(dexAnnotation.getValue());\n\t\t} else if (value instanceof DexValue.DexValueArray dexArray) {\n\t\t\tDexValue[] values = dexArray.getValues();\n\t\t\tObject[] unboxed = new Object[values.length];\n\t\t\tfor (int i = 0; i < values.length; i++) {\n\t\t\t\tunboxed[i] = unbox(values[i]);\n\t\t\t}\n\t\t\treturn unboxed;\n\t\t} else if (value instanceof DexValue.DexValueEnum dexEnum) {\n\t\t\tDexField field = dexEnum.getValue();\n\t\t\treturn field.getHolderType().getTypeName() + \" \" +\n\t\t\t\t\tfield.getName() +\n\t\t\t\t\tfield.getType().toDescriptorString();\n\t\t} else if (value instanceof DexValue.DexValueField dexField) {\n\t\t\tDexField field = dexField.getValue();\n\t\t\treturn field.getHolderType().getTypeName() + \" \" +\n\t\t\t\t\tfield.getName() +\n\t\t\t\t\tfield.getType().toDescriptorString();\n\t\t} else if (value instanceof DexValue.DexValueMethod dexMethod) {\n\t\t\tDexMethod method = dexMethod.getValue();\n\t\t\treturn method.getHolderType().getTypeName() + \" \" +\n\t\t\t\t\tmethod.getName() +\n\t\t\t\t\tmethod.getProto().toDescriptorString();\n\t\t} else if (value instanceof DexValue.DexValueMethodHandle dexMethodHandle) {\n\t\t\treturn dexMethodHandle.getValue().toAsmHandle(null);\n\t\t} else if (value instanceof DexValue.DexValueMethodType dexMethodType) {\n\t\t\treturn dexMethodType.getValue().toDescriptorString();\n\t\t} else if (value instanceof DexValue.DexValueType dexType) {\n\t\t\treturn dexType.getValue().getTypeName();\n\t\t} else if (value instanceof DexValue.DexValueNull) {\n\t\t\treturn null;\n\t\t}\n\t\tthrow new UnsupportedOperationException(\"Unsupported dex value type: \" + value);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/ApkFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.*;\n\n/**\n * Builder for {@link ApkFileInfo}.\n *\n * @author Matt Coley\n */\npublic class ApkFileInfoBuilder extends ZipFileInfoBuilder {\n\tpublic ApkFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic ApkFileInfoBuilder(@Nonnull ApkFileInfo apkInfo) {\n\t\tsuper(apkInfo);\n\t}\n\n\tpublic ApkFileInfoBuilder(@Nonnull ZipFileInfoBuilder other) {\n\t\tsuper(other);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicApkFileInfo build() {\n\t\treturn new BasicApkFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/ArscFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ArscFileInfo;\nimport software.coley.recaf.info.BasicArscFileInfo;\nimport software.coley.recaf.info.BasicBinaryXmlFileInfo;\n\n/**\n * Builder for {@link BasicBinaryXmlFileInfo}.\n *\n * @author Matt Coley\n */\npublic class ArscFileInfoBuilder extends ChunkFileInfoBuilder<ArscFileInfoBuilder> {\n\tpublic ArscFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic ArscFileInfoBuilder(ArscFileInfo arscInfo) {\n\t\tsuper(arscInfo);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicArscFileInfo build() {\n\t\treturn new BasicArscFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/AudioFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.BasicAudioFileInfo;\nimport software.coley.recaf.info.AudioFileInfo;\n\n/**\n * Builder for {@link AudioFileInfo}.\n *\n * @author Matt Coley\n */\npublic class AudioFileInfoBuilder extends FileInfoBuilder<AudioFileInfoBuilder> {\n\tpublic AudioFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic AudioFileInfoBuilder(AudioFileInfo audioFileInfo) {\n\t\tsuper(audioFileInfo);\n\t}\n\n\tpublic AudioFileInfoBuilder(FileInfoBuilder<?> other) {\n\t\tsuper(other);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicAudioFileInfo build() {\n\t\treturn new BasicAudioFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/BinaryXmlFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.BasicBinaryXmlFileInfo;\nimport software.coley.recaf.info.BinaryXmlFileInfo;\n\n/**\n * Builder for {@link BasicBinaryXmlFileInfo}.\n *\n * @author Matt Coley\n */\npublic class BinaryXmlFileInfoBuilder extends ChunkFileInfoBuilder<BinaryXmlFileInfoBuilder> {\n\tpublic BinaryXmlFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic BinaryXmlFileInfoBuilder(BinaryXmlFileInfo xmlInfo) {\n\t\tsuper(xmlInfo);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicBinaryXmlFileInfo build() {\n\t\treturn new BasicBinaryXmlFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/ChunkFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport software.coley.recaf.info.AndroidChunkFileInfo;\n\n/**\n * Common builder for {@link ArscFileInfoBuilder} and {@link BinaryXmlFileInfoBuilder}.\n *\n * @param <B>\n * \t\tSelf type. Exists so implementations don't get stunted in their chaining.\n *\n * @author Matt Coley\n */\npublic abstract class ChunkFileInfoBuilder<B extends ChunkFileInfoBuilder<?>> extends FileInfoBuilder<B> {\n\tpublic ChunkFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic ChunkFileInfoBuilder(AndroidChunkFileInfo chunkInfo) {\n\t\tsuper(chunkInfo);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/DexFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.BasicDexFileInfo;\nimport software.coley.recaf.info.DexFileInfo;\n\n/**\n * Builder for {@link DexFileInfo}.\n *\n * @author Matt Coley\n */\npublic class DexFileInfoBuilder extends FileInfoBuilder<DexFileInfoBuilder> {\n\tpublic DexFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic DexFileInfoBuilder(DexFileInfo dexFileInfo) {\n\t\tsuper(dexFileInfo);\n\t}\n\n\tpublic DexFileInfoBuilder(FileInfoBuilder<?> other) {\n\t\tsuper(other);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicDexFileInfo build() {\n\t\treturn new BasicDexFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/FileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.*;\nimport software.coley.recaf.info.properties.BasicPropertyContainer;\nimport software.coley.recaf.info.properties.Property;\nimport software.coley.recaf.info.properties.PropertyContainer;\nimport software.coley.recaf.util.StringDecodingResult;\nimport software.coley.recaf.util.StringUtil;\n\n/**\n * Common builder info for {@link FileInfo}.\n *\n * @param <B>\n * \t\tSelf type. Exists so implementations don't get stunted in their chaining.\n *\n * @author Matt Coley\n */\npublic class FileInfoBuilder<B extends FileInfoBuilder<?>> {\n\tprivate PropertyContainer properties = new BasicPropertyContainer();\n\tprivate String name;\n\tprivate byte[] rawContent;\n\tprotected StringDecodingResult decodingResult;\n\n\tpublic FileInfoBuilder() {\n\t\t// default\n\t}\n\n\tprotected FileInfoBuilder(@Nonnull FileInfo fileInfo) {\n\t\t// copy state\n\t\twithName(fileInfo.getName());\n\t\twithRawContent(fileInfo.getRawContent());\n\t\twithProperties(new BasicPropertyContainer(fileInfo.getProperties()));\n\t}\n\n\tprotected FileInfoBuilder(@Nonnull FileInfoBuilder<?> other) {\n\t\twithName(other.getName());\n\t\twithRawContent(other.getRawContent());\n\t\twithProperties(other.getProperties());\n\t}\n\n\t@Nonnull\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static <B extends FileInfoBuilder<?>> B forFile(FileInfo info) {\n\t\tFileInfoBuilder<?> builder;\n\t\tif (info.isZipFile()) {\n\t\t\t// Handle different container types\n\t\t\tif (info instanceof JarFileInfo jarInfo) {\n\t\t\t\tbuilder = new JarFileInfoBuilder(jarInfo);\n\t\t\t} else if (info instanceof JModFileInfo modInfo) {\n\t\t\t\tbuilder = new JModFileInfoBuilder(modInfo);\n\t\t\t} else if (info instanceof WarFileInfo warInfo) {\n\t\t\t\tbuilder = new WarFileInfoBuilder(warInfo);\n\t\t\t} else {\n\t\t\t\tbuilder = new ZipFileInfoBuilder(info.asZipFile());\n\t\t\t}\n\t\t} else if (info instanceof DexFileInfo dexInfo) {\n\t\t\tbuilder = new DexFileInfoBuilder(dexInfo);\n\t\t} else if (info instanceof ModulesFileInfo modInfo) {\n\t\t\tbuilder = new ModulesFileInfoBuilder(modInfo);\n\t\t} else if (info instanceof TextFileInfo textInfo) {\n\t\t\tbuilder = new TextFileInfoBuilder(textInfo);\n\t\t} else if (info instanceof BinaryXmlFileInfo xmlInfo) {\n\t\t\tbuilder = new BinaryXmlFileInfoBuilder(xmlInfo);\n\t\t} else if (info instanceof ArscFileInfo arscInfo) {\n\t\t\tbuilder = new ArscFileInfoBuilder(arscInfo);\n\t\t} else {\n\t\t\tbuilder = new FileInfoBuilder<>(info);\n\t\t}\n\t\treturn (B) builder;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withProperties(@Nonnull PropertyContainer properties) {\n\t\tthis.properties = properties;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withProperty(@Nonnull Property<?> property) {\n\t\tproperties.setProperty(property);\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withName(@Nonnull String name) {\n\t\tthis.name = name;\n\t\treturn (B) this;\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tpublic B withRawContent(@Nonnull byte[] rawContent) {\n\t\tthis.rawContent = rawContent;\n\t\tdecodingResult = null; // Clear decoding when content changes\n\t\treturn (B) this;\n\t}\n\n\tpublic PropertyContainer getProperties() {\n\t\treturn properties;\n\t}\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic byte[] getRawContent() {\n\t\treturn rawContent;\n\t}\n\n\t/**\n\t * @return Computed string decoding result.\n\t */\n\t@Nonnull\n\tprotected StringDecodingResult getDecodingResult() {\n\t\tif (decodingResult == null)\n\t\t\tdecodingResult = StringUtil.decodeString(rawContent);\n\t\treturn decodingResult;\n\t}\n\n\t@Nonnull\n\tpublic BasicFileInfo build() {\n\t\tif (name == null) throw new IllegalArgumentException(\"Name is required\");\n\t\tif (rawContent == null) throw new IllegalArgumentException(\"Content is required\");\n\t\tif (getDecodingResult().couldDecode())\n\t\t\treturn new TextFileInfoBuilder(this, getDecodingResult()).build();\n\t\telse\n\t\t\treturn new BasicFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/ImageFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.BasicImageFileInfo;\nimport software.coley.recaf.info.ImageFileInfo;\n\n/**\n * Builder for {@link ImageFileInfo}.\n *\n * @author Matt Coley\n */\npublic class ImageFileInfoBuilder extends FileInfoBuilder<ImageFileInfoBuilder> {\n\tpublic ImageFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic ImageFileInfoBuilder(ImageFileInfo imageFileInfo) {\n\t\tsuper(imageFileInfo);\n\t}\n\n\tpublic ImageFileInfoBuilder(FileInfoBuilder<?> other) {\n\t\tsuper(other);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicImageFileInfo build() {\n\t\treturn new BasicImageFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/JModFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.*;\n\n/**\n * Builder for {@link JModFileInfo}.\n *\n * @author Matt Coley\n */\npublic class JModFileInfoBuilder extends ZipFileInfoBuilder {\n\tpublic JModFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic JModFileInfoBuilder(@Nonnull JModFileInfo jmodInfo) {\n\t\tsuper(jmodInfo);\n\t}\n\n\tpublic JModFileInfoBuilder(@Nonnull FileInfoBuilder<?> other) {\n\t\tsuper(other);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicJModFileInfo build() {\n\t\treturn new BasicJModFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/JarFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.BasicJarFileInfo;\nimport software.coley.recaf.info.JarFileInfo;\n\n/**\n * Builder for {@link JarFileInfo}.\n *\n * @author Matt Coley\n */\npublic class JarFileInfoBuilder extends ZipFileInfoBuilder {\n\tpublic JarFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic JarFileInfoBuilder(@Nonnull JarFileInfo jarInfo) {\n\t\tsuper(jarInfo);\n\t}\n\n\tpublic JarFileInfoBuilder(@Nonnull ZipFileInfoBuilder other) {\n\t\tsuper(other);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicJarFileInfo build() {\n\t\treturn new BasicJarFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/JvmClassInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.Attribute;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.RecordComponentVisitor;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.TypePath;\nimport software.coley.cafedude.classfile.attribute.AttributeContexts;\nimport software.coley.cafedude.io.AttributeHolderType;\nimport software.coley.recaf.info.BasicInnerClassInfo;\nimport software.coley.recaf.info.BasicJvmClassInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.annotation.AnnotationElement;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.annotation.BasicAnnotationElement;\nimport software.coley.recaf.info.annotation.BasicAnnotationEnumReference;\nimport software.coley.recaf.info.annotation.BasicAnnotationInfo;\nimport software.coley.recaf.info.annotation.TypeAnnotationInfo;\nimport software.coley.recaf.info.member.BasicFieldMember;\nimport software.coley.recaf.info.member.BasicLocalVariable;\nimport software.coley.recaf.info.member.BasicMethodMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.info.properties.builtin.UnknownAttributesProperty;\nimport software.coley.recaf.util.MultiMap;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.EnumSet;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.TreeSet;\nimport java.util.function.Consumer;\n\nimport static software.coley.recaf.RecafConstants.getAsmVersion;\n\n/**\n * Builder for {@link JvmClassInfo}.\n *\n * @author Matt Coley\n */\npublic class JvmClassInfoBuilder extends AbstractClassInfoBuilder<JvmClassInfoBuilder> {\n\tprivate byte[] bytecode;\n\tprivate int version = JvmClassInfo.BASE_VERSION + 8; // Java 8\n\tprivate boolean skipValidationChecks = true;\n\t@Nullable\n\tprivate ClassBuilderAdapter adapter;\n\n\t/**\n\t * Create empty builder.\n\t */\n\tpublic JvmClassInfoBuilder() {\n\t\tsuper();\n\t}\n\n\t/**\n\t * Create a builder with data pulled from the given class.\n\t *\n\t * @param classInfo\n\t * \t\tClass to pull data from.\n\t */\n\tpublic JvmClassInfoBuilder(@Nonnull JvmClassInfo classInfo) {\n\t\tsuper(classInfo);\n\t\twithBytecode(classInfo.getBytecode());\n\t\twithVersion(classInfo.getVersion());\n\t}\n\n\t/**\n\t * Creates a builder with data pulled from the given bytecode.\n\t *\n\t * @param reader\n\t * \t\tASM class reader to read bytecode from.\n\t */\n\tpublic JvmClassInfoBuilder(@Nonnull ClassReader reader) {\n\t\tadaptFrom(reader);\n\t}\n\n\t/**\n\t * Creates a builder with data pulled from the given bytecode.\n\t *\n\t * @param reader\n\t * \t\tASM class reader to read bytecode from.\n\t * @param readerFlags\n\t * \t\tReader flags to use when populating information via {@link ClassReader#accept(ClassVisitor, int)}.\n\t */\n\tpublic JvmClassInfoBuilder(@Nonnull ClassReader reader, int readerFlags) {\n\t\tadaptFrom(reader, readerFlags);\n\t}\n\n\t/**\n\t * Creates a builder with the given bytecode.\n\t *\n\t * @param bytecode\n\t * \t\tClass bytecode to read values from.\n\t */\n\tpublic JvmClassInfoBuilder(@Nonnull byte[] bytecode) {\n\t\tthis(bytecode, 0);\n\t}\n\n\t/**\n\t * Creates a builder with the given bytecode.\n\t *\n\t * @param bytecode\n\t * \t\tClass bytecode to read values from.\n\t * @param readerFlags\n\t * \t\tReader flags to use when populating information via {@link ClassReader#accept(ClassVisitor, int)}.\n\t */\n\tpublic JvmClassInfoBuilder(@Nonnull byte[] bytecode, int readerFlags) {\n\t\tadaptFrom(bytecode, readerFlags);\n\t}\n\n\t/**\n\t * Copies over values by reading the contents of the class file in the reader.\n\t * Calls {@link #adaptFrom(byte[], int)} with {@code flags=0}.\n\t * <p>\n\t * <b>IMPORTANT:</b> If {@link #skipValidationChecks(boolean)} is {@code false} and validation checks are active\n\t * extra steps are taken to ensure the class is fully ASM compliant. You will want to wrap this call in a try-catch\n\t * block handling {@link Throwable} to cover any potential ASM failure.\n\t * <br>\n\t * Validation is disabled by default.\n\t * <br>\n\t * If you wish to validate the input, you must use the one of the given constructors:\n\t * <ul>\n\t *     <li>{@link #JvmClassInfoBuilder()}</li>\n\t *     <li>{@link #JvmClassInfoBuilder(JvmClassInfo)}</li>\n\t * </ul>\n\t *\n\t * @param code\n\t * \t\tClass bytecode to pull data from.\n\t *\n\t * @return Builder.\n\t */\n\t@Nonnull\n\tpublic JvmClassInfoBuilder adaptFrom(@Nonnull byte[] code) {\n\t\treturn adaptFrom(code, 0);\n\t}\n\n\t/**\n\t * Copies over values by reading the contents of the class file in the reader.\n\t * Calls {@link #adaptFrom(ClassReader, int)} with {@code flags}.\n\t * <p>\n\t * <b>IMPORTANT:</b> If {@link #skipValidationChecks(boolean)} is {@code false} and validation checks are active\n\t * extra steps are taken to ensure the class is fully ASM compliant. You will want to wrap this call in a try-catch\n\t * block handling {@link Throwable} to cover any potential ASM failure.\n\t * <br>\n\t * Validation is disabled by default.\n\t * <br>\n\t * If you wish to validate the input, you must use the one of the given constructors:\n\t * <ul>\n\t *     <li>{@link #JvmClassInfoBuilder()}</li>\n\t *     <li>{@link #JvmClassInfoBuilder(JvmClassInfo)}</li>\n\t * </ul>\n\t *\n\t * @param code\n\t * \t\tClass bytecode to pull data from.\n\t * @param readerFlags\n\t * \t\tReader flags to use when populating information via {@link ClassReader#accept(ClassVisitor, int)}.\n\t *\n\t * @return Builder.\n\t */\n\t@Nonnull\n\tpublic JvmClassInfoBuilder adaptFrom(@Nonnull byte[] code, int readerFlags) {\n\t\treturn adaptFrom(new ClassReader(code), readerFlags);\n\t}\n\n\t/**\n\t * Copies over values by reading the contents of the class file in the reader.\n\t * Calls {@link #adaptFrom(ClassReader, int)} with {@code flags=0}.\n\t * <p>\n\t * <b>IMPORTANT:</b> If {@link #skipValidationChecks(boolean)} is {@code false} and validation checks are active\n\t * extra steps are taken to ensure the class is fully ASM compliant. You will want to wrap this call in a try-catch\n\t * block handling {@link Throwable} to cover any potential ASM failure.\n\t * <br>\n\t * Validation is disabled by default.\n\t * <br>\n\t * If you wish to validate the input, you must use the one of the given constructors:\n\t * <ul>\n\t *     <li>{@link #JvmClassInfoBuilder()}</li>\n\t *     <li>{@link #JvmClassInfoBuilder(JvmClassInfo)}</li>\n\t * </ul>\n\t *\n\t * @param reader\n\t * \t\tASM class reader to pull data from.\n\t *\n\t * @return Builder.\n\t */\n\t@Nonnull\n\tpublic JvmClassInfoBuilder adaptFrom(@Nonnull ClassReader reader) {\n\t\treturn adaptFrom(reader, 0);\n\t}\n\n\t/**\n\t * Copies over values by reading the contents of the class file in the reader.\n\t * <p>\n\t * <b>IMPORTANT:</b> If {@link #skipValidationChecks(boolean)} is {@code false} and validation checks are active\n\t * extra steps are taken to ensure the class is fully ASM compliant. You will want to wrap this call in a try-catch\n\t * block handling {@link Throwable} to cover any potential ASM failure.\n\t * <br>\n\t * Validation is disabled by default.\n\t * <br>\n\t * If you wish to validate the input, you must use the one of the given constructors:\n\t * <ul>\n\t *     <li>{@link #JvmClassInfoBuilder()}</li>\n\t *     <li>{@link #JvmClassInfoBuilder(JvmClassInfo)}</li>\n\t * </ul>\n\t *\n\t * @param reader\n\t * \t\tASM class reader to pull data from.\n\t * @param flags\n\t * \t\tReader flags to use when populating information.\n\t *\n\t * @return Builder.\n\t */\n\t@Nonnull\n\t@SuppressWarnings(value = \"deprecation\")\n\tpublic JvmClassInfoBuilder adaptFrom(@Nonnull ClassReader reader, int flags) {\n\t\t// If we are doing validation checks, delegating the reader to a writer should catch most issues\n\t\t// that would normally crash ASM. It is the caller's responsibility to error handle ASM failing\n\t\t// if such failures occur.\n\t\tif (skipValidationChecks) {\n\t\t\tadapter = new ClassBuilderAdapter(null);\n\t\t\treader.accept(adapter, flags);\n\t\t} else {\n\t\t\tClassWriter cw = new ClassWriter(reader, 0);\n\t\t\tadapter = new ClassBuilderAdapter(cw);\n\t\t\treader.accept(adapter, flags);\n\t\t\tcw.toByteArray();\n\t\t}\n\n\t\treturn withBytecode(reader.b);\n\t}\n\n\t@Nonnull\n\tpublic JvmClassInfoBuilder withBytecode(byte[] bytecode) {\n\t\tthis.bytecode = bytecode;\n\t\treturn this;\n\t}\n\n\t@Nonnull\n\tpublic JvmClassInfoBuilder withVersion(int version) {\n\t\tthis.version = version;\n\t\treturn this;\n\t}\n\n\t/**\n\t * The default value is {@code true}. Setting to {@code false} enables class validation steps.\n\t * When {@link #verify()} is run it will check if there are any custom attributes.\n\t *\n\t * @param skipValidationChecks\n\t *        {@code false} if we want to verify the classes custom attribute\n\t *\n\t * @return {@code JvmClassInfoBuilder}\n\t */\n\t@Nonnull\n\tpublic JvmClassInfoBuilder skipValidationChecks(boolean skipValidationChecks) {\n\t\tthis.skipValidationChecks = skipValidationChecks;\n\t\treturn this;\n\t}\n\n\tpublic byte[] getBytecode() {\n\t\treturn bytecode;\n\t}\n\n\tpublic int getVersion() {\n\t\treturn version;\n\t}\n\n\t@Override\n\tpublic JvmClassInfo build() {\n\t\tif (adapter != null && adapter.hasCustomAttributes())\n\t\t\tgetPropertyContainer().setProperty(new UnknownAttributesProperty(adapter.getCustomAttributeNames()));\n\t\tverify();\n\t\treturn new BasicJvmClassInfo(this);\n\t}\n\n\t@Override\n\tprotected void verify() {\n\t\tsuper.verify();\n\t\tif (bytecode == null)\n\t\t\tthrow new IllegalStateException(\"Bytecode required\");\n\t\tif (version < JvmClassInfo.BASE_VERSION)\n\t\t\tthrow new IllegalStateException(\"Version cannot be lower than 44 (v1)\");\n\t}\n\n\t/**\n\t * Converts ASM visitor actions to 'with' actions in the class builder.\n\t * Results in a fully reconstructed class model.\n\t *\n\t * @see FieldBuilderAdapter\n\t * @see MethodBuilderAdapter\n\t */\n\tprivate class ClassBuilderAdapter extends ClassVisitor {\n\t\tprivate List<AnnotationInfo> annotations;\n\t\tprivate List<TypeAnnotationInfo> typeAnnotations;\n\t\tprivate List<InnerClassInfo> innerClasses;\n\t\tprivate List<FieldMember> fields;\n\t\tprivate List<MethodMember> methods;\n\t\tprivate List<Attribute> classCustomAttributes;\n\t\tprivate final MultiMap<String, Attribute, List<Attribute>> fieldCustomAttributes;\n\t\tprivate final MultiMap<String, Attribute, List<Attribute>> methodCustomAttributes;\n\n\t\tprotected ClassBuilderAdapter(@Nullable ClassVisitor cv) {\n\t\t\tsuper(getAsmVersion(), cv);\n\t\t\tfieldCustomAttributes = MultiMap.from(new HashMap<>(), ArrayList::new);\n\t\t\tmethodCustomAttributes = MultiMap.from(new HashMap<>(), ArrayList::new);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {\n\t\t\tsuper.visit(version, access, name, signature, superName, interfaces);\n\t\t\tif (name == null)\n\t\t\t\t// If you encounter this, you probably generated the class with ClassWriter, but forgot to actually\n\t\t\t\t// pass the ClassWriter as a delegate to the ClassVisitor manipulating the class. Thus, it is never\n\t\t\t\t// informed of the actual contents of the class.\n\t\t\t\tthrow new IllegalStateException(\"Invalid class, name is null\");\n\t\t\twithVersion(version & 0xFF);\n\t\t\twithAccess(access);\n\t\t\twithName(name);\n\t\t\twithSignature(signature);\n\t\t\twithSuperName(superName);\n\t\t\twithInterfaces(Arrays.asList(interfaces));\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitSource(String source, String debug) {\n\t\t\tsuper.visitSource(source, debug);\n\t\t\twithSourceFileName(source);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitOuterClass(String owner, String name, String descriptor) {\n\t\t\tsuper.visitOuterClass(owner, name, descriptor);\n\t\t\twithOuterClassName(owner);\n\t\t\twithOuterMethodName(name);\n\t\t\twithOuterMethodDescriptor(descriptor);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\tif (annotations == null)\n\t\t\t\tannotations = new ArrayList<>();\n\t\t\treturn new AnnotationBuilderAdapter(visible, descriptor, annotations::add);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (typeAnnotations == null)\n\t\t\t\ttypeAnnotations = new ArrayList<>();\n\t\t\treturn new AnnotationBuilderAdapter(visible, descriptor,\n\t\t\t\t\tanno -> typeAnnotations.add(anno.withTypeInfo(typeRef, typePath)));\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitInnerClass(String name, String outerName, String innerName, int access) {\n\t\t\tsuper.visitInnerClass(name, outerName, innerName, access);\n\n\t\t\t// Get the name of the class being visited\n\t\t\tString currentClassName = getName();\n\n\t\t\t// Add the inner data\n\t\t\tif (innerClasses == null)\n\t\t\t\tinnerClasses = new ArrayList<>();\n\t\t\tinnerClasses.add(new BasicInnerClassInfo(currentClassName, name, outerName, innerName, access));\n\n\t\t\t// If the local 'name' is the current class name, then we are visiting an inner class entry\n\t\t\t// that most likely is a representation of the current class. If this entry has data about\n\t\t\t// the outer class, we want to grab it.\n\t\t\tif (name.equals(currentClassName)) {\n\t\t\t\t// Only need to do this once, and some entries may not have data.\n\t\t\t\t// Because they can be in any order we need to protect against re-assigning null.\n\t\t\t\tif (getOuterClassName() == null &&\n\t\t\t\t\t\touterName != null &&\n\t\t\t\t\t\tcurrentClassName.startsWith(outerName)) {\n\t\t\t\t\twithOuterClassName(outerName);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitAttribute(Attribute attribute) {\n\t\t\tvalidateAttribute(attribute, AttributeHolderType.CLASS);\n\t\t\tif (classCustomAttributes == null)\n\t\t\t\tclassCustomAttributes = new ArrayList<>();\n\t\t\tclassCustomAttributes.add(attribute);\n\t\t\tsuper.visitAttribute(attribute);\n\t\t}\n\n\t\tprivate void validateAttribute(@Nonnull Attribute attribute, @Nonnull AttributeHolderType context) {\n\t\t\tif (!skipValidationChecks) {\n\t\t\t\t// Check if this is a known attribute (allowed context is not empty)\n\t\t\t\t// and if it is, check if it is allowed on the given context.\n\t\t\t\tEnumSet<AttributeHolderType> allowedContexts = AttributeContexts.getAllowedContexts(attribute.type);\n\t\t\t\tif (!allowedContexts.isEmpty() && !allowedContexts.contains(context))\n\t\t\t\t\tthrow new IllegalStateException(\"Invalid\" + context.name().toLowerCase() + \" attribute: \" + attribute.type);\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {\n\t\t\tFieldVisitor fv = super.visitField(access, name, descriptor, signature, value);\n\t\t\treturn new FieldBuilderAdapter(fv, access, name, descriptor, signature, value) {\n\n\t\t\t\t@Override\n\t\t\t\tpublic void visitAttribute(Attribute attribute) {\n\t\t\t\t\tvalidateAttribute(attribute, AttributeHolderType.FIELD);\n\t\t\t\t\tfieldCustomAttributes.get(name).add(attribute);\n\t\t\t\t\tsuper.visitAttribute(attribute);\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic void visitEnd() {\n\t\t\t\t\tif (fields == null)\n\t\t\t\t\t\tfields = new ArrayList<>();\n\t\t\t\t\tfields.add(getFieldMember());\n\t\t\t\t\tsuper.visitEnd();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\t@Override\n\t\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\t\tMethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);\n\t\t\treturn new MethodBuilderAdapter(mv, access, name, descriptor, signature, exceptions) {\n\n\t\t\t\t@Override\n\t\t\t\tpublic void visitAttribute(Attribute attribute) {\n\t\t\t\t\tvalidateAttribute(attribute, AttributeHolderType.METHOD);\n\t\t\t\t\tmethodCustomAttributes.get(name).add(attribute);\n\t\t\t\t\tsuper.visitAttribute(attribute);\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic void visitEnd() {\n\t\t\t\t\tsuper.visitEnd();\n\t\t\t\t\tif (methods == null)\n\t\t\t\t\t\tmethods = new ArrayList<>();\n\t\t\t\t\tmethods.add(getMethodMember());\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\t@Override\n\t\tpublic RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {\n\t\t\tRecordComponentVisitor rcv = super.visitRecordComponent(name, descriptor, signature);\n\t\t\treturn new RecordComponentVisitor(getAsmVersion(), rcv) {\n\t\t\t\t@Override\n\t\t\t\tpublic void visitAttribute(Attribute attribute) {\n\t\t\t\t\tvalidateAttribute(attribute, AttributeHolderType.RECORD_COMPONENT);\n\t\t\t\t\tsuper.visitAttribute(attribute);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitEnd() {\n\t\t\tsuper.visitEnd();\n\t\t\tif (fields == null)\n\t\t\t\tfields = Collections.emptyList();\n\t\t\tif (methods == null)\n\t\t\t\tmethods = Collections.emptyList();\n\t\t\tif (innerClasses == null)\n\t\t\t\tinnerClasses = Collections.emptyList();\n\t\t\tif (annotations == null)\n\t\t\t\tannotations = Collections.emptyList();\n\t\t\tif (typeAnnotations == null)\n\t\t\t\ttypeAnnotations = Collections.emptyList();\n\t\t\twithAnnotations(annotations);\n\t\t\twithTypeAnnotations(typeAnnotations);\n\t\t\twithFields(fields);\n\t\t\twithMethods(methods);\n\t\t\twithInnerClasses(innerClasses);\n\t\t}\n\n\t\t/**\n\t\t * @return {@code true} when any custom attributes were found.\n\t\t */\n\t\tpublic boolean hasCustomAttributes() {\n\t\t\treturn (classCustomAttributes != null && !classCustomAttributes.isEmpty()) ||\n\t\t\t\t\t(!fieldCustomAttributes.isEmpty()) ||\n\t\t\t\t\t!methodCustomAttributes.isEmpty();\n\t\t}\n\n\t\t/**\n\t\t * @return Unique names of attributes found.\n\t\t */\n\t\t@Nonnull\n\t\tpublic Collection<String> getCustomAttributeNames() {\n\t\t\tSet<String> names = new TreeSet<>();\n\t\t\tif (classCustomAttributes != null)\n\t\t\t\tclassCustomAttributes.stream()\n\t\t\t\t\t\t.map(a -> a.type)\n\t\t\t\t\t\t.forEach(names::add);\n\t\t\tfieldCustomAttributes.values()\n\t\t\t\t\t.map(a -> a.type)\n\t\t\t\t\t.forEach(names::add);\n\t\t\tmethodCustomAttributes.values()\n\t\t\t\t\t.map(a -> a.type)\n\t\t\t\t\t.forEach(names::add);\n\t\t\treturn names;\n\t\t}\n\t}\n\n\tprivate static class FieldBuilderAdapter extends FieldVisitor {\n\t\tprivate final BasicFieldMember fieldMember;\n\n\t\tpublic FieldBuilderAdapter(FieldVisitor fv, int access, String name, String descriptor,\n\t\t                           String signature, Object value) {\n\t\t\tsuper(getAsmVersion(), fv);\n\t\t\tfieldMember = new BasicFieldMember(name, descriptor, signature, access, value);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\treturn new AnnotationBuilderAdapter(visible, descriptor, fieldMember::addAnnotation);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\treturn new AnnotationBuilderAdapter(visible, descriptor,\n\t\t\t\t\tanno -> fieldMember.addTypeAnnotation(anno.withTypeInfo(typeRef, typePath)));\n\t\t}\n\n\t\t@Nonnull\n\t\tpublic BasicFieldMember getFieldMember() {\n\t\t\treturn fieldMember;\n\t\t}\n\t}\n\n\tprivate static class MethodBuilderAdapter extends MethodVisitor {\n\t\tprivate final BasicMethodMember methodMember;\n\t\tprivate final Type methodDescriptor;\n\t\tprivate List<LocalVariable> parameters;\n\t\tprivate int parameterIndex;\n\t\tprivate int parameterSlot;\n\n\t\tpublic MethodBuilderAdapter(MethodVisitor mv, int access, String name, String descriptor,\n\t\t                            String signature, String[] exceptions) {\n\t\t\tsuper(getAsmVersion(), mv);\n\t\t\tList<String> exceptionList = exceptions == null ? Collections.emptyList() : Arrays.asList(exceptions);\n\t\t\tmethodMember = new BasicMethodMember(name, descriptor, signature, access, exceptionList);\n\t\t\tmethodDescriptor = Type.getMethodType(descriptor);\n\t\t\tparameterSlot = methodMember.hasStaticModifier() ? 0 : 1;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\treturn new AnnotationBuilderAdapter(visible, descriptor, methodMember::addAnnotation);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\treturn new AnnotationBuilderAdapter(visible, descriptor,\n\t\t\t\t\tanno -> methodMember.addTypeAnnotation(anno.withTypeInfo(typeRef, typePath)));\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {\n\t\t\tif (name != null && descriptor != null)\n\t\t\t\tmethodMember.addLocalVariable(new BasicLocalVariable(index, name, descriptor, signature));\n\t\t\tsuper.visitLocalVariable(name, descriptor, signature, start, end, index);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitParameter(String name, int access) {\n\t\t\tsuper.visitParameter(name, access);\n\n\t\t\tType[] argumentTypes = methodDescriptor.getArgumentTypes();\n\t\t\tif (parameterIndex < argumentTypes.length) {\n\t\t\t\tType argumentType = argumentTypes[parameterIndex];\n\n\t\t\t\t// Only add when we have a name for the parameter.\n\t\t\t\tif (name != null) {\n\t\t\t\t\tif (parameters == null)\n\t\t\t\t\t\tparameters = new ArrayList<>(methodDescriptor.getArgumentCount());\n\t\t\t\t\tparameters.add(new BasicLocalVariable(parameterSlot, name, argumentType.getDescriptor(), null));\n\t\t\t\t}\n\n\t\t\t\tparameterIndex++;\n\t\t\t\tparameterSlot += argumentType.getSize();\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotationDefault() {\n\t\t\treturn new DefaultAnnotationAdapter(anno -> {\n\t\t\t\tAnnotationElement element = anno.getElements().get(DefaultAnnotationAdapter.KEY);\n\t\t\t\tif (element != null) methodMember.setAnnotationDefault(element);\n\t\t\t});\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitEnd() {\n\t\t\tsuper.visitEnd();\n\n\t\t\t// Add local variables generated from the visited parameters if the local variable table hasn't already\n\t\t\t// provided variables for those indices. This assists in providing variable models for abstract methods.\n\t\t\t// This only works when a 'MethodParameters' attribute is present on the method. The javac compiler\n\t\t\t// emits this when passing '-parameters'.\n\t\t\tif (parameters != null)\n\t\t\t\tfor (LocalVariable parameter : parameters)\n\t\t\t\t\tif (methodMember.getLocalVariable(parameter.getIndex()) == null)\n\t\t\t\t\t\tmethodMember.addLocalVariable(parameter);\n\t\t}\n\n\t\t@Nonnull\n\t\tpublic BasicMethodMember getMethodMember() {\n\t\t\treturn methodMember;\n\t\t}\n\t}\n\n\tprivate static class AnnotationBuilderAdapter extends AnnotationVisitor {\n\t\tprivate final Consumer<BasicAnnotationInfo> annotationConsumer;\n\t\tprotected final Map<String, AnnotationElement> elements = new HashMap<>();\n\t\tprivate final List<Object> arrayValues = new ArrayList<>();\n\t\tprivate final List<BasicAnnotationInfo> subAnnotations = new ArrayList<>();\n\t\tprivate final boolean visible;\n\t\tprivate final String descriptor;\n\n\t\tprotected AnnotationBuilderAdapter(boolean visible, String descriptor,\n\t\t                                   Consumer<BasicAnnotationInfo> annotationConsumer) {\n\t\t\tsuper(getAsmVersion());\n\t\t\tthis.visible = visible;\n\t\t\tthis.descriptor = descriptor;\n\t\t\tthis.annotationConsumer = annotationConsumer;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visit(String name, Object value) {\n\t\t\tsuper.visit(name, value);\n\t\t\t// The 'value' can technically be a primitive array, but it doesn't really matter\n\t\t\t// how we capture it.\n\t\t\tif (name == null) {\n\t\t\t\tarrayValues.add(value);\n\t\t\t} else {\n\t\t\t\telements.put(name, new BasicAnnotationElement(name, value));\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitEnum(String name, String descriptor, String value) {\n\t\t\tsuper.visitEnum(name, descriptor, value);\n\t\t\tBasicAnnotationEnumReference enumRef = new BasicAnnotationEnumReference(descriptor, value);\n\t\t\tif (name == null) {\n\t\t\t\tarrayValues.add(enumRef);\n\t\t\t} else {\n\t\t\t\telements.put(name, new BasicAnnotationElement(name, enumRef));\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String name, String descriptor) {\n\t\t\treturn new AnnotationBuilderAdapter(true, descriptor, anno -> {\n\t\t\t\tif (name == null) {\n\t\t\t\t\tAnnotationBuilderAdapter.this.arrayValues.add(anno);\n\t\t\t\t} else {\n\t\t\t\t\tAnnotationBuilderAdapter.this.elements.put(name, new BasicAnnotationElement(name, anno));\n\t\t\t\t}\n\t\t\t}) {\n\t\t\t\t@Override\n\t\t\t\tprotected void populate(@Nonnull BasicAnnotationInfo anno) {\n\t\t\t\t\tsuper.populate(anno);\n\t\t\t\t\tAnnotationBuilderAdapter.this.subAnnotations.add(anno);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitArray(String name) {\n\t\t\tAnnotationBuilderAdapter outer = this;\n\t\t\treturn new AnnotationBuilderAdapter(true, \"\", null) {\n\t\t\t\t@Override\n\t\t\t\tpublic void visitEnd() {\n\t\t\t\t\tAnnotationBuilderAdapter inner = this;\n\t\t\t\t\touter.elements.put(name, new BasicAnnotationElement(name, inner.arrayValues));\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitEnd() {\n\t\t\tsuper.visitEnd();\n\t\t\tpopulate(new BasicAnnotationInfo(visible, descriptor));\n\t\t}\n\n\t\tprotected void populate(@Nonnull BasicAnnotationInfo anno) {\n\t\t\telements.forEach((name, value) -> anno.addElement(value));\n\t\t\tsubAnnotations.forEach(anno::addAnnotation);\n\t\t\tif (annotationConsumer != null) annotationConsumer.accept(anno);\n\t\t}\n\t}\n\n\tprivate static class DefaultAnnotationAdapter extends AnnotationBuilderAdapter {\n\t\tprivate static final String KEY = \"value\";\n\n\t\tprotected DefaultAnnotationAdapter(Consumer<BasicAnnotationInfo> annotationConsumer) {\n\t\t\tsuper(true, \"Ljava/lang/Object;\", annotationConsumer);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitEnum(String name, String descriptor, String value) {\n\t\t\tname = KEY;\n\n\t\t\tBasicAnnotationEnumReference enumRef = new BasicAnnotationEnumReference(descriptor, value);\n\t\t\telements.put(name, new BasicAnnotationElement(name, enumRef));\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/ModulesFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.BasicModulesFileInfo;\nimport software.coley.recaf.info.ModulesFileInfo;\n\n/**\n * Builder for {@link ModulesFileInfo}.\n *\n * @author Matt Coley\n */\npublic class ModulesFileInfoBuilder extends FileInfoBuilder<ModulesFileInfoBuilder> {\n\tpublic ModulesFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic ModulesFileInfoBuilder(ModulesFileInfo modulesFileInfo) {\n\t\tsuper(modulesFileInfo);\n\t}\n\n\tpublic ModulesFileInfoBuilder(FileInfoBuilder<?> other) {\n\t\tsuper(other);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicModulesFileInfo build() {\n\t\treturn new BasicModulesFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/NativeLibraryFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.BasicNativeLibraryFileInfo;\nimport software.coley.recaf.info.NativeLibraryFileInfo;\n\n/**\n * Builder for {@link NativeLibraryFileInfo}.\n *\n * @author Matt Coley\n */\npublic class NativeLibraryFileInfoBuilder extends FileInfoBuilder<NativeLibraryFileInfoBuilder> {\n\tpublic NativeLibraryFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic NativeLibraryFileInfoBuilder(NativeLibraryFileInfo libraryFileInfo) {\n\t\tsuper(libraryFileInfo);\n\t}\n\n\tpublic NativeLibraryFileInfoBuilder(FileInfoBuilder<?> other) {\n\t\tsuper(other);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicNativeLibraryFileInfo build() {\n\t\treturn new BasicNativeLibraryFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/TextFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.BasicTextFileInfo;\nimport software.coley.recaf.info.TextFileInfo;\nimport software.coley.recaf.util.StringDecodingResult;\n\nimport java.nio.charset.Charset;\nimport java.util.Objects;\n\n/**\n * Builder for {@link TextFileInfo}.\n *\n * @author Matt Coley\n */\npublic class TextFileInfoBuilder extends FileInfoBuilder<TextFileInfoBuilder> {\n\tpublic TextFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic TextFileInfoBuilder(@Nonnull TextFileInfo textInfo) {\n\t\tsuper(textInfo);\n\t\tthis.decodingResult = new StringDecodingResult(textInfo.getRawContent(), textInfo.getCharset(), textInfo.getText());\n\t}\n\n\tpublic TextFileInfoBuilder(@Nonnull FileInfoBuilder<?> other, @Nonnull StringDecodingResult decodingResult) {\n\t\tsuper(other);\n\t\tthis.decodingResult = decodingResult;\n\t}\n\n\t@Nonnull\n\tpublic TextFileInfoBuilder withText(@Nonnull String text) {\n\t\treturn withRawContent(text.getBytes(getCharset()));\n\t}\n\n\t@Nonnull\n\tpublic String getText() {\n\t\treturn Objects.requireNonNull(getDecodingResult().text(), \"File '\" + getName() + \"' could not be decoded\");\n\t}\n\n\t@Nonnull\n\tpublic Charset getCharset() {\n\t\treturn Objects.requireNonNull(getDecodingResult().charset(), \"File '\" + getName() + \"' could not be decoded\");\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicTextFileInfo build() {\n\t\treturn new BasicTextFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/VideoFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.BasicVideoFileInfo;\nimport software.coley.recaf.info.VideoFileInfo;\n\n/**\n * Builder for {@link VideoFileInfo}.\n *\n * @author Matt Coley\n */\npublic class VideoFileInfoBuilder extends FileInfoBuilder<VideoFileInfoBuilder> {\n\tpublic VideoFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic VideoFileInfoBuilder(VideoFileInfo videoFileInfo) {\n\t\tsuper(videoFileInfo);\n\t}\n\n\tpublic VideoFileInfoBuilder(FileInfoBuilder<?> other) {\n\t\tsuper(other);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicVideoFileInfo build() {\n\t\treturn new BasicVideoFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/WarFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.*;\n\n/**\n * Builder for {@link WarFileInfo}.\n *\n * @author Matt Coley\n */\npublic class WarFileInfoBuilder extends ZipFileInfoBuilder {\n\tpublic WarFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic WarFileInfoBuilder(@Nonnull WarFileInfo warInfo) {\n\t\tsuper(warInfo);\n\t}\n\n\tpublic WarFileInfoBuilder(@Nonnull FileInfoBuilder<?> other) {\n\t\tsuper(other);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicWarFileInfo build() {\n\t\treturn new BasicWarFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/builder/ZipFileInfoBuilder.java",
    "content": "package software.coley.recaf.info.builder;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.BasicZipFileInfo;\nimport software.coley.recaf.info.ZipFileInfo;\n\n/**\n * Builder for {@link ZipFileInfo}.\n *\n * @author Matt Coley\n * @see JarFileInfoBuilder\n * @see JModFileInfoBuilder\n * @see WarFileInfoBuilder\n * @see ApkFileInfoBuilder\n */\npublic class ZipFileInfoBuilder extends FileInfoBuilder<ZipFileInfoBuilder> {\n\tpublic ZipFileInfoBuilder() {\n\t\t// empty\n\t}\n\n\tpublic ZipFileInfoBuilder(@Nonnull ZipFileInfo zipInfo) {\n\t\tsuper(zipInfo);\n\t}\n\n\tpublic ZipFileInfoBuilder(@Nonnull FileInfoBuilder<?> other) {\n\t\tsuper(other);\n\t}\n\n\t@Nonnull\n\tpublic JarFileInfoBuilder asJar() {\n\t\treturn new JarFileInfoBuilder(this);\n\t}\n\n\t@Nonnull\n\tpublic ApkFileInfoBuilder asApk() {\n\t\treturn new ApkFileInfoBuilder(this);\n\t}\n\n\t@Nonnull\n\tpublic JModFileInfoBuilder asJMod() {\n\t\treturn new JModFileInfoBuilder(this);\n\t}\n\n\t@Nonnull\n\tpublic WarFileInfoBuilder asWar() {\n\t\treturn new WarFileInfoBuilder(this);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic BasicZipFileInfo build() {\n\t\treturn new BasicZipFileInfo(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/member/BasicFieldMember.java",
    "content": "package software.coley.recaf.info.member;\n\nimport java.util.Objects;\n\n/**\n * Basic implementation of a field member.\n *\n * @author Matt Coley\n */\npublic class BasicFieldMember extends BasicMember implements FieldMember {\n\tprivate final Object defaultValue;\n\n\t/**\n\t * @param name\n\t * \t\tField name.\n\t * @param desc\n\t * \t\tField descriptor.\n\t * @param signature\n\t * \t\tField generic signature. May be {@code null}.\n\t * @param access\n\t * \t\tField access modifiers.\n\t * @param defaultValue\n\t * \t\tDefault value. May be {@code null}.\n\t */\n\tpublic BasicFieldMember(String name, String desc, String signature, int access, Object defaultValue) {\n\t\tsuper(name, desc, signature, access);\n\t\tthis.defaultValue = defaultValue;\n\t}\n\n\t@Override\n\tpublic Object getDefaultValue() {\n\t\treturn defaultValue;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"Field: \" + getDescriptor() + \" \" + getName();\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || !FieldMember.class.isAssignableFrom(o.getClass())) return false;\n\n\t\tFieldMember field = (FieldMember) o;\n\n\t\tif (!getName().equals(field.getName())) return false;\n\t\tif (!Objects.equals(getSignature(), field.getSignature())) return false;\n\t\tif (!Objects.equals(getDefaultValue(), field.getDefaultValue())) return false;\n\t\treturn getDescriptor().equals(field.getDescriptor());\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = getName().hashCode();\n\t\tresult = 31 * result + getDescriptor().hashCode();\n\t\tif (getSignature() != null) result = 31 * result + getSignature().hashCode();\n\t\tif (getDefaultValue() != null) result = 31 * result + getDefaultValue().hashCode();\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/member/BasicLocalVariable.java",
    "content": "package software.coley.recaf.info.member;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport java.util.Objects;\n\n/**\n * Basic implementation of a local variable.\n *\n * @author Matt Coley\n */\npublic class BasicLocalVariable implements LocalVariable {\n\tprivate final int index;\n\tprivate final String name;\n\tprivate final String desc;\n\tprivate final String signature;\n\n\t/**\n\t * @param index\n\t * \t\tVariable index.\n\t * @param name\n\t * \t\tVariable name.\n\t * @param desc\n\t * \t\tVariable type.\n\t * @param signature\n\t * \t\tVariable generic type.\n\t */\n\tpublic BasicLocalVariable(int index, @Nonnull String name, @Nonnull String desc, @Nullable String signature) {\n\t\tthis.index = index;\n\t\tthis.name = name;\n\t\tthis.desc = desc;\n\t\tthis.signature = signature;\n\t}\n\n\t@Override\n\tpublic int getIndex() {\n\t\treturn index;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getDescriptor() {\n\t\treturn desc;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getSignature() {\n\t\treturn signature;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tBasicLocalVariable that = (BasicLocalVariable) o;\n\n\t\tif (index != that.index) return false;\n\t\tif (!name.equals(that.name)) return false;\n\t\tif (!desc.equals(that.desc)) return false;\n\t\treturn Objects.equals(signature, that.signature);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = index;\n\t\tresult = 31 * result + name.hashCode();\n\t\tresult = 31 * result + desc.hashCode();\n\t\tresult = 31 * result + (signature != null ? signature.hashCode() : 0);\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn index + \": \" + name;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/member/BasicMember.java",
    "content": "package software.coley.recaf.info.member;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.annotation.TypeAnnotationInfo;\nimport software.coley.recaf.info.properties.BasicPropertyContainer;\nimport software.coley.recaf.info.properties.Property;\nimport software.coley.recaf.info.properties.PropertyContainer;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Common base for basic member implementations.\n *\n * @author Matt Coley\n */\npublic abstract class BasicMember implements ClassMember {\n\tprivate final String name;\n\tprivate final String desc;\n\tprivate final String signature;\n\tprivate final int access;\n\tprivate PropertyContainer properties;\n\tprivate List<AnnotationInfo> annotations;\n\tprivate List<TypeAnnotationInfo> typeAnnotations;\n\tprivate ClassInfo declaringClass;\n\n\tprotected BasicMember(@Nonnull String name, @Nonnull String desc, @Nullable String signature, int access) {\n\t\tthis.name = name;\n\t\tthis.desc = desc;\n\t\tthis.signature = signature;\n\t\tthis.access = access;\n\t}\n\n\t/**\n\t * For internal use when populating the model.\n\t * Adding an annotation here does not change the bytecode of the class.\n\t *\n\t * @param annotation\n\t * \t\tAnnotation to add.\n\t */\n\tpublic void addAnnotation(@Nonnull AnnotationInfo annotation) {\n\t\tif (annotations == null)\n\t\t\tannotations = new ArrayList<>(2);\n\t\tannotations.add(annotation);\n\t}\n\n\t/**\n\t * For internal use when populating the model.\n\t * Adding an annotation here does not change the bytecode of the class.\n\t *\n\t * @param typeAnnotation\n\t * \t\tAnnotation to add.\n\t */\n\tpublic void addTypeAnnotation(@Nonnull TypeAnnotationInfo typeAnnotation) {\n\t\tif (typeAnnotations == null)\n\t\t\ttypeAnnotations = new ArrayList<>(2);\n\t\ttypeAnnotations.add(typeAnnotation);\n\t}\n\n\t/**\n\t * For internal use when populating the model.\n\t *\n\t * @param declaringClass\n\t * \t\tDeclaring class to assign.\n\t */\n\tpublic void setDeclaringClass(@Nonnull ClassInfo declaringClass) {\n\t\tthis.declaringClass = declaringClass;\n\t}\n\n\t@Override\n\tpublic ClassInfo getDeclaringClass() {\n\t\treturn declaringClass;\n\t}\n\n\t@Override\n\tpublic int getAccess() {\n\t\treturn access;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getDescriptor() {\n\t\treturn desc;\n\t}\n\n\t@Override\n\tpublic String getSignature() {\n\t\treturn signature;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<AnnotationInfo> getAnnotations() {\n\t\tif (annotations == null)\n\t\t\treturn Collections.emptyList();\n\t\treturn annotations;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<TypeAnnotationInfo> getTypeAnnotations() {\n\t\tif (typeAnnotations == null)\n\t\t\treturn Collections.emptyList();\n\t\treturn typeAnnotations;\n\t}\n\n\t@Override\n\tpublic <V> void setProperty(Property<V> property) {\n\t\tif (properties == null)\n\t\t\tproperties = new BasicPropertyContainer();\n\t\tproperties.setProperty(property);\n\t}\n\n\t@Override\n\tpublic void removeProperty(String key) {\n\t\tif (properties != null)\n\t\t\tproperties.removeProperty(key);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, Property<?>> getProperties() {\n\t\tif (properties == null)\n\t\t\treturn Collections.emptyMap();\n\t\treturn properties.getProperties();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/member/BasicMethodMember.java",
    "content": "package software.coley.recaf.info.member;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.annotation.AnnotationElement;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * Basic implementation of a method member.\n *\n * @author Matt Coley\n */\npublic class BasicMethodMember extends BasicMember implements MethodMember {\n\tprivate final List<String> thrownTypes;\n\tprivate List<LocalVariable> variables;\n\tprivate AnnotationElement annotationDefault;\n\n\t/**\n\t * @param name\n\t * \t\tMethod name.\n\t * @param desc\n\t * \t\tMethod descriptor.\n\t * @param signature\n\t * \t\tMethod generic signature. May be {@code null}.\n\t * @param access\n\t * \t\tMethod access modifiers.\n\t * @param thrownTypes\n\t * \t\tMethod's thrown exceptions.\n\t */\n\tpublic BasicMethodMember(@Nonnull String name, @Nonnull String desc, @Nullable String signature, int access,\n\t                         @Nonnull List<String> thrownTypes) {\n\t\tsuper(name, desc, signature, access);\n\t\tthis.thrownTypes = thrownTypes;\n\t}\n\n\t/**\n\t * @param variable\n\t * \t\tVariable to add.\n\t */\n\tpublic void addLocalVariable(@Nonnull LocalVariable variable) {\n\t\tif (variables == null)\n\t\t\tvariables = new ArrayList<>();\n\t\tvariables.add(variable);\n\t}\n\n\t/**\n\t * @param annotationDefault\n\t * \t\tElement value to set.\n\t */\n\tpublic void setAnnotationDefault(@Nonnull AnnotationElement annotationDefault) {\n\t\tthis.annotationDefault = annotationDefault;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<String> getThrownTypes() {\n\t\treturn thrownTypes;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<LocalVariable> getLocalVariables() {\n\t\tif (variables == null)\n\t\t\treturn Collections.emptyList();\n\t\treturn variables;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic AnnotationElement getAnnotationDefault() {\n\t\treturn annotationDefault;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"Method: \" + getName() + getDescriptor();\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || !MethodMember.class.isAssignableFrom(o.getClass())) return false;\n\n\t\tMethodMember method = (MethodMember) o;\n\n\t\tif (!getName().equals(method.getName())) return false;\n\t\tif (!Objects.equals(getSignature(), method.getSignature())) return false;\n\t\tif (!getThrownTypes().equals(method.getThrownTypes())) return false;\n\t\tif (!getLocalVariables().equals(method.getLocalVariables())) return false;\n\t\treturn getDescriptor().equals(method.getDescriptor());\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = getName().hashCode();\n\t\tresult = 31 * result + getDescriptor().hashCode();\n\t\tresult = 31 * result + getThrownTypes().hashCode();\n\t\tresult = 31 * result + getLocalVariables().hashCode();\n\t\tif (getSignature() != null) result = 31 * result + getSignature().hashCode();\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/member/ClassMember.java",
    "content": "package software.coley.recaf.info.member;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.Accessed;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.Named;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.properties.PropertyContainer;\n\n/**\n * Component of a {@link ClassInfo}.\n *\n * @author Matt Coley\n */\npublic interface ClassMember extends PropertyContainer, Annotated, Accessed, Named {\n\t/**\n\t * @return Member name.\n\t */\n\t@Nonnull\n\tString getName();\n\n\t/**\n\t * @return Member descriptor.\n\t */\n\t@Nonnull\n\tString getDescriptor();\n\n\t/**\n\t * @return Signature containing generic information. May be {@code null}.\n\t */\n\t@Nullable\n\tString getSignature();\n\n\t/**\n\t * @return The class declaring this member.\n\t * May be {@code null} if this information is not known.\n\t */\n\t@Nullable\n\tdefault ClassInfo getDeclaringClass() {\n\t\t// Not required to be implemented.\n\t\treturn null;\n\t}\n\n\t/**\n\t * @return {@code true} when the member is aware of its declaring {@link ClassInfo}\n\t */\n\tdefault boolean isDeclarationAware() {\n\t\treturn getDeclaringClass() != null;\n\t}\n\n\t/**\n\t * @return {@code true} when the member is a field.\n\t */\n\tboolean isField();\n\n\t/**\n\t * @return {@code true} when the member is a method.\n\t */\n\tboolean isMethod();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/member/FieldMember.java",
    "content": "package software.coley.recaf.info.member;\n\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\n\n/**\n * Field component of a {@link ClassInfo}.\n *\n * @author Matt Coley\n */\npublic interface FieldMember extends ClassMember {\n\t/**\n\t * Fields can declare default values.\n\t * Only acknowledged by the JVM when {@link #hasStaticModifier()} is {@code true}.\n\t *\n\t * @return Default value of the field. Must be an {@code Integer}, a {@code Float}, a {@code Long},\n\t * a {@code Double} or a {@code String}. May be {@code null}.\n\t */\n\t@Nullable\n\tObject getDefaultValue();\n\n\t@Override\n\tdefault boolean isField() {\n\t\treturn true;\n\t}\n\n\t@Override\n\tdefault boolean isMethod() {\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/member/LocalVariable.java",
    "content": "package software.coley.recaf.info.member;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\n/**\n * Local variable component of a {@link MethodMember}.\n *\n * @author Matt Coley\n */\npublic interface LocalVariable {\n\t/**\n\t * @return Variable index.\n\t */\n\tint getIndex();\n\n\t/**\n\t * @return Variable name.\n\t */\n\t@Nonnull\n\tString getName();\n\n\t/**\n\t * @return Variable type.\n\t */\n\t@Nonnull\n\tString getDescriptor();\n\n\t/**\n\t * @return Variable generic type.\n\t */\n\t@Nullable\n\tString getSignature();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/member/MethodMember.java",
    "content": "package software.coley.recaf.info.member;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.annotation.AnnotationElement;\n\nimport java.util.List;\n\n/**\n * Field component of a {@link ClassInfo}.\n *\n * @author Matt Coley\n */\npublic interface MethodMember extends ClassMember {\n\t/**\n\t * @return List of thrown exceptions.\n\t */\n\t@Nonnull\n\tList<String> getThrownTypes();\n\n\t/**\n\t * @return List of local variables.\n\t */\n\t@Nonnull\n\tList<LocalVariable> getLocalVariables();\n\n\t/**\n\t * @return Element holding the default value for an annotation method.\n\t */\n\t@Nullable\n\tAnnotationElement getAnnotationDefault();\n\n\t/**\n\t * @param index\n\t * \t\tLocal variable index.\n\t *\n\t * @return Local variable at the index, or {@code null} if not known.\n\t */\n\t@Nullable\n\tdefault LocalVariable getLocalVariable(int index) {\n\t\treturn getLocalVariables().stream()\n\t\t\t\t.filter(v -> index == v.getIndex())\n\t\t\t\t.findFirst().orElse(null);\n\t}\n\n\t@Override\n\tdefault boolean isField() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tdefault boolean isMethod() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/BasicProperty.java",
    "content": "package software.coley.recaf.info.properties;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Objects;\n\n/**\n * Basic property implementation.\n *\n * @param <V>\n * \t\tValue type.\n *\n * @author Matt Coley\n */\npublic class BasicProperty<V> implements Property<V> {\n\tprivate final String key;\n\tprivate final V value;\n\n\t/**\n\t * @param key\n\t * \t\tProperty key.\n\t * @param value\n\t * \t\tProperty value.\n\t */\n\tpublic BasicProperty(String key, V value) {\n\t\tthis.key = key;\n\t\tthis.value = value;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String key() {\n\t\treturn key;\n\t}\n\n\t@Override\n\tpublic V value() {\n\t\treturn value;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tBasicProperty<?> that = (BasicProperty<?>) o;\n\n\t\tif (!Objects.equals(key, that.key)) return false;\n\t\treturn Objects.equals(value, that.value);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = key != null ? key.hashCode() : 0;\n\t\tresult = 31 * result + (value != null ? value.hashCode() : 0);\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"SimpleProperty{\" +\n\t\t\t\t\"key='\" + key + '\\'' +\n\t\t\t\t\", value=\" + value +\n\t\t\t\t'}';\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/BasicPropertyContainer.java",
    "content": "package software.coley.recaf.info.properties;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * Basic implementation of property container.\n *\n * @author Matt Coley\n */\npublic class BasicPropertyContainer implements PropertyContainer {\n\tprivate Map<String, Property<?>> properties;\n\n\t/**\n\t * Container with empty map.\n\t */\n\tpublic BasicPropertyContainer() {\n\t\tthis(null);\n\t}\n\n\t/**\n\t * @param properties\n\t * \t\tPre-defined property map.\n\t */\n\tpublic BasicPropertyContainer(@Nullable Map<String, Property<?>> properties) {\n\t\tthis.properties = properties == null || properties.isEmpty() ? null : new HashMap<>(properties);\n\t}\n\n\t@Override\n\tpublic <V> void setProperty(Property<V> property) {\n\t\tif (properties == null) // Memory optimization to keep null by default\n\t\t\tproperties = new HashMap<>();\n\t\tproperties.put(property.key(), property);\n\t}\n\n\t@Override\n\tpublic void removeProperty(String key) {\n\t\tif (properties != null)\n\t\t\tproperties.remove(key);\n\t}\n\n\t@Nonnull\n\tpublic Map<String, Property<?>> getProperties() {\n\t\tif (properties == null)\n\t\t\treturn Collections.emptyMap();\n\t\t// Disallow modification\n\t\treturn Collections.unmodifiableMap(properties);\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tBasicPropertyContainer other = (BasicPropertyContainer) o;\n\n\t\treturn Objects.equals(properties, other.properties);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tif (properties == null) return 0;\n\t\treturn properties.hashCode();\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\tString typeName = getClass().getSimpleName();\n\t\tif (properties == null) return typeName + \"[0]\";\n\t\treturn typeName + \"[\" + properties.size() + \" items]\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/Property.java",
    "content": "package software.coley.recaf.info.properties;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\n/**\n * Basic property outline.\n *\n * @param <V>\n * \t\tValue type.\n *\n * @author Matt Coley\n */\npublic interface Property<V> {\n\t/**\n\t * @return Property key.\n\t */\n\t@Nonnull\n\tString key();\n\n\t/**\n\t * @return Property value.\n\t */\n\t@Nullable\n\tV value();\n\n\t/**\n\t * Generally, when an info type is updated in a workspace it will be copying state from the original value.\n\t * When that occurs properties of the original info instance get copied to the new one. Not all properties should\n\t * be copied though. Consider these two cases:\n\t * <ul>\n\t *     <li>{@link software.coley.recaf.info.properties.builtin.ZipCommentProperty} -\n\t *     Is based on the input file content. Will never change based on info state, thus\n\t *     should be copied between info instances.</li>\n\t *     <li>{@link software.coley.recaf.info.properties.builtin.CachedDecompileProperty} -\n\t *     Caches the decompiled code of a class file to prevent duplicate work. Changes when info state\n\t *     <i>(bytecode)</i> is updated, thus should <b>NOT</b> be copied between info instances</li>\n\t * </ul>\n\t * By default, this returns {@code true}.\n\t *\n\t * @return {@code true} to represent content that should be persisted across instances.\n\t * {@code false} to represent content that should be dropped between instances.\n\t */\n\tdefault boolean persistent() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/PropertyContainer.java",
    "content": "package software.coley.recaf.info.properties;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport java.util.Map;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\n\n/**\n * Outline of a type with additional properties able to be assigned.\n *\n * @author Matt Coley\n */\npublic interface PropertyContainer {\n\t/**\n\t * @param key\n\t * \t\tKey of property to set.\n\t * @param value\n\t * \t\tValue of property to set.\n\t * @param <V>\n\t * \t\tProperty value type.\n\t */\n\tdefault <V> void setPropertyValue(String key, V value) {\n\t\tsetProperty(new BasicProperty<>(key, value));\n\t}\n\n\t/**\n\t * @param property\n\t * \t\tProperty to set.\n\t * @param <V>\n\t * \t\tProperty value type.\n\t */\n\t<V> void setProperty(Property<V> property);\n\n\t/**\n\t * @param key\n\t * \t\tKey of property to set.\n\t * @param property\n\t * \t\tProperty to set, if no value is associated with the given key.\n\t * @param <V>\n\t * \t\tProperty value type.\n\t */\n\tdefault <V> void setPropertyIfMissing(String key, Supplier<Property<V>> property) {\n\t\tif (getProperty(key) == null)\n\t\t\tsetProperty(property.get());\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tKey of property to remove.\n\t */\n\tvoid removeProperty(String key);\n\n\t/**\n\t * @param key\n\t * \t\tProperty key.\n\t * @param <V>\n\t * \t\tProperty value type.\n\t *\n\t * @return Property associated with key. May be {@code null} for unknown keys.\n\t */\n\t@Nullable\n\t@SuppressWarnings(\"unchecked\")\n\tdefault <V> Property<V> getProperty(String key) {\n\t\treturn (Property<V>) getProperties().get(key);\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tProperty key.\n\t * @param <V>\n\t * \t\tProperty value type.\n\t *\n\t * @return Value of property, or {@code null} if the property is not set.\n\t */\n\t@Nullable\n\tdefault <V> V getPropertyValueOrNull(String key) {\n\t\tProperty<V> property = getProperty(key);\n\t\tif (property == null)\n\t\t\treturn null;\n\t\treturn property.value();\n\t}\n\n\t/**\n\t * @return Properties.\n\t */\n\t@Nonnull\n\tMap<String, Property<?>> getProperties();\n\n\t/**\n\t * @return Properties, but only those that are {@link Property#persistent()}.\n\t */\n\tdefault Map<String, Property<?>> getPersistentProperties() {\n\t\treturn getProperties().entrySet().stream()\n\t\t\t\t.filter((e) -> e.getValue().persistent())\n\t\t\t\t.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/BinaryXmlDecodedProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.BinaryXmlFileInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.BasicProperty;\n\n/**\n * Built in property for caching the decoded contents of {@link BinaryXmlFileInfo} items.\n *\n * @author Matt Coley\n */\npublic class BinaryXmlDecodedProperty extends BasicProperty<String> {\n\tpublic static final String KEY = \"decoded-bin-xml\";\n\n\t/**\n\t * @param value\n\t * \t\tDecoded content.\n\t */\n\tpublic BinaryXmlDecodedProperty(@Nonnull String value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Decoded XML associated with instance, or {@code null} when no association exists.\n\t */\n\t@Nullable\n\tpublic static String get(@Nonnull Info info) {\n\t\treturn info.getPropertyValueOrNull(KEY);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param xml\n\t * \t\tDecoded XML to associate with the item.\n\t */\n\tpublic static void set(@Nonnull Info info, @Nonnull String xml) {\n\t\tinfo.setProperty(new BinaryXmlDecodedProperty(xml));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull Info info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/CachedDecompileProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.services.decompile.DecompileResult;\nimport software.coley.recaf.services.decompile.Decompiler;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Built in property to cache decompilation results for {@link software.coley.recaf.info.ClassInfo} instances,\n * reducing wasted duplicate work on decompiling the same code over and over again.\n *\n * @author Matt Coley\n */\npublic class CachedDecompileProperty extends BasicProperty<CachedDecompileProperty.Cache> {\n\tpublic static final String KEY = \"cached-decompiled-code\";\n\n\t/**\n\t * New empty cache.\n\t */\n\tpublic CachedDecompileProperty() {\n\t\tsuper(KEY, new Cache());\n\t}\n\n\t/**\n\t * @param classInfo\n\t * \t\tClass to cache decompilation of.\n\t * @param decompiler\n\t * \t\tAssociated  decompiler.\n\t * @param result\n\t * \t\tDecompiler result to cache.\n\t */\n\tpublic static void set(@Nonnull ClassInfo classInfo, @Nonnull Decompiler decompiler,\n\t\t\t\t\t\t   @Nonnull DecompileResult result) {\n\t\tCache cache = classInfo.getPropertyValueOrNull(KEY);\n\t\tif (cache == null) {\n\t\t\tCachedDecompileProperty property = new CachedDecompileProperty();\n\t\t\tclassInfo.setProperty(property);\n\t\t\tcache = property.value();\n\t\t}\n\t\t// Save to cache\n\t\tcache.save(decompiler.getName(), result);\n\t}\n\n\t/**\n\t * @param classInfo\n\t * \t\tClass with cached decompilation.\n\t * @param decompiler\n\t * \t\tAssociated decompiler.\n\t *\n\t * @return Cached decompilation result, or {@code null} when no cached value exists.\n\t */\n\t@Nullable\n\tpublic static DecompileResult get(@Nonnull ClassInfo classInfo, @Nonnull Decompiler decompiler) {\n\t\tCache cache = classInfo.getPropertyValueOrNull(KEY);\n\t\tif (cache == null) return null;\n\t\treturn cache.get(decompiler.getName());\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull ClassInfo info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n\n\t@Override\n\tpublic boolean persistent() {\n\t\t// We should disregard decompilation results between 'versions' of an info object.\n\t\treturn false;\n\t}\n\n\t/**\n\t * Basic cache for decompiler results.\n\t */\n\tpublic static class Cache {\n\t\tprivate final Map<String, DecompileResult> implToCode = new HashMap<>();\n\n\t\t/**\n\t\t * @param decompilerId\n\t\t * \t\tUnique ID of decompiler.\n\t\t *\n\t\t * @return Decompiler result of prior run.\n\t\t */\n\t\tpublic DecompileResult get(String decompilerId) {\n\t\t\treturn implToCode.get(decompilerId);\n\t\t}\n\n\t\t/**\n\t\t * @param decompilerId\n\t\t * \t\tUnique ID of decompiler.\n\t\t * @param result\n\t\t * \t\tDecompiler result to cache.\n\t\t */\n\t\tpublic void save(String decompilerId, DecompileResult result) {\n\t\t\timplToCode.put(decompilerId, result);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/HasMappedReferenceProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.properties.BasicProperty;\n\n/**\n * Built in property to track if an {@link ClassInfo} references mapped classes or members.\n *\n * @author Matt Coley\n * @see OriginalClassNameProperty Classes that have been renamed also have this applied.\n */\npublic class HasMappedReferenceProperty extends BasicProperty<Void> {\n\tpublic static final String KEY = \"has-mapped-ref\";\n\tprivate static final HasMappedReferenceProperty INSTANCE = new HasMappedReferenceProperty();\n\n\tprivate HasMappedReferenceProperty() {\n\t\tsuper(KEY, null);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass to mark as having a mapped reference.\n\t */\n\tpublic static void set(@Nonnull ClassInfo info) {\n\t\tinfo.setProperty(INSTANCE);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass to strip this marker from.\n\t */\n\tpublic static void remove(@Nonnull ClassInfo info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass to check for this marker on.\n\t *\n\t * @return {@code true} when the property is set on the class.\n\t */\n\tpublic static boolean get(@Nonnull ClassInfo info) {\n\t\treturn info.getProperties().containsKey(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/IllegalClassSuspectProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.info.properties.Property;\n\n/**\n * Built in property to track {@link FileInfo} instances that look like {@link ClassInfo} but could not be parsed.\n *\n * @author Matt Coley\n */\npublic class IllegalClassSuspectProperty extends BasicProperty<Boolean> {\n\tpublic static final IllegalClassSuspectProperty INSTANCE = new IllegalClassSuspectProperty();\n\tpublic static final String KEY = \"ill-class\";\n\n\t/**\n\t * New property value.\n\t */\n\tprivate IllegalClassSuspectProperty() {\n\t\tsuper(KEY, true);\n\t}\n\n\t@Override\n\tpublic boolean persistent() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tFile info instance\n\t *\n\t * @return {@code true} when the file is a suspected malformed class.\n\t */\n\tpublic static boolean get(@Nonnull FileInfo info) {\n\t\tProperty<Boolean> property = info.getProperty(KEY);\n\t\tif (property != null) {\n\t\t\tBoolean value = property.value();\n\t\t\treturn value != null && value;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Marks the file as being a suspected malformed class.\n\t *\n\t * @param info\n\t * \t\tFile info instance.\n\t */\n\tpublic static void set(@Nonnull FileInfo info) {\n\t\tinfo.setProperty(IllegalClassSuspectProperty.INSTANCE);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tFile info instance.\n\t */\n\tpublic static void remove(@Nonnull FileInfo info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/InputFilePathProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.BasicProperty;\n\nimport java.nio.file.Path;\n\n/**\n * Built in property for associating a {@link Path} with some info type.\n *\n * @author Matt Coley\n */\npublic class InputFilePathProperty extends BasicProperty<Path> {\n\tpublic static final String KEY = \"input-file-path\";\n\n\t/**\n\t * @param value\n\t * \t\tInput path.\n\t */\n\tpublic InputFilePathProperty(@Nonnull Path value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Input path associated with instance, or {@code null} when no association exists.\n\t */\n\t@Nullable\n\tpublic static Path get(@Nonnull Info info) {\n\t\treturn info.getPropertyValueOrNull(KEY);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param inputPath\n\t * \t\tInput path to associate with the item.\n\t */\n\tpublic static void set(@Nonnull Info info, @Nonnull Path inputPath) {\n\t\tinfo.setProperty(new InputFilePathProperty(inputPath));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull Info info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/MemberIndexAcceleratorProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.info.properties.Property;\n\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * Used to accelerate the {@link ClassMember} to {@code int index} lookup process in extreme scenarios.\n *\n * @author Matt Coley\n */\npublic class MemberIndexAcceleratorProperty extends BasicProperty<Map<ClassMember, Integer>> {\n\tpublic static final String KEY = \"member-index-accel\";\n\t/**\n\t * Number of elements required to enable use of this property.\n\t */\n\tpublic static final int CUTOFF = 1000;\n\n\t/**\n\t * @param classInfo\n\t * \t\tTarget class to pull fields/methods from.\n\t */\n\tpublic MemberIndexAcceleratorProperty(@Nonnull ClassInfo classInfo) {\n\t\tsuper(KEY, new IdentityHashMap<>());\n\t\tMap<ClassMember, Integer> value = Objects.requireNonNull(value());\n\t\tList<FieldMember> fields = classInfo.getFields();\n\t\tfor (int i = 0; i < fields.size(); i++)\n\t\t\tvalue.put(fields.get(i), i);\n\t\tList<MethodMember> methods = classInfo.getMethods();\n\t\tfor (int i = 0; i < methods.size(); i++)\n\t\t\tvalue.put(methods.get(i), i);\n\t}\n\n\t/**\n\t * Get or creates an {@link MemberIndexAcceleratorProperty} for the given {@link ClassInfo}.\n\t *\n\t * @param classInfo\n\t * \t\tTarget class to pull fields/methods from.\n\t *\n\t * @return Accelerator property for class.\n\t */\n\t@Nonnull\n\tpublic static MemberIndexAcceleratorProperty get(@Nonnull ClassInfo classInfo) {\n\t\tProperty<?> property = classInfo.getProperty(KEY);\n\t\tif (property instanceof MemberIndexAcceleratorProperty acceleratorProperty)\n\t\t\treturn acceleratorProperty;\n\t\tMemberIndexAcceleratorProperty acceleratorProperty = new MemberIndexAcceleratorProperty(classInfo);\n\t\tclassInfo.setProperty(acceleratorProperty);\n\t\treturn acceleratorProperty;\n\t}\n\n\t/**\n\t * @param member\n\t * \t\tMember to get an index of. Must match an exact instance of the member in the containing {@link ClassInfo}.\n\t *\n\t * @return Index of member, or {@code -1} if not present in the class.\n\t */\n\tpublic int indexOf(@Nonnull ClassMember member) {\n\t\treturn Objects.requireNonNull(value()).getOrDefault(member, -1);\n\t}\n\n\t@Override\n\tpublic boolean persistent() {\n\t\t// Member instances change between class info containers, so we dont want to persist this.\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/OriginalClassNameProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.services.mapping.MappingApplier;\n\n/**\n * Built in property to track the original name of an {@link ClassInfo}.\n *\n * @author Matt Coley\n * @see HasMappedReferenceProperty If a class isn't mapped, but has references to something that is, this is added.\n * @see MappingApplier Adds this property to renamed classes.\n */\npublic class OriginalClassNameProperty extends BasicProperty<String> {\n\tpublic static final String KEY = \"original-class-name\";\n\n\t/**\n\t * @param value\n\t * \t\tOriginal class name.\n\t */\n\tpublic OriginalClassNameProperty(@Nonnull String value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass info instance.\n\t *\n\t * @return Original name of the class if set, otherwise the existing class name.\n\t */\n\t@Nonnull\n\tpublic static String map(@Nonnull ClassInfo info) {\n\t\tString name = info.getName();\n\t\tString original = info.getPropertyValueOrNull(KEY);\n\t\tif (original != null)\n\t\t\treturn original;\n\t\treturn name;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass info instance.\n\t *\n\t * @return Class name associated with instance, or {@code null} when no association exists.\n\t */\n\t@Nullable\n\tpublic static String get(@Nonnull ClassInfo info) {\n\t\treturn info.getPropertyValueOrNull(KEY);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass info instance.\n\t * @param original\n\t * \t\tOriginal class name to associate with the item.\n\t */\n\tpublic static void set(@Nonnull ClassInfo info, @Nonnull String original) {\n\t\tinfo.setProperty(new OriginalClassNameProperty(original));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass info instance.\n\t */\n\tpublic static void remove(@Nonnull ClassInfo info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/PathOriginalNameProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.BasicProperty;\n\n/**\n * Built in property to track the original name of an {@link Info} type, primarily {@link ClassInfo} items.\n *\n * @author Matt Coley\n * @see PathPrefixProperty\n * @see PathSuffixProperty\n */\npublic class PathOriginalNameProperty extends BasicProperty<String> {\n\tpublic static final String KEY = \"path-original-full\";\n\n\t/**\n\t * @param value\n\t * \t\tOriginal path name.\n\t */\n\tpublic PathOriginalNameProperty(@Nonnull String value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Original name of the info if set, otherwise the existing info name.\n\t */\n\t@Nonnull\n\tpublic static String map(@Nonnull Info info) {\n\t\tString name = info.getName();\n\t\tString original = info.getPropertyValueOrNull(KEY);\n\t\tif (original != null)\n\t\t\treturn original;\n\t\treturn name;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Original name associated with instance, or {@code null} when no association exists.\n\t */\n\t@Nullable\n\tpublic static String get(@Nonnull Info info) {\n\t\treturn info.getPropertyValueOrNull(KEY);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param original\n\t * \t\tOriginal name to associate with the item.\n\t */\n\tpublic static void set(@Nonnull Info info, @Nonnull String original) {\n\t\tinfo.setProperty(new PathOriginalNameProperty(original));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull Info info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/PathPrefixProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.BasicProperty;\n\n/**\n * Built in property to track the original suffix of an {@link Info} type, primarily {@link ClassInfo} items.\n * <br>\n * Consider a WAR file, which prefixes all classes with {@link software.coley.recaf.info.WarFileInfo#WAR_CLASS_PREFIX}.\n * When we export the workspace, the {@link ClassInfo#getName()} will only yield the JVM name of the class, but not\n * the prefix. Thus, with this property holding the class prefix we can restore the full path of the {@link ClassInfo}\n * when exporting.\n *\n * @author Matt Coley\n * @see PathSuffixProperty\n * @see PathOriginalNameProperty\n */\npublic class PathPrefixProperty extends BasicProperty<String> {\n\tpublic static final String KEY = \"path-prefix\";\n\n\t/**\n\t * @param value\n\t * \t\tPrefix.\n\t */\n\tpublic PathPrefixProperty(@Nonnull String value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Name of the info, with the suffix applied if any exist.\n\t */\n\t@Nonnull\n\tpublic static String map(@Nonnull Info info) {\n\t\tString name = info.getName();\n\t\tString prefix = get(info);\n\t\tif (prefix != null)\n\t\t\treturn prefix + name;\n\t\treturn name;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Prefix associated with instance, or {@code null} when no association exists.\n\t */\n\t@Nullable\n\tpublic static String get(@Nonnull Info info) {\n\t\treturn info.getPropertyValueOrNull(KEY);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param prefix\n\t * \t\tSuffix to associate with the item.\n\t */\n\tpublic static void set(@Nonnull Info info, @Nonnull String prefix) {\n\t\tinfo.setProperty(new PathPrefixProperty(prefix));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull Info info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/PathSuffixProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.BasicProperty;\n\n/**\n * Built in property to track the suffix of an {@link Info} type, primarily the extension of {@link ClassInfo} files.\n * In most cases the value will be {@code .class}.\n *\n * @author Matt Coley\n * @see PathPrefixProperty\n * @see PathOriginalNameProperty\n */\npublic class PathSuffixProperty extends BasicProperty<String> {\n\tpublic static final String KEY = \"path-suffix\";\n\n\t/**\n\t * @param value\n\t * \t\tSuffix.\n\t */\n\tpublic PathSuffixProperty(@Nonnull String value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Name of the info, with the suffix applied if any exist.\n\t */\n\t@Nonnull\n\tpublic static String map(@Nonnull Info info) {\n\t\tString name = info.getName();\n\t\tString suffix = get(info);\n\t\tif (suffix != null)\n\t\t\treturn name + suffix;\n\t\treturn name;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Suffix associated with instance, or {@code null} when no association exists.\n\t */\n\t@Nullable\n\tpublic static String get(@Nonnull Info info) {\n\t\treturn info.getPropertyValueOrNull(KEY);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param suffix\n\t * \t\tSuffix to associate with the item.\n\t */\n\tpublic static void set(@Nonnull Info info, @Nonnull String suffix) {\n\t\tinfo.setProperty(new PathSuffixProperty(suffix));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull Info info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/ReferencedClassesProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.properties.BasicProperty;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.NavigableSet;\nimport java.util.Objects;\nimport java.util.TreeSet;\n\n/**\n * Built in property to track what classes are referenced by this type.\n *\n * @author Matt Coley\n */\npublic class ReferencedClassesProperty extends BasicProperty<NavigableSet<String>> {\n\tpublic static final String KEY = \"referenced-classes\";\n\n\t/**\n\t * @param classes\n\t * \t\tCollection of referenced classes.\n\t */\n\tpublic ReferencedClassesProperty(@Nonnull Collection<String> classes) {\n\t\tsuper(KEY, Collections.unmodifiableNavigableSet(new TreeSet<>(classes)));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Set of referenced classes, or {@code null} when no association exists.\n\t */\n\t@Nullable\n\tpublic static NavigableSet<String> get(@Nonnull ClassInfo info) {\n\t\treturn info.getPropertyValueOrNull(KEY);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param classes\n\t * \t\tCollection of referenced classes.\n\t *\n\t * @return Sorted set of referenced classes assigned.\n\t */\n\t@Nonnull\n\tpublic static NavigableSet<String> set(@Nonnull ClassInfo info, @Nonnull Collection<String> classes) {\n\t\tReferencedClassesProperty property = new ReferencedClassesProperty(classes);\n\t\tinfo.setProperty(property);\n\t\treturn Objects.requireNonNull(property.value());\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull ClassInfo info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n\n\t@Override\n\tpublic boolean persistent() {\n\t\t// Modifications to a class will likely invalidate this data\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/RemapOriginTaskProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.services.mapping.MappingApplier;\nimport software.coley.recaf.services.mapping.MappingResults;\nimport software.coley.recaf.workspace.model.resource.ResourceAndroidClassListener;\nimport software.coley.recaf.workspace.model.resource.ResourceJvmClassListener;\n\n/**\n * Built in property associating the creation of a {@link ClassInfo} with a\n * mapping operation. This can be used to check if a class, when received in a listener\n * such as {@link ResourceJvmClassListener} or {@link ResourceAndroidClassListener} has\n * been created as a result of usage of {@link MappingApplier}.\n *\n * @author Matt Coley\n */\npublic class RemapOriginTaskProperty extends BasicProperty<MappingResults> {\n\tpublic static final String KEY = \"remap-origin-task\";\n\n\t/**\n\t * @param value\n\t * \t\tProperty value.\n\t */\n\tpublic RemapOriginTaskProperty(@Nonnull MappingResults value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t@Override\n\tpublic boolean persistent() {\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/RemoteClassloaderProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.workspace.model.resource.WorkspaceRemoteVmResource;\n\n/**\n * Built in property to the classloader ID a {@link JvmClassInfo} is associated with\n * in a {@link WorkspaceRemoteVmResource}.\n *\n * @author Matt Coley\n */\npublic class RemoteClassloaderProperty extends BasicProperty<Integer> {\n\tprivate static final Int2ObjectMap<RemoteClassloaderProperty> cache = new Int2ObjectArrayMap<>();\n\tpublic static final String KEY = \"remote-classloader-id\";\n\n\t/**\n\t * @param value\n\t * \t\tLoader ID\n\t */\n\tprivate RemoteClassloaderProperty(int value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t/**\n\t * @param classInfo\n\t * \t\tClass info to link with a classloader via its ID.\n\t *\n\t * @return Classloader ID associated with the class,\n\t * used as key for {@link WorkspaceRemoteVmResource#getJvmClassloaderBundles()}.\n\t * May be {@code null} if no loader association exists.\n\t */\n\t@Nullable\n\tpublic static Integer get(@Nonnull JvmClassInfo classInfo) {\n\t\treturn classInfo.getPropertyValueOrNull(KEY);\n\t}\n\n\t/**\n\t * @param classInfo\n\t * \t\tClass info to link with a version.\n\t * @param loaderId\n\t * \t\tLoader ID associated with the class,\n\t * \t\tused as key for {@link WorkspaceRemoteVmResource#getJvmClassloaderBundles()}.\n\t */\n\tpublic static synchronized void set(@Nonnull JvmClassInfo classInfo, int loaderId) {\n\t\tsynchronized (cache) {\n\t\t\tclassInfo.setProperty(cache.computeIfAbsent(loaderId, RemoteClassloaderProperty::new));\n\t\t}\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull JvmClassInfo info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/StringDefinitionsProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.properties.BasicProperty;\n\nimport java.util.Collection;\nimport java.util.SortedSet;\nimport java.util.TreeSet;\n\n/**\n * Built in property to track what strings are defined by this type.\n *\n * @author Matt Coley\n */\npublic class StringDefinitionsProperty extends BasicProperty<SortedSet<String>> {\n\tpublic static final String KEY = \"strings\";\n\n\t/**\n\t * @param strings\n\t * \t\tCollection of defined strings.\n\t */\n\tpublic StringDefinitionsProperty(@Nonnull Collection<String> strings) {\n\t\tsuper(KEY, new TreeSet<>(strings));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Set of defined strings, or {@code null} when no association exists.\n\t */\n\t@Nullable\n\tpublic static SortedSet<String> get(@Nonnull ClassInfo info) {\n\t\treturn info.getPropertyValueOrNull(KEY);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param strings\n\t * \t\tCollection of defined strings.\n\t */\n\tpublic static void set(@Nonnull ClassInfo info, @Nonnull Collection<String> strings) {\n\t\tinfo.setProperty(new StringDefinitionsProperty(strings));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull ClassInfo info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n\n\t@Override\n\tpublic boolean persistent() {\n\t\t// Modifications to a class will likely invalidate this data\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/ThrowableProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.info.properties.Property;\n\n/**\n * Built in property to track {@link ClassInfo} instances that inherit {@link Throwable} either directly\n * or indirectly.\n *\n * @author Matt Coley\n */\npublic class ThrowableProperty extends BasicProperty<Boolean> {\n\tpublic static final String KEY = \"is-throwable\";\n\n\t/**\n\t * New property value.\n\t */\n\tpublic ThrowableProperty() {\n\t\tsuper(KEY, true);\n\t}\n\n\t@Override\n\tpublic boolean persistent() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass info instance\n\t *\n\t * @return {@code true} when the class inherits from {@link Throwable}.\n\t */\n\tpublic static boolean get(@Nonnull ClassInfo info) {\n\t\tProperty<Boolean> property = info.getProperty(KEY);\n\t\tif (property != null) {\n\t\t\tBoolean value = property.value();\n\t\t\treturn value != null && value;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Marks the class as inheriting {@link Throwable}\n\t *\n\t * @param info\n\t * \t\tClass info instance.\n\t */\n\tpublic static void set(@Nonnull ClassInfo info) {\n\t\tinfo.setProperty(new ThrowableProperty());\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass info instance.\n\t */\n\tpublic static void remove(@Nonnull ClassInfo info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/UnknownAttributesProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.info.properties.Property;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Objects;\n\n/**\n * Built in property to track if an {@link ClassInfo} contains unknown attributes.\n *\n * @author Matt Coley\n */\npublic class UnknownAttributesProperty extends BasicProperty<Collection<String>> {\n\tpublic static final String KEY = \"unknown-attr\";\n\n\t/**\n\t * @param unknownAttributeNames\n\t * \t\tNames of the unknown attributes.\n\t */\n\tpublic UnknownAttributesProperty(@Nonnull Collection<String> unknownAttributeNames) {\n\t\tsuper(KEY, unknownAttributeNames);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass to strip this marker from.\n\t */\n\tpublic static void remove(@Nonnull ClassInfo info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass to check for this marker on.\n\t *\n\t * @return {@code true} when the property is set on the class.\n\t */\n\tpublic static boolean has(@Nonnull ClassInfo info) {\n\t\treturn info.getProperties().containsKey(KEY);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass to check for this marker on.\n\t *\n\t * @return Collection of unknown attributes on the class.\n\t */\n\t@Nonnull\n\tpublic static Collection<String> get(@Nonnull ClassInfo info) {\n\t\tProperty<?> property = info.getProperties().get(KEY);\n\t\tif (property instanceof UnknownAttributesProperty unknown)\n\t\t\treturn Objects.requireNonNullElse(unknown.value(), Collections.emptyList());\n\t\treturn Collections.emptyList();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/VersionedClassProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Built in property to track the version a {@link JvmClassInfo} belongs to in\n * {@link WorkspaceResource#getVersionedJvmClassBundles()}\n *\n * @author Matt Coley\n */\npublic class VersionedClassProperty extends BasicProperty<Integer> {\n\tprivate static final Int2ObjectMap<VersionedClassProperty> cache = new Int2ObjectArrayMap<>();\n\tpublic static final String KEY = \"meta-inf-versioned\";\n\n\t/**\n\t * @param value\n\t * \t\tVersion value\n\t */\n\tprivate VersionedClassProperty(int value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t/**\n\t * @param classInfo\n\t * \t\tClass info to link with a version.\n\t *\n\t * @return Version associated with the class,\n\t * used as key for {@link WorkspaceResource#getVersionedJvmClassBundles()}.\n\t * May be {@code null} if no version association exists.\n\t */\n\t@Nullable\n\tpublic static Integer get(@Nonnull JvmClassInfo classInfo) {\n\t\treturn classInfo.getPropertyValueOrNull(KEY);\n\t}\n\n\t/**\n\t * @param classInfo\n\t * \t\tClass info to link with a version.\n\t * @param version\n\t * \t\tVersion associated with the class,\n\t * \t\tused as key for {@link WorkspaceResource#getVersionedJvmClassBundles()}.\n\t */\n\tpublic static void set(@Nonnull JvmClassInfo classInfo, int version) {\n\t\tsynchronized (cache) {\n\t\t\tclassInfo.setProperty(cache.computeIfAbsent(version, VersionedClassProperty::new));\n\t\t}\n\t}\n\n\t/**\n\t * @param classInfo\n\t * \t\tClass info to unlink with a version.\n\t */\n\tpublic static void remove(@Nonnull JvmClassInfo classInfo) {\n\t\tclassInfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/ZipAccessTimeProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.info.properties.Property;\n\n/**\n * Built in property to track the original zip entry access time used for an {@link Info}\n * value stored inside a ZIP container.\n * <br>\n * Unlike modification time, access time is not part of the ZIP spec, so it is stored in the\n * extra data field.\n *\n * @author Matt Coley\n */\npublic class ZipAccessTimeProperty extends BasicProperty<Long> {\n\tpublic static final String KEY = \"zip-access-time\";\n\n\t/**\n\t * @param value\n\t * \t\tAccess time.\n\t */\n\tpublic ZipAccessTimeProperty(long value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Access time.\n\t * {@code null} when no property value is assigned.\n\t */\n\t@Nullable\n\tpublic static Long get(@Nonnull Info info) {\n\t\tProperty<Long> property = info.getProperty(KEY);\n\t\tif (property != null) {\n\t\t\treturn property.value();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param fallback\n\t * \t\tFallback time if there is no recorded type for the info instance.\n\t *\n\t * @return Access time.\n\t * {@code null} when no property value is assigned.\n\t */\n\tpublic static long getOr(@Nonnull Info info, long fallback) {\n\t\tLong value = get(info);\n\t\tif (value == null) return fallback;\n\t\treturn value;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param value\n\t * \t\tAccess time.\n\t */\n\tpublic static void set(@Nonnull Info info, long value) {\n\t\tinfo.setProperty(new ZipAccessTimeProperty(value));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull Info info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/ZipCommentProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.info.properties.Property;\n\n/**\n * Built in property to track the original zip compression method used for an {@link Info}\n * value stored inside a ZIP container.\n *\n * @author Matt Coley\n */\npublic class ZipCommentProperty extends BasicProperty<String> {\n\tpublic static final String KEY = \"zip-comment\";\n\n\t/**\n\t * @param value\n\t * \t\tOptional comment.\n\t */\n\tpublic ZipCommentProperty(@Nullable String value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Optional comment.\n\t * {@code null} when no property value is assigned.\n\t */\n\t@Nullable\n\tpublic static String get(@Nonnull Info info) {\n\t\tProperty<String> property = info.getProperty(KEY);\n\t\tif (property != null) {\n\t\t\treturn property.value();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param fallback\n\t * \t\tFallback comment if there is no recorded type for the info instance.\n\t *\n\t * @return Optional comment.\n\t * {@code null} when no property value is assigned.\n\t */\n\tpublic static String getOr(@Nonnull Info info, String fallback) {\n\t\tString value = get(info);\n\t\tif (value == null) return fallback;\n\t\treturn value;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param value\n\t * \t\tOptional comment.\n\t */\n\tpublic static void set(@Nonnull Info info, @Nonnull String value) {\n\t\tinfo.setProperty(new ZipCommentProperty(value));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull Info info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/ZipCompressionProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.lljzip.format.compression.ZipCompressions;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.info.properties.Property;\n\n/**\n * Built in property to track the original zip compression method used for an {@link Info}\n * value stored inside a ZIP container.\n *\n * @author Matt Coley\n */\npublic class ZipCompressionProperty extends BasicProperty<Integer> {\n\tpublic static final String KEY = \"zip-compression\";\n\n\t/**\n\t * @param value\n\t * \t\tCompression type. See {@link ZipCompressions} for values.\n\t */\n\tpublic ZipCompressionProperty(int value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Compression type. See {@link ZipCompressions} for values.\n\t * {@code null} when no property value is assigned.\n\t */\n\t@Nullable\n\tpublic static Integer get(@Nonnull Info info) {\n\t\tProperty<Integer> property = info.getProperty(KEY);\n\t\tif (property != null) {\n\t\t\treturn property.value();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param fallback\n\t * \t\tFallback compression type if there is no recorded type for the info instance.\n\t *\n\t * @return Compression type. See {@link ZipCompressions} for values.\n\t * {@code fallback} when no property value is assigned.\n\t */\n\tpublic static int getOr(@Nonnull Info info, int fallback) {\n\t\tInteger value = get(info);\n\t\tif (value == null) return fallback;\n\t\treturn value;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param value\n\t * \t\tCompression type. See {@link ZipCompressions} for values.\n\t */\n\tpublic static void set(@Nonnull Info info, int value) {\n\t\tinfo.setProperty(new ZipCompressionProperty(value));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull Info info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/ZipCreationTimeProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.info.properties.Property;\n\n/**\n * Built in property to track the original zip entry creation time used for an {@link Info}\n * value stored inside a ZIP container.\n * <br>\n * Unlike modification time, creation time is not part of the ZIP spec, so it is stored in the\n * extra data field.\n *\n * @author Matt Coley\n */\npublic class ZipCreationTimeProperty extends BasicProperty<Long> {\n\tpublic static final String KEY = \"zip-create-time\";\n\n\t/**\n\t * @param value\n\t * \t\tCreation time.\n\t */\n\tpublic ZipCreationTimeProperty(long value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Creation time.\n\t * {@code null} when no property value is assigned.\n\t */\n\t@Nullable\n\tpublic static Long get(@Nonnull Info info) {\n\t\tProperty<Long> property = info.getProperty(KEY);\n\t\tif (property != null) {\n\t\t\treturn property.value();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param fallback\n\t * \t\tFallback time if there is no recorded type for the info instance.\n\t *\n\t * @return Creation time.\n\t * {@code null} when no property value is assigned.\n\t */\n\tpublic static long getOr(@Nonnull Info info, long fallback) {\n\t\tLong value = get(info);\n\t\tif (value == null) return fallback;\n\t\treturn value;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param value\n\t * \t\tCreation time.\n\t */\n\tpublic static void set(@Nonnull Info info, long value) {\n\t\tinfo.setProperty(new ZipCreationTimeProperty(value));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull Info info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/ZipEntryIndexProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.info.properties.Property;\n\n/**\n * Built in property to track the original index of the file as it appears in the zip structure.\n *\n * @author Matt Coley\n */\npublic class ZipEntryIndexProperty extends BasicProperty<Integer> {\n\tpublic static final String KEY = \"zip-entry-index\";\n\n\t/**\n\t * @param value\n\t * \t\tThe index of the local file in its containing zip archive.\n\t */\n\tpublic ZipEntryIndexProperty(int value) {\n\t\tsuper(KEY, value);\n\t}\n\n\n\t@Override\n\tpublic boolean persistent() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Zip entry index.\n\t * {@code null} when no property value is assigned.\n\t */\n\t@Nullable\n\tpublic static Integer get(@Nonnull Info info) {\n\t\tProperty<Integer> property = info.getProperty(KEY);\n\t\tif (property != null) {\n\t\t\treturn property.value();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param fallback\n\t * \t\tFallback index if there is no recorded index for the info instance.\n\t *\n\t * @return Zip entry index.\n\t * {@code null} when no property value is assigned.\n\t */\n\tpublic static int getOr(@Nonnull Info info, int fallback) {\n\t\tInteger value = get(info);\n\t\tif (value == null) return fallback;\n\t\treturn value;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param value\n\t * \t\tZip entry index.\n\t */\n\tpublic static void set(@Nonnull Info info, int value) {\n\t\tinfo.setProperty(new ZipEntryIndexProperty(value));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull Info info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/ZipMarkerProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.info.properties.Property;\n\n/**\n * Built in property to track {@link FileInfo} instances that have a ZIP file header within their contents.\n *\n * @author Matt Coley\n */\npublic class ZipMarkerProperty extends BasicProperty<Boolean> {\n\tpublic static final String KEY = \"has-zip-marker\";\n\n\t/**\n\t * New property value.\n\t */\n\tpublic ZipMarkerProperty() {\n\t\tsuper(KEY, true);\n\t}\n\n\t@Override\n\tpublic boolean persistent() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tFile info instance\n\t *\n\t * @return {@code true} when the file has a ZIP file header in the contents.\n\t */\n\tpublic static boolean get(@Nonnull FileInfo info) {\n\t\tProperty<Boolean> property = info.getProperty(KEY);\n\t\tif (property != null) {\n\t\t\tBoolean value = property.value();\n\t\t\treturn value != null && value;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Marks the class as inheriting containing a ZIP file header.\n\t *\n\t * @param info\n\t * \t\tFile info instance.\n\t */\n\tpublic static void set(@Nonnull FileInfo info) {\n\t\tinfo.setProperty(new ZipMarkerProperty());\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tFile info instance.\n\t */\n\tpublic static void remove(@Nonnull FileInfo info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/ZipModificationTimeProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.info.properties.Property;\n\n/**\n * Built in property to track the original zip entry modification time used for an {@link Info}\n * value stored inside a ZIP container.\n *\n * @author Matt Coley\n */\npublic class ZipModificationTimeProperty extends BasicProperty<Long> {\n\tpublic static final String KEY = \"zip-modify-time\";\n\n\t/**\n\t * @param value\n\t * \t\tModification time.\n\t */\n\tpublic ZipModificationTimeProperty(long value) {\n\t\tsuper(KEY, value);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Modification time.\n\t * {@code null} when no property value is assigned.\n\t */\n\t@Nullable\n\tpublic static Long get(@Nonnull Info info) {\n\t\tProperty<Long> property = info.getProperty(KEY);\n\t\tif (property != null) {\n\t\t\treturn property.value();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param fallback\n\t * \t\tFallback time if there is no recorded type for the info instance.\n\t *\n\t * @return Modification time.\n\t * {@code null} when no property value is assigned.\n\t */\n\tpublic static long getOr(@Nonnull Info info, long fallback) {\n\t\tLong value = get(info);\n\t\tif (value == null) return fallback;\n\t\treturn value;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param value\n\t * \t\tModification time.\n\t */\n\tpublic static void set(@Nonnull Info info, long value) {\n\t\tinfo.setProperty(new ZipModificationTimeProperty(value));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull Info info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/info/properties/builtin/ZipPrefixDataProperty.java",
    "content": "package software.coley.recaf.info.properties.builtin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.BasicProperty;\nimport software.coley.recaf.info.properties.Property;\n\n/**\n * Built in property to track data appearing before the ZIP header in an archive.\n *\n * @author Matt Coley\n */\npublic class ZipPrefixDataProperty extends BasicProperty<byte[]> {\n\tpublic static final String KEY = \"zip-prefix-data\";\n\n\t/**\n\t * @param data\n\t * \t\tOptional data.\n\t */\n\tpublic ZipPrefixDataProperty(@Nullable byte[] data) {\n\t\tsuper(KEY, data);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t *\n\t * @return Optional data.\n\t * {@code null} when no property value is assigned.\n\t */\n\t@Nullable\n\tpublic static byte[] get(@Nonnull Info info) {\n\t\tProperty<byte[]> property = info.getProperty(KEY);\n\t\tif (property != null) {\n\t\t\treturn property.value();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t * @param value\n\t * \t\tOptional data.\n\t */\n\tpublic static void set(@Nonnull Info info, @Nonnull byte[] value) {\n\t\tinfo.setProperty(new ZipPrefixDataProperty(value));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tInfo instance.\n\t */\n\tpublic static void remove(@Nonnull Info info) {\n\t\tinfo.removeProperty(KEY);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/launch/LaunchArguments.java",
    "content": "package software.coley.recaf.launch;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.services.file.RecafDirectoriesConfig;\n\nimport java.io.File;\n\n/**\n * Launch arguments of Recaf.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class LaunchArguments {\n\tprivate final RecafDirectoriesConfig directoriesConfig;\n\tprivate LaunchCommand command;\n\tprivate String[] args = new String[0];\n\n\t@Inject\n\tpublic LaunchArguments(@Nonnull RecafDirectoriesConfig directoriesConfig) {\n\t\tthis.directoriesConfig = directoriesConfig;\n\t}\n\n\t/**\n\t * @param command\n\t * \t\tPicoCli command impl for receiving inputs.\n\t */\n\tpublic void setCommand(@Nonnull LaunchCommand command) {\n\t\t// Only allow setting it once.\n\t\tif (this.command == null)\n\t\t\tthis.command = command;\n\t}\n\n\t/**\n\t * @param args\n\t * \t\tLiteral args used to launch recaf.\n\t */\n\tpublic void setRawArgs(@Nonnull String[] args) {\n\t\tthis.args = args;\n\t}\n\n\t/**\n\t * @return Literal args used to launch recaf.\n\t */\n\t@Nonnull\n\tpublic String[] getArgs() {\n\t\treturn args;\n\t}\n\n\t/**\n\t * @return Input to load into a workspace on startup.\n\t */\n\t@Nullable\n\tpublic File getInput() {\n\t\tif (command == null) return null;\n\t\treturn command.getInput();\n\t}\n\n\t/**\n\t * @return Script to run on startup.\n\t *\n\t * @see #getScriptInScriptsDirectory() Alternative to check for the existence of the script file path\n\t * in the script directory.\n\t */\n\t@Nullable\n\tpublic File getScript() {\n\t\tif (command == null) return null;\n\t\treturn command.getScript();\n\t}\n\n\t/**\n\t * @return Script to run on startup.\n\t *\n\t * @see #getScript() Alternative to check for the existence of the script file path\n\t * relative to the current directory.\n\t */\n\t@Nullable\n\tpublic File getScriptInScriptsDirectory() {\n\t\tFile script = getScript();\n\t\tif (script == null) return null;\n\t\treturn directoriesConfig.getScriptsDirectory().resolve(script.getName()).toFile();\n\t}\n\n\t/**\n\t * @return Flag to skip over initializing the UI.\n\t */\n\tpublic boolean isHeadless() {\n\t\tif (command == null) return false;\n\t\treturn command.isHeadless();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/launch/LaunchCommand.java",
    "content": "package software.coley.recaf.launch;\n\nimport ch.qos.logback.classic.Level;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.inject.spi.Bean;\nimport jakarta.enterprise.inject.spi.BeanManager;\nimport picocli.CommandLine.Command;\nimport picocli.CommandLine.Option;\nimport software.coley.recaf.Bootstrap;\nimport software.coley.recaf.RecafBuildConfig;\nimport software.coley.recaf.analytics.SystemInformation;\nimport software.coley.recaf.analytics.logging.RecafLoggingFilter;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.io.File;\nimport java.io.StringWriter;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.concurrent.Callable;\n\n/**\n * Launch arguments for Recaf.\n *\n * @author Matt Coley\n * @see LaunchArguments Bean accesible form availble to CDI components.\n */\n@Command(name = \"recaf\", mixinStandardHelpOptions = true, version = RecafBuildConfig.VERSION,\n\t\tdescription = \"Recaf: The modern Java reverse engineering tool.\")\npublic class LaunchCommand implements Callable<Boolean> {\n\t@Option(names = {\"-i\", \"--input\"}, description = \"Input to load into a workspace on startup.\")\n\tprivate File input;\n\t@Option(names = {\"-s\", \"--script\"}, description = \"Script to run on startup.\")\n\tprivate File script;\n\t@Option(names = {\"-d\", \"--datadir\"}, description = \"Override the directory to store recaf info within.\")\n\tprivate File dataDir;\n\t@Option(names = {\"-r\", \"--extraplugins\"}, description = \"Point to an external location to load additional plugins.\")\n\tprivate File extraPluginDirectory;\n\t@Option(names = {\"-h\", \"--headless\"}, description = \"Flag to skip over initializing the UI. Should be paired with -i or -s.\")\n\tprivate boolean headless;\n\t@Option(names = {\"-q\", \"--silent\"}, description = \"Disable slf4j logging to std-out.\")\n\tprivate boolean silent;\n\t@Option(names = {\"-v\", \"--version\"}, description = \"Display the version information.\")\n\tprivate boolean version;\n\t@Option(names = {\"-l\", \"--listservices\"}, description = \"Display the version information.\")\n\tprivate boolean listServices;\n\t@Option(names = {\"-p\", \"--listprops\"}, description = \"Display system properties.\")\n\tprivate boolean dumpProperties;\n\n\t@Override\n\tpublic Boolean call() throws Exception {\n\t\tboolean ret = false;\n\t\tif (silent)\n\t\t\tRecafLoggingFilter.defaultLevel = Level.OFF;\n\t\tif (dataDir != null)\n\t\t\tSystem.setProperty(\"RECAF_DIR\", dataDir.getAbsolutePath());\n\t\tif (extraPluginDirectory != null)\n\t\t\tSystem.setProperty(\"RECAF_EXTRA_PLUGINS\", extraPluginDirectory.getAbsolutePath());\n\t\tif (version || listServices || dumpProperties)\n\t\t\tSystem.out.println(\"======================= RECAF =======================\");\n\t\tif (version) {\n\t\t\tSystem.out.printf(\"\"\"\n\t\t\t\t\t\t\tVERSION:    %s\n\t\t\t\t\t\t\tGIT-COMMIT: %s\n\t\t\t\t\t\t\tGIT-TIME:   %s\n\t\t\t\t\t\t\tGIT-BRANCH: %s\n\t\t\t\t\t\t\t=====================================================\n\t\t\t\t\t\t\t\"\"\",\n\t\t\t\t\tRecafBuildConfig.VERSION,\n\t\t\t\t\tRecafBuildConfig.GIT_SHA,\n\t\t\t\t\tRecafBuildConfig.GIT_DATE,\n\t\t\t\t\tRecafBuildConfig.GIT_BRANCH\n\t\t\t);\n\t\t\tret = true;\n\t\t}\n\t\tif (listServices) {\n\t\t\ttry {\n\t\t\t\tBeanManager beanManager = Bootstrap.get().getContainer().getBeanManager();\n\t\t\t\tList<Bean<?>> beans = beanManager.getBeans(Service.class).stream()\n\t\t\t\t\t\t.sorted(Comparator.comparing(o -> o.getBeanClass().getName()))\n\t\t\t\t\t\t.toList();\n\t\t\t\tSystem.out.println(\"Services: \" + beans.size());\n\t\t\t\tfor (Bean<?> bean : beans)\n\t\t\t\t\tSystem.out.println(\" - \" + bean.getBeanClass().getName());\n\t\t\t} catch (Throwable t) {\n\t\t\t\tSystem.out.println(\"Error occurred iterating over services...\");\n\t\t\t\tSystem.out.println(StringUtil.traceToString(t));\n\t\t\t}\n\t\t\tSystem.out.println(\"=====================================================\");\n\t\t\tret = true;\n\t\t}\n\t\tif (dumpProperties) {\n\t\t\tStringWriter sw = new StringWriter();\n\t\t\tSystemInformation.dump(sw);\n\t\t\tSystem.out.println(sw);\n\t\t\tSystem.out.println(\"=====================================================\");\n\t\t\tret = true;\n\t\t}\n\t\treturn ret;\n\t}\n\n\t/**\n\t * @return Input to load into a workspace on startup.\n\t */\n\t@Nullable\n\tpublic File getInput() {\n\t\treturn input;\n\t}\n\n\t/**\n\t * @return Script to run on startup.\n\t */\n\t@Nullable\n\tpublic File getScript() {\n\t\treturn script;\n\t}\n\n\t/**\n\t * @return Flag to skip over initializing the UI.\n\t */\n\tpublic boolean isHeadless() {\n\t\treturn headless;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/launch/LaunchHandler.java",
    "content": "package software.coley.recaf.launch;\n\nimport jakarta.annotation.PostConstruct;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport software.coley.recaf.cdi.EagerInitialization;\nimport software.coley.recaf.cdi.InitializationStage;\n\n/**\n * A special {@link EagerInitialization eagerly initialized bean} wrapper of {@link Runnable} which is specially by\n * the entry-point of the program. The lack of the {@link EagerInitialization} annotation on this type is intentional\n * as the entry-point determines if it should be run with {@link InitializationStage#IMMEDIATE} or\n * {@link InitializationStage#AFTER_UI_INIT} dynamically.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class LaunchHandler {\n\t/**\n\t * Task to execute.\n\t */\n\tpublic static Runnable task;\n\n\t@PostConstruct\n\tprivate void run() {\n\t\tif (task != null)\n\t\t\ttask.run();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/AbstractPathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.collections.Unchecked;\n\nimport java.util.Objects;\n\n/**\n * Base implementation of {@link PathNode}.\n *\n * @param <P>\n * \t\tExpected parent path node value type.\n * @param <V>\n * \t\tWrapped path value type.\n *\n * @author Matt Coley\n */\npublic abstract class AbstractPathNode<P, V> implements PathNode<V> {\n\tprivate final PathNode<P> parent;\n\tprivate final Class<V> valueType;\n\tprivate final String id;\n\tprivate final V value;\n\n\t/**\n\t * @param id\n\t * \t\tUnique node type ID.\n\t * @param parent\n\t * \t\tOptional parent node.\n\t * @param value\n\t * \t\tValue instance.\n\t */\n\tprotected AbstractPathNode(@Nonnull String id, @Nullable PathNode<P> parent, @Nonnull V value) {\n\t\tthis.id = id;\n\t\tthis.parent = parent;\n\t\tthis.value = value;\n\t\tthis.valueType = Unchecked.cast(value.getClass());\n\t}\n\n\t/**\n\t * Convenient parent value getter.\n\t *\n\t * @return Parent value, or {@code null}.\n\t */\n\t@Nullable\n\tprotected P parentValue() {\n\t\treturn parent == null ? null : parent.getValue();\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tSome other path.\n\t *\n\t * @return Comparing our parent value type to the given path,\n\t * and the other path parent value type to our own.\n\t * If we are the child type, then {@code -1} or {@link 1} if the parent type.\n\t * Otherwise {@code 0}.\n\t */\n\tprotected int cmpHierarchy(@Nonnull PathNode<?> path) {\n\t\tif (getValueType() != path.getValueType()) {\n\t\t\t// Check direct parent (quicker validation) and then if that does not pass, a multi-level descendant test.\n\t\t\tif ((parent != null && parent.typeIdMatch(path)) || isDescendantOf(path)) {\n\t\t\t\t// We are the child type, show last.\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t// Check direct parent (quicker validation) and then if that does not pass, a multi-level descendant test.\n\t\t\tif ((path.getParent() != null && typeIdMatch(path.getParent())) || path.isDescendantOf(this)) {\n\t\t\t\t// We are the parent type, show first.\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\n\t\t// Unknown\n\t\treturn 0;\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tSome other path.\n\t *\n\t * @return Comparing {@link #getParent() the parent} to the given value.\n\t */\n\tprotected int cmpParent(@Nonnull PathNode<?> path) {\n\t\tif (parent != null)\n\t\t\treturn parent.compareTo(path);\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic PathNode<P> getParent() {\n\t\treturn parent;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String typeId() {\n\t\treturn id;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Class<V> getValueType() {\n\t\treturn valueType;\n\t}\n\n\t@Nonnull\n\t@Override\n\t@SuppressWarnings(\"all\")\n\tpublic V getValue() {\n\t\treturn value;\n\t}\n\n\t@Override\n\tpublic int compareTo(@Nonnull PathNode<?> o) {\n\t\tif (this == o) return 0;\n\t\tint cmp = localCompare(o);\n\t\tif (cmp == 0) cmp = cmpHierarchy(o);\n\t\tif (cmp == 0) cmp = cmpParent(o);\n\t\treturn cmp;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tPathNode<?> node = (PathNode<?>) o;\n\n\t\treturn getValue().equals(node.getValue()) && Objects.equals(parent, node.getParent());\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint hash = getValue().hashCode();\n\t\tif (parent != null)\n\t\t\treturn 31 * parent.hashCode() + hash;\n\t\treturn hash;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn value.toString();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/AnnotationPathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\n\nimport java.util.Set;\n\n/**\n * Path node for annotations on {@link Annotated} types such as classes, fields, and methods.\n *\n * @author Matt Coley\n */\npublic class AnnotationPathNode extends AbstractPathNode<Object, AnnotationInfo> {\n\t/**\n\t * Type identifier for annotation nodes.\n\t */\n\tpublic static final String TYPE_ID = \"annotation\";\n\n\t/**\n\t * Node without parent.\n\t *\n\t * @param annotation\n\t * \t\tAnnotation.\n\t */\n\tpublic AnnotationPathNode(@Nonnull AnnotationInfo annotation) {\n\t\tthis(null, annotation);\n\t}\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param annotation\n\t * \t\tAnnotation.\n\t *\n\t * @see ClassMemberPathNode#childAnnotation(AnnotationInfo)\n\t * @see ClassPathNode#child(AnnotationInfo)\n\t * @see AnnotationPathNode#child(AnnotationInfo)\n\t * @see InnerClassPathNode#child(AnnotationInfo)\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic AnnotationPathNode(@Nullable PathNode<?> parent, @Nonnull AnnotationInfo annotation) {\n\t\tsuper(TYPE_ID, (PathNode<Object>) parent, annotation);\n\t}\n\n\t/**\n\t * @param annotation\n\t * \t\tAnnotation to wrap into node.\n\t *\n\t * @return Path node of annotation, with the current annotation as parent.\n\t */\n\t@Nonnull\n\tpublic AnnotationPathNode child(@Nonnull AnnotationInfo annotation) {\n\t\treturn new AnnotationPathNode(this, annotation);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(ClassPathNode.TYPE_ID, ClassMemberPathNode.TYPE_ID, InnerClassPathNode.TYPE_ID, AnnotationPathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o) return 0;\n\n\t\tif (o instanceof AnnotationPathNode node) {\n\t\t\treturn getValue().getDescriptor().compareTo(node.getValue().getDescriptor());\n\t\t}\n\n\t\t// Show before inner classes & annotations\n\t\tif (o instanceof InnerClassPathNode || o instanceof ClassMemberPathNode)\n\t\t\treturn 1;\n\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/BundlePathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.Named;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.VersionedJvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * Path node for {@link Bundle} types.\n *\n * @author Matt Coley\n */\n@SuppressWarnings(\"rawtypes\")\npublic class BundlePathNode extends AbstractPathNode<WorkspaceResource, Bundle> {\n\t/**\n\t * Type identifier for bundle nodes.\n\t */\n\tpublic static final String TYPE_ID = \"bundle\";\n\n\t/**\n\t * Node without parent.\n\t *\n\t * @param bundle\n\t * \t\tBundle value.\n\t */\n\tpublic BundlePathNode(@Nonnull Bundle<?> bundle) {\n\t\tthis(null, bundle);\n\t}\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param bundle\n\t * \t\tBundle value.\n\t *\n\t * @see ResourcePathNode#child(Bundle)\n\t */\n\tpublic BundlePathNode(@Nullable ResourcePathNode parent, @Nonnull Bundle<?> bundle) {\n\t\tsuper(TYPE_ID, parent, bundle);\n\t}\n\n\t/**\n\t * @param directory\n\t * \t\tDirectory to wrap in path node.\n\t *\n\t * @return Path node of directory, with the current bundle as parent.\n\t */\n\t@Nonnull\n\tpublic DirectoryPathNode child(@Nullable String directory) {\n\t\treturn new DirectoryPathNode(this, directory == null ? \"\" : directory);\n\t}\n\n\t/**\n\t * @return {@code true} when the path is in the resource's immediate JVM class bundle.\n\t */\n\tpublic boolean isInJvmBundle() {\n\t\tWorkspaceResource resource = parentValue();\n\t\treturn resource != null && resource.getJvmClassBundle() == getValue();\n\t}\n\n\t/**\n\t * @return {@code true} when the path is in one of the resource's Android bundles.\n\t */\n\t@SuppressWarnings(\"all\")\n\tpublic boolean isInAndroidBundle() {\n\t\tWorkspaceResource resource = parentValue();\n\t\treturn resource != null && resource.getAndroidClassBundles().containsValue(getValue());\n\t}\n\n\t/**\n\t * @return {@code true} when the path is in the resource's immediate file bundle.\n\t */\n\tpublic boolean isInFileBundle() {\n\t\tWorkspaceResource resource = parentValue();\n\t\treturn resource != null && resource.getFileBundle() == getValue();\n\t}\n\n\t/**\n\t * @return {@code true} when the path is in one of the resource's versioned class bundles.\n\t */\n\tpublic boolean isInVersionedJvmBundle() {\n\t\tWorkspaceResource resource = parentValue();\n\t\tif (resource != null && getValue() instanceof VersionedJvmClassBundle jvmBundle)\n\t\t\treturn resource.getVersionedJvmClassBundles().containsValue(jvmBundle);\n\t\treturn false;\n\t}\n\n\t/**\n\t * @return Bit-mask used for ordering in {@link #compareTo(PathNode)}.\n\t */\n\tprivate int bundleMask() {\n\t\treturn ((isInJvmBundle() ? 1 : 0) << 10) |\n\t\t\t\t((isInVersionedJvmBundle() ? 1 : 0) << 12) |\n\t\t\t\t((isInAndroidBundle() ? 1 : 0) << 14) |\n\t\t\t\t((isInFileBundle() ? 1 : 0) << 16);\n\t}\n\n\t@Override\n\tpublic ResourcePathNode getParent() {\n\t\treturn (ResourcePathNode) super.getParent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(ResourcePathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic boolean hasEqualOrChildValue(@Nonnull PathNode<?> other) {\n\t\t// Bundle equality checks are abysmally slow, but also potentially problematic for path comparisons.\n\t\t// Two bundles may have the same contents, but they are not the same bundle.\n\t\t// We kill two birds with one stone by doing a reference check here.\n\t\t// If they are the same bundle, then they are the same path.\n\t\tif (other instanceof BundlePathNode otherBundlePath)\n\t\t\treturn getValue() == otherBundlePath.getValue();\n\t\treturn super.hasEqualOrChildValue(other);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o)\n\t\t\treturn 0;\n\n\t\tif (o instanceof BundlePathNode bundlePathNode) {\n\t\t\t// Quick check for bundle reference equality. If they are the same bundle, then they are the same path.\n\t\t\tBundle bundle = getValue();\n\t\t\tBundle otherBundle = bundlePathNode.getValue();\n\t\t\tif (bundle == otherBundle)\n\t\t\t\treturn 0;\n\n\t\t\t// Order bundles by type.\n\t\t\t// This is a bitmask that encodes the bundle type which also doubles as order preference.\n\t\t\tint cmp = Integer.compare(bundleMask(), bundlePathNode.bundleMask());\n\t\t\tif (cmp != 0)\n\t\t\t\treturn cmp;\n\n\t\t\t// Order dex class bundles to be in alphabetical order.\n\t\t\tif (getParent() != null &&\n\t\t\t\t\tbundle instanceof AndroidClassBundle &&\n\t\t\t\t\totherBundle instanceof AndroidClassBundle) {\n\t\t\t\tWorkspaceResource resource = getParent().getValue();\n\t\t\t\tSet<Map.Entry<String, AndroidClassBundle>> androidBundles = resource.getAndroidClassBundles().entrySet();\n\t\t\t\tString dexName = androidBundles.stream()\n\t\t\t\t\t\t.filter(e -> e.getValue() == bundle)\n\t\t\t\t\t\t.map(Map.Entry::getKey)\n\t\t\t\t\t\t.findFirst()\n\t\t\t\t\t\t.orElse(null);\n\t\t\t\tString otherDexName = androidBundles.stream()\n\t\t\t\t\t\t.filter(e -> e.getValue() == otherBundle)\n\t\t\t\t\t\t.map(Map.Entry::getKey)\n\t\t\t\t\t\t.findFirst()\n\t\t\t\t\t\t.orElse(null);\n\t\t\t\treturn Named.STRING_PATH_COMPARATOR.compare(dexName, otherDexName);\n\t\t\t}\n\t\t\t// Order versioned JVM class bundles by version number.\n\t\t\telse if (bundle instanceof VersionedJvmClassBundle versionedBundle &&\n\t\t\t\t\totherBundle instanceof VersionedJvmClassBundle otherVersionedBundle) {\n\t\t\t\treturn Integer.compare(versionedBundle.version(), otherVersionedBundle.version());\n\t\t\t}\n\t\t}\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\t// Bundle equality checks are abysmally slow because are checking if all the contained\n\t\t// contents are also equal. Realistically we can get away with a reference check.\n\t\tif (o instanceof BundlePathNode otherPath)\n\t\t\treturn getValue() == otherPath.getValue() && Objects.equals(getParent(), otherPath.getParent());\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/CatchPathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport java.util.Set;\n\n/**\n * Path node for {@code catch(Exception)} within {@link MethodMember} instances.\n *\n * @author Matt Coley\n */\npublic class CatchPathNode extends AbstractPathNode<ClassMember, String> {\n\t/**\n\t * Type identifier for catch nodes.\n\t */\n\tpublic static final String TYPE_ID = \"catch\";\n\n\t/**\n\t * Node without parent.\n\t *\n\t * @param type\n\t * \t\tException type.\n\t */\n\tpublic CatchPathNode(@Nonnull String type) {\n\t\tthis(null, type);\n\t}\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param type\n\t * \t\tException type.\n\t *\n\t * @see ClassMemberPathNode#childCatch(String)\n\t */\n\tpublic CatchPathNode(@Nullable ClassMemberPathNode parent, @Nonnull String type) {\n\t\tsuper(TYPE_ID, parent, type);\n\t}\n\n\t@Override\n\tpublic ClassMemberPathNode getParent() {\n\t\treturn (ClassMemberPathNode) super.getParent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(ClassMemberPathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o) return 0;\n\n\t\tif (o instanceof CatchPathNode node)\n\t\t\treturn getValue().compareTo(node.getValue());\n\t\telse if (o instanceof LocalVariablePathNode || o instanceof InstructionPathNode)\n\t\t\treturn -1;\n\t\telse if (o instanceof ThrowsPathNode)\n\t\t\treturn 1;\n\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/ClassMemberPathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.Named;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.properties.builtin.MemberIndexAcceleratorProperty;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * Path node for {@link ClassMember} types.\n *\n * @author Matt Coley\n */\npublic class ClassMemberPathNode extends AbstractPathNode<ClassInfo, ClassMember> {\n\t/**\n\t * Type identifier for member nodes.\n\t */\n\tpublic static final String TYPE_ID = \"member\";\n\n\t/**\n\t * Node without parent.\n\t *\n\t * @param member\n\t * \t\tMember value.\n\t */\n\tpublic ClassMemberPathNode(@Nonnull ClassMember member) {\n\t\tthis(null, member);\n\t}\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param member\n\t * \t\tMember value.\n\t *\n\t * @see ClassPathNode#child(ClassMember)\n\t */\n\tpublic ClassMemberPathNode(@Nullable ClassPathNode parent, @Nonnull ClassMember member) {\n\t\tsuper(TYPE_ID, parent, member);\n\t}\n\n\t/**\n\t * @return {@code true} when wrapping a field.\n\t */\n\tpublic boolean isField() {\n\t\treturn getValue().isField();\n\t}\n\n\t/**\n\t * @return {@code true} when wrapping a method.\n\t */\n\tpublic boolean isMethod() {\n\t\treturn getValue().isMethod();\n\t}\n\n\t/**\n\t * @param thrownType\n\t * \t\tThrown type to wrap into node.\n\t *\n\t * @return Path node of thrown type, with the current member as parent.\n\t */\n\t@Nonnull\n\tpublic ThrowsPathNode childThrows(@Nonnull String thrownType) {\n\t\tif (isMethod())\n\t\t\treturn new ThrowsPathNode(this, thrownType);\n\t\tthrow new IllegalStateException(\"Cannot make child for throws on non-method member\");\n\t}\n\n\t/**\n\t * @param exceptionType\n\t * \t\tThrown type to wrap into node.\n\t *\n\t * @return Path node of caught type, with the current member as parent.\n\t */\n\t@Nonnull\n\tpublic CatchPathNode childCatch(@Nonnull String exceptionType) {\n\t\tif (isMethod())\n\t\t\treturn new CatchPathNode(this, exceptionType);\n\t\tthrow new IllegalStateException(\"Cannot make child for catch on non-method member\");\n\t}\n\n\t/**\n\t * @param annotation\n\t * \t\tAnnotation to wrap into node.\n\t *\n\t * @return Path node of annotation, with the current member as parent.\n\t */\n\t@Nonnull\n\tpublic AnnotationPathNode childAnnotation(@Nonnull AnnotationInfo annotation) {\n\t\treturn new AnnotationPathNode(this, annotation);\n\t}\n\n\t/**\n\t * @param variable\n\t * \t\tVariable to wrap into node.\n\t *\n\t * @return Path node of local variable, with the current member as parent.\n\t */\n\t@Nonnull\n\tpublic LocalVariablePathNode childVariable(LocalVariable variable) {\n\t\tif (isMethod())\n\t\t\treturn new LocalVariablePathNode(this, variable);\n\t\tthrow new IllegalStateException(\"Cannot make child for catch on non-method member\");\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tInstruction to wrap into node.\n\t * @param index\n\t * \t\tIndex of the instruction within the method code.\n\t *\n\t * @return Path node of instruction, with the current member as parent.\n\t */\n\t@Nonnull\n\tpublic InstructionPathNode childInsn(@Nonnull AbstractInsnNode insn, int index) {\n\t\tif (isMethod())\n\t\t\treturn new InstructionPathNode(this, insn, index);\n\t\tthrow new IllegalStateException(\"Cannot make child for insn on non-method member\");\n\t}\n\n\t@Override\n\tpublic boolean hasEqualOrChildValue(@Nonnull PathNode<?> other) {\n\t\tif (other instanceof ClassMemberPathNode otherMemberPath) {\n\t\t\tClassMember member = getValue();\n\t\t\tClassMember otherMember = otherMemberPath.getValue();\n\n\t\t\t// We'll determine equality just by the name+type of the contained member.\n\t\t\t// Path equality should match by location, so comparing just by name+type\n\t\t\t// allows this path and the other path to have different versions of\n\t\t\t// the same member.\n\t\t\treturn member.getName().equals(otherMember.getName()) &&\n\t\t\t\t\tmember.getDescriptor().equals(otherMember.getDescriptor());\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic ClassPathNode getParent() {\n\t\treturn (ClassPathNode) super.getParent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(ClassPathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o) return 0;\n\n\t\tif (o instanceof ClassMemberPathNode classMemberNode) {\n\t\t\tClassMember member = getValue();\n\t\t\tClassMember otherMember = classMemberNode.getValue();\n\n\t\t\t// Show fields first\n\t\t\tif (member.isField() && otherMember.isMethod()) {\n\t\t\t\treturn -1;\n\t\t\t} else if (member.isMethod() && otherMember.isField()) {\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\tint cmp;\n\t\t\tClassPathNode parent = getParent();\n\t\t\tif (parent != null) {\n\t\t\t\tClassPathNode otherParent = classMemberNode.getParent();\n\t\t\t\tif (otherParent != null) {\n\t\t\t\t\tcmp = parent.compareTo(otherParent);\n\t\t\t\t\tif (cmp != 0)\n\t\t\t\t\t\treturn cmp;\n\t\t\t\t}\n\n\t\t\t\t// Sort by appearance order in parent.\n\t\t\t\tClassInfo classInfo = parent.getValue();\n\t\t\t\tList<? extends ClassMember> list = member.isField() ?\n\t\t\t\t\t\tclassInfo.getFields() : classInfo.getMethods();\n\t\t\t\tif (list.size() > MemberIndexAcceleratorProperty.CUTOFF) {\n\t\t\t\t\tMemberIndexAcceleratorProperty accel = MemberIndexAcceleratorProperty.get(classInfo);\n\t\t\t\t\tcmp = Integer.compare(accel.indexOf(member), accel.indexOf(otherMember));\n\t\t\t\t} else {\n\t\t\t\t\tcmp = Integer.compare(list.indexOf(member), list.indexOf(otherMember));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Just sort alphabetically if parent not known.\n\t\t\t\tString key = member.getName() + member.getDescriptor();\n\t\t\t\tString otherKey = otherMember.getName() + member.getDescriptor();\n\t\t\t\tcmp = Named.STRING_COMPARATOR.compare(key, otherKey);\n\t\t\t}\n\t\t\treturn cmp;\n\t\t}\n\n\t\t// Show after inner classes & annotations\n\t\tif (o instanceof InnerClassPathNode || o instanceof AnnotationPathNode)\n\t\t\treturn 1;\n\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\t// If the member names/signatures are the same and the parent paths are also equal, then this path points to the same location.\n\t\tif (o instanceof ClassMemberPathNode otherPath)\n\t\t\treturn getValue().getName().equals(otherPath.getValue().getName())\n\t\t\t\t\t&& getValue().getDescriptor().equals(otherPath.getValue().getDescriptor())\n\t\t\t\t\t&& Objects.equals(getParent(), otherPath.getParent());\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/ClassPathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.Named;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\n\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * Path node for {@link ClassInfo} types.\n *\n * @author Matt Coley\n */\npublic class ClassPathNode extends AbstractPathNode<String, ClassInfo> {\n\t/**\n\t * Type identifier for class nodes.\n\t */\n\tpublic static final String TYPE_ID = \"class\";\n\n\t/**\n\t * Node without parent.\n\t *\n\t * @param info\n\t * \t\tClass value.\n\t */\n\tpublic ClassPathNode(@Nonnull ClassInfo info) {\n\t\tthis(null, info);\n\t}\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param info\n\t * \t\tClass value.\n\t *\n\t * @see DirectoryPathNode#child(ClassInfo)\n\t */\n\tpublic ClassPathNode(@Nullable DirectoryPathNode parent, @Nonnull ClassInfo info) {\n\t\tsuper(TYPE_ID, parent, info);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tField or method name in the current class.\n\t * @param desc\n\t * \t\tField or method descriptor in the current class.\n\t *\n\t * @return Path node of member, with the current class as parent.\n\t * {@code null} if a field or method with the given name/descriptor could not be found.\n\t */\n\t@Nullable\n\tpublic ClassMemberPathNode child(@Nonnull String name, @Nonnull String desc) {\n\t\tClassInfo classInfo = getValue();\n\t\tClassMember member;\n\t\tif (!desc.isEmpty() && desc.charAt(0) == '(')\n\t\t\tmember = classInfo.getDeclaredMethod(name, desc);\n\t\telse\n\t\t\tmember = classInfo.getDeclaredField(name, desc);\n\n\t\tif (member != null) return child(member);\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param member\n\t * \t\tMember to wrap into node.\n\t *\n\t * @return Path node of member, with the current class as parent.\n\t */\n\t@Nonnull\n\tpublic ClassMemberPathNode child(@Nonnull ClassMember member) {\n\t\treturn new ClassMemberPathNode(this, member);\n\t}\n\n\t/**\n\t * @param innerClass\n\t * \t\tInner class to wrap into node.\n\t *\n\t * @return Path node of inner class, with the current class as parent.\n\t */\n\t@Nonnull\n\tpublic InnerClassPathNode child(@Nonnull InnerClassInfo innerClass) {\n\t\treturn new InnerClassPathNode(this, innerClass);\n\t}\n\n\t/**\n\t * @param annotation\n\t * \t\tAnnotation to wrap into node.\n\t *\n\t * @return Path node of annotation, with the current member as parent.\n\t */\n\t@Nonnull\n\tpublic AnnotationPathNode child(@Nonnull AnnotationInfo annotation) {\n\t\treturn new AnnotationPathNode(this, annotation);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassPathNode withCurrentWorkspaceContent() {\n\t\tDirectoryPathNode parent = getParent();\n\t\tif (parent == null) return this;\n\t\tClassBundle<?> bundle = getValueOfType(ClassBundle.class);\n\t\tif (bundle == null) return this;\n\t\tClassInfo classInfo = bundle.get(getValue().getName());\n\t\tif (classInfo == null || classInfo == getValue()) return this;\n\t\treturn parent.child(classInfo);\n\t}\n\n\t@Override\n\tpublic boolean hasEqualOrChildValue(@Nonnull PathNode<?> other) {\n\t\tif (other instanceof ClassPathNode otherClassPath) {\n\t\t\tClassInfo cls = getValue();\n\t\t\tClassInfo otherCls = otherClassPath.getValue();\n\n\t\t\t// We'll determine equality just by the name of the contained class.\n\t\t\t// Path equality should match by location, so comparing just by name\n\t\t\t// allows this path and the other path to have different versions of\n\t\t\t// the same class.\n\t\t\treturn cls.getName().equals(otherCls.getName());\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic DirectoryPathNode getParent() {\n\t\treturn (DirectoryPathNode) super.getParent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(DirectoryPathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o) return 0;\n\n\t\tif (o instanceof ClassPathNode classPathNode) {\n\t\t\tString name = getValue().getName();\n\t\t\tString otherName = classPathNode.getValue().getName();\n\t\t\treturn Named.STRING_PATH_COMPARATOR.compare(name, otherName);\n\t\t}\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\t// If the class names are the same and the parent paths are also equal, then this path points to the same location.\n\t\tif (o instanceof ClassPathNode otherPath) {\n\t\t\tString name = getValue().getName();\n\t\t\tString otherName = otherPath.getValue().getName();\n\t\t\treturn name.hashCode() == otherName.hashCode() // Hash check first which is very fast, and the result is cached.\n\t\t\t\t\t&& name.equals(otherName) // Sanity check for matching items to prevent hash collisions.\n\t\t\t\t\t&& Objects.equals(getParent(), otherPath.getParent()); // Parents must also match.\n\t\t}\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/DirectoryPathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.Named;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\n\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * Path node for packages of {@link ClassInfo} and directories of {@link FileInfo} types.\n *\n * @author Matt Coley\n */\n@SuppressWarnings(\"rawtypes\")\npublic class DirectoryPathNode extends AbstractPathNode<Bundle, String> {\n\t/**\n\t * Type identifier for directory nodes.\n\t */\n\tpublic static final String TYPE_ID = \"directory\";\n\n\t/**\n\t * Node without parent.\n\t *\n\t * @param directory\n\t * \t\tDirectory name.\n\t */\n\tpublic DirectoryPathNode(@Nonnull String directory) {\n\t\tthis(null, directory);\n\t}\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param directory\n\t * \t\tDirectory name.\n\t *\n\t * @see BundlePathNode#child(String)\n\t */\n\tpublic DirectoryPathNode(@Nullable BundlePathNode parent, @Nonnull String directory) {\n\t\tsuper(TYPE_ID, parent, directory);\n\t}\n\n\t/**\n\t * @param directory\n\t * \t\tNew directory name.\n\t *\n\t * @return New node with same parent, but different directory name value.\n\t */\n\t@Nonnull\n\tpublic DirectoryPathNode withDirectory(@Nonnull String directory) {\n\t\treturn new DirectoryPathNode(getParent(), directory);\n\t}\n\n\t/**\n\t * @param classInfo\n\t * \t\tClass to wrap into node.\n\t *\n\t * @return Path node of class, with the current package as parent.\n\t */\n\t@Nonnull\n\tpublic ClassPathNode child(@Nonnull ClassInfo classInfo) {\n\t\treturn new ClassPathNode(this, classInfo);\n\t}\n\n\t/**\n\t * @param fileInfo\n\t * \t\tFile to wrap into node.\n\t *\n\t * @return Path node of file, with the current directory as parent.\n\t */\n\t@Nonnull\n\tpublic FilePathNode child(@Nonnull FileInfo fileInfo) {\n\t\treturn new FilePathNode(this, fileInfo);\n\t}\n\n\t@Override\n\t@SuppressWarnings(\"all\")\n\tpublic BundlePathNode getParent() {\n\t\treturn (BundlePathNode) super.getParent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(BundlePathNode.TYPE_ID, DirectoryPathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic boolean hasEqualOrChildValue(@Nonnull PathNode<?> other) {\n\t\tif (other instanceof DirectoryPathNode otherDirectory) {\n\t\t\tString dir = getValue();\n\t\t\tString maybeParentDir = otherDirectory.getValue();\n\n\t\t\t// We cannot do just a basic 'startsWith' check on the path values since they do not\n\t\t\t// end with a trailing slash. This could lead to cases where:\n\t\t\t//  'co' is a parent value of 'com/foo'\n\t\t\t//\n\t\t\t// By doing an equals check, we allow for 'co' vs 'com' to fail but 'co' vs 'co' to pass,\n\t\t\t// and the following startsWith check with a slash allows us to not fall to the suffix issue described above.\n\t\t\treturn dir.equals(maybeParentDir) || dir.startsWith(maybeParentDir + \"/\");\n\t\t}\n\n\t\treturn super.hasEqualOrChildValue(other);\n\t}\n\n\t@Override\n\tpublic boolean isDescendantOf(@Nonnull PathNode<?> other) {\n\t\t// Descendant check comparing between directories will check for containment within the local value's path.\n\t\t// This way 'a/b/c' is seen as a descendant of 'a/b'.\n\t\tif (typeId().equals(other.typeId()))\n\t\t\treturn hasEqualOrChildValue(other) && allParentsMatch(other);\n\n\t\treturn super.isDescendantOf(other);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o) return 0;\n\n\t\tif (o instanceof DirectoryPathNode pathNode) {\n\t\t\tString name = getValue();\n\t\t\tString otherName = pathNode.getValue();\n\t\t\treturn Named.STRING_PATH_COMPARATOR.compare(name, otherName);\n\t\t}\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\t// If the directories are the same and the parent paths are also equal, then this path points to the same location.\n\t\tif (o instanceof DirectoryPathNode otherPath) {\n\t\t\tString dir = getValue();\n\t\t\tString otherDir = otherPath.getValue();\n\t\t\treturn dir.hashCode() == otherDir.hashCode() // Hash check first which is very fast, and the result is cached.\n\t\t\t\t\t&& dir.equals(otherDir) // Sanity check for matching items to prevent hash collisions.\n\t\t\t\t\t&& Objects.equals(getParent(), otherPath.getParent()); // Parents must also match.\n\t\t}\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/EmbeddedResourceContainerPathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Set;\n\n/**\n * Path node for housing one or more embedded resources in another resource.\n *\n * @author Matt Coley\n */\npublic class EmbeddedResourceContainerPathNode extends AbstractPathNode<WorkspaceResource, Workspace> {\n\t/**\n\t * Type identifier for embedded containers.\n\t */\n\tpublic static final String TYPE_ID = \"embedded-container\";\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param workspace\n\t * \t\tWorkspace containing the host resource.\n\t */\n\tpublic EmbeddedResourceContainerPathNode(@Nullable ResourcePathNode parent,\n\t                                         @Nonnull Workspace workspace) {\n\t\tsuper(TYPE_ID, parent, workspace);\n\t}\n\n\t/**\n\t * @param resource\n\t * \t\tResource to wrap into node.\n\t *\n\t * @return Path node of resource, with the current workspace as parent.\n\t */\n\t@Nonnull\n\tpublic ResourcePathNode child(@Nonnull WorkspaceResource resource) {\n\t\treturn new ResourcePathNode(this, resource);\n\t}\n\n\t@Override\n\tpublic ResourcePathNode getParent() {\n\t\treturn (ResourcePathNode) super.getParent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(EmbeddedResourceContainerPathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/FilePathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.Named;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\n\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * Path node for {@link FileInfo} types.\n *\n * @author Matt Coley\n */\npublic class FilePathNode extends AbstractPathNode<String, FileInfo> {\n\t/**\n\t * Type identifier for file nodes.\n\t */\n\tpublic static final String TYPE_ID = \"file\";\n\n\t/**\n\t * Node without parent.\n\t *\n\t * @param info\n\t * \t\tFile value.\n\t */\n\tpublic FilePathNode(@Nonnull FileInfo info) {\n\t\tthis(null, info);\n\t}\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param info\n\t * \t\tFile value.\n\t *\n\t * @see DirectoryPathNode#child(FileInfo)\n\t */\n\tpublic FilePathNode(@Nullable DirectoryPathNode parent, @Nonnull FileInfo info) {\n\t\tsuper(TYPE_ID, parent, info);\n\t}\n\n\t/**\n\t * @param lineNo\n\t * \t\tLine number to wrap into node.\n\t *\n\t * @return Path node of line number, with the current file as parent.\n\t */\n\t@Nonnull\n\tpublic LineNumberPathNode child(int lineNo) {\n\t\treturn new LineNumberPathNode(this, lineNo);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic FilePathNode withCurrentWorkspaceContent() {\n\t\tDirectoryPathNode parent = getParent();\n\t\tif (parent == null) return this;\n\t\tFileBundle bundle = getValueOfType(FileBundle.class);\n\t\tif (bundle == null) return this;\n\t\tFileInfo fileInfo = bundle.get(getValue().getName());\n\t\tif (fileInfo == null || fileInfo == getValue()) return this;\n\t\treturn parent.child(fileInfo);\n\t}\n\n\t@Override\n\tpublic boolean hasEqualOrChildValue(@Nonnull PathNode<?> other) {\n\t\tif (other instanceof FilePathNode otherClassPathNode) {\n\t\t\tFileInfo cls = getValue();\n\t\t\tFileInfo otherCls = otherClassPathNode.getValue();\n\n\t\t\t// We'll determine equality just by the name of the contained file.\n\t\t\t// Path equality should match by location, so comparing just by name\n\t\t\t// allows this path and the other path to have different versions of\n\t\t\t// the same file.\n\t\t\treturn cls.getName().equals(otherCls.getName());\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic DirectoryPathNode getParent() {\n\t\treturn (DirectoryPathNode) super.getParent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(DirectoryPathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o) return 0;\n\n\t\tif (o instanceof FilePathNode fileNode) {\n\t\t\tString name = getValue().getName();\n\t\t\tString otherName = fileNode.getValue().getName();\n\t\t\treturn Named.STRING_PATH_COMPARATOR.compare(name, otherName);\n\t\t}\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\t// If the file names are the same and the parent paths are also equal, then this path points to the same location.\n\t\tif (o instanceof FilePathNode otherPath) {\n\t\t\tString name = getValue().getName();\n\t\t\tString otherName = otherPath.getValue().getName();\n\t\t\treturn name.hashCode() == otherName.hashCode() // Hash check first which is very fast, and the result is cached.\n\t\t\t\t\t&& name.equals(otherName) // Sanity check for matching items to prevent hash collisions.\n\t\t\t\t\t&& Objects.equals(getParent(), otherPath.getParent()); // Parents must also match.\n\t\t}\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/IncompletePathException.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\n/**\n * Used by methods when handling input {@link PathNode} content indicates an element in the path is missing.\n *\n * @author Matt Coley\n */\npublic class IncompletePathException extends Exception {\n\tprivate final Class<?> missingType;\n\n\t/**\n\t * @param missingType\n\t * \t\tMissing type in the path.\n\t */\n\tpublic IncompletePathException(@Nonnull Class<?> missingType) {\n\t\tthis(missingType, null);\n\t}\n\n\t/**\n\t * @param missingType\n\t * \t\tMissing type in the path.\n\t * @param message\n\t * \t\tProblem message.\n\t */\n\tpublic IncompletePathException(@Nonnull Class<?> missingType, @Nullable String message) {\n\t\tsuper(message);\n\t\tthis.missingType = missingType;\n\t}\n\n\t/**\n\t * @return Missing type in the path.\n\t */\n\t@Nonnull\n\tpublic Class<?> getMissingType() {\n\t\treturn missingType;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/InnerClassPathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.Named;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\n\nimport java.util.Set;\n\n/**\n * Path node for {@link InnerClassInfo} types.\n *\n * @author Matt Coley\n */\npublic class InnerClassPathNode extends AbstractPathNode<ClassInfo, InnerClassInfo> {\n\t/**\n\t * Type identifier for inner class nodes.\n\t */\n\tpublic static final String TYPE_ID = \"inner-class\";\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tOptional parent node.\n\t * @param innerClass\n\t * \t\tInner class instance.\n\t *\n\t * @see ClassPathNode#child(InnerClassInfo)\n\t */\n\tpublic InnerClassPathNode(@Nullable ClassPathNode parent,\n\t                          @Nonnull InnerClassInfo innerClass) {\n\t\tsuper(TYPE_ID, parent, innerClass);\n\t}\n\n\t/**\n\t * @param annotation\n\t * \t\tAnnotation to wrap into node.\n\t *\n\t * @return Path node of annotation, with the current inner class as parent.\n\t */\n\t@Nonnull\n\tpublic AnnotationPathNode child(@Nonnull AnnotationInfo annotation) {\n\t\treturn new AnnotationPathNode(this, annotation);\n\t}\n\n\t@Override\n\tpublic ClassPathNode getParent() {\n\t\treturn (ClassPathNode) super.getParent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(ClassPathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o) return 0;\n\n\t\tif (o instanceof InnerClassPathNode innerClassPathNode) {\n\t\t\tString name = getValue().getInnerClassName();\n\t\t\tString otherName = innerClassPathNode.getValue().getInnerClassName();\n\t\t\treturn Named.STRING_PATH_COMPARATOR.compare(name, otherName);\n\t\t}\n\n\t\t// Show before members\n\t\tif (o instanceof ClassMemberPathNode)\n\t\t\treturn -1;\n\n\t\t// Show after annos\n\t\tif (o instanceof AnnotationPathNode)\n\t\t\treturn 1;\n\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/InstructionPathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport java.util.Set;\n\n/**\n * Path node for instructions within {@link MethodMember} instances.\n *\n * @author Matt Coley\n */\npublic class InstructionPathNode extends AbstractPathNode<ClassMember, AbstractInsnNode> {\n\t/**\n\t * Type identifier for instruction nodes.\n\t */\n\tpublic static final String TYPE_ID = \"instruction\";\n\tprivate final int index;\n\n\t/**\n\t * Node without parent.\n\t *\n\t * @param insn\n\t * \t\tInstruction value.\n\t * @param index\n\t * \t\tIndex of the instruction within the method code.\n\t */\n\tpublic InstructionPathNode(@Nonnull AbstractInsnNode insn, int index) {\n\t\tthis(null, insn, index);\n\t}\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param insn\n\t * \t\tInstruction value.\n\t * @param index\n\t * \t\tIndex of the instruction within the method code.\n\t *\n\t * @see ClassMemberPathNode#childInsn(AbstractInsnNode, int)\n\t */\n\tpublic InstructionPathNode(@Nullable ClassMemberPathNode parent, @Nonnull AbstractInsnNode insn, int index) {\n\t\tsuper(TYPE_ID, parent, insn);\n\t\tthis.index = index;\n\t}\n\n\t/**\n\t * @return Index of the instruction within the method code <i>(As determined by ASM)</i>.\n\t */\n\tpublic int getInstructionIndex() {\n\t\treturn index;\n\t}\n\n\t@Override\n\tpublic ClassMemberPathNode getParent() {\n\t\treturn (ClassMemberPathNode) super.getParent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(ClassMemberPathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o) return 0;\n\n\t\tif (o instanceof InstructionPathNode node)\n\t\t\treturn Integer.compare(index, node.index);\n\t\telse if (o instanceof ThrowsPathNode || o instanceof CatchPathNode || o instanceof LocalVariablePathNode)\n\t\t\treturn 1;\n\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/LineNumberPathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.FileInfo;\n\nimport java.util.Set;\n\n/**\n * Path node for line numbers within text {@link FileInfo} instances.\n *\n * @author Matt Coley\n */\npublic class LineNumberPathNode extends AbstractPathNode<FileInfo, Integer> {\n\t/**\n\t * Type identifier for line number nodes.\n\t */\n\tpublic static final String TYPE_ID = \"line\";\n\n\t/**\n\t * Node without parent.\n\t *\n\t * @param line\n\t * \t\tLine number value.\n\t */\n\tpublic LineNumberPathNode(int line) {\n\t\tthis(null, line);\n\t}\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param line\n\t * \t\tLine number value.\n\t *\n\t * @see FilePathNode#child(int)\n\t */\n\tpublic LineNumberPathNode(@Nullable FilePathNode parent, int line) {\n\t\tsuper(TYPE_ID, parent, line);\n\t}\n\n\t@Override\n\tpublic FilePathNode getParent() {\n\t\treturn (FilePathNode) super.getParent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(FilePathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o) return 0;\n\n\t\tif (o instanceof LineNumberPathNode lineNode) {\n\t\t\tint i = getValue().compareTo(lineNode.getValue());\n\t\t\tif (i == 0) {\n\t\t\t\t// Fall back to parent file comparison if the local line numbers are the same.\n\t\t\t\t// Not ideal, but we can't validate anything else here.\n\t\t\t\tFilePathNode parent = getParent();\n\t\t\t\tFilePathNode otherParent = lineNode.getParent();\n\t\t\t\tif (parent != null && otherParent != null)\n\t\t\t\t\ti = parent.localCompare(otherParent);\n\t\t\t}\n\t\t\treturn i;\n\t\t}\n\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/LocalVariablePathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport java.util.Set;\n\n/**\n * Path node for local variables within {@link MethodMember} instances.\n *\n * @author Matt Coley\n */\npublic class LocalVariablePathNode extends AbstractPathNode<ClassMember, LocalVariable> {\n\t/**\n\t * Type identifier for local variable nodes.\n\t */\n\tpublic static final String TYPE_ID = \"variable\";\n\n\t/**\n\t * Node without parent.\n\t *\n\t * @param variable\n\t * \t\tVariable value.\n\t */\n\tpublic LocalVariablePathNode(@Nonnull LocalVariable variable) {\n\t\tthis(null, variable);\n\t}\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param variable\n\t * \t\tVariable value.\n\t *\n\t * @see ClassMemberPathNode#childVariable(LocalVariable)\n\t */\n\tpublic LocalVariablePathNode(@Nullable ClassMemberPathNode parent, @Nonnull LocalVariable variable) {\n\t\tsuper(TYPE_ID, parent, variable);\n\t}\n\n\t@Override\n\tpublic ClassMemberPathNode getParent() {\n\t\treturn (ClassMemberPathNode) super.getParent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(ClassMemberPathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o) return 0;\n\n\t\tif (o instanceof LocalVariablePathNode node) {\n\t\t\tLocalVariable value = getValue();\n\t\t\tLocalVariable otherValue = node.getValue();\n\t\t\tint cmp = Integer.compare(value.getIndex(), otherValue.getIndex());\n\t\t\tif (cmp == 0)\n\t\t\t\tcmp = value.getName().compareTo(otherValue.getName());\n\t\t\tif (cmp == 0)\n\t\t\t\tcmp = value.getDescriptor().compareTo(otherValue.getDescriptor());\n\t\t\treturn cmp;\n\t\t} else if (o instanceof ThrowsPathNode || o instanceof CatchPathNode)\n\t\t\treturn 1;\n\t\telse if (o instanceof InstructionPathNode)\n\t\t\treturn -1;\n\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/PathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Set;\nimport java.util.function.Consumer;\n\n/**\n * A <i>\"modular\"</i> value type for representing <i>\"paths\"</i> to content in a {@link Workspace}.\n * The path must contain all data in a <i>\"chain\"</i> such that it can have access from most specific portion\n * all the way up to the {@link Workspace} portion.\n * <p>\n * <b>NOTE: Regarding contents in embedded resources,</b> the path result of the methods like\n * {@link Workspace#findClass(String)} will contain the root {@link WorkspaceResource} but the exact {@link Bundle}.\n * To find the exact embedded resource of a result use {@link WorkspaceResource#resolveBundleContainer(Bundle)}.\n *\n * @param <V>\n * \t\tPath value type.\n *\n * @author Matt Coley\n */\npublic interface PathNode<V> extends Comparable<PathNode<?>> {\n\t/**\n\t * The parent node of this node. This value does not have to be present in the actual UI model.\n\t * The parent linkage is so that child types like {@link ClassPathNode} can access their full scope,\n\t * including their containing {@link DirectoryPathNode package}, {@link BundlePathNode bundle},\n\t * {@link ResourcePathNode resource}, and {@link WorkspacePathNode workspace}.\n\t * <br>\n\t * This allows child-types such as {@link ClassPathNode} to be passed around to consuming APIs and retain access\n\t * to the mentioned scoped values.\n\t *\n\t * @return Parent node.\n\t *\n\t * @see #getValueOfType(Class) Used by child-types to look up values in themselves, and their parents.\n\t */\n\t@Nullable\n\t@SuppressWarnings(\"rawtypes\")\n\tPathNode getParent();\n\n\t/**\n\t * Creates a copy of the path node with this child-most node's value being looked up for a newer\n\t * value in the associated workspace.\n\t * <p>\n\t * <b>Note:</b> A {@link WorkspacePathNode} must be present.\n\t *\n\t * @return A new path node pointing to the same location,\n\t * but with the {@link #getValue() value} being updated to the current in the associated {@link Workspace}.\n\t * If a lookup could not be done then the current instance is returned.\n\t */\n\t@Nonnull\n\t@SuppressWarnings(\"rawtypes\")\n\tdefault PathNode withCurrentWorkspaceContent() {\n\t\treturn this;\n\t}\n\n\t/**\n\t * @return Wrapped value.\n\t */\n\t@Nonnull\n\tV getValue();\n\n\t/**\n\t * @param other\n\t * \t\tSome other path node.\n\t *\n\t * @return {@code true} when the other path has the same {@link #getValue() local value}.\n\t */\n\tdefault boolean hasEqualOrChildValue(@Nonnull PathNode<?> other) {\n\t\tV value = getValue();\n\t\tObject otherValue = other.getValue();\n\t\treturn this == other || value == otherValue || value.equals(otherValue);\n\t}\n\n\t/**\n\t * Used to differentiate path nodes in a chain that have the same {@link #getValueType()}.\n\t *\n\t * @return String unique ID per path-node type.\n\t */\n\t@Nonnull\n\tString typeId();\n\n\t/**\n\t * @param node\n\t * \t\tOther node to check.\n\t *\n\t * @return {@code true} when the current {@link #typeId()} is the same as the other's ID.\n\t */\n\tdefault boolean typeIdMatch(@Nonnull PathNode<?> node) {\n\t\treturn typeId().equals(node.typeId());\n\t}\n\n\t/**\n\t * @return Set of expected {@link #typeId()} values for {@link #getParent() parent nodes}.\n\t */\n\t@Nonnull\n\tSet<String> directParentTypeIds();\n\n\t/**\n\t * @return The type of this path node's {@link #getValue() wrapped value}.\n\t */\n\t@Nonnull\n\tClass<V> getValueType();\n\n\t/**\n\t * @param type\n\t * \t\tSome type contained in the full path.\n\t * \t\tThis includes the current {@link PathNode} and any {@link #getParent() parent}.\n\t * @param <T>\n\t * \t\tImplied value type.\n\t * @param <I>\n\t * \t\tImplied path node implementation type.\n\t *\n\t * @return Node in the path holding a value of the given type.\n\t *\n\t * @see #getValueOfType(Class) Get the direct value of the parent node.\n\t */\n\t@Nullable\n\t@SuppressWarnings(\"unchecked\")\n\tdefault <T, I extends PathNode<? extends T>> I getPathOfType(@Nonnull Class<T> type) {\n\t\tPathNode<?> path = this;\n\t\twhile (path != null) {\n\t\t\tif (type.isAssignableFrom(path.getValueType()))\n\t\t\t\treturn (I) path;\n\t\t\tpath = path.getParent();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tSome type contained in the full path.\n\t * \t\tThis includes the current {@link PathNode} and any {@link #getParent() parent}.\n\t * @param <T>\n\t * \t\tImplied value type.\n\t *\n\t * @return Instance of value from the path, or {@code null} if not found in this path.\n\t *\n\t * @see #getPathOfType(Class) Get the containing {@link PathNode} instead of the direct value.\n\t */\n\t@Nullable\n\t@SuppressWarnings(\"unchecked\")\n\tdefault <T> T getValueOfType(@Nonnull Class<T> type) {\n\t\tPathNode<?> path = this;\n\t\twhile (path != null) {\n\t\t\tif (type.isAssignableFrom(path.getValueType()))\n\t\t\t\treturn (T) path.getValue();\n\t\t\tpath = path.getParent();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tSome type contained in the full path.\n\t * \t\tThis includes the current {@link PathNode} and any {@link #getParent() parent}.\n\t * @param action\n\t * \t\tAction to run on the discovered value in the path.\n\t * \t\tIf no value is found, the action is not run.\n\t * @param <T>\n\t * \t\tImplied value type.\n\t *\n\t * @return {@code true} when a matching value was found and the action was run.\n\t */\n\tdefault <T> boolean onValue(@Nonnull Class<T> type, @Nonnull Consumer<T> action) {\n\t\tT value = getValueOfType(type);\n\t\tif (value != null) {\n\t\t\taction.accept(value);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tSome {@link PathNode} type.\n\t * @param action\n\t * \t\tAction to run on the discovered path node.\n\t * \t\tIf no path node is found, the action is not run.\n\t * @param <T>\n\t * \t\tImplied path type.\n\t *\n\t * @return {@code true} when a matching path node was found and the action was run.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tdefault <T extends PathNode<?>> boolean onPath(@Nonnull Class<T> type, @Nonnull Consumer<T> action) {\n\t\tif (type == getClass()) {\n\t\t\taction.accept((T) this);\n\t\t\treturn true;\n\t\t} else {\n\t\t\tPathNode<?> parent = getParent();\n\t\t\tif (parent != null)\n\t\t\t\treturn parent.onPath(type, action);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Checks for tree alignment. Consider this simple example:\n\t * <pre>\n\t *   Path1   Path2   Path3\n\t *     A       A       A\n\t *     |       |       |\n\t *     B       B       B\n\t *     |       |       |\n\t *     C       C       X\n\t * </pre>\n\t * With this setup:\n\t * <ul>\n\t *     <li>{@code path1C.allParentsMatch(path1C) == true} Self checks are equal</li>\n\t *     <li>{@code path1C.allParentsMatch(path2C) == true} Two identical paths <i>(by value of each node)</i> are equal</li>\n\t *     <li>{@code path1C.allParentsMatch(path2B) == false} Comparing between non-parallel levels are not equal</li>\n\t *     <li>{@code path1C.allParentsMatch(path3X) == false} Paths to different items are not equal</li>\n\t * </ul>\n\t *\n\t * @param other\n\t * \t\tSome other path node.\n\t *\n\t * @return {@code true} when from this level all parents going up the path match values.\n\t */\n\tdefault boolean allParentsMatch(@Nonnull PathNode<?> other) {\n\t\t// Type identifiers should match for all levels.\n\t\tif (!typeId().equals(other.typeId()))\n\t\t\treturn false;\n\n\t\t// Should both have the same level of tree heights (number of parents).\n\t\tPathNode<?> parent = getParent();\n\t\tPathNode<?> otherParent = other.getParent();\n\t\tif (parent == null && otherParent == null)\n\t\t\t// Root node edge case\n\t\t\treturn hasEqualOrChildValue(other);\n\t\telse if (parent == null || otherParent == null)\n\t\t\t// Mismatch in tree structure height\n\t\t\treturn false;\n\n\t\t// Go up the chain if the matching values continue.\n\t\tif (hasEqualOrChildValue(other))\n\t\t\treturn parent.allParentsMatch(otherParent);\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param other\n\t * \t\tSome other path node.\n\t *\n\t * @return {@code true} when our path represents a more generic path than the given one.\n\t * {@code false} when our path does not belong to parent path of the given item.\n\t */\n\tdefault boolean isParentOf(@Nonnull PathNode<?> other) {\n\t\treturn other.isDescendantOf(this);\n\t}\n\n\t/**\n\t * @param other\n\t * \t\tSome other path node.\n\t *\n\t * @return {@code true} when our path represents a more specific path than the given one.\n\t * {@code false} when our path does not belong to a potential sub-path of the given item.\n\t */\n\tdefault boolean isDescendantOf(@Nonnull PathNode<?> other) {\n\t\t// If our type identifiers are the same everything going up the path should match.\n\t\tString otherTypeId = other.typeId();\n\t\tif (otherTypeId.equals(typeId()))\n\t\t\treturn hasEqualOrChildValue(other) && allParentsMatch(other);\n\n\t\t// Check if the other is an allowed parent.\n\t\tPathNode<?> parent = getParent();\n\t\tif (directParentTypeIds().contains(otherTypeId) && parent != null) {\n\t\t\t// The parent is an allowed type, check if the parent says it is a descendant of the other path.\n\t\t\tif (parent == other || (parent.hasEqualOrChildValue(other)))\n\t\t\t\treturn parent.isDescendantOf(other);\n\t\t}\n\n\t\t// Check in parent.\n\t\tif (parent != null)\n\t\t\treturn parent.isDescendantOf(other);\n\n\t\t// Not a descendant.\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param o\n\t * \t\tSome other path node.\n\t *\n\t * @return Comparison for visual sorting purposes.\n\t */\n\tint localCompare(PathNode<?> o);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/PathNodes.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collections;\nimport java.util.Set;\n\n/**\n * Utility methods for constructing paths.\n * <p>\n * Generally you should use this only when the path creation is\n * a <i>\"one time\"</i> action. For instance, if you want to make a path to a single method, use\n * {@link #memberPath(Workspace, WorkspaceResource, Bundle, ClassInfo, ClassMember)}.\n * <p>\n * However, if you want to make a path to <i>all methods in a class</i> then you would use\n * {@link #classPath(Workspace, WorkspaceResource, Bundle, ClassInfo)} to get a {@link ClassPathNode}\n * and then use {@link ClassPathNode#child(ClassMember)} for each member. This reduces the number of redundant\n * allocations of parent path node types in the chain.\n *\n * @author Matt Coley\n */\npublic class PathNodes {\n\tprivate PathNodes() {\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t *\n\t * @return Path to a workspace.\n\t */\n\t@Nonnull\n\tpublic static WorkspacePathNode workspacePath(@Nonnull Workspace workspace) {\n\t\treturn new WorkspacePathNode(workspace);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t * @param resource\n\t * \t\tResource to wrap into path.\n\t *\n\t * @return Path to resource.\n\t */\n\t@Nonnull\n\tpublic static ResourcePathNode resourcePath(@Nonnull Workspace workspace,\n\t                                            @Nonnull WorkspaceResource resource) {\n\t\treturn workspacePath(workspace).child(resource);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t * @param resource\n\t * \t\tResource to wrap into path.\n\t * @param bundle\n\t * \t\tBundle to wrap into path.\n\t *\n\t * @return Path to bundle.\n\t */\n\t@Nonnull\n\tpublic static BundlePathNode bundlePath(@Nonnull Workspace workspace,\n\t                                        @Nonnull WorkspaceResource resource,\n\t                                        @Nonnull Bundle<?> bundle) {\n\t\treturn resourcePath(workspace, resource).child(bundle);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t * @param resource\n\t * \t\tResource to wrap into path.\n\t * @param bundle\n\t * \t\tBundle to wrap into path.\n\t * @param directory\n\t * \t\tDirectory or package name to wrap into path.\n\t *\n\t * @return Path to directory or package <i>(Depending on bundle type)</i>.\n\t */\n\t@Nonnull\n\tpublic static DirectoryPathNode directoryPath(@Nonnull Workspace workspace,\n\t                                              @Nonnull WorkspaceResource resource,\n\t                                              @Nonnull Bundle<?> bundle,\n\t                                              @Nullable String directory) {\n\t\treturn bundlePath(workspace, resource, bundle).child(directory);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t * @param resource\n\t * \t\tResource to wrap into path.\n\t * @param bundle\n\t * \t\tBundle to wrap into path.\n\t * @param cls\n\t * \t\tClass to wrap into path.\n\t *\n\t * @return Path to class.\n\t */\n\t@Nonnull\n\tpublic static ClassPathNode classPath(@Nonnull Workspace workspace,\n\t                                      @Nonnull WorkspaceResource resource,\n\t                                      @Nonnull Bundle<?> bundle,\n\t                                      @Nonnull ClassInfo cls) {\n\t\treturn directoryPath(workspace, resource, bundle, cls.getPackageName()).child(cls);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t * @param resource\n\t * \t\tResource to wrap into path.\n\t * @param bundle\n\t * \t\tBundle to wrap into path.\n\t * @param cls\n\t * \t\tClass to wrap into path.\n\t * @param member\n\t * \t\tMember to wrap into path.\n\t *\n\t * @return Path to class member <i>(field or method)</i>.\n\t */\n\t@Nonnull\n\tpublic static ClassMemberPathNode memberPath(@Nonnull Workspace workspace,\n\t                                             @Nonnull WorkspaceResource resource,\n\t                                             @Nonnull Bundle<?> bundle,\n\t                                             @Nonnull ClassInfo cls,\n\t                                             @Nonnull ClassMember member) {\n\t\treturn classPath(workspace, resource, bundle, cls).child(member);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t * @param resource\n\t * \t\tResource to wrap into path.\n\t * @param bundle\n\t * \t\tBundle to wrap into path.\n\t * @param cls\n\t * \t\tClass to wrap into path.\n\t * @param member\n\t * \t\tMember to wrap into path.\n\t * @param annotation\n\t * \t\tAnnotation on member to wrap into path.\n\t *\n\t * @return Path to annotation on the member.\n\t */\n\t@Nonnull\n\tpublic static AnnotationPathNode annotationPath(@Nonnull Workspace workspace,\n\t                                                @Nonnull WorkspaceResource resource,\n\t                                                @Nonnull Bundle<?> bundle,\n\t                                                @Nonnull ClassInfo cls,\n\t                                                @Nonnull ClassMember member,\n\t                                                @Nonnull AnnotationInfo annotation) {\n\t\treturn memberPath(workspace, resource, bundle, cls, member).childAnnotation(annotation);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t * @param resource\n\t * \t\tResource to wrap into path.\n\t * @param bundle\n\t * \t\tBundle to wrap into path.\n\t * @param cls\n\t * \t\tClass to wrap into path.\n\t * @param method\n\t * \t\tMethod to wrap into path.\n\t * @param variable\n\t * \t\tVariable in method to wrap into path.\n\t *\n\t * @return Path to variable in the method.\n\t */\n\t@Nonnull\n\tpublic static LocalVariablePathNode variablePath(@Nonnull Workspace workspace,\n\t                                                 @Nonnull WorkspaceResource resource,\n\t                                                 @Nonnull Bundle<?> bundle,\n\t                                                 @Nonnull ClassInfo cls,\n\t                                                 @Nonnull MethodMember method,\n\t                                                 @Nonnull LocalVariable variable) {\n\t\treturn memberPath(workspace, resource, bundle, cls, method).childVariable(variable);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t * @param resource\n\t * \t\tResource to wrap into path.\n\t * @param bundle\n\t * \t\tBundle to wrap into path.\n\t * @param cls\n\t * \t\tClass to wrap into path.\n\t * @param method\n\t * \t\tMethod to wrap into path.\n\t * @param insn\n\t * \t\tInstruction in method to wrap into path.\n\t * @param index\n\t * \t\tIndex of the instruction within the method code.\n\t *\n\t * @return Path to instruction in the method.\n\t */\n\t@Nonnull\n\tpublic static InstructionPathNode instructionPath(@Nonnull Workspace workspace,\n\t                                                  @Nonnull WorkspaceResource resource,\n\t                                                  @Nonnull Bundle<?> bundle,\n\t                                                  @Nonnull ClassInfo cls,\n\t                                                  @Nonnull MethodMember method,\n\t                                                  @Nonnull AbstractInsnNode insn,\n\t                                                  int index) {\n\t\treturn memberPath(workspace, resource, bundle, cls, method).childInsn(insn, index);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t * @param resource\n\t * \t\tResource to wrap into path.\n\t * @param bundle\n\t * \t\tBundle to wrap into path.\n\t * @param cls\n\t * \t\tClass to wrap into path.\n\t * @param method\n\t * \t\tMethod to wrap into path.\n\t * @param thrownType\n\t * \t\tType thrown by the method to wrap into path.\n\t *\n\t * @return Path to the thrown type on the method.\n\t */\n\t@Nonnull\n\tpublic static ThrowsPathNode throwsPath(@Nonnull Workspace workspace,\n\t                                        @Nonnull WorkspaceResource resource,\n\t                                        @Nonnull Bundle<?> bundle,\n\t                                        @Nonnull ClassInfo cls,\n\t                                        @Nonnull MethodMember method,\n\t                                        @Nonnull String thrownType) {\n\t\treturn memberPath(workspace, resource, bundle, cls, method).childThrows(thrownType);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t * @param resource\n\t * \t\tResource to wrap into path.\n\t * @param bundle\n\t * \t\tBundle to wrap into path.\n\t * @param cls\n\t * \t\tClass to wrap into path.\n\t * @param method\n\t * \t\tMethod to wrap into path.\n\t * @param caughtType\n\t * \t\tException type caught by a {@code catch(T)} block to wrap into path.\n\t *\n\t * @return Path to any {@code catch(T)} block in the method of the given exception type.\n\t */\n\t@Nonnull\n\tpublic static CatchPathNode catchPath(@Nonnull Workspace workspace,\n\t                                      @Nonnull WorkspaceResource resource,\n\t                                      @Nonnull Bundle<?> bundle,\n\t                                      @Nonnull ClassInfo cls,\n\t                                      @Nonnull MethodMember method,\n\t                                      @Nonnull String caughtType) {\n\t\treturn memberPath(workspace, resource, bundle, cls, method).childCatch(caughtType);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t * @param resource\n\t * \t\tResource to wrap into path.\n\t * @param bundle\n\t * \t\tBundle to wrap into path.\n\t * @param cls\n\t * \t\tClass to wrap into path.\n\t * @param innerCls\n\t * \t\tInner class to wrap into path.\n\t *\n\t * @return Path to inner class.\n\t */\n\t@Nonnull\n\tpublic static InnerClassPathNode innerClassPath(@Nonnull Workspace workspace,\n\t                                                @Nonnull WorkspaceResource resource,\n\t                                                @Nonnull Bundle<?> bundle,\n\t                                                @Nonnull ClassInfo cls,\n\t                                                @Nonnull InnerClassInfo innerCls) {\n\t\treturn classPath(workspace, resource, bundle, cls).child(innerCls);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t * @param resource\n\t * \t\tResource to wrap into path.\n\t * @param bundle\n\t * \t\tBundle to wrap into path.\n\t * @param cls\n\t * \t\tClass to wrap into path.\n\t * @param annotation\n\t * \t\tAnnotation on the class to wrap into path.\n\t *\n\t * @return Path to annotation on the class.\n\t */\n\t@Nonnull\n\tpublic static AnnotationPathNode annotationPath(@Nonnull Workspace workspace,\n\t                                                @Nonnull WorkspaceResource resource,\n\t                                                @Nonnull Bundle<?> bundle,\n\t                                                @Nonnull ClassInfo cls,\n\t                                                @Nonnull AnnotationInfo annotation) {\n\t\treturn classPath(workspace, resource, bundle, cls).child(annotation);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to wrap into path.\n\t * @param resource\n\t * \t\tResource to wrap into path.\n\t * @param bundle\n\t * \t\tBundle to wrap into path.\n\t * @param file\n\t * \t\tFile to wrap into path.\n\t *\n\t * @return Path to file.\n\t */\n\t@Nonnull\n\tpublic static FilePathNode filePath(@Nonnull Workspace workspace,\n\t                                    @Nonnull WorkspaceResource resource,\n\t                                    @Nonnull Bundle<?> bundle,\n\t                                    @Nonnull FileInfo file) {\n\t\treturn directoryPath(workspace, resource, bundle, file.getDirectoryName()).child(file);\n\t}\n\n\t/**\n\t * @param identifier\n\t * \t\tUnique identifier for this path.\n\t *\n\t * @return Path of unique identifier.\n\t */\n\t@Nonnull\n\tpublic static PathNode<?> unique(@Nonnull String identifier) {\n\t\treturn new ArbitraryStringPathNode(identifier);\n\t}\n\n\t/**\n\t * A path node that just holds an arbitrary string.\n\t * <p>\n\t * Intended for use in the UI where displayed panels are intended to be navigable for tracking, not actually\n\t * navigable in terms of their relationship to some location in a workspace.\n\t *\n\t * @see #unique(String)\n\t */\n\tprivate static class ArbitraryStringPathNode extends AbstractPathNode<Object, Object> {\n\t\t/**\n\t\t * @param value\n\t\t * \t\tValue instance.\n\t\t */\n\t\tprotected ArbitraryStringPathNode(@Nonnull String value) {\n\t\t\tsuper(\"uid\", null, value);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Set<String> directParentTypeIds() {\n\t\t\treturn Collections.emptySet();\n\t\t}\n\n\t\t@Override\n\t\tpublic int localCompare(PathNode<?> o) {\n\t\t\treturn 0;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/ResourcePathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.collections.Maps;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.info.Named;\nimport software.coley.recaf.util.CollectionUtils;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * Path node for {@link WorkspaceResource} types.\n *\n * @author Matt Coley\n */\npublic class ResourcePathNode extends AbstractPathNode<Workspace, WorkspaceResource> {\n\t/**\n\t * Type identifier for annotation nodes.\n\t */\n\tpublic static final String TYPE_ID = \"resource\";\n\n\t/**\n\t * Node without parent.\n\t *\n\t * @param resource\n\t * \t\tResource value.\n\t */\n\tpublic ResourcePathNode(@Nonnull WorkspaceResource resource) {\n\t\tthis((WorkspacePathNode) null, resource);\n\t}\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param resource\n\t * \t\tResource value.\n\t *\n\t * @see WorkspacePathNode#child(WorkspaceResource)\n\t */\n\tpublic ResourcePathNode(@Nullable WorkspacePathNode parent, @Nonnull WorkspaceResource resource) {\n\t\tsuper(TYPE_ID, parent, resource);\n\t}\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param resource\n\t * \t\tResource value.\n\t *\n\t * @see WorkspacePathNode#child(WorkspaceResource)\n\t */\n\tpublic ResourcePathNode(@Nullable EmbeddedResourceContainerPathNode parent, @Nonnull WorkspaceResource resource) {\n\t\tsuper(TYPE_ID, parent, resource);\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tBundle to wrap into node.\n\t *\n\t * @return Path node of bundle, with the current resource as parent.\n\t */\n\t@Nonnull\n\tpublic BundlePathNode child(@Nonnull Bundle<?> bundle) {\n\t\treturn new BundlePathNode(this, bundle);\n\t}\n\n\t/**\n\t * @return Path node for a container of multiple embedded resources.\n\t */\n\t@Nonnull\n\tpublic EmbeddedResourceContainerPathNode embeddedChildContainer() {\n\t\tWorkspace valueOfType = Objects.requireNonNull(getValueOfType(Workspace.class),\n\t\t\t\t\"Path did not contain workspace in parent\");\n\t\treturn new EmbeddedResourceContainerPathNode(this, valueOfType);\n\t}\n\n\t/**\n\t * @return {@code true} when this resource node, wraps the primary resource of a workspace.\n\t */\n\tpublic boolean isPrimary() {\n\t\tPathNode<Workspace> parent = getParent();\n\t\tif (parent == null)\n\t\t\treturn false;\n\t\treturn parent.getValue().getPrimaryResource() == getValue();\n\t}\n\n\t/**\n\t * @return {@code true} when this resource node, wraps the primary resource of a workspace or any resource embedded in the primary resource.\n\t */\n\tpublic boolean isPrimaryOrEmbeddedInPrimary() {\n\t\tPathNode<Workspace> parent = getParent();\n\t\tif (parent == null)\n\t\t\treturn false;\n\t\tWorkspace workspace = parent.getValue();\n\t\tWorkspaceResource primary = workspace.getPrimaryResource();\n\t\tWorkspaceResource resource = getValue();\n\t\twhile (resource != null) {\n\t\t\tif (primary == resource)\n\t\t\t\treturn true;\n\t\t\tresource = resource.getContainingResource();\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(WorkspacePathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o) return 0;\n\n\t\tif (o instanceof ResourcePathNode resourcePathNode) {\n\t\t\tPathNode<Workspace> parent = getParent();\n\t\t\tWorkspace workspace = parentValue();\n\t\t\tWorkspaceResource resource = getValue();\n\t\t\tWorkspaceResource otherResource = resourcePathNode.getValue();\n\n\t\t\tif (parent instanceof EmbeddedResourceContainerPathNode) {\n\t\t\t\tPathNode<WorkspaceResource> parentOfParent = Unchecked.cast(parent.getParent());\n\t\t\t\tMap<WorkspaceFileResource, String> lookup = Maps.reverse(parentOfParent.getValue().getEmbeddedResources());\n\t\t\t\tString ourKey = lookup.getOrDefault(resource, \"?\");\n\t\t\t\tString otherKey = lookup.getOrDefault(otherResource, \"?\");\n\t\t\t\treturn Named.STRING_PATH_COMPARATOR.compare(ourKey, otherKey);\n\t\t\t} else {\n\t\t\t\tif (workspace != null) {\n\t\t\t\t\tif (resource == otherResource)\n\t\t\t\t\t\treturn 0;\n\n\t\t\t\t\t// Show in order as in the workspace.\n\t\t\t\t\tList<WorkspaceResource> resources = workspace.getAllResources(false);\n\t\t\t\t\treturn Integer.compare(CollectionUtils.identityIndexOf(resources, resource), CollectionUtils.identityIndexOf(resources, otherResource));\n\t\t\t\t} else {\n\t\t\t\t\t// Enforce some ordering. Not ideal but works.\n\t\t\t\t\treturn Named.STRING_COMPARATOR.compare(\n\t\t\t\t\t\t\tresource.getClass().getSimpleName(),\n\t\t\t\t\t\t\totherResource.getClass().getSimpleName()\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\t// Resource equality checks are abysmally slow because are checking if all the contained\n\t\t// contents are also equal. Realistically we can get away with a reference check.\n\t\tif (o instanceof ResourcePathNode otherPath)\n\t\t\treturn getValue() == otherPath.getValue() && Objects.equals(getParent(), otherPath.getParent());\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/ThrowsPathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport java.util.Set;\n\n/**\n * Path node for {@code throws} on {@link MethodMember} instances.\n *\n * @author Matt Coley\n */\npublic class ThrowsPathNode extends AbstractPathNode<ClassMember, String> {\n\t/**\n\t * Type identifier for throws nodes.\n\t */\n\tpublic static final String TYPE_ID = \"throws\";\n\n\t/**\n\t * Node without parent.\n\t *\n\t * @param type\n\t * \t\tThrown type.\n\t */\n\tpublic ThrowsPathNode(@Nonnull String type) {\n\t\tthis(null, type);\n\t}\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param type\n\t * \t\tThrown type.\n\t *\n\t * @see ClassMemberPathNode#childThrows(String)\n\t */\n\tpublic ThrowsPathNode(@Nullable ClassMemberPathNode parent, @Nonnull String type) {\n\t\tsuper(TYPE_ID, parent, type);\n\t}\n\n\t@Override\n\tpublic ClassMemberPathNode getParent() {\n\t\treturn (ClassMemberPathNode) super.getParent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(ClassMemberPathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o) return 0;\n\n\t\tif (o instanceof ThrowsPathNode node)\n\t\t\treturn getValue().compareTo(node.getValue());\n\t\telse if (o instanceof LocalVariablePathNode || o instanceof InstructionPathNode || o instanceof CatchPathNode)\n\t\t\treturn -1;\n\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/path/WorkspacePathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collections;\nimport java.util.Set;\n\n/**\n * Path node for {@link Workspace} types.\n *\n * @author Matt Coley\n */\npublic class WorkspacePathNode extends AbstractPathNode<Object, Workspace> {\n\t/**\n\t * Type identifier for workspace nodes.\n\t */\n\tpublic static final String TYPE_ID = \"workspace\";\n\n\t/**\n\t * Node without parent.\n\t *\n\t * @param value\n\t * \t\tWorkspace value.\n\t */\n\tpublic WorkspacePathNode(@Nonnull Workspace value) {\n\t\tsuper(TYPE_ID, null, value);\n\t}\n\n\t/**\n\t * @param resource\n\t * \t\tResource to wrap into node.\n\t *\n\t * @return Path node of resource, with the current workspace as parent,\n\t * or a {@link EmbeddedResourceContainerPathNode} if the passed resource is an embedded resource.\n\t */\n\t@Nonnull\n\tpublic ResourcePathNode child(@Nonnull WorkspaceResource resource) {\n\t\t// Base case, resource is top-level in the workspace.\n\t\tWorkspaceResource containingResource = resource.getContainingResource();\n\t\tif (containingResource == null)\n\t\t\treturn new ResourcePathNode(this, resource);\n\n\t\t// Resource is embedded, so we need to represent the path a bit differently.\n\t\t// - Note: We flatten the representation of embedded resources here.\n\t\tWorkspaceResource rootResource = containingResource;\n\t\twhile (rootResource.getContainingResource() != null)\n\t\t\trootResource = rootResource.getContainingResource();\n\t\treturn new ResourcePathNode(this, rootResource)\n\t\t\t\t.embeddedChildContainer()\n\t\t\t\t.child(resource);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Collections.emptySet();\n\t}\n\n\t@Override\n\tpublic boolean isDescendantOf(@Nonnull PathNode<?> other) {\n\t\t// Workspace is the root of all paths.\n\t\t// Only other workspace paths with the same value should count here.\n\t\tif (typeId().equals(other.typeId()))\n\t\t\treturn getValue().equals(other.getValue());\n\n\t\t// We have no parents.\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\t// Workspace equality checks are abysmally slow because are checking if all the contained\n\t\t// contents are also equal. Realistically we can get away with a reference check.\n\t\tif (o instanceof WorkspacePathNode otherPath)\n\t\t\treturn getValue() == otherPath.getValue();\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/plugin/Plugin.java",
    "content": "package software.coley.recaf.plugin;\n\n/**\n * Base interface that all plugins must inherit from.\n * Classes that implement this type should also be annotated with {@link PluginInformation}.\n *\n * @author xDark\n */\npublic interface Plugin {\n\t/**\n\t * Called when plugin is being enabled.\n\t */\n\tvoid onEnable();\n\n\t/**\n\t * Called when plugin is being disabled.\n\t */\n\tvoid onDisable();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/plugin/PluginInformation.java",
    "content": "package software.coley.recaf.plugin;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Plugin annotation containing necessary information.\n *\n * @author xDark\n */\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface PluginInformation {\n\n\t/**\n\t * @return ID of the plugin.\n\t */\n\tString id();\n\n\t/**\n\t * @return Name of the plugin.\n\t */\n\tString name();\n\n\t/**\n\t * @return Version of the plugin.\n\t */\n\tString version();\n\n\t/**\n\t * @return Author of the plugin.\n\t */\n\tString author() default \"\";\n\n\t/**\n\t * @return Description of the plugin.\n\t */\n\tString description() default \"\";\n\n\t/**\n\t * @return Plugin dependencies (IDs of dependency plugins).\n\t */\n\tString[] dependencies() default {};\n\n\t/**\n\t * @return Plugin soft dependencies (IDs of dependency plugins).\n\t */\n\tString[] softDependencies() default {};\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/Service.java",
    "content": "package software.coley.recaf.services;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Outline of a service.\n *\n * @author Matt Coley\n */\npublic interface Service {\n\t/**\n\t * @return A unique string for identifying the service.\n\t */\n\t@Nonnull\n\tString getServiceId();\n\n\t/**\n\t * @return The config instance for the service.\n\t */\n\t@Nonnull\n\tServiceConfig getServiceConfig();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/ServiceConfig.java",
    "content": "package software.coley.recaf.services;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.ConfigContainer;\n\n/**\n * Base type for a service's config type.\n * When a service implementation uses {@link Inject} to inject the config instance some rules should be followed:\n * <ul>\n *     <li>The type injected is the implementation type, not {@link ServiceConfig}</li>\n *     <li>The config implementation type is annotated with {@link ApplicationScoped} so that it is shared\n *     among all instances of a service.</li>\n * </ul>\n *\n * @author Matt Coley\n */\npublic interface ServiceConfig extends ConfigContainer {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/AbstractAssemblerPipeline.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.ast.ASTElement;\nimport me.darknet.assembler.ast.ElementType;\nimport me.darknet.assembler.compiler.ClassRepresentation;\nimport me.darknet.assembler.compiler.ClassResult;\nimport me.darknet.assembler.compiler.Compiler;\nimport me.darknet.assembler.compiler.CompilerOptions;\nimport me.darknet.assembler.compiler.InheritanceChecker;\nimport me.darknet.assembler.error.Error;\nimport me.darknet.assembler.error.Result;\nimport me.darknet.assembler.printer.AnnotationHolder;\nimport me.darknet.assembler.printer.AnnotationPrinter;\nimport me.darknet.assembler.printer.ClassPrinter;\nimport me.darknet.assembler.printer.PrintContext;\nimport me.darknet.assembler.printer.Printer;\nimport software.coley.observables.AbstractObservable;\nimport software.coley.observables.ChangeListener;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.path.AnnotationPathNode;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.List;\n\n/**\n * Common pipeline implementation details for all class types.\n *\n * @param <C>\n * \t\tClass type which will be assembled.\n * @param <R>\n * \t\tCompile return value for JASM {@link Compiler}.\n * @param <I>\n * \t\tClass intermediate representation type.\n *\n * @author Justus Garbe\n */\npublic abstract class AbstractAssemblerPipeline<C extends ClassInfo, R extends ClassResult, I extends ClassRepresentation> implements AssemblerPipeline<C, R, I> {\n\tprotected final AssemblerPipelineConfig pipelineConfig;\n\tprivate final AssemblerPipelineGeneralConfig generalConfig;\n\tprivate final ListenerHost indentListener = new ListenerHost();\n\tprotected PrintContext<?> context;\n\n\tpublic AbstractAssemblerPipeline(@Nonnull AssemblerPipelineGeneralConfig generalConfig,\n\t                                 @Nonnull AssemblerPipelineConfig pipelineConfig) {\n\t\tthis.generalConfig = generalConfig;\n\t\tthis.pipelineConfig = pipelineConfig;\n\n\t\tgeneralConfig.getDisassemblyIndent().addChangeListener(indentListener);\n\t}\n\n\tprivate void refreshContext() {\n\t\tcontext = new PrintContext<>(generalConfig.getDisassemblyIndent().getValue());\n\n\t\t// 10000000000 vs 1E10\n\t\tif (generalConfig.getUseWholeFloatingNumbers().getValue())\n\t\t\tcontext.setForceWholeNumberRepresentation(true);\n\n\t\t// Enable comments that outline where try-catch ranges begin/end.\n\t\tif (pipelineConfig instanceof JvmAssemblerPipelineConfig jvmConfig && jvmConfig.emitTryRangeComments())\n\t\t\tcontext.setDebugTryCatchRanges(true);\n\t}\n\n\t@Nonnull\n\tprotected abstract Result<ClassPrinter> classPrinter(@Nonnull ClassPathNode path);\n\n\t@Nonnull\n\tprotected abstract CompilerOptions<? extends CompilerOptions<?>> getCompilerOptions();\n\n\t@Nonnull\n\tprotected abstract Compiler getCompiler();\n\n\t@Nonnull\n\tprotected abstract InheritanceChecker getInheritanceChecker();\n\n\tprotected abstract int getClassVersion(@Nonnull C info);\n\n\t@Nonnull\n\t@SuppressWarnings(\"unchecked\")\n\tprotected Result<R> compile(@Nonnull List<ASTElement> elements, @Nonnull PathNode<?> path) {\n\t\tif (elements.isEmpty()) {\n\t\t\treturn Result.err(Error.of(\"No elements to compile\", null));\n\t\t}\n\t\tif (elements.size() != 1) {\n\t\t\treturn Result.err(Error.of(\"Multiple elements to compile\", elements.get(1).location()));\n\t\t}\n\t\tASTElement element = elements.get(0);\n\n\t\tif (element == null) {\n\t\t\treturn Result.err(Error.of(\"No element to compile\", null));\n\t\t}\n\n\t\tClassInfo classInfo = path.getValueOfType(ClassInfo.class);\n\t\tif (classInfo == null) {\n\t\t\treturn Result.err(Error.of(\"Dangling member\", null));\n\t\t}\n\n\t\tC info = (C) classInfo;\n\n\t\tCompilerOptions<? extends CompilerOptions<?>> options = getCompilerOptions();\n\t\toptions.version(getClassVersion(info))\n\t\t\t\t.inheritanceChecker(getInheritanceChecker());\n\n\t\tif (element.type() != ElementType.CLASS) {\n\t\t\toptions.overlay(getRepresentation(info));\n\n\t\t\tif (element.type() == ElementType.ANNOTATION) {\n\t\t\t\t// build annotation path\n\t\t\t\tString annoPath = \"this\";\n\t\t\t\tPathNode<?> parent = path.getParent();\n\t\t\t\tif (parent instanceof ClassMemberPathNode memberPathNode) {\n\t\t\t\t\tannoPath += memberPathNode.isMethod() ? \".method.\" : \".field.\";\n\t\t\t\t\tannoPath += memberPathNode.getValue().getName() + \".\";\n\t\t\t\t\tannoPath += memberPathNode.getValue().getDescriptor();\n\t\t\t\t}\n\n\t\t\t\tAnnotated annotated = path.getValueOfType(Annotated.class);\n\n\t\t\t\tif (annotated == null) {\n\t\t\t\t\treturn Result.err(Error.of(\"Dangling annotation\", null));\n\t\t\t\t}\n\n\t\t\t\tAnnotationInfo annotation = (AnnotationInfo) path.getValue();\n\n\t\t\t\tannoPath += annotated.getAnnotations().indexOf(annotation);\n\n\t\t\t\toptions.annotationPath(annoPath);\n\t\t\t}\n\t\t}\n\n\t\tCompiler compiler = getCompiler();\n\n\t\treturn (Result<R>) compiler.compile(elements, options);\n\t}\n\n\t@Nonnull\n\tprotected Result<Printer> memberPrinter(@Nonnull ClassMemberPathNode path) {\n\t\tClassPathNode owner = path.getParent();\n\t\tif (owner == null)\n\t\t\treturn Result.err(Error.of(\"Dangling member\", null));\n\n\t\tClassMember member = path.getValue();\n\t\treturn classPrinter(owner).flatMap((printer) -> {\n\t\t\tPrinter memberPrinter = null;\n\n\t\t\tif (member.isMethod()) {\n\t\t\t\tmemberPrinter = printer.method(member.getName(), member.getDescriptor());\n\t\t\t} else if (member.isField()) {\n\t\t\t\tmemberPrinter = printer.field(member.getName(), member.getDescriptor());\n\t\t\t}\n\n\t\t\tif (memberPrinter == null) {\n\t\t\t\treturn Result.err(Error.of(\"Failed to find member\", null));\n\t\t\t} else {\n\t\t\t\treturn Result.ok(memberPrinter);\n\t\t\t}\n\t\t});\n\t}\n\n\t@Nonnull\n\tprotected Result<AnnotationPrinter> annotationPrinter(@Nonnull AnnotationPathNode path) {\n\t\tif (path.getParent() == null) {\n\t\t\treturn Result.err(Error.of(\"Dangling annotation\", null));\n\t\t}\n\n\t\tObject parent = path.getParent().getValue();\n\t\tResult<? extends Printer> parentPrinter;\n\t\tif (parent instanceof ClassPathNode classNode) {\n\t\t\tparentPrinter = classPrinter(classNode);\n\t\t} else if (parent instanceof ClassMemberPathNode classMember) {\n\t\t\tparentPrinter = memberPrinter(classMember);\n\t\t} else {\n\t\t\treturn Result.err(Error.of(\"Invalid parent type\", null));\n\t\t}\n\n\t\tAnnotationInfo annotation = path.getValue();\n\n\t\tif (parent instanceof Annotated annotated) {\n\n\t\t\treturn parentPrinter.flatMap((printer) -> {\n\t\t\t\tif (printer instanceof AnnotationHolder holder) {\n\t\t\t\t\treturn Result.ok(holder.annotation(annotated.getAnnotations().indexOf(annotation)));\n\t\t\t\t} else {\n\t\t\t\t\treturn Result.err(Error.of(\"Parent is not an annotation holder\", null));\n\t\t\t\t}\n\t\t\t});\n\n\t\t} else {\n\t\t\treturn Result.err(Error.of(\"Parent cannot hold annotations\", null));\n\t\t}\n\t}\n\n\t@Nonnull\n\tprotected String print(@Nonnull Printer printer) {\n\t\trefreshContext(); // new context\n\t\tprinter.print(context);\n\t\treturn context.toString();\n\t}\n\n\t/**\n\t * Called when the associated {@link Workspace} for this pipeline is closed.\n\t */\n\tpublic void close() {\n\t\tgeneralConfig.getDisassemblyIndent().removeChangeListener(indentListener);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic AssemblerPipelineConfig getConfig() {\n\t\treturn pipelineConfig;\n\t}\n\n\tprivate class ListenerHost implements ChangeListener<String> {\n\t\t@Override\n\t\tpublic void changed(AbstractObservable<? extends String> abstractObservable, String s, String t1) {\n\t\t\trefreshContext();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/AndroidAssemblerPipeline.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Dalvik assembler pipeline implementation.\n *\n * @author Matt Coley\n */\npublic class AndroidAssemblerPipeline {\n\tpublic static final String SERVICE_ID = \"dalvik-assembler\";\n\n\tpublic AndroidAssemblerPipeline(@Nonnull AssemblerPipelineGeneralConfig generalConfig,\n\t                                @Nonnull AndroidAssemblerPipelineConfig androidConfig) {\n\t\t// TODO: Implement when dalvik assembler pipeline is implemented\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/AndroidAssemblerPipelineConfig.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link AndroidAssemblerPipeline}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class AndroidAssemblerPipelineConfig extends BasicConfigContainer implements ServiceConfig, AssemblerPipelineConfig {\n\tprivate final ObservableBoolean valueAnalysis = new ObservableBoolean(true);\n\tprivate final ObservableBoolean simulateJvmCalls = new ObservableBoolean(true);\n\n\t@Inject\n\tpublic AndroidAssemblerPipelineConfig() {\n\t\tsuper(ConfigGroups.SERVICE_ASSEMBLER, AndroidAssemblerPipeline.SERVICE_ID + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"value-analysis\", boolean.class, valueAnalysis));\n\t\taddValue(new BasicConfigValue<>(\"simulate-jvm-calls\", boolean.class, simulateJvmCalls));\n\t}\n\n\t@Override\n\tpublic boolean isValueAnalysisEnabled() {\n\t\treturn valueAnalysis.getValue();\n\t}\n\n\t@Override\n\tpublic boolean isSimulatingCommonJvmCalls() {\n\t\treturn simulateJvmCalls.hasValue();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/AssemblerPipeline.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.ast.ASTElement;\nimport me.darknet.assembler.compiler.ClassRepresentation;\nimport me.darknet.assembler.compiler.ClassResult;\nimport me.darknet.assembler.compiler.Compiler;\nimport me.darknet.assembler.error.Error;\nimport me.darknet.assembler.error.Result;\nimport me.darknet.assembler.parser.DeclarationParser;\nimport me.darknet.assembler.parser.ParsingResult;\nimport me.darknet.assembler.parser.Token;\nimport me.darknet.assembler.parser.Tokenizer;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.AnnotationPathNode;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\n\nimport java.util.List;\n\n/**\n * Assembler pipeline outline.\n *\n * @param <C>\n * \t\tClass type which will be assembled.\n * @param <R>\n * \t\tCompile return value for JASM {@link Compiler}.\n * @param <I>\n * \t\tClass intermediate representation type.\n *\n * @author Justus Garbe\n */\npublic interface AssemblerPipeline<C extends ClassInfo, R extends ClassResult, I extends ClassRepresentation> {\n\t/**\n\t * @param input\n\t * \t\tThe text to tokenize.\n\t * @param source\n\t * \t\tIdentifier of where the text originates from.\n\t *\n\t * @return Result wrapping a list of tokens on successful tokenization.\n\t * Result wrapping a list of tokenization errors otherwise.\n\t */\n\t@Nonnull\n\tdefault Result<List<Token>> tokenize(@Nonnull String input, @Nonnull String source) {\n\t\treturn new Tokenizer().tokenize(source, input);\n\t}\n\n\t/**\n\t * Parses only AST declarations. The contents of those declarations are not parsed.\n\t * You will want to pass this result into {@link #concreteParse(List)} to get all\n\t * contents parsed.\n\t * <br>\n\t * Alternatively you can use {@link #fullParse(List)} which does these two steps for you.\n\t *\n\t * @param tokens\n\t * \t\tTokens to parse into a series of AST elements.\n\t *\n\t * @return Result wrapping the partial constructed AST elements on successful parsing.\n\t * Result wrapping a list of parse errors otherwise.\n\t *\n\t * @see #concreteParse(List) Second step to fully parse AST elements.\n\t * @see #fullParse(List) Full parse so that you do not have to do the two-step system yourself.\n\t */\n\t@Nonnull\n\tdefault ParsingResult<List<ASTElement>> roughParse(@Nonnull List<Token> tokens) {\n\t\treturn new DeclarationParser().parseDeclarations(tokens);\n\t}\n\n\t/**\n\t * The second step used after {@link #roughParse(List)}.\n\t *\n\t * @param elements\n\t * \t\tDeclaration elements to complete parsing of.\n\t *\n\t * @return Result wrapping fully constructed AST elements on successful parsing.\n\t * Result wrapping a list of parse errors otherwise.\n\t */\n\t@Nonnull\n\tResult<List<ASTElement>> concreteParse(@Nonnull List<ASTElement> elements);\n\n\t/**\n\t * The full parse operation if you do not want to call both {@link #roughParse(List)} and\n\t * {@link #concreteParse(List)} individually.\n\t *\n\t * @param tokens\n\t * \t\tTokens to parse into a series of AST elements.\n\t *\n\t * @return Result wrapping fully constructed AST elements on successful parsing.\n\t * Result wrapping a list of parse errors otherwise.\n\t */\n\t@Nonnull\n\tdefault Result<List<ASTElement>> fullParse(@Nonnull List<Token> tokens) {\n\t\tvar result = roughParse(tokens);\n\t\tif (result.isOk()) {\n\t\t\treturn concreteParse(result.get());\n\t\t} else {\n\t\t\treturn result;\n\t\t}\n\t}\n\n\t/**\n\t * Takes a list of AST elements, assumed to be fully parsed, and assembles it to the target class type.\n\t *\n\t * @param elements\n\t * \t\tList of AST elements representing a class to construct into a class.\n\t * @param path\n\t * \t\tPath to the expected class destination in the workspace.\n\t *\n\t * @return Result wrapping the assembled class on successful assembling.\n\t * Result wrapping a list of assemble errors otherwise.\n\t */\n\t@Nonnull\n\tResult<R> assemble(@Nonnull List<ASTElement> elements, @Nonnull PathNode<?> path);\n\n\t/**\n\t * Takes a list of AST elements, assumed to be fully parsed, and assembles it to the target class type.\n\t *\n\t * @param elements\n\t * \t\tList of AST elements representing a class to construct into a class.\n\t * @param path\n\t * \t\tPath to the expected class destination in the workspace.\n\t *\n\t * @return Result wrapping the assembled class on successful assembling.\n\t * Result wrapping a list of assemble errors otherwise.\n\t */\n\t@Nonnull\n\tdefault Result<C> assembleAndWrap(@Nonnull List<ASTElement> elements, @Nonnull PathNode<?> path) {\n\t\treturn assemble(elements, path)\n\t\t\t\t.flatMap(r -> Result.ok(getClassInfo((I) r.representation())));\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to class to disassemble.\n\t *\n\t * @return Result wrapping the disassembled class on successful disassembling.\n\t * Result wrapping disassemble errors otherwise.\n\t */\n\t@Nonnull\n\tResult<String> disassemble(@Nonnull ClassPathNode path);\n\n\t/**\n\t * @param path\n\t * \t\tPath to a field or method to disassemble.\n\t *\n\t * @return Result wrapping the disassembled field/method on successful disassembling.\n\t * Result wrapping disassemble errors otherwise.\n\t */\n\t@Nonnull\n\tResult<String> disassemble(@Nonnull ClassMemberPathNode path);\n\n\t/**\n\t * @param path\n\t * \t\tPath to annotation to disassemble.\n\t *\n\t * @return Result wrapping the disassembled annotation on successful disassembling.\n\t * Result wrapping disassemble errors otherwise.\n\t */\n\t@Nonnull\n\tResult<String> disassemble(@Nonnull AnnotationPathNode path);\n\n\t/**\n\t * @param path\n\t * \t\tPath to some item that can be disassembled.\n\t *\n\t * @return Result wrapping the disassembled item on successful disassembling.\n\t * Result wrapping disassemble errors otherwise.\n\t */\n\t@Nonnull\n\tdefault Result<String> disassemble(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof ClassPathNode classPathNode)\n\t\t\treturn disassemble(classPathNode);\n\t\tif (path instanceof ClassMemberPathNode classMemberPathNode)\n\t\t\treturn disassemble(classMemberPathNode);\n\t\tif (path instanceof AnnotationPathNode annotationPathNode)\n\t\t\treturn disassemble(annotationPathNode);\n\t\treturn Result.err(Error.of(\"Unsupported node type: \" + path.getClass().getName(), null));\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass info to convert into the intermediate representation format.\n\t *\n\t * @return IR.\n\t */\n\t@Nonnull\n\tI getRepresentation(@Nonnull C info);\n\n\t/**\n\t * @param representation\n\t * \t\tIntermediate representation format to map into Recaf's class info type.\n\t *\n\t * @return Class info.\n\t */\n\t@Nonnull\n\tC getClassInfo(@Nonnull I representation);\n\n\t/**\n\t * @return Pipeline's specific config.\n\t */\n\t@Nonnull\n\tAssemblerPipelineConfig getConfig();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/AssemblerPipelineConfig.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport software.coley.recaf.config.ConfigContainer;\n\n/**\n * Assembler pipeline config outline.\n *\n * @author Justus Garbe\n * @see AndroidAssemblerPipelineConfig\n * @see JvmAssemblerPipelineConfig\n */\npublic interface AssemblerPipelineConfig extends ConfigContainer {\n\t/**\n\t * @return {@code true} when the assembler's analyzer should use more detailed frame models which include\n\t * values for primitives and strings where possible. {@code false} to only track the expected type of values\n\t * and nothing else.\n\t */\n\tboolean isValueAnalysisEnabled();\n\n\t/**\n\t * Requires {@link #isValueAnalysisEnabled()} be {@code true}.\n\t *\n\t * @return {@code true} to enhance value enabled analysis with the ability to look up values of fields and methods\n\t * of known calls. Usually this is for supplying constants like {@link Integer#MAX_VALUE} and such to the analyzer.\n\t * {@code false} to disable value content simulation for all field and method references.\n\t */\n\tboolean isSimulatingCommonJvmCalls();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/AssemblerPipelineGeneralConfig.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.observables.ObservableString;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Common config for all assemblers.\n *\n * @author Justus Garbe\n */\n@ApplicationScoped\npublic class AssemblerPipelineGeneralConfig extends BasicConfigContainer implements ServiceConfig {\n\tprivate final ObservableString disassemblyIndent = new ObservableString(\"    \");\n\tprivate final ObservableInteger disassemblyAstParseDelay = new ObservableInteger(100);\n\tprivate final ObservableBoolean useWholeFloatingNumbers = new ObservableBoolean(true);\n\n\t@Inject\n\tpublic AssemblerPipelineGeneralConfig() {\n\t\tsuper(ConfigGroups.SERVICE_ASSEMBLER, AssemblerPipelineManager.SERVICE_ID + ConfigGroups.PACKAGE_SPLIT + \"general\" + CONFIG_SUFFIX);\n\n\t\taddValue(new BasicConfigValue<>(\"disassembly-indent\", String.class, disassemblyIndent));\n\t\taddValue(new BasicConfigValue<>(\"disassembly-ast-parse-delay\", int.class, disassemblyAstParseDelay));\n\t\taddValue(new BasicConfigValue<>(\"disassembly-whole-floating\", boolean.class, useWholeFloatingNumbers));\n\t}\n\n\t/**\n\t * @return String of a single indentation level.\n\t */\n\t@Nonnull\n\tpublic ObservableString getDisassemblyIndent() {\n\t\treturn disassemblyIndent;\n\t}\n\n\t/**\n\t * @return Delay between each parse operation.\n\t */\n\t@Nonnull\n\tpublic ObservableInteger getDisassemblyAstParseDelay() {\n\t\treturn disassemblyAstParseDelay;\n\t}\n\n\t/**\n\t * @return {@code true} to prefer {@code 10000000000} over {@link 1e10}.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getUseWholeFloatingNumbers() {\n\t\treturn useWholeFloatingNumbers;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/AssemblerPipelineManager.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport me.darknet.assembler.compiler.ClassRepresentation;\nimport me.darknet.assembler.compiler.ClassResult;\nimport software.coley.recaf.cdi.EagerInitialization;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.workspace.WorkspaceCloseListener;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.services.workspace.WorkspaceOpenListener;\nimport software.coley.recaf.workspace.model.EmptyWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Objects;\n\n/**\n * Assembler implementations manager.\n *\n * @author Justus Garbe\n */\n@EagerInitialization\n@ApplicationScoped\npublic class AssemblerPipelineManager implements Service {\n\tpublic static final String SERVICE_ID = \"assembler-pipeline\";\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final InheritanceGraphService graphService;\n\tprivate final JvmAssemblerPipelineConfig jvmConfig;\n\tprivate final AndroidAssemblerPipelineConfig androidConfig;\n\tprivate final AssemblerPipelineGeneralConfig config;\n\tprivate JvmAssemblerPipeline currentJvmPipeline;\n\n\t@Inject\n\tpublic AssemblerPipelineManager(@Nonnull WorkspaceManager workspaceManager,\n\t                                @Nonnull InheritanceGraphService graphService,\n\t                                @Nonnull AssemblerPipelineGeneralConfig config,\n\t                                @Nonnull AndroidAssemblerPipelineConfig androidConfig,\n\t                                @Nonnull JvmAssemblerPipelineConfig jvmConfig) {\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.graphService = graphService;\n\t\tthis.config = config;\n\t\tthis.jvmConfig = jvmConfig;\n\t\tthis.androidConfig = androidConfig;\n\n\t\tListenerHost host = new ListenerHost();\n\t\tworkspaceManager.addWorkspaceOpenListener(host);\n\t\tworkspaceManager.addWorkspaceCloseListener(host);\n\t}\n\n\t/**\n\t * Automatically pick a pipeline for the content in the given path.\n\t *\n\t * @param path\n\t * \t\tPath to some item in the workspace to get an assembler pipeline for.\n\t *\n\t * @return Either a {@link JvmAssemblerPipeline} or {@link AndroidAssemblerPipeline} based on the path contents.\n\t */\n\t@Nonnull\n\tpublic AssemblerPipeline<? extends ClassInfo, ? extends ClassResult, ? extends ClassRepresentation> getPipeline(@Nonnull PathNode<?> path) {\n\t\tClassInfo info = path.getValueOfType(ClassInfo.class);\n\t\tif (info == null)\n\t\t\tthrow new IllegalStateException(\"Failed to find class info for node: \" + path);\n\t\tif (info.isJvmClass()) {\n\t\t\tWorkspace workspace = Objects.requireNonNullElseGet(path.getValueOfType(Workspace.class), EmptyWorkspace::get);\n\t\t\treturn newJvmAssemblerPipeline(workspace);\n\t\t} else {\n\t\t\t// TODO: Implement when dalvik assembler pipeline is implemented\n\t\t\tthrow new UnsupportedOperationException(\"Dalvik assembler pipeline is not implemented\");\n\t\t}\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull class data from.\n\t *\n\t * @return Assembler pipeline for JVM classes.\n\t */\n\t@Nonnull\n\tpublic JvmAssemblerPipeline newJvmAssemblerPipeline(@Nonnull Workspace workspace) {\n\t\tif (currentJvmPipeline != null && workspace == workspaceManager.getCurrent())\n\t\t\treturn currentJvmPipeline;\n\n\t\tInheritanceGraph graph = graphService.getOrCreateInheritanceGraph(workspace);\n\t\treturn new JvmAssemblerPipeline(workspace, Objects.requireNonNull(graph), config, jvmConfig);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull class data from.\n\t *\n\t * @return Assembler pipeline for Dalvik classes.\n\t */\n\t@Nonnull\n\tpublic AndroidAssemblerPipeline newAndroidAssemblerPipeline(@Nonnull Workspace workspace) {\n\t\treturn new AndroidAssemblerPipeline(config, androidConfig);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic AssemblerPipelineGeneralConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\tprivate class ListenerHost implements WorkspaceOpenListener, WorkspaceCloseListener {\n\t\t@Override\n\t\tpublic void onWorkspaceOpened(@Nonnull Workspace workspace) {\n\t\t\tcurrentJvmPipeline = newJvmAssemblerPipeline(workspace);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onWorkspaceClosed(@Nonnull Workspace workspace) {\n\t\t\tif (currentJvmPipeline != null) {\n\t\t\t\tcurrentJvmPipeline.close();\n\t\t\t\tcurrentJvmPipeline = null;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/ExpressionCompileException.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Exception encompassing problems ocurring during the compilation of an expression all via {@link ExpressionCompiler}.\n *\n * @author Matt Coley\n */\npublic class ExpressionCompileException extends Exception {\n\t/**\n\t * @param message\n\t * \t\tError message.\n\t */\n\tpublic ExpressionCompileException(@Nonnull String message) {\n\t\tsuper(message);\n\t}\n\n\t/**\n\t * @param cause\n\t * \t\tThe cause of the exception.\n\t * @param message\n\t * \t\tError message.\n\t */\n\tpublic ExpressionCompileException(@Nonnull Throwable cause, @Nonnull String message) {\n\t\tsuper(message, cause);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/ExpressionCompiler.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport dev.xdark.blw.type.MethodType;\nimport dev.xdark.blw.type.Types;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport me.darknet.assembler.printer.JvmClassPrinter;\nimport me.darknet.assembler.printer.MethodPrinter;\nimport me.darknet.assembler.printer.PrintContext;\nimport org.objectweb.asm.Opcodes;\nimport org.slf4j.Logger;\nimport regexodus.Pattern;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.compile.CompilerDiagnostic;\nimport software.coley.recaf.services.compile.CompilerResult;\nimport software.coley.recaf.services.compile.JavacArguments;\nimport software.coley.recaf.services.compile.JavacCompiler;\nimport software.coley.recaf.services.compile.stub.ExpressionHostingClassStubGenerator;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.util.JavaVersion;\nimport software.coley.recaf.util.NumberUtil;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * Compiles Java source expressions into JASM.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class ExpressionCompiler {\n\tprivate static final Logger logger = Logging.get(ExpressionCompiler.class);\n\tprivate static final Pattern IMPORT_EXTRACT_PATTERN = RegexUtil.pattern(\"^\\\\s*(import \\\\w.+;)\");\n\tpublic static final String EXPR_MARKER = \"/* EXPR_START */\";\n\tprivate final JavacCompiler javac;\n\tprivate final Workspace workspace;\n\tprivate final AssemblerPipelineGeneralConfig assemblerConfig;\n\tprivate final InheritanceGraph inheritanceGraph;\n\tprivate int classAccess;\n\tprivate String className;\n\tprivate String superName;\n\tprivate List<String> implementing;\n\tprivate int versionTarget;\n\tprivate List<FieldMember> fields;\n\tprivate List<MethodMember> methods;\n\tprivate List<InnerClassInfo> innerClasses;\n\tprivate String methodName;\n\tprivate MethodType methodType;\n\tprivate int methodFlags;\n\tprivate List<LocalVariable> methodVariables;\n\n\t@Inject\n\tpublic ExpressionCompiler(@Nonnull WorkspaceManager workspaceManager,\n\t\t\t\t\t\t\t  @Nonnull InheritanceGraphService inheritanceGraphService,\n\t                          @Nonnull JavacCompiler javac,\n\t                          @Nonnull AssemblerPipelineGeneralConfig assemblerConfig) {\n\t\tthis.workspace = Objects.requireNonNull(workspaceManager.getCurrent(), \"No open workspace\");\n\t\tthis.inheritanceGraph = inheritanceGraphService.getCurrentWorkspaceInheritanceGraph();\n\t\tthis.javac = javac;\n\t\tthis.assemblerConfig = assemblerConfig;\n\t\tclearContext();\n\t}\n\n\t/**\n\t * Resets the assembler to have no class or method context.\n\t */\n\tpublic void clearContext() {\n\t\tclassName = \"RecafExpression\";\n\t\tclassAccess = 0;\n\t\tsuperName = null;\n\t\timplementing = Collections.emptyList();\n\t\tversionTarget = JavaVersion.get();\n\t\tfields = Collections.emptyList();\n\t\tmethods = Collections.emptyList();\n\t\tinnerClasses = Collections.emptyList();\n\t\tmethodName = \"generated\";\n\t\tmethodType = Types.methodType(\"()V\");\n\t\tmethodFlags = Opcodes.ACC_STATIC | Opcodes.ACC_BRIDGE; // Bridge used to denote default state.\n\t\tmethodVariables = Collections.emptyList();\n\t}\n\n\t/**\n\t * Updates the expression compiler to create the expression within the given class.\n\t * This allows access to the class's fields, methods, and type hierarchy.\n\t *\n\t * @param classInfo\n\t * \t\tClass to pull info from.\n\t */\n\tpublic void setClassContext(@Nonnull JvmClassInfo classInfo) {\n\t\tString type = classInfo.getName();\n\t\tString superType = classInfo.getSuperName();\n\t\tclassName = type;\n\t\tclassAccess = classInfo.getAccess();\n\t\tversionTarget = NumberUtil.intClamp(classInfo.getVersion() - JvmClassInfo.BASE_VERSION, JavacCompiler.getMinTargetVersion(), JavaVersion.get());\n\t\tsuperName = classInfo.getSuperName();\n\t\timplementing = classInfo.getInterfaces();\n\t\tfields = classInfo.getFields();\n\t\tmethods = classInfo.getMethods();\n\t\tinnerClasses = classInfo.getInnerClasses();\n\n\t\t// We use bridge to denote that the default flags are set.\n\t\t// When we assign a class, there may be non-static fields/methods the user will want to interact with.\n\t\t// Thus, we should clear our flags from the default so that they can do that.\n\t\tif (AccessFlag.isBridge(methodFlags))\n\t\t\tmethodFlags = 0;\n\n\t\t// TODO: Support for generics (For example, if we implement Supplier<String> and we have a method \"String get()\")\n\t\t//  - Also will want per-method signatures for things like 'List<String> strings' as a parameter\n\t\t//  - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.9.1\n\t}\n\n\t/**\n\t * Updates the expression compiler to create the expression within the given method.\n\t * This allows access to the method's parameters.\n\t *\n\t * @param method\n\t * \t\tMethod to pull info from.\n\t */\n\tpublic void setMethodContext(@Nonnull MethodMember method) {\n\t\tmethodName = method.getName();\n\t\tmethodType = Types.methodType(method.getDescriptor());\n\t\tmethodFlags = method.getAccess();\n\t\tmethodVariables = method.getLocalVariables();\n\t}\n\n\t/**\n\t * Set the target version of Java.\n\t * <br>\n\t * Java 8 would pass 8.\n\t *\n\t * @param versionTarget\n\t * \t\tJava version target.\n\t * \t\tRange of supported values: [{@link JavacCompiler#getMinTargetVersion()} - {@link JavaVersion#get()}]\n\t */\n\tpublic void setVersionTarget(int versionTarget) {\n\t\tthis.versionTarget = versionTarget;\n\t}\n\n\t/**\n\t * Compiles the given expression with the current context.\n\t *\n\t * @param expression\n\t * \t\tExpression to compile.\n\t *\n\t * @return Expression compilation result.\n\t *\n\t * @see #setClassContext(JvmClassInfo) For allowing access to a class's fields/methods/inheritance.\n\t * @see #setMethodContext(MethodMember) For allowing access to a method's parameters & other local variables.\n\t */\n\t@Nonnull\n\tpublic ExpressionResult compile(@Nonnull String expression) {\n\t\t// Generate source of a class to house the expression within\n\t\tExpressionHostingClassStubGenerator stubber;\n\t\tString code;\n\t\ttry {\n\t\t\tstubber = new ExpressionHostingClassStubGenerator(workspace, inheritanceGraph, classAccess, className, superName, implementing,\n\t\t\t\t\tfields, methods, innerClasses, methodFlags, methodName, methodType, methodVariables, expression);\n\t\t\tcode = stubber.generate();\n\t\t} catch (ExpressionCompileException ex) {\n\t\t\treturn new ExpressionResult(ex);\n\t\t}\n\n\t\t// Compile the generated class\n\t\tJavacArguments arguments = new JavacArguments(className, code, null, Math.max(versionTarget, JavacCompiler.getMinTargetVersion()), -1, true, false, false);\n\t\tCompilerResult result = javac.compile(arguments, workspace, null);\n\t\tif (!result.wasSuccess()) {\n\t\t\tThrowable exception = result.getException();\n\t\t\tif (exception != null)\n\t\t\t\treturn new ExpressionResult(new ExpressionCompileException(exception, \"Compilation task encountered an error\"));\n\t\t\tList<CompilerDiagnostic> diagnostics = result.getDiagnostics();\n\t\t\tif (!diagnostics.isEmpty())\n\t\t\t\treturn new ExpressionResult(remap(code, diagnostics));\n\t\t}\n\t\tbyte[] klass = result.getCompilations().get(className);\n\t\tif (klass == null)\n\t\t\treturn new ExpressionResult(new ExpressionCompileException(\"Compilation results missing the generated expression class\"));\n\n\t\t// Convert the compiled class to JASM\n\t\ttry {\n\t\t\tPrintContext<?> context = new PrintContext<>(assemblerConfig.getDisassemblyIndent().getValue());\n\t\t\tcontext.setLabelPrefix(\"g\");\n\t\t\tJvmClassPrinter printer = new JvmClassPrinter(new ByteArrayInputStream(klass));\n\t\t\tMethodPrinter method = printer.method(stubber.getAdaptedMethodName(), stubber.methodDescriptorWithVariables());\n\t\t\tif (method == null)\n\t\t\t\treturn new ExpressionResult(new ExpressionCompileException(\"Target method was not in generated class\"));\n\t\t\tmethod.print(context);\n\t\t\treturn new ExpressionResult(context.toString());\n\t\t} catch (IOException ex) {\n\t\t\treturn new ExpressionResult(new ExpressionCompileException(ex, \"Failed to print generated class\"));\n\t\t} catch (ExpressionCompileException ex) {\n\t\t\treturn new ExpressionResult(ex);\n\t\t}\n\t}\n\n\t/**\n\t * @param code\n\t * \t\tGenerateed code to work with.\n\t * @param diagnostics\n\t * \t\tCompiler diagnostics affecting the given code.\n\t *\n\t * @return Diagnostics mapped to the original expression lines, rather than the lines in the full generated code.\n\t */\n\t@Nonnull\n\tprivate static List<CompilerDiagnostic> remap(@Nonnull String code, @Nonnull List<CompilerDiagnostic> diagnostics) {\n\t\t// Given the following example code:\n\t\t//\n\t\t// 1:  package foo;\n\t\t// 2:  class Foo extends Bar {\n\t\t// 3:  void method() { /* EXPR_START */\n\t\t// 4:    // Code here\n\t\t//\n\t\t// The expression marker is on line 3, and our code starts on line four. So the reported line numbers need to\n\t\t// be shifted down by three. There are two line breaks between the start and the marker, so we add plus one\n\t\t// to consider the line the marker is itself on.\n\t\tint exprStart = code.indexOf(EXPR_MARKER);\n\t\tint lineOffset = StringUtil.count('\\n', code.substring(0, exprStart)) + 1;\n\t\treturn diagnostics.stream()\n\t\t\t\t.map(d -> d.withLine(d.line() - lineOffset))\n\t\t\t\t.toList();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/ExpressionResult.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.services.compile.CompilerDiagnostic;\n\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Result of an expression compilation attempt.\n *\n * @author Matt Coley\n */\npublic class ExpressionResult {\n\tprivate final String assembly;\n\tprivate final List<CompilerDiagnostic> diagnostics;\n\tprivate final ExpressionCompileException exception;\n\n\t/**\n\t * @param assembly\n\t * \t\tOutput JASM from the input expression.\n\t */\n\tpublic ExpressionResult(@Nonnull String assembly) {\n\t\tthis.assembly = assembly;\n\t\tthis.diagnostics = Collections.emptyList();\n\t\tthis.exception = null;\n\t}\n\n\t/**\n\t * @param diagnostics\n\t * \t\tCompiler warnings and errors from the input compilation attempt.\n\t */\n\tpublic ExpressionResult(@Nonnull List<CompilerDiagnostic> diagnostics) {\n\t\tthis.assembly = null;\n\t\tthis.diagnostics = diagnostics;\n\t\tthis.exception = null;\n\t}\n\n\t/**\n\t * @param exception\n\t * \t\tException thrown during the code-building or compilation process.\n\t */\n\tpublic ExpressionResult(@Nonnull ExpressionCompileException exception) {\n\t\tthis.assembly = null;\n\t\tthis.diagnostics = Collections.emptyList();\n\t\tthis.exception = exception;\n\t}\n\n\t/**\n\t * @return The assembled expression.\n\t */\n\t@Nullable\n\tpublic String getAssembly() {\n\t\treturn assembly;\n\t}\n\n\t/**\n\t * @return Exception encountered when compiling the expression.\n\t */\n\t@Nullable\n\tpublic ExpressionCompileException getException() {\n\t\treturn exception;\n\t}\n\n\t/**\n\t * @return List of compiler errors encountered when compiling the expression.\n\t */\n\t@Nonnull\n\tpublic List<CompilerDiagnostic> getDiagnostics() {\n\t\treturn diagnostics;\n\t}\n\n\t/**\n\t * @return {@code true} when the expression was compiled and a {@link #getAssembly() method AST} was created.\n\t */\n\tpublic boolean wasSuccess() {\n\t\treturn assembly != null;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/JvmAssemblerPipeline.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.ast.ASTElement;\nimport me.darknet.assembler.compile.JavaClassRepresentation;\nimport me.darknet.assembler.compile.JvmCompiler;\nimport me.darknet.assembler.compile.JvmCompilerOptions;\nimport me.darknet.assembler.compile.analysis.BasicFieldValueLookup;\nimport me.darknet.assembler.compile.analysis.BasicMethodValueLookup;\nimport me.darknet.assembler.compile.analysis.jvm.ValuedJvmAnalysisEngine;\nimport me.darknet.assembler.compile.visitor.JavaCompileResult;\nimport me.darknet.assembler.compiler.Compiler;\nimport me.darknet.assembler.compiler.CompilerOptions;\nimport me.darknet.assembler.compiler.InheritanceChecker;\nimport me.darknet.assembler.error.Result;\nimport me.darknet.assembler.parser.BytecodeFormat;\nimport me.darknet.assembler.parser.processor.ASTProcessor;\nimport me.darknet.assembler.printer.ClassPrinter;\nimport me.darknet.assembler.printer.JvmClassPrinter;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.path.AnnotationPathNode;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.util.JavaVersion;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.ByteArrayInputStream;\nimport java.util.List;\n\n/**\n * JVM assembler pipeline implementation.\n *\n * @author Justus Garbe\n */\npublic class JvmAssemblerPipeline extends AbstractAssemblerPipeline<JvmClassInfo, JavaCompileResult, JavaClassRepresentation> {\n\tpublic static final String SERVICE_ID = \"jvm-assembler\";\n\tprivate static final Logger logger = Logging.get(JvmAssemblerPipeline.class);\n\tprivate final ASTProcessor processor = new ASTProcessor(BytecodeFormat.JVM);\n\tprivate final InheritanceGraph inheritanceGraph;\n\tprivate final Workspace workspace;\n\n\tpublic JvmAssemblerPipeline(@Nonnull Workspace workspace,\n\t                            @Nonnull InheritanceGraph inheritanceGraph,\n\t                            @Nonnull AssemblerPipelineGeneralConfig generalConfig,\n\t                            @Nonnull JvmAssemblerPipelineConfig jvmConfig) {\n\t\tsuper(generalConfig, jvmConfig);\n\t\tthis.workspace = workspace;\n\t\tthis.inheritanceGraph = inheritanceGraph;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Result<List<ASTElement>> concreteParse(@Nonnull List<ASTElement> elements) {\n\t\treturn processor.processAST(elements);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Result<JavaCompileResult> assemble(@Nonnull List<ASTElement> elements, @Nonnull PathNode<?> path) {\n\t\treturn compile(elements, path);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Result<String> disassemble(@Nonnull ClassPathNode path) {\n\t\treturn classPrinter(path).map(this::print);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Result<String> disassemble(@Nonnull ClassMemberPathNode path) {\n\t\treturn memberPrinter(path).map(this::print);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Result<String> disassemble(@Nonnull AnnotationPathNode path) {\n\t\treturn annotationPrinter(path).map(this::print);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic JavaClassRepresentation getRepresentation(@Nonnull JvmClassInfo info) {\n\t\treturn new JavaClassRepresentation(info.getBytecode());\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected CompilerOptions<? extends CompilerOptions<?>> getCompilerOptions() {\n\t\tJvmCompilerOptions options = new JvmCompilerOptions();\n\t\tif (pipelineConfig.isValueAnalysisEnabled())\n\t\t\toptions.engineProvider(vars -> {\n\t\t\t\tValuedJvmAnalysisEngine engine = new ValuedJvmAnalysisEngine(vars);\n\t\t\t\tif (pipelineConfig.isSimulatingCommonJvmCalls()) {\n\t\t\t\t\tengine.setFieldValueLookup(new WorkspaceFieldValueLookup(workspace, new BasicFieldValueLookup()));\n\t\t\t\t\tengine.setMethodValueLookup(new BasicMethodValueLookup());\n\t\t\t\t}\n\t\t\t\treturn engine;\n\t\t\t});\n\t\treturn options;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected Compiler getCompiler() {\n\t\treturn new JvmCompiler();\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected InheritanceChecker getInheritanceChecker() {\n\t\treturn new InheritanceChecker() {\n\t\t\t@Override\n\t\t\tpublic boolean isSubclassOf(String child, String parent) {\n\t\t\t\treturn inheritanceGraph.isAssignableFrom(parent, child);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic String getCommonSuperclass(String type1, String type2) {\n\t\t\t\treturn inheritanceGraph.getCommon(type1, type2);\n\t\t\t}\n\t\t};\n\t}\n\n\t@Override\n\tprotected int getClassVersion(@Nonnull JvmClassInfo info) {\n\t\treturn info.getVersion() - JavaVersion.VERSION_OFFSET;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic JvmClassInfo getClassInfo(@Nonnull JavaClassRepresentation representation) {\n\t\treturn new JvmClassInfoBuilder(representation.classFile()).build();\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected Result<ClassPrinter> classPrinter(@Nonnull ClassPathNode path) {\n\t\tClassInfo classInfo = path.getValue();\n\t\ttry {\n\t\t\treturn Result.ok(new JvmClassPrinter(new ByteArrayInputStream(classInfo.asJvmClass().getBytecode())));\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Uncaught error creating class printer for: {}\", classInfo.getName(), t);\n\t\t\treturn Result.exception(t);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/JvmAssemblerPipelineConfig.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link JvmAssemblerPipeline}.\n *\n * @author Justus Garbe\n */\n@ApplicationScoped\npublic class JvmAssemblerPipelineConfig extends BasicConfigContainer implements ServiceConfig, AssemblerPipelineConfig {\n\tprivate final ObservableBoolean valueAnalysis = new ObservableBoolean(true);\n\tprivate final ObservableBoolean simulateJvmCalls = new ObservableBoolean(true);\n\tprivate final ObservableBoolean tryRangeComments = new ObservableBoolean(true);\n\n\n\t@Inject\n\tpublic JvmAssemblerPipelineConfig() {\n\t\tsuper(ConfigGroups.SERVICE_ASSEMBLER, JvmAssemblerPipeline.SERVICE_ID + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"value-analysis\", boolean.class, valueAnalysis));\n\t\taddValue(new BasicConfigValue<>(\"simulate-jvm-calls\", boolean.class, simulateJvmCalls));\n\t\taddValue(new BasicConfigValue<>(\"try-range-comments\", boolean.class, tryRangeComments));\n\t}\n\n\t@Override\n\tpublic boolean isValueAnalysisEnabled() {\n\t\treturn valueAnalysis.getValue();\n\t}\n\n\t@Override\n\tpublic boolean isSimulatingCommonJvmCalls() {\n\t\treturn simulateJvmCalls.getValue();\n\t}\n\n\t/**\n\t * @return {@code true} to emit comments for where try-catch ranges start/end/catch.\n\t */\n\tpublic boolean emitTryRangeComments() {\n\t\treturn tryRangeComments.getValue();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/Snippet.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator;\n\nimport java.util.Comparator;\n\n/**\n * Outline of a named snippet of code for {@link SnippetManager}.\n *\n * @param name\n * \t\tSnippet name / title.\n * @param description\n * \t\tDescription of the snippets content.\n * @param content\n * \t\tSnippet content.\n *\n * @author Matt Coley\n */\npublic record Snippet(@Nonnull String name, @Nonnull String description, @Nonnull String content) {\n\t/**\n\t * Shared comparator for snippets by name.\n\t */\n\tpublic static final Comparator<Snippet> NAME_COMPARATOR = (a, b) -> CaseInsensitiveSimpleNaturalComparator.getInstance().compare(a.name(), b.name());\n\n\t/**\n\t * @param newContent\n\t * \t\tNew content.\n\t *\n\t * @return A copy of this snippet with different content specified.\n\t */\n\t@Nonnull\n\tpublic Snippet withContent(@Nonnull String newContent) {\n\t\treturn new Snippet(name(), description(), newContent);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/SnippetListener.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\n\n/**\n * Listener for receiving updates when {@link Snippet} entries are added/updated/removed from {@link SnippetManager}.\n *\n * @author Matt Coley\n */\npublic interface SnippetListener extends PrioritySortable {\n\t/**\n\t * @param snippet\n\t * \t\tNewly added snippet.\n\t */\n\tdefault void onSnippetAdded(@Nonnull Snippet snippet) {}\n\n\t/**\n\t * @param old\n\t * \t\tOld snippet instance.\n\t * @param current\n\t * \t\tNew snippet instance.\n\t */\n\tdefault void onSnippetModified(@Nonnull Snippet old, @Nonnull Snippet current) {}\n\n\t/**\n\t * @param snippet\n\t * \t\tRemoved snippet.\n\t */\n\tdefault void onSnippetRemoved(@Nonnull Snippet snippet) {}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/SnippetManager.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.services.Service;\n\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * Simple code snippet manager to hold common assembler samples for common operations.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class SnippetManager implements Service {\n\tprivate static final Logger logger = Logging.get(SnippetManager.class);\n\n\tpublic static final String SERVICE_ID = \"snippets\";\n\tprivate final SnippetManagerConfig config;\n\tprivate final List<SnippetListener> listeners = new CopyOnWriteArrayList<>();\n\n\t@Inject\n\tpublic SnippetManager(@Nonnull SnippetManagerConfig config) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * @return List of recorded snippets.\n\t */\n\t@Nonnull\n\tpublic List<Snippet> getSnippets() {\n\t\treturn config.getSnippets().values().stream()\n\t\t\t\t.sorted(Snippet.NAME_COMPARATOR)\n\t\t\t\t.toList();\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName of snippet to look up.\n\t *\n\t * @return Snippet by the given name, if one exists.\n\t */\n\t@Nullable\n\tpublic Snippet getByName(@Nonnull String name) {\n\t\treturn config.getSnippets().get(name);\n\t}\n\n\t/**\n\t * Register or update a snippet.\n\t *\n\t * @param snippet\n\t * \t\tSnippet to register.\n\t * \t\tIf an existing snippet with the same {@link Snippet#name()} exists, it will be replaced.\n\t */\n\tpublic void putSnippet(@Nonnull Snippet snippet) {\n\t\tString name = snippet.name();\n\t\tSnippet old = config.getSnippets().put(name, snippet);\n\t\tif (snippet.equals(old)) return;\n\t\tif (old == null)\n\t\t\tUnchecked.checkedForEach(listeners, listener -> listener.onSnippetAdded(snippet),\n\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when registering snippet '{}'\", name, t));\n\t\telse\n\t\t\tUnchecked.checkedForEach(listeners, listener -> listener.onSnippetModified(old, snippet),\n\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when updating snippet '{}'\", name, t));\n\t}\n\n\t/**\n\t * Remove a given snippet.\n\t *\n\t * @param snippet\n\t * \t\tSnippet to remove.\n\t */\n\tpublic void removeSnippet(@Nonnull Snippet snippet) {\n\t\tremoveSnippet(snippet.name());\n\t}\n\n\t/**\n\t * Remove a given snippet by name.\n\t *\n\t * @param name\n\t * \t\tSnippet name / identifier.\n\t */\n\tpublic void removeSnippet(@Nonnull String name) {\n\t\tSnippet removed = config.getSnippets().remove(name);\n\t\tif (removed != null)\n\t\t\tUnchecked.checkedForEach(listeners, listener -> listener.onSnippetRemoved(removed),\n\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when removing snippet '{}'\", name, t));\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tpublic void addSnippetListener(@Nonnull SnippetListener listener) {\n\t\tPrioritySortable.add(listeners, listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tpublic void removeSnippetListener(@Nonnull SnippetListener listener) {\n\t\tlisteners.remove(listener);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic SnippetManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/SnippetManagerConfig.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableMap;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicMapConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.config.RestoreAwareConfigContainer;\nimport software.coley.recaf.services.ServiceConfig;\nimport software.coley.recaf.services.json.GsonProvider;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Config for {@link SnippetManager}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class SnippetManagerConfig extends BasicConfigContainer implements ServiceConfig, RestoreAwareConfigContainer {\n\tprivate final SnippetMap snippets = new SnippetMap();\n\n\t@Inject\n\tpublic SnippetManagerConfig(@Nonnull GsonProvider gsonProvider) {\n\t\tsuper(ConfigGroups.SERVICE_UI, SnippetManager.SERVICE_ID + CONFIG_SUFFIX);\n\t\taddValue(new BasicMapConfigValue<>(\"snippet-map\", SnippetMap.class, String.class, Snippet.class, snippets, true));\n\t}\n\n\t@Override\n\tpublic void onNoRestore() {\n\t\tsnippets.put(\"println\", new Snippet(\"println\", \"A simple single-string println() call\", \"\"\"\n\t\t\t\t// System.out.println(\"Hello\");\n\t\t\t\tgetstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\tldc \"Hello\"\n\t\t\t\tinvokevirtual java/io/PrintStream.println (Ljava/lang/String;)V\n\t\t\t\t\"\"\"));\n\t\tsnippets.put(\"println-fmt\", new Snippet(\"println formatted\", \"A formatted string println() call\", \"\"\"\n\t\t\t\t// String name = \"bob\";\n\t\t\t\t// System.out.printf(\"hello %s\\\\n\", name);\n\t\t\t\tstart:\n\t\t\t\t    ldc \"bob\"\n\t\t\t\t    astore name\n\t\t\t\tprintf:\n\t\t\t\t    getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t    ldc \"hello %s\\\\u000A\"\n\t\t\t\t    iconst_1\n\t\t\t\t    anewarray Ljava/lang/Object;\n\t\t\t\t    dup\n\t\t\t\t    iconst_0\n\t\t\t\t    aload name\n\t\t\t\t    aastore\n\t\t\t\t    invokevirtual java/io/PrintStream.printf (Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;\n\t\t\t\t    pop\n\t\t\t\tend:\n\t\t\t\t\"\"\"));\n\t\tsnippets.put(\"fori\", new Snippet(\"for-i\", \"for-loop between [0, 9]\", \"\"\"\n\t\t\t\t// for (int i = 0; i < 10; i++) someMethod();\n\t\t\t\tinit:\n\t\t\t\t    // int i = 0\n\t\t\t\t    iconst_0\n\t\t\t\t    istore i\n\t\t\t\tcheck:\n\t\t\t\t    // if (i >= 10) break\n\t\t\t\t    iload i\n\t\t\t\t    bipush 10\n\t\t\t\t    if_icmpge exit\n\t\t\t\tcontents:\n\t\t\t\t    // Inside the for-loop\n\t\t\t\t    invokestatic com/example/MyClass.someMethod ()V\n\t\t\t\tinc:\n\t\t\t\t    // i++ and continue; portion of for-loop\n\t\t\t\t    iinc i 1\n\t\t\t\t    goto check\n\t\t\t\texit:\n\t\t\t\t// End of for-loop\n\t\t\t\t\"\"\"));\n\t\tsnippets.put(\"while\", new Snippet(\"while\", \"A simple while loop\", \"\"\"\n\t\t\t\t// int i = 100;\n\t\t\t\t// while (i >= 0) {\n\t\t\t\t//   someMethod();\n\t\t\t\t//   i--;\n\t\t\t\t// }\n\t\t\t\tinit:\n\t\t\t\t    // int i = 100;\n\t\t\t\t    bipush 100\n\t\t\t\t    istore i\n\t\t\t\tcheck:\n\t\t\t\t    // if (i < 0) break;\n\t\t\t\t    iload i\n\t\t\t\t    iflt exit\n\t\t\t\tcontents:\n\t\t\t\t    // Inside the while loop\n\t\t\t\t    invokestatic com/example/MyClass.someMethod ()V\n\t\t\t\t    iinc i -1\n\t\t\t\t    goto check\n\t\t\t\texit:\n\t\t\t\t// End of while-loop\n\t\t\t\t\"\"\"));\n\t\tsnippets.put(\"if-else\", new Snippet(\"if ... else ...\", \"A simple if else statement\", \"\"\"\n\t\t\t\t// if (b)\n\t\t\t\t//   whenTrue();\n\t\t\t\t// else\n\t\t\t\t//   whenFalse();\n\t\t\t\tstart:\n\t\t\t\t    iload someBoolean\n\t\t\t\t    ifeq isFalse\n\t\t\t\t    invokestatic com/example/MyClass.whenTrue ()V\n\t\t\t\t    goto end\n\t\t\t\tisFalse:\n\t\t\t\t    invokestatic com/example/MyClass.whenFalse ()V\n\t\t\t\tend:\n\t\t\t\t\"\"\"));\n\t}\n\n\t/**\n\t * @return Map of recorded snippets.\n\t */\n\t@Nonnull\n\tpublic SnippetMap getSnippets() {\n\t\treturn snippets;\n\t}\n\n\t/**\n\t * Map type to hold snippets.\n\t */\n\tpublic static class SnippetMap extends ObservableMap<String, Snippet, Map<String, Snippet>> {\n\t\tpublic SnippetMap() {\n\t\t\tsuper(HashMap::new);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/assembler/WorkspaceFieldValueLookup.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport dev.xdark.blw.code.instruction.FieldInstruction;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.compile.analysis.Value;\nimport me.darknet.assembler.compile.analysis.Values;\nimport me.darknet.assembler.compile.analysis.jvm.FieldValueLookup;\nimport org.objectweb.asm.Opcodes;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Field value lookup for items in a {@link Workspace}.\n * <p>\n * Its very basic, only looking at static fields default values.\n *\n * @author Matt Coley\n */\npublic class WorkspaceFieldValueLookup implements FieldValueLookup {\n\tprivate final Workspace workspace;\n\tprivate final FieldValueLookup parent;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull values from.\n\t * @param parent\n\t * \t\tParent lookup if no such value could be found in the workspace.\n\t */\n\tpublic WorkspaceFieldValueLookup(@Nonnull Workspace workspace, @Nullable FieldValueLookup parent) {\n\t\tthis.workspace = workspace;\n\t\tthis.parent = parent;\n\t}\n\n\t@Override\n\t@Nullable\n\tpublic Value accept(@Nonnull FieldInstruction fieldRef, @Nullable Value.ObjectValue context) {\n\t\tif (fieldRef.opcode() == Opcodes.GETSTATIC) {\n\t\t\tClassPathNode owner = workspace.findClass(fieldRef.owner().internalName());\n\t\t\tif (owner != null) {\n\t\t\t\tFieldMember field = owner.getValue().getDeclaredField(fieldRef.name(), fieldRef.type().descriptor());\n\t\t\t\tif (field != null) {\n\t\t\t\t\tObject value = field.getDefaultValue();\n\t\t\t\t\tif (value instanceof Integer v)\n\t\t\t\t\t\treturn Values.valueOf(v);\n\t\t\t\t\tif (value instanceof Long v)\n\t\t\t\t\t\treturn Values.valueOf(v);\n\t\t\t\t\tif (value instanceof Float v)\n\t\t\t\t\t\treturn Values.valueOf(v);\n\t\t\t\t\tif (value instanceof Double v)\n\t\t\t\t\t\treturn Values.valueOf(v);\n\t\t\t\t\tif (value instanceof String v)\n\t\t\t\t\t\treturn Values.valueOfString(v);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (parent == null)\n\t\t\treturn null;\n\t\treturn parent.accept(fieldRef, context);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/attach/AttachManager.java",
    "content": "package software.coley.recaf.services.attach;\n\nimport com.sun.tools.attach.VirtualMachine;\nimport com.sun.tools.attach.VirtualMachineDescriptor;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.collections.observable.ObservableList;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.workspace.model.resource.WorkspaceRemoteVmResource;\n\nimport java.io.IOException;\nimport java.util.Properties;\n\n/**\n * Outline for attach service.\n *\n * @author Matt Coley\n */\npublic interface AttachManager extends Service {\n\tString SERVICE_ID = \"attach\";\n\n\t/**\n\t * @return {@code true} when attach is supported.\n\t * Typically only {@code false} when the agent fails to extract onto the local file system.\n\t */\n\tboolean canAttach();\n\n\t/**\n\t * Refresh available remote JVMs.\n\t */\n\tvoid scan();\n\n\t/**\n\t * Create a {@link WorkspaceRemoteVmResource} for the given VM.\n\t * Users must call {@link WorkspaceRemoteVmResource#connect()} to <i>'enable'</i> the resource.\n\t *\n\t * @param item\n\t * \t\tVM descriptor for VM to connect to.\n\t *\n\t * @return Agent client resource, not yet connected.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the remote resource couldn't be created.\n\t * \t\tCausing exceptions depend on implementation.\n\t */\n\t@Nonnull\n\tWorkspaceRemoteVmResource createRemoteResource(VirtualMachineDescriptor item) throws IOException;\n\n\t/**\n\t * @param descriptor\n\t * \t\tLookup descriptor.\n\t *\n\t * @return Remote VM, if known. Otherwise {@code null}.\n\t */\n\t@Nullable\n\tVirtualMachine getVirtualMachine(@Nonnull VirtualMachineDescriptor descriptor);\n\n\t/**\n\t * @param descriptor\n\t * \t\tLookup descriptor.\n\t *\n\t * @return Exception when attempting to connect to remote VM, if there was one. Otherwise {@code null}.\n\t */\n\t@Nullable\n\tException getVirtualMachineConnectionFailure(@Nonnull VirtualMachineDescriptor descriptor);\n\n\t/**\n\t * @param descriptor\n\t * \t\tLookup descriptor.\n\t *\n\t * @return Remote VM PID, or {@code -1} if no PID is known for the remote VM.\n\t */\n\tint getVirtualMachinePid(@Nonnull VirtualMachineDescriptor descriptor);\n\n\t/**\n\t * @param descriptor\n\t * \t\tLookup descriptor.\n\t *\n\t * @return Remote VM {@link System#getProperties()} if known, otherwise {@code null}.\n\t */\n\t@Nullable\n\tProperties getVirtualMachineProperties(@Nonnull VirtualMachineDescriptor descriptor);\n\n\t/**\n\t * @param descriptor\n\t * \t\tLookup descriptor.\n\t *\n\t * @return Remote main class of VM.\n\t */\n\t@Nullable\n\tString getVirtualMachineMainClass(@Nonnull VirtualMachineDescriptor descriptor);\n\n\t/**\n\t * @param descriptor\n\t * \t\tLookup descriptor.\n\t *\n\t * @return JMX bean server connection to remote VM.\n\t */\n\t@Nullable\n\tJmxBeanServerConnection getJmxServerConnection(@Nonnull VirtualMachineDescriptor descriptor);\n\n\t/**\n\t * @return Observable list of virtual machine descriptors.\n\t */\n\t@Nonnull\n\tObservableList<VirtualMachineDescriptor> getVirtualMachineDescriptors();\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tvoid addPostScanListener(@Nonnull PostScanListener listener);\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tvoid removePostScanListener(@Nonnull PostScanListener listener);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/attach/AttachManagerConfig.java",
    "content": "package software.coley.recaf.services.attach;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\nimport software.coley.recaf.services.file.RecafDirectoriesConfig;\n\nimport javax.management.MBeanServerConnection;\nimport java.nio.file.Path;\n\n/**\n * Config for {@link AttachManager}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class AttachManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\tprivate final RecafDirectoriesConfig directories;\n\tprivate final ObservableBoolean passiveScanning = new ObservableBoolean(false);\n\tprivate final ObservableBoolean attachJmxAgent = new ObservableBoolean(true);\n\n\t@Inject\n\tpublic AttachManagerConfig(@Nonnull RecafDirectoriesConfig directories) {\n\t\tsuper(ConfigGroups.SERVICE_DEBUG, AttachManager.SERVICE_ID + CONFIG_SUFFIX);\n\t\tthis.directories = directories;\n\n\t\t// Add values\n\t\t//  - The 'passiveScanning' field is *intentionally* not registered as a value.\n\t\taddValue(new BasicConfigValue<>(\"attach-jmx-bean-agent\", boolean.class, attachJmxAgent));\n\t}\n\n\t/**\n\t * @return Mirror of {@link RecafDirectoriesConfig#getAgentDirectory()}.\n\t */\n\tpublic Path getAgentDirectory() {\n\t\treturn directories.getAgentDirectory();\n\t}\n\n\t/**\n\t * @return {@code true} to enable passive scanning in the {@link AttachManager}.\n\t */\n\tpublic ObservableBoolean getPassiveScanning() {\n\t\treturn passiveScanning;\n\t}\n\n\t/**\n\t * @return {@code true} to enable attaching the JMX agent to discovered servers,\n\t * allowing usage of {@link MBeanServerConnection}.\n\t */\n\tpublic ObservableBoolean getAttachJmxAgent() {\n\t\treturn attachJmxAgent;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/attach/BasicAttachManager.java",
    "content": "package software.coley.recaf.services.attach;\n\nimport com.sun.tools.attach.*;\nimport com.sun.tools.attach.spi.AttachProvider;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.annotation.PreDestroy;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.collections.observable.ObservableList;\nimport software.coley.instrument.BuildConfig;\nimport software.coley.instrument.Client;\nimport software.coley.instrument.Extractor;\nimport software.coley.instrument.io.ByteBufferAllocator;\nimport software.coley.instrument.message.MessageFactory;\nimport software.coley.instrument.sock.SocketAvailability;\nimport software.coley.instrument.util.Discovery;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.util.DevDetection;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.threading.ThreadUtil;\nimport software.coley.recaf.workspace.model.resource.AgentServerRemoteVmResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceRemoteVmResource;\n\nimport javax.management.MBeanServerConnection;\nimport javax.management.remote.JMXConnector;\nimport javax.management.remote.JMXConnectorFactory;\nimport javax.management.remote.JMXServiceURL;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.InvalidPathException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.concurrent.*;\nimport java.util.jar.JarFile;\n\n/**\n * Manager for handling instrumentation of remote JVMs.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicAttachManager implements AttachManager {\n\tprivate static final Logger logger = Logging.get(BasicAttachManager.class);\n\tprivate static final long currentPid = ProcessHandle.current().pid();\n\tprivate static final String JMX_AGENT_ADDRESS = \"com.sun.management.jmxremote.localConnectorAddress\";\n\tprivate static ExtractState extractState = ExtractState.DEFAULT;\n\n\tprivate final DescriptorComparator descriptorComparator = new DescriptorComparator();\n\tprivate final Map<VirtualMachineDescriptor, VirtualMachine> virtualMachineMap = new ConcurrentHashMap<>();\n\tprivate final Map<VirtualMachineDescriptor, Exception> virtualMachineFailureMap = new ConcurrentHashMap<>();\n\tprivate final Map<VirtualMachineDescriptor, Integer> virtualMachinePidMap = new ConcurrentHashMap<>();\n\tprivate final Map<VirtualMachineDescriptor, Properties> virtualMachinePropertiesMap = new ConcurrentHashMap<>();\n\tprivate final Map<VirtualMachineDescriptor, String> virtualMachineMainClassMap = new ConcurrentHashMap<>();\n\tprivate final Map<VirtualMachineDescriptor, JmxBeanServerConnection> virtualMachineJmxConnMap = new ConcurrentHashMap<>();\n\tprivate final ObservableList<VirtualMachineDescriptor> virtualMachineDescriptors = new ObservableList<>();\n\tprivate final List<PostScanListener> postScanListeners = new CopyOnWriteArrayList<>();\n\tprivate final AttachManagerConfig config;\n\tprivate ScheduledFuture<?> future;\n\n\t@Inject\n\tpublic BasicAttachManager(AttachManagerConfig config) {\n\t\tthis.config = config;\n\t\textractAgent();\n\t}\n\n\t/**\n\t * Extracts the agent jar from Recaf to its own file.\n\t */\n\tprivate void extractAgent() {\n\t\tif (extractState == ExtractState.DEFAULT) {\n\t\t\tPath agentPath = getAgentJarPath();\n\t\t\tif (!Files.isRegularFile(agentPath)) {\n\t\t\t\t// Not extracted already\n\t\t\t\ttry {\n\t\t\t\t\tlogger.debug(\"Extracting agent jar to Recaf directory: {}\", agentPath.getFileName());\n\t\t\t\t\tFiles.createDirectories(config.getAgentDirectory());\n\t\t\t\t\tExtractor.extractToPath(agentPath);\n\t\t\t\t\tfuture = ThreadUtil.scheduleAtFixedRate(this::passiveScanUpdate, 0, 1, TimeUnit.SECONDS);\n\t\t\t\t\textractState = ExtractState.SUCCESS;\n\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\tlogger.error(\"Failed to extract agent jar to Recaf directory\", ex);\n\t\t\t\t\textractState = ExtractState.FAILURE;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Already extracted before\n\t\t\t\textractState = ExtractState.SUCCESS;\n\t\t\t\tfuture = ThreadUtil.scheduleAtFixedRate(this::passiveScanUpdate, 0, 1, TimeUnit.SECONDS);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Cancel passive scan loop when shutting down.\n\t */\n\t@PreDestroy\n\tprivate void onShutdown() {\n\t\tfuture.cancel(true);\n\t}\n\n\t/**\n\t * Check for new virtual machines in the background.\n\t */\n\tprivate void passiveScanUpdate() {\n\t\ttry {\n\t\t\tif (config.getPassiveScanning().getValue())\n\t\t\t\tscan();\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Unhandled exception in JVM scan\", t);\n\t\t}\n\t}\n\n\t/**\n\t * @param descriptor\n\t * \t\tVM descriptor.\n\t *\n\t * @return Main class of VM, if possible to resolve.\n\t */\n\t@Nonnull\n\tprivate String mapToMainClass(@Nonnull VirtualMachineDescriptor descriptor) {\n\t\t// Get source string to find main class name from\n\t\tString source = descriptor.displayName();\n\t\tif (source == null || source.isBlank() || source.toLowerCase().contains(\".jar\")) {\n\t\t\tProperties properties = virtualMachinePropertiesMap.get(descriptor);\n\t\t\tif (properties != null) {\n\t\t\t\t// Check if we can get the main class from the command.\n\t\t\t\t// It may start with it, such as \"com.example.Main args...\"\n\t\t\t\t// it may be a file path, such as from \"-jar <path> <args...>\"\n\t\t\t\tString command = properties.getProperty(\"sun.java.command\", \"\");\n\n\t\t\t\t// Check if the command is a path.\n\t\t\t\tString commandLower = command.toLowerCase();\n\t\t\t\tint jarIndex = commandLower.indexOf(\".jar\");\n\t\t\t\tif (jarIndex > 0) {\n\t\t\t\t\t// Depending on the invocation, it may not be the full path.\n\t\t\t\t\t// We may need to pre-pend the current directory\n\t\t\t\t\tString commandJarName = command.substring(0, jarIndex + 4);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tPath jarPath = Paths.get(commandJarName);\n\t\t\t\t\t\tif (!Files.isRegularFile(jarPath)) {\n\t\t\t\t\t\t\t// Prepend remote vm's user directory\n\t\t\t\t\t\t\tString commandUserDir = properties.getProperty(\"user.dir\");\n\t\t\t\t\t\t\tif (commandUserDir.endsWith(\"/\"))\n\t\t\t\t\t\t\t\tcommandUserDir = commandUserDir.substring(0, commandUserDir.length() - 1);\n\t\t\t\t\t\t\tif (commandJarName.startsWith(\"/\"))\n\t\t\t\t\t\t\t\tcommandJarName = commandJarName.substring(1);\n\t\t\t\t\t\t\tjarPath = Paths.get(commandUserDir + \"/\" + commandJarName);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Read main class attribute from jar manifest\n\t\t\t\t\t\tif (Files.isRegularFile(jarPath)) {\n\t\t\t\t\t\t\ttry (JarFile jar = new JarFile(jarPath.toFile())) {\n\t\t\t\t\t\t\t\tsource = jar.getManifest().getMainAttributes().getValue(\"Main-Class\");\n\t\t\t\t\t\t\t} catch (IOException ignored) {\n\t\t\t\t\t\t\t\t// Can't read from jar, oh well\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (InvalidPathException ignored) {\n\t\t\t\t\t\t// Expected for cases like 'com.example.Main foo.jar'\n\t\t\t\t\t\t// In this case we know the substring up to the '.jar' isn't a path in totality.\n\t\t\t\t\t\t// Only a section of it is, so likely the '.jar' match is part of an argument.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Still null/missing? Give up\n\t\tif (source == null || source.isEmpty())\n\t\t\treturn \"<unknown main-class>\";\n\n\t\t// Some 'display name' values are '<class> <args...>' so strip out the args\n\t\tString trim = source.trim();\n\t\tint end = trim.indexOf(' ');\n\t\tif (end == -1)\n\t\t\tend = trim.length();\n\t\treturn trim.substring(0, end);\n\t\t// Alternative idea for later: Use 'sun/launcher/LauncherHelper' as mentioned by xxDark\n\t\t//  - reliable source for main-class\n\t}\n\n\t/**\n\t * @param descriptor\n\t * \t\tVM descriptor.\n\t *\n\t * @return PID of VM process.\n\t */\n\tprivate int mapToPid(@Nonnull VirtualMachineDescriptor descriptor) {\n\t\tString id = descriptor.id();\n\t\tif (id.matches(\"\\\\d+\")) {\n\t\t\treturn Integer.parseInt(descriptor.id());\n\t\t} else {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\t/**\n\t * @return {@code true} when the agent jar is extracted and ready to be used.\n\t * {@code false} means instrumentation cannot be done since the agent is not available.\n\t */\n\tpublic static boolean isAgentReady() {\n\t\treturn extractState == ExtractState.SUCCESS;\n\t}\n\n\t/**\n\t * @return Path to agent jar file.\n\t */\n\t@Nonnull\n\tpublic Path getAgentJarPath() {\n\t\tString jarName = \"agent-\" + BuildConfig.VERSION + \".jar\";\n\t\treturn config.getAgentDirectory().resolve(jarName);\n\t}\n\n\t@Override\n\tpublic boolean canAttach() {\n\t\treturn isAgentReady();\n\t}\n\n\t@Override\n\tpublic void scan() {\n\t\tint numDescriptors = virtualMachineDescriptors.size();\n\t\tList<VirtualMachineDescriptor> remoteVmList = VirtualMachine.list();\n\t\tSet<VirtualMachineDescriptor> toRemove = new HashSet<>(virtualMachineDescriptors);\n\t\tSet<VirtualMachineDescriptor> toAdd = new HashSet<>();\n\t\tList<CompletableFuture<?>> attachFutures = new ArrayList<>();\n\t\tfor (VirtualMachineDescriptor descriptor : remoteVmList) {\n\t\t\t// Still active in VM list, keep it.\n\t\t\ttoRemove.remove(descriptor);\n\n\t\t\t// Add if not in the list.\n\t\t\tif (!virtualMachineDescriptors.contains(descriptor)) {\n\t\t\t\tString label = descriptor.id() + \" - \" + StringUtil.withEmptyFallback(descriptor.displayName(), \"?\");\n\t\t\t\tint pid = mapToPid(descriptor);\n\t\t\t\tif (pid == currentPid) // skip self\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Using futures for attach in case one of the VM's decides to hang on response.\n\t\t\t\t// Using 'orTimeout' we can prevent such hangs from affecting us.\n\t\t\t\tattachFutures.add(ThreadUtil.supply(() -> {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tAttachProvider provider = descriptor.provider();\n\t\t\t\t\t\treturn provider.attachVirtualMachine(descriptor);\n\t\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\t\tvirtualMachineFailureMap.put(descriptor, ex);\n\t\t\t\t\t\tlogger.debug(\"Remote JVM descriptor found (attach-success, read-failure): \" + label);\n\t\t\t\t\t} catch (AttachNotSupportedException ex) {\n\t\t\t\t\t\tvirtualMachineFailureMap.put(descriptor, ex);\n\t\t\t\t\t\tlogger.debug(\"Remote JVM descriptor found (attach-failure): \" + label);\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tlogger.error(\"Unhandled exception populating remote VM info\", t);\n\t\t\t\t\t}\n\t\t\t\t\treturn null;\n\t\t\t\t}, null).orTimeout(500, TimeUnit.MILLISECONDS).thenAccept(machine -> {\n\t\t\t\t\t// Get information from machine if it is available.\n\t\t\t\t\tif (machine != null) {\n\t\t\t\t\t\tvirtualMachineMap.put(descriptor, machine);\n\t\t\t\t\t\tlogger.debug(\"Remote JVM descriptor found (attach-success): \" + label);\n\n\t\t\t\t\t\t// Extract additional information\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tProperties systemProperties = machine.getSystemProperties();\n\t\t\t\t\t\t\tvirtualMachinePropertiesMap.put(descriptor, systemProperties);\n\t\t\t\t\t\t\tvirtualMachinePidMap.put(descriptor, pid);\n\t\t\t\t\t\t\tvirtualMachineMainClassMap.put(descriptor, mapToMainClass(descriptor));\n\n\t\t\t\t\t\t\t// Enable optional JMX agent\n\t\t\t\t\t\t\tif (config.getAttachJmxAgent().getValue()) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tProperties agentProperties = machine.getAgentProperties();\n\t\t\t\t\t\t\t\t\tString serviceUrl = agentProperties.getProperty(JMX_AGENT_ADDRESS);\n\t\t\t\t\t\t\t\t\tif (serviceUrl == null) {\n\t\t\t\t\t\t\t\t\t\tserviceUrl = machine.startLocalManagementAgent();\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif (serviceUrl != null) {\n\t\t\t\t\t\t\t\t\t\tJMXServiceURL url = new JMXServiceURL(serviceUrl);\n\t\t\t\t\t\t\t\t\t\t@SuppressWarnings(\"resource\") // Do NOT wrap this in a try-with-resource. It will close the connection.\n\t\t\t\t\t\t\t\t\t\tJMXConnector connector = JMXConnectorFactory.connect(url);\n\t\t\t\t\t\t\t\t\t\tMBeanServerConnection connection = connector.getMBeanServerConnection();\n\t\t\t\t\t\t\t\t\t\tvirtualMachineJmxConnMap.put(descriptor, new JmxBeanServerConnection(connection));\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tlogger.warn(\"Could fetch JMX agent address, skipping connection for: {}\", label);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} catch (Exception ex) {\n\t\t\t\t\t\t\t\t\tlogger.error(\"Failed to attach JMX agent to remote JVM: {}\", label, ex);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\t\t\tlogger.error(\"Could not read system properties from remote JVM: \" + label, ex);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Add to list for listener call later.\n\t\t\t\t\ttoAdd.add(descriptor);\n\n\t\t\t\t\t// Insert descriptor in sorted order.\n\t\t\t\t\tint lastComparison = 1;\n\t\t\t\t\tsynchronized (virtualMachineDescriptors) {\n\t\t\t\t\t\tfor (int i = 0; i < numDescriptors; i++) {\n\t\t\t\t\t\t\tVirtualMachineDescriptor other = virtualMachineDescriptors.get(i);\n\t\t\t\t\t\t\tint comparison = descriptorComparator.compare(descriptor, other);\n\t\t\t\t\t\t\tif (comparison < lastComparison) {\n\t\t\t\t\t\t\t\tvirtualMachineDescriptors.add(i, descriptor);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Greater than all entries, append to end\n\t\t\t\t\t\tvirtualMachineDescriptors.add(descriptor);\n\t\t\t\t\t}\n\t\t\t\t}));\n\t\t\t}\n\t\t}\n\t\t// When all attach attachFutures complete, update the observable list to update the UI\n\t\tThreadUtil.allOf(attachFutures.toArray(new CompletableFuture[0])).thenRun(() -> {\n\t\t\t// Remove entries not visited in this pass\n\t\t\tvirtualMachineDescriptors.removeAll(toRemove);\n\t\t\tfor (VirtualMachineDescriptor descriptor : toRemove) {\n\t\t\t\tString label = descriptor.id() + \" - \" + StringUtil.withEmptyFallback(descriptor.displayName(), \"?\");\n\t\t\t\tlogger.debug(\"Remote JVM descriptor removed: \" + label);\n\t\t\t}\n\n\t\t\t// Call listeners\n\t\t\tUnchecked.checkedForEach(postScanListeners, listener -> listener.onScanCompleted(toAdd, toRemove),\n\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown after scan completion\", t));\n\t\t});\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic WorkspaceRemoteVmResource createRemoteResource(VirtualMachineDescriptor item) throws IOException {\n\t\tVirtualMachine virtualMachine = virtualMachineMap.get(item);\n\t\ttry {\n\t\t\t// Will initialize agent server with default arguments\n\t\t\tProperties properties = virtualMachinePropertiesMap.get(item);\n\t\t\tint port = Discovery.extractPort(properties);\n\t\t\tif (port <= 0) {\n\t\t\t\t// Port not found, server is not running on remote VM.\n\t\t\t\t// Load the agent to start it.\n\t\t\t\ttry {\n\t\t\t\t\tport = SocketAvailability.findAvailable();\n\t\t\t\t\tString agentAbsolutePath = StringUtil.pathToAbsoluteString(getAgentJarPath());\n\t\t\t\t\tString agentArgs = \"port=\" + port+\",notrampolines\";\n\t\t\t\t\tif (DevDetection.isDevEnv())\n\t\t\t\t\t\tagentArgs += \",debug\";\n\t\t\t\t\telse\n\t\t\t\t\t\tagentArgs += \",namelessThreads\";\n\t\t\t\t\tvirtualMachine.loadAgent(agentAbsolutePath, agentArgs);\n\n\t\t\t\t\t// The agent server will update some properties to indicate its active.\n\t\t\t\t\t// We need to update our map so that we can see this indicator so that we can extract\n\t\t\t\t\t// the port that the server is running on if we want to reconnect.\n\t\t\t\t\tProperties systemProperties = virtualMachine.getSystemProperties();\n\t\t\t\t\tvirtualMachinePropertiesMap.put(item, systemProperties);\n\t\t\t\t} catch (AgentLoadException ex) {\n\t\t\t\t\t// The agent jar file is written in Java 8. But Recaf uses Java 11+.\n\t\t\t\t\t// This is a problem on OUR side because Java 11+ handles agent interactions differently.\n\t\t\t\t\t//  - https://stackoverflow.com/a/54454418/\n\t\t\t\t\t// Basically in Java 10 they added a prefix string requirement.\n\t\t\t\t\t// But the Java 8 VM doesn't have that so our VM will mark it as invalid.\n\t\t\t\t\tif (!ex.getMessage().equals(\"0\"))\n\t\t\t\t\t\t// If the result we get back is '0' then that means it was actually a success\n\t\t\t\t\t\tthrow ex;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Connect with client\n\t\t\tClient client = new Client(\"localhost\", port, ByteBufferAllocator.HEAP, MessageFactory.create());\n\t\t\treturn new AgentServerRemoteVmResource(virtualMachine, client);\n\t\t} catch (AgentLoadException ex) {\n\t\t\tlogger.error(\"Agent on remote VM '{}' could not be loaded\", item, ex);\n\t\t\tthrow new IOException(\"Failed remote load\", ex);\n\t\t} catch (AgentInitializationException ex) {\n\t\t\tlogger.error(\"Agent on remote VM '{}' crashed on initialization\", item, ex);\n\t\t\tthrow new IOException(\"Failed remote initialization\", ex);\n\t\t} catch (IOException ex) {\n\t\t\tlogger.error(\"IO error when loading agent to remote VM '{}'\", item, ex);\n\t\t\tthrow ex;\n\t\t}\n\t}\n\n\t@Override\n\tpublic VirtualMachine getVirtualMachine(@Nonnull VirtualMachineDescriptor descriptor) {\n\t\treturn virtualMachineMap.get(descriptor);\n\t}\n\n\t@Override\n\tpublic Exception getVirtualMachineConnectionFailure(@Nonnull VirtualMachineDescriptor descriptor) {\n\t\treturn virtualMachineFailureMap.get(descriptor);\n\t}\n\n\t@Override\n\tpublic int getVirtualMachinePid(@Nonnull VirtualMachineDescriptor descriptor) {\n\t\treturn virtualMachinePidMap.getOrDefault(descriptor, -1);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic Properties getVirtualMachineProperties(@Nonnull VirtualMachineDescriptor descriptor) {\n\t\treturn virtualMachinePropertiesMap.get(descriptor);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getVirtualMachineMainClass(@Nonnull VirtualMachineDescriptor descriptor) {\n\t\treturn virtualMachineMainClassMap.get(descriptor);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic JmxBeanServerConnection getJmxServerConnection(@Nonnull VirtualMachineDescriptor descriptor) {\n\t\treturn virtualMachineJmxConnMap.get(descriptor);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ObservableList<VirtualMachineDescriptor> getVirtualMachineDescriptors() {\n\t\treturn virtualMachineDescriptors;\n\t}\n\n\t@Override\n\tpublic void addPostScanListener(@Nonnull PostScanListener listener) {\n\t\tPrioritySortable.add(postScanListeners, listener);\n\t}\n\n\t@Override\n\tpublic void removePostScanListener(@Nonnull PostScanListener listener) {\n\t\tpostScanListeners.remove(listener);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic AttachManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\t/**\n\t * Comparator for {@link VirtualMachineDescriptor} using {@link #mapToMainClass(VirtualMachineDescriptor)}.\n\t */\n\tclass DescriptorComparator implements Comparator<VirtualMachineDescriptor> {\n\t\t@Override\n\t\tpublic int compare(VirtualMachineDescriptor o1, VirtualMachineDescriptor o2) {\n\t\t\tString k1 = virtualMachineMainClassMap.getOrDefault(o1, o1.displayName());\n\t\t\tString k2 = virtualMachineMainClassMap.getOrDefault(o2, o2.displayName());\n\t\t\treturn k1.compareTo(k2);\n\t\t}\n\t}\n\n\tenum ExtractState {\n\t\tDEFAULT,\n\t\tSUCCESS,\n\t\tFAILURE\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/attach/JmxBeanServerConnection.java",
    "content": "package software.coley.recaf.services.attach;\n\nimport jakarta.annotation.Nonnull;\n\nimport javax.management.MBeanServerConnection;\nimport javax.management.MalformedObjectNameException;\nimport javax.management.ObjectName;\nimport java.lang.management.*;\n\n/**\n * Helper to wrap {@link MBeanServerConnection}.\n *\n * @author Matt Coley\n */\npublic class JmxBeanServerConnection {\n\tpublic static final ObjectName CLASS_LOADING = named(ManagementFactory.CLASS_LOADING_MXBEAN_NAME);\n\tpublic static final ObjectName COMPILATION = named(ManagementFactory.COMPILATION_MXBEAN_NAME);\n\tpublic static final ObjectName OPERATING_SYSTEM = named(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME);\n\tpublic static final ObjectName RUNTIME = named(ManagementFactory.RUNTIME_MXBEAN_NAME);\n\tpublic static final ObjectName THREAD = named(ManagementFactory.THREAD_MXBEAN_NAME);\n\t// The following beans require knowing the bean's domain in advance to supply it as an additional parameter:\n\t//  - GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE\n\t//  - MEMORY_MANAGER_MXBEAN_DOMAIN_TYPE\n\t//  - MEMORY_POOL_MXBEAN_DOMAIN_TYPE\n\tprivate final MBeanServerConnection connection;\n\n\t/**\n\t * @param connection\n\t * \t\tUnderlying connection.\n\t */\n\tpublic JmxBeanServerConnection(@Nonnull MBeanServerConnection connection) {\n\t\tthis.connection = connection;\n\t}\n\n\t/**\n\t * @return Attributes and operations of {@link ClassLoadingMXBean}.\n\t *\n\t * @throws Exception\n\t * \t\tWhen the bean could not be fetched.\n\t */\n\t@Nonnull\n\tpublic NamedMBeanInfo getClassloadingBeanInfo() throws Exception {\n\t\treturn new NamedMBeanInfo(CLASS_LOADING, connection.getMBeanInfo(CLASS_LOADING));\n\t}\n\n\t/**\n\t * @return Attributes and operations of {@link CompilationMXBean}.\n\t *\n\t * @throws Exception\n\t * \t\tWhen the bean could not be fetched.\n\t */\n\t@Nonnull\n\tpublic NamedMBeanInfo getCompilationBeanInfo() throws Exception {\n\t\treturn new NamedMBeanInfo(COMPILATION, connection.getMBeanInfo(COMPILATION));\n\t}\n\n\t/**\n\t * @return Attributes and operations of {@link OperatingSystemMXBean}.\n\t *\n\t * @throws Exception\n\t * \t\tWhen the bean could not be fetched.\n\t */\n\t@Nonnull\n\tpublic NamedMBeanInfo getOperatingSystemBeanInfo() throws Exception {\n\t\treturn new NamedMBeanInfo(OPERATING_SYSTEM, connection.getMBeanInfo(OPERATING_SYSTEM));\n\t}\n\n\t/**\n\t * @return Attributes and operations of {@link RuntimeMXBean}.\n\t *\n\t * @throws Exception\n\t * \t\tWhen the bean could not be fetched.\n\t */\n\t@Nonnull\n\tpublic NamedMBeanInfo getRuntimeBeanInfo() throws Exception {\n\t\treturn new NamedMBeanInfo(RUNTIME, connection.getMBeanInfo(RUNTIME));\n\t}\n\n\t/**\n\t * @return Attributes and operations of {@link ThreadMXBean}.\n\t *\n\t * @throws Exception\n\t * \t\tWhen the bean could not be fetched.\n\t */\n\t@Nonnull\n\tpublic NamedMBeanInfo getThreadBeanInfo() throws Exception {\n\t\treturn new NamedMBeanInfo(THREAD, connection.getMBeanInfo(THREAD));\n\t}\n\n\t/**\n\t * @return Underlying connection.\n\t */\n\t@Nonnull\n\tpublic MBeanServerConnection getConnection() {\n\t\treturn connection;\n\t}\n\n\t@Nonnull\n\tprivate static ObjectName named(@Nonnull String name) {\n\t\ttry {\n\t\t\treturn new ObjectName(name);\n\t\t} catch (MalformedObjectNameException ex) {\n\t\t\tthrow new IllegalStateException(ex);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/attach/NamedMBeanInfo.java",
    "content": "package software.coley.recaf.services.attach;\n\nimport jakarta.annotation.Nonnull;\n\nimport javax.management.*;\n\n/**\n * Extension of {@link MBeanInfo} providing access to the associated {@link ObjectName}.\n *\n * @author Matt Coley\n */\npublic class NamedMBeanInfo extends MBeanInfo {\n\tprivate final ObjectName objectName;\n\n\t/**\n\t * @param objectName\n\t * \t\tAssociated name to the bean info.\n\t * @param info\n\t * \t\tWrapped bean info.\n\t */\n\tpublic NamedMBeanInfo(@Nonnull ObjectName objectName, @Nonnull MBeanInfo info) {\n\t\tthis(objectName,\n\t\t\t\tinfo.getClassName(),\n\t\t\t\tinfo.getDescription(),\n\t\t\t\tinfo.getAttributes(),\n\t\t\t\tinfo.getConstructors(),\n\t\t\t\tinfo.getOperations(),\n\t\t\t\tinfo.getNotifications(),\n\t\t\t\tinfo.getDescriptor());\n\t}\n\n\tprivate NamedMBeanInfo(@Nonnull ObjectName objectName,\n\t\t\t\t\t\t   String className,\n\t\t\t\t\t\t   String description,\n\t\t\t\t\t\t   MBeanAttributeInfo[] attributes,\n\t\t\t\t\t\t   MBeanConstructorInfo[] constructors,\n\t\t\t\t\t\t   MBeanOperationInfo[] operations,\n\t\t\t\t\t\t   MBeanNotificationInfo[] notifications,\n\t\t\t\t\t\t   Descriptor descriptor) throws IllegalArgumentException {\n\t\tsuper(className, description, attributes, constructors, operations, notifications, descriptor);\n\t\tthis.objectName = objectName;\n\t}\n\n\t/**\n\t * @return Key to use in {@link MBeanServerConnection} operations.\n\t */\n\t@Nonnull\n\tpublic ObjectName getObjectName() {\n\t\treturn objectName;\n\t}\n\n\t/**\n\t * @param connection\n\t * \t\tJMX connection.\n\t * @param attribute\n\t * \t\tAttribute to get value of.\n\t *\n\t * @return Value of attribute.\n\t *\n\t * @throws Exception\n\t * \t\tSee: {@link MBeanServerConnection#getAttribute(ObjectName, String)}.\n\t */\n\tpublic Object getAttributeValue(@Nonnull JmxBeanServerConnection connection,\n\t\t\t\t\t\t\t\t\t@Nonnull MBeanAttributeInfo attribute) throws Exception {\n\t\treturn getAttributeValue(connection, attribute.getName());\n\t}\n\n\t/**\n\t * @param connection\n\t * \t\tJMX connection.\n\t * @param attributeName\n\t * \t\tName of attribute to get value of.\n\t *\n\t * @return Value of attribute.\n\t *\n\t * @throws Exception\n\t * \t\tSee: {@link MBeanServerConnection#getAttribute(ObjectName, String)}.\n\t */\n\tpublic Object getAttributeValue(@Nonnull JmxBeanServerConnection connection,\n\t\t\t\t\t\t\t\t\t@Nonnull String attributeName) throws Exception {\n\t\treturn connection.getConnection().getAttribute(getObjectName(), attributeName);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/attach/PostScanListener.java",
    "content": "package software.coley.recaf.services.attach;\n\nimport com.sun.tools.attach.VirtualMachineDescriptor;\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\n\nimport java.util.Set;\n\n/**\n * Listener called after {@link AttachManager#scan()} completion.\n *\n * @author Matt Coley\n */\npublic interface PostScanListener extends PrioritySortable {\n\t/**\n\t * Called when scan is completed.\n\t *\n\t * @param added\n\t * \t\tNewly found VMs.\n\t * @param removed\n\t * \t\tOld VMs that are no longer available.\n\t */\n\tvoid onScanCompleted(@Nonnull Set<VirtualMachineDescriptor> added,\n\t\t\t\t\t\t @Nonnull Set<VirtualMachineDescriptor> removed);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/callgraph/CachedLinkResolver.java",
    "content": "package software.coley.recaf.services.callgraph;\n\nimport dev.xdark.jlinker.ClassInfo;\nimport dev.xdark.jlinker.LinkResolver;\nimport dev.xdark.jlinker.Resolution;\nimport dev.xdark.jlinker.Result;\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.util.MemoizedFunctions;\n\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\n\n/**\n * Memoized implementation of {@link LinkResolver}.\n *\n * @author Amejonah\n */\npublic class CachedLinkResolver implements LinkResolver<JvmClassInfo, MethodMember, FieldMember> {\n\tprivate final LinkResolver<JvmClassInfo, MethodMember, FieldMember> backedResolver = LinkResolver.jvm();\n\tprivate final Function<ClassInfo<JvmClassInfo>, BiFunction<String, String, Result<Resolution<JvmClassInfo, MethodMember>>>>\n\t\t\tvirtualMethodResolver = MemoizedFunctions.memoize(\n\t\t\tc -> MemoizedFunctions.memoize((name, descriptor) -> backedResolver.resolveVirtualMethod(c, name, descriptor))\n\t);\n\tprivate final Function<ClassInfo<JvmClassInfo>, BiFunction<String, String, Result<Resolution<JvmClassInfo, MethodMember>>>>\n\t\t\tstaticMethodResolver = MemoizedFunctions.memoize(\n\t\t\tc -> MemoizedFunctions.memoize((name, descriptor) -> backedResolver.resolveStaticMethod(c, name, descriptor))\n\t);\n\tprivate final Function<ClassInfo<JvmClassInfo>, BiFunction<String, String, Result<Resolution<JvmClassInfo, MethodMember>>>>\n\t\t\tinterfaceMethodResolver = MemoizedFunctions.memoize(\n\t\t\tc -> MemoizedFunctions.memoize((name, descriptor) -> backedResolver.resolveInterfaceMethod(c, name, descriptor))\n\t);\n\tprivate final Function<ClassInfo<JvmClassInfo>, BiFunction<String, String, Result<Resolution<JvmClassInfo, FieldMember>>>>\n\t\t\tvirtualFieldResolver = MemoizedFunctions.memoize(\n\t\t\tc -> MemoizedFunctions.memoize((name, descriptor) -> backedResolver.resolveVirtualField(c, name, descriptor))\n\t);\n\tprivate final Function<ClassInfo<JvmClassInfo>, BiFunction<String, String, Result<Resolution<JvmClassInfo, FieldMember>>>>\n\t\t\tstaticFieldResolver = MemoizedFunctions.memoize(\n\t\t\tc -> MemoizedFunctions.memoize((name, descriptor) -> backedResolver.resolveStaticField(c, name, descriptor))\n\t);\n\tprivate final Function<dev.xdark.jlinker.ClassInfo<JvmClassInfo>, BiFunction<String, String, Result<Resolution<JvmClassInfo, MethodMember>>>>\n\t\t\tspecialMethodResolver = MemoizedFunctions.memoize(\n\t\t\tc -> MemoizedFunctions.memoize((name, descriptor) -> backedResolver.resolveSpecialMethod(c, name, descriptor))\n\t);\n\n\t@Override\n\tpublic Result<Resolution<JvmClassInfo, MethodMember>> resolveStaticMethod(@Nonnull ClassInfo<JvmClassInfo> owner,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull String name, @Nonnull String descriptor, boolean itf) {\n\t\treturn staticMethodResolver.apply(owner).apply(name, descriptor);\n\t}\n\n\t@Override\n\tpublic Result<Resolution<JvmClassInfo, MethodMember>> resolveSpecialMethod(@Nonnull ClassInfo<JvmClassInfo> owner,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull String name, @Nonnull String descriptor, boolean itf) {\n\t\treturn specialMethodResolver.apply(owner).apply(name, descriptor);\n\t}\n\n\t@Override\n\tpublic Result<Resolution<JvmClassInfo, MethodMember>> resolveVirtualMethod(@Nonnull ClassInfo<JvmClassInfo> owner,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull String name, @Nonnull String descriptor) {\n\t\treturn virtualMethodResolver.apply(owner).apply(name, descriptor);\n\t}\n\n\t@Override\n\tpublic Result<Resolution<JvmClassInfo, MethodMember>> resolveInterfaceMethod(@Nonnull ClassInfo<JvmClassInfo> owner,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull String name, @Nonnull String descriptor) {\n\t\treturn interfaceMethodResolver.apply(owner).apply(name, descriptor);\n\t}\n\n\t@Override\n\tpublic Result<Resolution<JvmClassInfo, FieldMember>> resolveStaticField(@Nonnull ClassInfo<JvmClassInfo> owner,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull String name, @Nonnull String descriptor) {\n\t\treturn staticFieldResolver.apply(owner).apply(name, descriptor);\n\t}\n\n\t@Override\n\tpublic Result<Resolution<JvmClassInfo, FieldMember>> resolveVirtualField(@Nonnull ClassInfo<JvmClassInfo> owner,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull String name, @Nonnull String descriptor) {\n\t\treturn virtualFieldResolver.apply(owner).apply(name, descriptor);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/callgraph/CallGraph.java",
    "content": "package software.coley.recaf.services.callgraph;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport dev.xdark.jlinker.MemberInfo;\nimport dev.xdark.jlinker.Resolution;\nimport dev.xdark.jlinker.ResolutionError;\nimport dev.xdark.jlinker.Result;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.Opcodes;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.util.AsmInsnUtil;\nimport software.coley.recaf.util.MultiMap;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.WorkspaceModificationListener;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.ResourceJvmClassListener;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.IdentityHashMap;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutorService;\n\n/**\n * Represents method calls as a navigable graph.\n *\n * @author Amejonah\n * @author Matt Coley\n * @see MethodVertex\n */\npublic class CallGraph implements WorkspaceModificationListener, ResourceJvmClassListener {\n\tprivate static final DebuggingLogger logger = Logging.get(CallGraph.class);\n\tprivate final ExecutorService threadPool = ThreadPoolFactory.newFixedThreadPool(\"call-graph\", 1, true);\n\tprivate final CachedLinkResolver resolver = new CachedLinkResolver();\n\tprivate final Map<JvmClassInfo, LinkedClass> classToLinkerType = Collections.synchronizedMap(new IdentityHashMap<>());\n\tprivate final Map<JvmClassInfo, ClassMethodsContainer> classToMethodsContainer = Collections.synchronizedMap(new IdentityHashMap<>());\n\tprivate final MultiMap<String, MethodRef, Set<MethodRef>> unresolvedDeclarations = MultiMap.from(\n\t\t\tnew ConcurrentHashMap<>(),\n\t\t\tConcurrentHashMap::newKeySet);\n\tprivate final MultiMap<String, CallingContext, Set<CallingContext>> unresolvedReferences = MultiMap.from(\n\t\t\tnew ConcurrentHashMap<>(),\n\t\t\tConcurrentHashMap::newKeySet);\n\tprivate final ObservableBoolean isReady = new ObservableBoolean(false);\n\tprivate final Workspace workspace;\n\tprivate final ClassLookup lookup;\n\tprivate boolean initialized;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull data from.\n\t */\n\tpublic CallGraph(@Nonnull Workspace workspace) {\n\t\tthis.workspace = workspace;\n\n\t\tlookup = new ClassLookup(workspace);\n\t}\n\n\t/**\n\t * @return {@code true} when {@link #initialize()} has been called.\n\t */\n\tpublic boolean isInitialized() {\n\t\treturn initialized;\n\t}\n\n\t/**\n\t * @return Observable boolean tracking the state of the call-graph's parsing of the current workspace.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean isReady() {\n\t\treturn isReady;\n\t}\n\n\t/**\n\t * @param classInfo\n\t * \t\tClass to wrap.\n\t *\n\t * @return Wrapper for easy {@link MethodVertex} management for the class.\n\t */\n\t@Nonnull\n\tpublic ClassMethodsContainer getClassMethodsContainer(@Nonnull JvmClassInfo classInfo) {\n\t\treturn classToMethodsContainer.computeIfAbsent(classInfo, c -> new ClassMethodsContainer(classInfo));\n\t}\n\n\t/**\n\t * @param method\n\t * \t\tMethod to get vertex of.\n\t *\n\t * @return Vertex of method. Can be {@code null} if the method member does not\n\t * define its {@link MethodMember#getDeclaringClass() declaring class}.\n\t */\n\t@Nullable\n\tpublic MethodVertex getVertex(@Nonnull MethodMember method) {\n\t\tClassInfo declaringClass = method.getDeclaringClass();\n\t\tif (declaringClass == null) return null;\n\t\treturn getClassMethodsContainer(declaringClass.asJvmClass()).getVertex(method);\n\t}\n\n\t/**\n\t * @param classInfo\n\t * \t\tClass to wrap.\n\t *\n\t * @return JLinker wrapper for class.\n\t */\n\t@Nonnull\n\tprivate LinkedClass linked(@Nonnull JvmClassInfo classInfo) {\n\t\treturn classToLinkerType.computeIfAbsent(classInfo, c -> new LinkedClass(lookup, c));\n\t}\n\n\t/**\n\t * Initialize the graph.\n\t */\n\tpublic void initialize() {\n\t\t// Only allow calls to initialize the graph once\n\t\tif (initialized) return;\n\t\tinitialized = true;\n\n\t\t// Register modification listeners so that we can update the graph when class state changes.\n\t\tworkspace.addWorkspaceModificationListener(this);\n\t\tworkspace.getPrimaryResource().addResourceJvmClassListener(this);\n\n\t\t// Initialize asynchronously, and mark 'isReady' if completed successfully\n\t\tCompletableFuture.runAsync(() -> {\n\t\t\tfor (WorkspaceResource resource : workspace.getAllResources(false)) {\n\t\t\t\tresource.jvmAllClassBundleStreamRecursive().forEach(bundle -> {\n\t\t\t\t\tfor (JvmClassInfo jvmClass : bundle.values())\n\t\t\t\t\t\tvisit(jvmClass);\n\t\t\t\t});\n\t\t\t}\n\t\t}, threadPool).whenComplete((unused, t) -> {\n\t\t\tif (t == null) {\n\t\t\t\tisReady.setValue(true);\n\t\t\t} else {\n\t\t\t\tlogger.error(\"Call graph initialization failed\", t);\n\t\t\t\tisReady.setValue(false);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Populate {@link MethodVertex} for all methods in {@link JvmClassInfo#getMethods()}.\n\t *\n\t * @param jvmClass\n\t * \t\tClass to visit.\n\t */\n\tprivate void visit(@Nonnull JvmClassInfo jvmClass) {\n\t\tClassMethodsContainer classMethodsContainer = getClassMethodsContainer(jvmClass);\n\t\tjvmClass.getClassReader().accept(new ClassVisitor(RecafConstants.getAsmVersion()) {\n\t\t\t@Override\n\t\t\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\t\t\tMutableMethodVertex methodVertex = (MutableMethodVertex) classMethodsContainer.getVertex(name, descriptor);\n\t\t\t\tif (methodVertex == null) {\n\t\t\t\t\tlogger.error(\"Method {}{} was visited, but not present in info for declaring class {}\",\n\t\t\t\t\t\t\tname, descriptor, jvmClass.getName());\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\treturn new MethodVisitor(RecafConstants.getAsmVersion()) {\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void visitEnd() {\n\t\t\t\t\t\tsuper.visitEnd();\n\n\t\t\t\t\t\tlinkedResolvedCalls(jvmClass, name, descriptor, methodVertex);\n\t\t\t\t\t}\n\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {\n\t\t\t\t\t\tonMethodCalled(jvmClass, methodVertex, opcode, owner, name, descriptor, isInterface);\n\t\t\t\t\t}\n\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {\n\t\t\t\t\t\tif (!\"java/lang/invoke/LambdaMetafactory\".equals(bootstrapMethodHandle.getOwner())\n\t\t\t\t\t\t\t\t|| !\"metafactory\".equals(bootstrapMethodHandle.getName())\n\t\t\t\t\t\t\t\t|| !\"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;\".equals(bootstrapMethodHandle.getDesc())) {\n\t\t\t\t\t\t\tsuper.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tObject handleObj = bootstrapMethodArguments.length == 3 ? bootstrapMethodArguments[1] : null;\n\t\t\t\t\t\tif (handleObj instanceof Handle handle) {\n\t\t\t\t\t\t\tswitch (handle.getTag()) {\n\t\t\t\t\t\t\t\tcase Opcodes.H_INVOKESPECIAL:\n\t\t\t\t\t\t\t\tcase Opcodes.H_INVOKEVIRTUAL:\n\t\t\t\t\t\t\t\tcase Opcodes.H_INVOKESTATIC:\n\t\t\t\t\t\t\t\tcase Opcodes.H_INVOKEINTERFACE:\n\t\t\t\t\t\t\t\t\tvisitMethodInsn(handle.getTag(), handle.getOwner(), handle.getName(), handle.getDesc(), handle.isInterface());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsuper.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);\n\t}\n\n\t/**\n\t * Called from the {@link MethodVisitor} in {@link #visit(JvmClassInfo)} when a method is visited.\n\t * <p>\n\t * This method ensures that {@link #unresolvedReferences unresolved references} are marked as resolved\n\t * and the {@link MethodVertex} model is updated when the given method details match a previously\n\t * unresolved declaration.\n\t *\n\t * @param owner\n\t * \t\tOwner type of the previously unresolved method call.\n\t * @param name\n\t * \t\tPreviously unresolved method name.\n\t * @param descriptor\n\t * \t\tPreviously unresolved method descriptor.\n\t * @param methodVertex\n\t * \t\tVertex for the previously unresolved method.\n\t */\n\tprivate void linkedResolvedCalls(@Nonnull JvmClassInfo owner,\n\t                                 @Nonnull String name,\n\t                                 @Nonnull String descriptor,\n\t                                 @Nonnull MutableMethodVertex methodVertex) {\n\t\t// Remove the method from the unresolved declarations map, since it is now resolved.\n\t\tunresolvedDeclarations.remove(owner.getName(), methodVertex.getMethod());\n\n\t\t// Skip if the unresolved references map does not contain the given class\n\t\t// or if the set of unresolved calls to this class is empty.\n\t\tString ownerName = owner.getName();\n\t\tCollection<CallingContext> unresolvedCallsToThisClass = unresolvedReferences.getIfPresent(ownerName);\n\t\tif (unresolvedCallsToThisClass.isEmpty())\n\t\t\treturn;\n\n\t\t// For each calling context that refers to this class, resolve the reference to this method (method params).\n\t\tfor (CallingContext context : unresolvedCallsToThisClass) {\n\t\t\tMethodRef callingMethod = context.callingMethod();\n\t\t\tClassMethodsContainer callingContainer = getClassMethodsContainer(context.callingClass());\n\t\t\tMutableMethodVertex callingVertex = (MutableMethodVertex) callingContainer.getVertex(callingMethod.name(), callingMethod.desc());\n\t\t\tif (callingVertex == null)\n\t\t\t\tcontinue;\n\t\t\tonMethodCalled(context.callingClass(), callingVertex, context.opcode(), ownerName, name, descriptor, context.itf());\n\t\t}\n\t}\n\n\t/**\n\t * Replace any unresolved calling contexts that reference an older instance (by name)\n\t * with contexts that reference the freshly provided class instance.\n\t *\n\t * @param updated\n\t * \t\tUpdated class to normalize calling contexts for.\n\t */\n\tprivate void normalizeCallingContexts(@Nonnull JvmClassInfo updated) {\n\t\tString updatedName = updated.getName();\n\n\t\t// Iterate over all owners in unresolvedReferences (callee class names) and update contexts\n\t\tfor (String owner : new HashSet<>(unresolvedReferences.keySet())) {\n\t\t\t// Skip if the owner does not match the updated class's name.\n\t\t\tCollection<CallingContext> contexts = unresolvedReferences.getIfPresent(owner);\n\t\t\tif (contexts.isEmpty())\n\t\t\t\tcontinue;\n\n\t\t\t// For each calling context, if the calling class name matches the updated class's name,\n\t\t\t// replace the context with a new one that references the fresh class instance.\n\t\t\tSet<CallingContext> snapshot = new HashSet<>(contexts);\n\t\t\tfor (CallingContext ctx : snapshot) {\n\t\t\t\tJvmClassInfo calling = ctx.callingClass();\n\t\t\t\tif (calling != updated && updatedName.equals(calling.getName())) {\n\t\t\t\t\tif (contexts.remove(ctx)) {\n\t\t\t\t\t\tCallingContext newCtx = new CallingContext(updated, ctx.callingMethod(), ctx.opcode(), ctx.itf());\n\t\t\t\t\t\tunresolvedReferences.put(owner, newCtx);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If all contexts were removed, remove the owner from the map.\n\t\t\tif (contexts.isEmpty())\n\t\t\t\tunresolvedReferences.remove(owner);\n\t\t}\n\t}\n\n\t/**\n\t * Remove any unresolved calling contexts that reference the exact removed instance.\n\t *\n\t * @param removed\n\t * \t\tRemoved class to remove stale contexts for.\n\t */\n\tprivate void removeStaleCallingContextsForRemovedClass(@Nonnull JvmClassInfo removed) {\n\t\tfor (String owner : new HashSet<>(unresolvedReferences.keySet())) {\n\t\t\t// Skip if the owner does not match the removed class name.\n\t\t\tCollection<CallingContext> contexts = unresolvedReferences.getIfPresent(owner);\n\t\t\tif (contexts.isEmpty())\n\t\t\t\tcontinue;\n\n\t\t\tSet<CallingContext> snapshot = new HashSet<>(contexts);\n\t\t\tboolean changed = false;\n\t\t\tfor (CallingContext ctx : snapshot) {\n\t\t\t\tif (ctx.callingClass() == removed) {\n\t\t\t\t\tif (contexts.remove(ctx))\n\t\t\t\t\t\tchanged = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (contexts.isEmpty()) {\n\t\t\t\tunresolvedReferences.remove(owner);\n\t\t\t} else if (changed) {\n\t\t\t\t// nothing else required - contexts collection already updated\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Called from the {@link ClassReader} in {@link #visit(JvmClassInfo)}.\n\t * Links the given vertex to the remote {@link MethodVertex} of the resolved method call,\n\t * if resolution is a success.\n\t * <p>\n\t * When not successful, the call is recorded as an unresolved reference in two directions.\n\t * <ul>\n\t *     <li>{@link #unresolvedDeclarations Class names --> Missing method declarations}</li>\n\t *     <li>{@link #unresolvedReferences  Class names --> Methods that have calls to missing method declarations</li>\n\t * </ul>\n\t *\n\t * @param callingClass\n\t * \t\tThe class that defines the calling method.\n\t * @param callingVertex\n\t * \t\tThe method that is doing the call.\n\t * @param opcode\n\t * \t\tCall opcode.\n\t * @param owner\n\t * \t\tCall owner.\n\t * @param name\n\t * \t\tMethod call name.\n\t * @param descriptor\n\t * \t\tMethod call descriptor.\n\t * @param isInterface\n\t * \t\tMethod interface flag.\n\t */\n\tprivate void onMethodCalled(@Nonnull JvmClassInfo callingClass, @Nonnull MutableMethodVertex callingVertex,\n\t                            int opcode, @Nonnull String owner, @Nonnull String name,\n\t                            @Nonnull String descriptor, boolean isInterface) {\n\t\tMethodRef ref = new MethodRef(owner, name, descriptor);\n\n\t\t// Resolve the method\n\t\tResult<Resolution<JvmClassInfo, MethodMember>> resolutionResult = resolve(opcode, owner, name, descriptor, isInterface);\n\n\t\t// Handle result\n\t\tCallingContext callContext = new CallingContext(callingClass, callingVertex.getMethod(), opcode, isInterface);\n\t\tif (resolutionResult.isSuccess()) {\n\t\t\t// Extract vertex from resolution\n\t\t\tResolution<JvmClassInfo, MethodMember> resolution = resolutionResult.value();\n\t\t\tClassMethodsContainer resolvedClass = getClassMethodsContainer(resolution.owner().innerValue());\n\t\t\tMutableMethodVertex resolvedMethodCallVertex = (MutableMethodVertex) resolvedClass.getVertex(resolution.member().innerValue());\n\n\t\t\t// Link the vertices\n\t\t\tcallingVertex.getCalls().add(resolvedMethodCallVertex);\n\t\t\tresolvedMethodCallVertex.getCallers().add(callingVertex);\n\n\t\t\t// Remove tracked unresolved call if any exist\n\t\t\tCollection<MethodRef> unresolvedWithinOwner = unresolvedDeclarations.getIfPresent(owner);\n\t\t\tif (unresolvedWithinOwner.remove(ref)) {\n\t\t\t\tif (unresolvedWithinOwner.isEmpty()) unresolvedDeclarations.remove(owner);\n\t\t\t\tlogger.debugging(l -> l.info(\"Satisfy unresolved call {}\", ref));\n\t\t\t}\n\n\t\t\t// Remove tracking of unresolved declarations/references\n\t\t\tCollection<CallingContext> unresolvedRefsToOwner = unresolvedReferences.getIfPresent(owner);\n\t\t\tif (unresolvedRefsToOwner.remove(callContext)) {\n\t\t\t\tif (unresolvedRefsToOwner.isEmpty()) unresolvedReferences.remove(owner);\n\t\t\t\tlogger.debugging(l -> l.info(\"Satisfy unresolved reference from {} to {}\", callContext.callingMethod(), ref));\n\t\t\t}\n\t\t} else {\n\t\t\tunresolvedDeclarations.put(owner, ref);\n\t\t\tunresolvedReferences.put(owner, callContext);\n\t\t\tlogger.debugging(l -> l.warn(\"Cannot resolve method: {} - {}\", ref, resolutionResult.error()));\n\t\t}\n\t}\n\n\t/**\n\t * @param opcode\n\t * \t\tMethod invoke opcode.\n\t * @param owner\n\t * \t\tDeclaring class of method.\n\t * @param name\n\t * \t\tName of method invoked.\n\t * @param descriptor\n\t * \t\tDescriptor of method invoked.\n\t * @param isInterface\n\t * \t\tInvoke interface flag.\n\t *\n\t * @return Resolution result of the method within the owner.\n\t * The result {@link Result#isError()} will be {@code true} when the {@code owner} could not be found.\n\t */\n\t@Nonnull\n\tpublic Result<Resolution<JvmClassInfo, MethodMember>> resolve(int opcode, @Nonnull String owner, @Nonnull String name,\n\t                                                              @Nonnull String descriptor, boolean isInterface) {\n\t\t// Skip if we cannot resolve owner\n\t\tJvmClassInfo ownerClass = lookup.apply(owner);\n\t\tif (ownerClass == null)\n\t\t\treturn Result.error(ResolutionError.NO_SUCH_METHOD);\n\n\t\tResult<Resolution<JvmClassInfo, MethodMember>> resolutionResult;\n\t\tLinkedClass linkedOwnerClass = linked(ownerClass);\n\t\tswitch (opcode) {\n\t\t\tcase Opcodes.H_INVOKESPECIAL:\n\t\t\tcase Opcodes.INVOKESPECIAL:\n\t\t\t\t// Invoke-Special is a direct call, so we do need to do resolving\n\t\t\t\tMemberInfo<MethodMember> method = linkedOwnerClass.getMethod(name, descriptor);\n\t\t\t\tif (method != null)\n\t\t\t\t\tresolutionResult = Result.ok(new Resolution<>(linkedOwnerClass, method, false));\n\t\t\t\telse\n\t\t\t\t\tresolutionResult = Result.error(ResolutionError.NO_SUCH_METHOD);\n\t\t\t\tbreak;\n\t\t\tcase Opcodes.H_INVOKEVIRTUAL:\n\t\t\tcase Opcodes.INVOKEVIRTUAL:\n\t\t\t\tresolutionResult = resolver.resolveVirtualMethod(linkedOwnerClass, name, descriptor);\n\t\t\t\tbreak;\n\t\t\tcase Opcodes.H_INVOKEINTERFACE:\n\t\t\tcase Opcodes.INVOKEINTERFACE:\n\t\t\t\tresolutionResult = resolver.resolveInterfaceMethod(linkedOwnerClass, name, descriptor);\n\t\t\t\tbreak;\n\t\t\tcase Opcodes.H_INVOKESTATIC:\n\t\t\tcase Opcodes.INVOKESTATIC:\n\t\t\t\tresolutionResult = resolver.resolveStaticMethod(linkedOwnerClass, name, descriptor);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new IllegalArgumentException(\"Invalid method opcode: \" + opcode);\n\t\t}\n\t\treturn resolutionResult;\n\t}\n\n\t@Override\n\tpublic void onAddLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library) {\n\t\t// Visit all library classes\n\t\tlibrary.jvmAllClassBundleStreamRecursive().forEach(bundle -> {\n\t\t\tfor (JvmClassInfo jvmClass : bundle.values())\n\t\t\t\tonNewClass(library, bundle, jvmClass);\n\t\t});\n\t}\n\n\t@Override\n\tpublic void onRemoveLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library) {\n\t\t// Remove all vertices from library\n\t\tlibrary.jvmAllClassBundleStreamRecursive().forEach(bundle -> {\n\t\t\tfor (JvmClassInfo jvmClass : bundle.values())\n\t\t\t\tonRemoveClass(library, bundle, jvmClass);\n\t\t});\n\t}\n\n\t@Override\n\tpublic void onNewClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls) {\n\t\tvisit(cls);\n\t}\n\n\t@Override\n\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo oldCls, @Nonnull JvmClassInfo newCls) {\n\t\tonRemoveClass(resource, bundle, oldCls);\n\t\tnormalizeCallingContexts(newCls);\n\t\tonNewClass(resource, bundle, newCls);\n\t}\n\n\t@Override\n\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls) {\n\t\tString clsName = cls.getName();\n\t\tClassMethodsContainer container = getClassMethodsContainer(cls);\n\t\tSet<MethodRef> unresolvedDeclarationsWithinOwner = unresolvedDeclarations.get(clsName);\n\t\tfor (MethodVertex vertex : container.getVertices()) {\n\t\t\tMethodRef ref = vertex.getMethod();\n\t\t\tif (vertex instanceof MutableMethodVertex mutableMethodVertex) {\n\t\t\t\tMethodMember resolvedMethod = vertex.getResolvedMethod();\n\t\t\t\tif (resolvedMethod != null) {\n\t\t\t\t\t// Mark any inbound calls to the removed method as an unresolved references.\n\t\t\t\t\tCollection<MethodVertex> callers = mutableMethodVertex.getCallers();\n\t\t\t\t\tfor (MethodVertex caller : callers) {\n\t\t\t\t\t\tMethodRef callerRef = caller.getMethod();\n\t\t\t\t\t\tString callerName = callerRef.owner();\n\t\t\t\t\t\tClassPathNode path = workspace.findClass(callerName);\n\t\t\t\t\t\tif (path != null) {\n\t\t\t\t\t\t\t// All callers to the removed method should be marked as an unresolved reference.\n\t\t\t\t\t\t\tint op = AsmInsnUtil.getInvokeForMethod(resolvedMethod.getAccess());\n\t\t\t\t\t\t\tCallingContext context = new CallingContext(path.getValue().asJvmClass(), callerRef, op, false);\n\t\t\t\t\t\t\tunresolvedReferences.put(clsName, context);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// If the removed method is called, then we need to mark the method as an unresolved declaration.\n\t\t\t\t\tif (!callers.isEmpty())\n\t\t\t\t\t\tunresolvedDeclarations.put(clsName, ref);\n\t\t\t\t}\n\n\t\t\t\t// Prune connections to other vertices, since this method is now removed.\n\t\t\t\tmutableMethodVertex.prune();\n\n\t\t\t\t// Also mark the method as an unresolved declaration, since it is now removed.\n\t\t\t\tunresolvedDeclarationsWithinOwner.add(ref);\n\t\t\t} else {\n\t\t\t\tlogger.warn(\"Could not prune reference: {}\", ref);\n\t\t\t}\n\t\t}\n\n\t\t// Remove from maps\n\t\tclassToLinkerType.remove(cls);\n\t\tclassToMethodsContainer.remove(cls);\n\n\t\t// Remove any unresolved calling contexts that reference the removed instance (stale data)\n\t\tremoveStaleCallingContextsForRemovedClass(cls);\n\t}\n\n\t/**\n\t * @return Map of classes that could not be resolved, to method declarations observed being made to them.\n\t */\n\t@Nonnull\n\t@VisibleForTesting\n\tMultiMap<String, MethodRef, Set<MethodRef>> getUnresolvedDeclarations() {\n\t\treturn unresolvedDeclarations;\n\t}\n\n\t/**\n\t * Models the calling context to some method.\n\t *\n\t * @param callingClass\n\t * \t\tClass that defines the calling method.\n\t * @param callingMethod\n\t * \t\tThe calling method information.\n\t * @param opcode\n\t * \t\tThe outgoing call opcode.\n\t * @param itf\n\t * \t\tThe outgoing call {@code isInterface} flag.\n\t */\n\tprivate record CallingContext(@Nonnull JvmClassInfo callingClass, @Nonnull MethodRef callingMethod,\n\t                              int opcode, boolean itf) {}\n\n\t/**\n\t * Mutable impl of {@link MethodVertex}.\n\t */\n\tstatic class MutableMethodVertex implements MethodVertex {\n\t\tprivate final Set<MethodVertex> callers = Collections.synchronizedSet(new HashSet<>());\n\t\tprivate final Set<MethodVertex> calls = Collections.synchronizedSet(new HashSet<>());\n\t\tprivate final MethodRef method;\n\t\tprivate final MethodMember resolvedMethod;\n\n\t\tMutableMethodVertex(@Nonnull MethodRef method, @Nonnull MethodMember resolvedMethod) {\n\t\t\tthis.method = method;\n\t\t\tthis.resolvedMethod = resolvedMethod;\n\t\t}\n\n\t\t/**\n\t\t * Removes this method vertex from connected vertices.\n\t\t */\n\t\tprivate void prune() {\n\t\t\t// Remove this vertex as a caller from the methods we call\n\t\t\tfor (MethodVertex out : getCalls()) {\n\t\t\t\tif (out instanceof MutableMethodVertex) {\n\t\t\t\t\tout.getCallers().remove(this);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove this vertex as a destination from methods that call us\n\t\t\tfor (MethodVertex in : getCallers()) {\n\t\t\t\tif (in instanceof MutableMethodVertex) {\n\t\t\t\t\tin.getCalls().remove(this);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic MethodRef getMethod() {\n\t\t\treturn method;\n\t\t}\n\n\t\t@Nullable\n\t\t@Override\n\t\tpublic MethodMember getResolvedMethod() {\n\t\t\treturn resolvedMethod;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Collection<MethodVertex> getCallers() {\n\t\t\treturn callers;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Collection<MethodVertex> getCalls() {\n\t\t\treturn calls;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn method.toString();\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean equals(Object o) {\n\t\t\tif (this == o) return true;\n\t\t\tif (o == null || getClass() != o.getClass()) return false;\n\t\t\tMutableMethodVertex vertex = (MutableMethodVertex) o;\n\t\t\treturn method.equals(vertex.method);\n\t\t}\n\n\t\t@Override\n\t\tpublic int hashCode() {\n\t\t\treturn method.hashCode();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/callgraph/CallGraphConfig.java",
    "content": "package software.coley.recaf.services.callgraph;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link CallGraph}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class CallGraphConfig extends BasicConfigContainer implements ServiceConfig {\n\n\t@Inject\n\tpublic CallGraphConfig() {\n\t\tsuper(ConfigGroups.SERVICE_ANALYSIS, CallGraphService.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/callgraph/CallGraphService.java",
    "content": "package software.coley.recaf.services.callgraph;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.cdi.EagerInitialization;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.workspace.WorkspaceCloseListener;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.services.workspace.WorkspaceOpenListener;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.concurrent.CompletableFuture;\n\n/**\n * Service offering the creation of {@link CallGraph call graphs} for workspaces.\n *\n * @author Matt Coley\n * @see CallGraph\n */\n@EagerInitialization\n@ApplicationScoped\npublic class CallGraphService implements Service {\n\tpublic static final String SERVICE_ID = \"graph-calls\";\n\tprivate static final DebuggingLogger logger = Logging.get(CallGraphService.class);\n\tprivate final CallGraphConfig config;\n\tprivate CallGraph currentWorkspaceGraph;\n\n\t/**\n\t * @param workspaceManager\n\t * \t\tManager to register listeners for, in order to manage a shared graph for the current workspace.\n\t * @param config\n\t * \t\tGraphing config options.\n\t */\n\t@Inject\n\tpublic CallGraphService(@Nonnull WorkspaceManager workspaceManager, @Nonnull CallGraphConfig config) {\n\t\tthis.config = config;\n\n\t\tListenerHost host = new ListenerHost();\n\t\tworkspaceManager.addWorkspaceOpenListener(host);\n\t\tworkspaceManager.addWorkspaceCloseListener(host);\n\t}\n\n\t/**\n\t * Creates a new call graph for the given workspace.\n\t * Before you use the graph, you will need to call {@link CallGraph#initialize()}.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to pull classes from.\n\t *\n\t * @return New call graph model for the given workspace.\n\t */\n\t@Nonnull\n\tpublic CallGraph newCallGraph(@Nonnull Workspace workspace) {\n\t\treturn new CallGraph(workspace);\n\t}\n\n\t/**\n\t * @return Call graph model for the {@link WorkspaceManager#getCurrent() current workspace}\n\t * or {@code null} if no workspace is currently open.\n\t */\n\t@Nullable\n\tpublic CallGraph getCurrentWorkspaceCallGraph() {\n\t\tCallGraph graph = currentWorkspaceGraph;\n\n\t\t// Lazily initialize the graph so that we don't do a full graph immediately when the workspace is opened.\n\t\t// It will only initialize when a user needs to use it.\n\t\tif (!graph.isInitialized())\n\t\t\tCompletableFuture.runAsync(graph::initialize);\n\n\t\treturn graph;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic CallGraphConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\tprivate class ListenerHost implements WorkspaceOpenListener, WorkspaceCloseListener {\n\t\t@Override\n\t\tpublic void onWorkspaceOpened(@Nonnull Workspace workspace) {\n\t\t\tcurrentWorkspaceGraph = newCallGraph(workspace);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onWorkspaceClosed(@Nonnull Workspace workspace) {\n\t\t\tcurrentWorkspaceGraph = null;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/callgraph/ClassLookup.java",
    "content": "package software.coley.recaf.services.callgraph;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.function.Function;\n\n/**\n * Lookup for convenience.\n *\n * @author Matt Coley\n */\npublic class ClassLookup implements Function<String, JvmClassInfo> {\n\tprivate final Workspace workspace;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull from.\n\t */\n\tpublic ClassLookup(@Nonnull Workspace workspace) {\n\t\tthis.workspace = workspace;\n\t}\n\n\t@Override\n\tpublic JvmClassInfo apply(String name) {\n\t\tif (name == null) return null;\n\t\tClassPathNode classPath = workspace.findJvmClass(name);\n\t\tif (classPath == null) classPath = workspace.findLatestVersionedJvmClass(name);\n\t\tif (classPath == null) return null;\n\t\treturn classPath.getValue().asJvmClass();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/callgraph/ClassMethodsContainer.java",
    "content": "package software.coley.recaf.services.callgraph;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.Map;\n\n/**\n * Representation of a {@link JvmClassInfo} for {@link CallGraph}.\n * All the {@link MethodMember} values of the {@link JvmClassInfo#getMethods()} can be mapped to vertices via\n * {@link #getVertex(MethodMember)}.\n *\n * @author Matt Coley\n */\npublic class ClassMethodsContainer {\n\tprivate final Map<MethodMember, MethodVertex> methodVertices = Collections.synchronizedMap(new IdentityHashMap<>());\n\tprivate final JvmClassInfo jvmClass;\n\n\t/**\n\t * @param jvmClass\n\t * \t\tClass to wrap.\n\t */\n\tpublic ClassMethodsContainer(@Nonnull JvmClassInfo jvmClass) {\n\t\tthis.jvmClass = jvmClass;\n\t}\n\n\t/**\n\t * @return Wrapped {@link JvmClassInfo}.\n\t */\n\t@Nonnull\n\tpublic JvmClassInfo getJvmClass() {\n\t\treturn jvmClass;\n\t}\n\n\t/**\n\t * @return Collection of method vertices within this class.\n\t */\n\t@Nonnull\n\tpublic Collection<MethodVertex> getVertices() {\n\t\treturn methodVertices.values();\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tMethod name.\n\t * @param descriptor\n\t * \t\tMethod descriptor.\n\t *\n\t * @return Method vertex of the declared method.\n\t * {@code null} when no method by the given name/desc exist in this class.\n\t */\n\t@Nullable\n\tpublic MethodVertex getVertex(@Nonnull String name, @Nonnull String descriptor) {\n\t\tMethodMember member = jvmClass.getDeclaredMethod(name, descriptor);\n\t\tif (member == null) return null;\n\t\treturn getVertex(member);\n\t}\n\n\t/**\n\t * @param member\n\t * \t\tMember declaration from the associated {@link JvmClassInfo}.\n\t *\n\t * @return Method vertex of the declared method.\n\t *\n\t * @throws IllegalArgumentException\n\t * \t\tWhen the member declaration does not belong to the associated {@link JvmClassInfo}.\n\t */\n\t@Nonnull\n\tpublic MethodVertex getVertex(@Nonnull MethodMember member) throws IllegalArgumentException {\n\t\tif (member.getDeclaringClass() != jvmClass)\n\t\t\tthrow new IllegalArgumentException(\"Member does not belong to class from this vertex\");\n\t\treturn methodVertices.computeIfAbsent(member, m -> new CallGraph.MutableMethodVertex(\n\t\t\t\tnew MethodRef(jvmClass.getName(), member.getName(), member.getDescriptor()),\n\t\t\t\tmember)\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/callgraph/LinkedClass.java",
    "content": "package software.coley.recaf.services.callgraph;\n\nimport dev.xdark.jlinker.ClassInfo;\nimport dev.xdark.jlinker.MemberInfo;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.util.MemoizedFunctions;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\n\n/**\n * JLinker wrapper of {@link JvmClassInfo}.\n *\n * @author Matt Coley\n */\npublic class LinkedClass implements ClassInfo<JvmClassInfo> {\n\tprivate static final DebuggingLogger logger = Logging.get(LinkedClass.class);\n\tprivate final JvmClassInfo info;\n\tprivate final Function<String, LinkedClass> superClassLookup;\n\tprivate final Function<List<String>, List<ClassInfo<JvmClassInfo>>> interfacesLookup;\n\tprivate final BiFunction<String, String, MemberInfo<FieldMember>> fieldLookup;\n\tprivate final BiFunction<String, String, MemberInfo<MethodMember>> methodLookup;\n\n\tpublic LinkedClass(@Nonnull ClassLookup lookup, @Nonnull JvmClassInfo info) {\n\t\tthis.info = info;\n\n\t\tsuperClassLookup = MemoizedFunctions.memoize((String superName) -> {\n\t\t\tJvmClassInfo superClass = lookup.apply(superName);\n\t\t\tif (superClass == null) {\n\t\t\t\tlogger.debugging(l -> l.warn(\"Lookup failed for super-class: {}\", superName));\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn new LinkedClass(lookup, superClass);\n\t\t});\n\n\t\tinterfacesLookup = MemoizedFunctions.memoize((List<String> interfaces) -> {\n\t\t\tif (interfaces.isEmpty())\n\t\t\t\treturn Collections.emptyList();\n\t\t\tList<ClassInfo<JvmClassInfo>> values = new ArrayList<>();\n\t\t\tfor (String itf : interfaces) {\n\t\t\t\tJvmClassInfo itfInfo = lookup.apply(itf);\n\t\t\t\tif (itfInfo == null)\n\t\t\t\t\tlogger.debugging(l -> l.warn(\"Lookup failed for interface: {}\", itf));\n\t\t\t\telse\n\t\t\t\t\tvalues.add(new LinkedClass(lookup, itfInfo));\n\t\t\t}\n\t\t\treturn values;\n\t\t});\n\n\t\tfieldLookup = MemoizedFunctions.memoize((name, descriptor) -> {\n\t\t\tFieldMember declaredField = info.getDeclaredField(name, descriptor);\n\t\t\tif (declaredField == null) {\n\t\t\t\tlogger.debugging(l -> l.warn(\"Missing declared field: {} {}\", descriptor, name));\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn new MemberInfo<>() {\n\t\t\t\t@Override\n\t\t\t\tpublic FieldMember innerValue() {\n\t\t\t\t\treturn declaredField;\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic int accessFlags() {\n\t\t\t\t\treturn declaredField.getAccess();\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic boolean isPolymorphic() {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t};\n\t\t});\n\n\t\tmethodLookup = MemoizedFunctions.memoize((name, descriptor) -> {\n\t\t\tMethodMember declaredMethod = info.getDeclaredMethod(name, descriptor);\n\t\t\tif (declaredMethod == null) {\n\t\t\t\tlogger.debugging(l -> l.warn(\"Missing declared method: {}{}\", name, descriptor));\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn new MemberInfo<>() {\n\t\t\t\t@Override\n\t\t\t\tpublic MethodMember innerValue() {\n\t\t\t\t\treturn declaredMethod;\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic int accessFlags() {\n\t\t\t\t\treturn declaredMethod.getAccess();\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic boolean isPolymorphic() {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t};\n\t\t});\n\t}\n\n\t@Override\n\tpublic JvmClassInfo innerValue() {\n\t\treturn info;\n\t}\n\n\t@Override\n\tpublic int accessFlags() {\n\t\treturn info.getAccess();\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic ClassInfo<JvmClassInfo> superClass() {\n\t\tString superName = info.getSuperName();\n\t\tif (superName == null) return null;\n\t\treturn superClassLookup.apply(superName);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<ClassInfo<JvmClassInfo>> interfaces() {\n\t\treturn interfacesLookup.apply(info.getInterfaces());\n\t}\n\n\t@Override\n\tpublic MemberInfo<MethodMember> getMethod(String name, String descriptor) {\n\t\treturn methodLookup.apply(name, descriptor);\n\t}\n\n\t@Override\n\tpublic MemberInfo<FieldMember> getField(String name, String descriptor) {\n\t\treturn fieldLookup.apply(name, descriptor);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/callgraph/MethodRef.java",
    "content": "package software.coley.recaf.services.callgraph;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Basic method outline.\n *\n * @param owner\n * \t\tMethod reference owner.\n * @param name\n * \t\tMethod name.\n * @param desc\n * \t\tMethod descriptor.\n *\n * @author Amejonah\n */\npublic record MethodRef(@Nonnull String owner, @Nonnull String name, @Nonnull String desc) {}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/callgraph/MethodVertex.java",
    "content": "package software.coley.recaf.services.callgraph;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport java.util.Collection;\n\n/**\n * Outline of a method vertex.\n *\n * @author Amejonah\n */\npublic interface MethodVertex {\n\t/**\n\t * @return Basic method details.\n\t */\n\t@Nonnull\n\tMethodRef getMethod();\n\n\t/**\n\t * @return Declaration of the method. Only known if the method vertex has been resolved.\n\t */\n\t@Nullable\n\tMethodMember getResolvedMethod();\n\n\t/**\n\t * @return Methods that call this method.\n\t */\n\t@Nonnull\n\tCollection<MethodVertex> getCallers();\n\n\t/**\n\t * @return Methods this method calls.\n\t */\n\t@Nonnull\n\tCollection<MethodVertex> getCalls();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/comment/ClassComments.java",
    "content": "package software.coley.recaf.services.comment;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport java.time.Instant;\n\n/**\n * Outline of a container for comments for a class and its contents.\n *\n * @author Matt Coley\n */\npublic interface ClassComments {\n\t/**\n\t * @return Time of the first created comment.\n\t */\n\t@Nonnull\n\tInstant getCreationTime();\n\n\t/**\n\t * @return Time of the last comment update.\n\t */\n\t@Nonnull\n\tInstant getLastUpdatedTime();\n\n\t/**\n\t * @return {@code true} when any comments exist on this class, or any of its declared members.\n\t */\n\tboolean hasComments();\n\n\t/**\n\t * @return Class comment, if any.\n\t */\n\t@Nullable\n\tString getClassComment();\n\n\t/**\n\t * @param comment\n\t * \t\tNew class comment, or {@code null} to remove an existing comment.\n\t */\n\tvoid setClassComment(@Nullable String comment);\n\n\t/**\n\t * @param member\n\t * \t\tField to look up.\n\t *\n\t * @return Field comment, if any.\n\t */\n\t@Nullable\n\tdefault String getFieldComment(@Nonnull FieldMember member) {\n\t\treturn getFieldComment(member.getName(), member.getDescriptor());\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tField name.\n\t * @param descriptor\n\t * \t\tField descriptor.\n\t *\n\t * @return Field comment, if any.\n\t */\n\t@Nullable\n\tString getFieldComment(@Nonnull String name, @Nonnull String descriptor);\n\n\t/**\n\t * @param member\n\t * \t\tMethod to look up.\n\t *\n\t * @return Method comment, if any.\n\t */\n\t@Nullable\n\tdefault String getMethodComment(@Nonnull MethodMember member) {\n\t\treturn getMethodComment(member.getName(), member.getDescriptor());\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tMethod name.\n\t * @param descriptor\n\t * \t\tMethod descriptor.\n\t *\n\t * @return Method comment, if any.\n\t */\n\t@Nullable\n\tString getMethodComment(@Nonnull String name, @Nonnull String descriptor);\n\n\t/**\n\t * @param member\n\t * \t\tField to assign comment to.\n\t * @param comment\n\t * \t\tNew field comment, or {@code null} to remove an existing comment.\n\t */\n\tdefault void setFieldComment(@Nonnull FieldMember member, @Nullable String comment) {\n\t\tsetFieldComment(member.getName(), member.getDescriptor(), comment);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tField name.\n\t * @param descriptor\n\t * \t\tField descriptor.\n\t * @param comment\n\t * \t\tNew field comment, or {@code null} to remove an existing comment.\n\t */\n\tvoid setFieldComment(@Nonnull String name, @Nonnull String descriptor, @Nullable String comment);\n\n\t/**\n\t * @param member\n\t * \t\tMethod to assign comment to.\n\t * @param comment\n\t * \t\tNew method comment, or {@code null} to remove an existing comment.\n\t */\n\tdefault void setMethodComment(@Nonnull MethodMember member, @Nullable String comment) {\n\t\tsetMethodComment(member.getName(), member.getDescriptor(), comment);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tMethod name.\n\t * @param descriptor\n\t * \t\tMethod descriptor.\n\t * @param comment\n\t * \t\tNew method comment, or {@code null} to remove an existing comment.\n\t */\n\tvoid setMethodComment(@Nonnull String name, @Nonnull String descriptor, @Nullable String comment);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/comment/CommentContainerListener.java",
    "content": "package software.coley.recaf.services.comment;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.path.ClassPathNode;\n\n/**\n * Listener for receiving updates when comment containers are removed.\n *\n * @author Matt Coley\n */\npublic interface CommentContainerListener extends PrioritySortable {\n\t/**\n\t * @param path\n\t * \t\tPath to class.\n\t * @param comments\n\t * \t\tNewly made comment container for the class.\n\t */\n\tdefault void onClassContainerCreated(@Nonnull ClassPathNode path, @Nullable ClassComments comments) {}\n\n\t/**\n\t * @param path\n\t * \t\tPath to class.\n\t * @param comments\n\t * \t\tRemoved comment container of the class.\n\t */\n\tdefault void onClassContainerRemoved(@Nonnull ClassPathNode path, @Nullable ClassComments comments) {}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/comment/CommentInsertingVisitor.java",
    "content": "package software.coley.recaf.services.comment;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.MethodVisitor;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassPathNode;\n\n/**\n * A class visitor which inserts {@link CommentKey} annotations into the given class.\n *\n * @author Matt Coley\n */\npublic class CommentInsertingVisitor extends ClassVisitor {\n\tprivate final ClassComments comments;\n\tprivate final ClassPathNode classPath;\n\tprivate int insertions;\n\n\t/**\n\t * @param comments\n\t * \t\tComment container for the class.\n\t * @param classPath\n\t * \t\tPath to class in its containing workspace.\n\t * @param cv\n\t * \t\tDelegate class-visitor.\n\t */\n\tpublic CommentInsertingVisitor(@Nonnull ClassComments comments, @Nonnull ClassPathNode classPath, @Nullable ClassVisitor cv) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.comments = comments;\n\t\tthis.classPath = classPath;\n\t}\n\n\t/**\n\t * @return Number of inserted comment annotations.\n\t */\n\tpublic int getInsertions() {\n\t\treturn insertions;\n\t}\n\n\t@Override\n\tpublic void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {\n\t\tsuper.visit(version, access, name, signature, superName, interfaces);\n\n\t\t// Insert key for comment\n\t\tString comment = comments.getClassComment();\n\t\tif (comment != null) {\n\t\t\tCommentKey key = CommentKey.id(classPath);\n\t\t\tvisitAnnotation(key.annotationDescriptor(), true);\n\t\t\tinsertions++;\n\t\t}\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {\n\t\tFieldVisitor fv = super.visitField(access, name, descriptor, signature, value);\n\n\t\t// Insert key for comment\n\t\tString comment = comments.getFieldComment(name, descriptor);\n\t\tif (comment != null) {\n\t\t\tFieldMember field = classPath.getValue().getDeclaredField(name, descriptor);\n\t\t\tif (field != null) {\n\t\t\t\tCommentKey key = CommentKey.id(classPath.child(field));\n\t\t\t\tfv.visitAnnotation(key.annotationDescriptor(), true);\n\t\t\t\tinsertions++;\n\t\t\t}\n\t\t}\n\n\t\treturn fv;\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\tMethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);\n\n\t\t// Insert key for comment\n\t\tString comment = comments.getMethodComment(name, descriptor);\n\t\tif (comment != null) {\n\t\t\tMethodMember method = classPath.getValue().getDeclaredMethod(name, descriptor);\n\t\t\tif (method != null) {\n\t\t\t\tCommentKey key = CommentKey.id(classPath.child(method));\n\t\t\t\tmv = new CommentAppender(mv, key);\n\t\t\t\tinsertions++;\n\t\t\t}\n\t\t}\n\n\t\treturn mv;\n\t}\n\n\t/**\n\t * This class exists to facilitate optimal use of {@link ClassWriter#ClassWriter(ClassReader, int)}\n\t * <i>(See: {@code MethodWriter#canCopyMethodAttributes(ClassReader, boolean, boolean, int, int, int)})</i>.\n\t * <br>\n\t * Methods are copied as-is unless there is a custom subtype of {@link MethodVisitor} used, hence this existing.\n\t * Any methods that aren't having comments inserted then get copied over much faster than if they were rebuilt\n\t * from scratch.\n\t */\n\tprivate static class CommentAppender extends MethodVisitor {\n\t\tprivate final CommentKey key;\n\n\t\tprivate CommentAppender(@Nullable MethodVisitor mv, @Nonnull CommentKey key) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), mv);\n\t\t\tthis.key = key;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitEnd() {\n\t\t\tsuper.visitEnd();\n\t\t\tvisitAnnotation(key.annotationDescriptor(), true);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/comment/CommentKey.java",
    "content": "package software.coley.recaf.services.comment;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.properties.builtin.InputFilePathProperty;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.tutorial.TutorialWorkspaceResource;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceDirectoryResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceRemoteVmResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.nio.file.Path;\nimport java.util.Objects;\n\n/**\n * A key to uniquely identify a comment in a workspace.\n *\n * @param workspaceHash\n * \t\tHash of workspace.\n * @param pathHash\n * \t\tHash of path where the comment resides.\n *\n * @author Matt Coley\n */\npublic record CommentKey(int workspaceHash, int pathHash) {\n\t/** The current username of the system should be unique enough to use as a salt, and should always exist. */\n\tprivate static final int SALT = System.getProperty(\"user.name\", \"recaf\").hashCode();\n\n\t/**\n\t * @param path\n\t * \t\tPath in a workspace.\n\t *\n\t * @return Key for path location.\n\t */\n\t@Nonnull\n\tpublic static CommentKey id(@Nonnull PathNode<?> path) {\n\t\tint workspaceHash = hashWorkspace(Objects.requireNonNull(path.getValueOfType(Workspace.class), \"Path did not contain 'workspace' node\"));\n\t\tint pathHash = hashPath(path);\n\t\treturn new CommentKey(workspaceHash, pathHash);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace input.\n\t *\n\t * @return Hash of workspace.\n\t */\n\tpublic static int hashWorkspace(@Nonnull Workspace workspace) {\n\t\tString input = workspaceInput(workspace);\n\t\treturn input.hashCode();\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath input.\n\t *\n\t * @return Hash of path.\n\t */\n\tpublic static int hashPath(@Nonnull PathNode<?> path) {\n\t\t// We only want to hash portions of a path that we support.\n\t\t//  - Classes\n\t\t//  - Members (Fields/methods)\n\t\tint baseHash;\n\t\tif (path instanceof ClassPathNode classPath) {\n\t\t\tbaseHash = classPath.getValue().getName().hashCode();\n\t\t} else if (path instanceof ClassMemberPathNode memberPath) {\n\t\t\tClassMember member = memberPath.getValue();\n\t\t\tint hash = 31 * member.getName().hashCode() + member.getDescriptor().hashCode();\n\t\t\tPathNode<?> parent = path.getParent();\n\t\t\tif (parent != null)\n\t\t\t\thash = 31 * hashPath(parent) + hash;\n\t\t\tbaseHash = hash;\n\t\t} else {\n\t\t\t// Unsupported path content.\n\t\t\treturn -1;\n\t\t}\n\n\t\t// Because class and member names are known to the authors of the code, they could predict the hashes.\n\t\t// Thus, we salt the hash with information from the local system, which cannot realistically be predicted.\n\t\t// This is a bit overkill since all that they could do is insert the same annotation we'd normally create,\n\t\t// but because the actual comment data is stored externally they can't insert unintended comments.\n\t\treturn SALT + 31 * baseHash;\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace instance.\n\t *\n\t * @return Path of input from workspace.\n\t */\n\t@Nonnull\n\tstatic String workspaceInput(@Nonnull Workspace workspace) {\n\t\t// The workspace hashCode reflects the state of its contents (which updates as the user makes changes)\n\t\t// We want to generate a key based on some info that is consistent and re-producible over time.\n\t\t// Ideally we can get a sort of 'source' of the loaded content from each primary resource of the given workspace.\n\t\tWorkspaceResource resource = workspace.getPrimaryResource();\n\t\tswitch (resource) {\n\t\t\tcase WorkspaceFileResource fileResource -> {\n\t\t\t\t// Hash based on file path of input, or file name if full path not known.\n\t\t\t\tFileInfo fileInfo = fileResource.getFileInfo();\n\t\t\t\tPath path = InputFilePathProperty.get(fileInfo);\n\t\t\t\treturn path == null ? fileInfo.getName() : path.toString();\n\t\t\t}\n\t\t\tcase WorkspaceDirectoryResource directoryResource -> {\n\t\t\t\t// Hash based on directory path of input.\n\t\t\t\treturn directoryResource.getDirectoryPath().toString();\n\t\t\t}\n\t\t\tcase WorkspaceRemoteVmResource remoteVmResource -> {\n\t\t\t\t// Hash based on VM id, which is generally consistent when re-running the same application.\n\t\t\t\treturn remoteVmResource.getVirtualMachine().id();\n\t\t\t}\n\t\t\tcase TutorialWorkspaceResource tutorialResource -> {\n\t\t\t\t// Constant name for the tutorial.\n\t\t\t\treturn TutorialWorkspaceResource.COMMENT_KEY;\n\t\t\t}\n\t\t\tdefault -> {\n\t\t\t}\n\t\t}\n\n\t\t// Unsupported workspace content.\n\t\treturn \"unknown-workspace\";\n\t}\n\n\t/**\n\t * @return Annotation descriptor of this comment key.\n\t */\n\t@Nonnull\n\tpublic String annotationDescriptor() {\n\t\tString paddedWorkspaceHash = StringUtil.fillLeft(8, \"0\", Integer.toHexString(workspaceHash));\n\t\tString paddedPathHash = StringUtil.fillLeft(8, \"0\", Integer.toHexString(pathHash));\n\t\treturn \"LRecafComment_\" + paddedWorkspaceHash + '_' + paddedPathHash + ';';\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn annotationDescriptor();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/comment/CommentManager.java",
    "content": "package software.coley.recaf.services.comment;\n\nimport com.google.gson.Gson;\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.TypeAdapterFactory;\nimport com.google.gson.reflect.TypeToken;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.annotation.PreDestroy;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.cdi.EagerInitialization;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.decompile.DecompilerManager;\nimport software.coley.recaf.services.decompile.filter.JvmBytecodeFilter;\nimport software.coley.recaf.services.decompile.filter.OutputTextFilter;\nimport software.coley.recaf.services.file.RecafDirectoriesConfig;\nimport software.coley.recaf.services.json.GsonProvider;\nimport software.coley.recaf.services.mapping.BasicMappingsRemapper;\nimport software.coley.recaf.services.mapping.MappingApplicationListener;\nimport software.coley.recaf.services.mapping.MappingListeners;\nimport software.coley.recaf.services.mapping.MappingResults;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.tutorial.TutorialWorkspaceResource;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.TestEnvironment;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * Manager for comment tracking on {@link ClassInfo} and {@link ClassMember} content in workspaces.\n *\n * @author Matt Coley\n */\n@EagerInitialization // We need to eagerly init so that we can register hooks in decompilation\n@ApplicationScoped\npublic class CommentManager implements Service, CommentUpdateListener, CommentContainerListener {\n\tpublic static final String SERVICE_ID = \"comments\";\n\tprivate static final Logger logger = Logging.get(CommentManager.class);\n\t/** Map of workspace comment impls used to fire off listener calls, delegates to persist map entries */\n\tprivate final Map<String, DelegatingWorkspaceComments> delegatingMap = new ConcurrentHashMap<>();\n\t/** Map of workspace comment impls modeling only data. Used for persistence. */\n\tprivate final Map<String, PersistWorkspaceComments> persistMap = new ConcurrentHashMap<>();\n\tprivate final List<CommentUpdateListener> commentUpdateListeners = new CopyOnWriteArrayList<>();\n\tprivate final List<CommentContainerListener> commentContainerListeners = new CopyOnWriteArrayList<>();\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final RecafDirectoriesConfig directoriesConfig;\n\tprivate final CommentManagerConfig config;\n\tprivate final GsonProvider gsonProvider;\n\n\t@Inject\n\tpublic CommentManager(@Nonnull DecompilerManager decompilerManager, @Nonnull WorkspaceManager workspaceManager,\n\t                      @Nonnull MappingListeners mappingListeners, @Nonnull GsonProvider gsonProvider,\n\t                      @Nonnull RecafDirectoriesConfig directoriesConfig, @Nonnull CommentManagerConfig config) {\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.gsonProvider = gsonProvider;\n\t\tthis.directoriesConfig = directoriesConfig;\n\t\tthis.config = config;\n\n\t\t// Register input filter to insert comment identifier annotations.\n\t\tJvmBytecodeFilter keyInsertingFilter = new JvmBytecodeFilter() {\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic byte[] filter(@Nonnull Workspace workspace, @Nonnull JvmClassInfo initialClassInfo, @Nonnull byte[] bytecode) {\n\t\t\t\t// Skip if comment insertion is disabled.\n\t\t\t\tif (!config.getEnableCommentDisplay().hasValue())\n\t\t\t\t\treturn bytecode;\n\n\t\t\t\t// Skip if there are no comments in the workspace.\n\t\t\t\tWorkspaceComments comments = getWorkspaceComments(workspace);\n\t\t\t\tif (comments == null)\n\t\t\t\t\treturn bytecode;\n\n\t\t\t\t// Skip if the class is not found in the workspace.\n\t\t\t\tClassPathNode classPath = workspace.findClass(initialClassInfo.getName());\n\t\t\t\tif (classPath == null)\n\t\t\t\t\treturn bytecode;\n\n\t\t\t\t// Skip if there are no comments for the class.\n\t\t\t\tClassComments classComments = comments.getClassComments(classPath);\n\t\t\t\tif (classComments == null)\n\t\t\t\t\treturn bytecode;\n\n\t\t\t\t// Adapt with comment annotations.\n\t\t\t\tClassReader reader = new ClassReader(bytecode);\n\t\t\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\t\t\tCommentInsertingVisitor inserter = new CommentInsertingVisitor(classComments, classPath, writer);\n\t\t\t\treader.accept(inserter, initialClassInfo.getClassReaderFlags());\n\t\t\t\tif (inserter.getInsertions() > 0)\n\t\t\t\t\treturn writer.toByteArray();\n\t\t\t\treturn bytecode;\n\t\t\t}\n\t\t};\n\t\tOutputTextFilter keyReplacementFilter = new OutputTextFilter() {\n\t\t\tprivate static final String KEY = \"@RecafComment_\";\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic String filter(@Nonnull Workspace workspace, @Nonnull ClassInfo classInfo, @Nonnull String code) {\n\t\t\t\tint codeLength = code.length();\n\t\t\t\tint keyLength = KEY.length();\n\t\t\t\tint i = codeLength;\n\n\t\t\t\t// Get class comments container if it exists.\n\t\t\t\tClassPathNode classPath = workspace.findClass(classInfo.getName());\n\t\t\t\tif (classPath == null)\n\t\t\t\t\treturn code;\n\t\t\t\tWorkspaceComments comments = persistMap.get(CommentKey.workspaceInput(workspace));\n\t\t\t\tif (comments == null)\n\t\t\t\t\treturn code;\n\t\t\t\tClassComments classComments = comments.getClassComments(classPath);\n\t\t\t\tif (classComments == null)\n\t\t\t\t\treturn code;\n\n\t\t\t\tdo {\n\t\t\t\t\tint commentIndex = code.lastIndexOf(KEY, i);\n\t\t\t\t\tif (commentIndex < 0)\n\t\t\t\t\t\tbreak;\n\t\t\t\t\ti = commentIndex - 1; // Move backwards\n\n\t\t\t\t\t// Extract path key text from annotation name.\n\t\t\t\t\tint keyValueStart = commentIndex + keyLength;\n\t\t\t\t\tint endWorkspaceKey = Math.min(codeLength, keyValueStart + 8);\n\t\t\t\t\tint endPathKey = Math.min(codeLength, keyValueStart + 17);\n\t\t\t\t\tString pathKeyText = code.substring(endWorkspaceKey + 1, endPathKey);\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// The values are integer hashCodes converted to unsigned hex.\n\t\t\t\t\t\t// Casting back will put em back to the expected value.\n\t\t\t\t\t\t//  Initial hash: -1845811502\n\t\t\t\t\t\t//  Unsigned:      2449155794 (out of int bounds)\n\t\t\t\t\t\t//  Casting will return to the original value.\n\t\t\t\t\t\tint pathKey = (int) Long.parseLong(pathKeyText, 16);\n\n\t\t\t\t\t\t// Lookup what path in the class correlates to the path-key.\n\t\t\t\t\t\tString comment = null;\n\t\t\t\t\t\tint pathHash = CommentKey.hashPath(classPath);\n\t\t\t\t\t\tif (pathHash == pathKey) {\n\t\t\t\t\t\t\tcomment = classComments.getClassComment();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfor (FieldMember field : classInfo.getFields()) {\n\t\t\t\t\t\t\t\tpathHash = CommentKey.hashPath(classPath.child(field));\n\t\t\t\t\t\t\t\tif (pathHash == pathKey) {\n\t\t\t\t\t\t\t\t\tcomment = classComments.getFieldComment(field);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (comment == null) {\n\t\t\t\t\t\t\t\tfor (MethodMember method : classInfo.getMethods()) {\n\t\t\t\t\t\t\t\t\tpathHash = CommentKey.hashPath(classPath.child(method));\n\t\t\t\t\t\t\t\t\tif (pathHash == pathKey) {\n\t\t\t\t\t\t\t\t\t\tcomment = classComments.getMethodComment(method);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Replace the annotation.\n\t\t\t\t\t\tString replacement;\n\t\t\t\t\t\tif (comment == null) {\n\t\t\t\t\t\t\treplacement = \"\";\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tint wordWrapLimit = config.getWordWrappingLimit().getValue();\n\t\t\t\t\t\t\tif (comment.length() > wordWrapLimit)\n\t\t\t\t\t\t\t\tcomment = StringUtil.wordWrap(comment, wordWrapLimit);\n\n\t\t\t\t\t\t\tif (comment.contains(\"\\n\")) {\n\t\t\t\t\t\t\t\t// The indent starts after the '\\n' so that in cases where there isn't a '\\n' found we\n\t\t\t\t\t\t\t\t// will consistently insert one.\n\t\t\t\t\t\t\t\tint indentBegin = Math.max(0, code.lastIndexOf(\"\\n\", commentIndex) + 1);\n\t\t\t\t\t\t\t\tString indent = '\\n' + code.substring(indentBegin, commentIndex);\n\t\t\t\t\t\t\t\tStringBuilder sb = new StringBuilder(\"/**\");\n\t\t\t\t\t\t\t\tcomment.lines().forEach(line -> sb.append(indent).append(\" * \").append(line));\n\t\t\t\t\t\t\t\tsb.append(indent).append(\" */\");\n\t\t\t\t\t\t\t\treplacement = sb.toString();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treplacement = \"/** \" + comment + \" */\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcode = StringUtil.replaceRange(code, commentIndex, commentIndex + 31, replacement);\n\t\t\t\t\t} catch (NumberFormatException ignored) {\n\t\t\t\t\t\t// Bogus anno\n\t\t\t\t\t}\n\t\t\t\t} while (true);\n\t\t\t\treturn code;\n\t\t\t}\n\t\t};\n\t\tdecompilerManager.addJvmBytecodeFilter(keyInsertingFilter);\n\t\tdecompilerManager.addOutputTextFilter(keyReplacementFilter);\n\n\t\t// Restore any saved comments from disk.\n\t\tloadComments();\n\n\t\t// Register mapping listeners so that when types & members are renamed the comments are migrated.\n\t\tmappingListeners.addMappingApplicationListener(new MappingApplicationListener() {\n\t\t\t@Override\n\t\t\tpublic void onPreApply(@Nonnull Workspace workspace, @Nonnull MappingResults mappingResults) {\n\t\t\t\t// Mappings must apply to the current workspace.\n\t\t\t\tWorkspaceComments comments = getCurrentWorkspaceComments();\n\t\t\t\tif (comments == null)\n\t\t\t\t\treturn;\n\t\t\t\tif (workspaceManager.getCurrent() != workspace)\n\t\t\t\t\treturn;\n\n\t\t\t\t// There are comments that may need to be updated.\n\t\t\t\tMappings mappings = mappingResults.getMappings();\n\t\t\t\tBasicMappingsRemapper remapper = new BasicMappingsRemapper(mappings);\n\t\t\t\tmappingResults.streamPreToPostMappingPaths().forEach(pair -> {\n\t\t\t\t\tClassPathNode preMapped = pair.getLeft();\n\t\t\t\t\tClassPathNode postMapped = pair.getRight();\n\n\t\t\t\t\t// Skip if class has no comments.\n\t\t\t\t\tClassComments classComments = comments.getClassComments(preMapped);\n\t\t\t\t\tif (classComments == null)\n\t\t\t\t\t\treturn;\n\n\t\t\t\t\t// If the class name changed, we will want to migrate the comment to a new target container.\n\t\t\t\t\tClassInfo preClassInfo = preMapped.getValue();\n\t\t\t\t\tString preClassName = preClassInfo.getName();\n\t\t\t\t\tClassComments targetComments;\n\t\t\t\t\tboolean isNewClassContainer;\n\t\t\t\t\tif (!preClassName.equals(postMapped.getValue().getName())) {\n\t\t\t\t\t\tcomments.deleteClassComments(preMapped);\n\t\t\t\t\t\ttargetComments = comments.getOrCreateClassComments(postMapped);\n\t\t\t\t\t\ttargetComments.setClassComment(classComments.getClassComment());\n\t\t\t\t\t\tisNewClassContainer = true;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttargetComments = classComments;\n\t\t\t\t\t\tisNewClassContainer = false;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Migrate field comments.\n\t\t\t\t\tfor (FieldMember field : preClassInfo.getFields()) {\n\t\t\t\t\t\tString fieldName = field.getName();\n\t\t\t\t\t\tString fieldDesc = field.getDescriptor();\n\n\t\t\t\t\t\t// If the field is mapped, or the class is mapped, migrate to the new definition.\n\t\t\t\t\t\tString targetFieldName = mappings.getMappedFieldName(preClassName, fieldName, fieldDesc);\n\t\t\t\t\t\tif (targetFieldName == null && isNewClassContainer)\n\t\t\t\t\t\t\ttargetFieldName = fieldName;\n\t\t\t\t\t\tif (targetFieldName == null)\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\tString comment = classComments.getFieldComment(fieldName, fieldDesc);\n\t\t\t\t\t\tif (comment != null) {\n\t\t\t\t\t\t\t// Clear old comment, set in target container with up-to-date field declaration.\n\t\t\t\t\t\t\tif (!isNewClassContainer) classComments.setFieldComment(fieldName, fieldDesc, null);\n\t\t\t\t\t\t\ttargetComments.setFieldComment(targetFieldName, remapper.mapDesc(fieldDesc), comment);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Migrate method comments.\n\t\t\t\t\tfor (MethodMember method : preClassInfo.getMethods()) {\n\t\t\t\t\t\tString methodName = method.getName();\n\t\t\t\t\t\tString methodDesc = method.getDescriptor();\n\n\t\t\t\t\t\t// If the method mapped, or the class is mapped, migrate to the new definition.\n\t\t\t\t\t\tString targetMethodName = mappings.getMappedMethodName(preClassName, methodName, methodDesc);\n\t\t\t\t\t\tif (targetMethodName == null && isNewClassContainer)\n\t\t\t\t\t\t\ttargetMethodName = methodName;\n\t\t\t\t\t\tif (targetMethodName == null)\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\tString comment = classComments.getMethodComment(methodName, methodDesc);\n\t\t\t\t\t\tif (comment != null) {\n\t\t\t\t\t\t\t// Clear old comment, set in target container with up-to-date method declaration.\n\t\t\t\t\t\t\tif (!isNewClassContainer) classComments.setMethodComment(methodName, methodDesc, null);\n\t\t\t\t\t\t\ttargetComments.setMethodComment(targetMethodName, remapper.mapMethodDesc(methodDesc), comment);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onPostApply(@Nonnull Workspace workspace, @Nonnull MappingResults mappingResults) {\n\t\t\t\t// no-op\n\t\t\t}\n\t\t});\n\n\t\t// Register serialization support for the persistent comment models.\n\t\tgsonProvider.addTypeAdapterFactory(new TypeAdapterFactory() {\n\t\t\t@Override\n\t\t\t@SuppressWarnings(\"unchecked\")\n\t\t\tpublic <T> TypeAdapter<T> create(@Nonnull Gson gson, @Nonnull TypeToken<T> type) {\n\t\t\t\tif (WorkspaceComments.class.equals(type.getRawType()))\n\t\t\t\t\treturn (TypeAdapter<T>) gson.getAdapter(PersistWorkspaceComments.class);\n\t\t\t\telse if (ClassComments.class.equals(type.getRawType()))\n\t\t\t\t\treturn (TypeAdapter<T>) gson.getAdapter(PersistClassComments.class);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Loads comments from disk.\n\t */\n\tprivate void loadComments() {\n\t\t// Skip loading in test environment\n\t\tif (TestEnvironment.isTestEnv())\n\t\t\treturn;\n\n\t\ttry {\n\t\t\t// TODO: Its not ideal having all comments across all workspaces loaded at once\n\t\t\t//  - Not a big deal right now since I doubt most users will utilize this feature much\n\t\t\tGson gson = gsonProvider.getGson();\n\t\t\tPath commentsDirectory = getCommentsDirectory();\n\t\t\tPath store = commentsDirectory.resolve(\"comments.json\");\n\t\t\tif (Files.exists(store)) {\n\t\t\t\tString json = Files.readString(store);\n\t\t\t\tvar deserialized = gson.fromJson(json, new TypeToken<Map<String, PersistWorkspaceComments>>() {});\n\t\t\t\tpersistMap.putAll(deserialized);\n\t\t\t}\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Failed to load comments\", t);\n\t\t}\n\t}\n\n\t/**\n\t * Persists comments to disk when shutdown is observed.\n\t */\n\t@PreDestroy\n\tprivate void onShutdown() {\n\t\t// Skip persist in test environment.\n\t\tif (TestEnvironment.isTestEnv())\n\t\t\treturn;\n\n\t\t// Do not persist the tutorial workspace comments.\n\t\tpersistMap.remove(TutorialWorkspaceResource.COMMENT_KEY);\n\n\t\t// Remove entries that are empty.\n\t\tSet<String> empty = new HashSet<>();\n\t\tpersistMap.forEach((key, workspaceComments) -> {\n\t\t\tif (workspaceComments.classKeys().isEmpty()) {\n\t\t\t\tempty.add(key);\n\t\t\t} else {\n\t\t\t\tboolean isEmpty = true;\n\t\t\t\tfor (ClassComments classComments : workspaceComments) {\n\t\t\t\t\tif (classComments.hasComments()) {\n\t\t\t\t\t\tisEmpty = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (isEmpty)\n\t\t\t\t\tempty.add(key);\n\t\t\t}\n\t\t});\n\t\tempty.forEach(persistMap::remove);\n\n\t\ttry {\n\t\t\tGson gson = gsonProvider.getGson();\n\t\t\tString serialized = gson.toJson(persistMap);\n\t\t\tPath commentsDirectory = getCommentsDirectory();\n\t\t\tif (!Files.isDirectory(commentsDirectory))\n\t\t\t\tFiles.createDirectories(commentsDirectory);\n\t\t\tPath store = commentsDirectory.resolve(\"comments.json\");\n\t\t\tFiles.writeString(store, serialized);\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Failed to save comments\", t);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void onClassCommentUpdated(@Nonnull ClassPathNode path, @Nullable String comment) {\n\t\tUnchecked.checkedForEach(commentUpdateListeners, listener -> listener.onClassCommentUpdated(path, comment),\n\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when updating class comment\", t));\n\t}\n\n\t@Override\n\tpublic void onFieldCommentUpdated(@Nonnull ClassMemberPathNode path, @Nullable String comment) {\n\t\tUnchecked.checkedForEach(commentUpdateListeners, listener -> listener.onFieldCommentUpdated(path, comment),\n\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when updating field comment\", t));\n\t}\n\n\t@Override\n\tpublic void onMethodCommentUpdated(@Nonnull ClassMemberPathNode path, @Nullable String comment) {\n\t\tUnchecked.checkedForEach(commentUpdateListeners, listener -> listener.onMethodCommentUpdated(path, comment),\n\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when updating method comment\", t));\n\t}\n\n\t@Override\n\tpublic void onClassContainerCreated(@Nonnull ClassPathNode path, @Nullable ClassComments comments) {\n\t\tUnchecked.checkedForEach(commentContainerListeners, listener -> listener.onClassContainerCreated(path, comments),\n\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when creating class comment container\", t));\n\t}\n\n\t@Override\n\tpublic void onClassContainerRemoved(@Nonnull ClassPathNode path, @Nullable ClassComments comments) {\n\t\tUnchecked.checkedForEach(commentContainerListeners, listener -> listener.onClassContainerRemoved(path, comments),\n\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when removing class comment container\", t));\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to check for comments.\n\t *\n\t * @return Comments container for the given workspace, creating one if none existed.\n\t */\n\t@Nonnull\n\tpublic WorkspaceComments getOrCreateWorkspaceComments(@Nonnull Workspace workspace) {\n\t\tWorkspaceComments comments = getWorkspaceComments(workspace);\n\t\tif (comments == null) {\n\t\t\t// No existing comments found, lets create them.\n\t\t\t// - One entry for persistence\n\t\t\t// - One entry for listener callbacks, delegating to the persist model\n\t\t\tString input = CommentKey.workspaceInput(workspace);\n\t\t\tPersistWorkspaceComments persistComments = persistMap.computeIfAbsent(input, i -> new PersistWorkspaceComments());\n\t\t\tDelegatingWorkspaceComments delegatingComments = newDelegatingWorkspaceComments(workspace, persistComments);\n\t\t\tdelegatingMap.put(input, delegatingComments);\n\n\t\t\t// We want to yield the delegating model for listener support.\n\t\t\tcomments = delegatingComments;\n\t\t}\n\t\treturn comments;\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to check for comments.\n\t *\n\t * @return Comments container for the given workspace, if any comments exist.\n\t * If there are no comments, then {@code null}.\n\t */\n\t@Nullable\n\tpublic WorkspaceComments getWorkspaceComments(@Nonnull Workspace workspace) {\n\t\tString input = CommentKey.workspaceInput(workspace);\n\t\tPersistWorkspaceComments persistComments = persistMap.get(input);\n\t\tif (persistComments == null)\n\t\t\treturn null; // No persist model, so there are no comments.\n\n\t\t// Wrap the persist model with a delegating model for listener support.\n\t\treturn delegatingMap.computeIfAbsent(input, i -> newDelegatingWorkspaceComments(workspace, persistComments));\n\t}\n\n\t/**\n\t * @return Comments container for the current workspace.\n\t * If there is no current workspace, then {@code null}.\n\t */\n\t@Nullable\n\tpublic WorkspaceComments getCurrentWorkspaceComments() {\n\t\tif (!workspaceManager.hasCurrentWorkspace())\n\t\t\treturn null;\n\t\treturn getOrCreateWorkspaceComments(workspaceManager.getCurrent());\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to remove comments of.\n\t *\n\t * @return {@code true} if a workspace was found and removed.\n\t * {@code false} if no comments existed for the workspace.\n\t */\n\tpublic boolean removeWorkspaceComments(@Nonnull Workspace workspace) {\n\t\tString input = CommentKey.workspaceInput(workspace);\n\t\treturn persistMap.remove(input) != null || delegatingMap.remove(input) != null;\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tpublic void addCommentListener(@Nonnull CommentUpdateListener listener) {\n\t\tPrioritySortable.add(commentUpdateListeners, listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tpublic void removeCommentListener(@Nonnull CommentUpdateListener listener) {\n\t\tcommentUpdateListeners.remove(listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tpublic void addCommentContainerListener(@Nonnull CommentContainerListener listener) {\n\t\tPrioritySortable.add(commentContainerListeners, listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tpublic void removeCommentContainerListener(@Nonnull CommentContainerListener listener) {\n\t\tcommentContainerListeners.remove(listener);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic CommentManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\t@Nonnull\n\tprivate Path getCommentsDirectory() {\n\t\treturn directoriesConfig.getBaseDirectory().resolve(\"comments\");\n\t}\n\n\t@Nonnull\n\tprivate DelegatingWorkspaceComments newDelegatingWorkspaceComments(@Nonnull Workspace workspace,\n\t                                                                   @Nonnull PersistWorkspaceComments persistComments) {\n\t\tDelegatingWorkspaceComments delegatingComments = new DelegatingWorkspaceComments(this, persistComments);\n\n\t\t// Initialize delegate class comment models for entries in the persist model.\n\t\tfor (String classKey : persistComments.classKeys()) {\n\t\t\tClassPathNode classPath = workspace.findClass(classKey);\n\t\t\tif (classPath != null)\n\t\t\t\tdelegatingComments.getClassComments(classPath);\n\t\t}\n\n\t\treturn delegatingComments;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/comment/CommentManagerConfig.java",
    "content": "package software.coley.recaf.services.comment;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\nimport software.coley.recaf.services.decompile.Decompiler;\n\n/**\n * Config for {@link CommentManager}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class CommentManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\tprivate final ObservableBoolean enableCommentDisplay = new ObservableBoolean(true);\n\tprivate final ObservableInteger wordWrappingLimit = new ObservableInteger(100);\n\n\t@Inject\n\tpublic CommentManagerConfig() {\n\t\tsuper(ConfigGroups.SERVICE_ANALYSIS, CommentManager.SERVICE_ID + CONFIG_SUFFIX);\n\t\t// Add values\n\t\taddValue(new BasicConfigValue<>(\"enable-display\", boolean.class, enableCommentDisplay));\n\t\taddValue(new BasicConfigValue<>(\"word-wrapping-limit\", int.class, wordWrappingLimit));\n\t}\n\n\t/**\n\t * @return {@code true} when comments should be enabled in {@link Decompiler} output.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getEnableCommentDisplay() {\n\t\treturn enableCommentDisplay;\n\t}\n\n\t/**\n\t * @return Number of characters to allow before line wrapping a comment.\n\t */\n\t@Nonnull\n\tpublic ObservableInteger getWordWrappingLimit() {\n\t\treturn wordWrappingLimit;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/comment/CommentUpdateListener.java",
    "content": "package software.coley.recaf.services.comment;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\n\n/**\n * Listener for receiving updates when comments are added to classes, fields, and methods.\n *\n * @author Matt Coley\n */\npublic interface CommentUpdateListener extends PrioritySortable {\n\t/**\n\t * @param path\n\t * \t\tPath to class commented.\n\t * @param comment\n\t * \t\tContent of comment for the class. Can be {@code null} to denote removal of a comment.\n\t */\n\tdefault void onClassCommentUpdated(@Nonnull ClassPathNode path, @Nullable String comment) {}\n\n\t/**\n\t * @param path\n\t * \t\tPath to field commented.\n\t * @param comment\n\t * \t\tContent of comment for the field. Can be {@code null} to denote removal of a comment.\n\t */\n\tdefault void onFieldCommentUpdated(@Nonnull ClassMemberPathNode path, @Nullable String comment) {}\n\n\t/**\n\t * @param path\n\t * \t\tPath to method commented.\n\t * @param comment\n\t * \t\tContent of comment for the method. Can be {@code null} to denote removal of a comment.\n\t */\n\tdefault void onMethodCommentUpdated(@Nonnull ClassMemberPathNode path, @Nullable String comment) {}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/comment/DelegatingClassComments.java",
    "content": "package software.coley.recaf.services.comment;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassPathNode;\n\nimport java.time.Instant;\n\n/**\n * Delegating class comment container implementation.\n *\n * @author Matt Coley\n */\npublic class DelegatingClassComments implements ClassComments {\n\tprivate final CommentUpdateListener listenerCallback;\n\tprivate final ClassPathNode path;\n\tprivate final ClassComments delegate;\n\n\t/**\n\t * New delegating comments container, which passes data to the persistence container,\n\t * and invokes {@link CommentUpdateListener} methods when comment data is updated.\n\t *\n\t * @param path\n\t * \t\tPath to the class this container is for.\n\t * @param listenerCallback\n\t * \t\tThe listener that delegates to other listeners registered in the {@link CommentManager}.\n\t * @param delegate\n\t * \t\tThe {@link ClassComments} implementation we actually want to store data in.\n\t */\n\tpublic DelegatingClassComments(@Nonnull ClassPathNode path, @Nonnull CommentUpdateListener listenerCallback, @Nonnull ClassComments delegate) {\n\t\tthis.listenerCallback = listenerCallback;\n\t\tthis.path = path;\n\t\tthis.delegate = delegate;\n\t}\n\n\t/**\n\t * @return Path to the class this container is for.\n\t */\n\t@Nonnull\n\tpublic ClassPathNode getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Instant getCreationTime() {\n\t\treturn delegate.getCreationTime();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Instant getLastUpdatedTime() {\n\t\treturn delegate.getLastUpdatedTime();\n\t}\n\n\t@Override\n\tpublic boolean hasComments() {\n\t\treturn delegate.hasComments();\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getClassComment() {\n\t\treturn delegate.getClassComment();\n\t}\n\n\t@Override\n\tpublic void setClassComment(@Nullable String comment) {\n\t\tdelegate.setClassComment(comment);\n\n\t\tlistenerCallback.onClassCommentUpdated(path, comment);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getFieldComment(@Nonnull String name, @Nonnull String descriptor) {\n\t\treturn delegate.getFieldComment(name, descriptor);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMethodComment(@Nonnull String name, @Nonnull String descriptor) {\n\t\treturn delegate.getMethodComment(name, descriptor);\n\t}\n\n\t@Override\n\tpublic void setFieldComment(@Nonnull String name, @Nonnull String descriptor, @Nullable String comment) {\n\t\tdelegate.setFieldComment(name, descriptor, comment);\n\n\t\tFieldMember field = path.getValue().getDeclaredField(name, descriptor);\n\t\tif (field != null)\n\t\t\tlistenerCallback.onFieldCommentUpdated(path.child(field), comment);\n\t}\n\n\t@Override\n\tpublic void setMethodComment(@Nonnull String name, @Nonnull String descriptor, @Nullable String comment) {\n\t\tdelegate.setMethodComment(name, descriptor, comment);\n\n\t\tMethodMember method = path.getValue().getDeclaredMethod(name, descriptor);\n\t\tif (method != null)\n\t\t\tlistenerCallback.onMethodCommentUpdated(path.child(method), comment);\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null) return false;\n\t\treturn delegate.equals(o);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn delegate.hashCode();\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn delegate.toString();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/comment/DelegatingWorkspaceComments.java",
    "content": "package software.coley.recaf.services.comment;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.path.ClassPathNode;\n\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Delegating workspace comment container implementation.\n *\n * @author Matt Coley\n */\npublic class DelegatingWorkspaceComments implements WorkspaceComments {\n\tprivate final Map<String, DelegatingClassComments> classCommentsMap = new ConcurrentHashMap<>();\n\tprivate final CommentManager listenerCallback;\n\tprivate final PersistWorkspaceComments delegate;\n\n\t/**\n\t * New delegating comments container, which passes data to the persistence container, and manages\n\t * the creation of {@link DelegatingClassComments} for backing {@link PersistClassComments} instances.\n\t *\n\t * @param listenerCallback\n\t * \t\tThe listener that delegates to other listeners registered in the {@link CommentManager}.\n\t * @param delegate\n\t * \t\tThe {@link WorkspaceComments} implementation we actually want to store data in.\n\t */\n\tpublic DelegatingWorkspaceComments(@Nonnull CommentManager listenerCallback, @Nonnull PersistWorkspaceComments delegate) {\n\t\tthis.listenerCallback = listenerCallback;\n\t\tthis.delegate = delegate;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassComments getOrCreateClassComments(@Nonnull ClassPathNode classPath) {\n\t\t// We will be delegating to the persist model when we lazily create our own delegating class comments here.\n\t\t// If the data does not exist upstream in the persist model, it will be made.\n\t\treturn classCommentsMap.computeIfAbsent(classPath.getValue().getName(),\n\t\t\t\tname -> {\n\t\t\t\t\tDelegatingClassComments newComments = new DelegatingClassComments(classPath, listenerCallback, delegate.getOrCreateClassComments(classPath));\n\t\t\t\t\tlistenerCallback.onClassContainerCreated(classPath, newComments);\n\t\t\t\t\treturn newComments;\n\t\t\t\t});\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic ClassComments getClassComments(@Nonnull ClassPathNode classPath) {\n\t\t// Check if the persist model has comments. If not, we do not need to make a wrapper.\n\t\tClassComments delegateClassComments = delegate.getClassComments(classPath);\n\t\tif (delegateClassComments == null)\n\t\t\treturn null;\n\n\t\t// Create a wrapper if one does not exist for the persist class comments model.\n\t\treturn classCommentsMap.computeIfAbsent(classPath.getValue().getName(),\n\t\t\t\tname -> {\n\t\t\t\t\tDelegatingClassComments newComments = new DelegatingClassComments(classPath, listenerCallback, delegateClassComments);\n\t\t\t\t\tlistenerCallback.onClassContainerCreated(classPath, newComments);\n\t\t\t\t\treturn newComments;\n\t\t\t\t});\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic ClassComments deleteClassComments(@Nonnull ClassPathNode classPath) {\n\t\tClassComments container = delegate.deleteClassComments(classPath);\n\t\tif (container != null)\n\t\t\tlistenerCallback.onClassContainerRemoved(classPath, container);\n\t\treturn container;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Iterator<ClassComments> iterator() {\n\t\treturn Unchecked.cast(classCommentsMap.values().iterator());\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null) return false;\n\t\treturn delegate.equals(o);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn delegate.hashCode();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/comment/PersistClassComments.java",
    "content": "package software.coley.recaf.services.comment;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport java.time.Instant;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Basic class comment container for persistence.\n *\n * @author Matt Coley\n */\npublic class PersistClassComments implements ClassComments {\n\tprivate final Map<String, String> fieldComments = new ConcurrentHashMap<>();\n\tprivate final Map<String, String> methodComments = new ConcurrentHashMap<>();\n\tprivate final Instant creationTime = Instant.now();\n\tprivate Instant lastUpdatedTime = creationTime;\n\tprivate String classComment;\n\n\t@Nonnull\n\t@Override\n\tpublic Instant getCreationTime() {\n\t\treturn creationTime;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Instant getLastUpdatedTime() {\n\t\treturn lastUpdatedTime;\n\t}\n\n\t@Override\n\tpublic boolean hasComments() {\n\t\treturn classComment != null\n\t\t\t\t|| fieldComments.values().stream().anyMatch(s -> s != null && !s.isBlank())\n\t\t\t\t|| methodComments.values().stream().anyMatch(s -> s != null && !s.isBlank());\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getClassComment() {\n\t\treturn classComment;\n\t}\n\n\t@Override\n\tpublic void setClassComment(@Nullable String comment) {\n\t\tclassComment = comment;\n\t\tlastUpdatedTime = Instant.now();\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getFieldComment(@Nonnull String name, @Nonnull String descriptor) {\n\t\treturn fieldComments.get(name + ' ' + descriptor);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMethodComment(@Nonnull String name, @Nonnull String descriptor) {\n\t\treturn methodComments.get(name + descriptor);\n\t}\n\n\t@Override\n\tpublic void setFieldComment(@Nonnull String name, @Nonnull String descriptor, @Nullable String comment) {\n\t\tString key = name + ' ' + descriptor;\n\t\tif (comment == null)\n\t\t\tfieldComments.remove(key);\n\t\telse\n\t\t\tfieldComments.put(key, comment);\n\t\tlastUpdatedTime = Instant.now();\n\t}\n\n\t@Override\n\tpublic void setMethodComment(@Nonnull String name, @Nonnull String descriptor, @Nullable String comment) {\n\t\tString key = name + descriptor;\n\t\tif (comment == null)\n\t\t\tmethodComments.remove(key);\n\t\telse\n\t\t\tmethodComments.put(key, comment);\n\t\tlastUpdatedTime = Instant.now();\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tPersistClassComments that = (PersistClassComments) o;\n\t\tif (!fieldComments.equals(that.fieldComments)) return false;\n\t\tif (!methodComments.equals(that.methodComments)) return false;\n\t\treturn Objects.equals(classComment, that.classComment);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = fieldComments.hashCode();\n\t\tresult = 31 * result + methodComments.hashCode();\n\t\tresult = 31 * result + (classComment != null ? classComment.hashCode() : 0);\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/comment/PersistWorkspaceComments.java",
    "content": "package software.coley.recaf.services.comment;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.path.ClassPathNode;\n\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Basic workspace comment container for persistence.\n *\n * @author Matt Coley\n */\npublic class PersistWorkspaceComments implements WorkspaceComments {\n\tprivate final Map<String, PersistClassComments> classCommentsMap = new ConcurrentHashMap<>();\n\n\t/**\n\t * @return Names of classes with comment containers.\n\t */\n\t@Nonnull\n\tCollection<String> classKeys() {\n\t\t// The class keys are exposed so that the comment manager can copy state over to the delegate models.\n\t\treturn classCommentsMap.keySet();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassComments getOrCreateClassComments(@Nonnull ClassPathNode classPath) {\n\t\treturn classCommentsMap.computeIfAbsent(classPath.getValue().getName(), name -> new PersistClassComments());\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic ClassComments getClassComments(@Nonnull ClassPathNode classPath) {\n\t\treturn classCommentsMap.get(classPath.getValue().getName());\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic ClassComments deleteClassComments(@Nonnull ClassPathNode classPath) {\n\t\treturn classCommentsMap.remove(classPath.getValue().getName());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Iterator<ClassComments> iterator() {\n\t\treturn Unchecked.cast(classCommentsMap.values().iterator());\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tPersistWorkspaceComments that = (PersistWorkspaceComments) o;\n\n\t\treturn classCommentsMap.equals(that.classCommentsMap);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn classCommentsMap.hashCode();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/comment/WorkspaceComments.java",
    "content": "package software.coley.recaf.services.comment;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\n\n/**\n * Outline of a container for commented elements in a workspace.\n *\n * @author Matt Coley\n */\npublic interface WorkspaceComments extends Iterable<ClassComments> {\n\t/**\n\t * @param classPath\n\t * \t\tClass path within a workspace.\n\t *\n\t * @return Comments container for the class, creating a new container if none exist.\n\t */\n\t@Nonnull\n\tClassComments getOrCreateClassComments(@Nonnull ClassPathNode classPath);\n\n\t/**\n\t * @param classPath\n\t * \t\tClass path within a workspace.\n\t *\n\t * @return Comments container for the class, if comments exist for the class. Otherwise {@code null}.\n\t */\n\t@Nullable\n\tClassComments getClassComments(@Nonnull ClassPathNode classPath);\n\n\t/**\n\t * @param classPath\n\t * \t\tClass path within a workspace.\n\t *\n\t * @return The removed comments container for the class, or {@code null} if no comments previously existed.\n\t */\n\t@Nullable\n\tClassComments deleteClassComments(@Nonnull ClassPathNode classPath);\n\n\t/**\n\t * @param path\n\t * \t\tClass or member path within a workspace.\n\t *\n\t * @return Class or member comment, if any is associated with the path.\n\t */\n\t@Nullable\n\tdefault String getComment(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof ClassPathNode classPath)\n\t\t\treturn getClassComment(classPath);\n\t\telse if (path instanceof ClassMemberPathNode memberPath)\n\t\t\treturn getMemberComment(memberPath);\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param classPath\n\t * \t\tClass path within a workspace.\n\t *\n\t * @return Class comment, if any is associated with the path.\n\t */\n\t@Nullable\n\tdefault String getClassComment(@Nonnull ClassPathNode classPath) {\n\t\tClassComments classComments = getClassComments(classPath);\n\t\tif (classComments == null)\n\t\t\treturn null;\n\t\treturn classComments.getClassComment();\n\t}\n\n\t/**\n\t * @param memberPath\n\t * \t\tMember path within a workspace.\n\t *\n\t * @return Member comment, if any is associated with the path.\n\t */\n\t@Nullable\n\tdefault String getMemberComment(@Nonnull ClassMemberPathNode memberPath) {\n\t\tClassPathNode classPath = memberPath.getParent();\n\t\tif (classPath == null)\n\t\t\treturn null;\n\n\t\tClassComments classComments = getClassComments(classPath);\n\t\tif (classComments == null)\n\t\t\treturn null;\n\n\t\tClassMember member = memberPath.getValue();\n\t\tif (member.isField())\n\t\t\treturn classComments.getFieldComment(member.getName(), member.getDescriptor());\n\t\telse\n\t\t\treturn classComments.getMethodComment(member.getName(), member.getDescriptor());\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/CompileMap.java",
    "content": "package software.coley.recaf.services.compile;\n\nimport jakarta.annotation.Nonnull;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.util.JavaDowngraderUtil;\nimport software.coley.recaf.util.JavaVersion;\nimport xyz.wagyourtail.jvmdg.ClassDowngrader;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.TreeMap;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * Wrapper of a string-to-bytecode map with additional utility methods.\n *\n * @author Matt Coley\n */\npublic class CompileMap extends TreeMap<String, byte[]> {\n\tprivate static final Logger logger = Logging.get(CompileMap.class);\n\n\t/**\n\t * @param map\n\t * \t\tMap to copy.\n\t */\n\tpublic CompileMap(@Nonnull Map<String, byte[]> map) {\n\t\tsuper(map);\n\t}\n\n\t/**\n\t * @return {@code true} when multiple classes are in the map.\n\t */\n\tpublic boolean hasMultipleClasses() {\n\t\treturn size() > 1;\n\t}\n\n\t/**\n\t * @return {@code true} when multiple classes are in the map,\n\t * and one of them is an inner class of one of the others.\n\t */\n\tpublic boolean hasInnerClasses() {\n\t\tif (hasMultipleClasses()) {\n\t\t\tfor (String name : keySet()) {\n\t\t\t\t// Name contains the inner class separator \"$\" and the outer-class is also in the map\n\t\t\t\tif (name.contains(\"$\") && containsKey(name.substring(0, name.lastIndexOf(\"$\")))) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Down sample all classes in the map to the target version.\n\t *\n\t * @param targetJavaVersion\n\t * \t\tTarget version to downsample to. To target 8, simply pass {@code 8} <i>()</i>.\n\t */\n\tpublic void downsample(int targetJavaVersion) {\n\t\ttry {\n\t\t\tJavaDowngraderUtil.downgrade(targetJavaVersion, new HashMap<>(this), this::put);\n\t\t} catch (IOException ex) {\n\t\t\tlogger.error(\"Failed down sampling to version {}\",  targetJavaVersion, ex);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/CompilerDiagnostic.java",
    "content": "package software.coley.recaf.services.compile;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Simple compiler feedback wrapper.\n *\n * @param line\n * \t\tLine the message applies to.\n * @param column\n * \t\tColumn the message applies to within the line.\n * @param length\n * \t\tLength beyond the column position where the message applies to.\n * @param message\n * \t\tMessage detailing the problem.\n * @param level\n * \t\tDiagnostic problem level.\n *\n * @author Matt Coley\n */\npublic record CompilerDiagnostic(int line, int column, int length, @Nonnull String message, @Nonnull Level level) {\n\t/**\n\t * @param line\n\t * \t\tNew line number.\n\t *\n\t * @return Copy of diagnostic, with changed line number.\n\t */\n\t@Nonnull\n\tpublic CompilerDiagnostic withLine(int line) {\n\t\treturn new CompilerDiagnostic(line, column, length, message, level);\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn level.name() + \" on line \" + line + \": \" + message;\n\t}\n\n\t/**\n\t * Diagnostic level.\n\t */\n\tpublic enum Level {\n\t\tERROR,\n\t\tWARNING,\n\t\tINFO\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/CompilerResult.java",
    "content": "package software.coley.recaf.services.compile;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Compiler results wrapper.\n *\n * @author Matt Coley\n */\npublic class CompilerResult {\n\tprivate final CompileMap compilations;\n\tprivate final List<CompilerDiagnostic> diagnostics;\n\tprivate final Throwable exception;\n\n\t/**\n\t * @param exception\n\t * \t\tError thrown when attempting to compile.\n\t */\n\tpublic CompilerResult(@Nonnull Throwable exception) {\n\t\tthis(new CompileMap(Collections.emptyMap()), Collections.emptyList(), exception);\n\t}\n\n\t/**\n\t * @param compileMap\n\t * \t\tCompilation results.\n\t * @param diagnostics\n\t * \t\tCompilation problem diagnostics.\n\t */\n\tpublic CompilerResult(@Nonnull CompileMap compileMap, @Nonnull List<CompilerDiagnostic> diagnostics) {\n\t\tthis(compileMap, diagnostics, null);\n\t}\n\n\tprivate CompilerResult(@Nonnull CompileMap compilations,\n\t\t\t\t\t\t   @Nonnull List<CompilerDiagnostic> diagnostics,\n\t\t\t\t\t\t   @Nullable Throwable exception) {\n\t\tthis.compilations = compilations;\n\t\tthis.exception = exception;\n\t\tthis.diagnostics = diagnostics;\n\t}\n\n\t/**\n\t * @return {@code true} when there are compilations, and no errors thrown.\n\t */\n\tpublic boolean wasSuccess() {\n\t\treturn compilations != null &&\n\t\t\t\t!compilations.isEmpty() &&\n\t\t\t\texception == null &&\n\t\t\t\tdiagnostics.stream().noneMatch(d -> d.level() == CompilerDiagnostic.Level.ERROR);\n\t}\n\n\t/**\n\t * @return Compilation results.\n\t * Empty when there are is an {@link #getException()}.\n\t */\n\t@Nonnull\n\tpublic CompileMap getCompilations() {\n\t\treturn compilations;\n\t}\n\n\t/**\n\t * @return Compilation problem diagnostics.\n\t * Empty when {@link #wasSuccess()}.\n\t */\n\t@Nonnull\n\tpublic List<CompilerDiagnostic> getDiagnostics() {\n\t\treturn diagnostics;\n\t}\n\n\t/**\n\t * @return Error thrown when attempting to compile.\n\t * {@code null} when compilation was a success.\n\t */\n\t@Nullable\n\tpublic Throwable getException() {\n\t\treturn exception;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/ForwardingListener.java",
    "content": "package software.coley.recaf.services.compile;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport javax.tools.Diagnostic;\nimport javax.tools.JavaFileObject;\n\n/**\n * Diagnostic listener that forwards reports to a delegate listener.\n *\n * @author Matt Coley\n */\npublic class ForwardingListener implements JavacListener {\n\tprivate final JavacListener delegate;\n\n\tForwardingListener(@Nullable JavacListener delegate) {\n\t\tthis.delegate = delegate;\n\t}\n\n\t@Override\n\tpublic void report(@Nonnull Diagnostic<? extends JavaFileObject> diagnostic) {\n\t\tif (delegate != null)\n\t\t\tdelegate.report(diagnostic);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/JavacArguments.java",
    "content": "package software.coley.recaf.services.compile;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Objects;\n\n/**\n * Arguments to pass to {@link JavacCompiler#compile(JavacArguments, Workspace, JavacListener)}.\n *\n * @author Matt Coley\n * @see JavacArgumentsBuilder\n */\npublic class JavacArguments {\n\t// Primary inputs\n\tprivate final String className;\n\tprivate final String classSource;\n\t// Options\n\tprivate final String classPath;\n\tprivate final int versionTarget;\n\tprivate final int downsampleTarget;\n\tprivate final boolean debugVariables;\n\tprivate final boolean debugLineNumbers;\n\tprivate final boolean debugSourceName;\n\n\t/**\n\t * @param className\n\t * \t\tInternal name of the class being compiled.\n\t * @param classSource\n\t * \t\tSource of the class.\n\t * @param classPath\n\t * \t\tClasspath to use with compiler.\n\t * @param versionTarget\n\t * \t\tJava version to target.\n\t * @param downsampleTarget\n\t * \t\tJava version to target via down sampling. Negative to disable downs sampling.\n\t * @param debugVariables\n\t * \t\tDebug flag to include variable info.\n\t * @param debugLineNumbers\n\t * \t\tDebug flag to include line number info.\n\t * @param debugSourceName\n\t * \t\tDebug flag to include source file name.\n\t */\n\tpublic JavacArguments(@Nonnull String className, @Nonnull String classSource,\n\t\t\t\t\t\t  @Nullable String classPath, int versionTarget, int downsampleTarget,\n\t\t\t\t\t\t  boolean debugVariables, boolean debugLineNumbers, boolean debugSourceName) {\n\t\tthis.className = className;\n\t\tthis.classSource = classSource;\n\t\tthis.classPath = classPath;\n\t\tthis.versionTarget = versionTarget;\n\t\tthis.downsampleTarget = downsampleTarget;\n\t\tthis.debugVariables = debugVariables;\n\t\tthis.debugLineNumbers = debugLineNumbers;\n\t\tthis.debugSourceName = debugSourceName;\n\t}\n\n\t/**\n\t * @return String representation of debug flags.\n\t */\n\t@Nonnull\n\tpublic String createDebugValue() {\n\t\tStringBuilder s = new StringBuilder();\n\t\tif (debugVariables)\n\t\t\ts.append(\"vars,\");\n\t\tif (debugLineNumbers)\n\t\t\ts.append(\"lines,\");\n\t\tif (debugSourceName)\n\t\t\ts.append(\"source\");\n\n\t\t// edge case\n\t\tif (s.isEmpty())\n\t\t\treturn \"-g:none\";\n\n\t\t// Substring off dangling comma\n\t\tString value = s.toString();\n\t\tif (value.endsWith(\",\"))\n\t\t\tvalue = s.substring(0, value.length() - 1);\n\t\treturn \"-g:\" + value;\n\t}\n\n\t/**\n\t * @return Internal name of the class being compiled.\n\t */\n\t@Nonnull\n\tpublic String getClassName() {\n\t\treturn className;\n\t}\n\n\t/**\n\t * @return Source of the class.\n\t */\n\t@Nonnull\n\tpublic String getClassSource() {\n\t\treturn classSource;\n\t}\n\n\t/**\n\t * @return Classpath to use with compiler.\n\t */\n\t@Nullable\n\tpublic String getClassPath() {\n\t\treturn classPath;\n\t}\n\n\t/**\n\t * @return Java version to target.\n\t */\n\tpublic int getVersionTarget() {\n\t\treturn versionTarget;\n\t}\n\n\t/**\n\t * @return Java version to target via down sampling. Negative to disable downs sampling.\n\t */\n\tpublic int getDownsampleTarget() {\n\t\treturn Math.min(downsampleTarget, JavacCompiler.MIN_DOWNSAMPLE_VER);\n\t}\n\n\t/**\n\t * @return Debug flag to include variable info.\n\t */\n\tpublic boolean isDebugVariables() {\n\t\treturn debugVariables;\n\t}\n\n\t/**\n\t * @return Debug flag to include line number info.\n\t */\n\tpublic boolean isDebugLineNumbers() {\n\t\treturn debugLineNumbers;\n\t}\n\n\t/**\n\t * @return Debug flag to include source file name.\n\t */\n\tpublic boolean isDebugSourceName() {\n\t\treturn debugSourceName;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tJavacArguments other = (JavacArguments) o;\n\n\t\tif (versionTarget != other.versionTarget) return false;\n\t\tif (debugVariables != other.debugVariables) return false;\n\t\tif (debugLineNumbers != other.debugLineNumbers) return false;\n\t\tif (debugSourceName != other.debugSourceName) return false;\n\t\tif (!className.equals(other.className)) return false;\n\t\tif (!classSource.equals(other.classSource)) return false;\n\t\treturn Objects.equals(classPath, other.classPath);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = className.hashCode();\n\t\tresult = 31 * result + classSource.hashCode();\n\t\tresult = 31 * result + (classPath != null ? classPath.hashCode() : 0);\n\t\tresult = 31 * result + versionTarget;\n\t\tresult = 31 * result + (debugVariables ? 1 : 0);\n\t\tresult = 31 * result + (debugLineNumbers ? 1 : 0);\n\t\tresult = 31 * result + (debugSourceName ? 1 : 0);\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/JavacArgumentsBuilder.java",
    "content": "package software.coley.recaf.services.compile;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.JavaVersion;\n\n/**\n * Builder for {@link JavacArguments}.\n *\n * @author Matt Coley\n */\npublic final class JavacArgumentsBuilder {\n\tprivate String className;\n\tprivate String classSource;\n\tprivate String classPath = System.getProperty(\"java.class.path\");\n\tprivate int versionTarget = JavaVersion.get();\n\tprivate int downsampleTarget = -1;\n\tprivate boolean debugVariables = true;\n\tprivate boolean debugLineNumbers = true;\n\tprivate boolean debugSourceName = true;\n\n\t/**\n\t * @param className\n\t * \t\tInternal name of the class being compiled.\n\t *\n\t * @return Builder.\n\t */\n\t@Nonnull\n\tpublic JavacArgumentsBuilder withClassName(@Nonnull String className) {\n\t\tthis.className = className;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param classSource\n\t * \t\tSource of the class.\n\t *\n\t * @return Builder.\n\t */\n\t@Nonnull\n\tpublic JavacArgumentsBuilder withClassSource(@Nonnull String classSource) {\n\t\tthis.classSource = classSource;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param classPath\n\t * \t\tClasspath to use with compiler.\n\t *\n\t * @return Builder.\n\t */\n\t@Nonnull\n\tpublic JavacArgumentsBuilder withClassPath(@Nullable String classPath) {\n\t\tthis.classPath = classPath;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param downsampleTarget\n\t * \t\tJava version to target via down sampling. Negative to disable downs sampling.\n\t * \t\tSee: {@link JavacCompiler#MIN_DOWNSAMPLE_VER} for lowest supported target.\n\t *\n\t * @return Builder.\n\t */\n\t@Nonnull\n\tpublic JavacArgumentsBuilder withDownsampleTarget(int downsampleTarget) {\n\t\tthis.downsampleTarget = downsampleTarget;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param versionTarget\n\t * \t\tJava version to target.\n\t *\n\t * @return Builder.\n\t */\n\t@Nonnull\n\tpublic JavacArgumentsBuilder withVersionTarget(int versionTarget) {\n\t\tthis.versionTarget = versionTarget;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param debugVariables\n\t * \t\tDebug flag to include variable info.\n\t *\n\t * @return Builder.\n\t */\n\t@Nonnull\n\tpublic JavacArgumentsBuilder withDebugVariables(boolean debugVariables) {\n\t\tthis.debugVariables = debugVariables;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param debugLineNumbers\n\t * \t\tDebug flag to include line number info.\n\t *\n\t * @return Builder.\n\t */\n\t@Nonnull\n\tpublic JavacArgumentsBuilder withDebugLineNumbers(boolean debugLineNumbers) {\n\t\tthis.debugLineNumbers = debugLineNumbers;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param debugSourceName\n\t * \t\tDebug flag to include source file name.\n\t *\n\t * @return Builder.\n\t */\n\t@Nonnull\n\tpublic JavacArgumentsBuilder withDebugSourceName(boolean debugSourceName) {\n\t\tthis.debugSourceName = debugSourceName;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @return Arguments instance.\n\t */\n\t@Nonnull\n\tpublic JavacArguments build() {\n\t\tif (className == null)\n\t\t\tthrow new IllegalArgumentException(\"Class name must not be null\");\n\t\tif (classSource == null)\n\t\t\tthrow new IllegalArgumentException(\"Class source must not be null\");\n\n\t\treturn new JavacArguments(className, classSource,\n\t\t\t\tclassPath, versionTarget, downsampleTarget,\n\t\t\t\tdebugVariables, debugLineNumbers, debugSourceName);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/JavacCompiler.java",
    "content": "package software.coley.recaf.services.compile;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.phantom.GeneratedPhantomWorkspaceResource;\nimport software.coley.recaf.services.phantom.PhantomGenerationException;\nimport software.coley.recaf.services.phantom.PhantomGenerator;\nimport software.coley.recaf.util.ReflectUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport javax.tools.Diagnostic;\nimport javax.tools.JavaCompiler;\nimport javax.tools.JavaFileManager;\nimport javax.tools.JavaFileObject;\nimport javax.tools.ToolProvider;\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.stream.Collectors;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\n/**\n * Wrapper for {@link JavaCompiler}.\n * <br>\n * Worth note, the minimum supported version is declared in {@code com.sun.tools.javac.jvm.Target} but is marked\n * as an unstable API subject to change without notice. As of Java 17, the minimum target version is Java 7.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class JavacCompiler implements Service {\n\tpublic static final String SERVICE_ID = \"java-compiler\";\n\tpublic static final int MIN_DOWNSAMPLE_VER = 8;\n\tprivate static final DebuggingLogger logger = Logging.get(JavacCompiler.class);\n\tprivate static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();\n\tprivate static int minTargetVersion = 7;\n\tprivate final PhantomGenerator phantomGenerator;\n\tprivate final JavacCompilerConfig config;\n\n\t@Inject\n\tpublic JavacCompiler(@Nonnull PhantomGenerator phantomGenerator,\n\t                     @Nonnull JavacCompilerConfig config) {\n\t\tthis.phantomGenerator = phantomGenerator;\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * @param arguments\n\t * \t\tWrapper of all arguments.\n\t * @param workspace\n\t * \t\tOptional workspace to include for additional classpath support.\n\t * @param listener\n\t * \t\tOptional listener to handle feedback with,\n\t * \t\tmirroring what is reported by {@link CompilerResult#getDiagnostics()}\n\t *\n\t * @return Compilation result wrapper.\n\t */\n\t@Nonnull\n\tpublic CompilerResult compile(@Nonnull JavacArguments arguments,\n\t                              @Nullable Workspace workspace,\n\t                              @Nullable JavacListener listener) {\n\t\treturn compile(arguments, workspace, null, listener);\n\t}\n\n\t/**\n\t * @param arguments\n\t * \t\tWrapper of all arguments.\n\t * @param workspace\n\t * \t\tOptional workspace to include for additional classpath support.\n\t * @param supplementaryResources\n\t * \t\tOptional resources to further extend the compilation classpath with.\n\t * @param listener\n\t * \t\tOptional listener to handle feedback with,\n\t * \t\tmirroring what is reported by {@link CompilerResult#getDiagnostics()}\n\t *\n\t * @return Compilation result wrapper.\n\t */\n\t@Nonnull\n\tpublic CompilerResult compile(@Nonnull JavacArguments arguments,\n\t                              @Nullable Workspace workspace,\n\t                              @Nullable List<WorkspaceResource> supplementaryResources,\n\t                              @Nullable JavacListener listener) {\n\t\tif (compiler == null)\n\t\t\treturn new CompilerResult(new IllegalStateException(\"Cannot load 'javac' compiler.\"));\n\n\t\tString className = arguments.getClassName();\n\n\t\t// Class input map\n\t\tVirtualUnitMap unitMap = new VirtualUnitMap();\n\t\tunitMap.addSource(className, arguments.getClassSource());\n\n\t\t// Create a file manager to track files in-memory rather than on-disk\n\t\tList<WorkspaceResource> virtualClassPath = workspace == null ?\n\t\t\t\tCollections.emptyList() : workspace.getAllResources(true);\n\t\tif (supplementaryResources != null)\n\t\t\tvirtualClassPath = Lists.combine(virtualClassPath, supplementaryResources);\n\n\t\t// Generate phantom classes if the workspace does not already have phantoms in it.\n\t\tif (workspace != null && config.getGeneratePhantoms().getValue()\n\t\t\t\t&& workspace.getSupportingResources().stream().noneMatch(resource -> resource instanceof GeneratedPhantomWorkspaceResource)) {\n\t\t\t// Only scan the target class and any of its inner classes for content to fill in.\n\t\t\tList<JvmClassInfo> classesToScan = workspace.findJvmClasses(c -> c.getName().equals(className) || c.isInnerClassOf(className)).stream()\n\t\t\t\t\t.map(p -> p.getValue().asJvmClass())\n\t\t\t\t\t.collect(Collectors.toList());\n\t\t\tif (!classesToScan.isEmpty()) {\n\t\t\t\ttry {\n\t\t\t\t\tWorkspaceResource phantomResource = phantomGenerator.createPhantomsForClasses(workspace, classesToScan);\n\t\t\t\t\tint generatedCount = phantomResource.getJvmClassBundle().size();\n\t\t\t\t\tif (generatedCount > 0)\n\t\t\t\t\t\tlogger.debug(\"Generated {} phantoms for pre-compile\", generatedCount);\n\t\t\t\t\tvirtualClassPath = Lists.add(virtualClassPath, phantomResource);\n\t\t\t\t} catch (PhantomGenerationException ex) {\n\t\t\t\t\tlogger.warn(\"Failed to generate phantoms for compilation against '{}'\", className, ex);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tList<CompilerDiagnostic> diagnostics = new ArrayList<>();\n\t\tJavacListener listenerWrapper = createRecordingListener(listener, diagnostics);\n\t\tJavaFileManager fmFallback = compiler.getStandardFileManager(listenerWrapper, Locale.getDefault(), UTF_8);\n\t\tJavaFileManager fm = new VirtualFileManager(unitMap, virtualClassPath, fmFallback);\n\n\t\t// Populate arguments\n\t\tList<String> args = new ArrayList<>();\n\n\t\t// Classpath\n\t\tString cp = arguments.getClassPath();\n\t\tif (cp != null) {\n\t\t\targs.add(\"-classpath\");\n\t\t\targs.add(cp);\n\t\t\tlogger.debugging(l -> l.info(\"Compiler classpath: {}\", cp));\n\t\t}\n\n\t\t// Target version\n\t\tint target = arguments.getVersionTarget();\n\t\targs.add(\"--release\");\n\t\targs.add(Integer.toString(target));\n\t\tlogger.debugging(l -> l.info(\"Compiler target: {}\", target));\n\n\t\t// Debug info\n\t\tString debugArg = arguments.createDebugValue();\n\t\targs.add(debugArg);\n\t\tlogger.debugging(l -> l.info(\"Compiler debug: {}\", debugArg));\n\n\t\t// Invoke compiler\n\t\ttry {\n\t\t\tJavaCompiler.CompilationTask task =\n\t\t\t\t\tcompiler.getTask(null, fm, listenerWrapper, args, null, unitMap.getFiles());\n\t\t\tif (task.call()) {\n\t\t\t\tlogger.debugging(l -> l.info(\"Compilation of '{}' finished\", className));\n\t\t\t} else {\n\t\t\t\tlogger.debugging(l -> l.error(\"Compilation of '{}' failed\", className));\n\t\t\t}\n\t\t\tCompileMap compilations = unitMap.getCompilations();\n\t\t\tint downsampleTarget = arguments.getDownsampleTarget();\n\t\t\tif (downsampleTarget >= MIN_DOWNSAMPLE_VER)\n\t\t\t\tcompilations.downsample(downsampleTarget);\n\t\t\telse if (downsampleTarget >= 0)\n\t\t\t\tlogger.warn(\"Cannot downsample beyond Java {}\", JavacCompiler.MIN_DOWNSAMPLE_VER);\n\t\t\treturn new CompilerResult(compilations, diagnostics);\n\t\t} catch (RuntimeException ex) {\n\t\t\tlogger.debugging(l -> l.error(\"Compilation of '{}' crashed\", className, ex));\n\t\t\treturn new CompilerResult(ex);\n\t\t}\n\t}\n\n\t/**\n\t * @return {@code true} when the compiler can be invoked.\n\t */\n\tpublic static boolean isAvailable() {\n\t\treturn compiler != null;\n\t}\n\n\t/**\n\t * @return Minimum target version supported by the compiler.\n\t */\n\tpublic static int getMinTargetVersion() {\n\t\treturn minTargetVersion;\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tOptional listener to wrap.\n\t * @param diagnostics\n\t * \t\tList to add diagnostics to.\n\t *\n\t * @return Listener to encompass recording behavior and the user defined listener.\n\t */\n\tprivate JavacListener createRecordingListener(@Nullable JavacListener listener,\n\t                                              @Nonnull List<CompilerDiagnostic> diagnostics) {\n\t\treturn new ForwardingListener(listener) {\n\t\t\t@Override\n\t\t\tpublic void report(@Nonnull Diagnostic<? extends JavaFileObject> diagnostic) {\n\t\t\t\t// Pass to user defined listener\n\t\t\t\tsuper.report(diagnostic);\n\n\t\t\t\t// Record the diagnostic to our output\n\t\t\t\tif (diagnostic.getKind() == Diagnostic.Kind.ERROR) {\n\t\t\t\t\tdiagnostics.add(new CompilerDiagnostic(\n\t\t\t\t\t\t\t(int) diagnostic.getLineNumber(),\n\t\t\t\t\t\t\t(int) diagnostic.getColumnNumber(),\n\t\t\t\t\t\t\t(int) diagnostic.getEndPosition() - (int) diagnostic.getPosition(),\n\t\t\t\t\t\t\tdiagnostic.getMessage(Locale.getDefault()),\n\t\t\t\t\t\t\tmapKind(diagnostic.getKind())\n\t\t\t\t\t));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprivate CompilerDiagnostic.Level mapKind(Diagnostic.Kind kind) {\n\t\t\t\tswitch (kind) {\n\t\t\t\t\tcase ERROR:\n\t\t\t\t\t\treturn CompilerDiagnostic.Level.ERROR;\n\t\t\t\t\tcase WARNING:\n\t\t\t\t\tcase MANDATORY_WARNING:\n\t\t\t\t\t\treturn CompilerDiagnostic.Level.WARNING;\n\t\t\t\t\tcase NOTE:\n\t\t\t\t\tcase OTHER:\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn CompilerDiagnostic.Level.INFO;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic JavacCompilerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\tstatic {\n\t\t// Lookup oldest supported version\n\t\ttry {\n\t\t\tMethodHandles.Lookup lookup = ReflectUtil.lookup();\n\t\t\tClass<?> target = Class.forName(\"com.sun.tools.javac.jvm.Target\");\n\t\t\tMethodHandle min = lookup.findStaticGetter(target, \"MIN\", target);\n\t\t\tObject minTarget = min.invoke();\n\t\t\tField majorVersion = minTarget.getClass().getDeclaredField(\"majorVersion\");\n\t\t\tmajorVersion.setAccessible(true);\n\t\t\tminTargetVersion = majorVersion.getInt(minTarget) - JvmClassInfo.BASE_VERSION;\n\t\t} catch (Throwable ignored) {\n\t\t\t// Oh well...\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/JavacCompilerConfig.java",
    "content": "package software.coley.recaf.services.compile;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.List;\n\n/**\n * Config for {@link JavacCompiler}.\n * <br>\n * Not to be confused with {@link JavacArguments individual arguments} to be passed when invoking the compiler.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class JavacCompilerConfig extends BasicConfigContainer implements ServiceConfig {\n\tprivate final ObservableBoolean generatePhantoms = new ObservableBoolean(true);\n\tprivate final ObservableBoolean defaultEmitDebug = new ObservableBoolean(true);\n\tprivate final ObservableInteger defaultTargetVersion = new ObservableInteger(-1);\n\tprivate final ObservableInteger defaultDownsampleTargetVersion = new ObservableInteger(-1);\n\n\t@Inject\n\tpublic JavacCompilerConfig() {\n\t\tsuper(ConfigGroups.SERVICE_COMPILE, JavacCompiler.SERVICE_ID + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"generate-phantoms\", boolean.class, generatePhantoms));\n\t\taddValue(new BasicConfigValue<>(\"default-emit-debug\", boolean.class, defaultEmitDebug));\n\t\taddValue(new BasicConfigValue<>(\"default-compile-target-version\", int.class, defaultTargetVersion));\n\t\taddValue(new BasicConfigValue<>(\"default-downsample-target-version\", int.class, defaultDownsampleTargetVersion));\n\t}\n\n\t/**\n\t * Not enforced internally by {@link JavacCompiler}.\n\t * Callers should check this value and ensure to call\n\t * {@link JavacCompiler#compile(JavacArguments, Workspace, List, JavacListener)} with the list populated.\n\t *\n\t * @return {@code true} to enable phantom generation when calling {@code javac}.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getGeneratePhantoms() {\n\t\treturn generatePhantoms;\n\t}\n\n\t/**\n\t * Not enforced internally by {@link JavacCompiler}.\n\t * Callers should check this value and ensure to call {@link JavacArgumentsBuilder#withDebugVariables(boolean)}\n\t * and other debug methods.\n\t *\n\t * @return {@code true} to enable debug info by default.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getDefaultEmitDebug() {\n\t\treturn defaultEmitDebug;\n\t}\n\n\t/**\n\t * Not enforced internally by {@link JavacCompiler}.\n\t * Callers should check this value and ensure to call {@link JavacArgumentsBuilder#withVersionTarget(int)}.\n\t *\n\t * @return Negative to match the input version of the class, otherwise target version\n\t * <i>(In class file version format)</i>\n\t */\n\t@Nonnull\n\tpublic ObservableInteger getDefaultTargetVersion() {\n\t\treturn defaultTargetVersion;\n\t}\n\n\t/**\n\t * Not enforced internally by {@link JavacCompiler}.\n\t * Callers should check this value and ensure to call {@link JavacArgumentsBuilder#withDownsampleTarget(int)}.\n\t *\n\t * @return Negative to disable down sampling, otherwise target version to downsample compiled code to\n\t * <i>(In class file version format)</i>\n\t */\n\t@Nonnull\n\tpublic ObservableInteger getDefaultDownsampleTargetVersion() {\n\t\treturn defaultDownsampleTargetVersion;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/JavacListener.java",
    "content": "package software.coley.recaf.services.compile;\n\nimport javax.tools.DiagnosticListener;\nimport javax.tools.JavaFileObject;\n\n/**\n * Diagnostic list wrapper.\n *\n * @author Matt Coley\n */\npublic interface JavacListener extends DiagnosticListener<JavaFileObject> {}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/ResourceVirtualJavaFileObject.java",
    "content": "package software.coley.recaf.services.compile;\n\nimport jakarta.annotation.Nonnull;\n\nimport javax.tools.SimpleJavaFileObject;\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\nimport java.net.URI;\n\n/**\n * Java file extension that exposes workspace resource for classpath.\n *\n * @author xDark\n */\npublic class ResourceVirtualJavaFileObject extends SimpleJavaFileObject {\n\tprivate final String resourceName;\n\tprivate final byte[] content;\n\n\t/**\n\t * @param resourceName\n\t * \t\tName of the resource.\n\t * @param content\n\t * \t\tClass source content.\n\t * @param resourceKind\n\t * \t\tKind of the resource.\n\t */\n\tpublic ResourceVirtualJavaFileObject(String resourceName, byte[] content, Kind resourceKind) {\n\t\tsuper(URI.create(\"memory://\" + resourceName + resourceKind.extension), resourceKind);\n\t\tthis.resourceName = resourceName;\n\t\tthis.content = content;\n\t}\n\n\t/**\n\t * @return Resource name.\n\t */\n\t@Nonnull\n\tpublic String getResourceName() {\n\t\treturn resourceName;\n\t}\n\n\t@Override\n\tpublic InputStream openInputStream() {\n\t\treturn new ByteArrayInputStream(content);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/VirtualFileManager.java",
    "content": "package software.coley.recaf.services.compile;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport javax.tools.FileObject;\nimport javax.tools.ForwardingJavaFileManager;\nimport javax.tools.JavaFileManager;\nimport javax.tools.JavaFileObject;\nimport javax.tools.StandardLocation;\nimport java.io.IOException;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.function.Predicate;\n\n/**\n * File manager extension for handling updates to java file object's output stream.\n * Additionally, registers inner classes as new files.\n *\n * @author Matt Coley\n */\npublic class VirtualFileManager extends ForwardingJavaFileManager<JavaFileManager> {\n\tprivate final VirtualUnitMap unitMap;\n\tprivate final List<WorkspaceResource> virtualClasspath;\n\n\t/**\n\t * @param unitMap\n\t * \t\tClass input map.\n\t * @param virtualClasspath\n\t * \t\tIn-memory classpath.\n\t * @param fallback\n\t * \t\tFallback manager.\n\t */\n\tpublic VirtualFileManager(@Nonnull VirtualUnitMap unitMap, @Nonnull List<WorkspaceResource> virtualClasspath, @Nonnull JavaFileManager fallback) {\n\t\tsuper(fallback);\n\t\tthis.virtualClasspath = virtualClasspath;\n\t\tthis.unitMap = unitMap;\n\t}\n\n\t@Override\n\tpublic Iterable<JavaFileObject> list(@Nonnull Location location, @Nonnull String packageName,\n\t                                     @Nonnull Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {\n\t\tIterable<JavaFileObject> list = super.list(location, packageName, kinds, recurse);\n\t\tif (StandardLocation.CLASS_PATH.equals(location) && kinds.contains(JavaFileObject.Kind.CLASS)) {\n\t\t\tString formatted = packageName.isEmpty() ? \"\" : packageName.replace('.', '/') + '/';\n\t\t\tPredicate<String> check;\n\t\t\tif (recurse) {\n\t\t\t\tcheck = name -> name.startsWith(formatted);\n\t\t\t} else {\n\t\t\t\tcheck = name -> name.startsWith(formatted) &&\n\t\t\t\t\t\tname.indexOf('/', formatted.length()) == -1;\n\t\t\t}\n\t\t\treturn () -> new ClassPathIterator(list.iterator(), virtualClasspath.stream()\n\t\t\t\t\t.flatMap(resource -> resource.jvmClassBundleStreamRecursive().flatMap(b -> b.entrySet().stream()))\n\t\t\t\t\t.filter(entry -> check.test(entry.getKey()))\n\t\t\t\t\t.<JavaFileObject>map(entry -> new ResourceVirtualJavaFileObject(entry.getKey(),\n\t\t\t\t\t\t\tentry.getValue().getBytecode(), JavaFileObject.Kind.CLASS))\n\t\t\t\t\t.iterator());\n\t\t}\n\t\treturn list;\n\t}\n\n\t@Override\n\tpublic String inferBinaryName(@Nonnull Location location, @Nonnull JavaFileObject file) {\n\t\tif (file instanceof ResourceVirtualJavaFileObject virtualObject && file.getKind() == JavaFileObject.Kind.CLASS) {\n\t\t\treturn virtualObject.getResourceName().replace('/', '.');\n\t\t}\n\t\treturn super.inferBinaryName(location, file);\n\t}\n\n\t@Override\n\tpublic JavaFileObject getJavaFileForOutput(@Nonnull JavaFileManager.Location location, @Nonnull String name, @Nonnull JavaFileObject.Kind\n\t\t\tkind, FileObject sibling) {\n\t\t// Name should be like \"com.example.MyClass$MyInner\"\n\t\tString internal = name.replace('.', '/');\n\t\tVirtualJavaFileObject obj = unitMap.getFile(internal);\n\n\t\t// Unknown class, assumed to be an inner class.\n\t\t// Add it to the unit map, so it can be fetched.\n\t\tif (obj == null) {\n\t\t\tobj = new VirtualJavaFileObject(internal, null);\n\t\t\tunitMap.addFile(internal, obj);\n\t\t}\n\t\treturn obj;\n\t}\n\n\tprivate static final class ClassPathIterator implements Iterator<JavaFileObject> {\n\t\tprivate final Iterator<JavaFileObject> first, second;\n\n\t\tClassPathIterator(@Nonnull Iterator<JavaFileObject> first, @Nonnull Iterator<JavaFileObject> second) {\n\t\t\tthis.first = first;\n\t\t\tthis.second = second;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean hasNext() {\n\t\t\treturn first.hasNext() || second.hasNext();\n\t\t}\n\n\t\t@Override\n\t\tpublic JavaFileObject next() {\n\t\t\tif (first.hasNext())\n\t\t\t\treturn first.next();\n\t\t\treturn second.next();\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/VirtualJavaFileObject.java",
    "content": "package software.coley.recaf.services.compile;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport javax.tools.SimpleJavaFileObject;\nimport java.io.ByteArrayOutputStream;\nimport java.io.OutputStream;\nimport java.net.URI;\n\n/**\n * Java file extension that keeps track of the compiled bytecode.\n *\n * @author Matt Coley\n */\npublic class VirtualJavaFileObject extends SimpleJavaFileObject {\n\tprivate final ByteArrayOutputStream baos = new ByteArrayOutputStream();\n\tprivate final String content;\n\n\t/**\n\t * @param className\n\t * \t\tName of class.\n\t * @param content\n\t * \t\tContent of source file to compile.\n\t */\n\tpublic VirtualJavaFileObject(@Nonnull String className, @Nullable String content) {\n\t\tsuper(URI.create(\"string:///\" + className.replace('.', '/') + Kind.SOURCE.extension),\n\t\t\t\tKind.SOURCE);\n\t\tthis.content = content;\n\t}\n\n\t/**\n\t * @return {@code true} when {@link #getBytecode()} has content.\n\t */\n\tpublic boolean hasOutput() {\n\t\treturn baos.size() > 0;\n\t}\n\n\t/**\n\t * @return Compiled bytecode of class.\n\t */\n\t@Nonnull\n\tpublic byte[] getBytecode() {\n\t\treturn baos.toByteArray();\n\t}\n\n\t/**\n\t * @return Class source code.\n\t */\n\t@Nonnull\n\tpublic String getSource() {\n\t\treturn content;\n\t}\n\n\t@Override\n\tpublic final OutputStream openOutputStream() {\n\t\treturn baos;\n\t}\n\n\t@Override\n\tpublic CharSequence getCharContent(boolean ignoreEncodingErrors) {\n\t\treturn content;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/VirtualUnitMap.java",
    "content": "package software.coley.recaf.services.compile;\n\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * {@link javax.tools.JavaFileObject} map wrapper for managing class inputs for the compiler.\n *\n * @author Matt Coley\n */\npublic class VirtualUnitMap {\n\tprivate final Map<String, VirtualJavaFileObject> unitMap = new HashMap<>();\n\n\t/**\n\t * Add class to compilation process.\n\t *\n\t * @param className\n\t * \t\tName of class to compile.\n\t * @param content\n\t * \t\tSource code of class.\n\t */\n\tpublic void addSource(@Nonnull String className, @Nonnull String content) {\n\t\taddFile(className, new VirtualJavaFileObject(className, content));\n\t}\n\n\t/**\n\t * Add class to compilation process.\n\t *\n\t * @param className\n\t * \t\tName of class to compile.\n\t * @param fileObject\n\t * \t\tFile object for source code of class.\n\t */\n\tpublic void addFile(@Nonnull String className, @Nonnull VirtualJavaFileObject fileObject) {\n\t\tunitMap.put(className, fileObject);\n\t}\n\n\t/**\n\t * @param className\n\t * \t\tName of class.\n\t *\n\t * @return File object for source code of class.\n\t */\n\t@Nullable\n\tpublic VirtualJavaFileObject getFile(@Nonnull String className) {\n\t\treturn unitMap.get(className);\n\t}\n\n\t/**\n\t * @return Collection of file objects for input classes.\n\t */\n\t@Nonnull\n\tpublic Collection<VirtualJavaFileObject> getFiles() {\n\t\treturn unitMap.values();\n\t}\n\n\t/**\n\t * @return Map of class names to bytecode.\n\t * Items that failed to compile will not have entries.\n\t */\n\t@Nonnull\n\tpublic CompileMap getCompilations() {\n\t\tMap<String, byte[]> map = unitMap.entrySet().stream()\n\t\t\t\t.filter(e -> e.getValue().hasOutput())\n\t\t\t\t.collect(Collectors.toMap(Map.Entry::getKey, v -> v.getValue().getBytecode()));\n\t\treturn new CompileMap(map);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/stub/ClassStubGenerator.java",
    "content": "package software.coley.recaf.services.compile.stub;\n\nimport dev.xdark.blw.type.ArrayType;\nimport dev.xdark.blw.type.ClassType;\nimport dev.xdark.blw.type.InstanceType;\nimport dev.xdark.blw.type.MethodType;\nimport dev.xdark.blw.type.ObjectType;\nimport dev.xdark.blw.type.PrimitiveType;\nimport dev.xdark.blw.type.Type;\nimport dev.xdark.blw.type.Types;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.assembler.ExpressionCompileException;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceVertex;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.util.Keywords;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.visitors.SkippingClassVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.lang.reflect.Modifier;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.Collectors;\n\n/**\n * Base stub generator for classes.\n *\n * @author Matt Coley\n */\npublic abstract class ClassStubGenerator {\n\tprotected final Workspace workspace;\n\tprotected final InheritanceGraph inheritanceGraph;\n\tprotected final int classAccess;\n\tprotected final String className;\n\tprotected final String superName;\n\tprotected final List<String> implementing;\n\tprotected final List<FieldMember> fields;\n\tprotected final List<MethodMember> methods;\n\tprotected final List<InnerClassInfo> innerClasses;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull class information from.\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph of the workspace.\n\t * @param classAccess\n\t * \t\tHost class access modifiers.\n\t * @param className\n\t * \t\tHost class name.\n\t * @param superName\n\t * \t\tHost class super name.\n\t * @param implementing\n\t * \t\tHost class interfaces implemented.\n\t * @param fields\n\t * \t\tHost class declared fields.\n\t * @param methods\n\t * \t\tHost class declared methods.\n\t * @param innerClasses\n\t * \t\tHost class declared inner classes.\n\t */\n\tpublic ClassStubGenerator(@Nonnull Workspace workspace,\n\t                          @Nonnull InheritanceGraph inheritanceGraph,\n\t                          int classAccess,\n\t                          @Nonnull String className,\n\t                          @Nullable String superName,\n\t                          @Nonnull List<String> implementing,\n\t                          @Nonnull List<FieldMember> fields,\n\t                          @Nonnull List<MethodMember> methods,\n\t                          @Nonnull List<InnerClassInfo> innerClasses) {\n\t\tthis.workspace = workspace;\n\t\tthis.inheritanceGraph = inheritanceGraph;\n\t\tthis.classAccess = classAccess;\n\t\tthis.className = isSafeInternalClassName(className) ? className : \"obfuscated_class\";\n\t\tthis.superName = isSafeReferencableName(superName) ? superName : null;\n\t\tthis.implementing = implementing.stream()\n\t\t\t\t.filter(this::isSafeReferencableName)\n\t\t\t\t.toList();\n\t\tthis.fields = fields;\n\t\tthis.methods = methods;\n\t\tthis.innerClasses = innerClasses;\n\t}\n\n\t/**\n\t * @return Generated stub for the target class.\n\t *\n\t * @throws ExpressionCompileException\n\t * \t\tWhen the class could not be fully stubbed out.\n\t */\n\tpublic abstract String generate() throws ExpressionCompileException;\n\n\t/**\n\t * Appends a package declaration if the {@link #className} is not in the default package.\n\t *\n\t * @param code\n\t * \t\tClass code to append package declaration to.\n\t */\n\tprotected void appendPackage(@Nonnull StringBuilder code) {\n\t\tif (className.indexOf('/') > 0) {\n\t\t\tString packageName = className.replace('/', '.').substring(0, className.lastIndexOf('/'));\n\t\t\tcode.append(\"package \").append(packageName).append(\";\\n\");\n\t\t}\n\t}\n\n\t/**\n\t * Appends the class's access modifiers, type (class, interface, enum), name, extended type, and any implemented interfaces.\n\t *\n\t * @param code\n\t * \t\tClass code to append the class type structure to.\n\t */\n\tprotected void appendClassStructure(@Nonnull StringBuilder code) {\n\t\t// Class structure\n\t\tInheritanceVertex classVertex = inheritanceGraph.getVertex(className);\n\t\tif (classVertex != null && classVertex.getParents().stream().anyMatch(this::isSealedType))\n\t\t\tcode.append(\"non-sealed \");\n\t\tcode.append(AccessFlag.isEnum(classAccess) ? \"enum \" : getLocalModifier() + \" class \").append(getLocalName());\n\t\tif (superName != null && !superName.equals(\"java/lang/Object\") && !superName.equals(\"java/lang/Enum\"))\n\t\t\tcode.append(\" extends \").append(superName.replace('/', '.'));\n\t\tif (implementing != null && !implementing.isEmpty())\n\t\t\tcode.append(\" implements \").append(implementing.stream().map(s -> s.replace('/', '.')).collect(Collectors.joining(\", \"))).append(' ');\n\t\tcode.append(\"{\\n\");\n\t}\n\n\t/**\n\t * Appends enum constants defined in {@link #fields} to the class.\n\t * Must be called before {@link #appendFields(StringBuilder)}.\n\t *\n\t * @param code\n\t * \t\tClass code to append enum constants to.\n\t */\n\tprotected void appendEnumConsts(@Nonnull StringBuilder code) {\n\t\t// Enum constants must come first if the class is an enum.\n\t\tif (AccessFlag.isEnum(classAccess)) {\n\t\t\tint enumConsts = 0;\n\t\t\tfor (FieldMember field : fields) {\n\t\t\t\tif (isEnumConst(field)) {\n\t\t\t\t\tif (enumConsts > 0)\n\t\t\t\t\t\tcode.append(\", \");\n\t\t\t\t\tcode.append(field.getName());\n\t\t\t\t\tenumConsts++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcode.append(';');\n\t\t}\n\t}\n\n\t/**\n\t * Appends all non-enum constant fields to the class.\n\t *\n\t * @param code\n\t * \t\tClass code to append the fields to.\n\t *\n\t * @throws ExpressionCompileException\n\t * \t\tWhen the fields could not be stubbed out.\n\t */\n\tprotected void appendFields(@Nonnull StringBuilder code) throws ExpressionCompileException {\n\t\t// Stub out fields / methods\n\t\tfor (FieldMember field : fields) {\n\t\t\t// Skip stubbing compiler-generated fields.\n\t\t\tif (field.hasBridgeModifier() || field.hasSyntheticModifier())\n\t\t\t\tcontinue;\n\n\t\t\t// Skip enum constants, we added those earlier.\n\t\t\tif (isEnumConst(field))\n\t\t\t\tcontinue;\n\n\t\t\t// Skip stubbing of illegally named fields.\n\t\t\tString name = field.getName();\n\t\t\tif (!isSafeName(name))\n\t\t\t\tcontinue;\n\t\t\tNameType fieldNameType = getInfo(name, field.getDescriptor());\n\t\t\tif (!isSafeClassName(fieldNameType.className))\n\t\t\t\tcontinue;\n\n\t\t\t// Skip fields with types that aren't accessible in the workspace.\n\t\t\tif (isMissingType(field.getDescriptor()))\n\t\t\t\tcontinue;\n\n\t\t\t// Append the field. The only modifier that we care about here is if it is static or not.\n\t\t\tif (field.hasStaticModifier())\n\t\t\t\tcode.append(\"static \");\n\t\t\tcode.append(fieldNameType.className).append(' ').append(fieldNameType.name).append(\";\\n\");\n\t\t}\n\t}\n\n\t/**\n\t * Appends all method stubs to the class.\n\t * Some methods can be skipped by implementing {@link #doSkipMethod(String, MethodType)}.\n\t *\n\t * @param code\n\t * \t\tClass code to append the methods to.\n\t *\n\t * @throws ExpressionCompileException\n\t * \t\tWhen the methods could not be stubbed out.\n\t */\n\tprotected void appendMethods(@Nonnull StringBuilder code) throws ExpressionCompileException {\n\t\tboolean isEnum = AccessFlag.isEnum(classAccess);\n\t\tfor (MethodMember method : methods) {\n\t\t\t// Skip stubbing compiler-generated methods.\n\t\t\tif (method.hasBridgeModifier() || method.hasSyntheticModifier())\n\t\t\t\tcontinue;\n\n\t\t\t// Skip stubbing of illegally named methods.\n\t\t\tString name = method.getName();\n\t\t\tboolean isCtor = false;\n\t\t\tif (name.equals(\"<init>\")) {\n\t\t\t\t// Skip constructors for enum classes since we always drop enum const parameters.\n\t\t\t\tif (isEnum)\n\t\t\t\t\tcontinue;\n\t\t\t\tisCtor = true;\n\t\t\t} else if (!isSafeName(name))\n\t\t\t\tcontinue;\n\n\t\t\t// Skip stubbing the method if it is the one we're assembling the expression within.\n\t\t\tString descriptor = method.getDescriptor();\n\t\t\tMethodType localMethodType = Types.methodType(descriptor);\n\t\t\tif (doSkipMethod(name, localMethodType))\n\t\t\t\tcontinue;\n\n\t\t\t// Skip enum's 'valueOf' + 'values'\n\t\t\tif (isEnum &&\n\t\t\t\t\tname.equals(\"valueOf\") &&\n\t\t\t\t\tdescriptor.equals(\"(Ljava/lang/String;)L\" + className + \";\"))\n\t\t\t\tcontinue;\n\t\t\tif (isEnum &&\n\t\t\t\t\tname.equals(\"values\") &&\n\t\t\t\t\tdescriptor.equals(\"()[L\" + className + \";\"))\n\t\t\t\tcontinue;\n\n\t\t\t// Skip stubbing of methods with bad return types / bad parameter types.\n\t\t\tNameType returnInfo = getInfo(name, localMethodType.returnType().descriptor());\n\t\t\tif (!isSafeClassName(returnInfo.className))\n\t\t\t\tcontinue;\n\t\t\tList<ClassType> parameterTypes = localMethodType.parameterTypes();\n\t\t\tif (!parameterTypes.stream().map(p -> {\n\t\t\t\ttry {\n\t\t\t\t\treturn getInfo(\"p\", p.descriptor()).className();\n\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\treturn \"\\0\"; // Bogus which will throw off the safe name check.\n\t\t\t\t}\n\t\t\t}).allMatch(ClassStubGenerator::isSafeClassName))\n\t\t\t\tcontinue;\n\n\t\t\t// Skip methods with return/parameter types that aren't accessible in the workspace.\n\t\t\tboolean hasMissingType = false;\n\t\t\tType[] types = new Type[parameterTypes.size() + 1];\n\t\t\tfor (int i = 0; i < types.length - 1; i++)\n\t\t\t\ttypes[i] = parameterTypes.get(i);\n\t\t\ttypes[parameterTypes.size()] = localMethodType.returnType();\n\t\t\tfor (Type type : types) {\n\t\t\t\thasMissingType = isMissingType(type);\n\t\t\t\tif (hasMissingType)\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (hasMissingType) continue;\n\n\t\t\t// Stub the method. Start with the access modifiers.\n\t\t\tif (method.hasPublicModifier())\n\t\t\t\tcode.append(\"public \");\n\t\t\telse if (method.hasProtectedModifier())\n\t\t\t\tcode.append(\"protected \");\n\t\t\telse if (method.hasPrivateModifier())\n\t\t\t\tcode.append(\"private \");\n\t\t\tif (method.hasStaticModifier())\n\t\t\t\tcode.append(\"static \");\n\n\t\t\t// Method name. Consider edge case for constructors.\n\t\t\tif (isCtor)\n\t\t\t\tcode.append(getLocalName()).append('(');\n\t\t\telse\n\t\t\t\tcode.append(returnInfo.className()).append(' ').append(returnInfo.name).append('(');\n\n\t\t\t// Add the parameters. We only care about the types, names don't really matter.\n\t\t\tList<ClassType> methodParameterTypes = parameterTypes;\n\t\t\tint parameterCount = methodParameterTypes.size();\n\t\t\tfor (int i = 0; i < parameterCount; i++) {\n\t\t\t\tClassType paramType = methodParameterTypes.get(i);\n\n\t\t\t\t// Skip this parameter if it is an inner class's outer \"this\" reference\n\t\t\t\tif (isCtor\n\t\t\t\t\t\t&& paramType instanceof ObjectType paramObjectType\n\t\t\t\t\t\t&& className.startsWith(paramObjectType.internalName() + '$'))\n\t\t\t\t\tcontinue;\n\n\t\t\t\tNameType paramInfo = getInfo(\"p\" + i, paramType.descriptor());\n\t\t\t\tcode.append(paramInfo.className).append(' ').append(paramInfo.name);\n\t\t\t\tif (i < parameterCount - 1)\n\t\t\t\t\tcode.append(\", \");\n\t\t\t}\n\t\t\tcode.append(\") { \");\n\t\t\tif (isCtor) {\n\t\t\t\t// If we know the parent type, we need to properly implement the constructor.\n\t\t\t\t// If we don't know the parent type, we cannot generate a valid constructor.\n\t\t\t\tClassPathNode superPath = superName == null ? null : workspace.findJvmClass(superName);\n\t\t\t\tif (superPath == null && superName != null)\n\t\t\t\t\t// Generally this shouldn't happen since we filter the super-name in the constructor.\n\t\t\t\t\t// But just in case we'll keep this error handling here.\n\t\t\t\t\tthrow new ExpressionCompileException(\"Cannot generate 'super(...)' for constructor, \" +\n\t\t\t\t\t\t\t\"missing type information for: \" + superName);\n\t\t\t\tif (superPath != null) {\n\t\t\t\t\t// To make it easy, we'll find the simplest constructor in the parent class and pass dummy values.\n\t\t\t\t\t// Unlike regular methods we cannot just say 'throw new RuntimeException();' since calling\n\t\t\t\t\t// the 'super(...)' is required.\n\t\t\t\t\tMethodType parentConstructor = superPath.getValue().methodStream()\n\t\t\t\t\t\t\t.filter(m -> m.getName().equals(\"<init>\"))\n\t\t\t\t\t\t\t.map(m -> Types.methodType(m.getDescriptor()))\n\t\t\t\t\t\t\t.min(Comparator.comparingInt(a -> a.parameterTypes().size()))\n\t\t\t\t\t\t\t.orElse(null);\n\t\t\t\t\tif (parentConstructor != null) {\n\t\t\t\t\t\tcode.append(\"super(\");\n\t\t\t\t\t\tparameterCount = parentConstructor.parameterTypes().size();\n\t\t\t\t\t\tfor (int i = 0; i < parameterCount; i++) {\n\t\t\t\t\t\t\tClassType type = parentConstructor.parameterTypes().get(i);\n\t\t\t\t\t\t\tif (type instanceof ObjectType) {\n\t\t\t\t\t\t\t\tcode.append(\"null\");\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tchar prim = type.descriptor().charAt(0);\n\t\t\t\t\t\t\t\tif (prim == 'Z')\n\t\t\t\t\t\t\t\t\tcode.append(\"false\");\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t\tcode.append('0');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (i < parameterCount - 1) code.append(\", \");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcode.append(\");\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcode.append(\"throw new RuntimeException();\");\n\t\t\t}\n\t\t\tcode.append(\" }\\n\");\n\t\t}\n\t}\n\n\n\t/**\n\t * @param code\n\t * \t\tClass code to append the inner classes to.\n\t *\n\t * @throws ExpressionCompileException\n\t * \t\tWhen the inner classes could not be stubbed out.\n\t */\n\tprotected void appendInnerClasses(@Nonnull StringBuilder code) throws ExpressionCompileException {\n\t\tfor (InnerClassInfo innerClass : innerClasses) {\n\t\t\tString innerClassName = innerClass.getInnerClassName();\n\t\t\tif (!innerClassName.startsWith(className))\n\t\t\t\tcontinue;\n\t\t\tif (innerClassName.length() <= className.length())\n\t\t\t\tcontinue;\n\t\t\tif (!isSafeClassName(innerClassName.replace('/', '.').replace('$', '.')))\n\t\t\t\tcontinue;\n\t\t\tClassPathNode innerClassPath = workspace.findClass(innerClassName);\n\t\t\tif (innerClassPath != null) {\n\t\t\t\tClassInfo innerClassInfo = innerClassPath.getValue();\n\t\t\t\tClassStubGenerator generator = new InnerClassStubGenerator(workspace, inheritanceGraph,\n\t\t\t\t\t\t// Bitwise or the flags together since we need to know if the inner class is static.\n\t\t\t\t\t\t// The inner class attribute will say whether it is or not, but the actual class will not.\n\t\t\t\t\t\tinnerClassInfo.getAccess() | (innerClass.getInnerAccess() & Modifier.STATIC),\n\t\t\t\t\t\tinnerClassInfo.getName(),\n\t\t\t\t\t\tinnerClassInfo.getSuperName(),\n\t\t\t\t\t\tinnerClassInfo.getInterfaces(),\n\t\t\t\t\t\tinnerClassInfo.getFields(),\n\t\t\t\t\t\tinnerClassInfo.getMethods(),\n\t\t\t\t\t\tinnerClassInfo.getInnerClasses()\n\t\t\t\t);\n\t\t\t\tString inner = generator.generate();\n\t\t\t\tcode.append('\\n').append(inner).append('\\n');\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Ends the class definition.\n\t *\n\t * @param code\n\t * \t\tClass code to append end to.\n\t */\n\tprotected void appendClassEnd(@Nonnull StringBuilder code) {\n\t\t// Done with the class\n\t\tcode.append(\"}\\n\");\n\t}\n\n\t/**\n\t * Controls which methods are included in {@link #appendMethods(StringBuilder)}.\n\t *\n\t * @param name\n\t * \t\tMethod name.\n\t * @param type\n\t * \t\tMethod type.\n\t *\n\t * @return {@code true} to skip. {@code false} to include in output stubbing.\n\t */\n\tprotected abstract boolean doSkipMethod(@Nonnull String name, @Nonnull MethodType type);\n\n\t/**\n\t * @return Modifier to prefix {@code Foo} in {@code class Foo {}}.\n\t */\n\t@Nonnull\n\tpublic String getLocalModifier() {\n\t\treturn \"abstract\";\n\t}\n\n\t/**\n\t * @return Name string to where {@code Foo} is in {@code class Foo {}}.\n\t */\n\t@Nonnull\n\tprotected String getLocalName() {\n\t\treturn StringUtil.shortenPath(className);\n\t}\n\n\t/**\n\t * @param field\n\t * \t\tField to check.\n\t *\n\t * @return {@code true} when it represents an enum constant.\n\t */\n\tprotected boolean isEnumConst(@Nonnull FieldMember field) {\n\t\t// This class must be an enum.\n\t\tif (!AccessFlag.isEnum(classAccess))\n\t\t\treturn false;\n\n\t\t// The field must be 'public static final'\n\t\tif (!field.hasFinalModifier() || !field.hasStaticModifier() || !field.hasPublicModifier())\n\t\t\treturn false;\n\n\t\t// The descriptor must be: L + className + ;\n\t\tif (field.getDescriptor().length() != className.length() + 2)\n\t\t\treturn false;\n\t\tInstanceType fieldDesc = Types.instanceTypeFromDescriptor(field.getDescriptor());\n\t\treturn fieldDesc.internalName().equals(className);\n\t}\n\n\t/**\n\t * @param vertex\n\t * \t\tInheritance vertex to check.\n\t *\n\t * @return {@code true} if the type is sealed <i>(Defines any permitted subclass)</i>.\n\t */\n\tprivate boolean isSealedType(@Nonnull InheritanceVertex vertex) {\n\t\tif (vertex.getValue() instanceof JvmClassInfo cls) {\n\t\t\tAtomicBoolean result = new AtomicBoolean(false);\n\t\t\tcls.getClassReader().accept(new SkippingClassVisitor() {\n\t\t\t\t@Override\n\t\t\t\tpublic void visitPermittedSubclass(String permittedSubclass) {\n\t\t\t\t\tresult.set(true);\n\t\t\t\t}\n\t\t\t}, ClassReader.SKIP_DEBUG);\n\t\t\treturn result.get();\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param descriptor\n\t * \t\tSome non-method descriptor.\n\t *\n\t * @return {@code true} if the type in the descriptor is found in the {@link #workspace}.\n\t */\n\tprotected boolean isMissingType(@Nonnull String descriptor) {\n\t\tType type = Types.typeFromDescriptor(descriptor);\n\t\treturn isMissingType(type);\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tSome non-method type.\n\t *\n\t * @return {@code true} if the type in the descriptor is found in the {@link #workspace}.\n\t */\n\tprotected boolean isMissingType(@Nonnull Type type) {\n\t\tif (type instanceof InstanceType instanceType && workspace.findClass(instanceType.internalName()) == null)\n\t\t\treturn true;\n\t\telse\n\t\t\treturn type instanceof ArrayType arrayType\n\t\t\t\t\t&& arrayType.rootComponentType() instanceof InstanceType instanceType\n\t\t\t\t\t&& workspace.findClass(instanceType.internalName()) == null;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tClass name to check.\n\t *\n\t * @return The class name if it is safe to reference, otherwise {@code null}.\n\t */\n\tprivate boolean isSafeReferencableName(@Nullable String name) {\n\t\tif (name == null)\n\t\t\treturn false;\n\n\t\t// Must be well-formed\n\t\tif (!isSafeInternalClassName(name))\n\t\t\treturn false;\n\n\t\t// Must be found in the workspace\n\t\treturn workspace.findClass(name) != null;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName to check.\n\t *\n\t * @return {@code true} when it can be used as a variable name safely.\n\t */\n\tprotected static boolean isSafeName(@Nonnull String name) {\n\t\t// Name must not be empty.\n\t\tif (name.isEmpty())\n\t\t\treturn false;\n\n\t\t// Must be comprised of valid identifier characters.\n\t\tchar first = name.charAt(0);\n\t\tif (!Character.isJavaIdentifierStart(first))\n\t\t\treturn false;\n\t\tchar[] chars = name.toCharArray();\n\t\tfor (int i = 1; i < chars.length; i++) {\n\t\t\tif (!Character.isJavaIdentifierPart(chars[i]))\n\t\t\t\treturn false;\n\t\t}\n\n\t\t// Cannot be a reserved keyword.\n\t\treturn !Keywords.getKeywords().contains(name);\n\t}\n\n\t/**\n\t * @param internalName\n\t * \t\tName to check. Expected to be in the internal format. IE {@code java/lang/String}.\n\t *\n\t * @return {@code true} when it can be used as a class name safely.\n\t */\n\tprotected static boolean isSafeInternalClassName(@Nonnull String internalName) {\n\t\t// Sanity check input\n\t\tif (internalName.indexOf('.') >= 0)\n\t\t\tthrow new IllegalStateException(\"Saw source name format, expected internal name format\");\n\n\t\t// Extending record directly is not allowed\n\t\tif (\"java/lang/Record\".equals(internalName))\n\t\t\treturn false;\n\n\t\t// All package name portions and the class name must be valid names.\n\t\treturn StringUtil.fastSplit(internalName, true, '/').stream()\n\t\t\t\t.allMatch(ClassStubGenerator::isSafeName);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName to check. Expected to be in the source format. IE {@code java.lang.String}.\n\t *\n\t * @return {@code true} when it can be used as a class name safely.\n\t */\n\tprotected static boolean isSafeClassName(@Nonnull String name) {\n\t\t// Sanity check input\n\t\tif (name.indexOf('/') >= 0)\n\t\t\tthrow new IllegalStateException(\"Saw internal name format, expected source name format\");\n\n\t\t// Strip array dimensions\n\t\tif (name.endsWith(\"[]\"))\n\t\t\tname = name.substring(0, name.indexOf('['));\n\n\t\t// Allow primitives\n\t\tif (software.coley.recaf.util.Types.isPrimitiveClassName(name))\n\t\t\treturn true;\n\n\t\t// All package name portions and the class name must be valid names.\n\t\treturn StringUtil.fastSplit(name, true, '.').stream()\n\t\t\t\t.allMatch(ClassStubGenerator::isSafeName);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tVariable name.\n\t * @param descriptor\n\t * \t\tVariable descriptor.\n\t *\n\t * @return Variable info wrapper.\n\t *\n\t * @throws ExpressionCompileException\n\t * \t\tWhen the variable descriptor is malformed.\n\t */\n\t@Nonnull\n\tprotected static NameType getInfo(@Nonnull String name, @Nonnull String descriptor) throws ExpressionCompileException {\n\t\tint size;\n\t\tString className;\n\t\tif (Types.isPrimitive(descriptor)) {\n\t\t\tPrimitiveType primitiveType = Types.primitiveFromDesc(descriptor);\n\t\t\tsize = Types.category(primitiveType);\n\t\t\tclassName = primitiveType.name();\n\t\t} else if (descriptor.charAt(0) == '[') {\n\t\t\tArrayType arrayParameterType = Types.arrayTypeFromDescriptor(descriptor);\n\t\t\tClassType componentReturnType = arrayParameterType.componentType();\n\t\t\tif (componentReturnType instanceof PrimitiveType primitiveParameter) {\n\t\t\t\tclassName = primitiveParameter.name();\n\t\t\t} else if (componentReturnType instanceof InstanceType instanceType) {\n\t\t\t\tclassName = instanceType.internalName().replace('/', '.').replace('$', '.');\n\t\t\t} else {\n\t\t\t\tthrow new ExpressionCompileException(\"Illegal component type: \" + componentReturnType);\n\t\t\t}\n\t\t\tclassName += \"[]\".repeat(arrayParameterType.dimensions());\n\t\t\tsize = 1;\n\t\t} else {\n\t\t\tsize = 1;\n\t\t\tclassName = Types.instanceTypeFromDescriptor(descriptor).internalName().replace('/', '.').replace('$', '.');\n\t\t}\n\t\treturn new NameType(size, name, className);\n\t}\n\n\t/**\n\t * Wrapper for field/variable info.\n\t *\n\t * @param size\n\t * \t\tVariable slot size.\n\t * @param name\n\t * \t\tVariable name.\n\t * @param className\n\t * \t\tVariable class type name.\n\t */\n\tprotected record NameType(int size, @Nonnull String name, @Nonnull String className) {\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/stub/ExpressionHostingClassStubGenerator.java",
    "content": "package software.coley.recaf.services.compile.stub;\n\nimport dev.xdark.blw.type.ArrayType;\nimport dev.xdark.blw.type.ClassType;\nimport dev.xdark.blw.type.InstanceType;\nimport dev.xdark.blw.type.MethodType;\nimport dev.xdark.blw.type.PrimitiveType;\nimport dev.xdark.blw.type.Types;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.slf4j.Logger;\nimport regexodus.Matcher;\nimport regexodus.Pattern;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.member.BasicLocalVariable;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.assembler.ExpressionCompileException;\nimport software.coley.recaf.services.assembler.ExpressionCompiler;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceVertex;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * Class stub generator which implements a specific method with a user-defined expression.\n *\n * @author Matt Coley\n * @see ExpressionCompiler#compile(String)\n */\npublic class ExpressionHostingClassStubGenerator extends ClassStubGenerator {\n\tprivate static final Logger logger = Logging.get(ExpressionHostingClassStubGenerator.class);\n\tprivate static final Pattern IMPORT_EXTRACT_PATTERN = RegexUtil.pattern(\"^\\\\s*(import \\\\w.+;)\");\n\tprivate final int methodFlags;\n\tprivate final String methodName;\n\tprivate final MethodType methodType;\n\tprivate final List<LocalVariable> methodVariables;\n\tprivate final String expression;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull class information from.\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph of the workspace.\n\t * @param classAccess\n\t * \t\tHost class access modifiers.\n\t * @param className\n\t * \t\tHost class name.\n\t * @param superName\n\t * \t\tHost class super name.\n\t * @param implementing\n\t * \t\tHost class interfaces implemented.\n\t * @param fields\n\t * \t\tHost class declared fields.\n\t * @param methods\n\t * \t\tHost class declared methods.\n\t * @param innerClasses\n\t * \t\tHost class declared inner classes.\n\t * @param methodFlags\n\t * \t\tExpression hosting method's access modifiers.\n\t * @param methodName\n\t * \t\tExpression hosting method's name.\n\t * @param methodType\n\t * \t\tExpression hosting method arguments + return type.\n\t * @param methodVariables\n\t * \t\tExpression hosting method's local variables.\n\t * @param expression\n\t * \t\tThe expression to insert into the target hosting method.\n\t */\n\tpublic ExpressionHostingClassStubGenerator(@Nonnull Workspace workspace,\n\t                                           @Nonnull InheritanceGraph inheritanceGraph,\n\t                                           int classAccess,\n\t                                           @Nonnull String className,\n\t                                           @Nullable String superName,\n\t                                           @Nonnull List<String> implementing,\n\t                                           @Nonnull List<FieldMember> fields,\n\t                                           @Nonnull List<MethodMember> methods,\n\t                                           @Nonnull List<InnerClassInfo> innerClasses,\n\t                                           int methodFlags,\n\t                                           @Nonnull String methodName,\n\t                                           @Nonnull MethodType methodType,\n\t                                           @Nonnull List<LocalVariable> methodVariables,\n\t                                           @Nonnull String expression) {\n\t\tsuper(workspace, inheritanceGraph, classAccess, className, superName, implementing, fields, methods, innerClasses);\n\n\t\t// Map edge cases for disallowed names.\n\t\tif (methodName.equals(\"<init>\"))\n\t\t\tmethodName = \"instance_ctor\";\n\t\telse if (methodName.equals(\"<clinit>\"))\n\t\t\tmethodName = \"static_ctor\";\n\t\telse if (!isSafeName(methodName))\n\t\t\tmethodName = \"obfuscated_method\";\n\t\telse if (AccessFlag.isEnum(classAccess) && isReservedEnumMethodName(methodName))\n\t\t\tmethodName = \"enum_method\";\n\n\t\t// Assign expression host method details\n\t\tthis.methodFlags = methodFlags;\n\t\tthis.methodName = methodName;\n\t\tthis.methodType = methodType;\n\t\tthis.methodVariables = methodVariables;\n\t\tthis.expression = expression;\n\t}\n\n\t@Override\n\tpublic String generate() throws ExpressionCompileException {\n\t\tString localExpression = expression;\n\n\t\tStringBuilder code = new StringBuilder();\n\t\tappendPackage(code);\n\t\tlocalExpression = appendExpressionImports(code, localExpression);\n\t\tappendClassStructure(code);\n\t\tappendEnumConsts(code);\n\t\tappendExpressionMethod(code, localExpression);\n\t\tappendFields(code);\n\t\tappendMethods(code);\n\t\tappendInnerClasses(code);\n\t\tappendClassEnd(code);\n\n\t\treturn code.toString();\n\t}\n\n\t@Override\n\tprotected boolean doSkipMethod(@Nonnull String name, @Nonnull MethodType type) {\n\t\t// We want to skip generating a stub of the method our expression will reside within.\n\t\treturn methodName.equals(name) && methodType.equals(type);\n\t}\n\n\t/**\n\t * @return Adapted method name for compiler-safe use.\n\t */\n\t@Nonnull\n\tpublic String getAdaptedMethodName() {\n\t\treturn methodName;\n\t}\n\n\t/**\n\t * Expressions can contain imports at the top so that the end-user can work without needing fully qualified names.\n\t * We want to take those out and append them to the class we're generating, and update the expression to remove\n\t * the imports so that we can slap it into the method body later without syntax issues coming from imports being\n\t * used in a method body.\n\t *\n\t * @param code\n\t * \t\tClass code to append imports to.\n\t * @param expression\n\t * \t\tExpression to extract imports from.\n\t *\n\t * @return Modified expression <i>(without imports)</i>\n\t */\n\t@Nonnull\n\tprivate String appendExpressionImports(@Nonnull StringBuilder code, @Nonnull String expression) {\n\t\t// Add imports from the user defined expression.\n\t\t// Remove the imports from the expression once copied to the output code.\n\t\tStringBuilder expressionBuffer = new StringBuilder();\n\t\texpression.lines().forEach(l -> {\n\t\t\tMatcher matcher = IMPORT_EXTRACT_PATTERN.matcher(l);\n\t\t\tif (matcher.find()) {\n\t\t\t\tcode.append(matcher.group(1)).append('\\n');\n\t\t\t} else {\n\t\t\t\texpressionBuffer.append(l).append('\\n');\n\t\t\t}\n\t\t});\n\t\treturn expressionBuffer.toString();\n\t}\n\n\t/**\n\t * @param code\n\t * \t\tClass code to append method definition to.\n\t * @param expression\n\t * \t\tUser-defined expression.\n\t *\n\t * @throws ExpressionCompileException\n\t * \t\tWhen the expression hosting method could not be fully generated.\n\t */\n\tprivate void appendExpressionMethod(@Nonnull StringBuilder code, @Nonnull String expression) throws ExpressionCompileException {\n\t\t// Need to build the method structure to house the expression.\n\t\t// We'll start off with the access level.\n\t\tint parameterVarIndex = 0;\n\t\tif (AccessFlag.isPublic(methodFlags))\n\t\t\tcode.append(\"public \");\n\t\telse if (AccessFlag.isProtected(methodFlags))\n\t\t\tcode.append(\"protected \");\n\t\telse if (AccessFlag.isPrivate(methodFlags))\n\t\t\tcode.append(\"private \");\n\t\tif (AccessFlag.isStatic(methodFlags))\n\t\t\tcode.append(\"static \");\n\t\telse\n\t\t\tparameterVarIndex++;\n\n\t\t// Add the return type.\n\t\tClassType returnType = methodType.returnType();\n\t\tif (returnType instanceof PrimitiveType primitiveReturn) {\n\t\t\tcode.append(primitiveReturn.name()).append(' ');\n\t\t} else if (returnType instanceof InstanceType instanceType) {\n\t\t\tcode.append(instanceType.internalName().replace('/', '.')).append(' ');\n\t\t} else if (returnType instanceof ArrayType arrayReturn) {\n\t\t\tClassType componentReturnType = arrayReturn.componentType();\n\t\t\tif (componentReturnType instanceof PrimitiveType primitiveReturn) {\n\t\t\t\tcode.append(primitiveReturn.name());\n\t\t\t} else if (componentReturnType instanceof InstanceType instanceType) {\n\t\t\t\tcode.append(instanceType.internalName().replace('/', '.'));\n\t\t\t}\n\t\t\tcode.append(\"[]\".repeat(arrayReturn.dimensions()));\n\t\t}\n\n\t\t// Now the method name.\n\t\tcode.append(' ').append(methodName).append('(');\n\n\t\t// And now the parameters.\n\t\tint parameterCount = methodType.parameterTypes().size();\n\t\tSet<String> usedVariables = new HashSet<>();\n\t\tfor (int i = 0; i < parameterCount; i++) {\n\t\t\t// Lookup the parameter variable\n\t\t\tLocalVariable parameterVariable = getParameterVariable(parameterVarIndex, i);\n\t\t\tString parameterName = parameterVariable.getName();\n\n\t\t\t// Record the parameter as being used\n\t\t\tusedVariables.add(parameterName);\n\n\t\t\t// Skip if the parameter is illegally named.\n\t\t\tif (!isSafeName(parameterName))\n\t\t\t\tcontinue;\n\n\t\t\t// Skip parameters with types that aren't accessible in the workspace.\n\t\t\tString descriptor = parameterVariable.getDescriptor();\n\t\t\tif (isMissingType(descriptor))\n\t\t\t\tcontinue;\n\n\t\t\t// Append the parameter.\n\t\t\tNameType varInfo = getInfo(parameterName, descriptor);\n\t\t\tparameterVarIndex += varInfo.size();\n\t\t\tcode.append(varInfo.className()).append(' ').append(varInfo.name());\n\t\t\tif (i < parameterCount - 1) code.append(\", \");\n\t\t}\n\t\tfor (LocalVariable variable : methodVariables) {\n\t\t\tString name = variable.getName();\n\n\t\t\t// Skip illegal named variables and the implicit 'this'\n\t\t\tif (!isSafeName(name) || name.equals(\"this\"))\n\t\t\t\tcontinue;\n\n\t\t\t// Skip if we already included the parameter in the loop above.\n\t\t\tboolean hasPriorParameters = !usedVariables.isEmpty();\n\t\t\tif (!usedVariables.add(name))\n\t\t\t\tcontinue;\n\n\t\t\t// Skip parameters with types that aren't accessible in the workspace.\n\t\t\tString descriptor = variable.getDescriptor();\n\t\t\tif (isMissingType(descriptor))\n\t\t\t\tcontinue;\n\n\t\t\t// Append the parameter.\n\t\t\tNameType varInfo = getInfo(name, descriptor);\n\t\t\tif (hasPriorParameters)\n\t\t\t\tcode.append(\", \");\n\t\t\tcode.append(varInfo.className()).append(' ').append(varInfo.name());\n\t\t}\n\n\t\t// If we skipped the last parameter for some reason we need to remove the trailing ', ' before closing\n\t\t// off the parameters section.\n\t\tif (code.substring(code.length() - 2).endsWith(\", \"))\n\t\t\tcode.setLength(code.length() - 2);\n\n\t\t// Close off declaration and add a 'throws Throwable' so the user doesn't need to specify try-catch.\n\t\t// If the method is a library method (something we cannot control, like Object.toString()) then\n\t\t// unfortunately we cannot add the 'throws'.\n\t\tInheritanceVertex classVertex = inheritanceGraph.getVertex(className);\n\t\tif (classVertex != null && classVertex.isLibraryMethod(methodName, methodType.descriptor()))\n\t\t\tcode.append(\") { \" + ExpressionCompiler.EXPR_MARKER + \" \\n\");\n\t\telse\n\t\t\tcode.append(\") throws Throwable { \" + ExpressionCompiler.EXPR_MARKER + \" \\n\");\n\t\tcode.append(expression);\n\t\tcode.append(\"}\\n\");\n\t}\n\n\t/**\n\t * <b>Note</b>: The logic for appending parameters to the desc within this method must align with {@link #generate()}.\n\t *\n\t * @return The method descriptor with additional parameters from the {@link #methodVariables} appended at the end.\n\t *\n\t * @throws ExpressionCompileException\n\t * \t\tWhen parameter variable information cannot be found.\n\t */\n\t@Nonnull\n\tpublic String methodDescriptorWithVariables() throws ExpressionCompileException {\n\t\tStringBuilder sb = new StringBuilder(\"(\");\n\t\tint parameterVarIndex = AccessFlag.isStatic(methodFlags) ? 0 : 1;\n\t\tint parameterCount = methodType.parameterTypes().size();\n\t\tSet<String> usedVariables = new HashSet<>();\n\t\tfor (int i = 0; i < parameterCount; i++) {\n\t\t\tLocalVariable parameterVariable = getParameterVariable(parameterVarIndex, i);\n\t\t\tString parameterName = parameterVariable.getName();\n\t\t\tusedVariables.add(parameterName);\n\t\t\tif (!isSafeName(parameterName))\n\t\t\t\tcontinue;\n\t\t\tString descriptor = parameterVariable.getDescriptor();\n\t\t\tif (isMissingType(descriptor))\n\t\t\t\tcontinue;\n\t\t\tNameType varInfo = getInfo(parameterName, descriptor);\n\t\t\tparameterVarIndex += varInfo.size();\n\t\t\tsb.append(descriptor);\n\t\t}\n\t\tfor (LocalVariable variable : methodVariables) {\n\t\t\tString name = variable.getName();\n\t\t\tif (!isSafeName(name) || name.equals(\"this\"))\n\t\t\t\tcontinue;\n\t\t\tif (!usedVariables.add(name))\n\t\t\t\tcontinue;\n\t\t\tString descriptor = variable.getDescriptor();\n\t\t\tif (isMissingType(descriptor))\n\t\t\t\tcontinue;\n\t\t\tsb.append(descriptor);\n\t\t}\n\t\tsb.append(')').append(methodType.returnType().descriptor());\n\t\treturn sb.toString();\n\t}\n\n\t/**\n\t * @param index\n\t * \t\tLocal variable index.\n\t *\n\t * @return Variable entry from the target method, or {@code null} if not known.\n\t */\n\t@Nullable\n\tprivate LocalVariable findVar(int index) {\n\t\tif (methodVariables == null) return null;\n\t\treturn methodVariables.stream()\n\t\t\t\t.filter(l -> l.getIndex() == index)\n\t\t\t\t.findFirst().orElse(null);\n\t}\n\n\t/**\n\t * @param parameterVarIndex\n\t * \t\tLocal variable index of the parameter.\n\t * @param parameterIndex\n\t * \t\tParameter index.\n\t *\n\t * @return Local variable info of the parameter.\n\t */\n\t@Nonnull\n\tprivate LocalVariable getParameterVariable(int parameterVarIndex, int parameterIndex) {\n\t\tLocalVariable parameterVariable = findVar(parameterVarIndex);\n\t\tif (parameterVariable == null) {\n\t\t\tList<ClassType> parameterTypes = methodType.parameterTypes();\n\t\t\tClassType parameterType;\n\t\t\tif (parameterIndex < parameterTypes.size()) {\n\t\t\t\tparameterType = parameterTypes.get(parameterIndex);\n\t\t\t} else {\n\t\t\t\tlogger.warn(\"Could not resolve parameter variable (pVar={}, pIndex={}) in {}\", parameterVarIndex, parameterIndex, methodName);\n\t\t\t\tparameterType = Types.OBJECT;\n\t\t\t}\n\t\t\tparameterVariable = new BasicLocalVariable(parameterVarIndex, \"p\" + parameterIndex, parameterType.descriptor(), null);\n\n\t\t}\n\t\treturn parameterVariable;\n\t}\n\n\tprivate static boolean isReservedEnumMethodName(@Nonnull String methodName) {\n\t\treturn methodName.equals(\"values\")\n\t\t\t\t|| methodName.equals(\"valueOf\")\n\t\t\t\t|| methodName.equals(\"ordinal\")\n\t\t\t\t|| methodName.equals(\"name\")\n\t\t\t\t|| methodName.equals(\"describeConstable\")\n\t\t\t\t|| methodName.equals(\"compareTo\")\n\t\t\t\t|| methodName.equals(\"equals\")\n\t\t\t\t|| methodName.equals(\"hashCode\");\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/compile/stub/InnerClassStubGenerator.java",
    "content": "package software.coley.recaf.services.compile.stub;\n\nimport dev.xdark.blw.type.MethodType;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.assembler.ExpressionCompileException;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.List;\n\n/**\n * Class stub generator which emits classes under the assumption they are inner classes of an outer class.\n *\n * @author Matt Coley\n */\npublic class InnerClassStubGenerator extends ClassStubGenerator {\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull class information from.\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph of the workspace.\n\t * @param classAccess\n\t * \t\tHost class access modifiers.\n\t * @param className\n\t * \t\tHost class name.\n\t * @param superName\n\t * \t\tHost class super name.\n\t * @param implementing\n\t * \t\tHost class interfaces implemented.\n\t * @param fields\n\t * \t\tHost class declared fields.\n\t * @param methods\n\t * \t\tHost class declared methods.\n\t * @param innerClasses\n\t * \t\tHost class declared inner classes.\n\t */\n\tpublic InnerClassStubGenerator(@Nonnull Workspace workspace,\n\t                               @Nonnull InheritanceGraph inheritanceGraph,\n\t                               int classAccess,\n\t                               @Nonnull String className,\n\t                               @Nullable String superName,\n\t                               @Nonnull List<String> implementing,\n\t                               @Nonnull List<FieldMember> fields,\n\t                               @Nonnull List<MethodMember> methods,\n\t                               @Nonnull List<InnerClassInfo> innerClasses) {\n\t\tsuper(workspace, inheritanceGraph, classAccess, className, superName, implementing, fields, methods, innerClasses);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected String getLocalName() {\n\t\t// Will be \"OuterClass$TheInner\"\n\t\tString localName = super.getLocalName();\n\n\t\t// We just want \"TheInner\"\n\t\tint innerSplit = localName.indexOf('$');\n\t\tif (innerSplit > 0)\n\t\t\tlocalName = localName.substring(innerSplit + 1);\n\n\t\treturn localName;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getLocalModifier() {\n\t\tStringBuilder sb = new StringBuilder();\n\n\t\t// I've seen this happen in Recaf but cannot reproduce a case outside.\n\t\t// https://stackoverflow.com/questions/19481680/error-illegal-static-declaration-in-inner-class\n\t\tif (AccessFlag.isStatic(classAccess))\n\t\t\tsb.append(\"static\");\n\t\tif (AccessFlag.isAbstract(classAccess))\n\t\t\tsb.append(\" abstract\");\n\n\t\t// If the inner class (this context) is not abstract, we do not want to force\n\t\t// it to be abstract in order to allow expressions to do \"new Inner()\" and stuff.\n\t\treturn sb.toString().trim();\n\t}\n\n\t@Override\n\tpublic String generate() throws ExpressionCompileException {\n\t\tStringBuilder code = new StringBuilder();\n\n\t\tappendClassStructure(code);\n\t\tappendEnumConsts(code);\n\t\tappendFields(code);\n\t\tappendMethods(code);\n\t\tappendInnerClasses(code);\n\t\tappendClassEnd(code);\n\n\t\treturn code.toString();\n\t}\n\n\t@Override\n\tprotected boolean doSkipMethod(@Nonnull String name, @Nonnull MethodType type) {\n\t\t// Do not skip any methods\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/config/ConfigManager.java",
    "content": "package software.coley.recaf.services.config;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonPrimitive;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonWriter;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.PreDestroy;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.event.Observes;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.cdi.InitializationEvent;\nimport software.coley.recaf.config.ConfigCollectionValue;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.config.RestoreAwareConfigContainer;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.ServiceConfig;\nimport software.coley.recaf.services.file.RecafDirectoriesConfig;\nimport software.coley.recaf.services.json.GsonProvider;\nimport software.coley.recaf.util.TestEnvironment;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.TreeMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * Tracker for all {@link ConfigContainer} instances.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ConfigManager implements Service {\n\tpublic static final String SERVICE_ID = \"config-manager\";\n\tprivate static final Logger logger = Logging.get(ConfigManager.class);\n\tprivate final Map<String, ConfigContainer> containers = new TreeMap<>();\n\tprivate final List<ManagedConfigListener> listeners = new CopyOnWriteArrayList<>();\n\tprivate final ConfigManagerConfig config;\n\tprivate final RecafDirectoriesConfig fileConfig;\n\tprivate final GsonProvider gsonProvider;\n\n\t@Inject\n\tpublic ConfigManager(@Nonnull ConfigManagerConfig config, @Nonnull RecafDirectoriesConfig fileConfig,\n\t                     @Nonnull GsonProvider gsonProvider, @Nonnull Instance<ConfigContainer> containers) {\n\t\tthis.config = config;\n\t\tthis.fileConfig = fileConfig;\n\t\tthis.gsonProvider = gsonProvider;\n\t\tfor (ConfigContainer container : containers)\n\t\t\tregisterContainer(container);\n\t}\n\n\tprivate void init(@Observes InitializationEvent event) {\n\t\tload();\n\t}\n\n\t@PreDestroy\n\tprivate void save() {\n\t\t// Skip persisting in test environments\n\t\tif (TestEnvironment.isTestEnv())\n\t\t\treturn;\n\n\t\tGson gson = gsonProvider.getGson();\n\t\tfor (ConfigContainer container : containers.values()) {\n\t\t\t// Skip writing empty containers\n\t\t\tif (container.getValues().isEmpty())\n\t\t\t\tcontinue;\n\n\t\t\t// Model the vales into a single object.\n\t\t\tJsonObject json = new JsonObject();\n\t\t\tfor (ConfigValue<?> configValue : container.getValues().values()) {\n\t\t\t\ttry {\n\t\t\t\t\tjson.add(configValue.getId(), gson.toJsonTree(configValue.getValue()));\n\t\t\t\t} catch (IllegalArgumentException e) {\n\t\t\t\t\tlogger.error(\"Could not find adapter for type: {}\", configValue.getType(), e);\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\tlogger.error(\"Failed to save config value: {}\", configValue.getId(), e);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Write the appropriate path based on the container id.\n\t\t\tString key = container.getGroupAndId();\n\t\t\tPath containerPath = fileConfig.getConfigDirectory().resolve(key + \".json\");\n\t\t\ttry (JsonWriter writer = gson.newJsonWriter(Files.newBufferedWriter(containerPath))) {\n\t\t\t\tgson.toJson(json, writer);\n\t\t\t} catch (IOException e) {\n\t\t\t\tlogger.error(\"Failed to save config container: {}\", key, e);\n\t\t\t}\n\t\t}\n\t}\n\n\t@SuppressWarnings({\"raw\", \"rawtypes\"})\n\tprivate void load() {\n\t\t// Skip loading in test environments\n\t\tif (TestEnvironment.isTestEnv())\n\t\t\treturn;\n\n\t\tGson gson = gsonProvider.getGson();\n\t\tfor (ConfigContainer container : containers.values()) {\n\t\t\tString key = container.getGroupAndId();\n\t\t\tPath containerPath = fileConfig.getConfigDirectory().resolve(key + \".json\");\n\t\t\tif (!Files.exists(containerPath)) {\n\t\t\t\tif (container instanceof RestoreAwareConfigContainer listener)\n\t\t\t\t\tlistener.onNoRestore();\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tJsonObject json;\n\t\t\ttry (JsonReader reader = gson.newJsonReader(Files.newBufferedReader(containerPath))) {\n\t\t\t\tjson = Objects.requireNonNull(gson.fromJson(reader, JsonObject.class));\n\t\t\t} catch (Exception ex) {\n\t\t\t\tlogger.error(\"Failed to load config container: {}\", key, ex);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (ConfigValue value : container.getValues().values()) {\n\t\t\t\tString id = value.getId();\n\n\t\t\t\t// Skip loading if the file doesn't list the entry.\n\t\t\t\tif (!json.has(id))\n\t\t\t\t\tcontinue;\n\n\t\t\t\ttry {\n\t\t\t\t\tloadValue(gson, container, value, json.get(id));\n\t\t\t\t} catch (IllegalArgumentException e) {\n\t\t\t\t\tlogger.error(\"Could not find adapter for type: {}\", value.getType(), e);\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\tlogger.error(\"Failed to load config value: {}.{}\", key, id, e);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Notify the container it has restored its config values from storage.\n\t\t\tif (container instanceof RestoreAwareConfigContainer listener)\n\t\t\t\tlistener.onRestore();\n\t\t}\n\t}\n\n\t@SuppressWarnings({\"unchecked\", \"rawtypes\"})\n\tprivate void loadValue(Gson gson, ConfigContainer container, ConfigValue value, JsonElement element) {\n\t\t// Validate that the value type matches the element type before attempting to load it.\n\t\t// This can happen if the config file is manually edited improperly, or if the config value type was changed between saves.\n\t\tClass<?> valueType = value.getType();\n\t\tif (element.isJsonPrimitive()) {\n\t\t\tJsonPrimitive primitive = element.getAsJsonPrimitive();\n\t\t\tif ((valueType == String.class && !primitive.isString())\n\t\t\t\t\t|| (Number.class.isAssignableFrom(valueType) && !primitive.isNumber())\n\t\t\t\t\t|| (int.class == valueType && !primitive.isNumber())\n\t\t\t\t\t|| (long.class == valueType && !primitive.isNumber())\n\t\t\t\t\t|| (float.class == valueType && !primitive.isNumber())\n\t\t\t\t\t|| (double.class == valueType && !primitive.isNumber())\n\t\t\t\t\t|| (valueType == Character.class && !primitive.isString())\n\t\t\t\t\t|| (valueType == Boolean.class && !primitive.isBoolean())) {\n\t\t\t\tlogger.warn(\"Type mismatch for config value '{}.{}'. Expected {}, but found {}. Skipping value.\",\n\t\t\t\t\t\tcontainer.getGroupAndId(), value.getId(), valueType.getSimpleName(), primitive);\n\t\t\t\treturn;\n\t\t\t}\n\t\t} else if (element.isJsonArray() && !valueType.isArray() && !Collection.class.isAssignableFrom(valueType)) {\n\t\t\tlogger.warn(\"Type mismatch for config value '{}.{}'. Expected {}, but found array. Skipping value.\",\n\t\t\t\t\tcontainer.getGroupAndId(), value.getId(), valueType.getSimpleName());\n\t\t\treturn;\n\t\t} else if (element.isJsonObject() && valueType.isPrimitive()) {\n\t\t\tlogger.warn(\"Type mismatch for config value '{}.{}'. Expected {}, but found object. Skipping value.\",\n\t\t\t\t\tcontainer.getGroupAndId(), value.getId(), valueType.getSimpleName());\n\t\t\treturn;\n\t\t}\n\n\t\t// Now that we know the types are compatible, attempt to load the value.\n\t\tif (value instanceof ConfigCollectionValue ccv) {\n\t\t\tList<Object> list = new ArrayList<>();\n\t\t\tJsonArray array = element.getAsJsonArray();\n\t\t\tfor (JsonElement e : array) {\n\t\t\t\tlist.add(gson.fromJson(e, ccv.getItemType()));\n\t\t\t}\n\t\t\tvalue.setValue(list);\n\t\t} else {\n\t\t\tvalue.setValue(gson.fromJson(element, value.getType()));\n\t\t}\n\t}\n\n\t/**\n\t * @return All registered containers.\n\t */\n\t@Nonnull\n\tpublic Collection<ConfigContainer> getContainers() {\n\t\treturn containers.values();\n\t}\n\n\t/**\n\t * @param container\n\t * \t\tContainer to register.\n\t */\n\tpublic void registerContainer(@Nonnull ConfigContainer container) {\n\t\tString id = container.getId();\n\t\tif (containers.containsKey(id))\n\t\t\tthrow new IllegalStateException(\"Container by ID '\" + id + \"' already registered\");\n\t\tcontainers.put(id, container);\n\n\t\t// Alert listeners when content added\n\t\tUnchecked.checkedForEach(listeners, listener -> listener.onRegister(container),\n\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when registering container '{}'\", container.getId(), t));\n\t}\n\n\t/**\n\t * @param container\n\t * \t\tContainer to unregister.\n\t */\n\tpublic void unregisterContainer(@Nonnull ConfigContainer container) {\n\t\tConfigContainer removed = containers.remove(container.getId());\n\n\t\t// Alert listeners when content removed\n\t\tif (removed != null) {\n\t\t\tUnchecked.checkedForEach(listeners, listener -> listener.onUnregister(container),\n\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when unregistering container '{}'\", container.getId(), t));\n\t\t}\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tpublic void addManagedConfigListener(@Nonnull ManagedConfigListener listener) {\n\t\tPrioritySortable.add(listeners, listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t *\n\t * @return {@code true} when the listener was removed.\n\t * {@code false} when it wasn't added in the first place.\n\t */\n\tpublic boolean removeManagedConfigListener(@Nonnull ManagedConfigListener listener) {\n\t\treturn listeners.remove(listener);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ServiceConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/config/ConfigManagerConfig.java",
    "content": "package software.coley.recaf.services.config;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link ConfigManager}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ConfigManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic ConfigManagerConfig() {\n\t\tsuper(ConfigGroups.SERVICE, ConfigManager.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/config/ManagedConfigListener.java",
    "content": "package software.coley.recaf.services.config;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.config.ConfigContainer;\n\n/**\n * Listenr for {@link ConfigManager#registerContainer(ConfigContainer)} and\n * {@link ConfigManager#unregisterContainer(ConfigContainer)} calls.\n *\n * @author Matt Coley\n */\npublic interface ManagedConfigListener extends PrioritySortable {\n\t/**\n\t * @param container\n\t * \t\tRegistered config.\n\t */\n\tvoid onRegister(@Nonnull ConfigContainer container);\n\n\t/**\n\t * @param container\n\t * \t\tUnregistered config.\n\t */\n\tvoid onUnregister(@Nonnull ConfigContainer container);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractAndroidDecompiler.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Basic setup for {@link AndroidDecompiler}.\n *\n * @author Matt Coley\n */\npublic abstract class AbstractAndroidDecompiler extends AbstractDecompiler implements AndroidDecompiler {\n\t/**\n\t * @param name\n\t * \t\tDecompiler name.\n\t * @param version\n\t * \t\tDecompiler version.\n\t * @param config\n\t * \t\tDecompiler configuration.\n\t */\n\tpublic AbstractAndroidDecompiler(@Nonnull String name, @Nonnull String version, @Nonnull DecompilerConfig config) {\n\t\tsuper(name, version, config);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractDecompiler.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.services.decompile.filter.OutputTextFilter;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Base for {@link Decompiler}.\n *\n * @author Matt Coley\n */\npublic abstract class AbstractDecompiler implements Decompiler {\n\tprotected final Set<OutputTextFilter> textFilters = new HashSet<>();\n\tprivate final String name;\n\tprivate final String version;\n\tprivate final DecompilerConfig config;\n\n\t/**\n\t * @param name\n\t * \t\tDecompiler name.\n\t * @param version\n\t * \t\tDecompiler version.\n\t * @param config\n\t * \t\tDecompiler configuration.\n\t */\n\tpublic AbstractDecompiler(@Nonnull String name, @Nonnull String version, @Nonnull DecompilerConfig config) {\n\t\tthis.name = name;\n\t\tthis.version = version;\n\t\tthis.config = config;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getVersion() {\n\t\treturn version;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic DecompilerConfig getConfig() {\n\t\treturn config;\n\t}\n\n\t@Override\n\tpublic boolean addOutputTextFilter(@Nonnull OutputTextFilter filter) {\n\t\treturn textFilters.add(filter);\n\t}\n\n\t@Override\n\tpublic boolean removeOutputTextFilter(@Nonnull OutputTextFilter filter) {\n\t\treturn textFilters.remove(filter);\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tAbstractDecompiler that = (AbstractDecompiler) o;\n\n\t\tif (!name.equals(that.name)) return false;\n\t\tif (!version.equals(that.version)) return false;\n\t\treturn config.equals(that.config);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = name.hashCode();\n\t\tresult = 31 * result + version.hashCode();\n\t\tresult = 31 * result + config.hashCode();\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn getName() + \" - \" + getVersion();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractJvmDecompiler.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.decompile.filter.JvmBytecodeFilter;\nimport software.coley.recaf.services.decompile.filter.OutputTextFilter;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Basic setup for {@link JvmDecompiler}.\n *\n * @author Matt Coley\n */\npublic abstract class AbstractJvmDecompiler extends AbstractDecompiler implements JvmDecompiler {\n\tprivate final List<JvmBytecodeFilter> bytecodeFilters = new ArrayList<>();\n\n\t/**\n\t * @param name\n\t * \t\tDecompiler name.\n\t * @param version\n\t * \t\tDecompiler version.\n\t * @param config\n\t * \t\tDecompiler configuration.\n\t */\n\tpublic AbstractJvmDecompiler(@Nonnull String name, @Nonnull String version, @Nonnull DecompilerConfig config) {\n\t\tsuper(name, version, config);\n\t}\n\n\t@Override\n\tpublic boolean addJvmBytecodeFilter(@Nonnull JvmBytecodeFilter filter) {\n\t\treturn bytecodeFilters.add(filter);\n\t}\n\n\t@Override\n\tpublic boolean removeJvmBytecodeFilter(@Nonnull JvmBytecodeFilter filter) {\n\t\treturn bytecodeFilters.remove(filter);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic final DecompileResult decompile(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo) {\n\t\t// Get bytecode and run through filters.\n\t\tJvmClassInfo filteredBytecode = JvmBytecodeFilter.applyFilters(workspace, classInfo, bytecodeFilters);\n\n\t\t// Pass to implementation.\n\t\tDecompileResult result = decompileInternal(workspace, filteredBytecode);\n\n\t\t// Adapt output decompilation if output filters are registered.\n\t\tif (result.getType() == DecompileResult.ResultType.SUCCESS && result.getText() != null && !textFilters.isEmpty()) {\n\t\t\tString text = result.getText();\n\t\t\tfor (OutputTextFilter filter : textFilters)\n\t\t\t\ttext = filter.filter(workspace, classInfo, text);\n\t\t\tresult = result.withText(text);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Takes on the work of {@link #decompile(Workspace, JvmClassInfo)} after the {@link #bytecodeFilters} have been applied to the class.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to pull data from.\n\t * @param classInfo\n\t * \t\tClass to decompile.\n\t *\n\t * @return Decompilation result.\n\t */\n\t@Nonnull\n\tprotected abstract DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo);\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\t\tif (!super.equals(o)) return false;\n\n\t\tAbstractJvmDecompiler other = (AbstractJvmDecompiler) o;\n\n\t\treturn bytecodeFilters.equals(other.bytecodeFilters);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = super.hashCode();\n\t\tresult = 31 * result + bytecodeFilters.hashCode();\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/AndroidDecompiler.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Outline for Android/Dalvik decompile capabilities.\n *\n * @author Matt Coley\n */\npublic interface AndroidDecompiler extends Decompiler {\n\t// Placeholder until more fleshed out API is implemented\n\tDecompileResult decompile(@Nonnull Workspace workspace, @Nonnull AndroidClassInfo classInfo);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/BaseDecompilerConfig.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.config.BasicConfigContainer;\n\nimport static software.coley.recaf.config.ConfigGroups.SERVICE_DECOMPILE_IMPL;\n\n/**\n * Base class for fields needed by all decompiler configurations\n *\n * @author therathatter\n */\npublic class BaseDecompilerConfig extends BasicConfigContainer implements DecompilerConfig {\n\tprivate int hash = 0;\n\n\t/**\n\t * @param id\n\t * \t\tContainer ID.\n\t */\n\tpublic BaseDecompilerConfig(@Nonnull String id) {\n\t\tsuper(SERVICE_DECOMPILE_IMPL, id);\n\t}\n\n\t@Override\n\tpublic int getHash() {\n\t\treturn hash;\n\t}\n\n\t@Override\n\tpublic void setHash(int hash) {\n\t\tthis.hash = hash;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompileResult.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.properties.builtin.CachedDecompileProperty;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.Objects;\n\n/**\n * Result for a {@link Decompiler} output.\n *\n * @author Matt Coley\n */\npublic class DecompileResult {\n\tprivate final String text;\n\tprivate final Throwable exception;\n\tprivate final ResultType type;\n\tprivate final int configHash;\n\n\t/**\n\t * Constructor for a successful decompilation.\n\t *\n\t * @param text\n\t * \t\tDecompiled text.\n\t * @param configHash\n\t * \t\tValue of {@link DecompilerConfig#getHash()} of associated decompiler.\n\t * \t\tUsed to determine if cached value in {@link CachedDecompileProperty} is up-to-date with current config.\n\t */\n\tpublic DecompileResult(@Nonnull String text, int configHash) {\n\t\tthis.text = text;\n\t\tthis.type = ResultType.SUCCESS;\n\t\tthis.configHash = configHash;\n\t\tthis.exception = null;\n\t}\n\n\t/**\n\t * Constructor for a failed decompilation.\n\t *\n\t * @param exception\n\t * \t\tFailure reason.\n\t * @param configHash\n\t * \t\tValue of {@link DecompilerConfig#getHash()} of associated decompiler.\n\t * \t\tUsed to determine if cached value in {@link CachedDecompileProperty} is up-to-date with current config.\n\t */\n\tpublic DecompileResult(@Nonnull Throwable exception, int configHash) {\n\t\tthis.text = \"// \" + StringUtil.traceToString(exception).replace(\"\\n\", \"\\n// \");\n\t\tthis.type = ResultType.FAILURE;\n\t\tthis.configHash = configHash;\n\t\tthis.exception = exception;\n\t}\n\n\t/**\n\t * Constructor for a skipped decompilation.\n\t *\n\t * @param configHash\n\t * \t\tValue of {@link DecompilerConfig#getHash()} of associated decompiler.\n\t * \t\tUsed to determine if cached value in {@link CachedDecompileProperty} is up-to-date with current config.\n\t */\n\tpublic DecompileResult(int configHash) {\n\t\tthis.text = null;\n\t\tthis.type = ResultType.SKIPPED;\n\t\tthis.configHash = configHash;\n\t\tthis.exception = null;\n\t}\n\n\t/**\n\t * Constructor for a skipped decompilation, with pre-defined text.\n\t * Typically used for displaying feedback if the decompiler had an issue or timed out.\n\t *\n\t * @param text\n\t * \t\tDecompiled text.\n\t */\n\tpublic DecompileResult(@Nonnull String text) {\n\t\tthis.text = text;\n\t\tthis.type = ResultType.SKIPPED;\n\t\tthis.configHash = 0;\n\t\tthis.exception = null;\n\t}\n\n\t/**\n\t * Private constructor for wither operations.\n\t *\n\t * @param text\n\t * \t\tDecompiled text.\n\t * @param exception\n\t * \t\tFailure reason.\n\t * @param type\n\t * \t\tResult type.\n\t * @param configHash\n\t * \t\tHash of config used to decompile the code.\n\t */\n\tprivate DecompileResult(String text, Throwable exception, ResultType type, int configHash) {\n\t\tthis.text = text;\n\t\tthis.exception = exception;\n\t\tthis.type = type;\n\t\tthis.configHash = configHash;\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tNew text content.\n\t *\n\t * @return Copy of result, with new text content.\n\t */\n\t@Nonnull\n\tpublic DecompileResult withText(@Nonnull String text) {\n\t\treturn new DecompileResult(text, exception, type, configHash);\n\t}\n\n\t/**\n\t * @return Decompiled text.\n\t * May be {@code null} when {@link #getType()} is not {@link ResultType#SUCCESS}.\n\t */\n\t@Nullable\n\tpublic String getText() {\n\t\treturn text;\n\t}\n\n\t/**\n\t * @return Failure reason.\n\t * May be {@code null} when {@link #getType()} is not {@link ResultType#FAILURE}.\n\t */\n\t@Nullable\n\tpublic Throwable getException() {\n\t\treturn exception;\n\t}\n\n\t/**\n\t * @return Result type.\n\t */\n\t@Nonnull\n\tpublic ResultType getType() {\n\t\treturn type;\n\t}\n\n\t/**\n\t * @return Value of {@link DecompilerConfig#getHash()} of associated decompiler.\n\t * Used to determine if cached value in {@link CachedDecompileProperty} is up-to-date with current config.\n\t */\n\tpublic int getConfigHash() {\n\t\treturn configHash;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tDecompileResult that = (DecompileResult) o;\n\n\t\tif (configHash != that.configHash) return false;\n\t\tif (!Objects.equals(text, that.text)) return false;\n\t\tif (!Objects.equals(exception, that.exception)) return false;\n\t\treturn type == that.type;\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = text != null ? text.hashCode() : 0;\n\t\tresult = 31 * result + (exception != null ? exception.hashCode() : 0);\n\t\tresult = 31 * result + type.hashCode();\n\t\tresult = 31 * result + configHash;\n\t\treturn result;\n\t}\n\n\t/**\n\t * Type of result.\n\t */\n\tpublic enum ResultType {\n\t\t/**\n\t\t * Successful decompilation.\n\t\t */\n\t\tSUCCESS,\n\t\t/**\n\t\t * Decompilation skipped for some reason. Likely due to a thread being cancelled.\n\t\t */\n\t\tSKIPPED,\n\t\t/**\n\t\t * Decompilation failed to emit any output.\n\t\t */\n\t\tFAILURE\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/Decompiler.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.properties.builtin.CachedDecompileProperty;\nimport software.coley.recaf.services.decompile.filter.OutputTextFilter;\n\n/**\n * Common decompiler operations.\n *\n * @author Matt Coley\n * @see JvmDecompiler For decompiling JVM bytecode.\n * @see AndroidDecompiler For decompiling Android/Dalvik bytecode.\n * @see DecompilerConfig For config management of decompiler values,\n * and ensuring {@link CachedDecompileProperty} values are compatible with current settings.\n */\npublic interface Decompiler {\n\t/**\n\t * @return Decompiler name.\n\t */\n\t@Nonnull\n\tString getName();\n\n\t/**\n\t * @return Decompiler version.\n\t */\n\t@Nonnull\n\tString getVersion();\n\n\t/**\n\t * @return Decompiler config.\n\t */\n\t@Nonnull\n\tDecompilerConfig getConfig();\n\n\t/**\n\t * Adds a filter which operates on the decompiler output, before the contents are returned to the user.\n\t *\n\t * @param filter\n\t * \t\tFilter to add.\n\t *\n\t * @return {@code true} on successful addition.\n\t * {@code false} if the filter has already been added.\n\t */\n\tboolean addOutputTextFilter(@Nonnull OutputTextFilter filter);\n\n\t/**\n\t * Removes a filter which operates on the decompiler output, before the contents are returned to the user.\n\t *\n\t * @param filter\n\t * \t\tFilter to remove.\n\t *\n\t * @return {@code true} on successful removal.\n\t * {@code false} if the filter was not already registered.\n\t */\n\tboolean removeOutputTextFilter(@Nonnull OutputTextFilter filter);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompilerConfig.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.properties.builtin.CachedDecompileProperty;\n\n/**\n * Subtype of {@link ConfigContainer} for use by {@link Decompiler} implementations.\n * <br>\n * Tracks the hash of all contained {@link ConfigValue} so that when decompilers check for\n * {@link CachedDecompileProperty} they can see if the {@link DecompileResult#getConfigHash()}\n * matches the current one of {@link #getHash()}.\n *\n * @author Matt Coley\n */\npublic interface DecompilerConfig extends ConfigContainer {\n\t/**\n\t * This value is compared to {@link DecompileResult#getConfigHash()} when a {@link Decompiler} implementation\n\t * looks to decompile a {@link ClassInfo} and finds an existing entry in {@link CachedDecompileProperty}.\n\t * <br>\n\t * If the values match, the cached result can be used.\n\t * Otherwise, the result must be ignored since the config difference can yield a different result.\n\t *\n\t * @return Unique hash of all contained {@link ConfigValue}.\n\t */\n\tint getHash();\n\n\t/**\n\t * @param hash\n\t * \t\tNew hash value.\n\t *\n\t * @see #getHash() For more detail.\n\t */\n\tvoid setHash(int hash);\n\n\t/**\n\t * Called by implementations after they add all their values to the container.\n\t *\n\t * @see #getHash() For more detail.\n\t */\n\tdefault void registerConfigValuesHashUpdates() {\n\t\t// Initial value computation.\n\t\tupdate();\n\n\t\t// Register listeners to ensure hash is up-to-date.\n\t\tgetValues().values().forEach(value ->\n\t\t\t\tvalue.getObservable().addChangeListener((ob, old, cur) -> update()));\n\t}\n\n\tprivate void update() {\n\t\tgetValues().values().stream()\n\t\t\t\t.map(ConfigValue::getValue)\n\t\t\t\t.mapToInt(value -> value == null ? 0 : value.hashCode())\n\t\t\t\t.reduce((a, b) -> (31 * a) + b)\n\t\t\t\t.ifPresent(this::setHash);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompilerManager.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport org.jboss.weld.util.LazyValueHolder;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.ClassWriter;\nimport software.coley.observables.ObservableObject;\nimport software.coley.observables.ObservableString;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.properties.builtin.CachedDecompileProperty;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.decompile.filter.JvmBytecodeFilter;\nimport software.coley.recaf.services.decompile.filter.OutputTextFilter;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.util.visitors.BogusNameRemovingVisitor;\nimport software.coley.recaf.util.visitors.ClassHollowingVisitor;\nimport software.coley.recaf.util.visitors.DuplicateAnnotationRemovingVisitor;\nimport software.coley.recaf.util.visitors.IllegalAnnotationRemovingVisitor;\nimport software.coley.recaf.util.visitors.IllegalSignatureRemovingVisitor;\nimport software.coley.recaf.util.visitors.LongAnnotationRemovingVisitor;\nimport software.coley.recaf.util.visitors.LongExceptionRemovingVisitor;\nimport software.coley.recaf.util.visitors.SyntheticRemovingVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.EnumSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TreeMap;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.ExecutorService;\n\n/**\n * Manager of multiple {@link Decompiler} instances.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class DecompilerManager implements Service {\n\tpublic static final String SERVICE_ID = \"decompilers\";\n\tprivate static final DebuggingLogger logger = Logging.get(DecompilerManager.class);\n\tprivate static final NoopJvmDecompiler NO_OP_JVM = NoopJvmDecompiler.getInstance();\n\tprivate static final NoopAndroidDecompiler NO_OP_ANDROID = NoopAndroidDecompiler.getInstance();\n\tprivate final JvmBytecodeFilter layeredJvmFilter = new LayeredJvmBytecodeFilter();\n\tprivate final ExecutorService decompileThreadPool = ThreadPoolFactory.newFixedThreadPool(SERVICE_ID);\n\tprivate final List<JvmBytecodeFilter> bytecodeFilters = new CopyOnWriteArrayList<>();\n\tprivate final List<OutputTextFilter> outputTextFilters = new CopyOnWriteArrayList<>();\n\tprivate final Map<String, JvmDecompiler> jvmDecompilers = new TreeMap<>();\n\tprivate final Map<String, AndroidDecompiler> androidDecompilers = new TreeMap<>();\n\tprivate final DecompilerManagerConfig config;\n\tprivate final ObservableObject<JvmDecompiler> targetJvmDecompiler;\n\tprivate final ObservableObject<AndroidDecompiler> targetAndroidDecompiler;\n\n\t/**\n\t * @param config\n\t * \t\tConfig to pull values from.\n\t * @param implementations\n\t * \t\tCDI provider of decompiler implementations.\n\t */\n\t@Inject\n\tpublic DecompilerManager(@Nonnull DecompilerManagerConfig config,\n\t                         @Nonnull Instance<Decompiler> implementations) {\n\t\tthis.config = config;\n\n\t\t// Register implementations\n\t\tfor (Decompiler implementation : implementations) {\n\t\t\tif (implementation instanceof JvmDecompiler jvmDecompiler) {\n\t\t\t\tregister(jvmDecompiler);\n\t\t\t} else if (implementation instanceof AndroidDecompiler androidDecompiler) {\n\t\t\t\tregister(androidDecompiler);\n\t\t\t}\n\t\t}\n\n\t\tObservableString preferredJvmDecompiler = config.getPreferredJvmDecompiler();\n\t\tObservableString preferredAndroidDecompiler = config.getPreferredAndroidDecompiler();\n\n\t\t// Mirror properties from config, mapped to instances\n\t\ttargetJvmDecompiler = preferredJvmDecompiler\n\t\t\t\t.mapObject(key -> jvmDecompilers.getOrDefault(key == null ? \"\" : key, NO_OP_JVM));\n\t\ttargetAndroidDecompiler = preferredAndroidDecompiler\n\t\t\t\t.mapObject(key -> androidDecompilers.getOrDefault(key == null ? \"\" : key, NO_OP_ANDROID));\n\n\t\t// Select first item if no value is present\n\t\tif (preferredJvmDecompiler.getValue() == null) {\n\t\t\tJvmDecompiler decompiler = jvmDecompilers.isEmpty() ?\n\t\t\t\t\tNO_OP_JVM : jvmDecompilers.values().iterator().next();\n\t\t\tpreferredJvmDecompiler.setValue(decompiler.getName());\n\t\t}\n\t\tif (preferredAndroidDecompiler.getValue() == null) {\n\t\t\tAndroidDecompiler decompiler = androidDecompilers.isEmpty() ?\n\t\t\t\t\tNO_OP_ANDROID : androidDecompilers.values().iterator().next();\n\t\t\tpreferredAndroidDecompiler.setValue(decompiler.getName());\n\t\t}\n\t}\n\n\t/**\n\t * Uses the built-in thread-pool to schedule the decompilation with the {@link #getTargetJvmDecompiler()}.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to pull additional information from.\n\t * @param classInfo\n\t * \t\tClass to decompile.\n\t *\n\t * @return Future of decompilation result.\n\t */\n\t@Nonnull\n\tpublic CompletableFuture<DecompileResult> decompile(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo) {\n\t\treturn decompile(getTargetJvmDecompiler(), workspace, classInfo);\n\t}\n\n\t/**\n\t * Uses the built-in thread-pool to schedule the decompilation.\n\t *\n\t * @param decompiler\n\t * \t\tDecompiler implementation to use.\n\t * @param workspace\n\t * \t\tWorkspace to pull additional information from.\n\t * @param classInfo\n\t * \t\tClass to decompile.\n\t *\n\t * @return Future of decompilation result.\n\t */\n\t@Nonnull\n\tpublic CompletableFuture<DecompileResult> decompile(@Nonnull JvmDecompiler decompiler, @Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo) {\n\t\treturn CompletableFuture.supplyAsync(() -> {\n\t\t\tboolean doCache = config.getCacheDecompilations().getValue();\n\t\t\tif (doCache) {\n\t\t\t\t// Check for cached result, returning the cached result if found\n\t\t\t\t// and only if the current config matches the one that yielded the cached result.\n\t\t\t\tDecompileResult cachedResult = CachedDecompileProperty.get(classInfo, decompiler);\n\t\t\t\tif (cachedResult != null) {\n\t\t\t\t\tif (cachedResult.getConfigHash() == decompiler.getConfig().getHash())\n\t\t\t\t\t\treturn cachedResult;\n\n\t\t\t\t\t// Config changed, void the cache.\n\t\t\t\t\tCachedDecompileProperty.remove(classInfo);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// We will use the layered filter manually here so any user requested cleanup is done before we pass the class to the decompiler.\n\t\t\t// The decompiler base implementation skips some work if there are no registered filters so doing it externally like this is\n\t\t\t// better for performance. If the user has no filtering enabled then no re-reads and re-writes are necessary.\n\t\t\tJvmClassInfo filteredClass = JvmBytecodeFilter.applyFilters(workspace, classInfo, Collections.singletonList(layeredJvmFilter));\n\n\t\t\t// Decompile and cache the results.\n\t\t\tDecompileResult result = decompiler.decompile(workspace, filteredClass);\n\t\t\tString decompilation = result.getText();\n\t\t\tif (decompilation != null && !outputTextFilters.isEmpty()) {\n\t\t\t\t// Apply output filters and re-wrap the result with the new output text.\n\t\t\t\tfor (OutputTextFilter textFilter : outputTextFilters)\n\t\t\t\t\tdecompilation = textFilter.filter(workspace, classInfo, decompilation);\n\t\t\t\tresult = new DecompileResult(decompilation, result.getConfigHash());\n\t\t\t}\n\t\t\tif (doCache)\n\t\t\t\tCachedDecompileProperty.set(classInfo, decompiler, result);\n\t\t\treturn result;\n\t\t}, decompileThreadPool);\n\t}\n\n\t/**\n\t * Uses the built-in thread-pool to schedule the decompilation with the {@link #getTargetAndroidDecompiler()}.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to pull additional information from.\n\t * @param classInfo\n\t * \t\tClass to decompile.\n\t *\n\t * @return Future of decompilation result.\n\t */\n\t@Nonnull\n\tpublic CompletableFuture<DecompileResult> decompile(@Nonnull Workspace workspace, @Nonnull AndroidClassInfo classInfo) {\n\t\treturn decompile(getTargetAndroidDecompiler(), workspace, classInfo);\n\t}\n\n\t/**\n\t * Uses the built-in thread-pool to schedule the decompilation.\n\t *\n\t * @param decompiler\n\t * \t\tDecompiler implementation to use.\n\t * @param workspace\n\t * \t\tWorkspace to pull additional information from.\n\t * @param classInfo\n\t * \t\tClass to decompile.\n\t *\n\t * @return Future of decompilation result.\n\t */\n\t@Nonnull\n\tpublic CompletableFuture<DecompileResult> decompile(@Nonnull AndroidDecompiler decompiler, @Nonnull Workspace workspace, @Nonnull AndroidClassInfo classInfo) {\n\t\treturn CompletableFuture.supplyAsync(() -> decompiler.decompile(workspace, classInfo), decompileThreadPool);\n\t}\n\n\t/**\n\t * Adds an input bytecode filter to all {@link JvmDecompiler} instances.\n\t *\n\t * @param filter\n\t * \t\tFilter to add.\n\t */\n\tpublic void addJvmBytecodeFilter(@Nonnull JvmBytecodeFilter filter) {\n\t\tbytecodeFilters.add(filter);\n\t}\n\n\t/**\n\t * Removes an input bytecode filter from all {@link JvmDecompiler} instances.\n\t *\n\t * @param filter\n\t * \t\tFilter to remove.\n\t */\n\tpublic void removeJvmBytecodeFilter(@Nonnull JvmBytecodeFilter filter) {\n\t\tbytecodeFilters.remove(filter);\n\t}\n\n\t/**\n\t * Adds an output text filter to all {@link Decompiler} instances.\n\t *\n\t * @param filter\n\t * \t\tFilter to add.\n\t */\n\tpublic void addOutputTextFilter(@Nonnull OutputTextFilter filter) {\n\t\toutputTextFilters.add(filter);\n\t}\n\n\t/**\n\t * Removes an output text filter from all {@link Decompiler} instances.\n\t *\n\t * @param filter\n\t * \t\tFilter to remove.\n\t */\n\tpublic void removeOutputTextFilter(@Nonnull OutputTextFilter filter) {\n\t\toutputTextFilters.remove(filter);\n\t}\n\n\t/**\n\t * @return Preferred JVM decompiler.\n\t */\n\t@Nonnull\n\tpublic JvmDecompiler getTargetJvmDecompiler() {\n\t\treturn targetJvmDecompiler.getValue();\n\t}\n\n\t/**\n\t * @return Preferred Android decompiler.\n\t */\n\t@Nonnull\n\tpublic AndroidDecompiler getTargetAndroidDecompiler() {\n\t\treturn targetAndroidDecompiler.getValue();\n\t}\n\n\t/**\n\t * @param decompiler\n\t * \t\tJVM decompiler to add.\n\t */\n\tpublic void register(@Nonnull JvmDecompiler decompiler) {\n\t\tjvmDecompilers.put(decompiler.getName(), decompiler);\n\t}\n\n\t/**\n\t * @param decompiler\n\t * \t\tAndroid decompiler to add.\n\t */\n\tpublic void register(@Nonnull AndroidDecompiler decompiler) {\n\t\tandroidDecompilers.put(decompiler.getName(), decompiler);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName of decompiler.\n\t *\n\t * @return Decompiler instance, or {@code null} if nothing by the ID was found.\n\t */\n\t@Nullable\n\tpublic JvmDecompiler getJvmDecompiler(@Nonnull String name) {\n\t\treturn jvmDecompilers.get(name);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName of decompiler.\n\t *\n\t * @return Decompiler instance, or {@code null} if nothing by the ID was found.\n\t */\n\t@Nullable\n\tpublic AndroidDecompiler getAndroidDecompiler(@Nonnull String name) {\n\t\treturn androidDecompilers.get(name);\n\t}\n\n\t/**\n\t * @return Available JVM class decompilers.\n\t */\n\t@Nonnull\n\tpublic Collection<JvmDecompiler> getJvmDecompilers() {\n\t\treturn jvmDecompilers.values();\n\t}\n\n\t/**\n\t * @return Available android class decompilers.\n\t */\n\t@Nonnull\n\tpublic Collection<AndroidDecompiler> getAndroidDecompilers() {\n\t\treturn androidDecompilers.values();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic DecompilerManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\t/**\n\t * JVM bytecode filter that applies multiple other filters:\n\t * <ol>\n\t *     <li>Any values in {@link #bytecodeFilters}</li>\n\t *     <li>Any filters based on the current values in {@link #config}</li>\n\t * </ol>\n\t */\n\tprivate class LayeredJvmBytecodeFilter implements JvmBytecodeFilter {\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic byte[] filter(@Nonnull Workspace workspace, @Nonnull JvmClassInfo initialClassInfo, @Nonnull byte[] bytecode) {\n\t\t\t// Apply filters to the input bytecode first\n\t\t\tfor (JvmBytecodeFilter filter : bytecodeFilters)\n\t\t\t\tbytecode = filter.filter(workspace, initialClassInfo, bytecode);\n\n\t\t\t// Setup filtering based on config\n\t\t\tbyte[] filteredBytecode = bytecode;\n\t\t\tLazyValueHolder<ClassReader> reader = LazyValueHolder.forSupplier(() -> new ClassReader(filteredBytecode));\n\t\t\tLazyValueHolder<ClassWriter> cw = LazyValueHolder.forSupplier(() -> {\n\t\t\t\t// In most cases we want to pass the class-reader along to the class-writer.\n\t\t\t\t// This will allow some operations to be sped up internally by ASM.\n\t\t\t\t//\n\t\t\t\t// However, we can't do this is we're filtering debug information since it blanket copies all\n\t\t\t\t// debug attribute information without checking what the class-reader flags are.\n\t\t\t\t// Thus, when we're pruning debug info, we should pass 'null'.\n\t\t\t\tClassReader backing = config.getFilterDebug().getValue() ? null : reader.get();\n\t\t\t\treturn new ClassWriter(backing, 0);\n\t\t\t});\n\t\t\tClassVisitor cv = null;\n\n\t\t\t// The things you want to 'filter' first need to appear last in this chain since we're building a chain\n\t\t\t// of visitors which delegate from one onto another.\n\t\t\tif (config.getFilterNonAsciiNames().getValue()) {\n\t\t\t\tcv = cw.get();\n\t\t\t\tcv = BogusNameRemovingVisitor.create(workspace, cv);\n\t\t\t}\n\t\t\tif (config.getFilterLongAnnotations().getValue()) {\n\t\t\t\tif (cv == null) cv = cw.get();\n\t\t\t\tcv = new LongAnnotationRemovingVisitor(cv, config.getFilterLongAnnotationsLength().getValue());\n\t\t\t}\n\t\t\tif (config.getFilterLongExceptions().getValue()) {\n\t\t\t\tif (cv == null) cv = cw.get();\n\t\t\t\tcv = new LongExceptionRemovingVisitor(cv, config.getFilterLongExceptionsLength().getValue());\n\t\t\t}\n\t\t\tif (config.getFilterDuplicateAnnotations().getValue()) {\n\t\t\t\tif (cv == null) cv = cw.get();\n\t\t\t\tcv = new DuplicateAnnotationRemovingVisitor(cv);\n\t\t\t}\n\t\t\tif (config.getFilterIllegalAnnotations().getValue()) {\n\t\t\t\tif (cv == null) cv = cw.get();\n\t\t\t\tcv = new IllegalAnnotationRemovingVisitor(cv);\n\t\t\t}\n\t\t\tif (config.getFilterHollow().getValue()) {\n\t\t\t\tif (cv == null) cv = cw.get();\n\t\t\t\tcv = new ClassHollowingVisitor(cv, EnumSet.allOf(ClassHollowingVisitor.Item.class));\n\t\t\t}\n\t\t\tif (config.getFilterSignatures().getValue()) {\n\t\t\t\tif (cv == null) cv = cw.get();\n\t\t\t\tcv = new IllegalSignatureRemovingVisitor(cv);\n\t\t\t}\n\t\t\tif (config.getFilterSynthetics().getValue()) {\n\t\t\t\tif (cv == null) cv = cw.get();\n\t\t\t\tcv = new SyntheticRemovingVisitor(cv);\n\t\t\t}\n\t\t\tif (config.getFilterDebug().getValue() && cv == null)\n\t\t\t\tcv = cw.get();\n\n\t\t\t// If no filtering has been requested, we never need to initialize the reader or writer.\n\t\t\t// Just return the original bytecode passed in.\n\t\t\tif (cv == null)\n\t\t\t\treturn bytecode;\n\n\t\t\ttry {\n\t\t\t\tint readFlags = config.getFilterDebug().getValue() ? ClassReader.SKIP_DEBUG : 0;\n\t\t\t\treader.get().accept(cv, readFlags);\n\t\t\t\treturn cw.get().toByteArray();\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Error applying filters to class '{}'\", initialClassInfo.getName(), t);\n\t\t\t\treturn bytecode;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompilerManagerConfig.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.observables.ObservableString;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link DecompilerManager}\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class DecompilerManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\tpublic static final String KEY_PREF_JVM_DECOMPILER = \"pref-jvm-decompiler\";\n\tpublic static final String KEY_PREF_ANDROID_DECOMPILER = \"pref-android-decompiler\";\n\tprivate final ObservableString preferredJvmDecompiler = new ObservableString(null);\n\tprivate final ObservableString preferredAndroidDecompiler = new ObservableString(null);\n\tprivate final ObservableBoolean cacheDecompilations = new ObservableBoolean(true);\n\tprivate final ObservableBoolean filterDebug = new ObservableBoolean(false);\n\tprivate final ObservableBoolean filterHollow = new ObservableBoolean(false);\n\tprivate final ObservableBoolean filterIllegalAnnotations = new ObservableBoolean(false);\n\tprivate final ObservableBoolean filterDuplicateAnnotations = new ObservableBoolean(false);\n\tprivate final ObservableBoolean filterLongAnnotations = new ObservableBoolean(false);\n\tprivate final ObservableInteger filterLongAnnotationsLength = new ObservableInteger(256);\n\tprivate final ObservableBoolean filterLongExceptions = new ObservableBoolean(false);\n\tprivate final ObservableInteger filterLongExceptionsLength = new ObservableInteger(256);\n\tprivate final ObservableBoolean filterSignatures = new ObservableBoolean(false);\n\tprivate final ObservableBoolean filterSynthetics = new ObservableBoolean(false);\n\tprivate final ObservableBoolean filterNonAsciiNames = new ObservableBoolean(false);\n\n\t@Inject\n\tpublic DecompilerManagerConfig() {\n\t\tsuper(ConfigGroups.SERVICE_DECOMPILE, DecompilerManager.SERVICE_ID + CONFIG_SUFFIX);\n\t\t// Add values\n\t\taddValue(new BasicConfigValue<>(KEY_PREF_JVM_DECOMPILER, String.class, preferredJvmDecompiler));\n\t\taddValue(new BasicConfigValue<>(KEY_PREF_ANDROID_DECOMPILER, String.class, preferredAndroidDecompiler));\n\t\taddValue(new BasicConfigValue<>(\"cache-decompilations\", boolean.class, cacheDecompilations));\n\t\taddValue(new BasicConfigValue<>(\"filter-strip-debug\", boolean.class, filterDebug));\n\t\taddValue(new BasicConfigValue<>(\"filter-hollow\", boolean.class, filterHollow));\n\t\taddValue(new BasicConfigValue<>(\"filter-annotations-illegal\", boolean.class, filterIllegalAnnotations));\n\t\taddValue(new BasicConfigValue<>(\"filter-annotations-duplicate\", boolean.class, filterDuplicateAnnotations));\n\t\taddValue(new BasicConfigValue<>(\"filter-annotations-long\", boolean.class, filterLongAnnotations));\n\t\taddValue(new BasicConfigValue<>(\"filter-annotations-long-limit\", int.class, filterLongAnnotationsLength));\n\t\taddValue(new BasicConfigValue<>(\"filter-exceptions-long\", boolean.class, filterLongExceptions));\n\t\taddValue(new BasicConfigValue<>(\"filter-exceptions-long-limit\", int.class, filterLongExceptionsLength));\n\t\taddValue(new BasicConfigValue<>(\"filter-illegal-signatures\", boolean.class, filterSignatures));\n\t\taddValue(new BasicConfigValue<>(\"filter-synthetics\", boolean.class, filterSynthetics));\n\t\taddValue(new BasicConfigValue<>(\"filter-names-ascii\", boolean.class, filterNonAsciiNames));\n\t}\n\n\t/**\n\t * @return {@link JvmDecompiler#getName()} for preferred JVM decompiler to use in {@link DecompilerManager}.\n\t */\n\t@Nonnull\n\tpublic ObservableString getPreferredJvmDecompiler() {\n\t\treturn preferredJvmDecompiler;\n\t}\n\n\t/**\n\t * @return {@link AndroidDecompiler#getName()} for preferred JVM decompiler to use in {@link DecompilerManager}.\n\t */\n\t@Nonnull\n\tpublic ObservableString getPreferredAndroidDecompiler() {\n\t\treturn preferredAndroidDecompiler;\n\t}\n\n\t/**\n\t * @return {@code true} to cache the results of decompilation tasks in via the {@link DecompilerManager}.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getCacheDecompilations() {\n\t\treturn cacheDecompilations;\n\t}\n\n\t/**\n\t * @return {@code true} to filter out <i>all</i> debug information including generics, line numbers, variable names, etc.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getFilterDebug() {\n\t\treturn filterDebug;\n\t}\n\n\t/**\n\t * @return {@code true}\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getFilterHollow() {\n\t\treturn filterHollow;\n\t}\n\n\t/**\n\t * @return {@code true} to filter out illegally typed annotations.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getFilterIllegalAnnotations() {\n\t\treturn filterIllegalAnnotations;\n\t}\n\n\t/**\n\t * @return {@code true} to filter out duplicate annotations applied to classes/fields/methods.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getFilterDuplicateAnnotations() {\n\t\treturn filterDuplicateAnnotations;\n\t}\n\n\t/**\n\t * @return {@code true} to filter out long named annotations.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getFilterLongAnnotations() {\n\t\treturn filterLongAnnotations;\n\t}\n\n\t/**\n\t * @return Max name length to allowed for {@link #getFilterLongAnnotations()}.\n\t */\n\t@Nonnull\n\tpublic ObservableInteger getFilterLongAnnotationsLength() {\n\t\treturn filterLongAnnotationsLength;\n\t}\n\n\t/**\n\t * @return {@code true} to filter out long named exceptions.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getFilterLongExceptions() {\n\t\treturn filterLongExceptions;\n\t}\n\n\t/**\n\t * @return Max name length to allowed for {@link #getFilterLongExceptions()}.\n\t */\n\t@Nonnull\n\tpublic ObservableInteger getFilterLongExceptionsLength() {\n\t\treturn filterLongExceptionsLength;\n\t}\n\n\t/**\n\t * @return {@code true} to strip out illegal signatures from classes.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getFilterSignatures() {\n\t\treturn filterSignatures;\n\t}\n\n\t/**\n\t * @return {@code true} to strip out synthetic/bridge modifiers from classes, fields, and methods.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getFilterSynthetics() {\n\t\treturn filterSynthetics;\n\t}\n\n\t/**\n\t * @return {@code true} to filter out any non-ascii referenced name.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getFilterNonAsciiNames() {\n\t\treturn filterNonAsciiNames;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/JvmDecompiler.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.decompile.filter.JvmBytecodeFilter;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Outline for decompilers targeting {@link JvmClassInfo}.\n *\n * @author Matt Coley\n */\npublic interface JvmDecompiler extends Decompiler {\n\t/**\n\t * Adds a filter which operates on the bytecode of classes before passing it along to the decompiler.\n\t *\n\t * @param filter\n\t * \t\tFilter to add.\n\t *\n\t * @return {@code true} on successful addition.\n\t * {@code false} if the filter has already been added.\n\t */\n\tboolean addJvmBytecodeFilter(@Nonnull JvmBytecodeFilter filter); // TODO: Make config for common defaults (debug stripping, virtual mapping?)\n\n\t/**\n\t * Removes a filter which operates on the bytecode of classes before passing it along to the decompiler.\n\t *\n\t * @param filter\n\t * \t\tFilter to remove.\n\t *\n\t * @return {@code true} on successful removal.\n\t * {@code false} if the filter was not already registered.\n\t */\n\tboolean removeJvmBytecodeFilter(@Nonnull JvmBytecodeFilter filter);\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull data from.\n\t * @param classInfo\n\t * \t\tClass to decompile.\n\t *\n\t * @return Decompilation result.\n\t */\n\t@Nonnull\n\tDecompileResult decompile(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopAndroidDecompiler.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * No-op decompiler for {@link JvmDecompiler}\n *\n * @author Matt Coley\n */\npublic class NoopAndroidDecompiler extends AbstractAndroidDecompiler {\n\tprivate static final NoopAndroidDecompiler INSTANCE = new NoopAndroidDecompiler();\n\n\tprivate NoopAndroidDecompiler() {\n\t\tsuper(\"no-op-android\", \"1.0.0\", new NoopDecompilerConfig());\n\t}\n\n\t/**\n\t * @return Singleton instance.\n\t */\n\tpublic static NoopAndroidDecompiler getInstance() {\n\t\treturn INSTANCE;\n\t}\n\n\t@Override\n\tpublic DecompileResult decompile(@Nonnull Workspace workspace, @Nonnull AndroidClassInfo classInfo) {\n\t\treturn new DecompileResult(getConfig().getHash());\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopDecompilerConfig.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\n\n/**\n * Dummy config for {@link NoopJvmDecompiler} and {@link NoopAndroidDecompiler}.\n *\n * @author Matt Coley\n */\n@ExcludeFromJacocoGeneratedReport(justification = \"Config POJO\")\npublic class NoopDecompilerConfig extends BaseDecompilerConfig implements DecompilerConfig {\n\t/**\n\t * New dummy config.\n\t */\n\tpublic NoopDecompilerConfig() {\n\t\tsuper(\"noop\");\n\t}\n\n\t@Override\n\tpublic int getHash() {\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic void setHash(int hash) {\n\t\t// no-op\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopJvmDecompiler.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * No-op decompiler for {@link JvmDecompiler}\n *\n * @author Matt Coley\n */\npublic class NoopJvmDecompiler extends AbstractJvmDecompiler {\n\tprivate static final NoopJvmDecompiler INSTANCE = new NoopJvmDecompiler();\n\n\tprivate NoopJvmDecompiler() {\n\t\tsuper(\"no-op-jvm\", \"1.0.0\", new NoopDecompilerConfig());\n\t}\n\n\t/**\n\t * @return Singleton instance.\n\t */\n\tpublic static NoopJvmDecompiler getInstance() {\n\t\treturn INSTANCE;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo) {\n\t\treturn new DecompileResult(getConfig().getHash());\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrConfig.java",
    "content": "package software.coley.recaf.services.decompile.cfr;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.benf.cfr.reader.api.CfrDriver;\nimport org.benf.cfr.reader.util.ClassFileVersion;\nimport org.benf.cfr.reader.util.getopt.OptionsImpl;\nimport org.benf.cfr.reader.util.getopt.PermittedOptionProvider;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.services.decompile.BaseDecompilerConfig;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\nimport software.coley.recaf.util.ReflectUtil;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.lang.reflect.Field;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Config for {@link CfrDecompiler}\n *\n * @author Matt Coley\n * @see OptionsImpl CFR options\n */\n@ApplicationScoped\n@SuppressWarnings(\"all\") // ignore unused refs / typos\n@ExcludeFromJacocoGeneratedReport(justification = \"Config POJO\")\npublic class CfrConfig extends BaseDecompilerConfig {\n\tprivate final ObservableObject<BooleanOption> stringbuffer = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> stringbuilder = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> stringconcat = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> decodeenumswitch = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> sugarenums = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> decodestringswitch = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> previewfeatures = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> sealed = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> switchexpression = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> recordtypes = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> instanceofpattern = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> arrayiter = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> collectioniter = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> tryresources = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> decodelambdas = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> innerclasses = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> forbidmethodscopedclasses = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> forbidanonymousclasses = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> skipbatchinnerclasses = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> hideutf = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> hidelongstrings = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> removeboilerplate = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> removeinnerclasssynthetics = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> relinkconst = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> relinkconststring = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> liftconstructorinit = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> removedeadmethods = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> removebadgenerics = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> sugarasserts = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> sugarboxing = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> sugarretrolambda = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> showversion = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> decodefinally = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> tidymonitors = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> commentmonitors = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> lenient = new ObservableObject<>(BooleanOption.TRUE);\n\tprivate final ObservableObject<BooleanOption> comments = new ObservableObject<>(BooleanOption.FALSE);\n\tprivate final ObservableObject<TrooleanOption> forcetopsort = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<ClassFileVersion> forceclassfilever = new ObservableObject<>(null);\n\tprivate final ObservableObject<TrooleanOption> forloopaggcapture = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> forcetopsortaggress = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> forcetopsortnopull = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> forcecondpropagate = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> reducecondscope = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> forcereturningifs = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> ignoreexceptionsalways = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> antiobf = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> obfcontrol = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> obfattr = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> constobf = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> hidebridgemethods = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> ignoreexceptions = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> forceexceptionprune = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> aexagg = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> aexagg2 = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> recovertypeclash = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> recovertypehints = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> recover = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> eclipse = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> override = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> showinferrable = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> version = new ObservableObject<>(BooleanOption.FALSE);\n\tprivate final ObservableObject<BooleanOption> labelledblocks = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> j14classobj = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> hidelangimports = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> renamedupmembers = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableInteger renamesmallmembers = new ObservableInteger(0);\n\tprivate final ObservableObject<BooleanOption> renameillegalidents = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> renameenumidents = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> removedeadconditionals = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> aggressivedoextension = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> aggressiveduff = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableInteger aggressivedocopy = new ObservableInteger(0);\n\tprivate final ObservableInteger aggressivesizethreshold = new ObservableInteger(13000);\n\tprivate final ObservableObject<BooleanOption> staticinitreturn = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> usenametable = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> pullcodecase = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<TrooleanOption> allowmalformedswitch = new ObservableObject<>(TrooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> elidescala = new ObservableObject<>(BooleanOption.DEFAULT);\n\tprivate final ObservableObject<BooleanOption> usesignatures = new ObservableObject<>(BooleanOption.DEFAULT);\n\n\t@Inject\n\tpublic CfrConfig() {\n\t\tsuper(\"decompiler-cfr\" + CONFIG_SUFFIX);\n\t\t// Add values\n\t\taddValue(new BasicConfigValue<>(\"stringbuffer\", BooleanOption.class, stringbuffer));\n\t\taddValue(new BasicConfigValue<>(\"stringbuilder\", BooleanOption.class, stringbuilder));\n\t\taddValue(new BasicConfigValue<>(\"stringconcat\", BooleanOption.class, stringconcat));\n\t\taddValue(new BasicConfigValue<>(\"decodeenumswitch\", BooleanOption.class, decodeenumswitch));\n\t\taddValue(new BasicConfigValue<>(\"sugarenums\", BooleanOption.class, sugarenums));\n\t\taddValue(new BasicConfigValue<>(\"decodestringswitch\", BooleanOption.class, decodestringswitch));\n\t\taddValue(new BasicConfigValue<>(\"previewfeatures\", BooleanOption.class, previewfeatures));\n\t\taddValue(new BasicConfigValue<>(\"sealed\", BooleanOption.class, sealed));\n\t\taddValue(new BasicConfigValue<>(\"switchexpression\", BooleanOption.class, switchexpression));\n\t\taddValue(new BasicConfigValue<>(\"recordtypes\", BooleanOption.class, recordtypes));\n\t\taddValue(new BasicConfigValue<>(\"instanceofpattern\", BooleanOption.class, instanceofpattern));\n\t\taddValue(new BasicConfigValue<>(\"arrayiter\", BooleanOption.class, arrayiter));\n\t\taddValue(new BasicConfigValue<>(\"collectioniter\", BooleanOption.class, collectioniter));\n\t\taddValue(new BasicConfigValue<>(\"tryresources\", BooleanOption.class, tryresources));\n\t\taddValue(new BasicConfigValue<>(\"decodelambdas\", BooleanOption.class, decodelambdas));\n\t\taddValue(new BasicConfigValue<>(\"innerclasses\", BooleanOption.class, innerclasses));\n\t\taddValue(new BasicConfigValue<>(\"forbidmethodscopedclasses\", BooleanOption.class, forbidmethodscopedclasses));\n\t\taddValue(new BasicConfigValue<>(\"forbidanonymousclasses\", BooleanOption.class, forbidanonymousclasses));\n\t\taddValue(new BasicConfigValue<>(\"skipbatchinnerclasses\", BooleanOption.class, skipbatchinnerclasses));\n\t\taddValue(new BasicConfigValue<>(\"hideutf\", BooleanOption.class, hideutf));\n\t\taddValue(new BasicConfigValue<>(\"hidelongstrings\", BooleanOption.class, hidelongstrings));\n\t\taddValue(new BasicConfigValue<>(\"removeboilerplate\", BooleanOption.class, removeboilerplate));\n\t\taddValue(new BasicConfigValue<>(\"removeinnerclasssynthetics\", BooleanOption.class, removeinnerclasssynthetics));\n\t\taddValue(new BasicConfigValue<>(\"relinkconst\", BooleanOption.class, relinkconst));\n\t\taddValue(new BasicConfigValue<>(\"relinkconststring\", BooleanOption.class, relinkconststring));\n\t\taddValue(new BasicConfigValue<>(\"liftconstructorinit\", BooleanOption.class, liftconstructorinit));\n\t\taddValue(new BasicConfigValue<>(\"removedeadmethods\", BooleanOption.class, removedeadmethods));\n\t\taddValue(new BasicConfigValue<>(\"removebadgenerics\", BooleanOption.class, removebadgenerics));\n\t\taddValue(new BasicConfigValue<>(\"sugarasserts\", BooleanOption.class, sugarasserts));\n\t\taddValue(new BasicConfigValue<>(\"sugarboxing\", BooleanOption.class, sugarboxing));\n\t\taddValue(new BasicConfigValue<>(\"sugarretrolambda\", BooleanOption.class, sugarretrolambda));\n\t\taddValue(new BasicConfigValue<>(\"showversion\", BooleanOption.class, showversion));\n\t\taddValue(new BasicConfigValue<>(\"decodefinally\", BooleanOption.class, decodefinally));\n\t\taddValue(new BasicConfigValue<>(\"tidymonitors\", BooleanOption.class, tidymonitors));\n\t\taddValue(new BasicConfigValue<>(\"commentmonitors\", BooleanOption.class, commentmonitors));\n\t\taddValue(new BasicConfigValue<>(\"lenient\", BooleanOption.class, lenient));\n\t\taddValue(new BasicConfigValue<>(\"comments\", BooleanOption.class, comments));\n\t\taddValue(new BasicConfigValue<>(\"forcetopsort\", TrooleanOption.class, forcetopsort));\n\t\taddValue(new BasicConfigValue<>(\"forceclassfilever\", ClassFileVersion.class, forceclassfilever));\n\t\taddValue(new BasicConfigValue<>(\"forloopaggcapture\", TrooleanOption.class, forloopaggcapture));\n\t\taddValue(new BasicConfigValue<>(\"forcetopsortaggress\", TrooleanOption.class, forcetopsortaggress));\n\t\taddValue(new BasicConfigValue<>(\"forcetopsortnopull\", TrooleanOption.class, forcetopsortnopull));\n\t\taddValue(new BasicConfigValue<>(\"forcecondpropagate\", TrooleanOption.class, forcecondpropagate));\n\t\taddValue(new BasicConfigValue<>(\"reducecondscope\", TrooleanOption.class, reducecondscope));\n\t\taddValue(new BasicConfigValue<>(\"forcereturningifs\", TrooleanOption.class, forcereturningifs));\n\t\taddValue(new BasicConfigValue<>(\"ignoreexceptionsalways\", BooleanOption.class, ignoreexceptionsalways));\n\t\taddValue(new BasicConfigValue<>(\"antiobf\", BooleanOption.class, antiobf));\n\t\taddValue(new BasicConfigValue<>(\"obfcontrol\", BooleanOption.class, obfcontrol));\n\t\taddValue(new BasicConfigValue<>(\"obfattr\", BooleanOption.class, obfattr));\n\t\taddValue(new BasicConfigValue<>(\"constobf\", BooleanOption.class, constobf));\n\t\taddValue(new BasicConfigValue<>(\"hidebridgemethods\", BooleanOption.class, hidebridgemethods));\n\t\taddValue(new BasicConfigValue<>(\"ignoreexceptions\", BooleanOption.class, ignoreexceptions));\n\t\taddValue(new BasicConfigValue<>(\"forceexceptionprune\", TrooleanOption.class, forceexceptionprune));\n\t\taddValue(new BasicConfigValue<>(\"aexagg\", TrooleanOption.class, aexagg));\n\t\taddValue(new BasicConfigValue<>(\"aexagg2\", TrooleanOption.class, aexagg2));\n\t\taddValue(new BasicConfigValue<>(\"recovertypeclash\", TrooleanOption.class, recovertypeclash));\n\t\taddValue(new BasicConfigValue<>(\"recovertypehints\", TrooleanOption.class, recovertypehints));\n\t\taddValue(new BasicConfigValue<>(\"recover\", BooleanOption.class, recover));\n\t\taddValue(new BasicConfigValue<>(\"eclipse\", BooleanOption.class, eclipse));\n\t\taddValue(new BasicConfigValue<>(\"override\", BooleanOption.class, override));\n\t\taddValue(new BasicConfigValue<>(\"showinferrable\", BooleanOption.class, showinferrable));\n\t\taddValue(new BasicConfigValue<>(\"version\", BooleanOption.class, version));\n\t\taddValue(new BasicConfigValue<>(\"labelledblocks\", BooleanOption.class, labelledblocks));\n\t\taddValue(new BasicConfigValue<>(\"j14classobj\", BooleanOption.class, j14classobj));\n\t\taddValue(new BasicConfigValue<>(\"hidelangimports\", BooleanOption.class, hidelangimports));\n\t\taddValue(new BasicConfigValue<>(\"renamedupmembers\", BooleanOption.class, renamedupmembers));\n\t\taddValue(new BasicConfigValue<>(\"renamesmallmembers\", int.class, renamesmallmembers));\n\t\taddValue(new BasicConfigValue<>(\"renameillegalidents\", BooleanOption.class, renameillegalidents));\n\t\taddValue(new BasicConfigValue<>(\"renameenumidents\", BooleanOption.class, renameenumidents));\n\t\taddValue(new BasicConfigValue<>(\"removedeadconditionals\", TrooleanOption.class, removedeadconditionals));\n\t\taddValue(new BasicConfigValue<>(\"aggressivedoextension\", TrooleanOption.class, aggressivedoextension));\n\t\taddValue(new BasicConfigValue<>(\"aggressiveduff\", TrooleanOption.class, aggressiveduff));\n\t\taddValue(new BasicConfigValue<>(\"aggressivedocopy\", int.class, aggressivedocopy));\n\t\taddValue(new BasicConfigValue<>(\"aggressivesizethreshold\", int.class, aggressivesizethreshold));\n\t\taddValue(new BasicConfigValue<>(\"staticinitreturn\", BooleanOption.class, staticinitreturn));\n\t\taddValue(new BasicConfigValue<>(\"usenametable\", BooleanOption.class, usenametable));\n\t\taddValue(new BasicConfigValue<>(\"pullcodecase\", BooleanOption.class, pullcodecase));\n\t\taddValue(new BasicConfigValue<>(\"allowmalformedswitch\", TrooleanOption.class, allowmalformedswitch));\n\t\taddValue(new BasicConfigValue<>(\"elidescala\", BooleanOption.class, elidescala));\n\t\taddValue(new BasicConfigValue<>(\"usesignatures\", BooleanOption.class, usesignatures));\n\t\tregisterConfigValuesHashUpdates();\n\t}\n\n\t/**\n\t * Fetch help description from configuration parameter.\n\t *\n\t * @param name\n\t * \t\tParameter/option name.\n\t *\n\t * @return Help description string, may be {@code null}.\n\t */\n\t@Nullable\n\t@SuppressWarnings(\"rawtypes\")\n\tpublic static String getOptHelp(String name) {\n\t\tfor (Field declaredField : OptionsImpl.class.getDeclaredFields()) {\n\t\t\tif (PermittedOptionProvider.ArgumentParam.class.isAssignableFrom(declaredField.getType())) {\n\t\t\t\tPermittedOptionProvider.ArgumentParam param = ReflectUtil.quietGet(null, declaredField);\n\t\t\t\tif (param != null && param.getName().equals(name))\n\t\t\t\t\treturn getOptHelp(param);\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * Fetch help description from configuration parameter.\n\t *\n\t * @param param\n\t * \t\tParameter.\n\t *\n\t * @return Help description string.\n\t */\n\t@Nonnull\n\tprivate static String getOptHelp(PermittedOptionProvider.ArgumentParam<?, ?> param) {\n\t\ttry {\n\t\t\tField fn = PermittedOptionProvider.ArgumentParam.class.getDeclaredField(\"help\");\n\t\t\tfn.setAccessible(true);\n\t\t\tString value = (String) fn.get(param);\n\t\t\tif (StringUtil.isNullOrEmpty(value))\n\t\t\t\tvalue = \"\";\n\t\t\treturn value;\n\t\t} catch (ReflectiveOperationException ex) {\n\t\t\tthrow new IllegalStateException(\"Failed to fetch description from Cfr parameter, did\" +\n\t\t\t\t\t\" the backend change?\");\n\t\t}\n\t}\n\n\t/**\n\t * @return CFR compatible string map for {@link CfrDriver.Builder#withOptions(Map)}.\n\t */\n\t@Nonnull\n\tpublic Map<String, String> toMap() {\n\t\tMap<String, String> map = new HashMap<>();\n\t\tgetValues().forEach((name, config) -> {\n\t\t\tClass<?> type = config.getType();\n\t\t\tif (type == BooleanOption.class) {\n\t\t\t\t// Boolean option, values should be 'true' or 'false'\n\t\t\t\tBooleanOption value = (BooleanOption) config.getValue();\n\t\t\t\tif (value != BooleanOption.DEFAULT) {\n\t\t\t\t\tString booleanName = value.name().toLowerCase();\n\t\t\t\t\tmap.put(name, booleanName);\n\t\t\t\t}\n\t\t\t} else if (type == TrooleanOption.class) {\n\t\t\t\t// Troolean option, values should be 'true' or 'false' for respective cases.\n\t\t\t\t// The 'neither' option is selected when null is passed.\n\t\t\t\tTrooleanOption value = (TrooleanOption) config.getValue();\n\t\t\t\tif (value == TrooleanOption.NEITHER) {\n\t\t\t\t\tmap.put(name, null);\n\t\t\t\t} else if (value != TrooleanOption.DEFAULT) {\n\t\t\t\t\tString booleanName = value.name().toLowerCase();\n\t\t\t\t\tmap.put(name, booleanName);\n\t\t\t\t}\n\t\t\t} else if (type == Integer.class) {\n\t\t\t\t// Integer option, value is just int\n\t\t\t\tInteger value = (Integer) config.getValue();\n\t\t\t\tif (value != null) {\n\t\t\t\t\tmap.put(name, value.toString());\n\t\t\t\t}\n\t\t\t} else if (type == ClassFileVersion.class) {\n\t\t\t\t// Class version option, values represented as 'MAJOR.MINOR'.\n\t\t\t\t// Java 8 would be '52.0'\n\t\t\t\tClassFileVersion value = (ClassFileVersion) config.getValue();\n\t\t\t\tif (value != null) {\n\t\t\t\t\t// The 'toString()' handles the format for us.\n\t\t\t\t\tmap.put(name, value.toString());\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\treturn map;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getStringbuffer() {\n\t\treturn stringbuffer;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getStringbuilder() {\n\t\treturn stringbuilder;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getStringconcat() {\n\t\treturn stringconcat;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getDecodeenumswitch() {\n\t\treturn decodeenumswitch;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getSugarenums() {\n\t\treturn sugarenums;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getDecodestringswitch() {\n\t\treturn decodestringswitch;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getPreviewfeatures() {\n\t\treturn previewfeatures;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getSealed() {\n\t\treturn sealed;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getSwitchexpression() {\n\t\treturn switchexpression;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getRecordtypes() {\n\t\treturn recordtypes;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getInstanceofpattern() {\n\t\treturn instanceofpattern;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getArrayiter() {\n\t\treturn arrayiter;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getCollectioniter() {\n\t\treturn collectioniter;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getTryresources() {\n\t\treturn tryresources;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getDecodelambdas() {\n\t\treturn decodelambdas;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getInnerclasses() {\n\t\treturn innerclasses;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getForbidmethodscopedclasses() {\n\t\treturn forbidmethodscopedclasses;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getForbidanonymousclasses() {\n\t\treturn forbidanonymousclasses;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getSkipbatchinnerclasses() {\n\t\treturn skipbatchinnerclasses;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getHideutf() {\n\t\treturn hideutf;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getHidelongstrings() {\n\t\treturn hidelongstrings;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getRemoveboilerplate() {\n\t\treturn removeboilerplate;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getRemoveinnerclasssynthetics() {\n\t\treturn removeinnerclasssynthetics;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getRelinkconst() {\n\t\treturn relinkconst;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getRelinkconststring() {\n\t\treturn relinkconststring;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getLiftconstructorinit() {\n\t\treturn liftconstructorinit;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getRemovedeadmethods() {\n\t\treturn removedeadmethods;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getRemovebadgenerics() {\n\t\treturn removebadgenerics;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getSugarasserts() {\n\t\treturn sugarasserts;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getSugarboxing() {\n\t\treturn sugarboxing;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getSugarretrolambda() {\n\t\treturn sugarretrolambda;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getShowversion() {\n\t\treturn showversion;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getDecodefinally() {\n\t\treturn decodefinally;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getTidymonitors() {\n\t\treturn tidymonitors;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getCommentmonitors() {\n\t\treturn commentmonitors;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getLenient() {\n\t\treturn lenient;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getComments() {\n\t\treturn comments;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getForcetopsort() {\n\t\treturn forcetopsort;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<ClassFileVersion> getForceclassfilever() {\n\t\treturn forceclassfilever;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getForloopaggcapture() {\n\t\treturn forloopaggcapture;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getForcetopsortaggress() {\n\t\treturn forcetopsortaggress;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getForcetopsortnopull() {\n\t\treturn forcetopsortnopull;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getForcecondpropagate() {\n\t\treturn forcecondpropagate;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getReducecondscope() {\n\t\treturn reducecondscope;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getForcereturningifs() {\n\t\treturn forcereturningifs;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getIgnoreexceptionsalways() {\n\t\treturn ignoreexceptionsalways;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getAntiobf() {\n\t\treturn antiobf;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getObfcontrol() {\n\t\treturn obfcontrol;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getObfattr() {\n\t\treturn obfattr;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getConstobf() {\n\t\treturn constobf;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getHidebridgemethods() {\n\t\treturn hidebridgemethods;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getIgnoreexceptions() {\n\t\treturn ignoreexceptions;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getForceexceptionprune() {\n\t\treturn forceexceptionprune;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getAexagg() {\n\t\treturn aexagg;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getAexagg2() {\n\t\treturn aexagg2;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getRecovertypeclash() {\n\t\treturn recovertypeclash;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getRecovertypehints() {\n\t\treturn recovertypehints;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getRecover() {\n\t\treturn recover;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getEclipse() {\n\t\treturn eclipse;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getOverride() {\n\t\treturn override;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getShowinferrable() {\n\t\treturn showinferrable;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getVersion() {\n\t\treturn version;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getLabelledblocks() {\n\t\treturn labelledblocks;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getJ14classobj() {\n\t\treturn j14classobj;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getHidelangimports() {\n\t\treturn hidelangimports;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getRenamedupmembers() {\n\t\treturn renamedupmembers;\n\t}\n\n\t@Nonnull\n\tpublic ObservableInteger getRenamesmallmembers() {\n\t\treturn renamesmallmembers;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getRenameillegalidents() {\n\t\treturn renameillegalidents;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getRenameenumidents() {\n\t\treturn renameenumidents;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getRemovedeadconditionals() {\n\t\treturn removedeadconditionals;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getAggressivedoextension() {\n\t\treturn aggressivedoextension;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getAggressiveduff() {\n\t\treturn aggressiveduff;\n\t}\n\n\t@Nonnull\n\tpublic ObservableInteger getAggressivedocopy() {\n\t\treturn aggressivedocopy;\n\t}\n\n\t@Nonnull\n\tpublic ObservableInteger getAggressivesizethreshold() {\n\t\treturn aggressivesizethreshold;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getStaticinitreturn() {\n\t\treturn staticinitreturn;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getUsenametable() {\n\t\treturn usenametable;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getPullcodecase() {\n\t\treturn pullcodecase;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<TrooleanOption> getAllowmalformedswitch() {\n\t\treturn allowmalformedswitch;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getElidescala() {\n\t\treturn elidescala;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BooleanOption> getUsesignatures() {\n\t\treturn usesignatures;\n\t}\n\n\t/**\n\t * Wrapper for CFR boolean values.\n\t */\n\tpublic enum BooleanOption {\n\t\tDEFAULT,\n\t\tTRUE,\n\t\tFALSE\n\t}\n\n\t/**\n\t * Wrapper for CFR troolean values.\n\t */\n\tpublic enum TrooleanOption {\n\t\tDEFAULT,\n\t\tNEITHER,\n\t\tTRUE,\n\t\tFALSE\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrDecompiler.java",
    "content": "package software.coley.recaf.services.decompile.cfr;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.benf.cfr.reader.api.CfrDriver;\nimport org.benf.cfr.reader.bytecode.analysis.structured.statement.StructuredComment;\nimport org.benf.cfr.reader.util.CfrVersionInfo;\nimport org.benf.cfr.reader.util.DecompilerComment;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.decompile.AbstractJvmDecompiler;\nimport software.coley.recaf.services.decompile.DecompileResult;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.util.ReflectUtil;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.lang.reflect.Field;\nimport java.util.Collections;\nimport java.util.Objects;\n\n/**\n * CFR decompiler implementation.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class CfrDecompiler extends AbstractJvmDecompiler {\n\tpublic static final String NAME = \"CFR\";\n\tprivate final CfrConfig config;\n\n\t/**\n\t * New CFR decompiler instance.\n\t *\n\t * @param workspaceManager\n\t * \t\tWorkspace manager.\n\t * @param config\n\t * \t\tConfig instance.\n\t */\n\t@Inject\n\tpublic CfrDecompiler(@Nonnull WorkspaceManager workspaceManager, @Nonnull CfrConfig config) {\n\t\tsuper(NAME, CfrVersionInfo.VERSION, config);\n\t\tthis.config = config;\n\n\t\tworkspaceManager.addWorkspaceCloseListener(workspace -> cleanup());\n\n\t\t// TODO: Update CFR when https://github.com/leibnitz27/cfr/issues/361 is fixed\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo) {\n\t\tString name = classInfo.getName();\n\t\tbyte[] bytecode = classInfo.getBytecode();\n\t\tClassSource source = new ClassSource(workspace, name, bytecode);\n\t\tSinkFactoryImpl sink = new SinkFactoryImpl();\n\t\tCfrDriver driver = new CfrDriver.Builder()\n\t\t\t\t.withClassFileSource(source)\n\t\t\t\t.withOutputSink(sink)\n\t\t\t\t.withOptions(config.toMap())\n\t\t\t\t.build();\n\t\tdriver.analyse(Collections.singletonList(name));\n\t\tString decompile = sink.getDecompilation();\n\t\tint configHash = getConfig().getHash();\n\t\tif (decompile == null) {\n\t\t\tThrowable exception = Objects.requireNonNullElseGet(sink.getException(), () -> {\n\t\t\t\tThrowable err = new IllegalStateException(\"CFR did not provide any output:\\n- No decompilation output\\n- No error message / trace\");\n\t\t\t\terr.setStackTrace(new StackTraceElement[0]);\n\t\t\t\treturn err;\n\t\t\t});\n\t\t\treturn new DecompileResult(exception, configHash);\n\t\t}\n\t\treturn new DecompileResult(filter(decompile), configHash);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic CfrConfig getConfig() {\n\t\treturn (CfrConfig) super.getConfig();\n\t}\n\n\tprivate static void cleanup() {\n\t\t// Some CFR code through a chain of events assigns a container to this constant, and it\n\t\t// holds a reference to our ClassSource which has the workspace data in it.\n\t\t// That causes a memory leak, so we clear the container here after each decomp.\n\t\tStructuredComment.EMPTY_COMMENT.setContainer(null);\n\t}\n\n\tprivate static String filter(String decompile) {\n\t\t// CFR emits a 'Decompiled with CFR' header, which is annoying, so we'll remove that.\n\t\tint commentStart = decompile.indexOf(\"/*\\n\");\n\t\tint commentEnd = decompile.indexOf(\" */\\n\");\n\t\tif (commentStart >= 0 && commentEnd > commentStart)\n\t\t\tdecompile = decompile.substring(0, commentStart) + decompile.substring(commentEnd + 4);\n\t\treturn decompile;\n\t}\n\n\tstatic {\n\t\ttry {\n\t\t\t// Rewrite CFR comments to not say \"use --option\" since this is not a command line context.\n\t\t\tField field = ReflectUtil.getDeclaredField(DecompilerComment.class, \"comment\");\n\t\t\tReflectUtil.quietSet(DecompilerComment.RENAME_MEMBERS, field, \"Duplicate member names detected\");\n\t\t\tReflectUtil.quietSet(DecompilerComment.ILLEGAL_IDENTIFIERS, field, \"Illegal identifiers detected\");\n\t\t\tReflectUtil.quietSet(DecompilerComment.MALFORMED_SWITCH, field, \"Recovered potentially malformed switches\");\n\t\t} catch (Exception ex) {\n\t\t\tex.printStackTrace();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/ClassSource.java",
    "content": "package software.coley.recaf.services.decompile.cfr;\n\nimport jakarta.annotation.Nonnull;\nimport org.benf.cfr.reader.api.ClassFileSource;\nimport org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Collection;\nimport java.util.Collections;\n\n/**\n * CFR class source. Provides access to workspace clases.\n *\n * @author Matt Coley\n */\npublic class ClassSource implements ClassFileSource {\n\tprivate final Workspace workspace;\n\tprivate final String targetClassName;\n\tprivate final byte[] targetClassBytecode;\n\n\t/**\n\t * Constructs a CFR class source.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to pull classes from.\n\t * @param targetClassName\n\t * \t\tName to override.\n\t * @param targetClassBytecode\n\t * \t\tBytecode to override.\n\t */\n\tpublic ClassSource(@Nonnull Workspace workspace, @Nonnull String targetClassName,\n\t                   @Nonnull byte[] targetClassBytecode) {\n\t\tthis.workspace = workspace;\n\t\tthis.targetClassName = targetClassName;\n\t\tthis.targetClassBytecode = targetClassBytecode;\n\t}\n\n\t@Override\n\tpublic void informAnalysisRelativePathDetail(String usePath, String specPath) {\n\t}\n\n\t@Override\n\tpublic Collection<String> addJar(String jarPath) {\n\t\treturn Collections.emptySet();\n\t}\n\n\t@Override\n\tpublic String getPossiblyRenamedPath(String path) {\n\t\treturn path;\n\t}\n\n\t@Override\n\tpublic Pair<byte[], String> getClassFileContent(String inputPath) {\n\t\tString className = inputPath.substring(0, inputPath.indexOf(\".class\"));\n\t\tbyte[] code;\n\t\tif (className.equals(targetClassName)) {\n\t\t\tcode = targetClassBytecode;\n\t\t} else {\n\t\t\tClassPathNode result = workspace.findClass(className);\n\t\t\tcode = result == null ? null : result.getValue().asJvmClass().getBytecode();\n\t\t}\n\t\treturn new Pair<>(code, inputPath);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/SinkFactoryImpl.java",
    "content": "package software.coley.recaf.services.decompile.cfr;\n\nimport jakarta.annotation.Nullable;\nimport org.benf.cfr.reader.api.OutputSinkFactory;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * Cfr logging/output sinker.\n *\n * @author Matt\n */\npublic class SinkFactoryImpl implements OutputSinkFactory {\n\tprivate static final Logger logger = Logging.get(SinkFactoryImpl.class);\n\tprivate Throwable exception;\n\tprivate String decompile;\n\n\t@Override\n\tpublic List<SinkClass> getSupportedSinks(SinkType sinkType, Collection<SinkClass> collection) {\n\t\treturn Arrays.asList(SinkClass.values());\n\t}\n\n\t@Override\n\tpublic <T> Sink<T> getSink(SinkType sinkType, SinkClass sinkClass) {\n\t\treturn switch (sinkType) {\n\t\t\tcase JAVA -> this::setDecompilation;\n\t\t\tcase EXCEPTION -> this::handleException;\n\t\t\tdefault -> t -> {\n\t\t\t};\n\t\t};\n\t}\n\n\tprivate <T> void handleException(@Nullable T value) {\n\t\tif (value instanceof Throwable) {\n\t\t\tlogger.error(\"CFR Error: {}\", value);\n\t\t\texception = (Throwable) value;\n\t\t} else {\n\t\t\tlogger.error(\"CFR encountered an error but provided no additional information\");\n\t\t}\n\t}\n\n\tprivate <T> void setDecompilation(T value) {\n\t\tdecompile = value.toString();\n\t}\n\n\t/**\n\t * @return Decompiled class content.\n\t */\n\t@Nullable\n\tpublic String getDecompilation() {\n\t\treturn decompile;\n\t}\n\n\t/**\n\t * @return Failure reason.\n\t */\n\t@Nullable\n\tpublic Throwable getException() {\n\t\treturn exception;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/fallback/FallbackConfig.java",
    "content": "package software.coley.recaf.services.decompile.fallback;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.services.decompile.BaseDecompilerConfig;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\n\n/**\n * Config for {@link FallbackDecompiler}\n *\n * @author Matt Coley\n */\n@ApplicationScoped\n@ExcludeFromJacocoGeneratedReport(justification = \"Config POJO\")\npublic class FallbackConfig extends BaseDecompilerConfig {\n\t@Inject\n\tpublic FallbackConfig() {\n\t\tsuper(\"decompiler-fallback\" + CONFIG_SUFFIX);\n\t\tregisterConfigValuesHashUpdates();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/fallback/FallbackDecompiler.java",
    "content": "package software.coley.recaf.services.decompile.fallback;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.decompile.AbstractJvmDecompiler;\nimport software.coley.recaf.services.decompile.DecompileResult;\nimport software.coley.recaf.services.decompile.fallback.print.ClassPrinter;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Fallback decompiler implementation.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class FallbackDecompiler extends AbstractJvmDecompiler {\n\tpublic static final String NAME = \"Fallback\";\n\tprivate static final String VERSION = \"1.0.0\";\n\tprivate final TextFormatConfig formatConfig;\n\n\t/**\n\t * New Procyon decompiler instance.\n\t *\n\t * @param config\n\t * \t\tConfig instance.\n\t */\n\t@Inject\n\tpublic FallbackDecompiler(@Nonnull FallbackConfig config, @Nonnull TextFormatConfig formatConfig) {\n\t\tsuper(NAME, VERSION, config);\n\t\tthis.formatConfig = formatConfig;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo) {\n\t\tString decompile = new ClassPrinter(formatConfig, classInfo).print();\n\t\tint configHash = getConfig().getHash();\n\t\treturn new DecompileResult(decompile, configHash);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/fallback/print/ClassPrinter.java",
    "content": "package software.coley.recaf.services.decompile.fallback.print;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.annotation.AnnotationElement;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * Basic class printer.\n *\n * @author Matt Coley\n */\npublic class ClassPrinter {\n\tprivate final TextFormatConfig format;\n\tprivate final JvmClassInfo classInfo;\n\n\t/**\n\t * @param format\n\t * \t\tFormat config.\n\t * @param classInfo\n\t * \t\tClass to print.\n\t */\n\tpublic ClassPrinter(@Nonnull TextFormatConfig format, @Nonnull JvmClassInfo classInfo) {\n\t\tthis.format = format;\n\t\tthis.classInfo = classInfo;\n\t}\n\n\t/**\n\t * @return Formatted class output.\n\t */\n\t@Nonnull\n\tpublic String print() {\n\t\tPrinter out = new Printer();\n\t\tappendPackage(out);\n\t\tappendImports(out);\n\t\tappendDeclaration(out);\n\t\tappendMembers(out);\n\t\treturn out.toString();\n\t}\n\n\t/**\n\t * Appends the package name to the output.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t */\n\tprivate void appendPackage(@Nonnull Printer out) {\n\t\tString className = classInfo.getName();\n\t\tif (className.contains(\"/\")) {\n\t\t\tString packageName = format.filterEscape(className.substring(0, className.lastIndexOf('/')));\n\t\t\tout.appendLine(\"package \" + packageName.replace('/', '.') + \";\");\n\t\t\tout.newLine();\n\t\t}\n\t}\n\n\t/**\n\t * Appends each imported class to the output.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t */\n\tprivate void appendImports(@Nonnull Printer out) {\n\t\tString lastRootPackage = null;\n\t\tNavigableSet<String> referencedClasses = new TreeSet<>(classInfo.getReferencedClasses());\n\t\tboolean hasImports = false;\n\t\tfor (String referencedClass : referencedClasses) {\n\t\t\t// Skip classes in the default package.\n\t\t\tif (!referencedClass.contains(\"/\")) continue;\n\n\t\t\t// Skip core classes that are implicitly imported.\n\t\t\tif (referencedClass.startsWith(\"java/lang/\")) continue;\n\n\t\t\t// Skip self.\n\t\t\tif (referencedClass.equals(classInfo.getName())) continue;\n\n\t\t\t// Break root package imports up for clarity. For example:\n\t\t\t//  - com.*\n\t\t\t//  - org.*\n\t\t\t// Between these two import groups will be a blank line.\n\t\t\tString rootPackage = referencedClass.substring(0, referencedClass.indexOf('/'));\n\t\t\tif (lastRootPackage == null) lastRootPackage = rootPackage;\n\t\t\tif (!rootPackage.equals(lastRootPackage)) {\n\t\t\t\tout.newLine();\n\t\t\t\tlastRootPackage = rootPackage;\n\t\t\t}\n\n\t\t\t// Add import\n\t\t\tout.appendLine(\"import \" + format.filterEscape(referencedClass.replace('/', '.')) + \";\");\n\t\t\thasImports = true;\n\n\t\t\t// TODO: Import names aren't always correct since '$' should also be escaped when it represents the separation of\n\t\t\t//     an outer and inner class. Since we have workspace and runtime access we 'should' check this\n\t\t\t//     and attempt to make more accurate output\n\t\t}\n\t\tif (hasImports) out.newLine();\n\t}\n\n\t/**\n\t * Appends the class declaration to the output.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t */\n\tprivate void appendDeclaration(@Nonnull Printer out) {\n\t\tappendDeclarationAnnotations(out);\n\t\tif (classInfo.hasEnumModifier()) {\n\t\t\tappendEnumDeclaration(out);\n\t\t} else if (classInfo.hasAnnotationModifier()) {\n\t\t\tappendAnnotationDeclaration(out);\n\t\t} else if (classInfo.hasInterfaceModifier()) {\n\t\t\tappendInterfaceDeclaration(out);\n\t\t} else {\n\t\t\tappendStandardDeclaration(out);\n\t\t}\n\t}\n\n\t/**\n\t * Appends class annotations to the output.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t */\n\tprivate void appendDeclarationAnnotations(@Nonnull Printer out) {\n\t\tString annotations = PrintUtils.annotationsToString(format, classInfo);\n\t\tif (!annotations.isBlank()) out.appendMultiLine(annotations);\n\t}\n\n\t/**\n\t * Appends the enum formatted declaration to the output.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t */\n\tprivate void appendEnumDeclaration(@Nonnull Printer out) {\n\t\tint acc = classInfo.getAccess();\n\n\t\t// Get flag-set and remove 'enum' and 'final'.\n\t\t// We will add 'enum' ourselves, and 'final' is redundant.\n\t\tSet<AccessFlag> flagSet = AccessFlag.getApplicableFlags(AccessFlag.Type.CLASS, acc);\n\t\tflagSet.remove(AccessFlag.ACC_ENUM);\n\t\tflagSet.remove(AccessFlag.ACC_FINAL);\n\t\tString decFlagsString = AccessFlag.sortAndToString(AccessFlag.Type.CLASS, flagSet);\n\t\tStringBuilder sb = new StringBuilder();\n\t\tif (decFlagsString.isBlank()) {\n\t\t\tsb.append(\"enum \");\n\t\t} else {\n\t\t\tsb.append(decFlagsString).append(\" enum \");\n\t\t}\n\t\tsb.append(format.filter(classInfo.getName()));\n\t\tString superName = classInfo.getSuperName();\n\n\t\t// Should normally extend enum. Technically bytecode allows for other types if those at runtime then\n\t\t// inherit from Enum.\n\t\tif (superName != null && !superName.equals(\"java/lang/Enum\")) {\n\t\t\tsb.append(\" extends \").append(format.filter(superName));\n\t\t}\n\t\tif (!classInfo.getInterfaces().isEmpty()) {\n\t\t\tsb.append(\" implements \");\n\t\t\tString interfaces = classInfo.getInterfaces().stream()\n\t\t\t\t\t.map(format::filter)\n\t\t\t\t\t.collect(Collectors.joining(\", \"));\n\t\t\tsb.append(interfaces);\n\t\t}\n\t\tout.appendLine(sb.toString());\n\t}\n\n\t/**\n\t * Appends the annotation formatted declaration to the output.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t */\n\tprivate void appendAnnotationDeclaration(@Nonnull Printer out) {\n\t\tint acc = classInfo.getAccess();\n\n\t\t// Get flag-set and remove 'interface' and 'abstract'.\n\t\t// We will add 'interface' ourselves, and 'abstract' is redundant.\n\t\tSet<AccessFlag> flagSet = AccessFlag.getApplicableFlags(AccessFlag.Type.CLASS, acc);\n\t\tflagSet.remove(AccessFlag.ACC_ANNOTATION);\n\t\tflagSet.remove(AccessFlag.ACC_INTERFACE);\n\t\tflagSet.remove(AccessFlag.ACC_ABSTRACT);\n\t\tString decFlagsString = AccessFlag.sortAndToString(AccessFlag.Type.CLASS, flagSet);\n\t\tStringBuilder sb = new StringBuilder();\n\t\tif (decFlagsString.isBlank()) {\n\t\t\tsb.append(\"@interface \");\n\t\t} else {\n\t\t\tsb.append(decFlagsString).append(\" @interface \");\n\t\t}\n\t\tsb.append(format.filter(classInfo.getName()));\n\t\tout.appendLine(sb.toString());\n\t}\n\n\t/**\n\t * Appends the interface formatted declaration to the output.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t */\n\tprivate void appendInterfaceDeclaration(@Nonnull Printer out) {\n\t\tint acc = classInfo.getAccess();\n\n\t\t// Get flag-set and remove 'interface' and 'abstract'.\n\t\t// We will add 'interface' ourselves, and 'abstract' is redundant.\n\t\tSet<AccessFlag> flagSet = AccessFlag.getApplicableFlags(AccessFlag.Type.CLASS, acc);\n\t\tflagSet.remove(AccessFlag.ACC_INTERFACE);\n\t\tflagSet.remove(AccessFlag.ACC_ABSTRACT);\n\t\tString decFlagsString = AccessFlag.sortAndToString(AccessFlag.Type.CLASS, flagSet);\n\t\tStringBuilder sb = new StringBuilder();\n\t\tif (decFlagsString.isBlank()) {\n\t\t\tsb.append(\"interface \");\n\t\t} else {\n\t\t\tsb.append(decFlagsString)\n\t\t\t\t\t.append(\" interface \");\n\t\t}\n\t\tsb.append(format.filter(classInfo.getName()));\n\t\tif (!classInfo.getInterfaces().isEmpty()) {\n\t\t\t// Interfaces use 'extends' rather than 'implements'.\n\t\t\tsb.append(\" extends \");\n\t\t\tString interfaces = classInfo.getInterfaces().stream()\n\t\t\t\t\t.map(format::filter)\n\t\t\t\t\t.collect(Collectors.joining(\", \"));\n\t\t\tsb.append(interfaces);\n\t\t}\n\t\tout.appendLine(sb.toString());\n\t}\n\n\t/**\n\t * Appends the class formatted declaration to the output.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t */\n\tprivate void appendStandardDeclaration(@Nonnull Printer out) {\n\t\tint acc = classInfo.getAccess();\n\t\tString decFlagsString = AccessFlag.sortAndToString(AccessFlag.Type.CLASS, acc);\n\t\tStringBuilder sb = new StringBuilder();\n\t\tif (decFlagsString.isBlank()) {\n\t\t\tsb.append(\"class \");\n\t\t} else {\n\t\t\tsb.append(decFlagsString).append(\" class \");\n\t\t}\n\t\tsb.append(format.filter(classInfo.getName()));\n\t\tString superName = classInfo.getSuperName();\n\t\tif (superName != null && !superName.equals(\"java/lang/Object\")) {\n\t\t\tsb.append(\" extends \").append(format.filter(superName));\n\t\t}\n\t\tif (!classInfo.getInterfaces().isEmpty()) {\n\t\t\tsb.append(\" implements \");\n\t\t\tString interfaces = classInfo.getInterfaces().stream()\n\t\t\t\t\t.map(format::filter)\n\t\t\t\t\t.collect(Collectors.joining(\", \"));\n\t\t\tsb.append(interfaces);\n\t\t}\n\t\tout.appendLine(sb.toString());\n\t}\n\n\t/**\n\t * Appends the class body (members).\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t */\n\tprivate void appendMembers(@Nonnull Printer out) {\n\t\tout.appendLine(\"{\");\n\t\tif (!classInfo.getFields().isEmpty()) {\n\t\t\tPrinter fieldPrinter = new Printer();\n\t\t\tfieldPrinter.setIndent(\"    \");\n\t\t\tif (classInfo.hasEnumModifier()) {\n\t\t\t\tappendEnumFieldMembers(fieldPrinter);\n\t\t\t} else {\n\t\t\t\tappendFieldMembers(fieldPrinter);\n\t\t\t}\n\t\t\tout.appendMultiLine(fieldPrinter.toString());\n\t\t\tout.appendLine(\"\");\n\t\t}\n\t\tif (!classInfo.getMethods().isEmpty()) {\n\t\t\tPrinter methodPrinter = new Printer();\n\t\t\tmethodPrinter.setIndent(\"    \");\n\n\t\t\t// Some method types we'll want to handle a bit differently.\n\t\t\t// Split them up:\n\t\t\t//  - Regular methods\n\t\t\t//  - The static initializer\n\t\t\t//  - Constructors\n\t\t\tList<MethodMember> methods = new ArrayList<>(classInfo.getMethods());\n\t\t\tMethodMember staticInitializer = classInfo.getDeclaredMethod(\"<clinit>\", \"()V\");\n\t\t\tList<MethodMember> constructors = classInfo.methodStream()\n\t\t\t\t\t.filter(m -> m.getName().equals(\"<init>\"))\n\t\t\t\t\t.toList();\n\t\t\tmethods.remove(staticInitializer);\n\t\t\tmethods.removeAll(constructors);\n\n\t\t\t// We'll place the static initializer first regardless of where its defined order-wise.\n\t\t\tif (staticInitializer != null) {\n\t\t\t\tappendStaticInitializer(methodPrinter, staticInitializer);\n\t\t\t\tmethodPrinter.newLine();\n\t\t\t}\n\n\t\t\t// Then the constructors.\n\t\t\tfor (MethodMember constructor : constructors) {\n\t\t\t\tappendConstructor(methodPrinter, constructor);\n\t\t\t\tmethodPrinter.newLine();\n\t\t\t}\n\n\t\t\t// Then the rest of the methods, in whatever order they're defined in.\n\t\t\tfor (MethodMember method : methods) {\n\t\t\t\tappendMethod(methodPrinter, method);\n\t\t\t\tmethodPrinter.newLine();\n\t\t\t}\n\n\t\t\t// Append them all to the output.\n\t\t\tout.appendMultiLine(methodPrinter.toString());\n\t\t}\n\t\tout.appendLine(\"}\");\n\t}\n\n\t/**\n\t * Appends all fields in the class.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t *\n\t * @see #appendEnumFieldMembers(Printer) To be used when the current class is an enum.\n\t */\n\tprivate void appendFieldMembers(@Nonnull Printer out) {\n\t\tfor (FieldMember field : classInfo.getFields()) {\n\t\t\tappendField(out, field);\n\t\t}\n\t}\n\n\t/**\n\t * Appends all enum constants, then other fields in the class.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t *\n\t * @see #appendEnumFieldMembers(Printer) To be used when the current class is not an enum.\n\t */\n\tprivate void appendEnumFieldMembers(@Nonnull Printer out) {\n\t\t// Filter out enum constants\n\t\tList<FieldMember> enumConstFields = new ArrayList<>();\n\t\tList<FieldMember> otherFields = new ArrayList<>();\n\t\tfor (FieldMember field : classInfo.getFields()) {\n\t\t\tif (isEnumConst(field)) {\n\t\t\t\tenumConstFields.add(field);\n\t\t\t} else {\n\t\t\t\totherFields.add(field);\n\t\t\t}\n\t\t}\n\n\t\t// Print enum constants first.\n\t\tfor (int i = 0; i < enumConstFields.size(); i++) {\n\t\t\tString suffix = i == enumConstFields.size() - 1 ? \";\\n\" : \", \";\n\t\t\tFieldMember enumConst = enumConstFields.get(i);\n\t\t\tStringBuilder sb = new StringBuilder();\n\t\t\tString annotations = PrintUtils.annotationsToString(format, enumConst);\n\t\t\tif (!annotations.isBlank())\n\t\t\t\tsb.append(annotations).append('\\n');\n\t\t\tsb.append(enumConst.getName()).append(suffix);\n\t\t\tout.appendMultiLine(sb.toString());\n\t\t}\n\t\tout.newLine();\n\n\t\t// And then the rest of the fields\n\t\tfor (FieldMember field : otherFields) {\n\t\t\tappendField(out, field);\n\t\t}\n\t}\n\n\t/**\n\t * Appends the given field.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t * @param field\n\t * \t\tField to write to the given printer.\n\t */\n\tprivate void appendField(@Nonnull Printer out, @Nonnull FieldMember field) {\n\t\tStringBuilder declaration = new StringBuilder();\n\n\t\t// Append annotations to builder.\n\t\tString annotations = PrintUtils.annotationsToString(format, field);\n\t\tif (!annotations.isBlank())\n\t\t\tdeclaration.append(annotations).append('\\n');\n\n\t\t// Append flags to builder.\n\t\tCollection<AccessFlag> flags = AccessFlag.getApplicableFlags(AccessFlag.Type.FIELD, field.getAccess());\n\t\tflags.remove(AccessFlag.ACC_ENUM); // We don't want to print 'enum' as a flag\n\t\tflags = AccessFlag.sort(AccessFlag.Type.FIELD, flags);\n\t\tif (!flags.isEmpty())\n\t\t\tdeclaration.append(AccessFlag.toString(flags)).append(' ');\n\n\t\t// Append type + name to builder.\n\t\tType type = Type.getType(field.getDescriptor());\n\t\tString typeName = format.filter(type.getClassName());\n\t\tif (typeName.contains(\".\"))\n\t\t\ttypeName = typeName.substring(typeName.lastIndexOf(\".\") + 1);\n\t\tdeclaration.append(typeName).append(' ').append(format.filter(field.getName()));\n\n\t\t// Append value to builder.\n\t\tObject value = field.getDefaultValue();\n\t\tif (value != null) {\n\t\t\tswitch (value) {\n\t\t\t\tcase String s -> value = \"\\\"\" + format.filter(s) + \"\\\"\";\n\t\t\t\tcase Float v -> value = value + \"F\";\n\t\t\t\tcase Long l -> value = value + \"L\";\n\t\t\t\tdefault -> {\n\t\t\t\t\t// No change\n\t\t\t\t}\n\t\t\t}\n\t\t\tdeclaration.append(\" = \").append(value);\n\t\t}\n\n\t\t// Cap it off.\n\t\tdeclaration.append(';');\n\t\tout.appendMultiLine(declaration.toString());\n\t}\n\n\t/**\n\t * @param field\n\t * \t\tField to check.\n\t *\n\t * @return {@code true} when it is an enum constant of the {@link #classInfo current class}.\n\t */\n\tprivate boolean isEnumConst(@Nonnull FieldMember field) {\n\t\tString descriptor = field.getDescriptor();\n\t\tif (descriptor.length() < 3) return false;\n\n\t\tString type = descriptor.substring(1, descriptor.length() - 1);\n\n\t\t// Must be same type as declaring class.\n\t\tif (!type.equals(classInfo.getName())) return false;\n\n\t\t// Must have enum const flags\n\t\treturn AccessFlag.hasAll(field.getAccess(), AccessFlag.ACC_STATIC, AccessFlag.ACC_FINAL);\n\t}\n\n\t/**\n\t * Appends the given static initializer method.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t * @param method\n\t * \t\tStatic initializer method.\n\t */\n\tprivate void appendStaticInitializer(@Nonnull Printer out, @Nonnull MethodMember method) {\n\t\tMethodPrinter clinitPrinter = new MethodPrinter(format, classInfo, method) {\n\t\t\t@Override\n\t\t\tprotected void buildDeclarationFlags(@Nonnull StringBuilder sb) {\n\t\t\t\t// Force only printing the modifier 'static' even if other flags are present\n\t\t\t\tsb.append(\"static\");\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tprotected void buildDeclarationReturnType(@Nonnull StringBuilder sb) {\n\t\t\t\t// no-op\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tprotected void buildDeclarationName(@Nonnull StringBuilder sb) {\n\t\t\t\t// no-op\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tprotected void buildDeclarationArgs(@Nonnull StringBuilder sb) {\n\t\t\t\t// no-op\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tprotected void buildDeclarationThrows(@Nonnull StringBuilder sb) {\n\t\t\t\t// no-op\n\t\t\t}\n\t\t};\n\t\tout.appendMultiLine(clinitPrinter.print());\n\t}\n\n\t/**\n\t * Appends the given constructor method.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t * @param method\n\t * \t\tConstructor method.\n\t */\n\tprivate void appendConstructor(@Nonnull Printer out, @Nonnull MethodMember method) {\n\t\tMethodPrinter constructorPrinter = new MethodPrinter(format, classInfo, method) {\n\t\t\t@Override\n\t\t\tprotected void buildDeclarationReturnType(@Nonnull StringBuilder sb) {\n\t\t\t\t// no-op\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tprotected void buildDeclarationName(@Nonnull StringBuilder sb) {\n\t\t\t\t// The name is always the class name\n\t\t\t\tsb.append(format.filterEscape(StringUtil.shortenPath(classInfo.getName())));\n\t\t\t}\n\t\t};\n\t\tout.appendMultiLine(constructorPrinter.print());\n\t}\n\n\t/**\n\t * Appends the given method.\n\t *\n\t * @param out\n\t * \t\tPrinter to write to.\n\t * @param method\n\t * \t\tRegular method.\n\t */\n\tprivate void appendMethod(@Nonnull Printer out, @Nonnull MethodMember method) {\n\t\tif (classInfo.hasAnnotationModifier()) {\n\t\t\tMethodPrinter constructorPrinter = new MethodPrinter(format, classInfo, method) {\n\t\t\t\t@Override\n\t\t\t\tprotected void buildDeclarationFlags(@Nonnull StringBuilder sb) {\n\t\t\t\t\t// no-op since all methods are 'public abstract' per interface contract (with additional restrictions)\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tprotected void appendAbstractBody(@Nonnull StringBuilder sb) {\n\t\t\t\t\tAnnotationElement annotationDefault = method.getAnnotationDefault();\n\t\t\t\t\tif (annotationDefault != null) {\n\t\t\t\t\t\tsb.append(\" default \").append(PrintUtils.elementToString(format, annotationDefault)).append(\";\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsb.append(\";\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t\tout.appendMultiLine(constructorPrinter.print());\n\t\t} else if (classInfo.hasInterfaceModifier()) {\n\t\t\tMethodPrinter constructorPrinter = new MethodPrinter(format, classInfo, method) {\n\t\t\t\t@Override\n\t\t\t\tprotected void buildDeclarationFlags(@Nonnull StringBuilder sb) {\n\t\t\t\t\tCollection<AccessFlag> flags = AccessFlag.getApplicableFlags(AccessFlag.Type.METHOD, method.getAccess());\n\t\t\t\t\tflags = AccessFlag.sort(AccessFlag.Type.METHOD, flags);\n\t\t\t\t\tflags.remove(AccessFlag.ACC_PUBLIC);\n\t\t\t\t\tflags.remove(AccessFlag.ACC_ABSTRACT);\n\t\t\t\t\tboolean isAbstract = AccessFlag.isAbstract(method.getAccess());\n\t\t\t\t\tif (!flags.isEmpty()) {\n\t\t\t\t\t\tString flagsStr = AccessFlag.toString(flags);\n\t\t\t\t\t\tif (!isAbstract)\n\t\t\t\t\t\t\tsb.append(\"default \");\n\t\t\t\t\t\tsb.append(flagsStr).append(' ');\n\t\t\t\t\t} else if (!isAbstract)\n\t\t\t\t\t\tsb.append(\"default \");\n\t\t\t\t}\n\t\t\t};\n\t\t\tout.appendMultiLine(constructorPrinter.print());\n\t\t} else {\n\t\t\tout.appendMultiLine(new MethodPrinter(format, classInfo, method).print());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/fallback/print/MethodPrinter.java",
    "content": "package software.coley.recaf.services.decompile.fallback.print;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.util.Textifier;\nimport org.objectweb.asm.util.TraceMethodVisitor;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.visitors.MemberFilteringVisitor;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.PrintWriter;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Utility for printing method bodies.\n *\n * @author Matt Coley\n */\npublic class MethodPrinter {\n\tprivate final TextFormatConfig format;\n\tprivate final JvmClassInfo classInfo;\n\tprivate final MethodMember method;\n\n\t/**\n\t * @param format\n\t * \t\tFormat config.\n\t * @param classInfo\n\t * \t\tClass containing the method.\n\t * @param method\n\t * \t\tMethod to print.\n\t */\n\tpublic MethodPrinter(@Nonnull TextFormatConfig format, @Nonnull JvmClassInfo classInfo, @Nonnull MethodMember method) {\n\t\tthis.format = format;\n\t\tthis.classInfo = classInfo;\n\t\tthis.method = method;\n\t}\n\n\t/**\n\t * @return Method string representation.\n\t */\n\t@Nonnull\n\tpublic String print() {\n\t\tStringBuilder sb = new StringBuilder();\n\t\tappendAnnotations(sb);\n\t\tappendDeclaration(sb);\n\t\tif (AccessFlag.isNative(method.getAccess()) || AccessFlag.isAbstract(method.getAccess())) {\n\t\t\tappendAbstractBody(sb);\n\t\t} else {\n\t\t\tappendBody(sb);\n\t\t}\n\t\treturn sb.toString();\n\t}\n\n\t/**\n\t * Appends annotations on the method declaration to the printer.\n\t *\n\t * @param sb\n\t * \t\tBuilder to add to.\n\t */\n\tprotected void appendAnnotations(@Nonnull StringBuilder sb) {\n\t\tString annotations = PrintUtils.annotationsToString(format, method);\n\t\tif (!annotations.isBlank()) sb.append(annotations).append('\\n');\n\t}\n\n\t/**\n\t * Appends the method declaration to the printer.\n\t * <ol>\n\t *     <li>{@link #buildDeclarationFlags(StringBuilder)}</li>\n\t *     <li>{@link #buildDeclarationReturnType(StringBuilder)}</li>\n\t *     <li>{@link #buildDeclarationName(StringBuilder)}</li>\n\t *     <li>{@link #buildDeclarationArgs(StringBuilder)}</li>\n\t * </ol>\n\t *\n\t * @param sb\n\t * \t\tBuilder to add to.\n\t */\n\tprotected void appendDeclaration(@Nonnull StringBuilder sb) {\n\t\tbuildDeclarationFlags(sb);\n\t\tbuildDeclarationReturnType(sb);\n\t\tbuildDeclarationName(sb);\n\t\tbuildDeclarationArgs(sb);\n\t\tbuildDeclarationThrows(sb);\n\t}\n\n\t/**\n\t * Appends the following pattern to the builder:\n\t * <pre>\n\t * public static abstract...\n\t * </pre>\n\t *\n\t * @param sb\n\t * \t\tBuilder to add to.\n\t */\n\tprotected void buildDeclarationFlags(@Nonnull StringBuilder sb) {\n\t\tCollection<AccessFlag> flags = AccessFlag.getApplicableFlags(AccessFlag.Type.METHOD, method.getAccess());\n\t\tflags = AccessFlag.sort(AccessFlag.Type.METHOD, flags);\n\t\tif (!flags.isEmpty()) {\n\t\t\tsb.append(AccessFlag.toString(flags)).append(' ');\n\t\t}\n\t}\n\n\t/**\n\t * Appends the following pattern to the builder:\n\t * <pre>\n\t * ReturnType\n\t * </pre>\n\t *\n\t * @param sb\n\t * \t\tBuilder to add to.\n\t */\n\tprotected void buildDeclarationReturnType(@Nonnull StringBuilder sb) {\n\t\tType methodType = Type.getMethodType(method.getDescriptor());\n\t\tString returnTypeName = format.filterEscape(methodType.getReturnType().getClassName());\n\t\tif (returnTypeName.contains(\".\"))\n\t\t\treturnTypeName = returnTypeName.substring(returnTypeName.lastIndexOf(\".\") + 1);\n\t\tsb.append(returnTypeName).append(' ');\n\t}\n\n\t/**\n\t * Appends the following pattern to the builder:\n\t * <pre>\n\t * methodName\n\t * </pre>\n\t *\n\t * @param sb\n\t * \t\tBuilder to add to.\n\t */\n\tprotected void buildDeclarationName(@Nonnull StringBuilder sb) {\n\t\tsb.append(format.filter(method.getName()));\n\t}\n\n\t/**\n\t * Appends the following pattern to the builder:\n\t * <pre>\n\t * (Type argName, Type argName)\n\t * </pre>\n\t *\n\t * @param sb\n\t * \t\tBuilder to add to.\n\t */\n\tprotected void buildDeclarationArgs(@Nonnull StringBuilder sb) {\n\t\tsb.append('(');\n\t\tboolean isVarargs = AccessFlag.isVarargs(method.getAccess());\n\t\tint varIndex = AccessFlag.isStatic(method.getAccess()) ? 0 : 1;\n\t\tType methodType = Type.getMethodType(method.getDescriptor());\n\t\tType[] argTypes = methodType.getArgumentTypes();\n\t\tfor (int param = 0; param < argTypes.length; param++) {\n\t\t\t// Get arg type text\n\t\t\tType argType = argTypes[param];\n\t\t\tString argTypeName = format.filterEscape(argType.getClassName());\n\t\t\tif (argTypeName.contains(\".\"))\n\t\t\t\targTypeName = argTypeName.substring(argTypeName.lastIndexOf(\".\") + 1);\n\t\t\tboolean isLast = param == argTypes.length - 1;\n\t\t\tif (isVarargs && isLast && argType.getSort() == Type.ARRAY) {\n\t\t\t\targTypeName = StringUtil.replaceLast(argTypeName, \"[]\", \"...\");\n\t\t\t}\n\n\t\t\t// Get arg name\n\t\t\tString name = \"p\" + varIndex;\n\t\t\tLocalVariable variable = method.getLocalVariable(varIndex);\n\t\t\tif (variable != null) {\n\t\t\t\tname = format.filter(variable.getName());\n\t\t\t}\n\n\t\t\t// Append to arg list\n\t\t\tsb.append(argTypeName).append(' ').append(name);\n\t\t\tif (!isLast) {\n\t\t\t\tsb.append(\", \");\n\t\t\t}\n\n\t\t\t// Increment for next var\n\t\t\tvarIndex += argType.getSize();\n\t\t}\n\t\tsb.append(')');\n\t}\n\n\t/**\n\t * Appends the following pattern to the builder:\n\t * <pre>\n\t * throws Item1, Item2, ...\n\t * </pre>\n\t *\n\t * @param sb\n\t * \t\tBuilder to add to.\n\t */\n\tprotected void buildDeclarationThrows(@Nonnull StringBuilder sb) {\n\t\tList<String> thrownTypes = method.getThrownTypes();\n\t\tif (thrownTypes.isEmpty())\n\t\t\treturn;\n\t\tString shortNames = thrownTypes.stream()\n\t\t\t\t.map(t -> format.filterEscape(StringUtil.shortenPath(t)))\n\t\t\t\t.collect(Collectors.joining(\", \"));\n\t\tsb.append(\" throws \").append(shortNames);\n\t}\n\n\t/**\n\t * Appends the abstract method body to the printer.\n\t *\n\t * @param sb\n\t * \t\tBuilder to add to.\n\t */\n\tprotected void appendAbstractBody(@Nonnull StringBuilder sb) {\n\t\tsb.append(';');\n\t}\n\n\t/**\n\t * Appends the method body to the printer.\n\t *\n\t * @param sb\n\t * \t\tBuilder to add to.\n\t */\n\tprotected void appendBody(@Nonnull StringBuilder sb) {\n\t\tTextifier textifier = new Textifier();\n\t\tClassVisitor printVisitor = new ClassVisitor(RecafConstants.getAsmVersion()) {\n\t\t\t@Override\n\t\t\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\t\t\treturn new TraceMethodVisitor(textifier);\n\t\t\t}\n\t\t};\n\t\tclassInfo.getClassReader().accept(new MemberFilteringVisitor(printVisitor, method), 0);\n\n\t\tsb.append(\" {\\n\");\n\t\tif (!textifier.getText().isEmpty()) {\n\t\t\t// Pipe ASM's text line model to an output.\n\t\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\t\tPrintWriter writer = new PrintWriter(baos);\n\t\t\ttextifier.print(writer);\n\t\t\twriter.close();\n\n\t\t\t// Cleanup the output text.\n\t\t\tString asmDump = baos.toString(StandardCharsets.UTF_8);\n\n\t\t\t// Indent it just a bit with our printer and append to the string builder.\n\t\t\tPrinter codePrinter = new Printer();\n\t\t\tcodePrinter.setIndent(\" \");\n\t\t\tcodePrinter.appendMultiLine(asmDump);\n\n\t\t\tsb.append(\"    /*\\n\");\n\t\t\tsb.append(codePrinter);\n\t\t\tsb.append(\"    */\\n\");\n\t\t}\n\t\tsb.append(\"    throw new RuntimeException(\\\"Stub method\\\");\\n\");\n\t\tsb.append(\"}\\n\");\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/fallback/print/PrintUtils.java",
    "content": "package software.coley.recaf.services.decompile.fallback.print;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationElement;\nimport software.coley.recaf.info.annotation.AnnotationEnumReference;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.util.EscapeUtil;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.Types;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.StringJoiner;\nimport java.util.stream.Collectors;\n\n/**\n * Various printing utilities.\n *\n * @author Matt Coley\n */\npublic class PrintUtils {\n\t/**\n\t * @param format\n\t * \t\tFormat config.\n\t * @param container\n\t * \t\tAnnotation container. Can be a class, field, or method.\n\t *\n\t * @return String display of the annotations on the given container. Empty string if there are no annotations.\n\t */\n\t@Nonnull\n\tpublic static String annotationsToString(@Nonnull TextFormatConfig format, @Nonnull Annotated container) {\n\t\t// Skip if there are no annotations.\n\t\tList<AnnotationInfo> annotations = container.getAnnotations();\n\t\tif (annotations.isEmpty())\n\t\t\treturn \"\";\n\n\t\t// Print all annotations.\n\t\tStringBuilder sb = new StringBuilder();\n\t\tfor (AnnotationInfo annotation : annotations)\n\t\t\tsb.append(annotationToString(format, annotation)).append('\\n');\n\t\tsb.setLength(sb.length() - 1); // Cut off ending '\\n'\n\t\treturn sb.toString();\n\t}\n\n\t/**\n\t * @param format\n\t * \t\tFormat config.\n\t * @param annotation\n\t * \t\tAnnotation to represent.\n\t *\n\t * @return String display of the annotation.\n\t */\n\t@Nonnull\n\tprivate static String annotationToString(@Nonnull TextFormatConfig format, @Nonnull AnnotationInfo annotation) {\n\t\tString annotationDesc = annotation.getDescriptor();\n\t\tif (Types.isValidDesc(annotationDesc)) {\n\t\t\tMap<String, AnnotationElement> elements = annotation.getElements();\n\t\t\tString annotationName = StringUtil.shortenPath(Type.getType(annotationDesc).getInternalName());\n\t\t\tStringBuilder sb = new StringBuilder(\"@\");\n\t\t\tsb.append(format.filterEscape(annotationName));\n\t\t\tif (!elements.isEmpty()) {\n\t\t\t\tif (elements.size() == 1 && elements.get(\"value\") != null) {\n\t\t\t\t\t// If we only have 'value' we can ommit the 'k=' portion of the standard 'k=v'\n\t\t\t\t\tAnnotationElement element = elements.values().iterator().next();\n\t\t\t\t\tsb.append(\"(\").append(elementToString(format, element)).append(\")\");\n\t\t\t\t} else {\n\t\t\t\t\t// Print all args in k=v format\n\t\t\t\t\tString args = elements.entrySet().stream()\n\t\t\t\t\t\t\t.map(e -> e.getKey() + \" = \" + elementToString(format, e.getValue()))\n\t\t\t\t\t\t\t.collect(Collectors.joining(\", \"));\n\t\t\t\t\tsb.append(\"(\").append(args).append(\")\");\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn sb.toString();\n\t\t} else {\n\t\t\treturn \"// Invalid annotation removed\";\n\t\t}\n\t}\n\n\t/**\n\t * @param format\n\t * \t\tFormat config.\n\t * @param element\n\t * \t\tAnnotation element to represent.\n\t *\n\t * @return String display of the annotation element.\n\t */\n\t@Nonnull\n\tpublic static String elementToString(@Nonnull TextFormatConfig format, @Nonnull AnnotationElement element) {\n\t\tObject value = element.getElementValue();\n\t\treturn elementValueToString(format, value);\n\t}\n\n\t/**\n\t * @param format\n\t * \t\tFormat config.\n\t * @param value\n\t * \t\tAnnotation element value to represent.\n\t *\n\t * @return String display of the element value.\n\t */\n\t@Nonnull\n\tprivate static String elementValueToString(@Nonnull TextFormatConfig format, @Nonnull Object value) {\n\t\tswitch (value) {\n\t\t\tcase String str -> {\n\t\t\t\t// String value\n\t\t\t\treturn '\"' + EscapeUtil.escapeStandardAndUnicodeWhitespace(str) + '\"';\n\t\t\t}\n\t\t\tcase Type type -> {\n\t\t\t\t// Class value\n\t\t\t\treturn format.filter(type.getInternalName()) + \".class\";\n\t\t\t}\n\t\t\tcase AnnotationInfo subAnnotation -> {\n\t\t\t\t// Annotation value\n\t\t\t\treturn annotationToString(format, subAnnotation);\n\t\t\t}\n\t\t\tcase AnnotationEnumReference enumReference -> {\n\t\t\t\t// Enum value\n\t\t\t\tString enumType = Type.getType(enumReference.getDescriptor()).getInternalName();\n\t\t\t\treturn format.filter(enumType) + '.' + enumReference.getValue();\n\t\t\t}\n\t\t\tcase List<?> list -> {\n\t\t\t\t// List of values\n\t\t\t\tString elements = list.stream()\n\t\t\t\t\t\t.map(e -> elementValueToString(format, e))\n\t\t\t\t\t\t.collect(Collectors.joining(\", \"));\n\t\t\t\treturn \"{ \" + elements + \" }\";\n\t\t\t}\n\t\t\tcase boolean[] array -> {\n\t\t\t\tStringJoiner str = new StringJoiner(\", \");\n\t\t\t\tfor (boolean b : array)\n\t\t\t\t\tstr.add(Boolean.toString(b));\n\t\t\t\treturn \"{ \" + str + \" }\";\n\t\t\t}\n\t\t\tcase byte[] array -> {\n\t\t\t\tStringJoiner str = new StringJoiner(\", \");\n\t\t\t\tfor (byte b : array)\n\t\t\t\t\tstr.add(Byte.toString(b));\n\t\t\t\treturn \"{ \" + str + \" }\";\n\t\t\t}\n\t\t\tcase char[] array -> {\n\t\t\t\tStringJoiner str = new StringJoiner(\", \");\n\t\t\t\tfor (char c : array)\n\t\t\t\t\tstr.add(Character.toString(c));\n\t\t\t\treturn \"{ \" + str + \" }\";\n\t\t\t}\n\t\t\tcase short[] array -> {\n\t\t\t\tStringJoiner str = new StringJoiner(\", \");\n\t\t\t\tfor (short s : array)\n\t\t\t\t\tstr.add(Short.toString(s));\n\t\t\t\treturn \"{ \" + str + \" }\";\n\t\t\t}\n\t\t\tcase int[] array -> {\n\t\t\t\tStringJoiner str = new StringJoiner(\", \");\n\t\t\t\tfor (int i : array)\n\t\t\t\t\tstr.add(Integer.toString(i));\n\t\t\t\treturn \"{ \" + str + \" }\";\n\t\t\t}\n\t\t\tcase float[] array -> {\n\t\t\t\tStringJoiner str = new StringJoiner(\", \");\n\t\t\t\tfor (float f : array)\n\t\t\t\t\tstr.add(Float.toString(f));\n\t\t\t\treturn \"{ \" + str + \" }\";\n\t\t\t}\n\t\t\tcase double[] array -> {\n\t\t\t\tStringJoiner str = new StringJoiner(\", \");\n\t\t\t\tfor (double d : array)\n\t\t\t\t\tstr.add(Double.toString(d));\n\t\t\t\treturn \"{ \" + str + \" }\";\n\t\t\t}\n\t\t\tcase long[] array -> {\n\t\t\t\tStringJoiner str = new StringJoiner(\", \");\n\t\t\t\tfor (long l : array)\n\t\t\t\t\tstr.add(Long.toString(l));\n\t\t\t\treturn \"{ \" + str + \" }\";\n\t\t\t}\n\t\t\tdefault -> {\n\t\t\t\t// Primitive\n\t\t\t\treturn value.toString();\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/fallback/print/Printer.java",
    "content": "package software.coley.recaf.services.decompile.fallback.print;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.StringUtil;\n\n/**\n * String printing wrapper of {@link StringBuilder}.\n * Helps with indentation and line-based print calls.\n *\n * @author Matt Coley\n */\npublic class Printer {\n\tprivate final StringBuilder out = new StringBuilder();\n\tprivate String indent;\n\n\t/**\n\t * @param indent\n\t * \t\tNew indentation prefix.\n\t */\n\tpublic void setIndent(@Nonnull String indent) {\n\t\tthis.indent = indent;\n\t}\n\n\t/**\n\t * Appends a line with a {@link #setIndent(String) configurable indent}.\n\t *\n\t * @param line\n\t * \t\tLine to print.\n\t */\n\tpublic void appendLine(@Nonnull String line) {\n\t\tif (indent != null)\n\t\t\tout.append(indent);\n\t\tout.append(line).append(\"\\n\");\n\t}\n\n\t/**\n\t * Appends all lines in the multi-line text.\n\t *\n\t * @param text\n\t * \t\tMulti-line text to append.\n\t *\n\t * @see #appendLine(String)\n\t */\n\tpublic void appendMultiLine(@Nonnull String text) {\n\t\tString[] lines = StringUtil.splitNewline(text);\n\t\tfor (String line : lines)\n\t\t\tappendLine(line);\n\t}\n\n\t/**\n\t * Append blank new line.\n\t */\n\tpublic void newLine() {\n\t\tout.append('\\n');\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn out.toString();\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/filter/JvmBytecodeFilter.java",
    "content": "package software.coley.recaf.services.decompile.filter;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.decompile.JvmDecompiler;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Collection;\n\n/**\n * Used to allow interception of bytecode passed to {@link JvmDecompiler} instances.\n *\n * @author Matt Coley\n */\npublic interface JvmBytecodeFilter {\n\t/**\n\t * @param workspace\n\t * \t\tThe workspace the class is from.\n\t * @param initialClassInfo\n\t * \t\tInitial information about the class, before any filtering <i>(by other filters)</i> has been applied.\n\t * \t\tContains a reference to the original bytecode.\n\t * @param bytecode\n\t * \t\tInput JVM class bytecode. May already be modified from the original bytecode by another filter.\n\t *\n\t * @return Output JVM class bytecode.\n\t */\n\t@Nonnull\n\tbyte[] filter(@Nonnull Workspace workspace, @Nonnull JvmClassInfo initialClassInfo, @Nonnull byte[] bytecode);\n\n\t/**\n\t * @param workspace\n\t * \t\tThe workspace the class is from.\n\t * @param initialClassInfo\n\t * \t\tInitial information about the class, before any filtering <i>(by other filters)</i> has been applied.\n\t * @param bytecodeFilters\n\t * \t\tCollection of filters to apply to the class.\n\t *\n\t * @return Filtered class model.\n\t */\n\t@Nonnull\n\tstatic JvmClassInfo applyFilters(@Nonnull Workspace workspace, @Nonnull JvmClassInfo initialClassInfo,\n\t                                 @Nonnull Collection<JvmBytecodeFilter> bytecodeFilters) {\n\t\tJvmClassInfo filteredBytecode;\n\t\tif (bytecodeFilters.isEmpty()) {\n\t\t\tfilteredBytecode = initialClassInfo;\n\t\t} else {\n\t\t\tboolean dirty = false;\n\t\t\tbyte[] bytecode = initialClassInfo.getBytecode();\n\t\t\tfor (JvmBytecodeFilter filter : bytecodeFilters) {\n\t\t\t\tbyte[] filtered = filter.filter(workspace, initialClassInfo, bytecode);\n\t\t\t\tif (filtered != bytecode) {\n\t\t\t\t\tbytecode = filtered;\n\t\t\t\t\tdirty = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfilteredBytecode = dirty ? initialClassInfo.toJvmClassBuilder().adaptFrom(bytecode).build() : initialClassInfo;\n\t\t}\n\t\treturn filteredBytecode;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/filter/OutputTextFilter.java",
    "content": "package software.coley.recaf.services.decompile.filter;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Used to allow interception of decompiler output before being returned to users.\n *\n * @author Matt Coley\n */\npublic interface OutputTextFilter {\n\t/**\n\t * @param workspace\n\t * \t\tThe workspace the class is from.\n\t * @param classInfo\n\t * \t\tInformation about the class the decompiled code models.\n\t * @param code\n\t * \t\tDecompiled code.\n\t *\n\t * @return Filtered decompiled code.\n\t */\n\t@Nonnull\n\tString filter(@Nonnull Workspace workspace, @Nonnull ClassInfo classInfo, @Nonnull String code);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonConfig.java",
    "content": "package software.coley.recaf.services.decompile.procyon;\n\nimport com.strobel.assembler.metadata.CompilerTarget;\nimport com.strobel.decompiler.DecompilerSettings;\nimport com.strobel.decompiler.languages.BytecodeOutputOptions;\nimport com.strobel.decompiler.languages.Language;\nimport com.strobel.decompiler.languages.Languages;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.services.decompile.BaseDecompilerConfig;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\n\n/**\n * Config for {@link ProcyonDecompiler}\n *\n * @author Matt Coley\n */\n@ApplicationScoped\n@ExcludeFromJacocoGeneratedReport(justification = \"Config POJO\")\npublic class ProcyonConfig extends BaseDecompilerConfig {\n\tprivate final ObservableBoolean includeLineNumbersInBytecode = new ObservableBoolean(true);\n\tprivate final ObservableBoolean showSyntheticMembers = new ObservableBoolean(false);\n\tprivate final ObservableBoolean alwaysGenerateExceptionVariableForCatchBlocks = new ObservableBoolean(true);\n\tprivate final ObservableBoolean forceFullyQualifiedReferences = new ObservableBoolean(false);\n\tprivate final ObservableBoolean forceExplicitImports = new ObservableBoolean(true);\n\tprivate final ObservableBoolean forceExplicitTypeArguments = new ObservableBoolean(false);\n\tprivate final ObservableBoolean flattenSwitchBlocks = new ObservableBoolean(false);\n\tprivate final ObservableBoolean excludeNestedTypes = new ObservableBoolean(false);\n\tprivate final ObservableBoolean retainRedundantCasts = new ObservableBoolean(false);\n\tprivate final ObservableBoolean retainPointlessSwitches = new ObservableBoolean(false);\n\tprivate final ObservableBoolean isUnicodeOutputEnabled = new ObservableBoolean(false);\n\tprivate final ObservableBoolean includeErrorDiagnostics = new ObservableBoolean(true);\n\tprivate final ObservableBoolean mergeVariables = new ObservableBoolean(false);\n\tprivate final ObservableBoolean disableForEachTransforms = new ObservableBoolean(false);\n\tprivate final ObservableBoolean showDebugLineNumbers = new ObservableBoolean(false);\n\tprivate final ObservableBoolean simplifyMemberReferences = new ObservableBoolean(false);\n\tprivate final ObservableBoolean arePreviewFeaturesEnabled = new ObservableBoolean(false);\n\tprivate final ObservableInteger textBlockLineMinimum = new ObservableInteger(3);\n\tprivate final ObservableObject<CompilerTarget> forcedCompilerTarget = new ObservableObject<>(null);\n\tprivate final ObservableObject<BytecodeOutputOptions> bytecodeOutputOptions = new ObservableObject<>(BytecodeOutputOptions.createDefault());\n\tprivate final ObservableObject<Language> languageTarget = new ObservableObject<>(Languages.java());\n\n\t@Inject\n\tpublic ProcyonConfig() {\n\t\tsuper(\"decompiler-procyon\" + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"includeLineNumbersInBytecode\", boolean.class, includeLineNumbersInBytecode));\n\t\taddValue(new BasicConfigValue<>(\"showSyntheticMembers\", boolean.class, showSyntheticMembers));\n\t\taddValue(new BasicConfigValue<>(\"alwaysGenerateExceptionVariableForCatchBlocks\", boolean.class, alwaysGenerateExceptionVariableForCatchBlocks));\n\t\taddValue(new BasicConfigValue<>(\"forceFullyQualifiedReferences\", boolean.class, forceFullyQualifiedReferences));\n\t\taddValue(new BasicConfigValue<>(\"forceExplicitImports\", boolean.class, forceExplicitImports));\n\t\taddValue(new BasicConfigValue<>(\"forceExplicitTypeArguments\", boolean.class, forceExplicitTypeArguments));\n\t\taddValue(new BasicConfigValue<>(\"flattenSwitchBlocks\", boolean.class, flattenSwitchBlocks));\n\t\taddValue(new BasicConfigValue<>(\"excludeNestedTypes\", boolean.class, excludeNestedTypes));\n\t\taddValue(new BasicConfigValue<>(\"retainRedundantCasts\", boolean.class, retainRedundantCasts));\n\t\taddValue(new BasicConfigValue<>(\"retainPointlessSwitches\", boolean.class, retainPointlessSwitches));\n\t\taddValue(new BasicConfigValue<>(\"isUnicodeOutputEnabled\", boolean.class, isUnicodeOutputEnabled));\n\t\taddValue(new BasicConfigValue<>(\"includeErrorDiagnostics\", boolean.class, includeErrorDiagnostics));\n\t\taddValue(new BasicConfigValue<>(\"mergeVariables\", boolean.class, mergeVariables));\n\t\taddValue(new BasicConfigValue<>(\"disableForEachTransforms\", boolean.class, disableForEachTransforms));\n\t\taddValue(new BasicConfigValue<>(\"showDebugLineNumbers\", boolean.class, showDebugLineNumbers));\n\t\taddValue(new BasicConfigValue<>(\"simplifyMemberReferences\", boolean.class, simplifyMemberReferences));\n\t\taddValue(new BasicConfigValue<>(\"arePreviewFeaturesEnabled\", boolean.class, arePreviewFeaturesEnabled));\n\t\taddValue(new BasicConfigValue<>(\"textBlockLineMinimum\", int.class, textBlockLineMinimum));\n\t\taddValue(new BasicConfigValue<>(\"forcedCompilerTarget\", CompilerTarget.class, forcedCompilerTarget));\n\t\taddValue(new BasicConfigValue<>(\"bytecodeOutputOptions\", BytecodeOutputOptions.class, bytecodeOutputOptions));\n\t\taddValue(new BasicConfigValue<>(\"languageTarget\", Language.class, languageTarget));\n\t\tregisterConfigValuesHashUpdates();\n\t}\n\n\t/**\n\t * @return Settings wrapper.\n\t */\n\t@Nonnull\n\tpublic DecompilerSettings toSettings() {\n\t\tDecompilerSettings decompilerSettings = new DecompilerSettings();\n\t\tdecompilerSettings.setIncludeLineNumbersInBytecode(includeLineNumbersInBytecode.getValue());\n\t\tdecompilerSettings.setShowSyntheticMembers(showSyntheticMembers.getValue());\n\t\tdecompilerSettings.setAlwaysGenerateExceptionVariableForCatchBlocks(alwaysGenerateExceptionVariableForCatchBlocks.getValue());\n\t\tdecompilerSettings.setForceFullyQualifiedReferences(forceFullyQualifiedReferences.getValue());\n\t\tdecompilerSettings.setForceExplicitImports(forceExplicitImports.getValue());\n\t\tdecompilerSettings.setForceExplicitTypeArguments(forceExplicitTypeArguments.getValue());\n\t\tdecompilerSettings.setFlattenSwitchBlocks(flattenSwitchBlocks.getValue());\n\t\tdecompilerSettings.setExcludeNestedTypes(excludeNestedTypes.getValue());\n\t\tdecompilerSettings.setRetainRedundantCasts(retainRedundantCasts.getValue());\n\t\tdecompilerSettings.setRetainPointlessSwitches(retainPointlessSwitches.getValue());\n\t\tdecompilerSettings.setUnicodeOutputEnabled(isUnicodeOutputEnabled.getValue());\n\t\tdecompilerSettings.setIncludeErrorDiagnostics(includeErrorDiagnostics.getValue());\n\t\tdecompilerSettings.setMergeVariables(mergeVariables.getValue());\n\t\tdecompilerSettings.setDisableForEachTransforms(disableForEachTransforms.getValue());\n\t\tdecompilerSettings.setShowDebugLineNumbers(showDebugLineNumbers.getValue());\n\t\tdecompilerSettings.setSimplifyMemberReferences(simplifyMemberReferences.getValue());\n\t\tdecompilerSettings.setPreviewFeaturesEnabled(arePreviewFeaturesEnabled.getValue());\n\t\tdecompilerSettings.setTextBlockLineMinimum(textBlockLineMinimum.getValue());\n\t\tdecompilerSettings.setForcedCompilerTarget(forcedCompilerTarget.getValue());\n\t\tdecompilerSettings.setBytecodeOutputOptions(bytecodeOutputOptions.getValue());\n\t\tdecompilerSettings.setLanguage(languageTarget.getValue());\n\t\treturn decompilerSettings;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getIncludeLineNumbersInBytecode() {\n\t\treturn includeLineNumbersInBytecode;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getShowSyntheticMembers() {\n\t\treturn showSyntheticMembers;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getAlwaysGenerateExceptionVariableForCatchBlocks() {\n\t\treturn alwaysGenerateExceptionVariableForCatchBlocks;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getForceFullyQualifiedReferences() {\n\t\treturn forceFullyQualifiedReferences;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getForceExplicitImports() {\n\t\treturn forceExplicitImports;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getForceExplicitTypeArguments() {\n\t\treturn forceExplicitTypeArguments;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getFlattenSwitchBlocks() {\n\t\treturn flattenSwitchBlocks;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getExcludeNestedTypes() {\n\t\treturn excludeNestedTypes;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getRetainRedundantCasts() {\n\t\treturn retainRedundantCasts;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getRetainPointlessSwitches() {\n\t\treturn retainPointlessSwitches;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getIsUnicodeOutputEnabled() {\n\t\treturn isUnicodeOutputEnabled;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getIncludeErrorDiagnostics() {\n\t\treturn includeErrorDiagnostics;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getMergeVariables() {\n\t\treturn mergeVariables;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getDisableForEachTransforms() {\n\t\treturn disableForEachTransforms;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getShowDebugLineNumbers() {\n\t\treturn showDebugLineNumbers;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getSimplifyMemberReferences() {\n\t\treturn simplifyMemberReferences;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getArePreviewFeaturesEnabled() {\n\t\treturn arePreviewFeaturesEnabled;\n\t}\n\n\t@Nonnull\n\tpublic ObservableInteger getTextBlockLineMinimum() {\n\t\treturn textBlockLineMinimum;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<CompilerTarget> getForcedCompilerTarget() {\n\t\treturn forcedCompilerTarget;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<BytecodeOutputOptions> getBytecodeOutputOptions() {\n\t\treturn bytecodeOutputOptions;\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<Language> getLanguageTarget() {\n\t\treturn languageTarget;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonDecompiler.java",
    "content": "package software.coley.recaf.services.decompile.procyon;\n\nimport com.google.gson.Gson;\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.TypeAdapterFactory;\nimport com.google.gson.reflect.TypeToken;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonWriter;\nimport com.strobel.Procyon;\nimport com.strobel.assembler.metadata.Buffer;\nimport com.strobel.assembler.metadata.CompositeTypeLoader;\nimport com.strobel.assembler.metadata.ITypeLoader;\nimport com.strobel.assembler.metadata.MetadataSystem;\nimport com.strobel.assembler.metadata.TypeReference;\nimport com.strobel.decompiler.DecompilationOptions;\nimport com.strobel.decompiler.DecompilerSettings;\nimport com.strobel.decompiler.PlainTextOutput;\nimport com.strobel.decompiler.languages.Language;\nimport com.strobel.decompiler.languages.Languages;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.decompile.AbstractJvmDecompiler;\nimport software.coley.recaf.services.decompile.DecompileResult;\nimport software.coley.recaf.services.json.GsonProvider;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.IOException;\nimport java.io.StringWriter;\n\n/**\n * Procyon decompiler implementation.\n *\n * @author xDark\n */\n@ApplicationScoped\npublic class ProcyonDecompiler extends AbstractJvmDecompiler {\n\tpublic static final String NAME = \"Procyon\";\n\tprivate final ProcyonConfig config;\n\n\t/**\n\t * New Procyon decompiler instance.\n\t *\n\t * @param gsonProvider\n\t * \t\tGson provider to register deserialization with.\n\t * @param config\n\t * \t\tConfig instance.\n\t */\n\t@Inject\n\tpublic ProcyonDecompiler(@Nonnull GsonProvider gsonProvider, @Nonnull ProcyonConfig config) {\n\t\tsuper(NAME, Procyon.version(), config);\n\t\tthis.config = config;\n\n\t\t// Support for mapping the 'language' model we store in the config.\n\t\tLanguageTypeAdapter adapter = new LanguageTypeAdapter();\n\t\tgsonProvider.addTypeAdapterFactory(new TypeAdapterFactory() {\n\t\t\t@Override\n\t\t\t@SuppressWarnings(\"unchecked\")\n\t\t\tpublic <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {\n\t\t\t\tif (Language.class.isAssignableFrom(type.getRawType()))\n\t\t\t\t\treturn (TypeAdapter<T>) adapter;\n\t\t\t\treturn null;\n\t\t\t}\n\t\t});\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo) {\n\t\tString name = classInfo.getName();\n\t\tbyte[] bytecode = classInfo.getBytecode();\n\t\tITypeLoader loader = new CompositeTypeLoader(\n\t\t\t\tnew TargetedTypeLoader(name, bytecode),\n\t\t\t\tnew WorkspaceTypeLoader(workspace)\n\t\t);\n\t\tDecompilerSettings settings = config.toSettings();\n\t\tsettings.setTypeLoader(loader);\n\t\tMetadataSystem system = new MetadataSystem(loader);\n\t\tTypeReference ref = system.lookupType(name);\n\t\tDecompilationOptions decompilationOptions = new DecompilationOptions();\n\t\tdecompilationOptions.setSettings(settings);\n\t\tStringWriter writer = new StringWriter();\n\t\tsettings.getLanguage().decompileType(ref.resolve(), new PlainTextOutput(writer), decompilationOptions);\n\t\tString decompile = writer.toString();\n\t\tint configHash = getConfig().getHash();\n\t\tif (decompile == null)\n\t\t\treturn new DecompileResult(new IllegalStateException(\"Missing decompilation output\"), configHash);\n\t\treturn new DecompileResult(decompile, configHash);\n\t}\n\n\t/**\n\t * Type loader to load a single class file.\n\t * Used as the first loader within a {@link CompositeTypeLoader} such that it overrides any\n\t * following type loader that could also procure the same class info.\n\t */\n\tprivate record TargetedTypeLoader(String name, byte[] data) implements ITypeLoader {\n\t\t@Override\n\t\tpublic boolean tryLoadType(String internalName, Buffer buffer) {\n\t\t\tif (internalName.equals(name)) {\n\t\t\t\tbyte[] data = this.data;\n\t\t\t\tbuffer.position(0);\n\t\t\t\tbuffer.putByteArray(data, 0, data.length);\n\t\t\t\tbuffer.position(0);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Adapter to read/write {@link Language} values from/to {@link ProcyonConfig}.\n\t */\n\tprivate static class LanguageTypeAdapter extends TypeAdapter<Language> {\n\t\t@Override\n\t\tpublic void write(JsonWriter writer, Language language) throws IOException {\n\t\t\twriter.value(language.getName());\n\t\t}\n\n\t\t@Override\n\t\tpublic Language read(JsonReader reader) throws IOException {\n\t\t\tif (reader.hasNext()) {\n\t\t\t\tString name = reader.nextString();\n\t\t\t\treturn Languages.all().stream()\n\t\t\t\t\t\t.filter(l -> l.getName().equals(name))\n\t\t\t\t\t\t.findFirst()\n\t\t\t\t\t\t.orElse(Languages.java());\n\t\t\t}\n\t\t\treturn Languages.java();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/WorkspaceTypeLoader.java",
    "content": "package software.coley.recaf.services.decompile.procyon;\n\nimport com.strobel.assembler.metadata.Buffer;\nimport com.strobel.assembler.metadata.ITypeLoader;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Type loader that pulls classes from a {@link Workspace}.\n *\n * @author xDark\n */\npublic final class WorkspaceTypeLoader implements ITypeLoader {\n\tprivate final Workspace workspace;\n\n\t/**\n\t * @param workspace\n\t * \t\tActive workspace.\n\t */\n\tpublic WorkspaceTypeLoader(Workspace workspace) {\n\t\tthis.workspace = workspace;\n\t}\n\n\t@Override\n\tpublic boolean tryLoadType(String internalName, Buffer buffer) {\n\t\tClassPathNode node = workspace.findClass(internalName);\n\t\tif (node == null)\n\t\t\treturn false;\n\t\tbyte[] data = node.getValue().asJvmClass().getBytecode();\n\t\tbuffer.position(0);\n\t\tbuffer.putByteArray(data, 0, data.length);\n\t\tbuffer.position(0);\n\t\treturn true;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/BaseSource.java",
    "content": "package software.coley.recaf.services.decompile.vineflower;\n\nimport jakarta.annotation.Nonnull;\nimport org.jetbrains.java.decompiler.main.extern.IContextSource;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\n\n/**\n * Base Vineflower class/library source.\n *\n * @author therathatter\n */\npublic abstract class BaseSource implements IContextSource {\n\tprotected final JvmClassInfo targetInfo;\n\tprotected final Workspace workspace;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull class files from.\n\t * @param targetInfo\n\t * \t\tTarget class to decompile.\n\t */\n\tprotected BaseSource(@Nonnull Workspace workspace, @Nonnull JvmClassInfo targetInfo) {\n\t\tthis.workspace = workspace;\n\t\tthis.targetInfo = targetInfo;\n\t}\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"Recaf\";\n\t}\n\n\t@Override\n\tpublic InputStream getInputStream(String resource) {\n\t\tString name = resource.substring(0, resource.length() - IContextSource.CLASS_SUFFIX.length());\n\t\tif (name.equals(targetInfo.getName()))\n\t\t\treturn new ByteArrayInputStream(targetInfo.getBytecode());\n\n\t\tClassPathNode node = workspace.findClass(name);\n\t\tif (node == null) return null; // VF wants missing data to be null here, not an IOException or empty stream.\n\t\treturn new ByteArrayInputStream(node.getValue().asJvmClass().getBytecode());\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/ClassSource.java",
    "content": "package software.coley.recaf.services.decompile.vineflower;\n\nimport jakarta.annotation.Nonnull;\nimport org.jetbrains.java.decompiler.main.extern.IResultSaver;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Single class source for Vineflower.\n *\n * @author Matt Coley\n * @author therathatter\n */\npublic class ClassSource extends BaseSource {\n\tprivate final DecompiledOutputSink sink;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull class files from.\n\t * @param targetInfo\n\t * \t\tTarget class to decompile.\n\t */\n\tprotected ClassSource(@Nonnull Workspace workspace, @Nonnull JvmClassInfo targetInfo) {\n\t\tsuper(workspace, targetInfo);\n\t\tsink = new DecompiledOutputSink(targetInfo);\n\t}\n\n\t/**\n\t * @return Output which holds the decompilation result after the decompilation task completes.\n\t */\n\t@Nonnull\n\tprotected DecompiledOutputSink getSink() {\n\t\treturn sink;\n\t}\n\n\t@Override\n\tpublic Entries getEntries() {\n\t\t// TODO: Bug in Vineflower makes it so that 'addLibrary' doesn't yield inner info for a class provided with 'addSource'\n\t\t//  So for now until this is fixed upstream we will also supply inners here.\n\t\t//  This will make Vineflower decompile each inner class separately as well, but its the best fix for now without\n\t\t//  too much of a perf hit.\n\t\tList<Entry> entries = new ArrayList<>();\n\t\tentries.add(new Entry(targetInfo.getName(), Entry.BASE_VERSION));\n\t\tfor (InnerClassInfo innerClass : targetInfo.getInnerClasses()) {\n\t\t\t// Only add entry if it exists in the workspace.\n\t\t\tif (workspace.findClass(innerClass.getInnerClassName()) != null)\n\t\t\t\tentries.add(new Entry(innerClass.getName(), Entry.BASE_VERSION));\n\t\t}\n\t\treturn new Entries(entries, Collections.emptyList(), Collections.emptyList());\n\t}\n\n\t@Override\n\tpublic IOutputSink createOutputSink(IResultSaver saver) {\n\t\treturn sink;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DecompiledOutputSink.java",
    "content": "package software.coley.recaf.services.decompile.vineflower;\n\nimport jakarta.annotation.Nonnull;\nimport org.jetbrains.java.decompiler.main.extern.IContextSource;\nimport software.coley.recaf.info.JvmClassInfo;\n\nimport java.io.IOException;\n\n/**\n * Output sink for Vineflower decompiler.\n *\n * @author therathatter\n */\npublic class DecompiledOutputSink implements IContextSource.IOutputSink {\n\tprotected final JvmClassInfo target;\n\tprotected final ThreadLocal<String> out = new ThreadLocal<>();\n\n\t/**\n\t * @param target\n\t * \t\tTarget class to get output of.\n\t */\n\tprotected DecompiledOutputSink(@Nonnull JvmClassInfo target) {\n\t\tthis.target = target;\n\t}\n\n\t/**\n\t * @return Local wrapper of decompilation output.\n\t */\n\t@Nonnull\n\tprotected ThreadLocal<String> getDecompiledOutput() {\n\t\treturn out;\n\t}\n\n\t@Override\n\tpublic void begin() {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void acceptClass(String qualifiedName, String fileName, String content, int[] mapping) {\n\t\tif (target.getName().equals(qualifiedName))\n\t\t\tout.set(content);\n\t}\n\n\t@Override\n\tpublic void acceptDirectory(String directory) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void acceptOther(String path) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void close() throws IOException {\n\t\t// no-op\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DummyResultSaver.java",
    "content": "package software.coley.recaf.services.decompile.vineflower;\n\nimport org.jetbrains.java.decompiler.main.extern.IResultSaver;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\n\nimport java.util.jar.Manifest;\n\n/**\n * Dummy result saver to prevent Vineflower from trying to touch disk.\n *\n * @author therathatter\n */\n@ExcludeFromJacocoGeneratedReport(justification = \"We don't use VF file IO, everything stays in memory\")\npublic class DummyResultSaver implements IResultSaver {\n    @Override\n    public void saveFolder(String s) {\n        // no-op\n    }\n\n    @Override\n    public void copyFile(String s, String s1, String s2) {\n        // no-op\n    }\n\n    @Override\n    public void saveClassFile(String s, String s1, String s2, String s3, int[] ints) {\n        // no-op\n    }\n\n    @Override\n    public void createArchive(String s, String s1, Manifest manifest) {\n        // no-op\n    }\n\n    @Override\n    public void saveDirEntry(String s, String s1, String s2) {\n        // no-op\n    }\n\n    @Override\n    public void copyEntry(String s, String s1, String s2, String s3) {\n        // no-op\n    }\n\n    @Override\n    public void saveClassEntry(String s, String s1, String s2, String s3, String s4) {\n        // no-op\n    }\n\n    @Override\n    public void closeArchive(String s, String s1) {\n        // no-op\n    }\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/LibrarySource.java",
    "content": "package software.coley.recaf.services.decompile.vineflower;\n\nimport jakarta.annotation.Nonnull;\nimport org.jetbrains.java.decompiler.main.extern.IContextSource;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Full library source for Vineflower.\n *\n * @author Matt Coley\n * @author therathatter\n */\npublic class LibrarySource extends BaseSource {\n\tprivate final List<Entry> entries;\n\n\t/**\n\t * @param entries\n\t * \t\tList of context entries in the given workspace.\n\t * @param workspace\n\t * \t\tWorkspace to pull class files from.\n\t * @param targetInfo\n\t * \t\tTarget class to decompile.\n\t */\n\tprotected LibrarySource(@Nonnull List<IContextSource.Entry> entries, @Nonnull Workspace workspace, @Nonnull JvmClassInfo targetInfo) {\n\t\tsuper(workspace, targetInfo);\n\t\tthis.entries = entries;\n\t}\n\n\t@Override\n\tpublic Entries getEntries() {\n\t\treturn new Entries(entries, Collections.emptyList(), Collections.emptyList());\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java",
    "content": "package software.coley.recaf.services.decompile.vineflower;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.jetbrains.java.decompiler.main.Fernflower;\nimport org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;\nimport org.slf4j.event.Level;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.services.decompile.BaseDecompilerConfig;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\n\nimport java.lang.reflect.Field;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Config for {@link VineflowerDecompiler}\n *\n * @author therathatter\n * @see IFernflowerPreferences Source of value definitions.\n */\n@ApplicationScoped\n@SuppressWarnings(\"all\") // ignore unused refs / typos\n@ExcludeFromJacocoGeneratedReport(justification = \"Config POJO\")\npublic class VineflowerConfig extends BaseDecompilerConfig {\n\tprivate final ObservableObject<Level> loggingLevel = new ObservableObject<>(Level.WARN);\n\tprivate final ObservableBoolean removeBridge = new ObservableBoolean(true);\n\tprivate final ObservableBoolean removeSynthetic = new ObservableBoolean(true);\n\tprivate final ObservableBoolean decompileInner = new ObservableBoolean(true);\n\tprivate final ObservableBoolean decompileClass_1_4 = new ObservableBoolean(true);\n\tprivate final ObservableBoolean decompileAssertions = new ObservableBoolean(true);\n\tprivate final ObservableBoolean hideEmptySuper = new ObservableBoolean(true);\n\tprivate final ObservableBoolean hideDefaultConstructor = new ObservableBoolean(true);\n\tprivate final ObservableBoolean decompileGenericSignatures = new ObservableBoolean(true);\n\tprivate final ObservableBoolean noExceptionsReturn = new ObservableBoolean(true);\n\tprivate final ObservableBoolean ensureSynchronizedMonitor = new ObservableBoolean(true);\n\tprivate final ObservableBoolean decompileEnum = new ObservableBoolean(true);\n\tprivate final ObservableBoolean removeGetClassNew = new ObservableBoolean(true);\n\tprivate final ObservableBoolean literalsAsIs = new ObservableBoolean(false);\n\tprivate final ObservableBoolean booleanTrueOne = new ObservableBoolean(true);\n\tprivate final ObservableBoolean asciiStringCharacters = new ObservableBoolean(false);\n\tprivate final ObservableBoolean syntheticNotSet = new ObservableBoolean(false);\n\tprivate final ObservableBoolean undefinedParamTypeObject = new ObservableBoolean(true);\n\tprivate final ObservableBoolean useDebugVarNames = new ObservableBoolean(true);\n\tprivate final ObservableBoolean useMethodParameters = new ObservableBoolean(true);\n\tprivate final ObservableBoolean removeEmptyRanges = new ObservableBoolean(true);\n\tprivate final ObservableBoolean finallyDeinline = new ObservableBoolean(true);\n\tprivate final ObservableBoolean ideaNotNullAnnotation = new ObservableBoolean(true);\n\tprivate final ObservableBoolean lambdaToAnonymousClass = new ObservableBoolean(false);\n\tprivate final ObservableBoolean bytecodeSourceMapping = new ObservableBoolean(false);\n\tprivate final ObservableBoolean dumpCodeLines = new ObservableBoolean(false);\n\tprivate final ObservableBoolean ignoreInvalidBytecode = new ObservableBoolean(false);\n\tprivate final ObservableBoolean verifyAnonymousClasses = new ObservableBoolean(false);\n\tprivate final ObservableBoolean ternaryConstantSimplification = new ObservableBoolean(false);\n\tprivate final ObservableBoolean overrideAnnotation = new ObservableBoolean(true);\n\tprivate final ObservableBoolean patternMatching = new ObservableBoolean(true);\n\tprivate final ObservableBoolean tryLoopFix = new ObservableBoolean(true);\n\tprivate final ObservableBoolean ternaryConditions = new ObservableBoolean(true);\n\tprivate final ObservableBoolean switchExpressions = new ObservableBoolean(true);\n\tprivate final ObservableBoolean showHiddenStatements = new ObservableBoolean(false);\n\tprivate final ObservableBoolean simplifyStackSecondPass = new ObservableBoolean(true);\n\tprivate final ObservableBoolean verifyVariableMerges = new ObservableBoolean(false);\n\tprivate final ObservableBoolean decompilePreview = new ObservableBoolean(true);\n\tprivate final ObservableBoolean explicitGenericArguments = new ObservableBoolean(false);\n\tprivate final ObservableBoolean inlineSimpleLambdas = new ObservableBoolean(true);\n\tprivate final ObservableBoolean useJadVarNaming = new ObservableBoolean(false);\n\tprivate final ObservableBoolean useJadParameterNaming = new ObservableBoolean(false);\n\tprivate final ObservableBoolean skipExtraFiles = new ObservableBoolean(false);\n\tprivate final ObservableBoolean warnInconsistentInnerClasses = new ObservableBoolean(true);\n\tprivate final ObservableBoolean dumpBytecodeOnError = new ObservableBoolean(true);\n\tprivate final ObservableBoolean dumpExceptionOnError = new ObservableBoolean(true);\n\tprivate final ObservableBoolean decompilerComments = new ObservableBoolean(false);\n\tprivate final ObservableBoolean sourceFileComments = new ObservableBoolean(false);\n\tprivate final ObservableBoolean decompileComplexCondys = new ObservableBoolean(false);\n\tprivate final ObservableBoolean forceJsrInline = new ObservableBoolean(false);\n\n\tpublic static void main(String[] args) {\n\t\tfor (Field field : IFernflowerPreferences.class.getDeclaredFields()) {\n\t\t\ttry {\n\t\t\t\tIFernflowerPreferences.Name name = field.getDeclaredAnnotation(IFernflowerPreferences.Name.class);\n\t\t\t\tString key = (String) field.get(null);\n\t\t\t\tSystem.out.println(\"service.decompile.impl.decompiler-vineflower-config.\" + key + \"=\" + name.value());\n\t\t\t} catch (Throwable t) {}\n\t\t}\n\t}\n\n\t@Inject\n\tpublic VineflowerConfig() {\n\t\tsuper(\"decompiler-vineflower\" + CONFIG_SUFFIX);\n\n\t\taddValue(new BasicConfigValue<>(\"logging-level\", Level.class, loggingLevel));\n\t\taddValue(new BasicConfigValue<>(\"remove-bridge\", boolean.class, removeBridge));\n\t\taddValue(new BasicConfigValue<>(\"remove-synthetic\", boolean.class, removeSynthetic));\n\t\taddValue(new BasicConfigValue<>(\"decompile-inner\", boolean.class, decompileInner));\n\t\taddValue(new BasicConfigValue<>(\"decompile-java4\", boolean.class, decompileClass_1_4));\n\t\taddValue(new BasicConfigValue<>(\"decompile-assert\", boolean.class, decompileAssertions));\n\t\taddValue(new BasicConfigValue<>(\"hide-empty-super\", boolean.class, hideEmptySuper));\n\t\taddValue(new BasicConfigValue<>(\"hide-default-constructor\", boolean.class, hideDefaultConstructor));\n\t\taddValue(new BasicConfigValue<>(\"decompile-generics\", boolean.class, decompileGenericSignatures));\n\t\taddValue(new BasicConfigValue<>(\"incorporate-returns\", boolean.class, noExceptionsReturn));\n\t\taddValue(new BasicConfigValue<>(\"ensure-synchronized-monitors\", boolean.class, ensureSynchronizedMonitor));\n\t\taddValue(new BasicConfigValue<>(\"decompile-enums\", boolean.class, decompileEnum));\n\t\taddValue(new BasicConfigValue<>(\"decompile-preview\", boolean.class, decompilePreview));\n\t\taddValue(new BasicConfigValue<>(\"remove-getclass\", boolean.class, removeGetClassNew));\n\t\taddValue(new BasicConfigValue<>(\"keep-literals\", boolean.class, literalsAsIs));\n\t\taddValue(new BasicConfigValue<>(\"boolean-as-int\", boolean.class, booleanTrueOne));\n\t\taddValue(new BasicConfigValue<>(\"ascii-strings\", boolean.class, asciiStringCharacters));\n\t\taddValue(new BasicConfigValue<>(\"synthetic-not-set\", boolean.class, syntheticNotSet));\n\t\taddValue(new BasicConfigValue<>(\"undefined-as-object\", boolean.class, undefinedParamTypeObject));\n\t\taddValue(new BasicConfigValue<>(\"use-lvt-names\", boolean.class, useDebugVarNames));\n\t\taddValue(new BasicConfigValue<>(\"use-method-parameters\", boolean.class, useMethodParameters));\n\t\taddValue(new BasicConfigValue<>(\"remove-empty-try-catch\", boolean.class, removeEmptyRanges));\n\t\taddValue(new BasicConfigValue<>(\"decompile-finally\", boolean.class, finallyDeinline));\n\t\taddValue(new BasicConfigValue<>(\"lambda-to-anonymous-class\", boolean.class, lambdaToAnonymousClass));\n\t\taddValue(new BasicConfigValue<>(\"bytecode-source-mapping\", boolean.class, bytecodeSourceMapping));\n\t\taddValue(new BasicConfigValue<>(\"__dump_original_lines__\", boolean.class, dumpCodeLines));\n\t\taddValue(new BasicConfigValue<>(\"ignore-invalid-bytecode\", boolean.class, ignoreInvalidBytecode));\n\t\taddValue(new BasicConfigValue<>(\"verify-anonymous-classes\", boolean.class, verifyAnonymousClasses));\n\t\taddValue(new BasicConfigValue<>(\"ternary-constant-simplification\", boolean.class, ternaryConstantSimplification));\n\t\taddValue(new BasicConfigValue<>(\"pattern-matching\", boolean.class, patternMatching));\n\t\taddValue(new BasicConfigValue<>(\"try-loop-fix\", boolean.class, tryLoopFix));\n\t\taddValue(new BasicConfigValue<>(\"ternary-in-if\", boolean.class, ternaryConditions));\n\t\taddValue(new BasicConfigValue<>(\"decompile-switch-expressions\", boolean.class, switchExpressions));\n\t\taddValue(new BasicConfigValue<>(\"show-hidden-statements\", boolean.class, showHiddenStatements));\n\t\taddValue(new BasicConfigValue<>(\"override-annotation\", boolean.class, overrideAnnotation));\n\t\taddValue(new BasicConfigValue<>(\"simplify-stack\", boolean.class, simplifyStackSecondPass));\n\t\taddValue(new BasicConfigValue<>(\"verify-merges\", boolean.class, verifyVariableMerges));\n\t\taddValue(new BasicConfigValue<>(\"explicit-generics\", boolean.class, explicitGenericArguments));\n\t\taddValue(new BasicConfigValue<>(\"inline-simple-lambdas\", boolean.class, inlineSimpleLambdas));\n\t\taddValue(new BasicConfigValue<>(\"skip-extra-files\", boolean.class, skipExtraFiles));\n\t\taddValue(new BasicConfigValue<>(\"warn-inconsistent-inner-attributes\", boolean.class, warnInconsistentInnerClasses));\n\t\taddValue(new BasicConfigValue<>(\"dump-bytecode-on-error\", boolean.class, dumpBytecodeOnError));\n\t\taddValue(new BasicConfigValue<>(\"dump-exception-on-error\", boolean.class, dumpExceptionOnError));\n\t\taddValue(new BasicConfigValue<>(\"decompiler-comments\", boolean.class, decompilerComments));\n\t\taddValue(new BasicConfigValue<>(\"sourcefile-comments\", boolean.class, sourceFileComments));\n\t\taddValue(new BasicConfigValue<>(\"decompile-complex-constant-dynamic\", boolean.class, decompileComplexCondys));\n\t\taddValue(new BasicConfigValue<>(\"force-jsr-inline\", boolean.class, forceJsrInline));\n\n\t\tregisterConfigValuesHashUpdates();\n\t}\n\n\t/**\n\t * @return Map of values to pass to the {@link Fernflower} instance.\n\t */\n\t@Nonnull\n\tprotected Map<String, Object> getFernflowerProperties() {\n\t\tMap<String, Object> properties = new HashMap<>(IFernflowerPreferences.DEFAULTS);\n\t\tgetValues().forEach((key, value) -> {\n\t\t\tif (value.getValue() instanceof Boolean bool)\n\t\t\t\tproperties.put(key, bool ? \"1\" : \"0\");\n\t\t});\n\n\t\t// We NEVER want kotlin output. It will break our AST parser.\n\t\tproperties.put(\"kt-enable\", \"0\");\n\n\t\treturn properties;\n\t}\n\n\t/**\n\t * @return Level to use for {@link VineflowerLogger}.\n\t */\n\t@Nonnull\n\tpublic ObservableObject<Level> getLoggingLevel() {\n\t\treturn loggingLevel;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getRemoveBridge() {\n\t\treturn removeBridge;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getRemoveSynthetic() {\n\t\treturn removeSynthetic;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getDecompileInner() {\n\t\treturn decompileInner;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getDecompileClass_1_4() {\n\t\treturn decompileClass_1_4;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getDecompileAssertions() {\n\t\treturn decompileAssertions;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getHideEmptySuper() {\n\t\treturn hideEmptySuper;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getHideDefaultConstructor() {\n\t\treturn hideDefaultConstructor;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getDecompileGenericSignatures() {\n\t\treturn decompileGenericSignatures;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getNoExceptionsReturn() {\n\t\treturn noExceptionsReturn;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getEnsureSynchronizedMonitor() {\n\t\treturn ensureSynchronizedMonitor;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getDecompileEnum() {\n\t\treturn decompileEnum;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getRemoveGetClassNew() {\n\t\treturn removeGetClassNew;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getLiteralsAsIs() {\n\t\treturn literalsAsIs;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getBooleanTrueOne() {\n\t\treturn booleanTrueOne;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getAsciiStringCharacters() {\n\t\treturn asciiStringCharacters;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getSyntheticNotSet() {\n\t\treturn syntheticNotSet;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getUndefinedParamTypeObject() {\n\t\treturn undefinedParamTypeObject;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getUseDebugVarNames() {\n\t\treturn useDebugVarNames;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getUseMethodParameters() {\n\t\treturn useMethodParameters;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getRemoveEmptyRanges() {\n\t\treturn removeEmptyRanges;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getFinallyDeinline() {\n\t\treturn finallyDeinline;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getIdeaNotNullAnnotation() {\n\t\treturn ideaNotNullAnnotation;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getLambdaToAnonymousClass() {\n\t\treturn lambdaToAnonymousClass;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getBytecodeSourceMapping() {\n\t\treturn bytecodeSourceMapping;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getDumpCodeLines() {\n\t\treturn dumpCodeLines;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getIgnoreInvalidBytecode() {\n\t\treturn ignoreInvalidBytecode;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getVerifyAnonymousClasses() {\n\t\treturn verifyAnonymousClasses;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getTernaryConstantSimplification() {\n\t\treturn ternaryConstantSimplification;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getOverrideAnnotation() {\n\t\treturn overrideAnnotation;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getPatternMatching() {\n\t\treturn patternMatching;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getTryLoopFix() {\n\t\treturn tryLoopFix;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getTernaryConditions() {\n\t\treturn ternaryConditions;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getSwitchExpressions() {\n\t\treturn switchExpressions;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getShowHiddenStatements() {\n\t\treturn showHiddenStatements;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getSimplifyStackSecondPass() {\n\t\treturn simplifyStackSecondPass;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getVerifyVariableMerges() {\n\t\treturn verifyVariableMerges;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getDecompilePreview() {\n\t\treturn decompilePreview;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getExplicitGenericArguments() {\n\t\treturn explicitGenericArguments;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getInlineSimpleLambdas() {\n\t\treturn inlineSimpleLambdas;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getUseJadVarNaming() {\n\t\treturn useJadVarNaming;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getUseJadParameterNaming() {\n\t\treturn useJadParameterNaming;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getSkipExtraFiles() {\n\t\treturn skipExtraFiles;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getWarnInconsistentInnerClasses() {\n\t\treturn warnInconsistentInnerClasses;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getDumpBytecodeOnError() {\n\t\treturn dumpBytecodeOnError;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getDumpExceptionOnError() {\n\t\treturn dumpExceptionOnError;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getDecompilerComments() {\n\t\treturn decompilerComments;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getSourceFileComments() {\n\t\treturn sourceFileComments;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getDecompileComplexCondys() {\n\t\treturn decompileComplexCondys;\n\t}\n\n\t@Nonnull\n\tpublic ObservableBoolean getForceJsrInline() {\n\t\treturn forceJsrInline;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java",
    "content": "package software.coley.recaf.services.decompile.vineflower;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.jetbrains.java.decompiler.main.Fernflower;\nimport org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;\nimport org.jetbrains.java.decompiler.main.extern.IResultSaver;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.decompile.AbstractJvmDecompiler;\nimport software.coley.recaf.services.decompile.DecompileResult;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Vineflower decompiler implementation.\n *\n * @author therathatter\n */\n@ApplicationScoped\npublic class VineflowerDecompiler extends AbstractJvmDecompiler {\n\tpublic static final String NAME = \"Vineflower\";\n\tprivate final VineflowerConfig config;\n\tprivate final IFernflowerLogger logger;\n\tprivate final IResultSaver dummySaver = new DummyResultSaver();\n\tprivate final WorkspaceEntriesCache workspaceEntriesCache = new WorkspaceEntriesCache();\n\n\t/**\n\t * New Vineflower decompiler instance.\n\t *\n\t * @param config\n\t * \t\tDecompiler configuration.\n\t */\n\t@Inject\n\tpublic VineflowerDecompiler(@Nonnull VineflowerConfig config) {\n\t\t// Change this version to be dynamic when / if the Vineflower authors make a function that returns the version...\n\t\tsuper(NAME, \"1.11.2\", config);\n\t\tthis.config = config;\n\t\tlogger = new VineflowerLogger(config);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo info) {\n\t\tFernflower fernflower = new Fernflower(dummySaver, config.getFernflowerProperties(), logger);\n\n\t\ttry {\n\t\t\tClassSource source = new ClassSource(workspace, info);\n\t\t\tfernflower.addSource(source);\n\t\t\tfernflower.addLibrary(new LibrarySource(workspaceEntriesCache.getCachedEntries(workspace), workspace, info));\n\t\t\tfernflower.decompileContext();\n\n\t\t\tString decompiled = source.getSink().getDecompiledOutput().get();\n\t\t\tif (decompiled == null || decompiled.isEmpty())\n\t\t\t\treturn new DecompileResult(new IllegalStateException(\"Missing decompilation output\"), config.getHash());\n\n\t\t\treturn new DecompileResult(decompiled, config.getHash());\n\t\t} catch (Exception e) {\n\t\t\treturn new DecompileResult(e, config.getHash());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java",
    "content": "package software.coley.recaf.services.decompile.vineflower;\n\nimport jakarta.annotation.Nonnull;\nimport org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;\nimport org.slf4j.Logger;\nimport org.slf4j.event.Level;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.analytics.logging.Logging;\n\n/**\n * Logger for Vineflower\n *\n * @author therathatter\n */\npublic class VineflowerLogger extends IFernflowerLogger {\n\tprivate static final Logger logger = Logging.get(VineflowerLogger.class);\n\tprivate static final String VF_PREFIX = \"VF: \";\n\tprivate final ObservableObject<Level> level;\n\n\tpublic VineflowerLogger(@Nonnull VineflowerConfig config) {\n\t\tthis.level = config.getLoggingLevel();\n\t}\n\n\t@Override\n\tpublic void writeMessage(String message, Severity severity) {\n\t\tswitch (severity) {\n\t\t\tcase TRACE -> {\n\t\t\t\tif (level.getValue().compareTo(Level.TRACE) >= 0) logger.trace(VF_PREFIX + message);\n\t\t\t}\n\t\t\tcase INFO -> {\n\t\t\t\tif (level.getValue().compareTo(Level.INFO) >= 0) logger.info(VF_PREFIX + message);\n\t\t\t}\n\t\t\tcase WARN -> {\n\t\t\t\tif (level.getValue().compareTo(Level.WARN) >= 0) logger.warn(VF_PREFIX + message);\n\t\t\t}\n\t\t\tcase ERROR -> logger.error(VF_PREFIX + message);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void writeMessage(String message, Severity severity, Throwable throwable) {\n\t\tlogger.error(VF_PREFIX + message, throwable);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/WorkspaceEntriesCache.java",
    "content": "package software.coley.recaf.services.decompile.vineflower;\n\nimport jakarta.annotation.Nonnull;\nimport org.jetbrains.java.decompiler.main.extern.IContextSource;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Caches the list of {@link IContextSource.Entry} items in a workspace.\n *\n * @author Matt Coley\n * @see LibrarySource\n */\npublic class WorkspaceEntriesCache {\n\tprivate List<IContextSource.Entry> cache;\n\tprivate int lastWorkspaceHash;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to get cached entries of.\n\t *\n\t * @return List of all distinctly named entries for classes in the workspace.\n\t */\n\t@Nonnull\n\tpublic synchronized List<IContextSource.Entry> getCachedEntries(@Nonnull Workspace workspace) {\n\t\tList<IContextSource.Entry> local = cache;\n\t\tint workspaceHash = workspace.hashCode();\n\t\tif (local == null || workspaceHash != lastWorkspaceHash) {\n\t\t\tlocal = workspace.getAllResources(false).stream()\n\t\t\t\t\t.flatMap(WorkspaceResource::jvmAllClassBundleStreamRecursive)\n\t\t\t\t\t.flatMap(c -> c.keySet().stream())\n\t\t\t\t\t.distinct()\n\t\t\t\t\t.map(className -> new IContextSource.Entry(className, IContextSource.Entry.BASE_VERSION))\n\t\t\t\t\t.collect(Collectors.toList());\n\t\t\tcache = local;\n\t\t\tlastWorkspaceHash = workspaceHash;\n\t\t}\n\t\treturn local;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/CallResultInliningTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;\nimport it.unimi.dsi.fastutil.objects.Object2BooleanMap;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.InsnNode;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.analysis.Frame;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.transform.ClassTransformer;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.analysis.eval.EvaluationResult;\nimport software.coley.recaf.util.analysis.eval.EvaluationYieldResult;\nimport software.coley.recaf.util.analysis.eval.FieldCacheManager;\nimport software.coley.recaf.util.analysis.eval.Evaluator;\nimport software.coley.recaf.util.analysis.value.DoubleValue;\nimport software.coley.recaf.util.analysis.value.LongValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * A transformer that inlines method calls that can be fully evaluated.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class CallResultInliningTransformer implements JvmClassTransformer {\n\tprivate final static int MAX_STEPS = 20_000; // TODO: Make configurable\n\tprivate final InheritanceGraphService graphService;\n\tprivate final Object2BooleanMap<String> canBeEvaluatedMap = new Object2BooleanArrayMap<>();\n\tprivate final FieldCacheManager fieldCacheManager = new FieldCacheManager();\n\n\tprivate InheritanceGraph inheritanceGraph;\n\tprivate Evaluator evaluator;\n\n\t@Inject\n\tpublic CallResultInliningTransformer(@Nonnull InheritanceGraphService graphService) {\n\t\tthis.graphService = graphService;\n\t}\n\n\t@Override\n\tpublic void setup(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace) {\n\t\tinheritanceGraph = graphService.getOrCreateInheritanceGraph(workspace);\n\t\tevaluator = new Evaluator(workspace, context.newInterpreter(inheritanceGraph), new FieldCacheManager(), MAX_STEPS, false);\n\t}\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tboolean dirty = false;\n\t\tString className = initialClassState.getName();\n\t\tClassNode node = context.getNode(bundle, initialClassState);\n\t\tfor (MethodNode method : node.methods) {\n\t\t\t// Skip if abstract.\n\t\t\tInsnList instructions = method.instructions;\n\t\t\tif (instructions == null)\n\t\t\t\tcontinue;\n\n\t\t\tFrame<ReValue>[] frames = context.analyze(inheritanceGraph, node, method);\n\t\t\tfor (int i = instructions.size() - 1; i >= 0; i--) {\n\t\t\t\tAbstractInsnNode insn = instructions.get(i);\n\t\t\t\tif (insn.getOpcode() == Opcodes.INVOKESTATIC && insn instanceof MethodInsnNode min) {\n\t\t\t\t\tFrame<ReValue> frame = frames[i];\n\t\t\t\t\tif (frame == null)\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t// Collect arguments.\n\t\t\t\t\tType methodType = Type.getMethodType(min.desc);\n\t\t\t\t\tList<ReValue> arguments = new ArrayList<>(methodType.getArgumentCount());\n\t\t\t\t\tfor (int j = 0; j < methodType.getArgumentCount(); j++)\n\t\t\t\t\t\targuments.addFirst(frame.getStack(frame.getStackSize() - 1 - j));\n\n\t\t\t\t\t// All arguments must have known values.\n\t\t\t\t\tif (arguments.stream().anyMatch(v -> !v.hasKnownValue()))\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t// Target method must be able to be evaluated.\n\t\t\t\t\tif (!canEvaluate(min))\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t// Reset instance support before each evaluation to prevent state pollution.\n\t\t\t\t\tfieldCacheManager.reset();\n\n\t\t\t\t\t// Attempt evaluation. If it yields a value, replace the call with the result.\n\t\t\t\t\tEvaluationResult result = evaluator.evaluate(min.owner, min.name, min.desc, null, arguments);\n\t\t\t\t\tif (result instanceof EvaluationYieldResult(ReValue retVal)) {\n\t\t\t\t\t\tAbstractInsnNode replacement = OpaqueConstantFoldingTransformer.toInsn(retVal);\n\t\t\t\t\t\tif (replacement != null) {\n\t\t\t\t\t\t\tfor (int arg = arguments.size() - 1; arg >= 0; arg--) {\n\t\t\t\t\t\t\t\tReValue argValue = arguments.get(arg);\n\t\t\t\t\t\t\t\tif (argValue instanceof LongValue || argValue instanceof DoubleValue)\n\t\t\t\t\t\t\t\t\tinstructions.insertBefore(min, new InsnNode(Opcodes.POP2));\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t\tinstructions.insertBefore(min, new InsnNode(Opcodes.POP));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tinstructions.set(min, replacement);\n\t\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (dirty)\n\t\t\tcontext.setNode(bundle, initialClassState, node);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<Class<? extends ClassTransformer>> recommendedSuccessors() {\n\t\t// This transformer results in the creation of a lot of POP/POP2 instructions.\n\t\t// The stack-operation folding transformer can clean up afterward.\n\t\treturn Collections.singleton(OpaqueConstantFoldingTransformer.class);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Call result inlining\";\n\t}\n\n\tprivate boolean canEvaluate(@Nonnull MethodInsnNode min) {\n\t\tString key = min.owner + \".\" + min.name + min.desc;\n\t\tsynchronized (canBeEvaluatedMap) {\n\t\t\treturn canBeEvaluatedMap.computeIfAbsent(key, k -> evaluator.canEvaluate(min.owner, min.name, min.desc));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/CycleClassRemovingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.inheritance.InheritanceVertex;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * A transformer that removes any class with cyclic inheritance.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class CycleClassRemovingTransformer implements JvmClassTransformer {\n\tprivate final InheritanceGraphService graphService;\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate InheritanceGraph inheritanceGraph;\n\n\t@Inject\n\tpublic CycleClassRemovingTransformer(@Nonnull WorkspaceManager workspaceManager,\n\t                                     @Nonnull InheritanceGraphService graphService) {\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.graphService = graphService;\n\t}\n\n\t@Override\n\tpublic void setup(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace) {\n\t\tinheritanceGraph = graphService.getOrCreateInheritanceGraph(workspace);\n\t}\n\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(initialClassState.getName());\n\t\tif (vertex != null && vertex.isLoop())\n\t\t\tcontext.markClassForRemoval(initialClassState);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Cycle class removal\";\n\t}\n\n\t@Override\n\tpublic boolean pruneAfterNoWork() {\n\t\t// Other transformers should not introduce cyclic classes,\n\t\t// so once the work is done there is no need to re-process classes on following passes.\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/DeadCodeRemovingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.JumpInsnNode;\nimport org.objectweb.asm.tree.LabelNode;\nimport org.objectweb.asm.tree.LocalVariableNode;\nimport org.objectweb.asm.tree.LookupSwitchInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.TableSwitchInsnNode;\nimport org.objectweb.asm.tree.TryCatchBlockNode;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.transform.ClassTransformer;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.AsmInsnUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Queue;\nimport java.util.Set;\n\nimport static org.objectweb.asm.Opcodes.NOP;\nimport static software.coley.recaf.util.AsmInsnUtil.fixMissingVariableLabels;\n\n/**\n * A transformer that removes dead code.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class DeadCodeRemovingTransformer implements JvmClassTransformer {\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tboolean dirty = false;\n\t\tString className = initialClassState.getName();\n\t\tClassNode node = context.getNode(bundle, initialClassState);\n\t\tfor (MethodNode method : node.methods)\n\t\t\tdirty |= prune(node, method);\n\t\tif (dirty)\n\t\t\tcontext.setNode(bundle, initialClassState, node);\n\t}\n\n\tpublic boolean prune(@Nonnull ClassNode node, @Nonnull MethodNode method) throws TransformationException {\n\t\tInsnList instructions = method.instructions;\n\t\tif (instructions == null || instructions.size() == 0)\n\t\t\treturn false;\n\n\t\t// Collect try blocks and the instructions within the start-end range (exclusive)\n\t\tList<TryCatch> tryCatches = new ArrayList<>(method.tryCatchBlocks.size());\n\t\tfor (TryCatchBlockNode block : method.tryCatchBlocks) {\n\t\t\tList<AbstractInsnNode> blockInsns = new ArrayList<>();\n\t\t\tAbstractInsnNode insn = block.start;\n\t\t\tLabelNode end = block.end;\n\t\t\twhile (insn != null) {\n\t\t\t\tinsn = insn.getNext();\n\t\t\t\tif (insn == end || insn == null)\n\t\t\t\t\tbreak;\n\t\t\t\tblockInsns.add(insn);\n\t\t\t}\n\t\t\ttryCatches.add(new TryCatch(block, blockInsns));\n\t\t}\n\n\t\tboolean dirty = false;\n\t\ttry {\n\t\t\t// Compute which instructions are visited by walking the method's control flow.\n\t\t\tSet<AbstractInsnNode> visited = Collections.newSetFromMap(new IdentityHashMap<>());\n\t\t\tList<AbstractInsnNode> flowStarts = new ArrayList<>();\n\t\t\tflowStarts.add(instructions.getFirst());\n\t\t\tfor (TryCatch tryCatch : tryCatches)\n\t\t\t\tflowStarts.add(tryCatch.block.handler);\n\t\t\tvisit(visited, flowStarts);\n\n\t\t\t// Prune any instructions not visited.\n\t\t\tint end = instructions.size() - 1;\n\t\t\tfor (int i = end; i >= 0; i--) {\n\t\t\t\tAbstractInsnNode insn = instructions.get(i);\n\t\t\t\tif (!visited.contains(insn) || insn.getOpcode() == NOP) {\n\t\t\t\t\t// Don't prune the tail label even if it is \"dead\" because the last method instruction\n\t\t\t\t\t// is terminal like 'return' or 'athrow'. The label will just get added back automatically\n\t\t\t\t\t// and cause the transform process to loop on repeat.\n\t\t\t\t\tif (i == end && insn.getType() == AbstractInsnNode.LABEL)\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t// Keep try-catch labels for now.\n\t\t\t\t\tif (insn.getType() == AbstractInsnNode.LABEL && method.tryCatchBlocks.stream().anyMatch(tryCatch -> {\n\t\t\t\t\t\tif (insn == tryCatch.start) return true;\n\t\t\t\t\t\tif (insn == tryCatch.end) return true;\n\t\t\t\t\t\treturn insn == tryCatch.handler;\n\t\t\t\t\t})) continue;\n\n\t\t\t\t\t// Remove instruction from method.\n\t\t\t\t\tinstructions.remove(insn);\n\n\t\t\t\t\t// Remove from any catch block's visited instructions.\n\t\t\t\t\tfor (TryCatch tryCatch : tryCatches)\n\t\t\t\t\t\ttryCatch.visitedInstructions.remove(insn);\n\n\t\t\t\t\t// Mark as dirty.\n\t\t\t\t\tdirty = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we have removed all the instructions of a try block's start-end range (because they're dead code)\n\t\t\t// then the try-catch block entry can be removed.\n\t\t\tfor (TryCatch tryCatch : tryCatches) {\n\t\t\t\tif (tryCatch.visitedInstructions.isEmpty()) {\n\t\t\t\t\tmethod.tryCatchBlocks.remove(tryCatch.block);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Ensure that after dead code removal (or any other transformers not cleaning up)\n\t\t\t// that all variables have labels that reside in the method code list.\n\t\t\tList<LocalVariableNode> variables = method.localVariables;\n\t\t\tif (variables != null && variables.stream().anyMatch(l -> !instructions.contains(l.start) || !instructions.contains(l.end))) {\n\t\t\t\tfixMissingVariableLabels(method);\n\t\t\t\tdirty = true;\n\t\t\t}\n\t\t} catch (Throwable t) {\n\t\t\tthrow new TransformationException(\"Error encountered when removing dead code\", t);\n\t\t}\n\t\treturn dirty;\n\t}\n\n\tprivate static void visit(@Nonnull Set<AbstractInsnNode> visited, @Nonnull List<AbstractInsnNode> startingPoints) {\n\t\tQueue<AbstractInsnNode> todo = new ArrayDeque<>(startingPoints);\n\t\twhile (!todo.isEmpty()) {\n\t\t\tAbstractInsnNode insn = todo.remove();\n\t\t\twhile (insn != null && visited.add(insn)) {\n\t\t\t\thandleNext:\n\t\t\t\t{\n\t\t\t\t\tswitch (insn.getType()) {\n\t\t\t\t\t\tcase AbstractInsnNode.INSN -> {\n\t\t\t\t\t\t\tif (AsmInsnUtil.isTerminalOrAlwaysTakeFlowControl(insn.getOpcode()))\n\t\t\t\t\t\t\t\tbreak handleNext;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase AbstractInsnNode.JUMP_INSN -> {\n\t\t\t\t\t\t\tJumpInsnNode jin = (JumpInsnNode) insn;\n\t\t\t\t\t\t\ttodo.add(jin.label);\n\t\t\t\t\t\t\tif (AsmInsnUtil.isTerminalOrAlwaysTakeFlowControl(jin.getOpcode()))\n\t\t\t\t\t\t\t\tbreak handleNext;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase AbstractInsnNode.TABLESWITCH_INSN -> {\n\t\t\t\t\t\t\tTableSwitchInsnNode tsin = (TableSwitchInsnNode) insn;\n\t\t\t\t\t\t\ttodo.add(tsin.dflt);\n\t\t\t\t\t\t\ttodo.addAll(tsin.labels);\n\t\t\t\t\t\t\tbreak handleNext;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase AbstractInsnNode.LOOKUPSWITCH_INSN -> {\n\t\t\t\t\t\t\tLookupSwitchInsnNode lsin = (LookupSwitchInsnNode) insn;\n\t\t\t\t\t\t\ttodo.add(lsin.dflt);\n\t\t\t\t\t\t\ttodo.addAll(lsin.labels);\n\t\t\t\t\t\t\tbreak handleNext;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tinsn = insn.getNext();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<Class<? extends ClassTransformer>> recommendedPredecessors() {\n\t\t// Having opaque predicates replaced with direct GOTO or fall-through\n\t\t// will allow  this transformer to properly detect dead code.\n\t\treturn Collections.singleton(OpaquePredicateFoldingTransformer.class);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Dead code removal\";\n\t}\n\n\trecord TryCatch(@Nonnull TryCatchBlockNode block, @Nonnull List<AbstractInsnNode> visitedInstructions) {\n\t\tboolean hasInsn(@Nonnull AbstractInsnNode insn) {\n\t\t\treturn visitedInstructions.contains(insn);\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/DuplicateAnnotationRemovingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.visitors.DuplicateAnnotationRemovingVisitor;\nimport software.coley.recaf.util.visitors.IllegalAnnotationRemovingVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * A transformer that removes any invalid annotations from classes and any of their declared members.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class DuplicateAnnotationRemovingTransformer implements JvmClassTransformer {\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\t// Adapt the class bytes by removing any duplicate annotation.\n\t\tClassReader reader = new ClassReader(context.getBytecode(bundle, initialClassState));\n\t\tClassWriter writer = new ClassWriter(reader, 0);\n\n\t\tDuplicateAnnotationRemovingVisitor remover = new DuplicateAnnotationRemovingVisitor(writer);\n\t\treader.accept(remover, initialClassState.getClassReaderFlags());\n\n\t\t// If the visitor did work, update the class.\n\t\tif (remover.hasDetectedDuplicateAnnotations())\n\t\t\tcontext.setBytecode(bundle, initialClassState, writer.toByteArray());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Duplicate annotation removal\";\n\t}\n\n\t@Override\n\tpublic boolean pruneAfterNoWork() {\n\t\t// Other transformers should not introduce duplicate annotations,\n\t\t// so once the work is done there is no need to re-process classes on following passes.\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/DuplicateCatchMergingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.JumpInsnNode;\nimport org.objectweb.asm.tree.LabelNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.TryCatchBlockNode;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.BlwUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport static software.coley.recaf.util.AsmInsnUtil.*;\n\n/**\n * A transformer that removes duplicate code in try-catch handler blocks.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class DuplicateCatchMergingTransformer implements JvmClassTransformer {\n\t/**\n\t * Allows us to skip blocks that are too simple to bother merging.\n\t * For instance:\n\t * <ul>\n\t *     <li>{@code { throw e; }}</li>\n\t *     <li>{@code { e.printStacktrace(); }}</li>\n\t *     <li>{@code { no-op }}</li>\n\t * </ul>\n\t */\n\tprivate static final int MIN_BLOCK_THRESHOLD = 4;\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tboolean isTransformed = false;\n\t\tClassNode node = context.getNode(bundle, initialClassState);\n\t\tfor (MethodNode method : node.methods) {\n\t\t\tInsnList instructions = method.instructions;\n\t\t\tif (instructions == null) continue;\n\t\t\tif (method.tryCatchBlocks == null || method.tryCatchBlocks.size() <= 1) continue;\n\n\t\t\t// Build model of catch block contents.\n\t\t\tSet<AbstractInsnNode> visitedHandlers = Collections.newSetFromMap(new IdentityHashMap<>());\n\t\t\tMap<TryCatchBlockNode, CodeBlock> catchBlocks = new IdentityHashMap<>();\n\t\t\tfor (TryCatchBlockNode tryCatchBlock : method.tryCatchBlocks) {\n\t\t\t\tList<AbstractInsnNode> catchBlockInsns = new ArrayList<>();\n\t\t\t\tAbstractInsnNode insn = tryCatchBlock.handler;\n\n\t\t\t\t// Skip if we've already visited this block.\n\t\t\t\tif (!visitedHandlers.add(insn))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Build block contents.\n\t\t\t\twhile (insn != null) {\n\t\t\t\t\t// Only include non-metadata/filler instructions.\n\t\t\t\t\tif (insn.getType() != AbstractInsnNode.FRAME && insn.getOpcode() != NOP)\n\t\t\t\t\t\tcatchBlockInsns.add(insn);\n\n\t\t\t\t\t// Abort when we encounter an exit or control flow.\n\t\t\t\t\tif (isReturn(insn) || isFlowControl(insn))\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tinsn = insn.getNext();\n\t\t\t\t}\n\n\t\t\t\t// Skip if the block is too small.\n\t\t\t\tif (catchBlockInsns.size() < MIN_BLOCK_THRESHOLD)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Skip if there is control flow pointing to a contained label.\n\t\t\t\tif (hasExternalFlowIntoCatchBlock(method, catchBlockInsns))\n\t\t\t\t\tcontinue;\n\n\t\t\t\tcatchBlocks.put(tryCatchBlock, new CodeBlock(catchBlockInsns));\n\t\t\t}\n\n\t\t\t// Sort the blocks by their position in the method code, and then redirect the control flow of earlier\n\t\t\t// catch blocks to the last catch block handler with equivalent code.\n\t\t\tList<CodeBlock> blocks = catchBlocks.values().stream()\n\t\t\t\t\t.sorted()\n\t\t\t\t\t.toList();\n\t\t\tfor (int i = 0; i < blocks.size() - 1; i++) {\n\t\t\t\tCodeBlock block = blocks.get(i);\n\t\t\t\tfor (int j = blocks.size() - 1; j > i; j--) {\n\t\t\t\t\tCodeBlock laterBlock = blocks.get(j);\n\t\t\t\t\tif (block.equals(laterBlock)) {\n\t\t\t\t\t\tblock.pruneContent(instructions);\n\t\t\t\t\t\tblock.redirectTo(instructions, (LabelNode) laterBlock.instructions.getFirst());\n\t\t\t\t\t\tisTransformed = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (isTransformed) {\n\t\t\tcontext.setRecomputeFrames(initialClassState.getName());\n\t\t\tcontext.setNode(bundle, initialClassState, node);\n\t\t}\n\t}\n\n\tprivate static boolean hasExternalFlowIntoCatchBlock(@Nonnull MethodNode method,\n\t                                                     @Nonnull List<AbstractInsnNode> block) {\n\t\t// We pass 'includeFirstInsn = false' because no catch block should be used to point to a label\n\t\t// that is in the middle of the block. However, if the handler is the start of the block, that is fine\n\t\t// as that is expected as a potential handler.\n\t\tif (hasHandlerFlowIntoBlock(method, block, false))\n\t\t\treturn true;\n\n\t\t// No control flow instruction should point to this block *at all*.\n\t\t// If we observe this to be the case, it would be very hard to manipulate the contents of this block\n\t\t// without breaking the flow of the method.\n\t\treturn hasInboundFlowReferences(method, block);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Duplicate catch merging\";\n\t}\n\n\t/**\n\t * Container for multiple instructions. Uses the disassembled presentation as a key\n\t * because ASM does not implement equals/hashCodes for their instruction models.\n\t */\n\tprivate final static class CodeBlock implements Comparable<CodeBlock> {\n\t\tprivate final List<AbstractInsnNode> instructions;\n\t\tprivate final String disassembled;\n\t\tprivate final int index;\n\n\t\tprivate CodeBlock(@Nonnull List<AbstractInsnNode> instructions) {\n\t\t\tthis.instructions = instructions;\n\t\t\tthis.disassembled = instructions.stream()\n\t\t\t\t\t.skip(1) // Skip first label, which will always be unique\n\t\t\t\t\t.map(BlwUtil::toString)\n\t\t\t\t\t.collect(Collectors.joining(\"\\n\"));\n\t\t\tthis.index = indexOf(instructions.getFirst());\n\t\t}\n\n\t\t/**\n\t\t * Prunes the instructions of this block that are not the initial label.\n\t\t *\n\t\t * @param container\n\t\t * \t\tMethod instruction container to remove the instructions from.\n\t\t */\n\t\tpublic void pruneContent(@Nonnull InsnList container) {\n\t\t\twhile (instructions.size() > 1) {\n\t\t\t\tAbstractInsnNode next = instructions.remove(1);\n\t\t\t\tcontainer.remove(next);\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Redirects the control flow of this block to the given label.\n\t\t *\n\t\t * @param container\n\t\t * \t\tMethod instruction container to modify control flow of.\n\t\t * @param target\n\t\t * \t\tTarget label to jump to.\n\t\t */\n\t\tpublic void redirectTo(@Nonnull InsnList container, @Nonnull LabelNode target) {\n\t\t\tAbstractInsnNode first = instructions.getFirst();\n\t\t\tcontainer.insert(first, new JumpInsnNode(GOTO, target));\n\t\t}\n\n\t\t/**\n\t\t * @return Starting index in the method code of this block.\n\t\t */\n\t\tpublic int getIndex() {\n\t\t\treturn index;\n\t\t}\n\n\t\t/**\n\t\t * @return Instructions contained by this block.\n\t\t */\n\t\t@Nonnull\n\t\tpublic List<AbstractInsnNode> getInstructions() {\n\t\t\treturn instructions;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean equals(Object o) {\n\t\t\tif (this == o) return true;\n\t\t\tif (!(o instanceof CodeBlock codeBlock)) return false;\n\t\t\treturn disassembled.equals(codeBlock.disassembled);\n\t\t}\n\n\t\t@Override\n\t\tpublic int hashCode() {\n\t\t\treturn disassembled.hashCode();\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn disassembled;\n\t\t}\n\n\t\t@Override\n\t\tpublic int compareTo(CodeBlock o) {\n\t\t\t// We already know only one code-block can exist per-each handler label\n\t\t\t// so our indices should always be unique.\n\t\t\treturn Integer.compare(index, o.index);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/EnumNameRestorationTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.FieldInsnNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.LdcInsnNode;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.Set;\n\nimport static org.objectweb.asm.Opcodes.*;\nimport static software.coley.recaf.util.AsmInsnUtil.getNextFollowGoto;\nimport static software.coley.recaf.util.AsmInsnUtil.isConstIntValue;\n\n/**\n * A transformer that creates mappings to rename obfuscated enum constants that have been not properly obfuscated.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class EnumNameRestorationTransformer implements JvmClassTransformer {\n\tprivate static final String VALUES_ARRAY_NAME = \"$values\";\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\t// Skip non-enum classes.\n\t\tif (!initialClassState.hasEnumModifier()) return;\n\n\t\t// Record mappings for enum constants where the static initializer\n\t\t// leaks their original names (assuming the code is name obfuscated).\n\t\tClassNode node = context.getNode(bundle, initialClassState);\n\t\tString constDesc = 'L' + node.name + \";\";\n\t\tString valuesDesc = '[' + constDesc;\n\t\tfor (MethodNode method : node.methods) {\n\t\t\t// Skip abstract methods.\n\t\t\tInsnList instructions = method.instructions;\n\t\t\tif (instructions == null)\n\t\t\t\tcontinue;\n\n\t\t\t// Skip any method that is not the static initializer.\n\t\t\tif (!method.name.equals(\"<clinit>\") || !method.desc.equals(\"()V\"))\n\t\t\t\tcontinue;\n\n\t\t\t// Pattern to match for constants:\n\t\t\t//   new ENUM_TYPE\n\t\t\t//   dup\n\t\t\t//   ldc \"NAME_OF_CONSTANT\"\n\t\t\t//   iconst_0\n\t\t\t//   invokespecial ENUM_TYPE.<init> (Ljava/lang/String;I)V   (may have additional arguments, but first two should be consistent)\n\t\t\t//   astore v0                                               (optional)\n\t\t\t//   aload v0                                                (optional)\n\t\t\t//   putstatic ENUM_TYPE.OBF_NAME_OF_CONSTANT LENUM_TYPE;\n\t\t\t// Pattern to match for $values array\n\t\t\t//   invokestatic ENUM_TYPE.$values ()[LENUM_TYPE;\n\t\t\t//   putstatic Example.OBF_NAME_OF_ARRAY [LENUM_TYPE;\n\t\t\tfor (AbstractInsnNode instruction : instructions) {\n\t\t\t\tint op = instruction.getOpcode();\n\t\t\t\tif (op == LDC)\n\t\t\t\t\thandleEnumConst(context, initialClassState, (LdcInsnNode) instruction, constDesc);\n\t\t\t\telse if (op == INVOKESTATIC)\n\t\t\t\t\thandleValuesArray(context, initialClassState, (MethodInsnNode) instruction, valuesDesc);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void handleValuesArray(@Nonnull JvmTransformerContext context,\n\t                               @Nonnull JvmClassInfo initialClassState,\n\t                               @Nonnull MethodInsnNode invokeInsn,\n\t                               @Nonnull String valuesDesc) {\n\t\tString enumOwner = initialClassState.getName();\n\t\tString invokeOwner = invokeInsn.owner;\n\t\tString invokeName = invokeInsn.name;\n\t\tString invokeDesc = invokeInsn.desc;\n\t\tSet<AbstractInsnNode> visited = Collections.newSetFromMap(new IdentityHashMap<>());\n\t\tif (invokeOwner.equals(enumOwner) && invokeDesc.equals(\"()\" + valuesDesc)) {\n\t\t\tAbstractInsnNode next = getNextFollowGoto(invokeInsn);\n\t\t\twhile (next != null && next.getOpcode() != PUTSTATIC && visited.add(next))\n\t\t\t\tnext = getNextFollowGoto(next);\n\t\t\tif (next != null && next.getOpcode() == PUTSTATIC) {\n\t\t\t\tFieldInsnNode assignmentInsn = (FieldInsnNode) next;\n\t\t\t\tString fieldOwner = assignmentInsn.owner;\n\t\t\t\tString fieldName = assignmentInsn.name;\n\t\t\t\tString fieldDesc = assignmentInsn.desc;\n\t\t\t\tif (fieldOwner.equals(enumOwner) && fieldDesc.equals(valuesDesc) && !fieldName.equals(VALUES_ARRAY_NAME))\n\t\t\t\t\tcontext.getMappings().addField(enumOwner, valuesDesc, fieldName, VALUES_ARRAY_NAME);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static void handleEnumConst(@Nonnull JvmTransformerContext context,\n\t                                    @Nonnull JvmClassInfo initialClassState,\n\t                                    @Nonnull LdcInsnNode nameInsn,\n\t                                    @Nonnull String constDesc) {\n\t\tif (!(nameInsn.cst instanceof String nameString) || !nameString.matches(\"\\\\w+\"))\n\t\t\treturn;\n\n\t\tAbstractInsnNode indexInsn = getNextFollowGoto(nameInsn);\n\t\tif (indexInsn == null || !isConstIntValue(indexInsn))\n\t\t\treturn;\n\n\t\tSet<AbstractInsnNode> visited = Collections.newSetFromMap(new IdentityHashMap<>());\n\t\tAbstractInsnNode next = getNextFollowGoto(indexInsn);\n\t\twhile (next != null && next.getOpcode() != PUTSTATIC && visited.add(next))\n\t\t\tnext = getNextFollowGoto(next);\n\t\tif (next != null && next.getOpcode() == PUTSTATIC) {\n\t\t\tFieldInsnNode assignmentInsn = (FieldInsnNode) next;\n\t\t\tString fieldName = assignmentInsn.name;\n\t\t\tString fieldDesc = assignmentInsn.desc;\n\t\t\tif (fieldDesc.equals(constDesc) && !fieldName.equals(nameString)) {\n\t\t\t\tFieldMember field = initialClassState.getDeclaredField(fieldName, fieldDesc);\n\t\t\t\tif (field != null && field.hasEnumModifier())\n\t\t\t\t\tcontext.getMappings().addField(initialClassState.getName(), constDesc, fieldName, nameString);\n\t\t\t}\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Enum name restoration\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/ExceptionCollectionTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.Opcodes;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.properties.builtin.ThrowableProperty;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.visitors.SkippingClassVisitor;\nimport software.coley.recaf.util.visitors.SkippingMethodVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * A transformer that collects information about exceptions in the workspace.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class ExceptionCollectionTransformer implements JvmClassTransformer, Opcodes {\n\tprivate final Set<String> thrownExceptions = new HashSet<>();\n\tprivate final InheritanceGraphService graphService;\n\tprivate InheritanceGraph inheritanceGraph;\n\n\t@Inject\n\tpublic ExceptionCollectionTransformer(@Nonnull InheritanceGraphService graphService) {\n\t\tthis.graphService = graphService;\n\n\t\t// Base types which we assume are implicitly thrown.\n\t\tthrownExceptions.add(\"java/lang/Throwable\");\n\t\tthrownExceptions.add(\"java/lang/Exception\");\n\t}\n\n\t@Override\n\tpublic void setup(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace) {\n\t\tinheritanceGraph = graphService.getOrCreateInheritanceGraph(workspace);\n\t}\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tinitialClassState.getClassReader().accept(new SkippingClassVisitor() {\n\t\t\t@Override\n\t\t\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\t\t\treturn new SkippingMethodVisitor() {\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void visitTypeInsn(int opcode, String type) {\n\t\t\t\t\t\t// Collect \"new T\" where \"T\" is a throwable type.\n\t\t\t\t\t\tif (opcode != NEW)\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\tClassPathNode path = workspace.findClass(false, type);\n\t\t\t\t\t\tif (path == null)\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\tif (ThrowableProperty.get(path.getValue()) || inheritanceGraph.isAssignableFrom(\"java/lang/Throwable\", type)) {\n\t\t\t\t\t\t\t// The constructed type is an exception type,\n\t\t\t\t\t\t\t// so we should add it and all parents to the known thrown types.\n\t\t\t\t\t\t\tClassInfo exInfo = path.getValue();\n\t\t\t\t\t\t\twhile (thrownExceptions.add(exInfo.getName()) && exInfo.getSuperName() != null) {\n\t\t\t\t\t\t\t\tpath = workspace.findClass(false, exInfo.getSuperName());\n\t\t\t\t\t\t\t\tif (path == null)\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t}, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Exception metadata collection\";\n\t}\n\n\t/**\n\t * @return Set of all exception types thrown in code defined in the workspace.\n\t */\n\t@Nonnull\n\tpublic Set<String> getThrownExceptions() {\n\t\treturn thrownExceptions;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/FrameRemovingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.FrameNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.MethodNode;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * A transformer that removes stack frames.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class FrameRemovingTransformer implements JvmClassTransformer {\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tif (context.isNode(bundle, initialClassState)) {\n\t\t\tClassNode node = context.getNode(bundle, initialClassState);\n\t\t\tfor (MethodNode method : node.methods) {\n\t\t\t\tInsnList instructions = method.instructions;\n\t\t\t\tif (instructions != null) {\n\t\t\t\t\tfor (int i = instructions.size() - 1; i > 0; i--) {\n\t\t\t\t\t\tAbstractInsnNode insn = instructions.get(i);\n\t\t\t\t\t\tif (insn instanceof FrameNode)\n\t\t\t\t\t\t\tinstructions.remove(insn);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tClassReader reader = new ClassReader(context.getBytecode(bundle, initialClassState));\n\t\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\t\treader.accept(writer, ClassReader.SKIP_FRAMES);\n\t\t\tcontext.setBytecode(bundle, initialClassState, writer.toByteArray());\n\t\t}\n\t\tcontext.setRecomputeFrames(initialClassState.getName());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Stack frame removal\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/GotoInliningTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.InsnNode;\nimport org.objectweb.asm.tree.JumpInsnNode;\nimport org.objectweb.asm.tree.LabelNode;\nimport org.objectweb.asm.tree.LookupSwitchInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.TableSwitchInsnNode;\nimport org.objectweb.asm.tree.TryCatchBlockNode;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.transform.ClassTransformer;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport static software.coley.recaf.util.AsmInsnUtil.*;\n\n/**\n * A transformer that inlines control flow of <i>(redundant)</i> {@link Opcodes#GOTO} instructions.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class GotoInliningTransformer implements JvmClassTransformer {\n\tprivate static final int CATCH_VISIT_COUNT = 100;\n\tprivate static final boolean DO_WE_CARE_ABOUT_BACKWARDS_SWITCH_FLOW = false;\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tboolean dirty = false;\n\t\tString className = initialClassState.getName();\n\t\tClassNode node = context.getNode(bundle, initialClassState);\n\n\t\tfor (int m = 0; m < node.methods.size(); m++) {\n\t\t\tMethodNode base = node.methods.get(m);\n\n\t\t\t// Skip if abstract.\n\t\t\tif (base.instructions == null)\n\t\t\t\tcontinue;\n\n\t\t\t// Because of the multiple \"stages\" we do, it's easier if we work on a copy of the method\n\t\t\t// and then write back our copy if we ended up making relevant changes. This way, if we\n\t\t\t// have changes from pre-processing, but there is no inlining work that gets done we can\n\t\t\t// throw away the changes and not worry about the changes accidentally being kept.\n\t\t\tMethodNode method = copyOf(base);\n\t\t\tInsnList instructions = method.instructions;\n\n\t\t\t// There are some obfuscators that put junk after the final 'return' instruction of methods.\n\t\t\t//\n\t\t\t// For example:\n\t\t\t//    return\n\t\t\t//    nop    // dead code removed by ASM\n\t\t\t//    athrow\n\t\t\t//\n\t\t\t// In this transformer, we ensure that removing blocks near the end of a method does not result in dangling code.\n\t\t\t// Without removing the dead code seen in the example, this transformer would see the 'athrow' and think it is\n\t\t\t// ok to move a block containing the 'return' somewhere else. However, the 'athrow' there is not valid because\n\t\t\t// there is nothing on the stack to throw.\n\t\t\t//\n\t\t\t// The simple fix is to do a dead-code removing pass before we run this transformer.\n\t\t\tcontext.pruneDeadCode(node, method);\n\n\t\t\t// Record where labels are visited from. Start with explicit control flow jumps.\n\t\t\tVisitCounters visitCounters = new VisitCounters();\n\t\t\tfor (int i = 0; i < instructions.size(); i++) {\n\t\t\t\tAbstractInsnNode insn = instructions.get(i);\n\t\t\t\tif (insn instanceof JumpInsnNode cast) {\n\t\t\t\t\tvisitCounters.of(cast.label).addExplicitSource(cast);\n\t\t\t\t} else if (insn instanceof TableSwitchInsnNode cast) {\n\t\t\t\t\tvisitCounters.of(cast.dflt).addExplicitSource(cast);\n\t\t\t\t\tcast.labels.forEach(l -> visitCounters.of(l).addExplicitSource(cast));\n\t\t\t\t} else if (insn instanceof LookupSwitchInsnNode cast) {\n\t\t\t\t\tvisitCounters.of(cast.dflt).addExplicitSource(cast);\n\t\t\t\t\tcast.labels.forEach(l -> visitCounters.of(l).addExplicitSource(cast));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Next record which labels are try-catch boundaries.\n\t\t\tif (method.tryCatchBlocks != null) {\n\t\t\t\tfor (TryCatchBlockNode tryCatch : method.tryCatchBlocks) {\n\t\t\t\t\tvisitCounters.of(tryCatch.start).markTryStart();\n\t\t\t\t\tvisitCounters.of(tryCatch.end).markTryEnd();\n\t\t\t\t\tvisitCounters.of(tryCatch.handler).markTryHandler();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Lastly record implicit flow into labels.\n\t\t\t// This just means if the code flows linearly from \"A\" into \"B\".\n\t\t\tfor (int i = 0; i < instructions.size(); i++) {\n\t\t\t\tAbstractInsnNode insn = instructions.get(i);\n\t\t\t\tif (insn instanceof LabelNode targetLabel) {\n\t\t\t\t\tAbstractInsnNode prev = targetLabel.getPrevious();\n\n\t\t\t\t\t// If the target label has no previous instruction then it must be the first label of the method.\n\t\t\t\t\t// Thus, it makes sense to say \"yeah, we can flow here\".\n\t\t\t\t\tif (prev == null) {\n\t\t\t\t\t\tvisitCounters.of(targetLabel).markImplicitFlow();\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// We want to see if we can naturally flow into this position.\n\t\t\t\t\t// Begin walking backwards linearly and see if we can end up at this target label.\n\t\t\t\t\twhile (true) {\n\t\t\t\t\t\t// If we encounter an instruction that terminates linear flow we will stop walking backward.\n\t\t\t\t\t\t// This should imply that the target label cannot naturally be flowed into.\n\t\t\t\t\t\tif (isTerminalOrAlwaysTakeFlowControl(prev.getOpcode()))\n\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\t// If we encounter another label that is visited at least once while walking backwards\n\t\t\t\t\t\t// then the flow should continue from there to our target label.\n\t\t\t\t\t\tif (prev instanceof LabelNode prevLabel && visitCounters.of(prevLabel).isVisited()) {\n\t\t\t\t\t\t\tvisitCounters.of(targetLabel).markImplicitFlow();\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Step backwards.\n\t\t\t\t\t\tprev = prev.getPrevious();\n\n\t\t\t\t\t\t// Same check that we had before the while loop. If we hit the start of the method,\n\t\t\t\t\t\t// then of course we can flow to here.\n\t\t\t\t\t\tif (prev == null) {\n\t\t\t\t\t\t\tvisitCounters.of(targetLabel).markImplicitFlow();\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check for super-simple goto instruction patterns that can be inlined easily.\n\t\t\tboolean localDirty = false;\n\t\t\tfor (int i = 0; i < instructions.size(); i++) {\n\t\t\t\tAbstractInsnNode insn = instructions.get(i);\n\n\t\t\t\t// Skip any non-goto instruction.\n\t\t\t\tif (insn.getOpcode() != GOTO)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// If the goto target label is just the next instruction then we can replace\n\t\t\t\t// the goto with a nop. The dead code pass later on will clean these up.\n\t\t\t\tJumpInsnNode jin = (JumpInsnNode) insn;\n\t\t\t\tVisitCounter counter = visitCounters.of(jin.label);\n\t\t\t\tif (jin.label == jin.getNext() && !counter.isTryTarget()) {\n\t\t\t\t\tlocalDirty = true;\n\t\t\t\t\tinstructions.set(jin, new InsnNode(NOP));\n\t\t\t\t\tcounter.removeExplicitSource(jin);\n\t\t\t\t\tcounter.markImplicitFlow();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check for goto instruction patterns that require more care.\n\t\t\t//  - Cannot result in creating end-of-method fall-through.\n\t\t\t//  - Cannot inline a block starting with a label that is flowed to both explicitly and implicitly.\n\t\t\t//  - Cannot inline a block that would break try-catch range contracts.\n\t\t\tfor (int i = 0; i < instructions.size(); i++) {\n\t\t\t\tAbstractInsnNode insn = instructions.get(i);\n\n\t\t\t\t// Skip any non-goto instruction.\n\t\t\t\tif (insn.getOpcode() != GOTO)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Skip any jump target labels that are visited more than once.\n\t\t\t\tJumpInsnNode jin = (JumpInsnNode) insn;\n\t\t\t\tif (visitCounters.of(jin.label).count() > 1)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Attempt to re-arrange the code at the goto's destination to be inline here.\n\t\t\t\tdoInline:\n\t\t\t\t{\n\t\t\t\t\tList<AbstractInsnNode> block = new ArrayList<>();\n\t\t\t\t\tAbstractInsnNode target = jin.label;\n\t\t\t\t\twhile (target != null) {\n\t\t\t\t\t\t// Abort if we loop back around.\n\t\t\t\t\t\tif (target == jin)\n\t\t\t\t\t\t\tbreak doInline;\n\n\t\t\t\t\t\t// Abort if we see that relocating the GOTO destination's code would change the behavior\n\t\t\t\t\t\t// with try-catch blocks.\n\t\t\t\t\t\tif (method.tryCatchBlocks != null) {\n\t\t\t\t\t\t\tfor (TryCatchBlockNode tryCatchBlock : method.tryCatchBlocks) {\n\t\t\t\t\t\t\t\tint gotoIndex = instructions.indexOf(jin);\n\t\t\t\t\t\t\t\tint targetIndex = instructions.indexOf(target);\n\t\t\t\t\t\t\t\tint tryStart = instructions.indexOf(tryCatchBlock.start);\n\t\t\t\t\t\t\t\tint tryEnd = instructions.indexOf(tryCatchBlock.end);\n\n\t\t\t\t\t\t\t\t// Skip if this would result in moving the targeted code inside a try-catch somewhere outside the try-catch.\n\t\t\t\t\t\t\t\tif (tryStart <= targetIndex && targetIndex < tryEnd) {\n\t\t\t\t\t\t\t\t\tif (tryStart > gotoIndex || gotoIndex >= tryEnd)\n\t\t\t\t\t\t\t\t\t\tbreak doInline;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Skip if this would result in moving the code outside the try-catch into the try-catch\n\t\t\t\t\t\t\t\tif (tryStart <= gotoIndex && gotoIndex < tryEnd) {\n\t\t\t\t\t\t\t\t\tif (tryStart > targetIndex || targetIndex >= tryEnd)\n\t\t\t\t\t\t\t\t\t\tbreak doInline;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tint targetOp = target.getOpcode();\n\n\t\t\t\t\t\t// TODO: Maybe remove this? Need to do more research into when it complains.\n\t\t\t\t\t\tif (DO_WE_CARE_ABOUT_BACKWARDS_SWITCH_FLOW) {\n\t\t\t\t\t\t\t// There are some weird cases where you're not allowed to jump backwards in switch instructions,\n\t\t\t\t\t\t\t// so it's better to abort if we see them so that we do not move them around in an illegal way.\n\t\t\t\t\t\t\tif (targetOp == TABLESWITCH || targetOp == LOOKUPSWITCH)\n\t\t\t\t\t\t\t\tbreak doInline;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Record this instruction as part of the block if it isn't metadata/junk.\n\t\t\t\t\t\tif (target.getType() != AbstractInsnNode.FRAME)\n\t\t\t\t\t\t\tblock.add(target);\n\n\t\t\t\t\t\t// Break out of this while loop if the target instruction is the end of a method's control flow,\n\t\t\t\t\t\t// or an always-branch instruction like goto/switch. This marks the end of our block.\n\t\t\t\t\t\t// We will do some final checks to see if this block can be inlined.\n\t\t\t\t\t\tif (isGotoBlockTerminator(targetOp)) {\n\t\t\t\t\t\t\t// Check if inlining this would cause dangling code (no return at the end of the method)\n\t\t\t\t\t\t\tAbstractInsnNode next = getNextInsn(target);\n\t\t\t\t\t\t\tif (instructions.getLast() == next || next == null) {\n\t\t\t\t\t\t\t\t// This block is the code at the end of the method.\n\t\t\t\t\t\t\t\t// Check if the code before this block has terminal control flow\n\t\t\t\t\t\t\t\t// (to prevent creation of dangling code at the end of the method)\n\t\t\t\t\t\t\t\tAbstractInsnNode prevBeforeBlock = getPreviousInsn(jin.label);\n\t\t\t\t\t\t\t\tif (prevBeforeBlock != null && !isTerminalOrAlwaysTakeFlowControl(prevBeforeBlock.getOpcode()))\n\t\t\t\t\t\t\t\t\tbreak doInline;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Check if the current target instruction isn't the initial goto destination label,\n\t\t\t\t\t\t\t// and the current target is a label that has been visited more than once by control flow\n\t\t\t\t\t\t\t// originating from outside of this block.\n\t\t\t\t\t\t\tfor (AbstractInsnNode blockInsn : block) {\n\t\t\t\t\t\t\t\tint blockInsnOp = blockInsn.getOpcode();\n\t\t\t\t\t\t\t\tif (blockInsn != jin.label && blockInsnOp == -1\n\t\t\t\t\t\t\t\t\t\t&& blockInsn instanceof LabelNode blockInsnLabel\n\t\t\t\t\t\t\t\t\t\t&& visitCounters.of(blockInsnLabel).getExplicitFlowSourcesExcluding(block) > 1)\n\t\t\t\t\t\t\t\t\tbreak doInline;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Move forward.\n\t\t\t\t\t\ttarget = target.getNext();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Cut and paste those instructions into a temporary list.\n\t\t\t\t\tInsnList tempInsnList = new InsnList();\n\t\t\t\t\tfor (AbstractInsnNode blockInsn : block) {\n\t\t\t\t\t\tinstructions.remove(blockInsn);\n\t\t\t\t\t\ttempInsnList.add(blockInsn);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Insert the block after the GOTO, then remove the GOTO.\n\t\t\t\t\tinstructions.insert(jin, tempInsnList);\n\t\t\t\t\tinstructions.remove(jin);\n\t\t\t\t\tlocalDirty = true;\n\n\t\t\t\t\t// Since we removed the original goto instruction, remove it from the label's visit counter.\n\t\t\t\t\tvisitCounters.of(jin.label).removeExplicitSource(jin);\n\n\t\t\t\t\t// Start over from the beginning.\n\t\t\t\t\ti = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (localDirty) {\n\t\t\t\t// Do another dead code removal pass.\n\t\t\t\tcontext.pruneDeadCode(node, method);\n\n\t\t\t\t// Fix references to labels that no longer exist in local variable debug metadata.\n\t\t\t\tfixMissingVariableLabels(method);\n\t\t\t\tdirty = true;\n\n\t\t\t\t// Update the class with our transformed copy of the method.\n\t\t\t\tnode.methods.set(m, method);\n\t\t\t}\n\t\t}\n\t\tif (dirty) {\n\t\t\tcontext.setRecomputeFrames(className);\n\t\t\tcontext.setNode(bundle, initialClassState, node);\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<Class<? extends ClassTransformer>> dependencies() {\n\t\treturn Collections.singleton(DeadCodeRemovingTransformer.class);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Goto inlining\";\n\t}\n\n\t/**\n\t * @param base\n\t * \t\tMethod model to copy.\n\t *\n\t * @return Copy of method model.\n\t */\n\t@Nonnull\n\tprivate static MethodNode copyOf(@Nonnull MethodNode base) {\n\t\tMethodNode copy = new MethodNode(RecafConstants.getAsmVersion(), base.access, base.name, base.desc, base.signature, base.exceptions.toArray(String[]::new)) {\n\t\t\t@Override\n\t\t\tpublic void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) {\n\t\t\t\t// Skip frames\n\t\t\t}\n\t\t};\n\t\tbase.accept(copy);\n\t\treturn copy;\n\t}\n\n\t/**\n\t * @param op\n\t * \t\tInstruction opcode.\n\t *\n\t * @return {@code true} when the opcode represents an instruction\n\t * that should mark the end of a {@code GOTO} destination block.\n\t */\n\tprivate static boolean isGotoBlockTerminator(int op) {\n\t\treturn isReturn(op) || op == GOTO || op == TABLESWITCH || op == LOOKUPSWITCH || op == ATHROW;\n\t}\n\n\t/**\n\t * Map of {@link LabelNode} to visit/flow data.\n\t */\n\tprivate static class VisitCounters {\n\t\tprivate final Map<LabelNode, VisitCounter> visitCounters = new IdentityHashMap<>();\n\n\t\t@Nonnull\n\t\tpublic VisitCounter of(@Nonnull LabelNode label) {\n\t\t\treturn visitCounters.computeIfAbsent(label, VisitCounter::new);\n\t\t}\n\t}\n\n\t/**\n\t * Model of control flow interactions with a {@link LabelNode}.\n\t */\n\tprivate static class VisitCounter {\n\t\tprivate final LabelNode label;\n\t\tprivate final Set<AbstractInsnNode> explicitFlowSources = Collections.newSetFromMap(new IdentityHashMap<>());\n\t\tprivate boolean implicitFlow;\n\t\tprivate boolean tryTarget;\n\n\t\tprivate VisitCounter(@Nonnull LabelNode label) {\n\t\t\tthis.label = label;\n\t\t}\n\n\t\tpublic void addExplicitSource(@Nonnull AbstractInsnNode insn) {\n\t\t\texplicitFlowSources.add(insn);\n\t\t}\n\n\t\tpublic void removeExplicitSource(@Nonnull AbstractInsnNode insn) {\n\t\t\texplicitFlowSources.remove(insn);\n\t\t}\n\n\t\tpublic void markImplicitFlow() {\n\t\t\timplicitFlow = true;\n\t\t}\n\n\t\tpublic boolean isVisited() {\n\t\t\tif (!explicitFlowSources.isEmpty())\n\t\t\t\treturn true;\n\t\t\treturn implicitFlow || tryTarget;\n\t\t}\n\n\t\tpublic boolean isImplicitFlow() {\n\t\t\treturn implicitFlow;\n\t\t}\n\n\t\tpublic boolean isTryTarget() {\n\t\t\treturn tryTarget;\n\t\t}\n\n\t\tpublic long getExplicitFlowSourcesExcluding(@Nonnull Collection<AbstractInsnNode> block) {\n\t\t\treturn explicitFlowSources.stream()\n\t\t\t\t\t.filter(insn -> !block.contains(insn))\n\t\t\t\t\t.count();\n\t\t}\n\n\t\tpublic int count() {\n\t\t\treturn explicitFlowSources.size() + (implicitFlow ? 1 : 0) + (tryTarget ? 1 : 0);\n\t\t}\n\n\t\tpublic void markTryStart() {\n\t\t\ttryTarget = true;\n\t\t}\n\n\t\tpublic void markTryEnd() {\n\t\t\ttryTarget = true;\n\t\t}\n\n\t\tpublic void markTryHandler() {\n\t\t\ttryTarget = true;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \"VisitCounter{\" +\n\t\t\t\t\t\"label=\" + indexOf(label) +\n\t\t\t\t\t\", explicitFlowSources=\" + explicitFlowSources.size() +\n\t\t\t\t\t\", implicitFlow=\" + implicitFlow +\n\t\t\t\t\t\", tryTarget=\" + tryTarget +\n\t\t\t\t\t'}';\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/IllegalAnnotationRemovingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.visitors.IllegalAnnotationRemovingVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * A transformer that removes any invalid annotations from classes and any of their declared members.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class IllegalAnnotationRemovingTransformer implements JvmClassTransformer {\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\t// Adapt the class bytes by removing any illegal annotation.\n\t\tClassReader reader = new ClassReader(context.getBytecode(bundle, initialClassState));\n\t\tClassWriter writer = new ClassWriter(reader, 0);\n\n\t\tIllegalAnnotationRemovingVisitor remover = new IllegalAnnotationRemovingVisitor(writer);\n\t\treader.accept(remover, initialClassState.getClassReaderFlags());\n\n\t\t// If the visitor did work, update the class.\n\t\tif (remover.hasDetectedIllegalAnnotations())\n\t\t\tcontext.setBytecode(bundle, initialClassState, writer.toByteArray());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Illegal annotation removal\";\n\t}\n\n\t@Override\n\tpublic boolean pruneAfterNoWork() {\n\t\t// Other transformers should not introduce junk annotations,\n\t\t// so once the work is done there is no need to re-process classes on following passes.\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/IllegalNameMappingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeKeywordNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeNonAsciiNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeNonJavaIdentifierNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeWhitespaceNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.NameGeneratorFilter;\nimport software.coley.recaf.services.mapping.gen.naming.IncrementingNameGenerator;\nimport software.coley.recaf.services.mapping.gen.naming.NameGenerator;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * A transformer that renames classes and members that are not valid Java identifiers.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class IllegalNameMappingTransformer implements JvmClassTransformer {\n\tprivate static final NameGeneratorFilter ILLEGAL_NAME_FILTER =\n\t\t\tnew IncludeWhitespaceNameFilter(new IncludeNonAsciiNameFilter(new IncludeKeywordNameFilter(new IncludeNonJavaIdentifierNameFilter(null))));\n\tprivate static final NameGenerator NAME_GENERATOR = new IncrementingNameGenerator();\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\t// Create mappings for classes and members with illegal names.\n\t\t// Anything that already has a name in the current mappings will be ignored.\n\t\tIntermediateMappings mappings = context.getMappings();\n\t\tString ownerName = initialClassState.getName();\n\t\tif (ILLEGAL_NAME_FILTER.shouldMapClass(initialClassState) && mappings.getMappedClassName(ownerName) == null)\n\t\t\tmappings.addClass(ownerName, NAME_GENERATOR.mapClass(initialClassState));\n\t\tfor (FieldMember field : initialClassState.getFields()) {\n\t\t\tString fieldDesc = field.getDescriptor();\n\t\t\tString fieldName = field.getName();\n\t\t\tif (ILLEGAL_NAME_FILTER.shouldMapField(initialClassState, field) && mappings.getMappedFieldName(ownerName, fieldDesc, fieldName) == null)\n\t\t\t\tmappings.addField(ownerName, fieldDesc, field.getName(), NAME_GENERATOR.mapField(initialClassState, field));\n\t\t}\n\t\tfor (MethodMember method : initialClassState.getMethods()) {\n\t\t\tString methodDesc = method.getDescriptor();\n\t\t\tString methodName = method.getName();\n\t\t\tif (ILLEGAL_NAME_FILTER.shouldMapMethod(initialClassState, method) && mappings.getMappedMethodName(ownerName, methodDesc, methodName) == null)\n\t\t\t\tmappings.addMethod(ownerName, methodDesc, method.getName(), NAME_GENERATOR.mapMethod(initialClassState, method));\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Illegal name mapping\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/IllegalSignatureRemovingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.visitors.IllegalSignatureRemovingVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * A transformer that removes any invalid signatures from classes and any of their declared members.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class IllegalSignatureRemovingTransformer implements JvmClassTransformer {\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\t// Adapt the class bytes by removing any illegal signature.\n\t\tClassReader reader = new ClassReader(context.getBytecode(bundle, initialClassState));\n\t\tClassWriter writer = new ClassWriter(reader, 0);\n\n\t\tIllegalSignatureRemovingVisitor remover = new IllegalSignatureRemovingVisitor(writer);\n\t\treader.accept(remover, initialClassState.getClassReaderFlags());\n\n\t\t// If the visitor did work, update the class.\n\t\tif (remover.hasDetectedIllegalSignatures())\n\t\t\tcontext.setBytecode(bundle, initialClassState, writer.toByteArray());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Illegal signature removal\";\n\t}\n\n\t@Override\n\tpublic boolean pruneAfterNoWork() {\n\t\t// Other transformers should not introduce junk signatures,\n\t\t// so once the work is done there is no need to re-process classes on following passes.\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/IllegalVarargsRemovingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.visitors.IllegalVarargsRemovingVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * A transformer that removes any invalid use of varargs from methods.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class IllegalVarargsRemovingTransformer implements JvmClassTransformer {\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\t// First scan the model to see if we need to actually reparse and patch the bytecode.\n\t\tboolean hasInvalidVarargs = false;\n\t\tfor (MethodMember method : initialClassState.getMethods()) {\n\t\t\tif (method.hasVarargsModifier()) {\n\t\t\t\tType methodType = Type.getMethodType(method.getDescriptor());\n\t\t\t\tType[] argumentTypes = methodType.getArgumentTypes();\n\t\t\t\tif (argumentTypes.length == 0 || argumentTypes[argumentTypes.length - 1].getSort() != Type.ARRAY) {\n\t\t\t\t\thasInvalidVarargs = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If we found an invalid use case, we'll do the work to remove it.\n\t\tif (hasInvalidVarargs) {\n\t\t\tClassReader reader = new ClassReader(context.getBytecode(bundle, initialClassState));\n\t\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\t\tIllegalVarargsRemovingVisitor remover = new IllegalVarargsRemovingVisitor(writer);\n\t\t\treader.accept(remover, initialClassState.getClassReaderFlags());\n\t\t\tif (remover.hasDetectedIllegalVarargs()) // Should always occur given the circumstances\n\t\t\t\tcontext.setBytecode(bundle, initialClassState, writer.toByteArray());\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Illegal varargs removal\";\n\t}\n\n\t@Override\n\tpublic boolean pruneAfterNoWork() {\n\t\t// Other transformers should not introduce junk varargs,\n\t\t// so once the work is done there is no need to re-process classes on following passes.\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/KotlinMetadataCollectionTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.Type;\nimport regexodus.Pattern;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.mapping.aggregate.AggregatedMappings;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.util.kotlin.KotlinMetadata;\nimport software.coley.recaf.util.kotlin.model.KtClass;\nimport software.coley.recaf.util.kotlin.model.KtFunction;\nimport software.coley.recaf.util.kotlin.model.KtProperty;\nimport software.coley.recaf.util.kotlin.model.KtType;\nimport software.coley.recaf.util.kotlin.model.KtVariable;\nimport software.coley.recaf.util.visitors.SkippingClassVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.function.Function;\n\n/**\n * A transformer that collects kotlin metadata and offers utilities to process it.\n * <p>\n * For deobfuscating descriptors it is recommended to use the following methods in order:\n * <ol>\n *     <li>{@link #mapKtDescriptor(KtType)} / {@link #mapKtDescriptor(KtFunction)}</li>\n *     <li>{@link #mapToJvm(String)}</li>\n *     <li>{@link #reverseMapDescriptor(String)}</li>\n * </ol>\n *\n * @author Matt Coley\n */\n@Dependent\npublic class KotlinMetadataCollectionTransformer implements JvmClassTransformer {\n\tprivate final Map<String, KtClass> kotlinClassModels = new HashMap<>();\n\tprivate AggregatedMappings kotlinClassMappings;\n\n\t@Override\n\tpublic void setup(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace) {\n\t\tkotlinClassMappings = new AggregatedMappings(workspace);\n\t}\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tString ownerName = initialClassState.getName();\n\n\t\t// Extract metadata\n\t\tKtClass ktClass = KotlinMetadata.extractKtModel(initialClassState);\n\t\tif (ktClass == null) {\n\t\t\t// Check if there is @JvmName as a fallback. The annotation is similar to SourceFileAttribute.\n\t\t\tinitialClassState.getClassReader().accept(new SkippingClassVisitor() {\n\t\t\t\t@Override\n\t\t\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\t\t\tif (!descriptor.equals(\"Lkotlin/jvm/JvmName;\"))\n\t\t\t\t\t\treturn null;\n\t\t\t\t\treturn new AnnotationVisitor(RecafConstants.getAsmVersion()) {\n\t\t\t\t\t\tprivate static final Pattern NAME_PATTERN = RegexUtil.pattern(\"\\\\w{1, 50}\");\n\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tpublic void visit(String name, Object value) {\n\t\t\t\t\t\t\tif (\"name\".equals(name) && value instanceof String sourceName && NAME_PATTERN.matches(sourceName)) {\n\t\t\t\t\t\t\t\tkotlinClassMappings.addClass(ownerName, initialClassState.getPackageName() + '/' + sourceName);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE);\n\t\t\treturn;\n\t\t}\n\n\t\t// The @MetaData class name is the full class descriptor. Add it as-is to the mappings.\n\t\tif (ktClass.getName() != null)\n\t\t\tkotlinClassMappings.addClass(ownerName, ktClass.getName());\n\t\tkotlinClassModels.put(ownerName, ktClass);\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass to find matching field within.\n\t * @param property\n\t * \t\tKotlin property model.\n\t *\n\t * @return Matching field for the property model.\n\t */\n\t@Nullable\n\tpublic FieldMember getField(@Nonnull ClassInfo info, @Nonnull KtProperty property) {\n\t\tString descriptor = mapKtDescriptor(property.getType());\n\t\tList<FieldMember> candidates = getCandidates(descriptor, info, ClassInfo::getFields);\n\n\t\t// No candidates found\n\t\tif (candidates.isEmpty())\n\t\t\treturn null;\n\n\t\t// Sole candidate found\n\t\tif (candidates.size() == 1)\n\t\t\treturn candidates.getFirst();\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass to find matching field within.\n\t * @param property\n\t * \t\tKotlin property model.\n\t *\n\t * @return Matching getter method for the property model.\n\t */\n\t@Nullable\n\tpublic MethodMember getFieldGetter(@Nonnull ClassInfo info, @Nonnull KtProperty property) {\n\t\tString descriptor = \"()\" + mapKtDescriptor(property.getType());\n\t\tList<MethodMember> candidates = getCandidates(descriptor, info, ClassInfo::getMethods);\n\n\t\t// No candidates found\n\t\tif (candidates.isEmpty())\n\t\t\treturn null;\n\n\t\t// Sole candidate found\n\t\tif (candidates.size() == 1)\n\t\t\treturn candidates.getFirst();\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass to find matching field within.\n\t * @param function\n\t * \t\tKotlin function model.\n\t *\n\t * @return Matching method for the function model.\n\t */\n\t@Nullable\n\tpublic MethodMember getMethod(@Nonnull ClassInfo info, @Nonnull KtFunction function) {\n\t\tString descriptor = mapKtDescriptor(function);\n\t\tList<MethodMember> candidates = getCandidates(descriptor, info, ClassInfo::getMethods);\n\n\t\t// No candidates found\n\t\tif (candidates.isEmpty())\n\t\t\treturn null;\n\n\t\t// Sole candidate found\n\t\tif (candidates.size() == 1)\n\t\t\treturn candidates.getFirst();\n\n\t\treturn null;\n\t}\n\n\t@Nonnull\n\tprivate <T extends ClassMember> List<T> getCandidates(@Nonnull String descriptor, @Nonnull ClassInfo info,\n\t                                                      @Nonnull Function<ClassInfo, Iterable<T>> memberLookup) {\n\t\tString descriptorMapped1 = reverseMapDescriptor(descriptor);\n\t\tString descriptorMapped2 = mapToJvm(descriptorMapped1);\n\n\t\t// Count candidates (members matching mapped descriptor)\n\t\tIterable<T> members = memberLookup.apply(info);\n\t\tList<T> candidates = null;\n\t\tfor (T member : members) {\n\t\t\tif (member.getName().charAt(0) == '<')\n\t\t\t\tcontinue;\n\t\t\tString memberDescriptor = member.getDescriptor();\n\t\t\tif (memberDescriptor.equals(descriptorMapped1) || memberDescriptor.equals(descriptorMapped2)) {\n\t\t\t\tif (candidates == null)\n\t\t\t\t\tcandidates = new ArrayList<>(4);\n\t\t\t\tcandidates.add(member);\n\t\t\t}\n\t\t}\n\n\t\treturn candidates == null ? Collections.emptyList() : candidates;\n\t}\n\n\t/**\n\t * Say you have some obfuscated {@code class c {...}} that has a {@code @Metadata} that\n\t * tells you {@code \"c\" == \"FooService\"}. This maps {@code c} in descriptors to {@code FooService}.\n\t *\n\t * @param descriptor\n\t * \t\tSome descriptor.\n\t *\n\t * @return Descriptor with reverse mappings applied from the collected kotlin metadata.\n\t */\n\t@Nonnull\n\tpublic String reverseMapDescriptor(@Nonnull String descriptor) {\n\t\treturn Objects.requireNonNull(kotlinClassMappings.applyReverseMappings(descriptor));\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tClass name.\n\t *\n\t * @return Kotlin class metadata for the class, if found.\n\t */\n\t@Nullable\n\tpublic KtClass getKtClass(@Nonnull String name) {\n\t\treturn kotlinClassModels.get(name);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tClass name.\n\t *\n\t * @return Kotlin class name within the metadata, if found.\n\t */\n\t@Nullable\n\tpublic String getKtFallbackMapping(@Nonnull String name) {\n\t\treturn kotlinClassMappings.getMappedClassName(name);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Kotlin metadata collection\";\n\t}\n\n\t/**\n\t * @param function\n\t * \t\tKotlin function model.\n\t *\n\t * @return Descriptor of the function model.\n\t *\n\t * @see #reverseMapDescriptor(String)\n\t */\n\t@Nonnull\n\tpublic static String mapKtDescriptor(@Nonnull KtFunction function) {\n\t\tStringBuilder sb = new StringBuilder(\"(\");\n\t\tfor (KtVariable parameter : function.getParameters())\n\t\t\tsb.append(mapKtDescriptor(parameter.getType()));\n\t\tsb.append(')').append(mapKtDescriptor(function.getReturnType()));\n\t\treturn sb.toString();\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tKotlin type model.\n\t *\n\t * @return Descriptor of the type model.\n\t *\n\t * @see #reverseMapDescriptor(String)\n\t */\n\t@Nonnull\n\tpublic static String mapKtDescriptor(@Nullable KtType type) {\n\t\tString descriptor = KtType.toDescriptor(type);\n\n\t\t// Special case handling for types that require knowledge of type arguments to map.\n\t\t// Any other cases will be handled later via 'mapToJvm'.\n\t\tif (descriptor.equals(\"Lkotlin/Array;\")) {\n\t\t\tList<KtType> arguments = Objects.requireNonNull(type).getArguments();\n\t\t\tif (arguments != null) {\n\t\t\t\tdescriptor = \"[\" + mapKtDescriptor(arguments.getFirst());\n\t\t\t}\n\t\t}\n\n\t\treturn descriptor;\n\t}\n\n\t/**\n\t * @param kotlinDescriptor\n\t * \t\tDescriptor containing Kotlin std-lib types.\n\t *\n\t * @return Descriptor with known std-lib type substitutions.\n\t *\n\t * @see #reverseMapDescriptor(String)\n\t */\n\t@Nonnull\n\tpublic static String mapToJvm(@Nonnull String kotlinDescriptor) {\n\t\tif (kotlinDescriptor.charAt(0) == '(') {\n\t\t\tType methodType = Type.getMethodType(kotlinDescriptor);\n\t\t\tStringBuilder sb = new StringBuilder(\"(\");\n\t\t\tfor (Type arg : methodType.getArgumentTypes())\n\t\t\t\tsb.append(mapToJvm(arg.getDescriptor()));\n\t\t\tsb.append(')').append(mapToJvm(methodType.getReturnType().getDescriptor()));\n\t\t\treturn sb.toString();\n\t\t}\n\t\treturn switch (kotlinDescriptor) {\n\t\t\tcase \"Lkotlin/Boolean;\" -> \"Z\";\n\t\t\tcase \"Lkotlin/BooleanArray;\" -> \"[Z\";\n\t\t\tcase \"Lkotlin/Byte;\" -> \"B\";\n\t\t\tcase \"Lkotlin/ByteArray;\" -> \"[B\";\n\t\t\tcase \"Lkotlin/UByte;\",\n\t\t\t\t\t\"Lkotlin/Int;\",\n\t\t\t\t\t\"Lkotlin/UInt;\",\n\t\t\t\t\t\"Lkotlin/UShort;\" -> \"I\";\n\t\t\tcase \"Lkotlin/UByteArray;\",\n\t\t\t\t\t\"Lkotlin/IntArray;\",\n\t\t\t\t\t\"Lkotlin/UIntArray;\",\n\t\t\t\t\t\"Lkotlin/UShortArray;\" -> \"[I\";\n\t\t\tcase \"Lkotlin/Char;\" -> \"C\";\n\t\t\tcase \"Lkotlin/CharArray;\" -> \"[C\";\n\t\t\tcase \"Lkotlin/Double;\" -> \"D\";\n\t\t\tcase \"Lkotlin/DoubleArray;\" -> \"[D\";\n\t\t\tcase \"Lkotlin/Float;\" -> \"F\";\n\t\t\tcase \"Lkotlin/FloatArray;\" -> \"[F\";\n\t\t\tcase \"Lkotlin/Long;\", \"Lkotlin/ULong;\" -> \"J\";\n\t\t\tcase \"Lkotlin/LongArray;\", \"Lkotlin/ULongArray;\" -> \"[J\";\n\t\t\tcase \"Lkotlin/Short;\" -> \"S\";\n\t\t\tcase \"Lkotlin/ShortArray;\" -> \"[S\";\n\t\t\tcase \"Lkotlin/Unit;\" -> \"V\";\n\t\t\tcase \"Lkotlin/Any;\" -> \"Ljava/lang/Object;\"; // This one isn't a 1-to-1...\n\t\t\tcase \"Lkotlin/String;\" -> \"Ljava/lang/String;\";\n\t\t\tcase \"Lkotlin/Enum;\" -> \"Ljava/lang/Enum;\";\n\t\t\tcase \"Lkotlin/Comparable;\" -> \"Ljava/lang/Comparable;\";\n\t\t\tcase \"Lkotlin/Comparator;\" -> \"Ljava/lang/Comparator;\";\n\n\t\t\t// Reflection\n\t\t\t// Some things are inconsistently mapped (like KFunction1/KMutableProperty1), so we can't operate on those.\n\t\t\tcase \"Lkotlin/reflect/KClass;\" -> \"Ljava/lang/Class;\";\n\n\t\t\t// Some collections are directly mapped to Java's\n\t\t\tcase \"Lkotlin/collections/Collection;\" -> \"Ljava/util/Collection;\";\n\t\t\tcase \"Lkotlin/collections/Iterable;\" -> \"Ljava/util/Iterable;\";\n\t\t\tcase \"Lkotlin/collections/Iterator;\", \"Lkotlin/collections/MutableIterator;\" -> \"Ljava/util/Iterator;\";\n\t\t\tcase \"Lkotlin/collections/ListIterator;\",\n\t\t\t\t\t\"Lkotlin/collections/MutableListIterator;\" -> \"Ljava/util/ListIterator;\";\n\t\t\tcase \"Lkotlin/collections/List;\", \"Lkotlin/collections/MutableList;\" -> \"Ljava/util/List;\";\n\t\t\tcase \"Lkotlin/collections/Set;\", \"Lkotlin/collections/MutableSet;\" -> \"Ljava/util/Set;\";\n\t\t\tcase \"Lkotlin/collections/Map;\", \"Lkotlin/collections/MutableMap;\" -> \"Ljava/util/Map;\";\n\n\t\t\t// Some function types get migrated\n\t\t\tcase \"Lkotlin/Function0;\" -> \"Lkotlin/jvm/functions/Function0;\";\n\t\t\tcase \"Lkotlin/Function1;\" -> \"Lkotlin/jvm/functions/Function1;\";\n\t\t\tcase \"Lkotlin/Function2;\" -> \"Lkotlin/jvm/functions/Function2;\";\n\t\t\tcase \"Lkotlin/Function3;\" -> \"Lkotlin/jvm/functions/Function3;\";\n\t\t\tcase \"Lkotlin/Function4;\" -> \"Lkotlin/jvm/functions/Function4;\";\n\t\t\tcase \"Lkotlin/Function5;\" -> \"Lkotlin/jvm/functions/Function5;\";\n\t\t\tcase \"Lkotlin/Function6;\" -> \"Lkotlin/jvm/functions/Function6;\";\n\t\t\tcase \"Lkotlin/Function7;\" -> \"Lkotlin/jvm/functions/Function7;\";\n\t\t\tcase \"Lkotlin/Function8;\" -> \"Lkotlin/jvm/functions/Function8;\";\n\t\t\tcase \"Lkotlin/Function9;\" -> \"Lkotlin/jvm/functions/Function9;\";\n\t\t\tcase \"Lkotlin/Function10;\" -> \"Lkotlin/jvm/functions/Function10;\";\n\t\t\tcase \"Lkotlin/FunctionN;\" -> \"Lkotlin/jvm/functions/FunctionN;\";\n\t\t\tdefault -> kotlinDescriptor;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/KotlinNameRestorationTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.mapping.aggregate.AggregatedMappings;\nimport software.coley.recaf.services.transform.ClassTransformer;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.kotlin.model.KtClass;\nimport software.coley.recaf.util.kotlin.model.KtFunction;\nimport software.coley.recaf.util.kotlin.model.KtProperty;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collections;\nimport java.util.Set;\n\n/**\n * A transformer that renames classes and members based on Kotlin metadata.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class KotlinNameRestorationTransformer implements JvmClassTransformer {\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tAggregatedMappings mappings = context.getMappings();\n\t\tString ownerName = initialClassState.getName();\n\t\tKotlinMetadataCollectionTransformer metadata = context.getJvmTransformer(KotlinMetadataCollectionTransformer.class);\n\t\tKtClass ktClass = metadata.getKtClass(ownerName);\n\t\tif (ktClass == null) {\n\t\t\t// No metadata model, but see if we were able to extract a name from some other kind of data from\n\t\t\t// our collection transformation pass.\n\t\t\tString mappedOwner = metadata.getKtFallbackMapping(ownerName);\n\t\t\tif (mappedOwner != null)\n\t\t\t\tmappings.addClass(ownerName, mappedOwner);\n\t\t\treturn;\n\t\t}\n\n\t\t// Sadly, the kotlin meta-data is unordered, so we can only be sure about name mappings\n\t\t// when there are only EXACT descriptor matches.\n\t\tif (ktClass.getName() != null)\n\t\t\tmappings.addClass(ownerName, ktClass.getName());\n\t\tfor (KtProperty property : ktClass.getProperties()) {\n\t\t\tString propertyName = property.getName();\n\t\t\tif (propertyName == null)\n\t\t\t\tcontinue;\n\n\t\t\t// Check for field match\n\t\t\tFieldMember field = metadata.getField(initialClassState, property);\n\t\t\tif (field != null)\n\t\t\t\tmappings.addField(ownerName, field.getDescriptor(), field.getName(), propertyName);\n\n\t\t\t// Check for getter match\n\t\t\tMethodMember method = metadata.getFieldGetter(initialClassState, property);\n\t\t\tif (method != null) {\n\t\t\t\tif (method.getName().equals(propertyName))\n\t\t\t\t\tcontinue;\n\t\t\t\tif (!propertyName.startsWith(\"get\") && !propertyName.startsWith(\"is\") && !propertyName.startsWith(\"do\"))\n\t\t\t\t\tpropertyName = \"get\" + StringUtil.uppercaseFirstChar(propertyName);\n\t\t\t\tmappings.addMethod(ownerName, method.getDescriptor(), method.getName(), propertyName);\n\t\t\t}\n\t\t}\n\t\tfor (KtFunction function : ktClass.getFunctions()) {\n\t\t\tif (function.getName() == null)\n\t\t\t\tcontinue;\n\n\t\t\t// Check for method match\n\t\t\tMethodMember method = metadata.getMethod(initialClassState, function);\n\t\t\tif (method != null)\n\t\t\t\tmappings.addMethod(ownerName, method.getDescriptor(), method.getName(), function.getName());\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Kotlin name restoration\";\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<Class<? extends ClassTransformer>> dependencies() {\n\t\treturn Collections.singleton(KotlinMetadataCollectionTransformer.class);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/LongAnnotationRemovingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.visitors.LongAnnotationRemovingVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * A transformer that removes any invalid annotations from classes and any of their declared members.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class LongAnnotationRemovingTransformer implements JvmClassTransformer {\n\tprivate static final int LONG_ANNO = 150;\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\t// Adapt the class bytes by removing any stupidly long annotation.\n\t\tClassReader reader = new ClassReader(context.getBytecode(bundle, initialClassState));\n\t\tClassWriter writer = new ClassWriter(reader, 0);\n\n\t\tLongAnnotationRemovingVisitor remover = new LongAnnotationRemovingVisitor(writer, LONG_ANNO);\n\t\treader.accept(remover, initialClassState.getClassReaderFlags());\n\n\t\t// If the visitor did work, update the class.\n\t\tif (remover.hasDetectedLongAnnotations())\n\t\t\tcontext.setBytecode(bundle, initialClassState, writer.toByteArray());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Long annotation removal\";\n\t}\n\n\t@Override\n\tpublic boolean pruneAfterNoWork() {\n\t\t// Other transformers should not introduce junk/long annotations,\n\t\t// so once the work is done there is no need to re-process classes on following passes.\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/LongExceptionRemovingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.visitors.LongExceptionRemovingVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * A transformer that removes any long/annoying exceptions from methods.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class LongExceptionRemovingTransformer implements JvmClassTransformer {\n\tprivate static final int LONG_EXCEPTION = 150;\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\t// Adapt the class bytes by removing any stupidly long annotation.\n\t\t// - Do not pass the reader as a writer parameter, MethodWriter.canCopyMethodAttributes breaks this\n\t\tClassReader reader = new ClassReader(context.getBytecode(bundle, initialClassState));\n\t\tClassWriter writer = new ClassWriter(0);\n\n\t\tLongExceptionRemovingVisitor remover = new LongExceptionRemovingVisitor(writer, LONG_EXCEPTION);\n\t\treader.accept(remover, initialClassState.getClassReaderFlags());\n\n\t\t// If the visitor did work, update the class.\n\t\tif (remover.hasDetectedLongExceptions())\n\t\t\tcontext.setBytecode(bundle, initialClassState, writer.toByteArray());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Long exception removal\";\n\t}\n\n\t@Override\n\tpublic boolean pruneAfterNoWork() {\n\t\t// Other transformers should not introduce junk/long exceptions,\n\t\t// so once the work is done there is no need to re-process classes on following passes.\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/OpaqueConstantFoldingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.IincInsnNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.InsnNode;\nimport org.objectweb.asm.tree.LabelNode;\nimport org.objectweb.asm.tree.LdcInsnNode;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.VarInsnNode;\nimport org.objectweb.asm.tree.analysis.Frame;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.transform.ClassTransformer;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.BlwUtil;\nimport software.coley.recaf.util.analysis.ReFrame;\nimport software.coley.recaf.util.analysis.eval.EvaluationResult;\nimport software.coley.recaf.util.analysis.eval.EvaluationYieldResult;\nimport software.coley.recaf.util.analysis.eval.Evaluator;\nimport software.coley.recaf.util.analysis.eval.FieldCacheManager;\nimport software.coley.recaf.util.analysis.value.DoubleValue;\nimport software.coley.recaf.util.analysis.value.FloatValue;\nimport software.coley.recaf.util.analysis.value.IntValue;\nimport software.coley.recaf.util.analysis.value.LongValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.StringValue;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport static org.objectweb.asm.Opcodes.*;\nimport static software.coley.recaf.util.AsmInsnUtil.*;\n\n\n/**\n * A transformer that folds sequences of computed values into constants.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class OpaqueConstantFoldingTransformer implements JvmClassTransformer {\n\tprivate static final int[] ARG_1_SIZE = new int[255];\n\tprivate static final int[] ARG_2_SIZE = new int[255];\n\tprivate final InheritanceGraphService graphService;\n\tprivate InheritanceGraph inheritanceGraph;\n\n\t@Inject\n\tpublic OpaqueConstantFoldingTransformer(@Nonnull InheritanceGraphService graphService) {\n\t\tthis.graphService = graphService;\n\t}\n\n\t@Override\n\tpublic void setup(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace) {\n\t\tinheritanceGraph = graphService.getOrCreateInheritanceGraph(workspace);\n\t}\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tboolean dirty = false;\n\t\tString className = initialClassState.getName();\n\t\tClassNode node = context.getNode(bundle, initialClassState);\n\t\tfor (MethodNode method : node.methods) {\n\t\t\tInsnList instructions = method.instructions;\n\t\t\tif (instructions == null)\n\t\t\t\tcontinue;\n\t\t\ttry {\n\t\t\t\t// This transformer runs in two passes.\n\t\t\t\t//\n\t\t\t\t// The first pass inlines various stack operations like DUP/SWAP/DUP_X.\n\t\t\t\t// By simplifying the stack we can mitigate some really annoying edge cases in the second pass.\n\t\t\t\t//\n\t\t\t\t// The second pass iteratively steps forwards and finds \"operations\" that act on stack values.\n\t\t\t\t// Where possible, if the result of the operation is known it will replace the operation instruction\n\t\t\t\t// and as many of the contributing instructions to it as possible.\n\t\t\t\t//\n\t\t\t\t// Any time either pass makes changes, it will generally replace instructions with NOP.\n\t\t\t\t// This is generally done over immediately removing them to reduce the risk of indexing errors.\n\t\t\t\tdirty |= pass1StackManipulation(context, node, method, instructions);\n\t\t\t\tdirty |= pass2SequenceFolding(context, node, method, instructions);\n\n\t\t\t\t// Now that we are done, we'll prune any NOP instructions if we made changes.\n\t\t\t\tif (dirty) {\n\t\t\t\t\tfor (AbstractInsnNode insn : instructions.toArray()) {\n\t\t\t\t\t\tif (insn.getOpcode() == NOP)\n\t\t\t\t\t\t\tinstructions.remove(insn);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (Throwable t) {\n\t\t\t\tthrow new TransformationException(\"Error encountered when folding constants\", t);\n\t\t\t}\n\t\t}\n\t\tif (dirty)\n\t\t\tcontext.setNode(bundle, initialClassState, node);\n\t}\n\n\t/**\n\t * Replaces stack manipulation instructions in the method. This may involve shifting the order of instructions\n\t * or placing copies of existing instructions in specific locations to achieve desired effects for operations\n\t * like {@code dup_x} instructions.\n\t *\n\t * @param context\n\t * \t\tTransformation context used to analyze methods for stack values.\n\t * @param node\n\t * \t\tClass defining the method.\n\t * @param method\n\t * \t\tThe method to transform.\n\t * @param instructions\n\t * \t\tThe instructions of the method.\n\t *\n\t * @return {@code true} when any stack operation was transformed.\n\t *\n\t * @throws TransformationException\n\t * \t\tWhen the method code couldn't be analyzed.\n\t */\n\tprivate boolean pass1StackManipulation(@Nonnull JvmTransformerContext context, @Nonnull ClassNode node,\n\t                                       @Nonnull MethodNode method, @Nonnull InsnList instructions) throws TransformationException {\n\t\tboolean dirty = false;\n\t\tint insertions = 0;\n\t\tFrame<ReValue>[] frames = context.analyze(inheritanceGraph, node, method);\n\t\tfor (int i = 1; i < instructions.size() - 1; i++) {\n\t\t\tFrame<ReValue> frame = frames[i - insertions];\n\t\t\tif (frame == null || frame.getStackSize() == 0)\n\t\t\t\tcontinue;\n\n\t\t\tAbstractInsnNode instruction = instructions.get(i);\n\t\t\tint opcode = instruction.getOpcode();\n\t\t\tswitch (opcode) {\n\t\t\t\tcase DUP -> {\n\t\t\t\t\tif (getSlotsOccupied(frame) < 1)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tReValue top = frame.getStack(frame.getStackSize() - 1);\n\t\t\t\t\tAbstractInsnNode replacement = toInsn(top);\n\t\t\t\t\tif (replacement != null) {\n\t\t\t\t\t\tinstructions.set(instruction, replacement);\n\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t} else if (isSupportedValueProducer(instruction.getPrevious())) {\n\t\t\t\t\t\tinstructions.set(instruction, instruction.getPrevious().clone(Collections.emptyMap()));\n\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcase DUP2 -> {\n\t\t\t\t\tif (getSlotsOccupied(frame) < 2)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tBinaryOperationArguments arguments = getBinaryOperationArguments(instruction.getPrevious(), DUP2);\n\t\t\t\t\tif (arguments != null && arguments.combinedIntermediates().isEmpty()) {\n\t\t\t\t\t\tif (arguments.argument2().sameAs(arguments.argument1())) {\n\t\t\t\t\t\t\t// Arguments are same since input is wide (long/double)\n\t\t\t\t\t\t\tAbstractInsnNode arg = arguments.argument2().insn();\n\t\t\t\t\t\t\tif (isSupportedValueProducer(arg)) {\n\t\t\t\t\t\t\t\tinstructions.set(instruction, arg.clone(Collections.emptyMap()));\n\t\t\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Two separate arguments\n\t\t\t\t\t\t\tAbstractInsnNode arg1 = arguments.argument1().insn();\n\t\t\t\t\t\t\tAbstractInsnNode arg2 = arguments.argument2().insn();\n\t\t\t\t\t\t\tinstructions.insert(instruction, arg2.clone(Collections.emptyMap()));\n\t\t\t\t\t\t\tinstructions.set(instruction, arg1.clone(Collections.emptyMap()));\n\t\t\t\t\t\t\tinsertions += 1;\n\t\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcase DUP_X1 -> {\n\t\t\t\t\tif (getSlotsOccupied(frame) < 2)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tBinaryOperationArguments arguments = getBinaryOperationArguments(instruction.getPrevious(), DUP2_X1);\n\t\t\t\t\tif (arguments != null && arguments.combinedIntermediates().isEmpty()) {\n\t\t\t\t\t\tinstructions.insertBefore(arguments.argument1().insn(), arguments.argument2().insn().clone(Collections.emptyMap()));\n\t\t\t\t\t\tinstructions.set(instruction, new InsnNode(NOP));\n\t\t\t\t\t\tinsertions += 1;\n\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcase DUP_X2 -> {\n\t\t\t\t\tif (getSlotsOccupied(frame) < 3)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tBinaryOperationArguments arguments = getBinaryOperationArguments(instruction.getPrevious(), DUP2_X1);\n\t\t\t\t\tif (arguments != null && arguments.combinedIntermediates().isEmpty()) {\n\t\t\t\t\t\tArgument prior = collectArgument(arguments.argument1().insn().getPrevious());\n\t\t\t\t\t\tif (prior == null)\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\tinstructions.insertBefore(prior.insn(), arguments.argument2().insn().clone(Collections.emptyMap()));\n\t\t\t\t\t\tinstructions.set(instruction, new InsnNode(NOP));\n\t\t\t\t\t\tinsertions += 1;\n\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcase DUP2_X1 -> {\n\t\t\t\t\tif (getSlotsOccupied(frame) < 2)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tBinaryOperationArguments arguments = getBinaryOperationArguments(instruction.getPrevious(), DUP2_X1);\n\t\t\t\t\tif (arguments == null || !arguments.combinedIntermediates().isEmpty())\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tArgument prior = collectArgument(arguments.argument1().insn().getPrevious());\n\t\t\t\t\tif (prior == null)\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\tAbstractInsnNode target = prior.insn();\n\t\t\t\t\tif (arguments.argument2().sameAs(arguments.argument1())) {\n\t\t\t\t\t\t// Arguments are same since input is wide (long/double)\n\t\t\t\t\t\tAbstractInsnNode arg = arguments.argument2().insn();\n\t\t\t\t\t\tif (isSupportedValueProducer(arg)) {\n\t\t\t\t\t\t\tinstructions.insertBefore(target, arg.clone(Collections.emptyMap()));\n\t\t\t\t\t\t\tinstructions.set(instruction, new InsnNode(NOP));\n\t\t\t\t\t\t\tinsertions += 1;\n\t\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Two separate arguments\n\t\t\t\t\t\tAbstractInsnNode arg1 = arguments.argument1().insn();\n\t\t\t\t\t\tAbstractInsnNode arg2 = arguments.argument2().insn();\n\t\t\t\t\t\tinstructions.insertBefore(target, arg1.clone(Collections.emptyMap()));\n\t\t\t\t\t\tinstructions.insertBefore(target, arg2.clone(Collections.emptyMap()));\n\t\t\t\t\t\tinstructions.set(instruction, new InsnNode(NOP));\n\t\t\t\t\t\tinsertions += 2;\n\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcase DUP2_X2 -> {\n\t\t\t\t\tif (getSlotsOccupied(frame) < 2)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tBinaryOperationArguments arguments = getBinaryOperationArguments(instruction.getPrevious(), DUP2_X1);\n\t\t\t\t\tif (arguments == null || !arguments.combinedIntermediates().isEmpty())\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tBinaryOperationArguments prior = getBinaryOperationArguments(arguments.argument1().insn().getPrevious(), DUP2_X1);\n\t\t\t\t\tif (prior == null || !prior.combinedIntermediates().isEmpty())\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\tAbstractInsnNode target = prior.argument1().insn();\n\t\t\t\t\tif (arguments.argument2().sameAs(arguments.argument1())) {\n\t\t\t\t\t\t// Arguments are same since input is wide (long/double)\n\t\t\t\t\t\tAbstractInsnNode arg = arguments.argument2().insn();\n\t\t\t\t\t\tif (isSupportedValueProducer(arg)) {\n\t\t\t\t\t\t\tinstructions.insertBefore(target, arg.clone(Collections.emptyMap()));\n\t\t\t\t\t\t\tinstructions.set(instruction, new InsnNode(NOP));\n\t\t\t\t\t\t\tinsertions += 1;\n\t\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Two separate arguments\n\t\t\t\t\t\tAbstractInsnNode arg1 = arguments.argument1().insn();\n\t\t\t\t\t\tAbstractInsnNode arg2 = arguments.argument2().insn();\n\t\t\t\t\t\tinstructions.insertBefore(target, arg1.clone(Collections.emptyMap()));\n\t\t\t\t\t\tinstructions.insertBefore(target, arg2.clone(Collections.emptyMap()));\n\t\t\t\t\t\tinstructions.set(instruction, new InsnNode(NOP));\n\t\t\t\t\t\tinsertions += 2;\n\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcase POP -> {\n\t\t\t\t\tif (getSlotsOccupied(frame) < 1)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tArgument argument = collectArgument(instruction.getPrevious());\n\t\t\t\t\tif (argument != null && argument.intermediates().isEmpty()) {\n\t\t\t\t\t\tinstructions.set(instruction, new InsnNode(NOP));\n\t\t\t\t\t\targument.replaceInsn(instructions);\n\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcase POP2 -> {\n\t\t\t\t\tif (getSlotsOccupied(frame) < 2)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tBinaryOperationArguments arguments = getBinaryOperationArguments(instruction.getPrevious(), POP2);\n\t\t\t\t\tif (arguments != null && arguments.combinedIntermediates().isEmpty()) {\n\t\t\t\t\t\tinstructions.set(instruction, new InsnNode(NOP));\n\t\t\t\t\t\targuments.replaceBinOp(instructions);\n\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcase SWAP -> {\n\t\t\t\t\tif (getSlotsOccupied(frame) < 2)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tBinaryOperationArguments arguments = getBinaryOperationArguments(instruction.getPrevious(), POP2);\n\t\t\t\t\tif (arguments != null && arguments.combinedIntermediates().isEmpty()) {\n\t\t\t\t\t\tinstructions.remove(arguments.argument1().insn());\n\t\t\t\t\t\tinstructions.insert(arguments.argument2().insn(), arguments.argument1().insn());\n\t\t\t\t\t\tinstructions.set(instruction, new InsnNode(NOP));\n\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn dirty;\n\t}\n\n\t/**\n\t * Detects sequences of instructions that are passed to an \"operation\" like {@code iadd/dmul/fcml/etc}.\n\t * Once a sequence is validated such that the inputs are aligned to the expected stack state of the operation inputs\n\t * the entire sequence is replaced with the resulting pushed stack value.\n\t *\n\t * @param context\n\t * \t\tTransformation context used to analyze methods for stack values.\n\t * @param node\n\t * \t\tClass defining the method.\n\t * @param method\n\t * \t\tThe method to transform.\n\t * @param instructions\n\t * \t\tThe instructions of the method.\n\t *\n\t * @return {@code true} when any stack operation was transformed.\n\t *\n\t * @throws TransformationException\n\t * \t\tWhen the method code couldn't be analyzed.\n\t */\n\tprivate boolean pass2SequenceFolding(@Nonnull JvmTransformerContext context, @Nonnull ClassNode node,\n\t                                     @Nonnull MethodNode method, @Nonnull InsnList instructions) throws TransformationException {\n\t\tboolean dirty = false;\n\t\tList<AbstractInsnNode> sequence = new ArrayList<>();\n\t\tFrame<ReValue>[] frames = context.analyze(inheritanceGraph, node, method);\n\t\tint endIndex = instructions.size() - 1;\n\t\tint unknownState = -1;\n\t\tfor (int i = 1; i < endIndex; i++) {\n\t\t\tAbstractInsnNode instruction = instructions.get(i);\n\t\t\tint opcode = instruction.getOpcode();\n\n\t\t\t// Iterate until we find an instruction that consumes values off the stack as part of an \"operation\".\n\t\t\tint sizeConsumed = getSizeConsumed(instruction);\n\t\t\tif (sizeConsumed == 0 || (opcode >= POP && opcode <= DUP2_X2))\n\t\t\t\tcontinue;\n\n\t\t\t// Return instructions consume values off the stack but unlike operations do not produce an outcome.\n\t\t\tboolean isReturn = isReturn(opcode) && opcode != RETURN;\n\n\t\t\t// Grab the current and next frame for later. We want to pull values from these to determine\n\t\t\t// if operations on constant inputs can be inlined.\n\t\t\t// However, a \"return\" isn't an operation, so we have an edge case handling those.\n\t\t\tFrame<ReValue> frame = frames[i];\n\t\t\tif (frame == null)\n\t\t\t\tcontinue;\n\t\t\tFrame<ReValue> nextFrame = frames[i + 1];\n\t\t\tif ((nextFrame == null || nextFrame.getStackSize() <= 0) && !isReturn)\n\t\t\t\tcontinue;\n\n\t\t\t// Walk backwards from this point and try and find a sequence of instructions that\n\t\t\t// will create the expected stack state we see for this operation instruction.\n\t\t\tboolean validSequence = true;\n\t\t\tint netStackChange = 0;\n\t\t\tint j = i;\n\t\t\tsequence.clear();\n\t\t\tMap<Integer, ReValue> sequenceVarWrites = new HashMap<>();\n\t\t\twhile (j >= 0) {\n\t\t\t\tAbstractInsnNode insn = instructions.get(j);\n\t\t\t\tint insnOp = insn.getOpcode();\n\t\t\t\tif (insnOp != NOP && insnOp != -1) // Skip adding NOP/Labels\n\t\t\t\t\tsequence.add(insn);\n\n\t\t\t\t// Abort if we've walked backwards into instructions where we observed an unknown stack state.\n\t\t\t\tif (j < unknownState) {\n\t\t\t\t\t// Move the unknown state forward since up to this point the stack is unbalanced\n\t\t\t\t\t// and thus this point relies on the prior point.\n\t\t\t\t\tunknownState = i;\n\t\t\t\t\tvalidSequence = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// Abort if we observe control flow. Both outbound and inbound breaks sequences.\n\t\t\t\t// If there is obfuscated control flow that is redundant use a control flow flattening transformer first.\n\t\t\t\tif (isFlowControl(insn)) {\n\t\t\t\t\tvalidSequence = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (insn.getType() == AbstractInsnNode.LABEL && hasInboundFlowReferences(method, Collections.singletonList(insn))) {\n\t\t\t\t\tvalidSequence = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// Record variable side effects.\n\t\t\t\t// Because j steps backwards the first encountered write will be the only thing we need to ensure\n\t\t\t\t// is kept after folding the sequence.\n\t\t\t\tFrame<ReValue> jframe = frames[j];\n\t\t\t\tif (isVarStore(insnOp) && insn instanceof VarInsnNode vin) {\n\t\t\t\t\tint index = vin.var;\n\t\t\t\t\tReValue stack = frame.getStack(frame.getStackSize() - 1);\n\t\t\t\t\tif (!stack.hasKnownValue())\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tsequenceVarWrites.putIfAbsent(index, stack);\n\t\t\t\t} else if (insn instanceof IincInsnNode iinc) {\n\t\t\t\t\tint index = iinc.var;\n\t\t\t\t\tReValue local = frame.getLocal(index);\n\t\t\t\t\tif (!local.hasKnownValue() || !(local instanceof IntValue intLocal))\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tsequenceVarWrites.putIfAbsent(index, intLocal.add(iinc.incr));\n\t\t\t\t}\n\n\t\t\t\t// Update the net stack size change.\n\t\t\t\tint stackDiff = computeInstructionStackDifference(frames, j, insn);\n\t\t\t\tnetStackChange += stackDiff;\n\n\t\t\t\t// Step backwards.\n\t\t\t\tj--;\n\n\t\t\t\t// If we see the net stack change is positive, our sequence is \"done\".\n\t\t\t\tif (netStackChange >= 1)\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Doing 'List.add' + 'reverse' is faster than 'List.addFirst' on large inputs.\n\t\t\tCollections.reverse(sequence);\n\n\t\t\t// Skip if the completed sequence isn't a viable candidate for folding.\n\t\t\t// - Explicitly marked as invalid\n\t\t\t// - Too small\n\t\t\t// - The sequence isn't balanced, or requires a larger scope to include all \"contributing\" instructions\n\t\t\tif (!validSequence || sequence.size() < 2 || shouldContinueSequence(sequence))\n\t\t\t\tcontinue;\n\n\t\t\t// Additionally if the sequence does NOT end with 'xreturn' then it should\n\t\t\t// have a positive stack effect (the final operation result should push a value).\n\t\t\tif (netStackChange < (isReturn ? 0 : 1))\n\t\t\t\tcontinue;\n\n\t\t\t// Keep the return instruction in the sequence.\n\t\t\tif (isReturn && isReturn(sequence.getLast())) {\n\t\t\t\tsequence.removeLast();\n\n\t\t\t\t// Removing the return can put us under the limit. In this case, there is nothing to fold.\n\t\t\t\t// There is just a value and then the return.\n\t\t\t\tif (sequence.size() < 2)\n\t\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Replace the operation with a constant value, or simplified instruction pattern.\n\t\t\tReValue topValue = isReturn ?\n\t\t\t\t\tframe.getStack(frame.getStackSize() - 1) :\n\t\t\t\t\tnextFrame.getStack(nextFrame.getStackSize() - 1);\n\n\t\t\t// In some cases where the next instruction is a label targeted by backwards jumps from dummy/dead code\n\t\t\t// the analyzer can get fooled into merging an unknown state into something that should be known.\n\t\t\t// When this happens we can evaluate our sequence and see what the result should be.\n\t\t\tif (!isReturn && !topValue.hasKnownValue() && isLabel(sequence.getLast().getNext()))\n\t\t\t\ttopValue = evaluateTopFromSequence(context, method, sequence, topValue, frames, j);\n\n\t\t\t// Handle replacing the sequence.\n\t\t\tAbstractInsnNode replacement = toInsn(topValue);\n\t\t\tif (replacement != null) {\n\t\t\t\t// If we have a replacement, remove all instructions in the sequence and replace the\n\t\t\t\t// operation instruction with one that pushes a constant value of the result in its place.\n\t\t\t\tfor (AbstractInsnNode item : sequence)\n\t\t\t\t\tinstructions.set(item, new InsnNode(NOP));\n\t\t\t\tif (isReturn) {\n\t\t\t\t\t// We know the sequence size must be >= 2, so the instruction before\n\t\t\t\t\t// the return should have been replaced with a nop, and is safe to replace\n\t\t\t\t\t// with our constant.\n\t\t\t\t\tAbstractInsnNode old = instructions.get(i - 1);\n\t\t\t\t\tinstructions.set(old, replacement);\n\t\t\t\t} else {\n\t\t\t\t\tinstructions.set(instructions.get(i), replacement);\n\n\t\t\t\t\t// Insert variable writes to ensure their states are not affected by our inlining.\n\t\t\t\t\tsequenceVarWrites.forEach((index, value) -> {\n\t\t\t\t\t\tAbstractInsnNode varReplacement = toInsn(value);\n\t\t\t\t\t\tVarInsnNode varStore = createVarStore(index, Objects.requireNonNull(value.type(), \"Missing var type\"));\n\t\t\t\t\t\tinstructions.insertBefore(replacement, varReplacement);\n\t\t\t\t\t\tinstructions.insertBefore(replacement, varStore);\n\t\t\t\t\t});\n\t\t\t\t\ti += sequenceVarWrites.size() * 2;\n\t\t\t\t}\n\t\t\t\tdirty = true;\n\t\t\t} else {\n\t\t\t\t// If we don't have a replacement (since the end state cannot be resolved) see if we can at least\n\t\t\t\t// fold redundant operations like \"x = x * 1\".\n\t\t\t\tif (foldRedundantOperations(instructions, instruction, frame)) {\n\t\t\t\t\tdirty = true;\n\t\t\t\t} else {\n\t\t\t\t\tint stackSize = frame.getStackSize();\n\t\t\t\t\tfor (int s = 0; s < stackSize; s++) {\n\t\t\t\t\t\tReValue stack = frame.getStack(s);\n\t\t\t\t\t\tif (!stack.hasKnownValue()) {\n\t\t\t\t\t\t\tunknownState = i;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn dirty;\n\t}\n\n\t/**\n\t * Attempts to evaluate the given sequence of instructions to find the resulting value.\n\t *\n\t * @param context\n\t * \t\tTransformer context.\n\t * @param method\n\t * \t\tMethod hosting the sequence of instructions.\n\t * @param sequence\n\t * \t\tSequence of instructions to evaluate.\n\t * @param topValue\n\t * \t\tThe existing top value that has an unknown value.\n\t * @param frames\n\t * \t\tThe method stack frames.\n\t * @param sequenceStartIndex\n\t * \t\tThe stack frame index where the sequence begins at.\n\t *\n\t * @return Top stack value after executing the given sequence of instructions.\n\t */\n\t@Nonnull\n\tprivate ReValue evaluateTopFromSequence(@Nonnull JvmTransformerContext context,\n\t                                        @Nonnull MethodNode method,\n\t                                        @Nonnull List<AbstractInsnNode> sequence,\n\t                                        @Nonnull ReValue topValue,\n\t                                        @Nonnull Frame<ReValue>[] frames,\n\t                                        int sequenceStartIndex) {\n\t\t// Need to wrap a copy of the instructions in its own InsnList\n\t\t// so that instructions have 'getNext()' and 'getPrevious()' set properly.\n\t\tMap<LabelNode, LabelNode> clonedLabels = Collections.emptyMap();\n\t\tInsnList block = new InsnList();\n\t\tfor (AbstractInsnNode insn : sequence)\n\t\t\tblock.add(insn.clone(clonedLabels));\n\n\t\t// Setup evaluator. We generally only support linear folding, so having the execution step limit\n\t\t// match the sequence length with a little leeway should be alright.\n\t\tfinal int maxSteps = sequence.size() + 10;\n\t\tReFrame initialBlockFrame = (ReFrame) frames[Math.max(0, sequenceStartIndex)];\n\t\tEvaluator evaluator = new Evaluator(context.getWorkspace(), context.newInterpreter(inheritanceGraph), new FieldCacheManager(), maxSteps, false);\n\n\t\t// Evaluate the sequence and return the result.\n\t\t// If evaluation fails, return the original unknown top value.\n\t\tEvaluationResult result = evaluator.evaluateBlock(block, initialBlockFrame, method.access);\n\t\tif (result instanceof EvaluationYieldResult(ReValue value)) {\n\t\t\tif (Objects.equals(value.type(), topValue.type())) // Sanity check\n\t\t\t\treturn value;\n\t\t}\n\n\t\t// Evaluation failed, this is to be expected as some cases cannot always be evaluated.\n\t\treturn topValue;\n\t}\n\n\t/**\n\t * @param instructions\n\t * \t\tInstructions to operate on.\n\t * @param instruction\n\t * \t\tThe instruction to check for being a redundant operation.\n\t * @param frame\n\t * \t\tThe stackframe at the instruction position.\n\t *\n\t * @return {@code true} when the instruction was a redundant operation that has been folded. Otherwise {@code false}.\n\t */\n\tprivate static boolean foldRedundantOperations(@Nonnull InsnList instructions, @Nonnull AbstractInsnNode instruction, @Nonnull Frame<ReValue> frame) {\n\t\t// Skip if this isn't an operation we can support\n\t\tif (frame.getStackSize() < 2)\n\t\t\treturn false;\n\n\t\t// We don't know the result of the operation. But if it is something we know is redundant\n\t\t// we will want to remove it anyways. For instance:\n\t\t//  x * 1 = x\n\t\t//  x / 1 = x\n\t\t//  x + 0 = x\n\t\t//  x | 0 = x\n\t\t//  x & -1 = x\n\t\t//  x ^ 0 = x\n\t\t//  x << 0 = x\n\t\t//  x >> 0 = x\n\t\t//  x >>> 0 = x\n\t\tReValue top = frame.getStack(frame.getStackSize() - 1);\n\t\tReValue topM1 = frame.getStack(frame.getStackSize() - 2);\n\t\tint opcode = instruction.getOpcode();\n\t\tint targetValue = switch (opcode) {\n\t\t\tcase IAND, LAND -> -1;\n\t\t\tcase IMUL, FMUL, DMUL, LMUL,\n\t\t\t     IDIV, FDIV, DDIV, LDIV -> 1;\n\t\t\tcase IADD, FADD, DADD, LADD,\n\t\t\t     IOR, LOR,\n\t\t\t     IXOR, LXOR,\n\t\t\t     ISHL, ISHR, IUSHR, LSHL, LSHR, LUSHR -> 0;\n\t\t\tdefault -> 25565;\n\t\t};\n\n\t\t// Skip if not an operation we can simplify.\n\t\tif (targetValue == 25565)\n\t\t\treturn false;\n\n\t\tif (ReValue.isPrimitiveEqualTo(top, targetValue)) {\n\t\t\t// Scan for the instructions that provide the argument values for the current instruction/binary operation.\n\t\t\t// - Start with the instruction before this one as a potential provider for the 2nd argument (right value in an operation)\n\t\t\tBinaryOperationArguments arguments = getBinaryOperationArguments(instruction.getPrevious(), opcode);\n\t\t\tif (arguments == null)\n\t\t\t\treturn false;\n\n\t\t\t// Remove redundant operation + top/right value provider.\n\t\t\tinstructions.set(instruction, new InsnNode(NOP));\n\t\t\tinstructions.set(arguments.argument2().insn(), new InsnNode(NOP));\n\t\t\tfor (AbstractInsnNode intermediate : arguments.argument2().intermediates())\n\t\t\t\tinstructions.set(intermediate, new InsnNode(NOP));\n\t\t\treturn true;\n\t\t} else if (ReValue.isPrimitiveEqualTo(topM1, targetValue)) {\n\t\t\t// Scan for the instructions that provide the argument values for the current instruction/binary operation.\n\t\t\t// - Start with the instruction before this one as a potential provider for the 2nd argument (right value in an operation)\n\t\t\tBinaryOperationArguments arguments = getBinaryOperationArguments(instruction.getPrevious(), opcode);\n\t\t\tif (arguments == null)\n\t\t\t\treturn false;\n\n\t\t\t// Remove redundant operation + top-1/left value provider.\n\t\t\tinstructions.set(instruction, new InsnNode(NOP));\n\t\t\tinstructions.set(arguments.argument1().insn(), new InsnNode(NOP));\n\t\t\tfor (AbstractInsnNode intermediate : arguments.argument1().intermediates())\n\t\t\t\tinstructions.set(intermediate, new InsnNode(NOP));\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param frames\n\t * \t\tMethod stack frames.\n\t * @param i\n\t * \t\tIndex in the stack frames of the given instruction.\n\t * @param insn\n\t * \t\tThe instruction to evaluate for stack size differences after execution.\n\t *\n\t * @return Stack size difference after execution.\n\t */\n\tprivate static int computeInstructionStackDifference(@Nonnull Frame<ReValue>[] frames, int i, @Nonnull AbstractInsnNode insn) {\n\t\t// Ideally we just check the size difference between this frame and the next frame.\n\t\t// Not all frames exist in the array, as dead code gets skipped by ASM's analyzer.\n\t\tFrame<ReValue> thisFrame = frames[i];\n\t\tFrame<ReValue> nextFrame = frames[i + 1];\n\t\tif (thisFrame != null && nextFrame != null) {\n\t\t\tint jsize = thisFrame.getStackSize();\n\t\t\tint jsizeNext = nextFrame.getStackSize();\n\t\t\treturn jsizeNext - jsize;\n\t\t}\n\n\t\t// This is our fallback. These utility methods are not ideal because they follow the JVM spec.\n\t\t// I know, that sounds ridiculous. But ASM's analyzer treats long/double as a single slot.\n\t\t// These size methods will treat long/double as two slots. The discrepancy can lead to us not\n\t\t// properly evaluating sequence lengths. This generally shouldn't ever be an issue unless we're\n\t\t// looking at dead code regions (which make the frame null checks above fail).\n\t\tint consumed = getSizeConsumed(insn);\n\t\tint produced = getSizeProduced(insn);\n\t\treturn produced - consumed;\n\t}\n\n\t/**\n\t * Check if the given sequence is unbalanced, or is prefixed with an instruction that implies\n\t * more instructions should be included for a <i>\"full scope\"</i> of <i>\"contributing\"</i> instructions.\n\t *\n\t * @param sequence\n\t * \t\tInstruction sequence.\n\t *\n\t * @return {@code true} when the instruction sequence should continue expanding backwards.\n\t */\n\tprivate static boolean shouldContinueSequence(@Nonnull List<AbstractInsnNode> sequence) {\n\t\tint stackDiff = 0;\n\t\tint consumed = 0;\n\t\tint produced = 0;\n\t\tint netStackChange = 0;\n\t\tfor (AbstractInsnNode seq : sequence) {\n\t\t\t// DUP operations operate on values on the stack. While the most simple DUP case is fine, any other variant\n\t\t\t// such as DUP2, DUP_X, etc. have edge cases which mean we cannot have a 100% foolproof/isolated sequence.\n\t\t\t// These require us to continue scanning backwards to expand the sequence.\n\t\t\tint op = seq.getOpcode();\n\t\t\tif ((op == DUP && netStackChange < 1)\n\t\t\t\t\t|| (op == DUP_X1 && netStackChange < 2)\n\t\t\t\t\t|| (op == DUP_X2 && netStackChange < 3)\n\t\t\t\t\t|| (op == DUP2 && netStackChange < 2)\n\t\t\t\t\t|| (op == DUP2_X1 && netStackChange < 3)\n\t\t\t\t\t|| (op == DUP2_X2 && netStackChange < 4)\n\t\t\t\t\t|| (op == SWAP && netStackChange < 2)\n\t\t\t\t\t|| (op == POP && netStackChange < 1)\n\t\t\t\t\t|| (op == POP2 && netStackChange < 2)) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Get stack change for this instruction in the sequence.\n\t\t\tconsumed = getSizeConsumed(seq);\n\t\t\tproduced = getSizeProduced(seq);\n\n\t\t\t// If we ever see the stack size in this sequence go negative, then it\n\t\t\t// cannot be treated as an \"isolated\" sequence. It implies that there is a reliance on a larger\n\t\t\t// stack size in the current sequence scope. We can't really \"recover\" this scope at this point.\n\t\t\t// However, if we create a new scope starting at a later instruction its possible it will give us\n\t\t\t// a larger scoped sequence which will include enough instructions to prevent this from occurring.\n\t\t\tif (consumed > netStackChange)\n\t\t\t\treturn true;\n\n\t\t\t// Update net stack change.\n\t\t\tstackDiff = produced - consumed;\n\t\t\tnetStackChange += stackDiff;\n\t\t}\n\t\treturn netStackChange > consumed || netStackChange != produced;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Opaque constant folding\";\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<Class<? extends ClassTransformer>> recommendedPredecessors() {\n\t\t// Basic goto obf will prevent this transformer from handling \"obvious\" cases.\n\t\treturn Collections.singleton(GotoInliningTransformer.class);\n\t}\n\n\t/**\n\t * Check if the instruction is responsible for providing some value we can possibly fold.\n\t * This method doesn't tell us if the value is known though. The next frame after this\n\t * instruction should have the provided value on the stack top.\n\t *\n\t * @param insn\n\t * \t\tInstruction to check.\n\t *\n\t * @return {@code true} when the instruction will produce a single value.\n\t */\n\tprotected static boolean isSupportedValueProducer(@Nonnull AbstractInsnNode insn) {\n\t\t// Skip if this instruction consumes a value off the stack.\n\t\tif (getSizeConsumed(insn) > 0)\n\t\t\treturn false;\n\n\t\t// The following cases are supported:\n\t\t//  - constants\n\t\t//  - variable loads (context will determine if value in variable is constant at the given position)\n\t\t//  - static field gets (context will determine if value in field is constant/known)\n\t\t//  - static method calls with 0 args (context will determine if returned value of method is constant/known)\n\t\tint op = insn.getOpcode();\n\t\tif (isConstValue(op))\n\t\t\treturn true;\n\t\tif (op >= ILOAD && op <= ALOAD)\n\t\t\treturn true;\n\t\tif (op == GETSTATIC)\n\t\t\treturn true;\n\t\treturn op == INVOKESTATIC\n\t\t\t\t&& insn instanceof MethodInsnNode min\n\t\t\t\t&& min.desc.startsWith(\"()\")\n\t\t\t\t&& !min.desc.endsWith(\")V\");\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to convert.\n\t *\n\t * @return Instruction representing the value,\n\t * or {@code null} if we don't/can't provide a mapping for the value content.\n\t */\n\t@Nullable\n\t@SuppressWarnings(\"OptionalGetWithoutIsPresent\")\n\tpublic static AbstractInsnNode toInsn(@Nonnull ReValue value) {\n\t\t// Skip if value is not known.\n\t\tif (!value.hasKnownValue())\n\t\t\treturn null;\n\n\t\t// Map known value types to constant value instructions.\n\t\treturn switch (value) {\n\t\t\tcase IntValue intValue -> intToInsn(intValue.value().getAsInt());\n\t\t\tcase FloatValue floatValue -> floatToInsn((float) floatValue.value().getAsDouble());\n\t\t\tcase DoubleValue doubleValue -> doubleToInsn(doubleValue.value().getAsDouble());\n\t\t\tcase LongValue longValue -> longToInsn(longValue.value().getAsLong());\n\t\t\tcase StringValue stringValue -> new LdcInsnNode(stringValue.getText().get());\n\t\t\tdefault -> null;\n\t\t};\n\t}\n\n\t/**\n\t * @param frame\n\t * \t\tFrame to count true stack size of <i>(in terms of occupied slots)</i>\n\t *\n\t * @return Number of stack slots occupied in the frame.\n\t */\n\tprivate static int getSlotsOccupied(@Nonnull Frame<ReValue> frame) {\n\t\tint valueCount = frame.getStackSize();\n\t\tint slots = 0;\n\t\tfor (int i = 0; i < valueCount; i++) {\n\t\t\tReValue value = frame.getStack(i);\n\t\t\tslots += value.getSize();\n\t\t}\n\t\treturn slots;\n\t}\n\n\t/**\n\t * This is essentially {@link #collectArgument(AbstractInsnNode)} but run twice, then wrapped up in a box.\n\t * The main difference is edge case handling for wide types and some sanity checks for edge cases applicable\n\t * only to cases where there are two arguments rather than just one.\n\t *\n\t * @param insnBeforeOp\n\t * \t\tStarting instruction representing a {@link #isSupportedValueProducer(AbstractInsnNode) value producer}\n\t * \t\tto an operation instruction <i>(like an {@code iconst_1} as part of an {@code iadd} operation)</i>.\n\t * @param binOperationOpcode\n\t * \t\tThe opcode for the operation instruction. Generally something like {@code iadd}, {@code dmul}, etc.\n\t * \t\tUsed to determine how to treat arguments in some wide-type edge cases.\n\t *\n\t * @return Wrapper containing the arguments <i>(and their instructions)</i> if found. Otherwise {@code null}.\n\t */\n\t@Nullable\n\tpublic static BinaryOperationArguments getBinaryOperationArguments(@Nonnull AbstractInsnNode insnBeforeOp, int binOperationOpcode) {\n\t\t// Get instruction of the top stack's contributing instruction.\n\t\tArgument argument2 = collectArgument(insnBeforeOp);\n\t\tif (argument2 == null)\n\t\t\treturn null;\n\n\t\t// Get instruction of the 2nd-to-top stack's contributing instruction.\n\t\t// In some cases this may be the same value as the instruction we grabbed above.\n\t\t// Consider the case:\n\t\t//  iconst_2\n\t\t//  dup2\n\t\t//  iadd\n\t\t// When we see \"iadd\" has arguments \"dup2\" it will satisfy both values in the addition.\n\t\tArgument argument1;\n\t\tif (argument2.providesBinaryOpValuesFor(binOperationOpcode)) {\n\t\t\t// If the instruction before produces a larger value than required we have\n\t\t\t// encountered a case that follows the example case above (likely a dup2).\n\t\t\targument1 = argument2;\n\t\t} else {\n\t\t\targument1 = collectArgument(argument2.insn().getPrevious());\n\n\t\t\t// If we didn't find a value for argument 1, we cannot handle this binary argument.\n\t\t\tif (argument1 == null)\n\t\t\t\treturn null;\n\n\t\t\t// If argument 1 was found, but is too wide (a double or dup2) for the binary argument considering\n\t\t\t// that we already have argument 2 resolved, then we also cannot handle this binary argument.\n\t\t\t//\n\t\t\t// Example:\n\t\t\t//   sipush 20\n\t\t\t//   sipush 10\n\t\t\t//   dup2       <---- Pushes [20, 10] onto stack, resulting in [20, 10, 20, 10]\n\t\t\t//   sipush -10\n\t\t\t//   swap       <---- If we are here, and want to see what instructions provide \"arguments\"\n\t\t\t//   ...              then the \"dup2\" provides [20, 10] on the stack, while we only operate on [10].\n\t\t\t//   ...              This makes it so that we can't correctly say \"dup2\" is 100% responsible for operands\n\t\t\t//   ...              in \"swap\" because it also produces \"20\" which isn't an operand for our \"swap.\n\t\t\tif (argument1.providesBinaryOpValuesFor(binOperationOpcode))\n\t\t\t\treturn null;\n\t\t}\n\n\t\t// If we saw an odd number of \"swap\" before we got (arg2) then we want to swap the references.\n\t\tint swapCount = (int) argument2.intermediates().stream()\n\t\t\t\t.filter(i -> i.getOpcode() == SWAP)\n\t\t\t\t.count();\n\t\tif (swapCount % 2 == 1) {\n\t\t\tArgument temp = argument1;\n\t\t\targument1 = argument2;\n\t\t\targument2 = temp;\n\t\t}\n\n\t\t// If we have recorded intermediate instructions that result in stack consumption\n\t\t// we need to remove the instructions they have consumed.\n\t\t// Track any intermediate instructions between the operation instruction\n\t\t// and the first argument instruction (arg2).\n\t\tList<AbstractInsnNode> combinedIntermediates = new ArrayList<>(argument1.getCombinedIntermediates(argument2));\n\t\tif (!canConsumeAccumulatedStackConsumption(argument1, argument2, combinedIntermediates))\n\t\t\treturn null;\n\t\treturn new BinaryOperationArguments(argument2, argument1, combinedIntermediates);\n\t}\n\n\t/**\n\t * Starting with the provided instruction <i>(inclusive)</i>, we walk backwards until a valid\n\t * {@link #isSupportedValueProducer(AbstractInsnNode) value producer} is found. There are certain\n\t * instructions which we will support as intermediates between the starting point and the final chosen instruction.\n\t * Intermediate instructions are generally stack manipulations which we want to remove as they are between the\n\t * actual instruction providing the value and the place where the value is used.\n\t *\n\t * @param insnBeforeOp\n\t * \t\tStarting instruction representing a {@link #isSupportedValueProducer(AbstractInsnNode) value producer}\n\t * \t\tto an operation instruction <i>(like an {@code iconst_1} as part of an {@code ineg} operation)</i>.\n\t *\n\t * @return Wrapper containing the instruction if it is a value producer was found. Otherwise {@code null}.\n\t */\n\t@Nullable\n\tpublic static Argument collectArgument(@Nullable AbstractInsnNode insnBeforeOp) {\n\t\tif (insnBeforeOp == null)\n\t\t\treturn null;\n\t\tList<AbstractInsnNode> intermediates = null;\n\t\tint intermediateStackConsumption = 0;\n\t\twhile (insnBeforeOp != null) {\n\t\t\tint argumentOp = insnBeforeOp.getOpcode();\n\t\t\tif (argumentOp == NOP) {\n\t\t\t\tinsnBeforeOp = insnBeforeOp.getPrevious();\n\t\t\t} else if (argumentOp == SWAP || argumentOp == POP || argumentOp == POP2) {\n\t\t\t\t// We already know the values in our frame, so these intermediate instructions\n\t\t\t\t// between the operation instruction and the instructions pushing those values\n\t\t\t\t// onto the stack can be recorded for removal.\n\t\t\t\tif (intermediates == null)\n\t\t\t\t\tintermediates = new ArrayList<>();\n\t\t\t\tintermediates.add(insnBeforeOp);\n\t\t\t\tintermediateStackConsumption += getSizeConsumed(insnBeforeOp);\n\t\t\t\tinsnBeforeOp = insnBeforeOp.getPrevious();\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (insnBeforeOp == null || !isSupportedValueProducer(insnBeforeOp))\n\t\t\treturn null;\n\t\treturn new Argument(insnBeforeOp, Objects.requireNonNullElse(intermediates, Collections.emptyList()), intermediateStackConsumption);\n\t}\n\n\tprivate static boolean canConsumeAccumulatedStackConsumption(@Nonnull Argument argument1, @Nonnull Argument argument2,\n\t                                                             @Nonnull List<AbstractInsnNode> intermediates) {\n\t\t// The first argument (provides value beneath the 2nd argument on the stack) is where\n\t\t// our backwards search (exclusive) for instructions will begin.\n\t\tAbstractInsnNode insn = argument1.insn();\n\n\t\t// Combine the stack consumption of both arguments and begin consumption.\n\t\tint intermediateStackConsumption = argument1.getCombinedStackConsumption(argument2);\n\t\treturn canConsumeAccumulatedStackConsumption(intermediateStackConsumption, intermediates, insn);\n\t}\n\n\tprivate static boolean canConsumeAccumulatedStackConsumption(int intermediateStackConsumption,\n\t                                                             @Nonnull List<AbstractInsnNode> intermediates,\n\t                                                             @Nonnull AbstractInsnNode start) {\n\t\t// If we have recorded intermediate instructions that result in stack consumption\n\t\t// we need to remove the instructions they have consumed. To do this, we will add them\n\t\t// to the intermediate instruction list.\n\t\tAbstractInsnNode insn = start;\n\t\twhile (intermediateStackConsumption > 0) {\n\t\t\tinsn = insn.getPrevious();\n\t\t\tif (insn == null)\n\t\t\t\tbreak;\n\t\t\tif (insn.getOpcode() == NOP)\n\t\t\t\tcontinue;\n\t\t\tif (isSupportedValueProducer(insn)) {\n\t\t\t\tintermediates.add(insn);\n\t\t\t\tintermediateStackConsumption -= getSizeProduced(insn);\n\t\t\t} else {\n\t\t\t\t// We don't know how to handle this instruction, bail out.\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn intermediateStackConsumption == 0;\n\t}\n\n\tstatic {\n\t\tArrays.fill(ARG_1_SIZE, -1);\n\t\tArrays.fill(ARG_2_SIZE, -1);\n\n\t\tARG_1_SIZE[IADD] = 1;\n\t\tARG_1_SIZE[FADD] = 1;\n\t\tARG_1_SIZE[ISUB] = 1;\n\t\tARG_1_SIZE[FSUB] = 1;\n\t\tARG_1_SIZE[IMUL] = 1;\n\t\tARG_1_SIZE[FMUL] = 1;\n\t\tARG_1_SIZE[IDIV] = 1;\n\t\tARG_1_SIZE[FDIV] = 1;\n\t\tARG_1_SIZE[IREM] = 1;\n\t\tARG_1_SIZE[FREM] = 1;\n\t\tARG_1_SIZE[ISHL] = 1;\n\t\tARG_1_SIZE[ISHR] = 1;\n\t\tARG_1_SIZE[IUSHR] = 1;\n\t\tARG_1_SIZE[IAND] = 1;\n\t\tARG_1_SIZE[IXOR] = 1;\n\t\tARG_1_SIZE[IOR] = 1;\n\t\tARG_1_SIZE[DREM] = 2;\n\t\tARG_1_SIZE[DDIV] = 2;\n\t\tARG_1_SIZE[DMUL] = 2;\n\t\tARG_1_SIZE[DSUB] = 2;\n\t\tARG_1_SIZE[DADD] = 2;\n\t\tARG_1_SIZE[LUSHR] = 2;\n\t\tARG_1_SIZE[LSHR] = 2;\n\t\tARG_1_SIZE[LSHL] = 2;\n\t\tARG_1_SIZE[LREM] = 2;\n\t\tARG_1_SIZE[LDIV] = 2;\n\t\tARG_1_SIZE[LMUL] = 2;\n\t\tARG_1_SIZE[LSUB] = 2;\n\t\tARG_1_SIZE[LADD] = 2;\n\t\tARG_1_SIZE[LAND] = 2;\n\t\tARG_1_SIZE[LOR] = 2;\n\t\tARG_1_SIZE[LXOR] = 2;\n\t\tARG_1_SIZE[FCMPL] = 1;\n\t\tARG_1_SIZE[FCMPG] = 1;\n\t\tARG_1_SIZE[LCMP] = 2;\n\t\tARG_1_SIZE[DCMPL] = 2;\n\t\tARG_1_SIZE[DCMPG] = 2;\n\n\t\tSystem.arraycopy(ARG_1_SIZE, 0, ARG_2_SIZE, 0, ARG_1_SIZE.length);\n\t\tARG_2_SIZE[LUSHR] = 1;\n\t\tARG_2_SIZE[LSHR] = 1;\n\t\tARG_2_SIZE[LSHL] = 1;\n\n\t\t// The rest of these aren't \"operations\" like the above\n\t\tARG_1_SIZE[DUP] = 1;\n\t\tARG_1_SIZE[DUP_X1] = 1;\n\t\tARG_1_SIZE[DUP_X2] = 1;\n\t\tARG_1_SIZE[DUP2] = 1;\n\t\tARG_2_SIZE[DUP2] = 1;\n\t\tARG_1_SIZE[DUP2_X1] = 1;\n\t\tARG_2_SIZE[DUP2_X1] = 1;\n\t\tARG_1_SIZE[DUP2_X2] = 1;\n\t\tARG_2_SIZE[DUP2_X2] = 1;\n\t\tARG_1_SIZE[POP] = 1;\n\t\tARG_1_SIZE[POP2] = 1;\n\t\tARG_2_SIZE[POP2] = 1;\n\n\t}\n\n\t/**\n\t * Wrapper of two {@link Argument}.\n\t *\n\t * @param argument2\n\t * \t\tArgument providing the left side value of a binary operation.\n\t * @param argument1\n\t * \t\tArgument providing the right side value of a binary operation.\n\t * @param combinedIntermediates\n\t * \t\tTrack any intermediate instructions between the operation instruction and the argument's instructions.\n\t */\n\tpublic record BinaryOperationArguments(@Nonnull Argument argument2, @Nonnull Argument argument1,\n\t                                       @Nonnull List<AbstractInsnNode> combinedIntermediates) {\n\t\t/**\n\t\t * Replace the instructions from the wrapped arguments with {@code nop}\n\t\t * or other value providing instructions if the stack state necessitates it.\n\t\t *\n\t\t * @param instructions\n\t\t * \t\tInstructions list to modify.\n\t\t */\n\t\tpublic void replaceBinOp(@Nonnull InsnList instructions) {\n\t\t\t// Replace right binary operation value.\n\t\t\targument2.replaceInsn(instructions);\n\n\t\t\t// Replace left binary operation value (If the right argument hasn't provided for both arguments).\n\t\t\tif (!argument1.sameAs(argument2))\n\t\t\t\targument1.replaceInsn(instructions);\n\n\t\t\t// Remove any intermediate instructions.\n\t\t\tfor (AbstractInsnNode intermediate : combinedIntermediates)\n\t\t\t\tif (instructions.contains(intermediate))\n\t\t\t\t\tinstructions.set(intermediate, new InsnNode(NOP));\n\t\t}\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tInstruction that may act as a provider for the operation instruction.\n\t * @param intermediates\n\t * \t\tInstructions between the operation instruction and the argument instruction.\n\t * @param intermediateStackConsumption\n\t * \t\tTrack any intermediate instructions between the operation instruction and the argument instruction.\n\t */\n\tpublic record Argument(@Nonnull AbstractInsnNode insn,\n\t                       @Nonnull List<AbstractInsnNode> intermediates,\n\t                       int intermediateStackConsumption) {\n\t\t/**\n\t\t * @return {@code true} when {@link #intermediates()} is empty.\n\t\t */\n\t\tpublic boolean hasIntermediates() {\n\t\t\treturn !intermediates.isEmpty();\n\t\t}\n\n\t\t/**\n\t\t * @param other\n\t\t * \t\tSome other argument.\n\t\t *\n\t\t * @return {@code true} when both this and the other arg wrap the same instruction.\n\t\t */\n\t\tpublic boolean sameAs(@Nonnull Argument other) {\n\t\t\treturn insn == other.insn;\n\t\t}\n\n\t\t/**\n\t\t * @param other\n\t\t * \t\tSome other argument.\n\t\t *\n\t\t * @return Combined stack consumption of this and the other argument.\n\t\t */\n\t\tpublic int getCombinedStackConsumption(@Nonnull Argument other) {\n\t\t\treturn sameAs(other) ?\n\t\t\t\t\tintermediateStackConsumption :\n\t\t\t\t\tintermediateStackConsumption + other.intermediateStackConsumption;\n\t\t}\n\n\t\t/**\n\t\t * @param other\n\t\t * \t\tSome other argument.\n\t\t *\n\t\t * @return Combined intermediates of both this and the other argument.\n\t\t */\n\t\t@Nonnull\n\t\tpublic List<AbstractInsnNode> getCombinedIntermediates(@Nonnull Argument other) {\n\t\t\tif (!hasIntermediates() && !other.hasIntermediates())\n\t\t\t\treturn Collections.emptyList();\n\t\t\tif (sameAs(other) || !other.hasIntermediates())\n\t\t\t\treturn intermediates;\n\t\t\treturn Lists.combine(intermediates, other.intermediates);\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\tString string = BlwUtil.toString(insn);\n\t\t\tif (intermediates.isEmpty())\n\t\t\t\treturn string;\n\t\t\treturn string + \"\\n - \" + intermediates.stream().map(BlwUtil::toString).collect(Collectors.joining(\"\\n - \"));\n\t\t}\n\n\t\t/**\n\t\t * Check if this {@link #insn()} provides values for the given {@code opcode}'s operation.\n\t\t *\n\t\t * @param opcode\n\t\t * \t\tSome binary operation <i>(Instruction operating on two stack values)</i>\n\t\t *\n\t\t * @return {@code true} if this argument {@link #insn() instruction} supplies the binary operation with <b>both</b> stack values.\n\t\t * {@code false} if this argument provides only one or none of the values.\n\t\t */\n\t\tpublic boolean providesBinaryOpValuesFor(int opcode) {\n\t\t\t// Get the required sizes of arguments for the given instruction.\n\t\t\tint arg1Size = ARG_1_SIZE[opcode];\n\t\t\tint arg2Size = ARG_2_SIZE[opcode];\n\t\t\tif (arg1Size < 0 || arg2Size < 0)\n\t\t\t\tthrow new IllegalStateException(\"Missing arg sizes for op: \" + opcode);\n\n\t\t\t// Cover cases like long/doubles\n\t\t\tint totalArgSize = arg1Size + arg2Size;\n\t\t\tif (getSizeProduced(insn) == totalArgSize)\n\t\t\t\treturn true;\n\n\t\t\t// The ONLY case where this is valid is DUP2 for some non-wide op (like IADD).\n\t\t\t// Other stack modifying instructions like DUP_X1/X2 + DUP2_X1/X2 move the values below the stack.\n\t\t\treturn insn.getOpcode() == DUP2\n\t\t\t\t\t&& arg1Size == 1\n\t\t\t\t\t&& arg2Size == 1;\n\t\t}\n\n\t\t/**\n\t\t * Replace the {@link #insn()} with either a {@code nop} or some other value providing instruction <i>(Depending on calling circumstances)</i>.\n\t\t *\n\t\t * @param instructions\n\t\t * \t\tInstructions list to modify.\n\t\t *\n\t\t * @return {@code true} when the instructions list was successfully modified.\n\t\t * {@code false} when no replacement could be made.\n\t\t */\n\t\tpublic boolean replaceInsn(@Nonnull InsnList instructions) {\n\t\t\tinstructions.set(insn, new InsnNode(NOP));\n\t\t\treturn true;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/OpaquePredicateFoldingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.InsnNode;\nimport org.objectweb.asm.tree.JumpInsnNode;\nimport org.objectweb.asm.tree.LookupSwitchInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.TableSwitchInsnNode;\nimport org.objectweb.asm.tree.analysis.Frame;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.transform.ClassTransformer;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.AsmInsnUtil;\nimport software.coley.recaf.util.analysis.value.IntValue;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.function.BiPredicate;\nimport java.util.function.Predicate;\n\nimport static org.objectweb.asm.Opcodes.*;\nimport static software.coley.recaf.services.deobfuscation.transform.generic.OpaqueConstantFoldingTransformer.isSupportedValueProducer;\nimport static software.coley.recaf.util.AsmInsnUtil.isFlowControl;\nimport static software.coley.recaf.util.AsmInsnUtil.isSwitchEffectiveGoto;\n\n/**\n * A transformer that folds opaque predicates into single-path control flows.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class OpaquePredicateFoldingTransformer implements JvmClassTransformer {\n\tprivate final InheritanceGraphService graphService;\n\tprivate InheritanceGraph inheritanceGraph;\n\n\t@Inject\n\tpublic OpaquePredicateFoldingTransformer(@Nonnull InheritanceGraphService graphService) {\n\t\tthis.graphService = graphService;\n\t}\n\n\t@Override\n\tpublic void setup(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace) {\n\t\tinheritanceGraph = graphService.getOrCreateInheritanceGraph(workspace);\n\t}\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tboolean dirty = false;\n\t\tString className = initialClassState.getName();\n\t\tClassNode node = context.getNode(bundle, initialClassState);\n\t\tfor (MethodNode method : node.methods) {\n\t\t\tInsnList instructions = method.instructions;\n\n\t\t\t// Skip if method is abstract.\n\t\t\tif (instructions == null)\n\t\t\t\tcontinue;\n\n\t\t\t// Some obfuscators will use 'switch' instructions with all labels being the same in order to\n\t\t\t// recreate the behavior of 'goto'. We will just replace these if we see them.\n\t\t\t// We do this in a pre-pass here since we end up inserting an additional 'POP' for any matched switch.\n\t\t\tfor (int i = 1; i < instructions.size() - 1; i++) {\n\t\t\t\tAbstractInsnNode instruction = instructions.get(i);\n\t\t\t\tif (instruction instanceof TableSwitchInsnNode switchInsn && isSwitchEffectiveGoto(switchInsn)) {\n\t\t\t\t\tAbstractInsnNode previous = switchInsn.getPrevious();\n\t\t\t\t\tif (isValueProducerOrTopDup(previous))\n\t\t\t\t\t\tinstructions.remove(previous);\n\t\t\t\t\telse\n\t\t\t\t\t\tinstructions.insertBefore(switchInsn, new InsnNode(POP));\n\t\t\t\t\tinstructions.set(switchInsn, new JumpInsnNode(GOTO, switchInsn.dflt));\n\t\t\t\t\tdirty = true;\n\t\t\t\t} else if (instruction instanceof LookupSwitchInsnNode switchInsn && isSwitchEffectiveGoto(switchInsn)) {\n\t\t\t\t\tAbstractInsnNode previous = switchInsn.getPrevious();\n\t\t\t\t\tif (isValueProducerOrTopDup(previous))\n\t\t\t\t\t\tinstructions.remove(previous);\n\t\t\t\t\telse\n\t\t\t\t\t\tinstructions.insertBefore(switchInsn, new InsnNode(POP));\n\t\t\t\t\tinstructions.set(switchInsn, new JumpInsnNode(GOTO, switchInsn.dflt));\n\t\t\t\t\tdirty = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tboolean localDirty = false;\n\t\t\t\tFrame<ReValue>[] frames = context.analyze(inheritanceGraph, node, method);\n\t\t\t\tfor (int i = 1; i < instructions.size() - 1; i++) {\n\t\t\t\t\tAbstractInsnNode instruction = instructions.get(i);\n\n\t\t\t\t\t// Skip if this isn't a control flow instruction.\n\t\t\t\t\t// We are only flattening control flow here.\n\t\t\t\t\tif (!isFlowControl(instruction))\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t// Skip goto, branch is always taken.\n\t\t\t\t\t// Use the goto inliner if you want to clean these up.\n\t\t\t\t\tif (instruction.getOpcode() == GOTO)\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t// Skip if there is no frame for this instruction.\n\t\t\t\t\tif (i >= frames.length)\n\t\t\t\t\t\tcontinue; // Can happen if there is dead code at the end\n\t\t\t\t\tFrame<ReValue> frame = frames[i];\n\t\t\t\t\tif (frame == null || frame.getStackSize() == 0)\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t// Skip if stack top is not known.\n\t\t\t\t\tReValue stackTop = frame.getStack(frame.getStackSize() - 1);\n\t\t\t\t\tif (!stackTop.hasKnownValue() && !(stackTop instanceof ObjectValue ov && ov.isNull()))\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t// Get instruction of the top stack's contributing instruction.\n\t\t\t\t\t// It must also be a value producing instruction.\n\t\t\t\t\t// If this is something that isn't value producing, another transformer needs to simplify it first.\n\t\t\t\t\tAbstractInsnNode prevInstruction = AsmInsnUtil.getPreviousInsn(instruction);\n\t\t\t\t\tif (prevInstruction == null || !isValueProducerOrTopDup(prevInstruction))\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t// Handle any control flow instruction and see if we know based on the frame contents if a specific\n\t\t\t\t\t// path is always taken.\n\t\t\t\t\tint insnType = instruction.getType();\n\t\t\t\t\tif (insnType == AbstractInsnNode.JUMP_INSN) {\n\t\t\t\t\t\tJumpInsnNode jin = (JumpInsnNode) instruction;\n\t\t\t\t\t\tint opcode = instruction.getOpcode();\n\t\t\t\t\t\tif ((opcode >= IFEQ && opcode <= IFLE) || opcode == IFNULL || opcode == IFNONNULL) {\n\t\t\t\t\t\t\t// Replace single argument binary control flow.\n\t\t\t\t\t\t\tlocalDirty |= switch (opcode) {\n\t\t\t\t\t\t\t\tcase IFEQ ->\n\t\t\t\t\t\t\t\t\t\treplaceIntValue(instructions, prevInstruction, stackTop, jin, v -> v.isEqualTo(0));\n\t\t\t\t\t\t\t\tcase IFNE ->\n\t\t\t\t\t\t\t\t\t\treplaceIntValue(instructions, prevInstruction, stackTop, jin, v -> v.isNotEqualTo(0));\n\t\t\t\t\t\t\t\tcase IFLT ->\n\t\t\t\t\t\t\t\t\t\treplaceIntValue(instructions, prevInstruction, stackTop, jin, v -> v.isLessThan(0));\n\t\t\t\t\t\t\t\tcase IFGE ->\n\t\t\t\t\t\t\t\t\t\treplaceIntValue(instructions, prevInstruction, stackTop, jin, v -> v.isGreaterThanOrEqual(0));\n\t\t\t\t\t\t\t\tcase IFGT ->\n\t\t\t\t\t\t\t\t\t\treplaceIntValue(instructions, prevInstruction, stackTop, jin, v -> v.isGreaterThan(0));\n\t\t\t\t\t\t\t\tcase IFLE ->\n\t\t\t\t\t\t\t\t\t\treplaceIntValue(instructions, prevInstruction, stackTop, jin, v -> v.isLessThanOrEqual(0));\n\t\t\t\t\t\t\t\tcase IFNULL ->\n\t\t\t\t\t\t\t\t\t\treplaceObjValue(instructions, prevInstruction, stackTop, jin, ObjectValue::isNull);\n\t\t\t\t\t\t\t\tcase IFNONNULL ->\n\t\t\t\t\t\t\t\t\t\treplaceObjValue(instructions, prevInstruction, stackTop, jin, ObjectValue::isNotNull);\n\t\t\t\t\t\t\t\tdefault -> localDirty;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t} else if (opcode >= IF_ICMPEQ && opcode <= IF_ACMPNE) {\n\t\t\t\t\t\t\t// Skip if the other argument to compare with is not available or known.\n\t\t\t\t\t\t\tif (frame.getStackSize() < 2)\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\tReValue stack2ndTop = frame.getStack(frame.getStackSize() - 2);\n\t\t\t\t\t\t\tif (!stack2ndTop.hasKnownValue() && !(stack2ndTop instanceof ObjectValue ov && ov.isNull()))\n\t\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\t\t// Skip if the other argument to compare with is not immediately backed by\n\t\t\t\t\t\t\t// a value supplying instruction.\n\t\t\t\t\t\t\tAbstractInsnNode prevPrevInstruction = prevInstruction.getPrevious();\n\t\t\t\t\t\t\tif (prevPrevInstruction == null || !isValueProducerOrTopDup(prevPrevInstruction))\n\t\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\t\t// Replace double argument binary control flow.\n\t\t\t\t\t\t\tlocalDirty |= switch (opcode) {\n\t\t\t\t\t\t\t\tcase IF_ICMPEQ ->\n\t\t\t\t\t\t\t\t\t\treplaceIntIntValue(instructions, prevPrevInstruction, prevInstruction, stack2ndTop, stackTop, jin, IntValue::isEqualTo);\n\t\t\t\t\t\t\t\tcase IF_ICMPNE ->\n\t\t\t\t\t\t\t\t\t\treplaceIntIntValue(instructions, prevPrevInstruction, prevInstruction, stack2ndTop, stackTop, jin, IntValue::isNotEqualTo);\n\t\t\t\t\t\t\t\tcase IF_ICMPLT ->\n\t\t\t\t\t\t\t\t\t\treplaceIntIntValue(instructions, prevPrevInstruction, prevInstruction, stack2ndTop, stackTop, jin, IntValue::isLessThan);\n\t\t\t\t\t\t\t\tcase IF_ICMPGE ->\n\t\t\t\t\t\t\t\t\t\treplaceIntIntValue(instructions, prevPrevInstruction, prevInstruction, stack2ndTop, stackTop, jin, IntValue::isGreaterThanOrEqual);\n\t\t\t\t\t\t\t\tcase IF_ICMPGT ->\n\t\t\t\t\t\t\t\t\t\treplaceIntIntValue(instructions, prevPrevInstruction, prevInstruction, stack2ndTop, stackTop, jin, IntValue::isGreaterThan);\n\t\t\t\t\t\t\t\tcase IF_ICMPLE ->\n\t\t\t\t\t\t\t\t\t\treplaceIntIntValue(instructions, prevPrevInstruction, prevInstruction, stack2ndTop, stackTop, jin, IntValue::isLessThanOrEqual);\n\t\t\t\t\t\t\t\tcase IF_ACMPEQ ->\n\t\t\t\t\t\t\t\t\t\treplaceObjObjValue(instructions, prevPrevInstruction, prevInstruction, stack2ndTop, stackTop, jin,\n\t\t\t\t\t\t\t\t\t\t\t\t(a, b) -> a.isNull() && b.isNull(), // Both null --> both are equal\n\t\t\t\t\t\t\t\t\t\t\t\t(a, b) -> (a.isNull() && b.isNotNull()) || (a.isNotNull() && b.isNull())); // Nullability conflict, both cannot be equal\n\t\t\t\t\t\t\t\tcase IF_ACMPNE ->\n\t\t\t\t\t\t\t\t\t\treplaceObjObjValue(instructions, prevPrevInstruction, prevInstruction, stack2ndTop, stackTop, jin,\n\t\t\t\t\t\t\t\t\t\t\t\t(a, b) -> (a.isNull() && b.isNotNull()) || (a.isNotNull() && b.isNull()), // Nullability conflict, both cannot be equal\n\t\t\t\t\t\t\t\t\t\t\t\t(a, b) -> a.isNull() && b.isNull()); // Both null --> both are equal\n\t\t\t\t\t\t\t\tdefault -> localDirty;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (insnType == AbstractInsnNode.LOOKUPSWITCH_INSN) {\n\t\t\t\t\t\tLookupSwitchInsnNode lsin = (LookupSwitchInsnNode) instruction;\n\n\t\t\t\t\t\t// Skip if stack top is not an integer.\n\t\t\t\t\t\tif (!(stackTop instanceof IntValue intValue))\n\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\t// Find matching key in switch.\n\t\t\t\t\t\tint keyIndex = -1;\n\t\t\t\t\t\tfor (int j = 0; j < lsin.keys.size(); j++) {\n\t\t\t\t\t\t\tint key = lsin.keys.get(j);\n\t\t\t\t\t\t\tif (intValue.isEqualTo(key)) {\n\t\t\t\t\t\t\t\tkeyIndex = j;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Replace switch with goto for the appropriate control flow path.\n\t\t\t\t\t\tJumpInsnNode replacement = keyIndex == -1 ?\n\t\t\t\t\t\t\t\tnew JumpInsnNode(GOTO, lsin.dflt) :\n\t\t\t\t\t\t\t\tnew JumpInsnNode(GOTO, lsin.labels.get(keyIndex));\n\t\t\t\t\t\tinstructions.set(lsin, replacement);\n\t\t\t\t\t\tinstructions.set(prevInstruction, new InsnNode(NOP));\n\t\t\t\t\t\tlocalDirty = true;\n\t\t\t\t\t} else if (insnType == AbstractInsnNode.TABLESWITCH_INSN) {\n\t\t\t\t\t\tTableSwitchInsnNode tsin = (TableSwitchInsnNode) instruction;\n\n\t\t\t\t\t\t// Skip if stack top is not an integer.\n\t\t\t\t\t\tif (!(stackTop instanceof IntValue intValue))\n\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\t// Find matching key in switch.\n\t\t\t\t\t\tint arg = intValue.value().getAsInt();\n\t\t\t\t\t\tint keyIndex = (arg > tsin.max || arg < tsin.min) ?\n\t\t\t\t\t\t\t\t-1 : (arg - tsin.min);\n\n\t\t\t\t\t\t// Replace switch with goto for the appropriate control flow path.\n\t\t\t\t\t\tJumpInsnNode replacement = keyIndex == -1 ?\n\t\t\t\t\t\t\t\tnew JumpInsnNode(GOTO, tsin.dflt) :\n\t\t\t\t\t\t\t\tnew JumpInsnNode(GOTO, tsin.labels.get(keyIndex));\n\t\t\t\t\t\tinstructions.set(tsin, replacement);\n\t\t\t\t\t\tinstructions.set(prevInstruction, new InsnNode(NOP));\n\t\t\t\t\t\tlocalDirty = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Clear any code that is no longer accessible. If we don't do this step ASM's auto-cleanup\n\t\t\t\t// will likely leave some ugly artifacts like \"athrow\" in dead code regions.\n\t\t\t\tif (localDirty) {\n\t\t\t\t\tcontext.pruneDeadCode(node, method);\n\t\t\t\t\tdirty = true;\n\t\t\t\t}\n\t\t\t} catch (Throwable t) {\n\t\t\t\tthrow new TransformationException(\"Error encountered when folding opaque predicates\", t);\n\t\t\t}\n\t\t}\n\t\tif (dirty) {\n\t\t\tcontext.setRecomputeFrames(initialClassState.getName());\n\t\t\tcontext.setNode(bundle, initialClassState, node);\n\t\t}\n\t}\n\n\tprivate static boolean replaceIntValue(@Nonnull InsnList instructions,\n\t                                       @Nonnull AbstractInsnNode stackValueProducerInsn,\n\t                                       @Nonnull ReValue stackTopValue,\n\t                                       @Nonnull JumpInsnNode jump,\n\t                                       @Nonnull Predicate<IntValue> gotoCondition) {\n\t\tif (stackTopValue instanceof IntValue intValue) {\n\t\t\tAbstractInsnNode replacement = gotoCondition.test(intValue) ?\n\t\t\t\t\tnew JumpInsnNode(GOTO, jump.label) :\n\t\t\t\t\tnew InsnNode(NOP);\n\t\t\tinstructions.set(jump, replacement);\n\t\t\tinstructions.set(stackValueProducerInsn, new InsnNode(NOP));\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate static boolean replaceIntIntValue(@Nonnull InsnList instructions,\n\t                                          @Nonnull AbstractInsnNode stackValueProducerInsnA,\n\t                                          @Nonnull AbstractInsnNode stackValueProducerInsnB,\n\t                                          @Nonnull ReValue stackTopValueA,\n\t                                          @Nonnull ReValue stackTopValueB,\n\t                                          @Nonnull JumpInsnNode jump,\n\t                                          @Nonnull BiPredicate<IntValue, IntValue> gotoCondition) {\n\t\tif (stackTopValueA instanceof IntValue intValueA && stackTopValueB instanceof IntValue intValueB) {\n\t\t\tAbstractInsnNode replacement = gotoCondition.test(intValueA, intValueB) ?\n\t\t\t\t\tnew JumpInsnNode(GOTO, jump.label) :\n\t\t\t\t\tnew InsnNode(NOP);\n\t\t\tinstructions.set(jump, replacement);\n\t\t\tinstructions.set(stackValueProducerInsnA, new InsnNode(NOP));\n\t\t\tinstructions.set(stackValueProducerInsnB, new InsnNode(NOP));\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate static boolean replaceObjValue(@Nonnull InsnList instructions,\n\t                                       @Nonnull AbstractInsnNode stackValueProducerInsn,\n\t                                       @Nonnull ReValue stackTopValue,\n\t                                       @Nonnull JumpInsnNode jump,\n\t                                       @Nonnull Predicate<ObjectValue> gotoCondition) {\n\t\tif (stackTopValue instanceof ObjectValue objectValue) {\n\t\t\tAbstractInsnNode replacement = gotoCondition.test(objectValue) ?\n\t\t\t\t\tnew JumpInsnNode(GOTO, jump.label) :\n\t\t\t\t\tnew InsnNode(NOP);\n\t\t\tinstructions.set(jump, replacement);\n\t\t\tinstructions.set(stackValueProducerInsn, new InsnNode(NOP));\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate static boolean replaceObjObjValue(@Nonnull InsnList instructions,\n\t                                          @Nonnull AbstractInsnNode stackValueProducerInsnA,\n\t                                          @Nonnull AbstractInsnNode stackValueProducerInsnB,\n\t                                          @Nonnull ReValue stackTopValueA,\n\t                                          @Nonnull ReValue stackTopValueB,\n\t                                          @Nonnull JumpInsnNode jump,\n\t                                          @Nonnull BiPredicate<ObjectValue, ObjectValue> gotoCondition,\n\t                                          @Nonnull BiPredicate<ObjectValue, ObjectValue> fallCondition) {\n\t\tif (stackTopValueA instanceof ObjectValue objValueA && stackTopValueB instanceof ObjectValue objValueB) {\n\t\t\t// Objects are a bit more complicated than primitives, so we have separate checks for replacing as a goto\n\t\t\t// versus a fallthrough case. Additionally, if neither conditions pass we must be in a state where the values\n\t\t\t// are technically known, but not well enough to the point where we can make a decision.\n\t\t\tAbstractInsnNode replacement = gotoCondition.test(objValueA, objValueB) ? new JumpInsnNode(GOTO, jump.label) : null;\n\t\t\tif (replacement == null) replacement = fallCondition.test(objValueA, objValueB) ? new InsnNode(NOP) : null;\n\t\t\tif (replacement == null) return false;\n\t\t\tinstructions.set(jump, replacement);\n\t\t\tinstructions.set(stackValueProducerInsnA, new InsnNode(NOP));\n\t\t\tinstructions.set(stackValueProducerInsnB, new InsnNode(NOP));\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate static boolean isValueProducerOrTopDup(@Nonnull AbstractInsnNode insnNode) {\n\t\tif (isSupportedValueProducer(insnNode))\n\t\t\treturn true;\n\n\t\tint op = insnNode.getOpcode();\n\t\treturn op == DUP || op == DUP2;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<Class<? extends ClassTransformer>> recommendedPredecessors() {\n\t\treturn Set.of(\n\t\t\t\t// Folding opaque constants like \"1 + 1\" into \"2\"\n\t\t\t\tOpaqueConstantFoldingTransformer.class,\n\t\t\t\t// Folding static constants into inline-usages (some obfuscators use fields to store opaque flow values)\n\t\t\t\tStaticValueInliningTransformer.class,\n\t\t\t\t// Folding constant values stored in variables\n\t\t\t\tVariableFoldingTransformer.class\n\t\t);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<Class<? extends ClassTransformer>> dependencies() {\n\t\treturn Collections.singleton(DeadCodeRemovingTransformer.class);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Opaque predicate simplification\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/RedundantTryCatchRemovingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\nimport it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.LabelNode;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.MultiANewArrayInsnNode;\nimport org.objectweb.asm.tree.TryCatchBlockNode;\nimport org.objectweb.asm.tree.TypeInsnNode;\nimport org.objectweb.asm.tree.analysis.Frame;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.inheritance.InheritanceVertex;\nimport software.coley.recaf.services.transform.ClassTransformer;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.AsmInsnUtil;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.analysis.value.ArrayValue;\nimport software.coley.recaf.util.analysis.value.IntValue;\nimport software.coley.recaf.util.analysis.value.LongValue;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Deque;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.IdentityHashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.OptionalInt;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport static org.objectweb.asm.Opcodes.*;\n\n/**\n * A transformer that removes redundant try-catch blocks.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class RedundantTryCatchRemovingTransformer implements JvmClassTransformer {\n\tprivate static final String EX_NPE = \"java/lang/NullPointerException\";\n\tprivate static final String EX_ASE = \"java/lang/ArrayStoreException\";\n\tprivate static final String EX_AIOOBE = \"java/lang/ArrayIndexOutOfBoundsException\";\n\tprivate static final String EX_NASE = \"java/lang/NegativeArraySizeException\";\n\tprivate static final String EX_IMSE = \"java/lang/IllegalMonitorStateException\";\n\tprivate static final String EX_CCE = \"java/lang/ClassCastException\";\n\tprivate static final String EX_AE = \"java/lang/ArithmeticException\";\n\n\tprivate final InheritanceGraphService graphService;\n\tprivate InheritanceGraph inheritanceGraph;\n\tprivate ExceptionCollectionTransformer exceptionCollector;\n\n\t@Inject\n\tpublic RedundantTryCatchRemovingTransformer(@Nonnull InheritanceGraphService graphService) {\n\t\tthis.graphService = graphService;\n\t}\n\n\t@Override\n\tpublic void setup(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace) {\n\t\tinheritanceGraph = graphService.getOrCreateInheritanceGraph(workspace);\n\t}\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tboolean dirty = false;\n\t\tClassNode node = context.getNode(bundle, initialClassState);\n\t\texceptionCollector = context.getJvmTransformer(ExceptionCollectionTransformer.class);\n\t\tfor (MethodNode method : node.methods) {\n\t\t\t// Skip methods that have no code or no try-catch blocks, as they can't have redundant entries.\n\t\t\tif (method.instructions == null || method.instructions.size() == 0)\n\t\t\t\tcontinue;\n\t\t\tif (method.tryCatchBlocks == null || method.tryCatchBlocks.isEmpty())\n\t\t\t\tcontinue;\n\n\t\t\ttry {\n\t\t\t\tdirty |= pruneRedundantTryCatches(context, node, method);\n\t\t\t} catch (TransformationException ex) {\n\t\t\t\tthrow ex;\n\t\t\t} catch (Throwable t) {\n\t\t\t\tthrow new TransformationException(\"Error encountered when removing redundant try-catch blocks\", t);\n\t\t\t}\n\t\t}\n\n\t\t// If we changed anything, we need to update the class node and mark frames for recomputation.\n\t\tif (dirty) {\n\t\t\tcontext.setRecomputeFrames(initialClassState.getName());\n\t\t\tcontext.setNode(bundle, initialClassState, node);\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<Class<? extends ClassTransformer>> dependencies() {\n\t\treturn Set.of(ExceptionCollectionTransformer.class, DeadCodeRemovingTransformer.class);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Redundant try-catch removal\";\n\t}\n\n\t/**\n\t * Removes redundant try-catch entries from the given method.\n\t *\n\t * @param context\n\t * \t\tTransformation context.\n\t * @param declaringClass\n\t * \t\tClass declaring the method.\n\t * @param method\n\t * \t\tMethod to transform.\n\t *\n\t * @return {@code true} when the method was changed.\n\t *\n\t * @throws TransformationException\n\t * \t\tWhen dead-code pruning fails.\n\t */\n\tprivate boolean pruneRedundantTryCatches(@Nonnull JvmTransformerContext context,\n\t                                         @Nonnull ClassNode declaringClass,\n\t                                         @Nonnull MethodNode method) throws TransformationException {\n\t\tInsnList instructions = method.instructions;\n\n\t\t// Snapshot the original state of the try-catch blocks so we can check if we made any changes at the end.\n\t\tList<TryCatchState> originalState = snapshotStates(instructions, method.tryCatchBlocks);\n\n\t\t// Pruning occurs in multiple passes to allow later passes to take advantage of the results of earlier ones.\n\t\tList<TryCatchBlockNode> tryCatches = mergeContinuousRanges(instructions, method.tryCatchBlocks);\n\t\ttryCatches = removeExactDuplicates(instructions, tryCatches);\n\t\ttryCatches = removeShadowedRanges(instructions, tryCatches);\n\n\t\t// Last pass requires frame analysis, so we do it after the cheaper passes to minimize the number of frames we need to analyze.\n\t\tFrame<ReValue>[] frames = context.analyze(inheritanceGraph, declaringClass, method);\n\t\ttryCatches = removeImpossibleCatches(instructions, frames, tryCatches);\n\n\t\t// If the final state is the same as the original state, we don't need to update anything.\n\t\tList<TryCatchState> updatedState = snapshotStates(instructions, tryCatches);\n\t\tif (originalState.equals(updatedState))\n\t\t\treturn false;\n\n\t\t// Update the method's try-catch blocks and prune any now-unreachable code.\n\t\tmethod.tryCatchBlocks.clear();\n\t\tmethod.tryCatchBlocks.addAll(tryCatches);\n\t\tcontext.pruneDeadCode(declaringClass, method);\n\t\treturn true;\n\t}\n\n\t/**\n\t * Removes ranges that cannot possibly be utilized at runtime.\n\t *\n\t * @param instructions\n\t * \t\tMethod instructions.\n\t * @param tryCatches\n\t * \t\tMethod try-catches.\n\t *\n\t * @return Deduplicated try-catch list.\n\t */\n\t@Nonnull\n\tprivate List<TryCatchBlockNode> removeIgnoredRanges(@Nonnull InsnList instructions, @Nonnull List<TryCatchBlockNode> tryCatches) {\n\t\t// Collect all try-catch handlers keyed by their range\n\t\tMap<TryRange, List<TryCatchBlockNode>> handlersMap = new HashMap<>();\n\t\tfor (TryCatchBlockNode tryCatch : tryCatches) {\n\t\t\tint start = codeBoundaryIndex(instructions, tryCatch.start);\n\t\t\tint end = codeBoundaryIndex(instructions, tryCatch.end);\n\t\t\tif (start < end) {\n\t\t\t\tTryRange range = new TryRange(start, end);\n\t\t\t\thandlersMap.computeIfAbsent(range, r -> new ArrayList<>()).add(tryCatch);\n\t\t\t}\n\t\t}\n\n\t\t// Prune handlers of narrower (or equal) types in the collection\n\t\t//  - Gives preference to handlers that appear first in the list, since the JVM will check them first.\n\t\tnew HashMap<>(handlersMap).forEach((range, blocks) -> {\n\t\t\tSet<String> seenTypes = new HashSet<>();\n\t\t\tIterator<TryCatchBlockNode> it = blocks.iterator();\n\t\t\twhile (it.hasNext()) {\n\t\t\t\tTryCatchBlockNode block = it.next();\n\t\t\t\tString handledType = Objects.requireNonNullElse(block.type, \"java/lang/Object\");\n\t\t\t\tinner:\n\t\t\t\t{\n\t\t\t\t\tfor (String seenType : seenTypes) {\n\t\t\t\t\t\tif (inheritanceGraph.isAssignableFrom(seenType, handledType)) {\n\t\t\t\t\t\t\tit.remove();\n\t\t\t\t\t\t\tbreak inner;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tseenTypes.add(handledType);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// Retain only remaining handlers in the collection\n\t\tSet<TryCatchBlockNode> allHandlers = handlersMap.values()\n\t\t\t\t.stream()\n\t\t\t\t.flatMap(Collection::stream)\n\t\t\t\t.collect(Collectors.toCollection(() -> Collections.newSetFromMap(new IdentityHashMap<>())));\n\t\tList<TryCatchBlockNode> retained = new ArrayList<>(tryCatches);\n\t\tretained.retainAll(tryCatches.stream()\n\t\t\t\t.filter(allHandlers::contains)\n\t\t\t\t.toList());\n\t\treturn retained;\n\t}\n\n\t/**\n\t * Merges adjacent ranges with identical handler targets and catch types.\n\t * Take this example scenario where we have multiple try-catch blocks that all\n\t * catch the same exception type and point to the same handler:\n\t * <pre>{@code\n\t *      try-handler: range=[A-B] handler=D:*\n\t *      try-handler: range=[B-C] handler=D:*\n\t *      try-handler: range=[C-D] handler=D:*\n\t *      --- D handler ----\n\t *      try-handler: range=[E-F] handler=D:*\n\t *      try-handler: range=[F-G] handler=D:*\n\t *      try-handler: range=[G-H] handler=D:*\n\t * }</pre>\n\t * This can be simplified to:\n\t * <pre>{@code\n\t *      try-handler: range=[A-D] handler=D:*\n\t *      --- D handler ----\n\t *      try-handler: range=[E-H] handler=D:*\n\t * }</pre>\n\t *\n\t * @param instructions\n\t * \t\tMethod instructions.\n\t * @param tryCatches\n\t * \t\tMethod try-catches.\n\t *\n\t * @return Condensed try-catch list.\n\t */\n\t@Nonnull\n\tprivate static List<TryCatchBlockNode> mergeContinuousRanges(@Nonnull InsnList instructions, @Nonnull List<TryCatchBlockNode> tryCatches) {\n\t\t// Skip if there is only one entry, as it can't be merged with anything else.\n\t\tif (tryCatches.size() <= 1)\n\t\t\treturn new ArrayList<>(tryCatches);\n\n\t\t// Compare each entry with the previous one to see if the ranges are adjacent and have the same handler and catch type.\n\t\t// We can keep merging as long as the entries are continuous, so we update the \"previous\" entry's end to merge the ranges together.\n\t\t// This results in intermediate try-catch entries being removed from the yielded list.\n\t\tList<TryCatchBlockNode> merged = new ArrayList<>(tryCatches.size());\n\t\tTryCatchBlockNode previous = null;\n\t\tfor (TryCatchBlockNode current : tryCatches) {\n\t\t\tif (previous != null &&\n\t\t\t\t\tObjects.equals(previous.type, current.type) &&\n\t\t\t\t\tcodeBoundaryIndex(instructions, previous.end) == codeBoundaryIndex(instructions, current.start) &&\n\t\t\t\t\tcodeBoundaryIndex(instructions, previous.handler) == codeBoundaryIndex(instructions, current.handler)) {\n\t\t\t\tprevious.end = current.end;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tmerged.add(current);\n\t\t\tprevious = current;\n\t\t}\n\t\treturn merged;\n\t}\n\n\t/**\n\t * Removes exact duplicate entries while preserving the first occurrence.\n\t *\n\t * @param instructions\n\t * \t\tMethod instructions.\n\t * @param tryCatches\n\t * \t\tMethod try-catches.\n\t *\n\t * @return Deduplicated try-catch list.\n\t */\n\t@Nonnull\n\tprivate static List<TryCatchBlockNode> removeExactDuplicates(@Nonnull InsnList instructions, @Nonnull List<TryCatchBlockNode> tryCatches) {\n\t\t// Skip if there is only one entry, as it can't be merged with anything else.\n\t\tif (tryCatches.size() <= 1)\n\t\t\treturn new ArrayList<>(tryCatches);\n\n\t\t// Simple merge pass that uses a set to track seen entries.\n\t\t// We can use the snapshot state as a unique identifier for try-catch blocks, since it captures all relevant properties of the block.\n\t\tSet<TryCatchState> seen = new HashSet<>(tryCatches.size());\n\t\tList<TryCatchBlockNode> kept = new ArrayList<>(tryCatches.size());\n\t\tfor (TryCatchBlockNode tryCatch : tryCatches)\n\t\t\tif (seen.add(snapshotState(instructions, tryCatch)))\n\t\t\t\tkept.add(tryCatch);\n\t\treturn kept;\n\t}\n\n\t/**\n\t * Removes entries that are fully shadowed by an earlier, broader entry in the exception table.\n\t * Given the following {@code { start, end, handler, ex-type } } blocks:\n\t * <pre>{@code\n\t * { R, S, Q, * },\n\t * { R, S, C, * },\n\t * { R, S, S, Ljava/lang/ArrayIndexOutOfBoundsException; }\n\t * }</pre>\n\t * Only the first is going to be used.\n\t * <ul>\n\t *     <li>It appears first, so it will be checked first by the JVM</li>\n\t *     <li>Its range covers all possible instructions of the other two try blocks</li>\n\t *     <li>Its handled type is more generic <i>({@code \"*\"} is catch-all/null)</i></li>\n\t * </ul>\n\t * See: <a href=\"https://github.com/openjdk/jdk21u/blob/master/src/hotspot/share/oops/method.cpp#L227\">method.cpp#fast_exception_handler_bci_for</a>\n\t *\n\t * @param instructions\n\t * \t\tMethod instructions.\n\t * @param tryCatches\n\t * \t\tMethod try-catches.\n\t *\n\t * @return Try-catch list without shadowed entries.\n\t */\n\t@Nonnull\n\tprivate List<TryCatchBlockNode> removeShadowedRanges(@Nonnull InsnList instructions, @Nonnull List<TryCatchBlockNode> tryCatches) {\n\t\tList<TryCatchBlockNode> kept = new ArrayList<>(tryCatches.size());\n\t\tfor (TryCatchBlockNode tryCatch : tryCatches) {\n\t\t\tTryCatchState state = snapshotState(instructions, tryCatch);\n\n\t\t\t// Compare this try-catch block against all previously-kept blocks to see if\n\t\t\t// it is fully covered by any of them and has a more specific catch type.\n\t\t\tboolean shadowed = false;\n\t\t\tfor (TryCatchBlockNode previous : kept) {\n\t\t\t\tTryCatchState previousState = snapshotState(instructions, previous);\n\t\t\t\tif (previousState.covers(state) && catchesSameOrBroaderException(previous.type, tryCatch.type)) {\n\t\t\t\t\tshadowed = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If the block is not shadowed by any previous block, we keep it for the final list.\n\t\t\tif (!shadowed)\n\t\t\t\tkept.add(tryCatch);\n\t\t}\n\t\treturn kept;\n\t}\n\n\t/**\n\t * Removes entries that cannot be matched by any reachable instruction in their protected range.\n\t *\n\t * @param instructions\n\t * \t\tMethod instructions.\n\t * @param frames\n\t * \t\tMethod stack frames.\n\t * @param tryCatches\n\t * \t\tMethod try-catches.\n\t *\n\t * @return Filtered try-catch list.\n\t */\n\t@Nonnull\n\tprivate List<TryCatchBlockNode> removeImpossibleCatches(@Nonnull InsnList instructions,\n\t                                                        @Nonnull Frame<ReValue>[] frames,\n\t                                                        @Nonnull List<TryCatchBlockNode> tryCatches) {\n\t\tList<TryCatchBlockNode> kept = new ArrayList<>(tryCatches.size());\n\t\tfor (TryCatchBlockNode tryCatch : tryCatches)\n\t\t\tif (canCatchBeUsed(instructions, frames, tryCatch))\n\t\t\t\tkept.add(tryCatch);\n\t\treturn kept;\n\t}\n\n\t/**\n\t * @param instructions\n\t * \t\tMethod instructions.\n\t * @param frames\n\t * \t\tMethod stack frames.\n\t * @param tryCatch\n\t * \t\tTry-catch entry to inspect.\n\t *\n\t * @return {@code true} when the try-catch has a reachable protected instruction that may match it.\n\t */\n\tprivate boolean canCatchBeUsed(@Nonnull InsnList instructions,\n\t                               @Nonnull Frame<ReValue>[] frames,\n\t                               @Nonnull TryCatchBlockNode tryCatch) {\n\t\t// If the catch type is a type defined in the workspace, but never thrown in the workspace,\n\t\t// then it can't be caught at runtime, and we can remove the try-catch block.\n\t\tString catchType = tryCatch.type;\n\t\tif (catchType != null && isWorkspaceExceptionNeverThrown(catchType))\n\t\t\treturn false;\n\n\t\tint start = codeBoundaryIndex(instructions, tryCatch.start);\n\t\tint end = codeBoundaryIndex(instructions, tryCatch.end);\n\t\tint handler = codeBoundaryIndex(instructions, tryCatch.handler);\n\n\t\t// If the start and end are the same, or the start is beyond the end,\n\t\t// then there are no instructions protected by this try-catch, so it can't be used.\n\t\tif (start >= end)\n\t\t\treturn false;\n\n\t\t// Determine which instructions in the protected range are reachable by normal control-flow (ignoring exception edges).\n\t\tboolean[] visited = computeVisitedInstructions(instructions, frames, start, end);\n\n\t\t// Check each instruction in the protected range to see if any of them can\n\t\t// throw an exception that would be caught by this try-catch block.\n\t\tfor (int i = start; i < end; i++) {\n\t\t\t// not reachable by normal flow within the protected range\n\t\t\tif (!visited[i])\n\t\t\t\tcontinue;\n\n\t\t\t// If there is no frame for this instruction, it means the instruction is unreachable, so we can skip it.\n\t\t\tFrame<ReValue> frame = i < frames.length ? frames[i] : null;\n\t\t\tif (frame == null)\n\t\t\t\tcontinue;\n\n\t\t\t// If the catch type is null, we check for any exception throwing potential.\n\t\t\t// Otherwise, we only check for the handler's caught type.\n\t\t\tAbstractInsnNode insn = instructions.get(i);\n\t\t\tif (catchType == null) {\n\t\t\t\tif (canInsnThrowAnyException(insn, frame))\n\t\t\t\t\treturn true;\n\t\t\t} else if (canInsnThrowCaughtException(insn, frame, catchType)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Compute which instructions in the protected range of a try-catch block\n\t * are reachable by normal control-flow <i>(ignoring exception edges)</i>.\n\t * <p>\n\t * This is necessary for some edge cases where the protected range may\n\t * include instructions that are not actually reachable without an exception being thrown.\n\t * For example, consider the following code snippet:\n\t * <pre>{@code\n\t * .method public static example ()V {\n\t *     exceptions: {\n\t *         { A, C, B, Ljava/lang/RuntimeException; }\n\t *      },\n\t *     code: {\n\t *     A:\n\t *         // try-start - protected by B and nothing in here can throw RuntimeException\n\t *         goto C\n\t *     B:\n\t *         // try-handler - but also inside the range A-C\n\t *         //               we should not consider any instruction here as reachable by normal flow\n\t *         dup\n\t *         invokevirtual java/lang/RuntimeException.printStackTrace ()V\n\t *         checkcast java/lang/Throwable\n\t *         athrow\n\t *     C:\n\t *         // try-end\n\t *         goto END\n\t *     END:\n\t *         return\n\t *     }\n\t * }\n\t * }</pre>\n\t *\n\t * @param instructions\n\t * \t\tMethod instructions.\n\t * @param frames\n\t * \t\tMethod stack frames.\n\t * @param start\n\t * \t\tProtected range start.\n\t * @param end\n\t * \t\tProtected range end.\n\t *\n\t * @return Boolean array of the same length as instructions,\n\t * where each index is the visited state within the protected range.\n\t */\n\tprivate static boolean[] computeVisitedInstructions(@Nonnull InsnList instructions,\n\t                                                    @Nonnull Frame<ReValue>[] frames,\n\t                                                    int start, int end) {\n\t\t// Build normal control-flow adjacency using the shared helper (no exception edges).\n\t\tInt2ObjectMap<List<Integer>> successorMap = new Int2ObjectOpenHashMap<>();\n\t\tInt2ObjectMap<List<Integer>> predecessorMap = new Int2ObjectOpenHashMap<>();\n\n\t\t// Wrap instructions into a temporary MethodNode so populateFlowMaps can operate.\n\t\tMethodNode temp = new MethodNode();\n\t\ttemp.instructions = instructions;\n\t\ttemp.tryCatchBlocks = Collections.emptyList();\n\n\t\t// Populate flow maps without exception edges.\n\t\tAsmInsnUtil.populateFlowMaps(temp, successorMap, predecessorMap, false);\n\n\t\t// Determine entry nodes into the [start, end) range:\n\t\t// any node in range that has a predecessor outside the range, or the range start itself.\n\t\tint size = instructions.size();\n\t\tDeque<Integer> queue = new ArrayDeque<>();\n\t\tboolean[] visited = new boolean[size];\n\t\tfor (int i = start; i < end && i < size; i++) {\n\t\t\tboolean hasOutsidePredecessor = false;\n\t\t\tfor (int predecessor : predecessorMap.getOrDefault(i, Collections.emptyList())) {\n\t\t\t\tif (predecessor < start || predecessor >= end) {\n\t\t\t\t\thasOutsidePredecessor = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (hasOutsidePredecessor || i == start) {\n\t\t\t\tqueue.add(i);\n\t\t\t\tvisited[i] = true;\n\t\t\t}\n\t\t}\n\n\t\t// If we found no entry but the start instruction is reachable according to frames, include it.\n\t\tif (queue.isEmpty() && start < frames.length && frames[start] != null) {\n\t\t\tqueue.add(start);\n\t\t\tvisited[start] = true;\n\t\t}\n\n\t\t// BFS within the protected range following normal control-flow only.\n\t\twhile (!queue.isEmpty()) {\n\t\t\tint cur = queue.removeFirst();\n\t\t\tfor (int to : successorMap.getOrDefault(cur, Collections.emptyList())) {\n\t\t\t\tif (to >= start && to < end && !visited[to]) {\n\t\t\t\t\tvisited[to] = true;\n\t\t\t\t\tqueue.addLast(to);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn visited;\n\t}\n\n\t/**\n\t * @param tryCatchType\n\t * \t\tCatch type from the exception table.\n\t *\n\t * @return {@code true} when the catch type belongs to the primary resource and is never thrown there.\n\t */\n\tprivate boolean isWorkspaceExceptionNeverThrown(@Nonnull String tryCatchType) {\n\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(tryCatchType);\n\t\tif (vertex == null || vertex.isLibraryVertex() || vertex.isModule())\n\t\t\treturn false;\n\t\treturn !exceptionCollector.getThrownExceptions().contains(tryCatchType);\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tInstruction to inspect.\n\t * @param frame\n\t * \t\tStack frame before the instruction.\n\t *\n\t * @return {@code true} when the instruction may throw any exception relevant to this transformer.\n\t */\n\tprivate boolean canInsnThrowAnyException(@Nonnull AbstractInsnNode insn, @Nonnull Frame<ReValue> frame) {\n\t\t// Most method calls can throw exceptions.\n\t\t// Since we are looking for *any* potential exception, we can just assume all method calls can throw.\n\t\tif (insn instanceof MethodInsnNode)\n\t\t\treturn true;\n\n\t\treturn switch (insn.getOpcode()) {\n\t\t\tcase ATHROW, FDIV, FREM, DDIV, DREM -> true;\n\t\t\tcase ARRAYLENGTH, MONITORENTER, GETFIELD -> isReferencePossiblyNull(peekStack(frame, 0));\n\t\t\tcase PUTFIELD -> isReferencePossiblyNull(peekStack(frame, 1));\n\t\t\tcase IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD ->\n\t\t\t\t\tcanArrayAccessThrow(peekStack(frame, 1), peekStack(frame, 0));\n\t\t\tcase IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE ->\n\t\t\t\t\tcanArrayStoreThrow(insn.getOpcode(), peekStack(frame, 2), peekStack(frame, 1), peekStack(frame, 0));\n\t\t\tcase IDIV, IREM, LDIV, LREM -> isZeroDivisorPossible(peekStack(frame, 0));\n\t\t\tcase NEWARRAY, ANEWARRAY -> isNegativeSizePossible(peekStack(frame, 0));\n\t\t\tcase MULTIANEWARRAY -> isNegativeMultiArraySizePossible((MultiANewArrayInsnNode) insn, frame);\n\t\t\tcase CHECKCAST -> canCheckCastThrow((TypeInsnNode) insn, peekStack(frame, 0));\n\t\t\tcase MONITOREXIT ->\n\t\t\t\t\tisReferencePossiblyNull(peekStack(frame, 0)) || !isReferenceKnownNull(peekStack(frame, 0));\n\t\t\tdefault -> false;\n\t\t};\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tInstruction to inspect.\n\t * @param frame\n\t * \t\tStack frame before the instruction.\n\t * @param catchType\n\t * \t\tCaught exception type.\n\t *\n\t * @return {@code true} when the instruction may throw an exception assignable to the catch type.\n\t */\n\tprivate boolean canInsnThrowCaughtException(@Nonnull AbstractInsnNode insn, @Nonnull Frame<ReValue> frame, @Nonnull String catchType) {\n\t\t// While we may be able to generalize that some methods are unlikely to throw certain exceptions,\n\t\t// it's safer to assume that all method calls can throw something that would be caught by the catch block.\n\t\t// - If we wanted to add a heuristic here, we would check if the method's declaring class is a library class\n\t\t//   and if the exception type is defined in the workspace as a checked exception.\n\t\t// - Library methods are unlikely to throw user-defined exceptions, especially checked ones.\n\t\tif (insn instanceof MethodInsnNode min)\n\t\t\treturn true;\n\n\t\treturn switch (insn.getOpcode()) {\n\t\t\tcase ATHROW -> canAthrowThrow(catchType, peekStack(frame, 0));\n\t\t\tcase GETFIELD, ARRAYLENGTH, MONITORENTER -> canThrowNullPointer(catchType, peekStack(frame, 0));\n\t\t\tcase PUTFIELD -> canThrowNullPointer(catchType, peekStack(frame, 1));\n\t\t\tcase IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD ->\n\t\t\t\t\tcanArrayAccessThrow(catchType, peekStack(frame, 1), peekStack(frame, 0));\n\t\t\tcase IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE ->\n\t\t\t\t\tcanArrayStoreThrow(catchType, insn.getOpcode(), peekStack(frame, 2), peekStack(frame, 1), peekStack(frame, 0));\n\t\t\tcase IDIV, IREM, LDIV, LREM, FDIV, FREM, DDIV, DREM ->\n\t\t\t\t\tcanArithmeticThrow(catchType, insn.getOpcode(), peekStack(frame, 0));\n\t\t\tcase NEWARRAY, ANEWARRAY ->\n\t\t\t\t\tisCaughtException(catchType, EX_NASE) && isNegativeSizePossible(peekStack(frame, 0));\n\t\t\tcase MULTIANEWARRAY ->\n\t\t\t\t\tisCaughtException(catchType, EX_NASE) && isNegativeMultiArraySizePossible((MultiANewArrayInsnNode) insn, frame);\n\t\t\tcase CHECKCAST ->\n\t\t\t\t\tisCaughtException(catchType, EX_CCE) && canCheckCastThrow((TypeInsnNode) insn, peekStack(frame, 0));\n\t\t\tcase MONITOREXIT -> canMonitorExitThrow(catchType, peekStack(frame, 0));\n\t\t\tdefault -> false;\n\t\t};\n\t}\n\n\t/**\n\t * @param arrayValue\n\t * \t\tArray reference.\n\t * @param indexValue\n\t * \t\tArray index.\n\t *\n\t * @return {@code true} when an array read may throw NPE or AIOOBE.\n\t */\n\tprivate static boolean canArrayAccessThrow(@Nullable ReValue arrayValue, @Nullable ReValue indexValue) {\n\t\treturn isReferencePossiblyNull(arrayValue) || canArrayIndexThrow(arrayValue, indexValue);\n\t}\n\n\t/**\n\t * @param catchType\n\t * \t\tCaught exception type.\n\t * @param arrayValue\n\t * \t\tArray reference.\n\t * @param indexValue\n\t * \t\tArray index.\n\t *\n\t * @return {@code true} when an array read may throw an exception matched by the catch.\n\t */\n\tprivate boolean canArrayAccessThrow(@Nonnull String catchType, @Nullable ReValue arrayValue, @Nullable ReValue indexValue) {\n\t\treturn canThrowNullPointer(catchType, arrayValue) ||\n\t\t\t\t(isCaughtException(catchType, EX_AIOOBE) &&\n\t\t\t\t\t\tcanArrayIndexThrow(arrayValue, indexValue));\n\t}\n\n\t/**\n\t * @param opcode\n\t * \t\tArray-store opcode.\n\t * @param arrayValue\n\t * \t\tArray reference.\n\t * @param indexValue\n\t * \t\tArray index.\n\t * @param storedValue\n\t * \t\tValue being stored.\n\t *\n\t * @return {@code true} when an array store may throw any relevant exception.\n\t */\n\tprivate boolean canArrayStoreThrow(int opcode, @Nullable ReValue arrayValue, @Nullable ReValue indexValue, @Nullable ReValue storedValue) {\n\t\tif (isReferencePossiblyNull(arrayValue) || canArrayIndexThrow(arrayValue, indexValue))\n\t\t\treturn true;\n\t\treturn opcode == AASTORE && canArrayStoreTypeThrow(arrayValue, storedValue);\n\t}\n\n\t/**\n\t * @param catchType\n\t * \t\tCaught exception type.\n\t * @param opcode\n\t * \t\tArray-store opcode.\n\t * @param arrayValue\n\t * \t\tArray reference.\n\t * @param indexValue\n\t * \t\tArray index.\n\t * @param storedValue\n\t * \t\tValue being stored.\n\t *\n\t * @return {@code true} when an array store may throw an exception matched by the catch.\n\t */\n\tprivate boolean canArrayStoreThrow(@Nonnull String catchType, int opcode, @Nullable ReValue arrayValue, @Nullable ReValue indexValue, @Nullable ReValue storedValue) {\n\t\tif (canThrowNullPointer(catchType, arrayValue))\n\t\t\treturn true;\n\t\tif (isCaughtException(catchType, EX_AIOOBE) && canArrayIndexThrow(arrayValue, indexValue))\n\t\t\treturn true;\n\t\treturn opcode == AASTORE\n\t\t\t\t&& isCaughtException(catchType, EX_ASE)\n\t\t\t\t&& canArrayStoreTypeThrow(arrayValue, storedValue);\n\t}\n\n\t/**\n\t * @param catchType\n\t * \t\tCaught exception type.\n\t * @param opcode\n\t * \t\tArithmetic opcode.\n\t * @param divisor\n\t * \t\tDivisor or remainder operand.\n\t *\n\t * @return {@code true} when the arithmetic instruction may throw an exception matched by the catch.\n\t */\n\tprivate boolean canArithmeticThrow(@Nonnull String catchType, int opcode, @Nullable ReValue divisor) {\n\t\t// Skip if the catch doesn't even catch ArithmeticException.\n\t\tif (!isCaughtException(catchType, EX_AE))\n\t\t\treturn false;\n\n\t\t// For floating point division and remainder, the JVM does not throw an exception on division by zero.\n\t\t// - 1F / 0F -> Infinity\n\t\t// - 1F % 0F -> NaN\n\t\tif (opcode == FDIV || opcode == FREM || opcode == DDIV || opcode == DREM)\n\t\t\treturn false;\n\n\t\t// For integer division and remainder, the JVM throws ArithmeticException on division by zero.\n\t\treturn isZeroDivisorPossible(divisor);\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tDivisor value.\n\t *\n\t * @return {@code true} when the divisor is unknown or zero.\n\t */\n\tprivate static boolean isZeroDivisorPossible(@Nullable ReValue value) {\n\t\tif (value instanceof IntValue intValue)\n\t\t\treturn intValue.value().isEmpty() || intValue.value().getAsInt() == 0;\n\t\tif (value instanceof LongValue longValue)\n\t\t\treturn longValue.value().isEmpty() || longValue.value().getAsLong() == 0L;\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param catchType\n\t * \t\tCaught exception type.\n\t * @param objectValue\n\t * \t\tThrown object value.\n\t *\n\t * @return {@code true} when {@code athrow} may be matched by the catch.\n\t */\n\tprivate boolean canAthrowThrow(@Nonnull String catchType, @Nullable ReValue objectValue) {\n\t\tif (objectValue instanceof ObjectValue object) {\n\t\t\t// 'athrow' with a null reference will throw NPE, so we need to check for that possibility first.\n\t\t\tif (object.isNull())\n\t\t\t\treturn isCaughtException(catchType, EX_NPE);\n\n\t\t\t// If the reference is not null, but we don't know its type, we have to assume it could be anything,\n\t\t\t// including a type that would be caught by the catch block.\n\t\t\tif (!object.isNotNull() && isCaughtException(catchType, EX_NPE))\n\t\t\t\treturn true;\n\n\t\t\t// If we know the reference is not null, and we know its type,\n\t\t\t// we can check if that type could be caught by the catch block.\n\t\t\tType valueType = object.type();\n\t\t\tif (valueType.getSort() != Type.OBJECT)\n\t\t\t\treturn true;\n\t\t\treturn canReferenceRuntimeTypeMatch(valueType, catchType);\n\t\t}\n\n\t\t// If we don't know anything about the reference, lets just assume it can throw.\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param catchType\n\t * \t\tCaught exception type.\n\t * @param monitorValue\n\t * \t\tMonitor reference.\n\t *\n\t * @return {@code true} when {@code monitorexit} may throw an exception matched by the catch.\n\t */\n\tprivate boolean canMonitorExitThrow(@Nonnull String catchType, @Nullable ReValue monitorValue) {\n\t\treturn canThrowNullPointer(catchType, monitorValue)\n\t\t\t\t|| (isCaughtException(catchType, EX_IMSE)\n\t\t\t\t&& !isReferenceKnownNull(monitorValue));\n\t}\n\n\t/**\n\t * @param cast\n\t * \t\tCast instruction.\n\t * @param value\n\t * \t\tValue being cast.\n\t *\n\t * @return {@code true} when the cast may throw {@link ClassCastException}.\n\t */\n\tprivate boolean canCheckCastThrow(@Nonnull TypeInsnNode cast, @Nullable ReValue value) {\n\t\t// Null can be cast to any type.\n\t\tif (isReferenceKnownNull(value))\n\t\t\treturn false;\n\n\t\t// If we don't know what type the value is, we can't safely assume the cast will succeed.\n\t\tType sourceType = value == null ? null : value.type();\n\t\tif (sourceType == null)\n\t\t\treturn true;\n\n\t\t// Sanity check to ensure the value is actually a reference type, since only reference types can be cast.\n\t\tint sourceSort = sourceType.getSort();\n\t\tif (sourceSort != Type.OBJECT && sourceSort != Type.ARRAY)\n\t\t\treturn false;\n\n\t\t// Finally check if the target type is assignable from the source type.\n\t\tType targetType = Type.getObjectType(cast.desc);\n\t\treturn !isAssignable(targetType, sourceType);\n\t}\n\n\t/**\n\t * @param arrayValue\n\t * \t\tArray reference.\n\t * @param storedValue\n\t * \t\tValue being stored.\n\t *\n\t * @return {@code true} when {@code aastore} may throw {@link ArrayStoreException}.\n\t */\n\tprivate boolean canArrayStoreTypeThrow(@Nullable ReValue arrayValue, @Nullable ReValue storedValue) {\n\t\tif (isReferenceKnownNull(arrayValue))\n\t\t\treturn false;\n\t\tif (storedValue instanceof ObjectValue object && object.isNull())\n\t\t\treturn false;\n\t\tif (!(arrayValue instanceof ArrayValue array))\n\t\t\treturn true;\n\n\t\tType arrayType = array.type();\n\t\tif (arrayType.getSort() != Type.ARRAY)\n\t\t\treturn true;\n\n\t\tType componentType = Types.undimension(arrayType);\n\t\tType valueType = storedValue == null ? null : storedValue.type();\n\t\tif (valueType == null)\n\t\t\treturn true;\n\n\t\treturn !isAssignable(componentType, valueType);\n\t}\n\n\t/**\n\t * @param targetType\n\t * \t\tTarget type.\n\t * @param valueType\n\t * \t\tValue type.\n\t *\n\t * @return {@code true} when the value type is assignable to the target type.\n\t */\n\tprivate boolean isAssignable(@Nonnull Type targetType, @Nonnull Type valueType) {\n\t\t// Base case, same type.\n\t\tif (targetType.equals(valueType))\n\t\t\treturn true;\n\n\t\t// Arrays can only be assigned to Object, and Object can be assigned from any array.\n\t\tint targetSort = targetType.getSort();\n\t\tint valueSort = valueType.getSort();\n\t\tif (targetSort == Type.ARRAY || valueSort == Type.ARRAY)\n\t\t\treturn targetSort == Type.OBJECT && Types.OBJECT_TYPE.equals(targetType);\n\n\t\t// For non-object types, these are not assignable between one another.\n\t\t// This method is used strictly for checking casts and object type operations.\n\t\t//\n\t\t// If either type is not an object, then the cast is only valid if both types are the same primitive type,\n\t\t// which is already handled by the equality check above.\n\t\tif (targetSort != Type.OBJECT || valueSort != Type.OBJECT)\n\t\t\treturn false;\n\n\t\t// Check inheritance graph for assignability of reference types.\n\t\treturn inheritanceGraph.isAssignableFrom(targetType.getInternalName(), valueType.getInternalName());\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tArray size value.\n\t *\n\t * @return {@code true} when the size is unknown or negative.\n\t */\n\tprivate static boolean isNegativeSizePossible(@Nullable ReValue value) {\n\t\tif (value instanceof IntValue intValue && intValue.value().isPresent())\n\t\t\treturn intValue.value().getAsInt() < 0;\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param multiArray\n\t * \t\tMulti-array instruction.\n\t * @param frame\n\t * \t\tStack frame before the instruction.\n\t *\n\t * @return {@code true} when any dimension is unknown or negative.\n\t */\n\tprivate static boolean isNegativeMultiArraySizePossible(@Nonnull MultiANewArrayInsnNode multiArray, @Nonnull Frame<ReValue> frame) {\n\t\tfor (int i = 0; i < multiArray.dims; i++)\n\t\t\tif (isNegativeSizePossible(peekStack(frame, i)))\n\t\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param arrayValue\n\t * \t\tArray reference.\n\t * @param indexValue\n\t * \t\tArray index.\n\t *\n\t * @return {@code true} when the index is unknown or outside known bounds of a non-null array.\n\t */\n\tprivate static boolean canArrayIndexThrow(@Nullable ReValue arrayValue, @Nullable ReValue indexValue) {\n\t\t// This would be a NPE instead.\n\t\tif (isReferenceKnownNull(arrayValue))\n\t\t\treturn false;\n\n\t\t// If we don't know the index, or we don't know the array length, then we have to assume the index could be out of bounds.\n\t\tif (!(indexValue instanceof IntValue index) || index.value().isEmpty())\n\t\t\treturn true;\n\t\tif (!(arrayValue instanceof ArrayValue array))\n\t\t\treturn true;\n\t\tOptionalInt length = array.getFirstDimensionLength();\n\t\tif (length.isEmpty())\n\t\t\treturn true;\n\n\t\t// Check if the index is outside the bounds of the array length.\n\t\tint literalIndex = index.value().getAsInt();\n\t\treturn literalIndex < 0 || literalIndex >= length.getAsInt();\n\t}\n\n\t/**\n\t * @param catchType\n\t * \t\tCaught exception type.\n\t * @param value\n\t * \t\tReference value.\n\t *\n\t * @return {@code true} when the reference may trigger a caught {@link NullPointerException}.\n\t */\n\tprivate boolean canThrowNullPointer(@Nonnull String catchType, @Nullable ReValue value) {\n\t\treturn isCaughtException(catchType, EX_NPE) && isReferencePossiblyNull(value);\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tThrown exception type.\n\t * @param catchType\n\t * \t\tCaught exception type.\n\t *\n\t * @return {@code true} when the runtime reference may still match the catch.\n\t */\n\tprivate boolean canReferenceRuntimeTypeMatch(@Nonnull Type type, @Nonnull String catchType) {\n\t\tif (type.getSort() != Type.OBJECT)\n\t\t\treturn false;\n\n\t\tString typeName = type.getInternalName();\n\t\treturn isCaughtException(catchType, typeName)\n\t\t\t\t|| inheritanceGraph.isAssignableFrom(typeName, catchType);\n\t}\n\n\t/**\n\t * @param catchType\n\t * \t\tCatch type.\n\t * @param thrownType\n\t * \t\tThrown type.\n\t *\n\t * @return {@code true} when the catch type can handle the thrown type.\n\t */\n\tprivate boolean isCaughtException(@Nonnull String catchType, @Nonnull String thrownType) {\n\t\treturn catchType.equals(thrownType)\n\t\t\t\t|| inheritanceGraph.isAssignableFrom(catchType, thrownType);\n\t}\n\n\t/**\n\t * @param broaderType\n\t * \t\tPossibly broader catch type.\n\t * @param narrowerType\n\t * \t\tPossibly narrower catch type.\n\t *\n\t * @return {@code true} when the first type catches the same or broader set of exceptions.\n\t */\n\tprivate boolean catchesSameOrBroaderException(@Nullable String broaderType, @Nullable String narrowerType) {\n\t\tif (broaderType == null)\n\t\t\treturn true;\n\t\tif (narrowerType == null)\n\t\t\treturn false;\n\t\treturn broaderType.equals(narrowerType)\n\t\t\t\t|| inheritanceGraph.isAssignableFrom(broaderType, narrowerType);\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tReference candidate.\n\t *\n\t * @return {@code true} when the value may be {@code null}.\n\t */\n\tprivate static boolean isReferencePossiblyNull(@Nullable ReValue value) {\n\t\treturn !(value instanceof ObjectValue object) || !object.isNotNull();\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tReference candidate.\n\t *\n\t * @return {@code true} when the value is definitely {@code null}.\n\t */\n\tprivate static boolean isReferenceKnownNull(@Nullable ReValue value) {\n\t\treturn value instanceof ObjectValue object && object.isNull();\n\t}\n\n\t/**\n\t * Reads a stack value relative to the stack top.\n\t *\n\t * @param frame\n\t * \t\tMethod frame before an instruction.\n\t * @param offsetFromTop\n\t * \t\tOffset from the stack top.\n\t *\n\t * @return Stack value, or {@code null} when unavailable.\n\t */\n\t@Nullable\n\tprivate static ReValue peekStack(@Nonnull Frame<ReValue> frame, int offsetFromTop) {\n\t\tint index = frame.getStackSize() - 1 - offsetFromTop;\n\t\tif (index < 0)\n\t\t\treturn null;\n\t\treturn frame.getStack(index);\n\t}\n\n\t/**\n\t * @param instructions\n\t * \t\tMethod instructions.\n\t * @param label\n\t * \t\tBoundary label.\n\t *\n\t * @return Index of the first executable instruction at or after the label.\n\t */\n\tprivate static int codeBoundaryIndex(@Nonnull InsnList instructions, @Nonnull LabelNode label) {\n\t\tAbstractInsnNode current = label;\n\t\tcurrent = AsmInsnUtil.getNextInsn(current);\n\t\treturn current == null ?\n\t\t\t\tinstructions.size() :\n\t\t\t\tinstructions.indexOf(current);\n\t}\n\n\t/**\n\t * @param instructions\n\t * \t\tMethod instructions.\n\t * @param tryCatch\n\t * \t\tTry-catch entry.\n\t *\n\t * @return Snapshot of the try-catch's effective range and handler.\n\t */\n\t@Nonnull\n\tprivate static TryCatchState snapshotState(@Nonnull InsnList instructions, @Nonnull TryCatchBlockNode tryCatch) {\n\t\treturn new TryCatchState(codeBoundaryIndex(instructions, tryCatch.start),\n\t\t\t\tcodeBoundaryIndex(instructions, tryCatch.end),\n\t\t\t\tcodeBoundaryIndex(instructions, tryCatch.handler),\n\t\t\t\ttryCatch.type);\n\t}\n\n\t/**\n\t * @param instructions\n\t * \t\tMethod instructions.\n\t * @param tryCatches\n\t * \t\tTry-catch entries.\n\t *\n\t * @return Snapshots of all entries in declaration order.\n\t */\n\t@Nonnull\n\tprivate static List<TryCatchState> snapshotStates(@Nonnull InsnList instructions, @Nonnull List<TryCatchBlockNode> tryCatches) {\n\t\tList<TryCatchState> states = new ArrayList<>(tryCatches.size());\n\t\tfor (TryCatchBlockNode tryCatch : tryCatches)\n\t\t\tstates.add(snapshotState(instructions, tryCatch));\n\t\treturn states;\n\t}\n\n\t/**\n\t * Simplified try-catch signature used for hashing/comparisons without {@link LabelNode} references.\n\t *\n\t * @param start\n\t * \t\tProtected range start.\n\t * @param end\n\t * \t\tProtected range end.\n\t * @param handler\n\t * \t\tHandler range start.\n\t * @param type\n\t * \t\tHandled exception type. Can be {@code null} for catch-all handlers.\n\t */\n\tprivate record TryCatchState(int start, int end, int handler, @Nullable String type) {\n\t\tprivate boolean covers(@Nonnull TryCatchState other) {\n\t\t\treturn start <= other.start && end >= other.end;\n\t\t}\n\t}\n\n\t/**\n\t * Simplified range.\n\t *\n\t * @param start\n\t * \t\tProtected range start.\n\t * @param end\n\t * \t\tProtected range end.\n\t */\n\tprivate record TryRange(int start, int end) {}\n\n\t/**\n\t * Collection of try catch blocks.\n\t *\n\t * @param blocks\n\t * \t\tWrapped list of blocks.\n\t * @param seenTypes\n\t * \t\tObserved types handled by the blocks.\n\t */\n\tprivate record Handlers(@Nonnull List<TryCatchBlockNode> blocks, @Nonnull Set<String> seenTypes) {\n\t\tprivate Handlers() {\n\t\t\tthis(new ArrayList<>(), new HashSet<>());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/SourceNameRestorationTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport regexodus.Pattern;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.util.visitors.SkippingClassVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * A transformer that creates mappings to rename obfuscated classes based on any remaining {@code SourceFile} attribute.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class SourceNameRestorationTransformer implements JvmClassTransformer {\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\t// Inner classes will retain the source attribute of their outer class, and\n\t\t// we only want to rename the top-level/outer class.\n\t\tif (initialClassState.isInnerClass())\n\t\t\treturn;\n\n\t\t// Extract the source-file attribute contents, see if its reasonable\n\t\t// and then add it to the mappings.\n\t\tinitialClassState.getClassReader().accept(new SkippingClassVisitor() {\n\t\t\tprivate static final Pattern SOURCE_NAME_PATTERN = RegexUtil.pattern(\"\\\\w{1, 50}\\\\.(?:java|kt)\");\n\n\t\t\t@Override\n\t\t\tpublic void visitSource(String source, String debug) {\n\t\t\t\tif (source == null || source.isBlank() || !SOURCE_NAME_PATTERN.matches(source))\n\t\t\t\t\treturn;\n\t\t\t\tString name = initialClassState.getName();\n\t\t\t\tString sourceName = source.substring(0, Math.max(source.lastIndexOf(\".java\"), source.lastIndexOf(\".kt\")));\n\t\t\t\tString packageName = initialClassState.getPackageName();\n\t\t\t\tString restoredName = packageName == null ? sourceName : packageName + '/' + sourceName;\n\t\t\t\tcontext.getMappings().addClass(name, restoredName);\n\t\t\t}\n\t\t}, 0);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Source name restoration\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/StaticValueCollectionTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.FieldInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.analysis.Frame;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.ReAnalyzer;\nimport software.coley.recaf.util.analysis.ReInterpreter;\nimport software.coley.recaf.util.analysis.lookup.GetStaticLookup;\nimport software.coley.recaf.util.analysis.value.IllegalValueException;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * A transformer that collects values of {@code static final} field assignments.\n * <ul>\n *     <li>Intended to be used in combination with {@link StaticValueInliningTransformer}.</li>\n *     <li>Can also be used as a {@link GetStaticLookup}</li>\n * </ul>\n *\n * @author Matt Coley\n */\n@Dependent\npublic class StaticValueCollectionTransformer implements JvmClassTransformer, GetStaticLookup {\n\tprivate final Map<String, StaticValues> classValues = new ConcurrentHashMap<>();\n\tprivate final Map<String, EffectivelyFinalFields> classFinals = new ConcurrentHashMap<>();\n\tprivate final InheritanceGraphService graphService;\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate InheritanceGraph inheritanceGraph;\n\n\t@Inject\n\tpublic StaticValueCollectionTransformer(@Nonnull WorkspaceManager workspaceManager, @Nonnull InheritanceGraphService graphService) {\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.graphService = graphService;\n\t}\n\n\t/**\n\t * @param className\n\t * \t\tName of class defining the field.\n\t * @param fieldName\n\t * \t\tField name.\n\t * @param fieldDesc\n\t * \t\tField descriptor.\n\t *\n\t * @return Static value wrapper if known, otherwise {@code null}.\n\t */\n\t@Nullable\n\tpublic ReValue getStaticValue(@Nonnull String className, @Nonnull String fieldName, @Nonnull String fieldDesc) {\n\t\tStaticValues values = classValues.get(className);\n\t\tif (values == null)\n\t\t\treturn null;\n\t\treturn values.get(fieldName, fieldDesc);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ReValue get(@Nonnull FieldInsnNode field) {\n\t\tReValue value = getStaticValue(field.owner, field.name, field.desc);\n\t\tif (value == null) {\n\t\t\ttry {\n\t\t\t\treturn Objects.requireNonNull(ReValue.ofType(Type.getType(field.desc), Nullness.UNKNOWN));\n\t\t\t} catch (Exception ex) {\n\t\t\t\t// Should never fail since fields cannot have illegal types like primitive void.\n\t\t\t\tthrow new IllegalStateException(ex);\n\t\t\t}\n\t\t}\n\t\treturn value;\n\t}\n\n\t@Override\n\tpublic boolean hasLookup(@Nonnull FieldInsnNode field) {\n\t\treturn getStaticValue(field.owner, field.name, field.desc) != null;\n\t}\n\n\t@Override\n\tpublic void setup(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace) {\n\t\tinheritanceGraph = graphService.getOrCreateInheritanceGraph(workspace);\n\t}\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tStaticValues valuesContainer = new StaticValues();\n\t\tEffectivelyFinalFields finalContainer = new EffectivelyFinalFields();\n\n\t\t// TODO: Make some config options for this\n\t\t//  - Option to make unsafe assumptions\n\t\t//    - treat all effectively final candidates as actually final\n\t\t//  - Option to scan other classes for references to our fields to have more thorough 'effective-final' checking\n\t\t//    - will be slower, but it will be opt-in and off by default\n\n\t\t// Populate initial values based on field's default value attribute\n\t\tfor (FieldMember field : initialClassState.getFields()) {\n\t\t\tif (!field.hasStaticModifier())\n\t\t\t\tcontinue;\n\n\t\t\t// Add to effectively-final container if it is 'static final'\n\t\t\t// If the field is private add it to the \"maybe\" effectively-final list, and we'll confirm it later\n\t\t\tif (field.hasFinalModifier())\n\t\t\t\tfinalContainer.add(field.getName(), field.getDescriptor());\n\t\t\telse if (field.hasPrivateModifier())\n\t\t\t\t// We can only assume private fields are effectively-final if nothing outside the <clinit> writes to them.\n\t\t\t\t// Any other level of access can be written to by child classes or classes in the same package.\n\t\t\t\tfinalContainer.addMaybe(field.getName(), field.getDescriptor());\n\t\t\t// TODO: As mentioned above, we can add another 'else' case here for non-private fields\n\t\t\t//  but then we need to make sure no other classes write to those fields. So its more computational work...\n\n\t\t\t// Skip if there is no default value\n\t\t\tObject defaultValue = field.getDefaultValue();\n\t\t\tif (defaultValue == null)\n\t\t\t\tcontinue;\n\n\t\t\t// Skip if the value cannot be mapped to our representation\n\t\t\tReValue mappedValue = Unchecked.getOr(() -> ReValue.ofConstant(defaultValue), null);\n\t\t\tif (mappedValue == null)\n\t\t\t\tcontinue;\n\n\t\t\t// Store the value\n\t\t\tvaluesContainer.put(field.getName(), field.getDescriptor(), mappedValue);\n\t\t}\n\n\t\t// Visit <clinit> of classes and collect static field values of primitives\n\t\tString className = initialClassState.getName();\n\t\tif (initialClassState.getDeclaredMethod(\"<clinit>\", \"()V\") != null) {\n\t\t\tClassNode node = context.getNode(bundle, initialClassState);\n\n\t\t\t// Find the static initializer and determine which fields are \"effectively-final\"\n\t\t\tMethodNode clinit = null;\n\t\t\tfor (MethodNode method : node.methods) {\n\t\t\t\tif ((method.access & Opcodes.ACC_STATIC) != 0 && method.name.equals(\"<clinit>\") && method.desc.equals(\"()V\")) {\n\t\t\t\t\tclinit = method;\n\t\t\t\t} else if (method.instructions != null) {\n\t\t\t\t\t// Any put-static to a field in our class means it is not effectively-final because the method is not the static initializer\n\t\t\t\t\tfor (AbstractInsnNode instruction : method.instructions) {\n\t\t\t\t\t\tif (instruction.getOpcode() == Opcodes.PUTSTATIC && instruction instanceof FieldInsnNode fieldInsn) {\n\t\t\t\t\t\t\t// Skip if not targeting our class\n\t\t\t\t\t\t\tif (!fieldInsn.owner.equals(className))\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\tString fieldName = fieldInsn.name;\n\t\t\t\t\t\t\tString fieldDesc = fieldInsn.desc;\n\t\t\t\t\t\t\tfinalContainer.removeMaybe(fieldName, fieldDesc);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfinalContainer.commitMaybeIntoEffectivelyFinals();\n\n\t\t\t// Only analyze if we see static setters\n\t\t\tif (clinit != null && hasStaticSetters(clinit)) {\n\t\t\t\ttry {\n\t\t\t\t\tReAnalyzer analyzer = context.newAnalyzer(inheritanceGraph, node, clinit);\n\t\t\t\t\tReInterpreter interpreter = analyzer.getInterpreter();\n\t\t\t\t\tFrame<ReValue>[] frames = analyzer.analyze(node.name, clinit);\n\t\t\t\t\tAbstractInsnNode[] instructions = clinit.instructions.toArray();\n\t\t\t\t\tfor (int i = 0; i < instructions.length; i++) {\n\t\t\t\t\t\tAbstractInsnNode instruction = instructions[i];\n\t\t\t\t\t\tif (instruction.getOpcode() == Opcodes.PUTSTATIC && instruction instanceof FieldInsnNode fieldInsn) {\n\t\t\t\t\t\t\t// Skip if not targeting our class\n\t\t\t\t\t\t\tif (!fieldInsn.owner.equals(className))\n\t\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\t\t// Skip if the field is not final, or effectively final\n\t\t\t\t\t\t\tString fieldName = fieldInsn.name;\n\t\t\t\t\t\t\tString fieldDesc = fieldInsn.desc;\n\t\t\t\t\t\t\tif (!finalContainer.contains(fieldName, fieldDesc))\n\t\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\t\t// Merge the static value state\n\t\t\t\t\t\t\tFrame<ReValue> frame = frames[i];\n\t\t\t\t\t\t\tReValue existingValue = valuesContainer.get(fieldName, fieldDesc);\n\t\t\t\t\t\t\tReValue stackValue = frame.getStack(frame.getStackSize() - 1);\n\t\t\t\t\t\t\tReValue merged = existingValue == null ? stackValue : interpreter.merge(existingValue, stackValue);\n\t\t\t\t\t\t\tvaluesContainer.put(fieldName, fieldDesc, merged);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Any static final fields that have not been assigned are assumed to contain their default values.\n\t\t\t\t\tvaluesContainer.commitRemainingAsDefaults(finalContainer);\n\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\tthrow new TransformationException(\"Error encountered when computing static constants\", t);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// If there is no static initializer, then assume the values are defaults (o for primitives, null for objects)\n\t\t\ttry {\n\t\t\t\tvaluesContainer.commitRemainingAsDefaults(finalContainer);\n\t\t\t} catch (IllegalValueException ex) {\n\t\t\t\tthrow new TransformationException(\"Error encountered when computing default constant values\", ex);\n\t\t\t}\n\t\t}\n\n\t\t// Record the values for the target class if we recorded at least one value\n\t\tif (!valuesContainer.staticFieldValues.isEmpty())\n\t\t\tclassValues.put(className, valuesContainer);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Static value collection\";\n\t}\n\n\t/**\n\t * @param method\n\t * \t\tMethod to check for {@link Opcodes#PUTSTATIC} use.\n\t *\n\t * @return {@code true} when the method has a {@link Opcodes#PUTSTATIC} instruction.\n\t */\n\tprivate static boolean hasStaticSetters(@Nonnull MethodNode method) {\n\t\tif (method.instructions == null)\n\t\t\treturn false;\n\t\tfor (AbstractInsnNode abstractInsnNode : method.instructions)\n\t\t\tif (abstractInsnNode.getOpcode() == Opcodes.PUTSTATIC) return true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tField name.\n\t * @param desc\n\t * \t\tField descriptor.\n\t *\n\t * @return Field key.\n\t */\n\t@Nonnull\n\tprivate static String key(@Nonnull String name, @Nonnull String desc) {\n\t\treturn name + ':' + desc;\n\t}\n\n\t/**\n\t * Wrapper/utility for field finality storage/lookups.\n\t */\n\tprivate static class EffectivelyFinalFields {\n\t\tprivate Set<String> finalFieldKeys;\n\t\tprivate Set<String> maybeFinalFieldKeys;\n\n\t\t/**\n\t\t * Add a {@code static final} field.\n\t\t *\n\t\t * @param name\n\t\t * \t\tField name.\n\t\t * @param desc\n\t\t * \t\tField descriptor.\n\t\t */\n\t\tpublic void add(@Nonnull String name, @Nonnull String desc) {\n\t\t\tif (finalFieldKeys == null)\n\t\t\t\tfinalFieldKeys = new HashSet<>();\n\t\t\tfinalFieldKeys.add(key(name, desc));\n\t\t}\n\n\t\t/**\n\t\t * Add a {@code static} field that <i>may be</i> effectively final.\n\t\t *\n\t\t * @param name\n\t\t * \t\tField name.\n\t\t * @param desc\n\t\t * \t\tField descriptor.\n\t\t */\n\t\tpublic void addMaybe(@Nonnull String name, @Nonnull String desc) {\n\t\t\tif (maybeFinalFieldKeys == null)\n\t\t\t\tmaybeFinalFieldKeys = new HashSet<>();\n\t\t\tmaybeFinalFieldKeys.add(key(name, desc));\n\t\t}\n\n\t\t/**\n\t\t * Remove a field from being considered possibly effectively final.\n\t\t *\n\t\t * @param name\n\t\t * \t\tField name.\n\t\t * @param desc\n\t\t * \t\tField descriptor.\n\t\t */\n\t\tpublic void removeMaybe(@Nonnull String name, @Nonnull String desc) {\n\t\t\tif (maybeFinalFieldKeys != null)\n\t\t\t\tmaybeFinalFieldKeys.remove(key(name, desc));\n\t\t}\n\n\t\t/**\n\t\t * Commit all possible effectively final fields into the final fields set.\n\t\t */\n\t\tpublic void commitMaybeIntoEffectivelyFinals() {\n\t\t\tif (maybeFinalFieldKeys != null)\n\t\t\t\tif (finalFieldKeys == null)\n\t\t\t\t\tfinalFieldKeys = new HashSet<>(maybeFinalFieldKeys);\n\t\t\t\telse\n\t\t\t\t\tfinalFieldKeys.addAll(maybeFinalFieldKeys);\n\t\t}\n\n\t\t/**\n\t\t * @param name\n\t\t * \t\tField name.\n\t\t * @param desc\n\t\t * \t\tField descriptor.\n\t\t *\n\t\t * @return {@code true} when the field is {@code final} or effectively {@code final}.\n\t\t */\n\t\tpublic boolean contains(@Nonnull String name, @Nonnull String desc) {\n\t\t\tif (finalFieldKeys == null)\n\t\t\t\treturn false;\n\t\t\treturn finalFieldKeys.contains(key(name, desc));\n\t\t}\n\t}\n\n\t/**\n\t * Wrapper/utility for field value storage/lookups.\n\t */\n\tprivate static class StaticValues {\n\t\tprivate final Map<String, ReValue> staticFieldValues = new ConcurrentHashMap<>();\n\n\t\tprivate void put(@Nonnull String name, @Nonnull String desc, @Nonnull ReValue value) {\n\t\t\tstaticFieldValues.put(key(name, desc), value);\n\t\t}\n\n\t\t@Nullable\n\t\tprivate ReValue get(@Nonnull String name, @Nonnull String desc) {\n\t\t\treturn staticFieldValues.get(key(name, desc));\n\t\t}\n\n\t\tprivate void commitRemainingAsDefaults(@Nonnull EffectivelyFinalFields finalFields) throws IllegalValueException {\n\t\t\tif (finalFields.finalFieldKeys == null)\n\t\t\t\treturn;\n\n\t\t\t// By the point this is called, the final fields container will have committed any \"maybe\" candidates\n\t\t\t// that are valid to being actually final. Thus, if we see anything in the final field set, we will\n\t\t\t// initialize them with default values here.\n\t\t\tfor (String key : finalFields.finalFieldKeys)\n\t\t\t\tif (!staticFieldValues.containsKey(key)) {\n\t\t\t\t\tType fieldType = Type.getType(key.substring(key.indexOf(':') + 1));\n\t\t\t\t\tstaticFieldValues.put(key, ReValue.ofTypeDefaultValue(fieldType));\n\t\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/StaticValueInliningTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.FieldInsnNode;\nimport org.objectweb.asm.tree.InsnNode;\nimport org.objectweb.asm.tree.LdcInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.transform.ClassTransformer;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.AsmInsnUtil;\nimport software.coley.recaf.util.analysis.value.DoubleValue;\nimport software.coley.recaf.util.analysis.value.FloatValue;\nimport software.coley.recaf.util.analysis.value.IntValue;\nimport software.coley.recaf.util.analysis.value.LongValue;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.StringValue;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collections;\nimport java.util.Set;\n\n/**\n * A transformer that inlines values from {@link StaticValueCollectionTransformer}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class StaticValueInliningTransformer implements JvmClassTransformer {\n\t@Override\n\t@SuppressWarnings(\"OptionalGetWithoutIsPresent\")\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tvar staticValueCollector = context.getJvmTransformer(StaticValueCollectionTransformer.class);\n\n\t\tboolean dirty = false;\n\t\tClassNode node = context.getNode(bundle, initialClassState);\n\t\tfor (MethodNode method : node.methods) {\n\t\t\t// Skip static initializer and abstract methods\n\t\t\tif (method.name.contains(\"<clinit>\") || method.instructions == null)\n\t\t\t\tcontinue;\n\n\t\t\tfor (AbstractInsnNode instruction : method.instructions) {\n\t\t\t\tif (instruction instanceof FieldInsnNode fieldInsn) {\n\t\t\t\t\t// Get the known value of the static field\n\t\t\t\t\tReValue value = staticValueCollector.getStaticValue(fieldInsn.owner, fieldInsn.name, fieldInsn.desc);\n\t\t\t\t\tif (value == null)\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t// Replace static get calls with their known values\n\t\t\t\t\tif (value.hasKnownValue()) {\n\t\t\t\t\t\tswitch (value) {\n\t\t\t\t\t\t\tcase IntValue intValue -> {\n\t\t\t\t\t\t\t\tmethod.instructions.set(instruction, AsmInsnUtil.intToInsn(intValue.value().getAsInt()));\n\t\t\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase LongValue longValue -> {\n\t\t\t\t\t\t\t\tmethod.instructions.set(instruction, AsmInsnUtil.longToInsn(longValue.value().getAsLong()));\n\t\t\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase DoubleValue doubleValue -> {\n\t\t\t\t\t\t\t\tmethod.instructions.set(instruction, AsmInsnUtil.doubleToInsn(doubleValue.value().getAsDouble()));\n\t\t\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase FloatValue floatValue -> {\n\t\t\t\t\t\t\t\tmethod.instructions.set(instruction, AsmInsnUtil.floatToInsn((float) floatValue.value().getAsDouble()));\n\t\t\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase StringValue stringValue -> {\n\t\t\t\t\t\t\t\tmethod.instructions.set(instruction, new LdcInsnNode(stringValue.getText().get()));\n\t\t\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdefault -> {\n\t\t\t\t\t\t\t\t// no-op\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (value == ObjectValue.VAL_OBJECT_NULL) {\n\t\t\t\t\t\tmethod.instructions.set(instruction, new InsnNode(Opcodes.ACONST_NULL));\n\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Record transformed class if we made any changes\n\t\tif (dirty)\n\t\t\tcontext.setNode(bundle, initialClassState, node);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Static value inlining\";\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<Class<? extends ClassTransformer>> dependencies() {\n\t\treturn Collections.singleton(StaticValueCollectionTransformer.class);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/UnknownAttributeRemovingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.FieldNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.RecordComponentNode;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.visitors.UnknownAttributeRemovingVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * A transformer that removes unknown attributes.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class UnknownAttributeRemovingTransformer implements JvmClassTransformer {\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tif (context.isNode(bundle, initialClassState)) {\n\t\t\tClassNode node = context.getNode(bundle, initialClassState);\n\t\t\tif (node.attrs != null)\n\t\t\t\tnode.attrs.clear();\n\t\t\tfor (FieldNode field : node.fields)\n\t\t\t\tif (field.attrs != null)\n\t\t\t\t\tfield.attrs.clear();\n\t\t\tfor (MethodNode method : node.methods)\n\t\t\t\tif (method.attrs != null)\n\t\t\t\t\tmethod.attrs.clear();\n\t\t\tif (node.recordComponents != null)\n\t\t\t\tfor (RecordComponentNode recordComponent : node.recordComponents)\n\t\t\t\t\tif (recordComponent.attrs != null)\n\t\t\t\t\t\trecordComponent.attrs.clear();\n\t\t} else {\n\t\t\tClassReader reader = new ClassReader(context.getBytecode(bundle, initialClassState));\n\t\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\t\treader.accept(new UnknownAttributeRemovingVisitor(writer), 0);\n\t\t\tcontext.setBytecode(bundle, initialClassState, writer.toByteArray());\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Unknown attribute removal\";\n\t}\n\n\t@Override\n\tpublic boolean pruneAfterNoWork() {\n\t\t// Other transformers should not introduce junk attributes,\n\t\t// so once the work is done there is no need to re-process classes on following passes.\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/VariableFoldingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.IincInsnNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.InsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.VarInsnNode;\nimport org.objectweb.asm.tree.analysis.Frame;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.transform.ClassTransformer;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Deque;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.NavigableSet;\nimport java.util.Set;\nimport java.util.TreeSet;\n\nimport static java.util.Collections.emptyList;\nimport static java.util.Collections.emptyNavigableSet;\nimport static software.coley.recaf.util.AsmInsnUtil.*;\n\n/**\n * A transformer that folds redundant variable use.\n * <br>\n * You should use {@link OpaqueConstantFoldingTransformer} after using this for {@code POP} cleanup.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class VariableFoldingTransformer implements JvmClassTransformer {\n\tprivate final InheritanceGraphService graphService;\n\tprivate InheritanceGraph inheritanceGraph;\n\n\t@Inject\n\tpublic VariableFoldingTransformer(@Nonnull InheritanceGraphService graphService) {\n\t\tthis.graphService = graphService;\n\t}\n\n\t@Override\n\tpublic void setup(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace) {\n\t\tinheritanceGraph = graphService.getOrCreateInheritanceGraph(workspace);\n\t}\n\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tboolean dirty = false;\n\t\tString className = initialClassState.getName();\n\t\tClassNode node = context.getNode(bundle, initialClassState);\n\t\tfor (MethodNode method : node.methods) {\n\t\t\t// Skip if abstract.\n\t\t\tInsnList instructions = method.instructions;\n\t\t\tif (instructions == null)\n\t\t\t\tcontinue;\n\n\t\t\t// Build successor and predecessor maps modeling control flow.\n\t\t\tInt2ObjectMap<List<Integer>> successorMap = new Int2ObjectArrayMap<>();\n\t\t\tInt2ObjectMap<List<Integer>> predecessorMap = new Int2ObjectArrayMap<>();\n\t\t\tpopulateFlowMaps(method, successorMap, predecessorMap);\n\n\t\t\t// Compute liveness using iterative backward data-flow analysis.\n\t\t\tint size = instructions.size();\n\t\t\tList<Set<Integer>> inLive = new ArrayList<>(size);\n\t\t\tList<Set<Integer>> outLive = new ArrayList<>(size);\n\t\t\tpopulateLiveness(method, inLive, outLive, successorMap, predecessorMap);\n\n\t\t\t// Populate local access state.\n\t\t\tInt2ObjectMap<LocalAccessState> accessStates = new Int2ObjectArrayMap<>();\n\t\t\tpopulateVariableAccessStates(method, accessStates);\n\n\t\t\t// Fold in reverse order.\n\t\t\tFrame<ReValue>[] frames = context.analyze(inheritanceGraph, node, method);\n\t\t\tfor (int i = size - 1; i >= 0; i--) {\n\t\t\t\tAbstractInsnNode insn = instructions.get(i);\n\t\t\t\tint op = insn.getOpcode();\n\n\t\t\t\t// Skip if dead code (unreachable code not analyzed).\n\t\t\t\tFrame<ReValue> frame = frames[i];\n\t\t\t\tif (frame == null)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif (isVarLoad(op) && insn instanceof VarInsnNode vin) {\n\t\t\t\t\t// Fold constant loads.\n\t\t\t\t\tReValue val = frame.getLocal(vin.var);\n\t\t\t\t\tif (val != null && val.hasKnownValue()) {\n\t\t\t\t\t\tAbstractInsnNode replacement = OpaqueConstantFoldingTransformer.toInsn(val);\n\t\t\t\t\t\tif (replacement != null) {\n\t\t\t\t\t\t\tinstructions.set(insn, replacement);\n\t\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Get variable index and type from store/iinc.\n\t\t\t\t\tint var;\n\t\t\t\t\tType type;\n\t\t\t\t\tif (insn instanceof VarInsnNode vin) {\n\t\t\t\t\t\tvar = vin.var;\n\t\t\t\t\t\ttype = getTypeForVarInsn(vin);\n\t\t\t\t\t} else if (insn instanceof IincInsnNode iinc) {\n\t\t\t\t\t\tvar = iinc.var;\n\t\t\t\t\t\ttype = Type.INT_TYPE;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remove dead stores/iinc.\n\t\t\t\t\tSet<Integer> liveAfter = outLive.get(i);\n\t\t\t\t\tif (!liveAfter.contains(var)) {\n\t\t\t\t\t\tif (op == IINC) {\n\t\t\t\t\t\t\tinstructions.set(insn, new InsnNode(NOP));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tAbstractInsnNode prev = insn.getPrevious();\n\t\t\t\t\t\t\tif (OpaqueConstantFoldingTransformer.isSupportedValueProducer(prev)) {\n\t\t\t\t\t\t\t\tinstructions.set(prev, new InsnNode(NOP));\n\t\t\t\t\t\t\t\tinstructions.set(insn, new InsnNode(NOP));\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tinstructions.set(insn, new InsnNode(type.getSize() == 2 ? POP2 : POP));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdirty = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle redundant variable copies.\n\t\t\tint[] keys = accessStates.keySet().toIntArray();\n\t\t\tfor (int keyY : keys) {\n\t\t\t\tLocalAccessState stateY = accessStates.get(keyY);\n\t\t\t\tint slotY = slotFromKey(keyY);\n\t\t\t\tint typeSort = typeSortFromKey(keyY);\n\n\t\t\t\t// Redundancy only applies if there is a single write to Y.\n\t\t\t\tNavigableSet<LocalAccess> writesY = stateY.getWrites();\n\t\t\t\tif (writesY.size() != 1)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Get the single write instruction to Y.\n\t\t\t\tLocalAccess writeAccessY = writesY.first();\n\t\t\t\tAbstractInsnNode writeInsnY = writeAccessY.instruction;\n\t\t\t\tif (!(writeInsnY instanceof VarInsnNode vinY && isVarStore(vinY.getOpcode())))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Check if the prior instruction is the copy source of 'load x'.\n\t\t\t\tAbstractInsnNode prevInsn = writeInsnY.getPrevious();\n\t\t\t\tif (!(prevInsn instanceof VarInsnNode vinX && isVarLoad(vinX.getOpcode())))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Check if the source variable is different and has a known state.\n\t\t\t\tint slotX = vinX.var;\n\t\t\t\tint keyX = key(slotX, typeSort);\n\t\t\t\tif (slotX == slotY || !accessStates.containsKey(keyX))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Check if the store to Y is redundant.\n\t\t\t\tif (isRedundantStore(accessStates, instructions, successorMap, writeAccessY.offset, slotX, slotY, typeSort)) {\n\t\t\t\t\t// Replace usages.\n\t\t\t\t\treplaceRedundantVariableUsage(instructions, slotX, slotY, typeSort);\n\n\t\t\t\t\t// Update state.\n\t\t\t\t\tstateY.getWrites().clear();\n\t\t\t\t\tstateY.getReads().clear();\n\n\t\t\t\t\t// Replace store with POP.\n\t\t\t\t\tType varType = Types.fromSort(typeSort);\n\t\t\t\t\tinstructions.set(writeInsnY, new InsnNode(varType.getSize() == 2 ? POP2 : POP));\n\t\t\t\t\tdirty = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (dirty)\n\t\t\tcontext.setNode(bundle, initialClassState, node);\n\t}\n\n\t/**\n\t * Populate variable liveness for the method.\n\t * <p>\n\t * Liveness is the set of variables that are live at each instruction.\n\t * A good reference for this can be found <a href=\"https://users.cs.northwestern.edu/%7Esimonec/files/Teaching/CAT/slides/DFA_part1.pdf\">here</a>.\n\t *\n\t * @param method\n\t * \t\tMethod to analyze.\n\t * @param inLive\n\t * \t\tOutput in-live sets.\n\t * @param outLive\n\t * \t\tOutput out-live sets.\n\t * @param successorMap\n\t * \t\tFlow successor map.\n\t * @param predecessorMap\n\t * \t\tFlow predecessor map.\n\t */\n\tprivate static void populateLiveness(@Nonnull MethodNode method,\n\t                                     @Nonnull List<Set<Integer>> inLive,\n\t                                     @Nonnull List<Set<Integer>> outLive,\n\t                                     @Nonnull Int2ObjectMap<List<Integer>> successorMap,\n\t                                     @Nonnull Int2ObjectMap<List<Integer>> predecessorMap) {\n\t\t// Initialize empty live sets for every instruction.\n\t\tInsnList instructions = method.instructions;\n\t\tint size = instructions.size();\n\t\tfor (int i = 0; i < size; i++) {\n\t\t\tinLive.add(new HashSet<>());\n\t\t\toutLive.add(new HashSet<>());\n\t\t}\n\n\t\t// Assume we need to check every instruction once.\n\t\tDeque<Integer> unprocessed = new ArrayDeque<>();\n\t\tfor (int i = 0; i < size; i++)\n\t\t\tunprocessed.add(i);\n\n\t\t// Iterate until no changes occur.\n\t\twhile (!unprocessed.isEmpty()) {\n\t\t\t// Next instruction to process.\n\t\t\tint i = unprocessed.poll();\n\t\t\tAbstractInsnNode insn = instructions.get(i);\n\t\t\tint op = insn.getOpcode();\n\n\t\t\t// A variable is live after the instruction if it is going to be used later, which can\n\t\t\t// be determined by looking at all successor instructions.\n\t\t\tSet<Integer> out = outLive.get(i);\n\t\t\tout.clear();\n\t\t\tfor (int s : successorMap.getOrDefault(i, emptyList()))\n\t\t\t\tout.addAll(inLive.get(s));\n\n\t\t\t// Compute gen/kill sets for the instruction.\n\t\t\t// - Gen: Variables read by the instruction.\n\t\t\t// - Kill: Variables written to by the instruction.\n\t\t\tSet<Integer> gen = new HashSet<>();\n\t\t\tSet<Integer> kill = new HashSet<>();\n\t\t\tint var;\n\t\t\tif (insn instanceof VarInsnNode vin) {\n\t\t\t\tvar = vin.var;\n\t\t\t\tif (isVarLoad(op)) gen.add(var);\n\t\t\t\tif (isVarStore(op)) kill.add(var);\n\t\t\t} else if (insn instanceof IincInsnNode iinc) {\n\t\t\t\t// IINC both reads and writes the variable.\n\t\t\t\tvar = iinc.var;\n\t\t\t\tgen.add(var);\n\t\t\t\tkill.add(var);\n\t\t\t}\n\n\t\t\t// Anything read by this instruction (gen) is live before it.\n\t\t\t// Anything written to by this instruction (kill) is not live going forward.\n\t\t\t// Everything else live after the instruction (out) is also live before it.\n\t\t\tSet<Integer> newIn = new HashSet<>(gen);\n\t\t\tSet<Integer> temp = new HashSet<>(out);\n\t\t\ttemp.removeAll(kill);\n\t\t\tnewIn.addAll(temp);\n\n\t\t\t// If the in-live set changed we discovered new live variables.\n\t\t\t// We need to re-check all instructions that can reach this one.\n\t\t\tif (!newIn.equals(inLive.get(i))) {\n\t\t\t\tinLive.set(i, newIn);\n\n\t\t\t\t// Queue predecessors for re-check.\n\t\t\t\tunprocessed.addAll(predecessorMap.getOrDefault(i, emptyList()));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Populate variable access states for the method.\n\t * <p>\n\t * This tracks when variables are read from and written to, which is necessary\n\t * for determining when variable copies are redundant and can be folded.\n\t *\n\t * @param method\n\t * \t\tMethod to analyze.\n\t * @param accessStates\n\t * \t\tOutput variable access states.\n\t */\n\tprivate static void populateVariableAccessStates(@Nonnull MethodNode method, @Nonnull Int2ObjectMap<LocalAccessState> accessStates) {\n\t\tInsnList instructions = method.instructions;\n\t\tint size = instructions.size();\n\t\tboolean isStatic = AccessFlag.isStatic(method.access);\n\t\tint paramSlot = isStatic ? 0 : 1;\n\n\t\t// Add implicit 'this' if non-static.\n\t\tif (!isStatic) {\n\t\t\tLocalAccessState thisState = new LocalAccessState(0);\n\t\t\tthisState.addWrite(-1, new VarInsnNode(ASTORE, 0));\n\t\t\taccessStates.put(key(0, Type.OBJECT), thisState);\n\t\t}\n\n\t\t// Add explicit parameters.\n\t\tfor (Type argType : Type.getArgumentTypes(method.desc)) {\n\t\t\tLocalAccessState paramState = new LocalAccessState(paramSlot);\n\t\t\tparamState.addWrite(-1, createVarStore(paramSlot, argType));\n\t\t\taccessStates.put(key(paramSlot, argType.getSort()), paramState);\n\t\t\tparamSlot += argType.getSize();\n\t\t}\n\n\t\t// Populate accesses from instructions.\n\t\tfor (int i = 0; i < size; i++) {\n\t\t\tAbstractInsnNode insn = instructions.get(i);\n\t\t\tint op = insn.getOpcode();\n\t\t\tif (insn instanceof VarInsnNode vin) {\n\t\t\t\t// Variable load/store.\n\t\t\t\tType type = getTypeForVarInsn(vin);\n\t\t\t\tLocalAccessState state = accessStates.computeIfAbsent(key(vin.var, type.getSort()), _ -> new LocalAccessState(vin.var));\n\t\t\t\tif (isVarLoad(op))\n\t\t\t\t\tstate.addRead(i, vin);\n\t\t\t\telse if (isVarStore(op))\n\t\t\t\t\tstate.addWrite(i, vin);\n\t\t\t} else if (op == IINC && insn instanceof IincInsnNode iinc) {\n\t\t\t\t// Increment is both a read and write.\n\t\t\t\tLocalAccessState state = accessStates.computeIfAbsent(key(iinc.var, Type.INT), _ -> new LocalAccessState(iinc.var));\n\t\t\t\tstate.addRead(i, iinc);\n\t\t\t\tstate.addWrite(i, iinc);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @param accessStates\n\t * \t\tVariable access states.\n\t * @param instructions\n\t * \t\tMethod instructions.\n\t * @param successorMap\n\t * \t\tControl flow successor map.\n\t * @param storeIndexY\n\t * \t\tInstructions index of the store to Y.\n\t * @param slotX\n\t * \t\tThe original variable index.\n\t * @param slotY\n\t * \t\tThe target variable index to check for redundancy.\n\t * @param typeSort\n\t * \t\tThe variable's type sort. See {@link Type#getSort()}.\n\t *\n\t * @return {@code true} when the store to Y is redundant and can be replaced safely.\n\t */\n\tprivate static boolean isRedundantStore(@Nonnull Int2ObjectMap<LocalAccessState> accessStates,\n\t                                        @Nonnull InsnList instructions,\n\t                                        @Nonnull Int2ObjectMap<List<Integer>> successorMap,\n\t                                        int storeIndexY, int slotX, int slotY, int typeSort) {\n\t\tLocalAccessState stateX = accessStates.get(key(slotX, typeSort));\n\t\tLocalAccessState stateY = accessStates.get(key(slotY, typeSort));\n\t\tif (stateX == null || stateY == null)\n\t\t\treturn false;\n\n\t\t// Single write to Y.\n\t\tNavigableSet<LocalAccess> writesY = stateY.getWrites();\n\t\tif (writesY.size() != 1)\n\t\t\treturn false;\n\t\tLocalAccess writeY = writesY.first();\n\t\tif (writeY.offset != storeIndexY)\n\t\t\treturn false;\n\n\t\t// Prior is load from X.\n\t\tAbstractInsnNode storeInsnY = instructions.get(storeIndexY);\n\t\tAbstractInsnNode prev = storeInsnY.getPrevious();\n\t\tif (!(prev instanceof VarInsnNode vinX && vinX.var == slotX && isMatchingLoad(typeSort, vinX.getOpcode())))\n\t\t\treturn false;\n\n\t\t// No intervening writes to X or Y between load X and store Y.\n\t\tfor (int j = instructions.indexOf(prev) + 1; j < storeIndexY; j++) {\n\t\t\tAbstractInsnNode ins = instructions.get(j);\n\t\t\tif (ins instanceof VarInsnNode vin) {\n\t\t\t\tif ((vin.var == slotX || vin.var == slotY) && isVarStore(vin.getOpcode()))\n\t\t\t\t\treturn false;\n\t\t\t} else if (ins instanceof IincInsnNode iinc) {\n\t\t\t\tif (iinc.var == slotX || iinc.var == slotY)\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// X defined before Y.\n\t\tNavigableSet<LocalAccess> writesX = stateX.getWrites();\n\t\tif (writesX.isEmpty())\n\t\t\treturn false;\n\t\tif (writesX.first().offset >= storeIndexY)\n\t\t\treturn false;\n\n\t\t// Check no updates to X on any path from store Y to reads of Y.\n\t\t//  -1 unvisited, 0 unchanged, 1 changed\n\t\tint size = instructions.size();\n\t\tint[] state = new int[size];\n\t\tArrays.fill(state, -1);\n\n\t\t// Add initial successors of store Y to the queue and mark\n\t\t// them as unchanged (0) since we haven't seen any updates to X yet.\n\t\tDeque<Integer> unprocessed = new ArrayDeque<>();\n\t\tfor (int s : successorMap.getOrDefault(storeIndexY, emptyList())) {\n\t\t\tstate[s] = 0;\n\t\t\tunprocessed.add(s);\n\t\t}\n\n\t\t// Iteratively propagate state until we reach all reads of Y.\n\t\twhile (!unprocessed.isEmpty()) {\n\t\t\tint i = unprocessed.poll();\n\t\t\tAbstractInsnNode insn = instructions.get(i);\n\t\t\tint op = insn.getOpcode();\n\t\t\tboolean isXWrite = (isVarStore(op) && ((VarInsnNode) insn).var == slotX) ||\n\t\t\t\t\t(op == IINC && ((IincInsnNode) insn).var == slotX);\n\t\t\tint newState = state[i];\n\t\t\tif (isXWrite)\n\t\t\t\tnewState = 1;\n\t\t\tfor (int s : successorMap.getOrDefault(i, emptyList())) {\n\t\t\t\tint oldState = state[s];\n\t\t\t\tint newStatePropagated = Math.max(oldState == -1 ? newState : oldState, newState);\n\t\t\t\tif (newStatePropagated != oldState) {\n\t\t\t\t\tstate[s] = newStatePropagated;\n\t\t\t\t\tunprocessed.add(s);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If any read of Y is reachable from store Y without seeing an\n\t\t// update to X (state 0 - unchanged), then the store is not redundant.\n\t\tfor (LocalAccess access : stateY.getReads())\n\t\t\tif (state[access.offset] == 1)\n\t\t\t\treturn false;\n\t\treturn true;\n\t}\n\n\t/**\n\t * Replaces usage of the redundant variable with the original variable.\n\t *\n\t * @param instructions\n\t * \t\tInstructions to modify.\n\t * @param slotX\n\t * \t\tThe original variable index.\n\t * @param slotY\n\t * \t\tThe target variable index that is redundant.\n\t * @param typeSort\n\t * \t\tThe variable's type sort. See {@link Type#getSort()}.\n\t */\n\tprivate static void replaceRedundantVariableUsage(@Nonnull InsnList instructions, int slotX, int slotY, int typeSort) {\n\t\tAbstractInsnNode replacement = createVarLoad(slotX, typeSort);\n\t\tfor (int i = 0; i < instructions.size(); i++) {\n\t\t\tAbstractInsnNode insn = instructions.get(i);\n\t\t\tif (insn instanceof VarInsnNode vin && vin.var == slotY && isVarLoad(vin.getOpcode())) {\n\t\t\t\tinstructions.set(insn, replacement.clone(null));\n\t\t\t} else if (insn instanceof IincInsnNode iinc && iinc.var == slotY) {\n\t\t\t\tiinc.var = slotX;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @param typeSort\n\t * \t\tThe variable's type sort. See {@link Type#getSort()}.\n\t * @param opcode\n\t * \t\tVariable load opcode.\n\t *\n\t * @return {@code true} when opcode matches expected type.\n\t */\n\tprivate static boolean isMatchingLoad(int typeSort, int opcode) {\n\t\treturn switch (typeSort) {\n\t\t\tcase Type.INT -> opcode == ILOAD;\n\t\t\tcase Type.FLOAT -> opcode == FLOAD;\n\t\t\tcase Type.LONG -> opcode == LLOAD;\n\t\t\tcase Type.DOUBLE -> opcode == DLOAD;\n\t\t\tcase Type.OBJECT, Type.ARRAY -> opcode == ALOAD;\n\t\t\tdefault -> false;\n\t\t};\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<Class<? extends ClassTransformer>> recommendedSuccessors() {\n\t\t// This transformer results in the creation of a lot of POP/POP2 instructions.\n\t\t// The stack-operation folding transformer can clean up afterward.\n\t\treturn Collections.singleton(OpaqueConstantFoldingTransformer.class);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Variable folding\";\n\t}\n\n\t/**\n\t * @param slot\n\t * \t\tVariable index.\n\t * @param typeSort\n\t * \t\tVariable type sort. See {@link Type#getSort()}.\n\t *\n\t * @return Key of typed variable.\n\t */\n\tprivate static int key(int slot, int typeSort) {\n\t\treturn slot | (typeSort << 16);\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tKey of typed variable.\n\t *\n\t * @return Variable index stored in the key.\n\t */\n\tprivate static int slotFromKey(int key) {\n\t\treturn key & 0xFFFF;\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tKey of typed variable.\n\t *\n\t * @return Variable type sort. See {@link Type#getSort()}.\n\t */\n\tprivate static int typeSortFromKey(int key) {\n\t\treturn key >> 16;\n\t}\n\n\t/**\n\t * State tracking for when variables are read from and written to.\n\t * <br>\n\t * These states are keyed by {@link #key(int, int)} which ensures that multiple types can target the\n\t * same variable index without issue.\n\t */\n\tprivate static class LocalAccessState {\n\t\tprivate final int index;\n\t\tprivate NavigableSet<LocalAccess> reads;\n\t\tprivate NavigableSet<LocalAccess> writes;\n\n\t\tprivate LocalAccessState(int index) {\n\t\t\tthis.index = index;\n\t\t}\n\n\t\tpublic void addRead(int offset, @Nonnull AbstractInsnNode instruction) {\n\t\t\tif (reads == null)\n\t\t\t\treads = new TreeSet<>();\n\t\t\treads.add(new LocalAccess(offset, instruction));\n\t\t}\n\n\t\tpublic void addWrite(int offset, @Nonnull AbstractInsnNode instruction) {\n\t\t\tif (writes == null)\n\t\t\t\twrites = new TreeSet<>();\n\t\t\twrites.add(new LocalAccess(offset, instruction));\n\t\t}\n\n\t\t@Nonnull\n\t\tpublic NavigableSet<LocalAccess> getReads() {\n\t\t\tif (reads == null)\n\t\t\t\treturn emptyNavigableSet();\n\t\t\treturn reads;\n\t\t}\n\n\t\t@Nonnull\n\t\tpublic NavigableSet<LocalAccess> getWrites() {\n\t\t\tif (writes == null)\n\t\t\t\treturn emptyNavigableSet();\n\t\t\treturn writes;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \"LocalAccessState{\" +\n\t\t\t\t\t\"index=\" + index +\n\t\t\t\t\t\", reads=\" + reads +\n\t\t\t\t\t\", writes=\" + writes +\n\t\t\t\t\t'}';\n\t\t}\n\t}\n\n\t/**\n\t * Model of an instruction at some code offset that accesses a variable.\n\t *\n\t * @param offset\n\t * \t\tInstruction index in {@link MethodNode#instructions}.\n\t * @param instruction\n\t * \t\tInstruction accessing a local variable.\n\t */\n\tprivate record LocalAccess(int offset, @Nonnull AbstractInsnNode instruction) implements Comparable<LocalAccess> {\n\t\t@Override\n\t\tpublic int compareTo(@Nonnull LocalAccess o) {\n\t\t\treturn Integer.compare(offset, o.offset);\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/generic/VariableTableNormalizingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.generic;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.LabelNode;\nimport org.objectweb.asm.tree.LocalVariableNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.ParameterNode;\nimport org.objectweb.asm.tree.VarInsnNode;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.util.AsmInsnUtil;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.TreeMap;\n\n/**\n * Replaces all local variables with basic patterns.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class VariableTableNormalizingTransformer implements JvmClassTransformer {\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\tboolean dirty = false;\n\t\tString className = initialClassState.getName();\n\t\tClassNode node = context.getNode(bundle, initialClassState);\n\t\tfor (MethodNode method : node.methods) {\n\t\t\tType[] argumentTypes = Type.getMethodType(method.desc).getArgumentTypes();\n\t\t\tboolean isStatic = AccessFlag.isStatic(method.access);\n\t\t\tint slot = isStatic ? 0 : 1;\n\n\t\t\tInsnList instructions = method.instructions;\n\t\t\tif (instructions == null) {\n\t\t\t\tList<ParameterNode> parameters = new ArrayList<>(argumentTypes.length);\n\t\t\t\tfor (Type argumentType : argumentTypes) {\n\t\t\t\t\tparameters.add(new ParameterNode(\"param\" + slot, 0));\n\t\t\t\t\tslot += argumentType.getSize();\n\t\t\t\t}\n\n\t\t\t\tif (!Objects.equals(parameters, method.parameters)) {\n\t\t\t\t\tmethod.parameters = parameters;\n\t\t\t\t\tmethod.localVariables = null;\n\t\t\t\t\tdirty = true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Its easier just to add labels than to trust that each method\n\t\t\t\t// has them in valid locations to span the whole method.\n\t\t\t\tLabelNode start = new LabelNode();\n\t\t\t\tLabelNode end = new LabelNode();\n\t\t\t\tmethod.instructions.insert(start);\n\t\t\t\tmethod.instructions.add(end);\n\n\t\t\t\t// Populate map of:\n\t\t\t\t//  variable index ---> variable name & type\n\t\t\t\tMap<Integer, NameType> slotToTempVariable = new TreeMap<>();\n\t\t\t\tif (!isStatic) {\n\t\t\t\t\tslotToTempVariable.put(0, new NameType(\"this\", Type.getObjectType(initialClassState.getName())));\n\t\t\t\t}\n\t\t\t\tfor (Type argumentType : argumentTypes) {\n\t\t\t\t\tslotToTempVariable.put(slot, new NameType(\"param\" + slot, argumentType));\n\t\t\t\t\tslot += argumentType.getSize();\n\t\t\t\t}\n\t\t\t\tfor (AbstractInsnNode insn : method.instructions) {\n\t\t\t\t\tif (insn instanceof VarInsnNode varInsn) {\n\t\t\t\t\t\tint varSlot = varInsn.var;\n\t\t\t\t\t\tslotToTempVariable.computeIfAbsent(varSlot, v -> {\n\t\t\t\t\t\t\tType varType = AsmInsnUtil.getTypeForVarInsn(varInsn);\n\t\t\t\t\t\t\treturn new NameType(\"v\" + v, varType);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Flatten map to list, check if we already have matching variables.\n\t\t\t\tList<NameType> newNameTypes = slotToTempVariable.values().stream().toList();\n\t\t\t\tList<NameType> existingNameTypes = method.localVariables == null ? Collections.emptyList() : method.localVariables.stream()\n\t\t\t\t\t\t.filter(l -> Types.isValidDesc(l.desc))\n\t\t\t\t\t\t.map(l -> new NameType(l.name, Type.getType(l.desc)))\n\t\t\t\t\t\t.toList();\n\t\t\t\tif (!Objects.equals(newNameTypes, existingNameTypes)) {\n\t\t\t\t\t// Not a match, replace what was found.\n\t\t\t\t\tList<LocalVariableNode> variables = slotToTempVariable.entrySet().stream()\n\t\t\t\t\t\t\t.map(e -> {\n\t\t\t\t\t\t\t\tint varSlot = e.getKey();\n\t\t\t\t\t\t\t\tNameType nameType = e.getValue();\n\t\t\t\t\t\t\t\treturn new LocalVariableNode(nameType.name(), nameType.type().getDescriptor(), null, start, end, varSlot);\n\t\t\t\t\t\t\t}).toList();\n\t\t\t\t\tmethod.parameters = null;\n\t\t\t\t\tmethod.localVariables = variables;\n\t\t\t\t\tdirty = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (dirty)\n\t\t\tcontext.setNode(bundle, initialClassState, node);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Variable table normalization\";\n\t}\n\n\tprivate record NameType(@Nonnull String name, @Nonnull Type type) {}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/deobfuscation/transform/specific/DashOpaqueSeedFoldingTransformer.java",
    "content": "package software.coley.recaf.services.deobfuscation.transform.specific;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.InsnNode;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.List;\n\nimport static org.objectweb.asm.Opcodes.ICONST_5;\nimport static org.objectweb.asm.Opcodes.IRETURN;\n\n/**\n * A transformer that folds opaque number providers in DashO obfuscated samples.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class DashOpaqueSeedFoldingTransformer implements JvmClassTransformer {\n\t@Override\n\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo initialClassState) throws TransformationException {\n\t\t// The generated DashO class has two methods:\n\t\t// - Seed supplier\n\t\t// - String decryptor\n\t\tList<MethodMember> methods = initialClassState.getMethods();\n\t\tif (methods.size() != 2)\n\t\t\treturn;\n\n\t\t// Must have one method that is the seed getter.\n\t\tif (methods.stream().noneMatch(m -> m.hasStaticModifier() && m.hasPublicModifier() && m.getDescriptor().equals(\"()I\")))\n\t\t\treturn;\n\n\t\t// Must have one method that is the string decryptor.\n\t\tif (methods.stream().noneMatch(m -> m.hasStaticModifier() && m.hasPublicModifier() && m.getDescriptor().matches(\"\\\\(.+\\\\)Ljava/lang/String;\")))\n\t\t\treturn;\n\n\t\t// Take the seed supplier and fold its return value.\n\t\tClassNode node = context.getNode(bundle, initialClassState);\n\t\tfor (MethodNode method : node.methods) {\n\t\t\tif (!method.desc.equals(\"()I\"))\n\t\t\t\tcontinue;\n\n\t\t\t// Skip if abstract.\n\t\t\tInsnList instructions = method.instructions;\n\t\t\tif (instructions == null)\n\t\t\t\tcontinue;\n\n\t\t\t// Sanity check existence of initial pattern.\n\t\t\t//  new Random().nextInt(X) + 1\n\t\t\tboolean found = false;\n\t\t\tfor (AbstractInsnNode insn : instructions) {\n\t\t\t\tif (insn instanceof MethodInsnNode min) {\n\t\t\t\t\tif (min.owner.equals(\"java/util/Random\") && min.name.equals(\"nextInt\") && min.desc.equals(\"(I)I\")) {\n\t\t\t\t\t\tfound = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!found)\n\t\t\t\tcontinue;\n\n\t\t\t// Just make it return a positive number.\n\t\t\t// All observed usages assume the value is positive and use opaque predicates such as:\n\t\t\t//  n * X % n != 0\n\t\t\t// Where X is some constant and n is the seed value.\n\t\t\tif (method.tryCatchBlocks != null)\n\t\t\t\tmethod.tryCatchBlocks.clear();\n\t\t\tinstructions.clear();\n\t\t\tinstructions.add(new InsnNode(ICONST_5));\n\t\t\tinstructions.add(new InsnNode(IRETURN));\n\n\t\t\t// Update once we're done with this method.\n\t\t\tcontext.setNode(bundle, initialClassState, node);\n\t\t\treturn;\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"DashO Opaque Seed Folding\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/file/RecafDirectoriesConfig.java",
    "content": "package software.coley.recaf.services.file;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.launch.LaunchCommand;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\nimport software.coley.recaf.util.IOUtil;\nimport software.coley.recaf.util.PlatformType;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\n/**\n * Config for common paths for Recaf.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\n@ExcludeFromJacocoGeneratedReport(justification = \"We do not access the config directories in tests (avoiding IO is preferred anyways)\")\npublic class RecafDirectoriesConfig extends BasicConfigContainer implements ConfigContainer {\n\tprivate static final Logger logger = Logging.get(RecafDirectoriesConfig.class);\n\tprivate final Path baseDirectory = createBaseDirectory();\n\tprivate final Path agentDirectory = resolveDirectory(\"agent\");\n\tprivate final Path configDirectory = resolveDirectory(\"config\");\n\tprivate final Path logsDirectory = resolveDirectory(\"logs\");\n\tprivate final Path pluginDirectory = resolveDirectory(\"plugins\");\n\tprivate final Path styleDirectory = resolveDirectory(\"style\");\n\tprivate final Path scriptsDirectory = resolveDirectory(\"scripts\");\n\tprivate final Path tempDirectory = resolveDirectory(\"temp\");\n\tprivate Path currentLog;\n\n\t@Inject\n\tpublic RecafDirectoriesConfig() {\n\t\tsuper(ConfigGroups.SERVICE_IO, \"directories\" + CONFIG_SUFFIX);\n\t\tsetupLocalTempDir();\n\t}\n\n\t/**\n\t * @param currentLog\n\t * \t\tPath to current log-file.\n\t */\n\tpublic void initCurrentLogPath(@Nonnull Path currentLog) {\n\t\tif (this.currentLog == null)\n\t\t\tthis.currentLog = currentLog;\n\t}\n\n\t/**\n\t * @return Path to current log-file.\n\t */\n\t@Nonnull\n\tpublic Path getCurrentLogPath() {\n\t\treturn currentLog;\n\t}\n\n\t/**\n\t * @return Base Recaf directory.\n\t */\n\t@Nonnull\n\tpublic Path getBaseDirectory() {\n\t\treturn baseDirectory;\n\t}\n\n\t/**\n\t * @return Directory where agent jars are stored.\n\t */\n\t@Nonnull\n\tpublic Path getAgentDirectory() {\n\t\treturn agentDirectory;\n\t}\n\n\t/**\n\t * @return Directory where configuration is stored.\n\t */\n\t@Nonnull\n\tpublic Path getConfigDirectory() {\n\t\treturn configDirectory;\n\t}\n\n\t/**\n\t * @return Directory where old logs are stored.\n\t */\n\t@Nonnull\n\tpublic Path getLogsDirectory() {\n\t\treturn logsDirectory;\n\t}\n\n\t/**\n\t * @return Directory where plugins are stored.\n\t */\n\t@Nonnull\n\tpublic Path getPluginDirectory() {\n\t\treturn pluginDirectory;\n\t}\n\n\t/**\n\t * Set via {@link LaunchCommand} to facilitate plugin development. Usually not set otherwise.\n\t *\n\t * @return Directory where extra plugins are stored. Can be {@code null}.\n\t */\n\t@Nullable\n\tpublic Path getExtraPluginDirectory() {\n\t\tString pathProperty = System.getProperty(\"RECAF_EXTRA_PLUGINS\");\n\t\tif (pathProperty == null)\n\t\t\treturn null;\n\t\tPath path = Paths.get(pathProperty);\n\t\tif (Files.isDirectory(path))\n\t\t\treturn path;\n\t\treturn null;\n\t}\n\n\t/**\n\t * @return Directory where disabled plugins are stored.\n\t */\n\t@Nonnull\n\tpublic Path getDisabledPluginDirectory() {\n\t\treturn getPluginDirectory().resolve(\"disabled\");\n\t}\n\n\t/**\n\t * @return Directory where additional stylesheets are stored.\n\t */\n\t@Nonnull\n\tpublic Path getStyleDirectory() {\n\t\treturn styleDirectory;\n\t}\n\n\t/**\n\t * @return Directory where scripts are stored.\n\t */\n\t@Nonnull\n\tpublic Path getScriptsDirectory() {\n\t\treturn scriptsDirectory;\n\t}\n\n\t/**\n\t * @return Directory where temporary files are stored.\n\t */\n\t@Nonnull\n\tpublic Path getTempDirectory() {\n\t\treturn tempDirectory;\n\t}\n\n\t@Nonnull\n\tprivate Path resolveDirectory(@Nonnull String dir) {\n\t\tPath path = baseDirectory.resolve(dir);\n\t\ttry {\n\t\t\tFiles.createDirectories(path);\n\t\t} catch (IOException ex) {\n\t\t\tlogger.error(\"Could not create Recaf directory: \" + dir, ex);\n\t\t}\n\t\treturn path;\n\t}\n\n\tprivate void setupLocalTempDir() {\n\t\t// If it does not exist yet, make it.\n\t\tif (!Files.isDirectory(tempDirectory)) {\n\t\t\ttry {\n\t\t\t\tFiles.createDirectories(tempDirectory);\n\t\t\t} catch (IOException ex) {\n\t\t\t\tlogger.error(\"Failed creating temp directory\", ex);\n\t\t\t}\n\t\t}\n\n\t\t// When we shut down, remove all files inside of it.\n\t\tRuntime.getRuntime().addShutdownHook(new Thread(() -> {\n\t\t\ttry {\n\t\t\t\tif (Files.isDirectory(tempDirectory))\n\t\t\t\t\tIOUtil.cleanDirectory(tempDirectory);\n\t\t\t} catch (IOException ex) {\n\t\t\t\tlogger.error(\"Failed clearing temp directory\", ex);\n\t\t\t}\n\t\t}));\n\t}\n\n\t/**\n\t * @return Root directory for storing Recaf data.\n\t */\n\t@Nonnull\n\tpublic static Path createBaseDirectory() {\n\t\t// Try system property first\n\t\tString recafDir = System.getProperty(\"RECAF_DIR\");\n\t\tif (recafDir == null) // Next try looking for an environment variable\n\t\t\trecafDir = System.getenv(\"RECAF\");\n\t\tif (recafDir != null)\n\t\t\treturn Paths.get(recafDir);\n\n\t\t// Otherwise put it in the system's config directory\n\t\tPath dir = getSystemConfigDir();\n\t\tif (dir == null)\n\t\t\tthrow new IllegalStateException(\"Failed to determine config directory for: \" + System.getProperty(\"os.name\"));\n\t\treturn dir.resolve(\"Recaf\");\n\t}\n\n\t/**\n\t * @return Root config directory for the current OS.\n\t */\n\t@Nullable\n\tprivate static Path getSystemConfigDir() {\n\t\tif (PlatformType.isWindows()) {\n\t\t\treturn Paths.get(System.getenv(\"APPDATA\"));\n\t\t} else if (PlatformType.isMac()) {\n\t\t\t// Mac-OS paths:\n\t\t\t//  https://developer.apple.com/library/archive/qa/qa1170/_index.html\n\t\t\treturn Paths.get(System.getProperty(\"user.home\") + \"/Library/Application Support\");\n\t\t} else if (PlatformType.isLinux()) {\n\t\t\t// $XDG_CONFIG_HOME or $HOME/.config\n\t\t\tString xdgConfigHome = System.getenv(\"XDG_CONFIG_HOME\");\n\t\t\tif (xdgConfigHome != null)\n\t\t\t\treturn Paths.get(xdgConfigHome);\n\t\t\treturn Paths.get(System.getProperty(\"user.home\") + \"/.config\");\n\t\t}\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/inheritance/ClassPathNodeProvider.java",
    "content": "package software.coley.recaf.services.inheritance;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.stream.Stream;\n\n/**\n * Provider of class path nodes.\n *\n * @author xDark\n */\nsealed interface ClassPathNodeProvider {\n\t/**\n\t * @param name\n\t * \t\tClass name to look up.\n\t *\n\t * @return Path node for the class with the given name, or {@code null} if no such class exists in the provider.\n\t */\n\t@Nullable\n\tClassPathNode getNode(@Nonnull String name);\n\n\t/**\n\t * Create a cached provider that contains all nodes from the workspace at the time of creation.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to cache nodes from.\n\t *\n\t * @return Provider that caches all nodes from the workspace at the time of creation.\n\t */\n\tstatic ClassPathNodeProvider.Cached cache(@Nonnull Workspace workspace) {\n\t\tStream<ClassPathNode> stream = workspace.classesStream();\n\t\tMap<String, ClassPathNode> nodes = new HashMap<>(4096);\n\t\tstream.forEach(classPathNode -> {\n\t\t\tnodes.putIfAbsent(classPathNode.getValue().getName(), classPathNode);\n\t\t});\n\t\treturn new Cached(Map.copyOf(nodes));\n\t}\n\n\t/**\n\t * Provider that looks up nodes directly from the workspace.\n\t * This is not recommended for repeated lookups, but it is useful for one-off lookups or when the workspace is expected to be changing frequently.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to look up nodes from.\n\t */\n\trecord Live(@Nonnull Workspace workspace) implements ClassPathNodeProvider {\n\t\t@Nullable\n\t\t@Override\n\t\tpublic ClassPathNode getNode(@Nonnull String name) {\n\t\t\treturn workspace.findClass(name);\n\t\t}\n\t}\n\n\t/**\n\t * Provider that caches all nodes from the workspace at the time of creation.\n\t * This is recommended for repeated lookups, but it is not suitable for workspaces that are expected to be changing frequently.\n\t *\n\t * @param nodes\n\t * \t\tMap of class names to their corresponding path nodes. This map is expected to be immutable.\n\t *\n\t * @see #cache(Workspace)\n\t */\n\trecord Cached(@Nonnull Map<String, ClassPathNode> nodes) implements ClassPathNodeProvider {\n\t\tint size() {\n\t\t\treturn nodes.size();\n\t\t}\n\n\t\t@Nullable\n\t\t@Override\n\t\tpublic ClassPathNode getNode(@Nonnull String name) {\n\t\t\treturn nodes.get(name);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/inheritance/InheritanceGraph.java",
    "content": "package software.coley.recaf.services.inheritance;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.StubClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.ResourcePathNode;\nimport software.coley.recaf.services.mapping.MappingApplicationListener;\nimport software.coley.recaf.services.mapping.MappingListeners;\nimport software.coley.recaf.services.mapping.MappingResults;\nimport software.coley.recaf.services.workspace.WorkspaceCloseListener;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.WorkspaceModificationListener;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.ResourceAndroidClassListener;\nimport software.coley.recaf.workspace.model.resource.ResourceJvmClassListener;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.IdentityHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.LinkedList;\nimport java.util.Map;\nimport java.util.Queue;\nimport java.util.SequencedSet;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * Represents class inheritance as a navigable graph.\n *\n * @author Matt Coley\n */\npublic class InheritanceGraph {\n\t/** Vertex used for classes that are not found in the workspace. */\n\tprivate static final InheritanceVertex STUB = new InheritanceStubVertex();\n\tprivate static final String OBJECT = \"java/lang/Object\";\n\tprivate final Map<String, Set<String>> parentToChild;\n\tprivate final Map<String, InheritanceVertex> vertices;\n\tprivate final Set<String> stubs = ConcurrentHashMap.newKeySet();\n\tprivate final ListenerHost listener = new ListenerHost();\n\tprivate final Workspace workspace;\n\tprivate final ClassPathNodeProvider workspaceNodeProvider;\n\n\t/**\n\t * Create an inheritance graph.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to pull classes from.\n\t */\n\tpublic InheritanceGraph(@Nonnull Workspace workspace) {\n\t\tthis.workspace = workspace;\n\t\tthis.workspaceNodeProvider = new ClassPathNodeProvider.Live(workspace);\n\n\t\t// Populate map lookups with the initial capacity of the number of classes in the workspace plus a buffer.\n\t\tint classesInWorkspace = workspace.allResourcesStream(false /* dont count internal resource classes */)\n\t\t\t\t.mapToInt(res -> res.classBundleStreamRecursive().mapToInt(Map::size).sum())\n\t\t\t\t.sum() + 1;\n\t\tparentToChild = new ConcurrentHashMap<>(classesInWorkspace);\n\t\tvertices = new ConcurrentHashMap<>(classesInWorkspace);\n\n\t\t// Add listeners to primary resource so when classes update we keep our graph up to date.\n\t\tWorkspaceResource primaryResource = workspace.getPrimaryResource();\n\t\tprimaryResource.addResourceJvmClassListener(listener);\n\t\tprimaryResource.addResourceAndroidClassListener(listener);\n\t\tworkspace.addWorkspaceModificationListener(listener);\n\n\t\t// Populate downwards (parent --> child) lookup\n\t\trefreshChildLookup();\n\t}\n\n\t/**\n\t * Registers our graph's listener for mapping updates.\n\t *\n\t * @param mappingListeners\n\t * \t\tListener service to register within.\n\t */\n\tpublic void installMappingListener(@Nonnull MappingListeners mappingListeners) {\n\t\tmappingListeners.addMappingApplicationListener(listener);\n\t}\n\n\t/**\n\t * Unregisters our graph's listener from mapping updates.\n\t *\n\t * @param mappingListeners\n\t * \t\tListener service to unregister within.\n\t * @param purge\n\t *        {@code true} to also clear the graph of all data,\n\t *        {@code false} to just remove the listener and keep the graph data intact.\n\t */\n\tpublic void uninstallMappingListener(@Nonnull MappingListeners mappingListeners, boolean purge) {\n\t\t// Remove the graph as a listener so that it can be feed by the garbage collector.\n\t\tmappingListeners.removeMappingApplicationListener(listener);\n\n\t\t// Notify the graph of closure.\n\t\tif (purge) listener.onWorkspaceClosed(workspace);\n\t}\n\n\t/**\n\t * Refresh parent-to-child lookup.\n\t */\n\tprivate void refreshChildLookup() {\n\t\t// Clear\n\t\tparentToChild.clear();\n\n\t\t// Repopulate\n\t\tClassPathNodeProvider.Cached nodeProvider = ClassPathNodeProvider.cache(workspace);\n\t\tSet<ClassInfo> visited = Collections.newSetFromMap(new IdentityHashMap<>(nodeProvider.size() + 1024 /* leeway */));\n\t\tworkspace.forEachClass(false, cls -> populateParentToChildLookup(cls, visited, nodeProvider));\n\t}\n\n\t/**\n\t * Populate a references from the given child class to the parent class.\n\t *\n\t * @param name\n\t * \t\tChild class name.\n\t * @param parentName\n\t * \t\tParent class name.\n\t * @param provider\n\t *      Node provider.\n\t */\n\tprivate void populateParentToChildLookup(@Nonnull String name, @Nonnull String parentName, @Nonnull ClassPathNodeProvider provider) {\n\t\tparentToChild.computeIfAbsent(parentName, k -> ConcurrentHashMap.newKeySet()).add(name);\n\n\t\t// Clear any cached relationships in the vertex and the parent vertex.\n\t\tInheritanceVertex parentVertex = getVertex(parentName, provider);\n\t\tInheritanceVertex childVertex = getVertex(name, provider);\n\t\tif (parentVertex != null) parentVertex.clearCachedVertices();\n\t\tif (childVertex != null) childVertex.clearCachedVertices();\n\t}\n\n\t/**\n\t * Populate a references from the given child class to the parent class.\n\t *\n\t * @param name\n\t * \t\tChild class name.\n\t * @param parentName\n\t * \t\tParent class name.\n\t */\n\tprivate void populateParentToChildLookup(@Nonnull String name, @Nonnull String parentName) {\n\t\tpopulateParentToChildLookup(name, parentName, workspaceNodeProvider);\n\t}\n\n\t/**\n\t * Populate all references from the given child class to its parents.\n\t *\n\t * @param info\n\t * \t\tChild class.\n\t */\n\tprivate void populateParentToChildLookup(@Nonnull ClassInfo info) {\n\t\tpopulateParentToChildLookup(info, Collections.newSetFromMap(new IdentityHashMap<>()), workspaceNodeProvider);\n\t}\n\n\t/**\n\t * Populate all references from the given child class to its parents.\n\t *\n\t * @param info\n\t * \t\tChild class.\n\t * @param visited\n\t * \t\tClasses already visited in population.\n\t * @param provider\n\t *      Node provider.\n\t */\n\tprivate void populateParentToChildLookup(@Nonnull ClassInfo info, @Nonnull Set<ClassInfo> visited, @Nonnull ClassPathNodeProvider provider) {\n\t\t// Since we have observed this class to exist, we will remove the \"stub\" placeholder for this name.\n\t\tstubs.remove(info.getName());\n\n\t\t// Skip if already visited\n\t\tif (!visited.add(info))\n\t\t\treturn;\n\n\t\t// Skip module classes\n\t\tif (info.hasModuleModifier())\n\t\t\treturn;\n\n\t\t// Add direct parent\n\t\tString name = info.getName();\n\t\tInheritanceVertex vertex = getVertex(name, provider);\n\t\tif (vertex != null)\n\t\t\tvertex.clearCachedVertices();\n\n\t\tString superName = info.getSuperName();\n\t\tif (superName != null) {\n\t\t\tpopulateParentToChildLookup(name, superName, provider);\n\n\t\t\t// Visit parent\n\t\t\tInheritanceVertex superVertex = getVertex(superName, provider);\n\t\t\tif (superVertex != null && !superVertex.isJavaLangObject() && !superVertex.isLoop())\n\t\t\t\tpopulateParentToChildLookup(superVertex.getValue(), visited, provider);\n\t\t}\n\n\t\t// Add direct interfaces\n\t\tfor (String itf : info.getInterfaces()) {\n\t\t\tpopulateParentToChildLookup(name, itf, provider);\n\n\t\t\t// Visit interfaces\n\t\t\tInheritanceVertex interfaceVertex = getVertex(itf, provider);\n\t\t\tif (interfaceVertex != null)\n\t\t\t\tpopulateParentToChildLookup(interfaceVertex.getValue(), visited, provider);\n\t\t}\n\t}\n\n\t/**\n\t * Populate all references from the given child class to its parents.\n\t *\n\t * @param info\n\t * \t\tChild class.\n\t * @param visited\n\t * \t\tClasses already visited in population.\n\t */\n\tprivate void populateParentToChildLookup(@Nonnull ClassInfo info, @Nonnull Set<ClassInfo> visited) {\n\t\tpopulateParentToChildLookup(info, visited, workspaceNodeProvider);\n\t}\n\n\t/**\n\t * Remove all references from the given child class to its parents.\n\t *\n\t * @param info\n\t * \t\tChild class.\n\t */\n\tprivate void removeParentToChildLookup(@Nonnull ClassInfo info) {\n\t\tString superName = info.getSuperName();\n\t\tif (superName != null)\n\t\t\tremoveParentToChildLookup(info.getName(), superName);\n\t\tfor (String itf : info.getInterfaces())\n\t\t\tremoveParentToChildLookup(info.getName(), itf);\n\t}\n\n\t/**\n\t * Remove a references from the given child class to the parent class.\n\t *\n\t * @param name\n\t * \t\tChild class name.\n\t * @param parentName\n\t * \t\tParent class name.\n\t */\n\tprivate void removeParentToChildLookup(@Nonnull String name, @Nonnull String parentName) {\n\t\tSet<String> children = parentToChild.get(parentName);\n\t\tif (children != null)\n\t\t\tchildren.remove(name);\n\n\t\t// Clear any cached relationships in the vertex and the parent vertex.\n\t\tInheritanceVertex parentVertex = getVertex(parentName);\n\t\tInheritanceVertex childVertex = getVertex(name);\n\t\tif (parentVertex != null) parentVertex.clearCachedVertices();\n\t\tif (childVertex != null) childVertex.clearCachedVertices();\n\t}\n\n\t/**\n\t * Removes the given class from the graph.\n\t *\n\t * @param cls\n\t * \t\tClass that was removed.\n\t */\n\tprivate void removeClass(@Nonnull ClassInfo cls) {\n\t\tremoveParentToChildLookup(cls);\n\n\t\tString name = cls.getName();\n\t\tvertices.remove(name);\n\t}\n\n\t/**\n\t * @param parent\n\t * \t\tParent to find children of.\n\t *\n\t * @return Direct extensions/implementations of the given parent.\n\t */\n\t@Nonnull\n\tprivate Set<String> getDirectChildren(@Nonnull String parent) {\n\t\treturn parentToChild.getOrDefault(parent, Collections.emptySet());\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tClass name.\n\t * @param provider\n\t *      Node provider.\n\t *\n\t * @return Vertex in graph of class. {@code null} if no such class was found in the inputs.\n\t */\n\t@Nullable\n\tprivate InheritanceVertex getVertex(@Nonnull String name, @Nonnull ClassPathNodeProvider provider) {\n\t\tInheritanceVertex vertex = vertices.get(name);\n\t\tif (vertex == null && !stubs.contains(name)) {\n\t\t\t// Vertex does not exist and was not marked as a stub.\n\t\t\t// We want to look up the vertex for the given class and figure out if its valid or needs to be stubbed.\n\t\t\tInheritanceVertex provided = createVertex(name, provider);\n\t\t\tif (provided == STUB || provided == null) {\n\t\t\t\t// Provider yielded either a stub OR no result. Discard it.\n\t\t\t\tstubs.add(name);\n\t\t\t} else {\n\t\t\t\t// Provider yielded a valid vertex. Update the return value and record it in the map.\n\t\t\t\tvertices.put(name, provided);\n\t\t\t\tvertex = provided;\n\t\t\t}\n\t\t}\n\t\treturn vertex;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tClass name.\n\t *\n\t * @return Vertex in graph of class. {@code null} if no such class was found in the inputs.\n\t */\n\t@Nullable\n\tpublic InheritanceVertex getVertex(@Nonnull String name) {\n\t\treturn getVertex(name, workspaceNodeProvider);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tClass name.\n\t * @param includeObject\n\t *        {@code true} to include {@link Object} as a vertex.\n\t *\n\t * @return Complete inheritance family of the class.\n\t */\n\t@Nonnull\n\tpublic Set<InheritanceVertex> getVertexFamily(@Nonnull String name, boolean includeObject) {\n\t\tInheritanceVertex vertex = getVertex(name);\n\t\tif (vertex == null)\n\t\t\treturn Collections.emptySet();\n\t\tif (vertex.isModule())\n\t\t\treturn Collections.singleton(vertex);\n\t\treturn vertex.getFamily(includeObject);\n\t}\n\n\t/**\n\t * Given {@code List.class.isAssignableFrom(ArrayList.class)} the {@code first} parameter would be\n\t * {@code java/util/List} and the {@code second} parameter would be {@code java/util/ArrayList}.\n\t *\n\t * @param first\n\t * \t\tAssumed super-class or interface type.\n\t * @param second\n\t * \t\tAssumed child class which extends the super-class or implements the interface type.\n\t *\n\t * @return {@code true} when {@code first.isAssignableFrom(second)}.\n\t */\n\tpublic boolean isAssignableFrom(@Nonnull String first, @Nonnull String second) {\n\t\t// Any Object can be assigned from T.\n\t\tif (OBJECT.equals(first))\n\t\t\treturn true;\n\n\t\t// Any T can be assigned from T.\n\t\tif (first.equals(second))\n\t\t\treturn true;\n\n\t\t// Any non-Object T cannot be assigned from Object.\n\t\tif (second.equals(OBJECT))\n\t\t\treturn false;\n\n\t\t// Lookup vertex for the child type, and see if any parent contains the supposed super/interface type.\n\t\tInheritanceVertex secondVertex = getVertex(second);\n\t\tif (secondVertex != null && secondVertex.hasParent(first))\n\t\t\treturn true;\n\n\t\t// Lookup vertex for the parent type, and see if any child contains the supposed type.\n\t\tInheritanceVertex firstVertex = getVertex(first);\n\t\treturn firstVertex != null && firstVertex.hasChild(second);\n\t}\n\n\t/**\n\t * @param first\n\t * \t\tFirst class name.\n\t * @param second\n\t * \t\tSecond class name.\n\t *\n\t * @return Common parent of the classes.\n\t */\n\t@Nonnull\n\tpublic String getCommon(@Nonnull String first, @Nonnull String second) {\n\t\t// Easy base cases\n\t\tif (OBJECT.equals(first) || OBJECT.equals(second))\n\t\t\treturn OBJECT;\n\t\tif (first.equals(second))\n\t\t\treturn first;\n\n\t\t// Try with the first name\n\t\tInheritanceVertex vertex = getVertex(first);\n\t\tif (vertex != null)\n\t\t\treturn getCommon(vertex, first, second);\n\n\t\t// Try again but with the other name\n\t\tvertex = getVertex(second);\n\t\tif (vertex != null)\n\t\t\treturn getCommon(vertex, second, first);\n\n\t\t// Neither is resolvable\n\t\treturn OBJECT;\n\t}\n\n\t/**\n\t * @param firstVertex\n\t * \t\tVertex of the {@code first} name.\n\t * @param first\n\t * \t\tFirst class name.\n\t * @param second\n\t * \t\tSecond class name.\n\t *\n\t * @return Common parent of the classes.\n\t */\n\t@Nonnull\n\tprivate String getCommon(@Nonnull InheritanceVertex firstVertex, @Nonnull String first, @Nonnull String second) {\n\t\t// Full upwards hierarchy for the first\n\t\tSequencedSet<String> firstParents = firstVertex.allParents()\n\t\t\t\t.map(InheritanceVertex::getParentAndCurrentNames)\n\t\t\t\t.flatMap(Collection::stream)\n\t\t\t\t.collect(Collectors.toCollection(LinkedHashSet::new));\n\t\tfirstParents.add(first);\n\n\t\t// Ensure 'Object' is last\n\t\tfirstParents.remove(OBJECT);\n\t\tfirstParents.add(OBJECT);\n\n\t\t// Base case\n\t\tif (firstParents.contains(second))\n\t\t\treturn second;\n\n\t\t// Iterate over second's parents via breadth-first-search\n\t\tQueue<String> queue = new LinkedList<>();\n\t\tqueue.add(second);\n\t\tdo {\n\t\t\t// Item to fetch parents of\n\t\t\tString next = queue.poll();\n\t\t\tif (next == null || next.equals(OBJECT))\n\t\t\t\tcontinue;\n\n\t\t\tInheritanceVertex nextVertex = getVertex(next);\n\t\t\tif (nextVertex == null)\n\t\t\t\tcontinue;\n\n\t\t\tfor (String parent : nextVertex.getParents().stream()\n\t\t\t\t\t.map(InheritanceVertex::getParentAndCurrentNames)\n\t\t\t\t\t.flatMap(Collection::stream)\n\t\t\t\t\t.toList()) {\n\t\t\t\tif (!parent.equals(OBJECT)) {\n\t\t\t\t\t// Parent in the set of visited classes? Then its valid.\n\t\t\t\t\tif (firstParents.contains(parent))\n\t\t\t\t\t\treturn parent;\n\t\t\t\t\t// Queue up the parent\n\t\t\t\t\tqueue.add(parent);\n\t\t\t\t}\n\t\t\t}\n\t\t} while (!queue.isEmpty());\n\n\t\t// Fallback option\n\t\treturn OBJECT;\n\t}\n\n\t/**\n\t * Check if the method is a library method. If the class is not found in the workspace, we assume it is a library method.\n\t *\n\t * @param name\n\t * \t\tDeclaring class name.\n\t * @param methodName\n\t * \t\tMethod name.\n\t * @param methodDesc\n\t * \t\tMethod descriptor.\n\t *\n\t * @return {@code true} if the method is a library method, {@code false} otherwise.\n\t */\n\tpublic boolean isLibraryMethod(@Nonnull String name, @Nonnull String methodName, @Nonnull String methodDesc) {\n\t\tInheritanceVertex vertex = getVertex(name);\n\t\tif (vertex == null)\n\t\t\treturn true; // Not in the workspace, so we assume it is a library method.\n\t\treturn vertex.isLibraryMethod(methodName, methodDesc);\n\t}\n\n\t/**\n\t * When {@link #STUB} is the return of this method, the class was not found.\n\t * <br>\n\t * When {@code null} is the return of this method, the class name is illegal.\n\t *\n\t * @param name\n\t * \t\tInternal class name.\n\t * @param provider\n\t *      Node provider.\n\t *\n\t * @return Vertex of class.\n\t */\n\t@Nullable\n\tprivate InheritanceVertex createVertex(@Nullable String name, @Nonnull ClassPathNodeProvider provider) {\n\t\t// Edge case handling for 'java/lang/Object' doing a parent lookup.\n\t\t// There is no parent, do not use STUB.\n\t\tif (name == null)\n\t\t\treturn null;\n\n\t\t// Edge case handling for arrays. There is no object typing of arrays.\n\t\tif (name.isEmpty() || name.charAt(0) == '[')\n\t\t\treturn null;\n\n\t\t// Find class in workspace, if not found yield stub.\n\t\tClassPathNode result = provider.getNode(name);\n\t\tif (result == null)\n\t\t\treturn STUB;\n\n\t\t// Map class to vertex.\n\t\tResourcePathNode resourcePath = result.getPathOfType(WorkspaceResource.class);\n\t\tboolean isPrimary = resourcePath != null && resourcePath.isPrimaryOrEmbeddedInPrimary();\n\t\tClassInfo info = result.getValue();\n\t\treturn new InheritanceVertex(info, this::getVertex, this::getDirectChildren, isPrimary);\n\t}\n\n\tprivate void onUpdateClassImpl(@Nonnull ClassInfo oldValue, @Nonnull ClassInfo newValue) {\n\t\tString name = oldValue.getName();\n\t\tif (!newValue.getName().equals(name))\n\t\t\tthrow new IllegalStateException(\"onUpdateClass should not permit a class name change\");\n\n\t\t// Update hierarchy now that super-name changed\n\t\tif (oldValue.getSuperName() != null && newValue.getSuperName() != null) {\n\t\t\tif (!oldValue.getSuperName().equals(newValue.getSuperName())) {\n\t\t\t\tremoveParentToChildLookup(name, oldValue.getSuperName());\n\t\t\t\tpopulateParentToChildLookup(name, newValue.getSuperName());\n\t\t\t}\n\t\t}\n\n\t\t// Same deal, but for interfaces\n\t\tSet<String> interfaces = new HashSet<>(oldValue.getInterfaces());\n\t\tinterfaces.addAll(newValue.getInterfaces());\n\t\tfor (String itf : interfaces) {\n\t\t\tboolean oldHas = oldValue.getInterfaces().contains(itf);\n\t\t\tboolean newHas = newValue.getInterfaces().contains(itf);\n\t\t\tif (oldHas && !newHas) {\n\t\t\t\tremoveParentToChildLookup(name, itf);\n\t\t\t} else if (!oldHas && newHas) {\n\t\t\t\tpopulateParentToChildLookup(name, itf);\n\t\t\t}\n\t\t}\n\n\t\t// Update vertex wrapped class-info\n\t\tInheritanceVertex vertex = getVertex(name);\n\t\tif (vertex != null)\n\t\t\tvertex.setValue(newValue);\n\t}\n\n\tprivate class ListenerHost implements WorkspaceModificationListener, WorkspaceCloseListener,\n\t\t\tResourceJvmClassListener, ResourceAndroidClassListener, MappingApplicationListener {\n\n\t\t@Override\n\t\tpublic void onNewClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls) {\n\t\t\tpopulateParentToChildLookup(cls);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onNewClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo cls) {\n\t\t\tpopulateParentToChildLookup(cls);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo oldCls, @Nonnull JvmClassInfo newCls) {\n\t\t\tonUpdateClassImpl(oldCls, newCls);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo oldCls, @Nonnull AndroidClassInfo newCls) {\n\t\t\tonUpdateClassImpl(oldCls, newCls);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls) {\n\t\t\tremoveClass(cls);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo cls) {\n\t\t\tremoveClass(cls);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onAddLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library) {\n\t\t\tSet<ClassInfo> visited = Collections.newSetFromMap(new IdentityHashMap<>());\n\t\t\tlibrary.jvmClassBundleStreamRecursive()\n\t\t\t\t\t.flatMap(Bundle::stream)\n\t\t\t\t\t.forEach(c -> populateParentToChildLookup(c, visited));\n\t\t\tlibrary.androidClassBundleStreamRecursive()\n\t\t\t\t\t.flatMap(Bundle::stream)\n\t\t\t\t\t.forEach(c -> populateParentToChildLookup(c, visited));\n\t\t\trefreshChildLookup();\n\t\t}\n\n\t\t@Override\n\t\tpublic void onRemoveLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library) {\n\t\t\tlibrary.jvmClassBundleStreamRecursive()\n\t\t\t\t\t.flatMap(Bundle::stream)\n\t\t\t\t\t.forEach(InheritanceGraph.this::removeClass);\n\t\t\tlibrary.androidClassBundleStreamRecursive()\n\t\t\t\t\t.flatMap(Bundle::stream)\n\t\t\t\t\t.forEach(InheritanceGraph.this::removeClass);\n\t\t\trefreshChildLookup();\n\t\t}\n\n\t\t@Override\n\t\tpublic void onWorkspaceClosed(@Nonnull Workspace workspace) {\n\t\t\tparentToChild.clear();\n\t\t\tvertices.clear();\n\t\t\tstubs.clear();\n\t\t}\n\n\t\t@Override\n\t\tpublic void onPreApply(@Nonnull Workspace workspace, @Nonnull MappingResults mappingResults) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic void onPostApply(@Nonnull Workspace workspace, @Nonnull MappingResults mappingResults) {\n\t\t\t// Must apply to the graph's associated workspace.\n\t\t\tif (InheritanceGraph.this.workspace != workspace)\n\t\t\t\treturn;\n\n\t\t\t// Remove vertices and lookups of items that no longer exist.\n\t\t\tmappingResults.getPreMappingPaths().forEach((name, path) -> {\n\t\t\t\t// If we see a 'stub' from the vertex creator, we know it is no longer\n\t\t\t\t// in the workspace and should be removed from our cache.\n\t\t\t\tInheritanceVertex vertex = createVertex(name, workspaceNodeProvider);\n\t\t\t\tif (vertex == STUB) {\n\t\t\t\t\tvertices.remove(name);\n\t\t\t\t\tparentToChild.remove(name);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// While applying mappings, the graph does not perfectly refresh, so we need to clear out some state\n\t\t\t// so that when the graph is used again the correct information will be fetched.\n\t\t\tSet<ClassInfo> visited = Collections.newSetFromMap(new IdentityHashMap<>());\n\t\t\tmappingResults.getPostMappingPaths().forEach((name, path) -> {\n\t\t\t\t// Stub information for classes we know exist in the workspace should be removed.\n\t\t\t\tstubs.remove(name);\n\n\t\t\t\t// Refresh the parent-->children mapping.\n\t\t\t\tparentToChild.remove(name);\n\t\t\t\tClassInfo postClass = path.getValue();\n\t\t\t\tpopulateParentToChildLookup(postClass, visited);\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate static class InheritanceStubVertex extends InheritanceVertex {\n\t\tprivate InheritanceStubVertex() {\n\t\t\tsuper(new StubClassInfo(\"java/lang/Object\").asJvmClass(), in -> null, in -> null, false);\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean hasField(@Nonnull String name, @Nonnull String desc) {\n\t\t\treturn false;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean hasMethod(@Nonnull String name, @Nonnull String desc) {\n\t\t\treturn false;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean isJavaLangObject() {\n\t\t\treturn false;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean isParentOf(@Nonnull InheritanceVertex vertex) {\n\t\t\treturn false;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean isChildOf(@Nonnull InheritanceVertex vertex) {\n\t\t\treturn false;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean isIndirectFamilyMember(@Nonnull InheritanceVertex vertex) {\n\t\t\treturn false;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean isIndirectFamilyMember(@Nonnull Set<InheritanceVertex> family, @Nonnull InheritanceVertex vertex) {\n\t\t\treturn false;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Set<InheritanceVertex> getFamily(boolean includeObject) {\n\t\t\treturn Collections.emptySet();\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Set<InheritanceVertex> getAllParents() {\n\t\t\treturn Collections.emptySet();\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Stream<InheritanceVertex> allParents() {\n\t\t\treturn Stream.empty();\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Set<InheritanceVertex> getParents() {\n\t\t\treturn Collections.emptySet();\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Set<InheritanceVertex> getAllChildren() {\n\t\t\treturn Collections.emptySet();\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Set<InheritanceVertex> getChildren() {\n\t\t\treturn Collections.emptySet();\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Set<InheritanceVertex> getAllDirectVertices() {\n\t\t\treturn Collections.emptySet();\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String getName() {\n\t\t\treturn \"$$STUB$$\";\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/inheritance/InheritanceGraphService.java",
    "content": "package software.coley.recaf.services.inheritance;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.cdi.EagerInitialization;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.mapping.MappingListeners;\nimport software.coley.recaf.services.workspace.WorkspaceCloseListener;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Objects;\n\n/**\n * Service offering the creation of {@link InheritanceGraph inheritance graphs} for workspaces.\n *\n * @author Matt Coley\n * @see InheritanceGraph\n */\n@EagerInitialization\n@ApplicationScoped\npublic class InheritanceGraphService implements Service {\n\tpublic static final String SERVICE_ID = \"graph-inheritance\";\n\tprivate final InheritanceGraphServiceConfig config;\n\tprivate final MappingListeners mappingListeners;\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate volatile InheritanceGraph currentWorkspaceGraph;\n\n\t@Inject\n\tpublic InheritanceGraphService(@Nonnull WorkspaceManager workspaceManager,\n\t                               @Nonnull MappingListeners mappingListeners,\n\t                               @Nonnull InheritanceGraphServiceConfig config) {\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.mappingListeners = mappingListeners;\n\t\tthis.config = config;\n\n\t\tListenerHost host = new ListenerHost();\n\t\tworkspaceManager.addWorkspaceCloseListener(host);\n\t}\n\n\t/**\n\t * Gets an existing graph if present for the workspace,\n\t * or makes a new one if there is no associated graph for the workspace.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to pull classes from.\n\t *\n\t * @return Inheritance graph model for the given workspace.\n\t */\n\t@Nonnull\n\tpublic InheritanceGraph getOrCreateInheritanceGraph(@Nonnull Workspace workspace) {\n\t\treturn workspaceManager.getCurrent() == workspace ?\n\t\t\t\tObjects.requireNonNull(getCurrentWorkspaceInheritanceGraph(), \"Failed to get current workspace graph\") :\n\t\t\t\tnewInheritanceGraph(workspace);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull classes from.\n\t *\n\t * @return New inheritance graph model for the given workspace.\n\t */\n\t@Nonnull\n\tpublic InheritanceGraph newInheritanceGraph(@Nonnull Workspace workspace) {\n\t\treturn new InheritanceGraph(workspace);\n\t}\n\n\t/**\n\t * @return Inheritance graph model for the {@link WorkspaceManager#getCurrent() current workspace}\n\t * or {@code null} if no workspace is currently open.\n\t */\n\t@Nullable\n\tpublic InheritanceGraph getCurrentWorkspaceInheritanceGraph() {\n\t\tif (!workspaceManager.hasCurrentWorkspace())\n\t\t\treturn null;\n\n\t\t// Building graphs can be expensive for large workspaces, so to prevent races we will double-check.\n\t\tif (currentWorkspaceGraph == null) {\n\t\t\tsynchronized (this) {\n\t\t\t\tif (currentWorkspaceGraph == null) {\n\t\t\t\t\tInheritanceGraph graph = newInheritanceGraph(workspaceManager.getCurrent());\n\t\t\t\t\tgraph.installMappingListener(mappingListeners);\n\t\t\t\t\tcurrentWorkspaceGraph = graph;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn currentWorkspaceGraph;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic InheritanceGraphServiceConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\tprivate class ListenerHost implements WorkspaceCloseListener {\n\t\t@Override\n\t\tpublic void onWorkspaceClosed(@Nonnull Workspace workspace) {\n\t\t\tif (currentWorkspaceGraph != null) {\n\t\t\t\tcurrentWorkspaceGraph.uninstallMappingListener(mappingListeners, true);\n\t\t\t\tcurrentWorkspaceGraph = null;\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/inheritance/InheritanceGraphServiceConfig.java",
    "content": "package software.coley.recaf.services.inheritance;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link InheritanceGraphService}\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class InheritanceGraphServiceConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic InheritanceGraphServiceConfig() {\n\t\tsuper(ConfigGroups.SERVICE_ANALYSIS, InheritanceGraphService.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/inheritance/InheritanceVertex.java",
    "content": "package software.coley.recaf.services.inheritance;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.collections.Lists;\nimport software.coley.collections.Sets;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.util.Streams;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * Graph element for a class inheritance hierarchy.\n *\n * @author Matt Coley\n */\npublic class InheritanceVertex {\n\tprivate final Function<String, InheritanceVertex> lookup;\n\tprivate final Function<String, Collection<String>> childrenLookup;\n\tprivate final boolean isPrimary;\n\tprivate volatile Set<InheritanceVertex> parents;\n\tprivate volatile Set<InheritanceVertex> children;\n\tprivate ClassInfo value;\n\n\t/**\n\t * @param value\n\t * \t\tThe wrapped value.\n\t * @param lookup\n\t * \t\tClass vertex lookup.\n\t * @param childrenLookup\n\t * \t\tClass child lookup.\n\t * @param isPrimary\n\t * \t\tFlag for if the class belongs to a workspaces primary resource.\n\t */\n\tpublic InheritanceVertex(@Nonnull ClassInfo value,\n\t                         @Nonnull Function<String, InheritanceVertex> lookup,\n\t                         @Nonnull Function<String, Collection<String>> childrenLookup, boolean isPrimary) {\n\t\tthis.value = value;\n\t\tthis.lookup = lookup;\n\t\tthis.childrenLookup = childrenLookup;\n\t\tthis.isPrimary = isPrimary;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tField name.\n\t * @param desc\n\t * \t\tField descriptor.\n\t *\n\t * @return If the field exists in the current vertex.\n\t */\n\tpublic boolean hasField(@Nonnull String name, @Nonnull String desc) {\n\t\tfor (FieldMember fn : value.getFields())\n\t\t\tif (fn.getName().equals(name) && fn.getDescriptor().equals(desc))\n\t\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tField name.\n\t * @param desc\n\t * \t\tField descriptor.\n\t *\n\t * @return If the field exists in the current vertex or in any parent vertex.\n\t */\n\tpublic boolean hasFieldInSelfOrParents(@Nonnull String name, @Nonnull String desc) {\n\t\tif (hasField(name, desc))\n\t\t\treturn true;\n\t\treturn allParents()\n\t\t\t\t.filter(v -> v != this)\n\t\t\t\t.anyMatch(parent -> parent.hasFieldInSelfOrParents(name, desc));\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tField name.\n\t * @param desc\n\t * \t\tField descriptor.\n\t *\n\t * @return If the field exists in the current vertex or in any child vertex.\n\t */\n\tpublic boolean hasFieldInSelfOrChildren(@Nonnull String name, @Nonnull String desc) {\n\t\tif (hasField(name, desc))\n\t\t\treturn true;\n\t\treturn allChildren()\n\t\t\t\t.filter(v -> v != this)\n\t\t\t\t.anyMatch(parent -> parent.hasFieldInSelfOrChildren(name, desc));\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tMethod name.\n\t * @param desc\n\t * \t\tMethod descriptor.\n\t *\n\t * @return If the method exists in the current vertex.\n\t */\n\tpublic boolean hasMethod(@Nonnull String name, @Nonnull String desc) {\n\t\tfor (MethodMember mn : value.getMethods())\n\t\t\tif (mn.getName().equals(name) && mn.getDescriptor().equals(desc))\n\t\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tMethod name.\n\t * @param desc\n\t * \t\tMethod descriptor.\n\t *\n\t * @return If the method exists in the current vertex or in any parent vertex.\n\t */\n\tpublic boolean hasMethodInSelfOrParents(@Nonnull String name, @Nonnull String desc) {\n\t\tif (hasMethod(name, desc))\n\t\t\treturn true;\n\t\treturn allParents()\n\t\t\t\t.filter(v -> v != this)\n\t\t\t\t.anyMatch(parent -> parent.hasMethodInSelfOrParents(name, desc));\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tMethod name.\n\t * @param desc\n\t * \t\tMethod descriptor.\n\t *\n\t * @return If the method exists in the current vertex or in any child vertex.\n\t */\n\tpublic boolean hasMethodInSelfOrChildren(@Nonnull String name, @Nonnull String desc) {\n\t\tif (hasMethod(name, desc))\n\t\t\treturn true;\n\t\treturn allChildren()\n\t\t\t\t.filter(v -> v != this)\n\t\t\t\t.anyMatch(parent -> parent.hasMethodInSelfOrChildren(name, desc));\n\t}\n\n\t/**\n\t * @return {@code true} if the class represented by this vertex is a library class.\n\t * This means a class that does not belong to the primary {@link WorkspaceResource}\n\t * of a {@link Workspace}.\n\t */\n\tpublic boolean isLibraryVertex() {\n\t\treturn !isPrimary;\n\t}\n\n\t/**\n\t * @return {@code true} when the current vertex represents {@link Object}.\n\t */\n\tpublic boolean isJavaLangObject() {\n\t\treturn getName().equals(\"java/lang/Object\");\n\t}\n\n\t/**\n\t * @return {@code true} when a parent of this vertex, is this vertex.\n\t */\n\tpublic boolean isLoop() {\n\t\t// Our vertex model silently drops cycles to prevent infinite loops, so what we\n\t\t// do instead is check if any of the vertices in the graph for this class extend\n\t\t// or implement the current vertex's class.\n\t\tString name = getName();\n\t\tPredicate<InheritanceVertex> extendsName = v -> {\n\t\t\tClassInfo cls = v.getValue();\n\t\t\treturn name.equals(cls.getSuperName()) || cls.getInterfaces().contains(name);\n\t\t};\n\t\treturn extendsName.test(this) || allParents().anyMatch(extendsName);\n\t}\n\n\t/**\n\t * @return {@code true} when the current vertex represents a {@code module-info}.\n\t */\n\tpublic boolean isModule() {\n\t\treturn getValue().hasModuleModifier() && getValue().getSuperName() == null;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tMethod name.\n\t * @param desc\n\t * \t\tMethod descriptor.\n\t *\n\t * @return {@code true} if method is an extension of an outside class's methods and thus should not be renamed.\n\t * {@code false} if the method is safe to rename.\n\t */\n\tpublic boolean isLibraryMethod(@Nonnull String name, @Nonnull String desc) {\n\t\t// Check against this definition\n\t\tif (!isPrimary && hasMethod(name, desc))\n\t\t\treturn true;\n\n\t\t// Check parents.\n\t\t// If we extend a class with a library definition then it should be considered a library method.\n\t\tfor (InheritanceVertex parent : getParents())\n\t\t\tif (parent.isLibraryMethod(name, desc))\n\t\t\t\treturn true;\n\n\t\t// No library definition found, so its safe to rename.\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param vertex\n\t * \t\tSupposed child vertex.\n\t *\n\t * @return {@code true} if the vertex is of a child type to this vertex's {@link #getName() type}.\n\t */\n\tpublic boolean isParentOf(@Nonnull InheritanceVertex vertex) {\n\t\treturn vertex.getAllParents().contains(this);\n\t}\n\n\t/**\n\t * @param vertex\n\t * \t\tSupposed parent vertex.\n\t *\n\t * @return {@code true} if the vertex is of a parent type to this vertex's {@link #getName() type}.\n\t */\n\tpublic boolean isChildOf(@Nonnull InheritanceVertex vertex) {\n\t\treturn getAllParents().contains(vertex);\n\t}\n\n\t/**\n\t * @param vertex\n\t * \t\tSupposed vertex that belongs in the family.\n\t *\n\t * @return {@code true} if the vertex is a family member, but is not a child or parent of the current vertex.\n\t */\n\tpublic boolean isIndirectFamilyMember(@Nonnull InheritanceVertex vertex) {\n\t\treturn isIndirectFamilyMember(getFamily(true), vertex);\n\t}\n\n\t/**\n\t * @param family\n\t * \t\tFamily to check in.\n\t * @param vertex\n\t * \t\tSupposed vertex that belongs in the family.\n\t *\n\t * @return {@code true} if the vertex is a family member, but is not a child or parent of the current vertex.\n\t */\n\tpublic boolean isIndirectFamilyMember(@Nonnull Set<InheritanceVertex> family, @Nonnull InheritanceVertex vertex) {\n\t\treturn this != vertex &&\n\t\t\t\tfamily.contains(vertex) &&\n\t\t\t\t!isChildOf(vertex) &&\n\t\t\t\t!isParentOf(vertex);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName of parent type.\n\t *\n\t * @return {@code true} when this vertex has the given parent.\n\t */\n\tpublic boolean hasParent(@Nonnull String name) {\n\t\t// This first check serves multiple purposes.\n\t\t// - The name comparison on the wrapped class's parent/interfaces is faster\n\t\t//   than walking the graph to find the same names in wrapped vertices\n\t\t// - This will cover cases where the given parent is not in the workspace\n\t\t//   but is a direct parent of a class that is in the workspace\n\t\tClassInfo cls = getValue();\n\t\tif (name.equals(cls.getSuperName()) || cls.getInterfaces().contains(name))\n\t\t\treturn true;\n\n\t\t// Check all parents for a matching name, or the same check as above but for the parent.\n\t\treturn allParents().anyMatch(parent -> {\n\t\t\tif (name.equals(parent.getName()))\n\t\t\t\treturn true;\n\n\t\t\tClassInfo parentCls = parent.getValue();\n\t\t\treturn name.equals(parentCls.getSuperName()) || parentCls.getInterfaces().contains(name);\n\t\t});\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName of child type.\n\t *\n\t * @return {@code true} when this vertex has the given child.\n\t */\n\tpublic boolean hasChild(@Nonnull String name) {\n\t\tfor (InheritanceVertex child : getAllChildren())\n\t\t\tif (name.equals(child.getName()))\n\t\t\t\treturn true;\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param includeObject\n\t *        {@code true} to include {@link Object} as a vertex.\n\t *\n\t * @return The entire class hierarchy.\n\t */\n\t@Nonnull\n\tpublic Set<InheritanceVertex> getFamily(boolean includeObject) {\n\t\tSet<InheritanceVertex> vertices = new LinkedHashSet<>();\n\t\tvisitFamily(vertices);\n\t\tif (!includeObject)\n\t\t\tvertices.removeIf(InheritanceVertex::isJavaLangObject);\n\t\treturn vertices;\n\t}\n\n\tprivate void visitFamily(@Nonnull Set<InheritanceVertex> vertices) {\n\t\tif (isModule())\n\t\t\treturn;\n\t\tif (vertices.add(this) && !isJavaLangObject())\n\t\t\tfor (InheritanceVertex vertex : getAllDirectVertices())\n\t\t\t\tvertex.visitFamily(vertices);\n\t}\n\n\t/**\n\t * @return All classes this extends or implements.\n\t */\n\t@Nonnull\n\tpublic Set<InheritanceVertex> getAllParents() {\n\t\treturn allParents().collect(Collectors.toCollection(LinkedHashSet::new));\n\t}\n\n\t/**\n\t * @return All classes this extends or implements.\n\t */\n\t@Nonnull\n\tpublic Stream<InheritanceVertex> allParents() {\n\t\t// Skip 1 to skip ourselves (which we use as the seed vertex)\n\t\treturn Streams.recurseWithoutCycles(this, InheritanceVertex::getParents)\n\t\t\t\t.skip(1);\n\t}\n\n\t/**\n\t * @return Classes this directly extends or implements.\n\t */\n\t@Nonnull\n\tpublic Set<InheritanceVertex> getParents() {\n\t\tSet<InheritanceVertex> parents = this.parents;\n\t\tif (parents == null) {\n\t\t\tsynchronized (this) {\n\t\t\t\tif (isModule()) {\n\t\t\t\t\tparents = Collections.emptySet();\n\t\t\t\t\tthis.parents = parents;\n\t\t\t\t\treturn parents;\n\t\t\t\t}\n\t\t\t\tparents = this.parents;\n\t\t\t\tif (parents == null) {\n\t\t\t\t\tString name = getName();\n\t\t\t\t\tparents = new LinkedHashSet<>();\n\t\t\t\t\tString superName = value.getSuperName();\n\t\t\t\t\tif (superName != null && !name.equals(superName)) {\n\t\t\t\t\t\tInheritanceVertex parentVertex = lookup.apply(superName);\n\t\t\t\t\t\tif (parentVertex != null)\n\t\t\t\t\t\t\tparents.add(parentVertex);\n\t\t\t\t\t}\n\t\t\t\t\tfor (String itf : value.getInterfaces()) {\n\t\t\t\t\t\tInheritanceVertex itfVertex = lookup.apply(itf);\n\t\t\t\t\t\tif (itfVertex != null && !name.equals(itf))\n\t\t\t\t\t\t\tparents.add(itfVertex);\n\t\t\t\t\t}\n\t\t\t\t\tthis.parents = parents;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn parents;\n\t}\n\n\t/**\n\t * @return All classes extending or implementing this type.\n\t */\n\t@Nonnull\n\tpublic Set<InheritanceVertex> getAllChildren() {\n\t\treturn allChildren().collect(Collectors.toCollection(LinkedHashSet::new));\n\t}\n\n\t/**\n\t * @return Stream of all classes extending or implementing this type.\n\t */\n\t@Nonnull\n\tpublic Stream<InheritanceVertex> allChildren() {\n\t\t// Skip 1 to skip ourselves (which we use as the seed vertex)\n\t\treturn Streams.recurseWithoutCycles(this, InheritanceVertex::getChildren)\n\t\t\t\t.skip(1);\n\t}\n\n\t/**\n\t * @return Classes that extend or implement this class.\n\t */\n\t@Nonnull\n\tpublic Set<InheritanceVertex> getChildren() {\n\t\tSet<InheritanceVertex> children = this.children;\n\t\tif (children == null) {\n\t\t\tsynchronized (this) {\n\t\t\t\tif (isModule()) {\n\t\t\t\t\tchildren = Collections.emptySet();\n\t\t\t\t\tthis.children = children;\n\t\t\t\t\treturn children;\n\t\t\t\t}\n\t\t\t\tchildren = this.children;\n\t\t\t\tif (children == null) {\n\t\t\t\t\tString name = getName();\n\t\t\t\t\tchildren = childrenLookup.apply(value.getName())\n\t\t\t\t\t\t\t.stream()\n\t\t\t\t\t\t\t.filter(childName -> !name.equals(childName))\n\t\t\t\t\t\t\t.map(lookup)\n\t\t\t\t\t\t\t.filter(Objects::nonNull)\n\t\t\t\t\t\t\t.collect(Collectors.toCollection(LinkedHashSet::new));\n\t\t\t\t\tthis.children = children;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn children;\n\t}\n\n\t/**\n\t * @return All direct parents and child vertices.\n\t */\n\t@Nonnull\n\tpublic Set<InheritanceVertex> getAllDirectVertices() {\n\t\treturn Sets.combine(getParents(), getChildren());\n\t}\n\n\t/**\n\t * Clears cached {@link #getParents()} and {@link #getChildren()} values.\n\t */\n\tpublic void clearCachedVertices() {\n\t\tsynchronized (this) {\n\t\t\tparents = null;\n\t\t\tchildren = null;\n\t\t}\n\t}\n\n\n\t/**\n\t * @return {@link #getValue() wrapped class's} name\n\t */\n\t@Nonnull\n\tpublic String getName() {\n\t\treturn value.getName();\n\t}\n\n\t/**\n\t * @return List of the {@link #getValue() wrapped class's} super class name, and any implemented interfaces.\n\t */\n\t@Nonnull\n\tpublic List<String> getParentNames() {\n\t\treturn value.getSuperName() != null ?\n\t\t\t\tLists.add(value.getInterfaces(), value.getSuperName()) :\n\t\t\t\tvalue.getInterfaces();\n\t}\n\n\t/**\n\t * @return Combined list of {@link #getName()} and {@link #getParentNames()}.\n\t */\n\t@Nonnull\n\tpublic List<String> getParentAndCurrentNames() {\n\t\treturn Lists.add(getParentNames(), getName());\n\t}\n\n\t/**\n\t * @return Wrapped class info.\n\t */\n\t@Nonnull\n\tpublic ClassInfo getValue() {\n\t\treturn value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tNew wrapped class info.\n\t */\n\tpublic void setValue(@Nonnull ClassInfo value) {\n\t\tthis.value = value;\n\t\tclearCachedVertices();\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\t\tInheritanceVertex vertex = (InheritanceVertex) o;\n\t\treturn Objects.equals(getName(), vertex.getName());\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn getName().hashCode();\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn getName();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/json/GsonProvider.java",
    "content": "package software.coley.recaf.services.json;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.InstanceCreator;\nimport com.google.gson.JsonDeserializer;\nimport com.google.gson.JsonSerializer;\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.TypeAdapterFactory;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.services.Service;\n\nimport java.lang.reflect.Type;\nimport java.util.function.Consumer;\n\n/**\n * Manages a common {@link Gson} instance for consistent handling across Recaf.\n *\n * @author Matt Coley\n * @author Justus Garbe\n */\n@ApplicationScoped\npublic class GsonProvider implements Service {\n\tpublic static final String SERVICE_ID = \"gson-provider\";\n\tprivate final GsonProviderConfig config;\n\tprivate GsonBuilder builder;\n\n\t@Inject\n\tpublic GsonProvider(@Nonnull GsonProviderConfig config) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * @return A gson instance from the current builder parameters.\n\t */\n\t@Nonnull\n\tpublic Gson getGson() {\n\t\tGsonBuilder copy = getBuilderCopy();\n\n\t\t// Apply config to a copy of the builder.\n\t\t// We do this at this step because you cannot unset pretty-printing from the builder.\n\t\t// Thus, we'll add it as post-processing when the user requests a gson instance.\n\t\tif (config.getPrettyPrint().getValue())\n\t\t\tcopy.setPrettyPrinting();\n\n\t\treturn copy.create();\n\t}\n\n\t/**\n\t * Register a type adapter factory for application-wide (de)serialization support\n\t * for supported types from the factory.\n\t *\n\t * @param factory\n\t * \t\tAdapter factory to register.\n\t *\n\t * @see GsonBuilder#registerTypeAdapterFactory(TypeAdapterFactory)\n\t */\n\tpublic void addTypeAdapterFactory(@Nonnull TypeAdapterFactory factory) {\n\t\tupdateBuilder(builder -> builder.registerTypeAdapterFactory(factory));\n\t}\n\n\t/**\n\t * Register a type adapter for application-wide serialization support.\n\t *\n\t * @param type\n\t * \t\tType definition for the type adapter being registered.\n\t * @param adapter\n\t * \t\tAdapter implementation for the given type.\n\t * @param <T>\n\t * \t\tType to adapt.\n\t *\n\t * @see GsonBuilder#registerTypeAdapter(Type, Object)\n\t */\n\tpublic <T> void addTypeAdapter(@Nonnull Class<T> type, @Nonnull TypeAdapter<T> adapter) {\n\t\tregister(type, adapter);\n\t}\n\n\t/**\n\t * Register an instance creator for application-wide support.\n\t *\n\t * @param type\n\t * \t\tType definition for the instance creator being registered.\n\t * @param creator\n\t * \t\tInstance creator implementation for the given type.\n\t * @param <T>\n\t * \t\tType to adapt.\n\t *\n\t * @see GsonBuilder#registerTypeAdapter(Type, Object)\n\t */\n\tpublic <T> void addTypeInstanceCreator(@Nonnull Class<? extends T> type, @Nonnull InstanceCreator<? extends T> creator) {\n\t\tregister(type, creator);\n\t}\n\n\t/**\n\t * Register a type deserializer for application-wide support.\n\t *\n\t * @param type\n\t * \t\tType definition for the type deserializer being registered.\n\t * @param deserializer\n\t * \t\tDeserializer implementation for the given type.\n\t * @param <T>\n\t * \t\tType to adapt.\n\t *\n\t * @see GsonBuilder#registerTypeAdapter(Type, Object)\n\t */\n\tpublic <T> void addTypeDeserializer(@Nonnull Class<? extends T> type, @Nonnull JsonDeserializer<? extends T> deserializer) {\n\t\tregister(type, deserializer);\n\t}\n\n\t/**\n\t * Register a type serializer for application-wide support.\n\t *\n\t * @param type\n\t * \t\tType definition for the type serializer being registered.\n\t * \t\tThis type must be the exact-intended type. When a subtype is serialized this will not be used.\n\t * @param serializer\n\t * \t\tSerializer implementation for the given type.\n\t * @param <T>\n\t * \t\tType to adapt.\n\t *\n\t * @see GsonBuilder#registerTypeAdapter(Type, Object)\n\t */\n\tpublic <T> void addTypeSerializer(@Nonnull Class<? extends T> type, @Nonnull JsonSerializer<? extends T> serializer) {\n\t\tregister(type, serializer);\n\t}\n\n\t/**\n\t * Register a type adapter <i>(Loose terminology, multiple types are supported)</i>\n\t * for application-wide serialization support.\n\t *\n\t * @param type\n\t * \t\tType definition for the type adapter being registered.\n\t * @param adapter\n\t * \t\tAdapter implementation for the given type. Must be a {@link TypeAdapter},\n\t *        {@link InstanceCreator}, {@link JsonSerializer},or {@link JsonDeserializer}.\n\t *\n\t * @see GsonBuilder#registerTypeAdapter(Type, Object)\n\t */\n\tprivate void register(@Nonnull Class<?> type, @Nonnull Object adapter) {\n\t\tupdateBuilder(builder -> builder.registerTypeAdapter(type, adapter));\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic GsonProviderConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\t/**\n\t * @return Copy of the current {@link #builder}.\n\t */\n\t@Nonnull\n\tprivate GsonBuilder getBuilderCopy() {\n\t\tGsonBuilder local = builder;\n\n\t\t// No builder set up yet, so it'd be the default.\n\t\tif (local == null)\n\t\t\treturn new GsonBuilder();\n\n\t\t// Create a copy of the builder instance.\n\t\t// It has the same setup as the managed builder instance.\n\t\treturn local.create().newBuilder();\n\t}\n\n\t/**\n\t * Update the {@link #builder} instance.\n\t *\n\t * @param consumer\n\t * \t\tConsumer to adapt the builder config with.\n\t */\n\tprivate void updateBuilder(@Nullable Consumer<GsonBuilder> consumer) {\n\t\tGsonBuilder newBuilder = getBuilderCopy();\n\t\tif (consumer != null) consumer.accept(newBuilder);\n\t\tbuilder = newBuilder;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/json/GsonProviderConfig.java",
    "content": "package software.coley.recaf.services.json;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link GsonProvider}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class GsonProviderConfig extends BasicConfigContainer implements ServiceConfig {\n\tprivate final ObservableBoolean prettyPrint = new ObservableBoolean(true);\n\n\t@Inject\n\tpublic GsonProviderConfig() {\n\t\tsuper(ConfigGroups.SERVICE_IO, GsonProvider.SERVICE_ID + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"pretty-print\", boolean.class, prettyPrint));\n\t}\n\n\t/**\n\t * @return {@code true} to enable pretty printing with {@link GsonProvider}.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getPrettyPrint() {\n\t\treturn prettyPrint;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/BasicMappingsRemapper.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.commons.Remapper;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * {@link Remapper} implementation that delegates to a provided {@link Mappings} and supports local variable renaming.\n *\n * @author Matt Coley\n */\npublic class BasicMappingsRemapper extends Remapper {\n\tprotected final Mappings mappings;\n\tprivate boolean modified;\n\n\t/**\n\t * @param mappings\n\t * \t\tMappings to pull from.\n\t */\n\tpublic BasicMappingsRemapper(@Nonnull Mappings mappings) {\n\t\tsuper(RecafConstants.getAsmVersion());\n\n\t\tthis.mappings = mappings;\n\t}\n\n\t@Override\n\tpublic String map(String internalName) {\n\t\tString mapped = mappings.getMappedClassName(internalName);\n\t\tif (mapped != null) {\n\t\t\tmarkModified();\n\t\t\treturn mapped;\n\t\t}\n\t\treturn super.map(internalName);\n\t}\n\n\t@Override\n\tpublic String mapType(String internalName) {\n\t\t// Type can be null (object supertype, or module-info)\n\t\tif (internalName == null)\n\t\t\treturn null;\n\n\t\t// Check for array type\n\t\tif (internalName.charAt(0) == '[')\n\t\t\treturn mapDesc(internalName);\n\n\t\t// Standard internal name\n\t\treturn map(internalName);\n\t}\n\n\t@Override\n\tpublic String mapFieldName(String owner, String name, String descriptor) {\n\t\tString mapped = mappings.getMappedFieldName(owner, name, descriptor);\n\t\tif (mapped != null) {\n\t\t\tmarkModified();\n\t\t\treturn mapped;\n\t\t}\n\t\treturn super.mapFieldName(owner, name, descriptor);\n\t}\n\n\t@Override\n\tpublic String mapMethodName(String owner, String name, String descriptor) {\n\t\tString mapped = mappings.getMappedMethodName(owner, name, descriptor);\n\t\tif (mapped != null) {\n\t\t\tmarkModified();\n\t\t\treturn mapped;\n\t\t}\n\t\treturn super.mapMethodName(owner, name, descriptor);\n\t}\n\n\t@Override\n\tpublic String mapMethodDesc(String methodDescriptor) {\n\t\tint lastTypeEndOffset = methodDescriptor.indexOf(';');\n\t\tif (lastTypeEndOffset == -1) {\n\t\t\t// No object typees to map\n\t\t\treturn methodDescriptor;\n\t\t}\n\t\tint lastTypeStartOffset = 0;\n\t\tStringBuilder builder = new StringBuilder(methodDescriptor.length());\n\t\tint tail;\n\t\tdo {\n\t\t\tint bookkeep = lastTypeStartOffset;\n\t\t\tlastTypeStartOffset = methodDescriptor.indexOf('L', lastTypeStartOffset);\n\n\t\t\t// Append leftover parts on the left side\n\t\t\tbuilder.append(methodDescriptor, bookkeep, lastTypeStartOffset);\n\t\t\tString type = methodDescriptor.substring(lastTypeStartOffset + 1, lastTypeEndOffset);\n\t\t\tString mapped = mapType(type);\n\t\t\tbuilder.append('L').append(mapped).append(';');\n\n\t\t\t// Skip L_TYPE_;\n\t\t\tlastTypeStartOffset += type.length() + 2;\n\t\t\ttail = lastTypeStartOffset;\n\t\t} while ((lastTypeEndOffset = methodDescriptor.indexOf(';', lastTypeEndOffset + 1)) != -1);\n\n\t\t// Append remaining characters (tail onwards)\n\t\tbuilder.append(methodDescriptor, tail, methodDescriptor.length());\n\t\treturn builder.toString();\n\t}\n\n\t@Override\n\tpublic String mapDesc(String descriptor) {\n\t\tif (descriptor == null || descriptor.isEmpty()) {\n\t\t\treturn descriptor;\n\t\t}\n\t\tif (descriptor.charAt(0) == '(') {\n\t\t\treturn mapMethodDesc(descriptor);\n\t\t}\n\t\tif (descriptor.charAt(descriptor.length() - 1) != ';') {\n\t\t\treturn descriptor;\n\t\t}\n\t\tint dimensions = 0;\n\t\twhile (descriptor.charAt(dimensions) == '[') {\n\t\t\tdimensions++;\n\t\t}\n\t\tStringBuilder builder = new StringBuilder(descriptor.length());\n\t\tint bookkeep = dimensions;\n\t\twhile (dimensions-- != 0) {\n\t\t\tbuilder.append('[');\n\t\t}\n\t\tbuilder.append('L');\n\t\tbuilder.append(map(descriptor.substring(bookkeep + 1, descriptor.length() - 1)));\n\t\tbuilder.append(';');\n\t\treturn builder.toString();\n\t}\n\n\t@Override\n\tpublic String mapRecordComponentName(String owner, String name, String descriptor) {\n\t\treturn mapMethodName(owner, name, descriptor);\n\t}\n\n\t@Override\n\tpublic String mapPackageName(String name) {\n\t\t// Used only by module attributes\n\t\treturn name;\n\t}\n\n\t@Override\n\tpublic String mapModuleName(String name) {\n\t\t// Used only by module attributes\n\t\treturn name;\n\t}\n\n\t@Override\n\tpublic String mapAnnotationAttributeName(String descriptor, String name) {\n\t\t// Used by annotation visitor\n\t\treturn name;\n\t}\n\n\t@Override\n\tpublic String mapInvokeDynamicMethodName(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {\n\t\treturn name;\n\t}\n\n\t/**\n\t * @param className\n\t * \t\tInternal name of the class defining the method the variable resides in.\n\t * @param methodName\n\t * \t\tName of the method.\n\t * @param methodDesc\n\t * \t\tDescriptor of the method.\n\t * @param name\n\t * \t\tName of the variable.\n\t * @param desc\n\t * \t\tDescriptor of the variable.\n\t * @param index\n\t * \t\tIndex of the variable.\n\t *\n\t * @return Mapped name of the variable, or the existing name if no mapping exists.\n\t */\n\tpublic String mapVariableName(String className, String methodName, String methodDesc,\n\t\t\t\t\t\t\t\t  String name, String desc, int index) {\n\t\tString mapped = mappings.getMappedVariableName(className, methodName, methodDesc, name, desc, index);\n\t\tif (mapped != null) {\n\t\t\tmarkModified();\n\t\t\treturn mapped;\n\t\t}\n\t\t// Use existing variable name.\n\t\treturn name;\n\t}\n\n\tprotected void markModified() {\n\t\tmodified = true;\n\t}\n\n\t/**\n\t * @return {@code true} when any mapping has been found and used.\n\t */\n\tpublic boolean hasMappingBeenApplied() {\n\t\treturn modified;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/IntermediateMappings.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.services.mapping.data.ClassMapping;\nimport software.coley.recaf.services.mapping.data.FieldMapping;\nimport software.coley.recaf.services.mapping.data.MethodMapping;\nimport software.coley.recaf.services.mapping.data.VariableMapping;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.TreeSet;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Collection of object representations of mappings.\n * Useful as an intermediate between multiple types of {@link Mappings}.\n *\n * @author Matt Coley\n */\npublic class IntermediateMappings implements Mappings {\n\tprotected final Map<String, ClassMapping> classes = new ConcurrentHashMap<>();\n\tprotected final Map<String, List<FieldMapping>> fields = new ConcurrentHashMap<>();\n\tprotected final Map<String, List<MethodMapping>> methods = new ConcurrentHashMap<>();\n\tprotected final Map<String, List<VariableMapping>> variables = new ConcurrentHashMap<>();\n\n\t/**\n\t * Copies all values from the given mappings into this instance.\n\t *\n\t * @param other\n\t * \t\tAnother intermediate mappings instance.\n\t */\n\tpublic void putAll(@Nonnull IntermediateMappings other) {\n\t\tclasses.putAll(other.classes);\n\t\tother.fields.forEach((className, otherFields) -> fields.merge(className, otherFields, Lists::combine));\n\t\tother.methods.forEach((className, otherMethods) -> methods.merge(className, otherMethods, Lists::combine));\n\t\tother.variables.forEach((className, otherVariables) -> variables.merge(className, otherVariables, Lists::combine));\n\t}\n\n\t/**\n\t * @param oldName\n\t * \t\tPre-mapping name.\n\t * @param newName\n\t * \t\tPost-mapping name.\n\t */\n\tpublic void addClass(@Nonnull String oldName, @Nonnull String newName) {\n\t\tif (Objects.equals(oldName, newName)) return; // Skip identity mappings\n\t\tclasses.put(oldName, new ClassMapping(oldName, newName));\n\t}\n\n\t/**\n\t * @param ownerName\n\t * \t\tName of class defining the field.\n\t * @param desc\n\t * \t\tDescriptor type of the field.\n\t * @param oldName\n\t * \t\tPre-mapping field name.\n\t * @param newName\n\t * \t\tPost-mapping field name.\n\t */\n\tpublic void addField(@Nonnull String ownerName, @Nullable String desc, @Nonnull String oldName, @Nonnull String newName) {\n\t\tif (Objects.equals(oldName, newName)) return; // Skip identity mappings\n\t\tfields.computeIfAbsent(ownerName, n -> Collections.synchronizedList(new ArrayList<>()))\n\t\t\t\t.add(new FieldMapping(ownerName, oldName, desc, newName));\n\t}\n\n\t/**\n\t * @param ownerName\n\t * \t\tName of class defining the method.\n\t * @param desc\n\t * \t\tDescriptor type of the method.\n\t * @param oldName\n\t * \t\tPre-mapping method name.\n\t * @param newName\n\t * \t\tPost-mapping method name.\n\t */\n\tpublic void addMethod(@Nonnull String ownerName, @Nonnull String desc, @Nonnull String oldName, @Nonnull String newName) {\n\t\tif (Objects.equals(oldName, newName)) return; // Skip identity mappings\n\t\tmethods.computeIfAbsent(ownerName, n -> Collections.synchronizedList(new ArrayList<>()))\n\t\t\t\t.add(new MethodMapping(ownerName, oldName, desc, newName));\n\t}\n\n\t/**\n\t * @param ownerName\n\t * \t\tName of class defining the method.\n\t * @param methodName\n\t * \t\tPre-mapping method name.\n\t * @param methodDesc\n\t * \t\tDescriptor type of the method.\n\t * @param desc\n\t * \t\tVariable descriptor.\n\t * @param oldName\n\t * \t\tVariable old name.\n\t * @param index\n\t * \t\tVariable index.\n\t * @param newName\n\t * \t\tPost-mapping method name.\n\t */\n\tpublic void addVariable(@Nonnull String ownerName, @Nonnull String methodName, @Nonnull String methodDesc,\n\t                        @Nullable String desc, @Nullable String oldName, int index,\n\t                        @Nonnull String newName) {\n\t\tif (Objects.equals(oldName, newName)) return; // Skip identity mappings\n\t\tString key = varKey(ownerName, methodName, methodDesc);\n\t\tvariables.computeIfAbsent(key, n -> Collections.synchronizedList(new ArrayList<>()))\n\t\t\t\t.add(new VariableMapping(ownerName, methodName, methodDesc, desc, oldName, index, newName));\n\t}\n\n\t/**\n\t * @return {@code true} when this mappings container has no entries.\n\t */\n\tpublic boolean isEmpty() {\n\t\treturn classes.isEmpty() && fields.isEmpty() && methods.isEmpty() && variables.isEmpty();\n\t}\n\n\t/**\n\t * @return Names of classes with mappings.\n\t */\n\t@Nonnull\n\tpublic Set<String> getClassesWithMappings() {\n\t\tSet<String> set = new TreeSet<>();\n\t\tset.addAll(classes.keySet());\n\t\tset.addAll(fields.keySet());\n\t\tset.addAll(methods.keySet());\n\t\treturn set;\n\t}\n\n\t/**\n\t * @return Class mappings.\n\t */\n\t@Nonnull\n\tpublic Map<String, ClassMapping> getClasses() {\n\t\treturn classes;\n\t}\n\n\t/**\n\t * @return Field mappings by owner type.\n\t */\n\t@Nonnull\n\tpublic Map<String, List<FieldMapping>> getFields() {\n\t\treturn fields;\n\t}\n\n\t/**\n\t * @return Method mappings by owner type.\n\t */\n\t@Nonnull\n\tpublic Map<String, List<MethodMapping>> getMethods() {\n\t\treturn methods;\n\t}\n\n\t/**\n\t * @return Variable mappings by declaring method type.\n\t */\n\t@Nonnull\n\tpublic Map<String, List<VariableMapping>> getVariables() {\n\t\treturn variables;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tPre-mapping name.\n\t *\n\t * @return Mapping instance of class. May be {@code null}.\n\t */\n\t@Nullable\n\tpublic ClassMapping getClassMapping(@Nonnull String name) {\n\t\treturn classes.get(name);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tDeclaring class name.\n\t *\n\t * @return List of field mapping instances.\n\t */\n\t@Nonnull\n\tpublic List<FieldMapping> getClassFieldMappings(@Nonnull String name) {\n\t\treturn fields.getOrDefault(name, Collections.emptyList());\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tDeclaring class name.\n\t *\n\t * @return List of method mapping instances.\n\t */\n\t@Nonnull\n\tpublic List<MethodMapping> getClassMethodMappings(@Nonnull String name) {\n\t\treturn methods.getOrDefault(name, Collections.emptyList());\n\t}\n\n\t/**\n\t * @param ownerName\n\t * \t\tDeclaring class name.\n\t * @param methodName\n\t * \t\tDeclaring method name.\n\t * @param methodDesc\n\t * \t\tDeclaring method descriptor.\n\t *\n\t * @return List of field mapping instances.\n\t */\n\t@Nonnull\n\tpublic List<VariableMapping> getMethodVariableMappings(@Nonnull String ownerName, @Nonnull String methodName, @Nonnull String methodDesc) {\n\t\treturn variables.getOrDefault(varKey(ownerName, methodName, methodDesc), Collections.emptyList());\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMappedClassName(@Nonnull String internalName) {\n\t\tClassMapping mapping = getClassMapping(internalName);\n\t\tif (mapping == null)\n\t\t\treturn null;\n\t\treturn mapping.getNewName();\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMappedFieldName(@Nonnull String ownerName, @Nonnull String fieldName, @Nonnull String fieldDesc) {\n\t\tList<FieldMapping> fieldInClass = getClassFieldMappings(ownerName);\n\t\tfor (FieldMapping field : fieldInClass)\n\t\t\t// Some mapping formats exclude descriptors (which sucks) so we bypass the desc check if that is the case.\n\t\t\tif ((field.getDesc() == null || Objects.equals(fieldDesc, field.getDesc())) && field.getOldName().equals(fieldName))\n\t\t\t\treturn field.getNewName();\n\t\treturn null;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMappedMethodName(@Nonnull String ownerName, @Nonnull String methodName, @Nonnull String methodDesc) {\n\t\tList<MethodMapping> methodsInClass = getClassMethodMappings(ownerName);\n\t\tfor (MethodMapping method : methodsInClass)\n\t\t\tif (methodDesc.equals(method.getDesc()) && method.getOldName().equals(methodName))\n\t\t\t\treturn method.getNewName();\n\t\treturn null;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMappedVariableName(@Nonnull String className, @Nonnull String methodName, @Nonnull String methodDesc,\n\t                                    @Nullable String name, @Nullable String desc, int index) {\n\t\tList<VariableMapping> variablesInMethod = getMethodVariableMappings(className, methodName, methodDesc);\n\t\tfor (VariableMapping variable : variablesInMethod) {\n\t\t\tif (equalsOrNull(desc, variable.getDesc()) && equalsOrNull(name, variable.getOldName())\n\t\t\t\t\t&& indexEqualsOrOOB(index, variable.getIndex())) {\n\t\t\t\treturn variable.getNewName();\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IntermediateMappings exportIntermediate() {\n\t\treturn this;\n\t}\n\n\t@Nonnull\n\tprotected static String varKey(@Nonnull String ownerName, @Nonnull String methodName, @Nonnull String methodDesc) {\n\t\treturn ownerName + \"\\t\" + methodName + \"\\t\" + methodDesc;\n\t}\n\n\tprivate static boolean indexEqualsOrOOB(int a, int b) {\n\t\treturn a < 0 || b < 0 || a == b;\n\t}\n\n\tprivate static boolean equalsOrNull(@Nullable String a, @Nullable String b) {\n\t\treturn a == null || b == null || a.equals(b);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/MappingApplicationListener.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Used to intercept application state before and after {@link MappingResults#apply()}.\n *\n * @author Matt Coley\n * @see MappingResults Can be added to the constuctor to affect a single mapping job.\n * @see MappingListeners Can be added in order to affect all mapping jobs.\n */\npublic interface MappingApplicationListener extends PrioritySortable {\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace the mappings are applied to.\n\t * @param mappingResults\n\t * \t\tMapping results to be applied.\n\t */\n\tvoid onPreApply(@Nonnull Workspace workspace, @Nonnull MappingResults mappingResults);\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace the mappings are applied to.\n\t * @param mappingResults\n\t * \t\tMapping results that were applied.\n\t */\n\tvoid onPostApply(@Nonnull Workspace workspace, @Nonnull MappingResults mappingResults);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/MappingApplier.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.ClassWriter;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.properties.builtin.HasMappedReferenceProperty;\nimport software.coley.recaf.info.properties.builtin.OriginalClassNameProperty;\nimport software.coley.recaf.info.properties.builtin.RemapOriginTaskProperty;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.mapping.aggregate.AggregateMappingManager;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.util.threading.ThreadUtil;\nimport software.coley.recaf.util.visitors.IllegalSignatureRemovingVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collection;\nimport java.util.concurrent.ExecutorService;\nimport java.util.stream.Stream;\n\n/**\n * Applies mappings to workspaces and workspace resources, wrapping the results in a {@link MappingResults}.\n * To update the workspace with the mapping results, use {@link MappingResults#apply()}.\n *\n * @author Matt Coley\n * @see MappingResults\n */\npublic class MappingApplier {\n\tprivate static final ExecutorService applierThreadPool = ThreadPoolFactory.newFixedThreadPool(MappingApplierService.SERVICE_ID);\n\tprivate final InheritanceGraph inheritanceGraph;\n\tprivate final AggregateMappingManager aggregateMappingManager;\n\tprivate final MappingListeners listeners;\n\tprivate final Workspace workspace;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to apply mappings in.\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph for the given workspace.\n\t * @param listeners\n\t * \t\tApplication mapping listeners\n\t * \t\t<i>(If the target workspace is the {@link WorkspaceManager#getCurrent() current one})</i>\n\t * @param aggregateMappingManager\n\t * \t\tAggregate mappings for tracking applications in the current workspace\n\t * \t\t<i>(If the target workspace is the {@link WorkspaceManager#getCurrent() current one})</i>\n\t */\n\tpublic MappingApplier(@Nonnull Workspace workspace,\n\t                      @Nonnull InheritanceGraph inheritanceGraph,\n\t                      @Nullable MappingListeners listeners,\n\t                      @Nullable AggregateMappingManager aggregateMappingManager) {\n\t\tthis.inheritanceGraph = inheritanceGraph;\n\t\tthis.aggregateMappingManager = aggregateMappingManager;\n\t\tthis.listeners = listeners;\n\t\tthis.workspace = workspace;\n\t}\n\n\t/**\n\t * Applies the mapping operation to the given classes.\n\t *\n\t * @param mappings\n\t * \t\tThe mappings to apply.\n\t * @param resource\n\t * \t\tResource containing the classes.\n\t * @param bundle\n\t * \t\tBundle containing the classes.\n\t * @param classes\n\t * \t\tClasses to apply mappings to.\n\t *\n\t * @return Result wrapper detailing affected classes from the mapping operation.\n\t */\n\t@Nonnull\n\tpublic MappingResults applyToClasses(@Nonnull Mappings mappings,\n\t                                     @Nonnull WorkspaceResource resource,\n\t                                     @Nonnull JvmClassBundle bundle,\n\t                                     @Nonnull Collection<JvmClassInfo> classes) {\n\t\tmappings = enrich(mappings);\n\t\tMappingApplicationListener listener = listeners == null ? null : listeners.createBundledMappingApplicationListener();\n\t\tMappingResults results = new MappingResults(workspace, mappings, listener);\n\t\tif (aggregateMappingManager != null)\n\t\t\tresults.withAggregateManager(aggregateMappingManager);\n\n\t\t// Apply mappings to the provided classes, collecting into the results model.\n\t\tMappings finalMappings = mappings;\n\t\tExecutorService service = ThreadUtil.phasingService(applierThreadPool);\n\t\tfor (JvmClassInfo classInfo : classes)\n\t\t\tservice.execute(() -> dumpIntoResults(results, workspace, resource, bundle, classInfo, finalMappings));\n\t\tThreadUtil.blockUntilComplete(service);\n\n\t\t// Yield results\n\t\treturn results;\n\t}\n\n\t/**\n\t * Applies the mapping operation to the current workspace's primary resource.\n\t *\n\t * @param mappings\n\t * \t\tThe mappings to apply.\n\t *\n\t * @return Result wrapper detailing affected classes from the mapping operation.\n\t */\n\t@Nonnull\n\tpublic MappingResults applyToPrimaryResource(@Nonnull Mappings mappings) {\n\t\tmappings = enrich(mappings);\n\t\tMappingApplicationListener listener = listeners == null ? null : listeners.createBundledMappingApplicationListener();\n\t\tMappingResults results = new MappingResults(workspace, mappings, listener);\n\t\tif (aggregateMappingManager != null)\n\t\t\tresults.withAggregateManager(aggregateMappingManager);\n\n\t\t// Apply mappings to all classes in the primary resource, collecting into the results model.\n\t\tMappings finalMappings = mappings;\n\t\tExecutorService service = ThreadUtil.phasingService(applierThreadPool);\n\t\tWorkspaceResource resource = workspace.getPrimaryResource();\n\t\tresource.jvmAllClassBundleStreamRecursive().forEach(bundle -> {\n\t\t\tbundle.forEach(classInfo -> {\n\t\t\t\tservice.execute(() -> dumpIntoResults(results, workspace, resource, bundle, classInfo, finalMappings));\n\t\t\t});\n\t\t});\n\t\tThreadUtil.blockUntilComplete(service);\n\n\t\t// Yield results\n\t\treturn results;\n\t}\n\n\t@Nonnull\n\tprivate Mappings enrich(@Nonnull Mappings mappings) {\n\t\t// Map intermediate mappings to the adapter so that we can pass in the inheritance graph for better coverage\n\t\t// of cases inherited field/method references.\n\t\tif (mappings instanceof IntermediateMappings intermediateMappings) {\n\t\t\t// Mapping formats that export to intermediate should mark whether they support\n\t\t\t// differentiation of field and variable types.\n\t\t\tboolean fieldDifferentiation = mappings.doesSupportFieldTypeDifferentiation();\n\t\t\tboolean varDifferentiation = mappings.doesSupportVariableTypeDifferentiation();\n\t\t\tMappingsAdapter adapter = new MappingsAdapter(fieldDifferentiation, varDifferentiation);\n\t\t\tadapter.importIntermediate(intermediateMappings);\n\t\t\tmappings = adapter;\n\t\t}\n\n\t\t// Check if mappings can be enriched with type look-ups\n\t\tif (mappings instanceof MappingsAdapter adapter) {\n\t\t\t// If we have \"Dog extends Animal\" and both define \"jump\" this lets \"Dog.jump()\" see \"Animal.jump()\"\n\t\t\t// allowing mappings that aren't complete for their type hierarchies to be filled in.\n\t\t\tadapter.enableHierarchyLookup(inheritanceGraph);\n\t\t}\n\n\t\treturn mappings;\n\t}\n\n\t/**\n\t * Applies mappings locally and dumps them into the provided results collection.\n\t * <p>\n\t * To apply these mappings you need to call {@link MappingResults#apply()}.\n\t *\n\t * @param results\n\t * \t\tResults collection to insert into.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param classInfo\n\t * \t\tThe class to apply mappings to.\n\t * @param mappings\n\t * \t\tThe mappings to apply.\n\t */\n\tprivate static void dumpIntoResults(@Nonnull MappingResults results,\n\t                                    @Nonnull Workspace workspace,\n\t                                    @Nonnull WorkspaceResource resource,\n\t                                    @Nonnull JvmClassBundle bundle,\n\t                                    @Nonnull JvmClassInfo classInfo,\n\t                                    @Nonnull Mappings mappings) {\n\t\tString originalName = classInfo.getName();\n\n\t\t// Apply renamer\n\t\tClassReader reader = classInfo.getClassReader();\n\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\tWorkspaceClassRemapper remapVisitor = new WorkspaceClassRemapper(writer, workspace, mappings);\n\t\tClassVisitor cv = new IllegalSignatureRemovingVisitor(remapVisitor); // Wrap because ASM crashes otherwise with obfuscated inputs.\n\t\treader.accept(cv, classInfo.getClassReaderFlags());\n\n\t\t// Update class if it has any modified references\n\t\tif (remapVisitor.hasMappingBeenApplied()) {\n\t\t\tJvmClassInfo updatedInfo = classInfo.toJvmClassBuilder()\n\t\t\t\t\t.adaptFrom(writer.toByteArray())\n\t\t\t\t\t.build();\n\n\t\t\t// Mark has referencing something mapped.\n\t\t\tHasMappedReferenceProperty.set(updatedInfo);\n\n\t\t\t// Set the result wrapper that caused this class to update.\n\t\t\tupdatedInfo.setProperty(new RemapOriginTaskProperty(results));\n\n\t\t\t// If the name changed, mark what the original was.\n\t\t\t// If this property was set before (A --> B, now B --> C) then we won't update it.\n\t\t\tif (!updatedInfo.getName().equals(originalName))\n\t\t\t\tupdatedInfo.setPropertyIfMissing(OriginalClassNameProperty.KEY,\n\t\t\t\t\t\t() -> new OriginalClassNameProperty(originalName));\n\n\t\t\t// Add to the results collection.\n\t\t\tresults.add(workspace, resource, bundle, classInfo, updatedInfo);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/MappingApplierConfig.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link MappingApplierService}\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class MappingApplierConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic MappingApplierConfig() {\n\t\tsuper(ConfigGroups.SERVICE_MAPPING, MappingApplierService.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/MappingApplierService.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.mapping.aggregate.AggregateMappingManager;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Objects;\nimport java.util.concurrent.ExecutorService;\n\n/**\n * Service offering the creation of {@link MappingApplier mapping appliers} for workspaces.\n *\n * @author Matt Coley\n * @see MappingApplier\n */\n@ApplicationScoped\npublic class MappingApplierService implements Service {\n\tpublic static final String SERVICE_ID = \"mapping-applier\";\n\tprivate static final ExecutorService applierThreadPool = ThreadPoolFactory.newFixedThreadPool(SERVICE_ID);\n\tprivate final InheritanceGraphService inheritanceGraphService;\n\tprivate final AggregateMappingManager aggregateMappingManager;\n\tprivate final MappingListeners listeners;\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final MappingApplierConfig config;\n\n\t@Inject\n\tpublic MappingApplierService(@Nonnull MappingApplierConfig config,\n\t                             @Nonnull InheritanceGraphService inheritanceGraphService,\n\t                             @Nonnull AggregateMappingManager aggregateMappingManager,\n\t                             @Nonnull MappingListeners listeners,\n\t                             @Nonnull WorkspaceManager workspaceManager) {\n\t\tthis.inheritanceGraphService = inheritanceGraphService;\n\t\tthis.aggregateMappingManager = aggregateMappingManager;\n\t\tthis.listeners = listeners;\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to apply mappings in.\n\t *\n\t * @return Applier for the given workspace.\n\t */\n\t@Nonnull\n\tpublic MappingApplier inWorkspace(@Nonnull Workspace workspace) {\n\t\tif (workspace == workspaceManager.getCurrent())\n\t\t\treturn Objects.requireNonNull(inCurrentWorkspace(), \"Failed to access current workspace for mapping application\");\n\t\treturn new MappingApplier(workspace, inheritanceGraphService.newInheritanceGraph(workspace), listeners, null);\n\t}\n\n\t/**\n\t * @return Applier for the current workspace, or {@code null} if no workspace is open.\n\t */\n\t@Nullable\n\tpublic MappingApplier inCurrentWorkspace() {\n\t\tif (!workspaceManager.hasCurrentWorkspace())\n\t\t\treturn null;\n\n\t\tInheritanceGraph currentWorkspaceInheritanceGraph = inheritanceGraphService.getCurrentWorkspaceInheritanceGraph();\n\t\tif (currentWorkspaceInheritanceGraph == null)\n\t\t\treturn null;\n\n\t\tWorkspace workspace = workspaceManager.getCurrent();\n\t\treturn new MappingApplier(workspace, currentWorkspaceInheritanceGraph, listeners, aggregateMappingManager);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic MappingApplierConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/MappingListeners.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * Manages listeners for things like {@link MappingResults} applying operations.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class MappingListeners implements Service {\n\tpublic static final String SERVICE_ID = \"mapping-listeners\";\n\tprivate static final Logger logger = Logging.get(MappingListeners.class);\n\tprivate final List<MappingApplicationListener> mappingApplicationListeners = new CopyOnWriteArrayList<>();\n\tprivate final MappingListenersConfig config;\n\n\t@Inject\n\tpublic MappingListeners(@Nonnull MappingListenersConfig config) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Adds a listener which is passed to created {@link MappingResults} from\n\t * {@link MappingApplier#applyToPrimaryResource(Mappings)} and\n\t * {@link MappingApplier#applyToClasses(Mappings, WorkspaceResource, JvmClassBundle, Collection)}.\n\t * <p>\n\t * This allows you to listen to all mapping operations done via proper API usage, intercepting before they\n\t * execute the task, and after they complete the mapping task.\n\t *\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tpublic synchronized void addMappingApplicationListener(@Nonnull MappingApplicationListener listener) {\n\t\tif (!mappingApplicationListeners.contains(listener))\n\t\t\tPrioritySortable.add(mappingApplicationListeners, listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t *\n\t * @return {@code true} when item was removed.\n\t * {@code false} when item was not in the list to begin with.\n\t */\n\tpublic synchronized boolean removeMappingApplicationListener(@Nonnull MappingApplicationListener listener) {\n\t\treturn mappingApplicationListeners.remove(listener);\n\t}\n\n\t/**\n\t * @return Application listener encompassing all the current items in {@link #mappingApplicationListeners},\n\t * or {@code null} if there are no listeners.\n\t */\n\t@Nullable\n\tpublic MappingApplicationListener createBundledMappingApplicationListener() {\n\t\tfinal List<MappingApplicationListener> listeners = mappingApplicationListeners;\n\n\t\t// Simple edge cases.\n\t\tif (listeners.isEmpty())\n\t\t\treturn null;\n\t\telse if (listeners.size() == 1)\n\t\t\treturn listeners.getFirst();\n\n\t\t// Bundle multiple listeners.\n\t\treturn new MappingApplicationListener() {\n\t\t\t@Override\n\t\t\tpublic void onPreApply(@Nonnull Workspace workspace, @Nonnull MappingResults mappingResults) {\n\t\t\t\tUnchecked.checkedForEach(listeners, listener -> listener.onPreApply(workspace, mappingResults),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown before applying mappings\", t));\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onPostApply(@Nonnull Workspace workspace, @Nonnull MappingResults mappingResults) {\n\t\t\t\tUnchecked.checkedForEach(listeners, listener -> listener.onPostApply(workspace, mappingResults),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown after applying mappings\", t));\n\t\t\t}\n\t\t};\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic MappingListenersConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/MappingListenersConfig.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link MappingListeners}\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class MappingListenersConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic MappingListenersConfig() {\n\t\tsuper(ConfigGroups.SERVICE_MAPPING, MappingListeners.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/MappingResults.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.slf4j.Logger;\nimport software.coley.collections.tuple.Pair;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.BundlePathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.services.mapping.aggregate.AggregateMappingManager;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.SortedSet;\nimport java.util.TreeSet;\nimport java.util.stream.Stream;\n\n/**\n * Result wrapper for {@link MappingApplier} operations.\n * Can serve as a preview for mapping operations before updating the affected {@link Workspace}.\n * <br>\n * Use {@link #apply()} to apply the mappings to the {@link WorkspaceResource} targeted in the mapping operation.\n *\n * @author Matt Coley\n */\npublic class MappingResults {\n\tprivate static final Logger logger = Logging.get(MappingResults.class);\n\tprivate final Map<String, String> mappedClasses = new HashMap<>();\n\tprivate final Map<String, String> mappedClassesReverse = new HashMap<>();\n\tprivate final Map<String, ClassPathNode> preMappingPaths = new HashMap<>();\n\tprivate final Map<String, ClassPathNode> postMappingPaths = new HashMap<>();\n\tprivate final MappingApplicationListener applicationHandler;\n\tprivate final Workspace workspace;\n\tprivate final Mappings mappings;\n\tprivate AggregateMappingManager aggregateMappingManager;\n\n\t/**\n\t * @param workspace\n\t * \t\tThe workspace the mappings are being applied to.\n\t * @param mappings\n\t * \t\tThe mappings implementation used in the operation.\n\t * @param applicationHandler\n\t * \t\tOptional handler for intercepting post/pre mapping states.\n\t */\n\tpublic MappingResults(@Nonnull Workspace workspace, @Nonnull Mappings mappings, @Nullable MappingApplicationListener applicationHandler) {\n\t\tthis.workspace = workspace;\n\t\tthis.mappings = mappings;\n\t\tthis.applicationHandler = applicationHandler;\n\t}\n\n\t/**\n\t * @param aggregateMappingManager\n\t * \t\tAggregate mapping manager to track mapping applications in.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic MappingResults withAggregateManager(@Nonnull AggregateMappingManager aggregateMappingManager) {\n\t\tthis.aggregateMappingManager = aggregateMappingManager;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace containing the class.\n\t * @param resource\n\t * \t\tResource containing the class.\n\t * @param bundle\n\t * \t\tBundle containing the class.\n\t * @param preMapping\n\t * \t\tThe pre-mapped class.\n\t * @param postMapping\n\t * \t\tThe post-mapped class.\n\t */\n\tpublic void add(@Nonnull Workspace workspace,\n\t                @Nonnull WorkspaceResource resource,\n\t                @Nonnull ClassBundle<?> bundle,\n\t                @Nonnull ClassInfo preMapping,\n\t                @Nonnull ClassInfo postMapping) {\n\t\tString preMappingName = preMapping.getName();\n\t\tString postMappingName = postMapping.getName();\n\t\tBundlePathNode bundlePath = PathNodes.bundlePath(workspace, resource, bundle);\n\t\tClassPathNode preMappingPath = bundlePath.child(preMapping.getPackageName()).child(preMapping);\n\t\tClassPathNode postMappingPath = bundlePath.child(postMapping.getPackageName()).child(postMapping);\n\t\tsynchronized (mappedClasses) {\n\t\t\tmappedClasses.put(preMappingName, postMappingName);\n\t\t}\n\t\tsynchronized (mappedClassesReverse) {\n\t\t\tmappedClassesReverse.put(postMappingName, preMappingName);\n\t\t}\n\t\tsynchronized (preMappingPaths) {\n\t\t\tpreMappingPaths.put(preMappingName, preMappingPath);\n\t\t}\n\t\tsynchronized (postMappingPaths) {\n\t\t\tpostMappingPaths.put(postMappingName, postMappingPath);\n\t\t}\n\t}\n\n\t/**\n\t * Applies the mappings to the {@link Workspace} / {@link WorkspaceResource} from {@link MappingApplier}.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic void apply() {\n\t\t// Track changes in aggregate manager, if given.\n\t\tif (aggregateMappingManager != null)\n\t\t\taggregateMappingManager.updateAggregateMappings(mappings);\n\n\t\t// Pass to handler to notify of application of mappings has started.\n\t\tif (applicationHandler != null)\n\t\t\ttry {\n\t\t\t\tapplicationHandler.onPreApply(workspace, this);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Mapping application handler failed on pre-application\", t);\n\t\t\t}\n\n\t\t// Record mapping application jobs into a sorted set.\n\t\t// We want to apply some changes before others.\n\t\tSortedSet<ApplicationEntry> applicationEntries = new TreeSet<>();\n\t\tfor (Map.Entry<String, String> entry : mappedClasses.entrySet()) {\n\t\t\tString preMappedName = entry.getKey();\n\t\t\tString postMappedName = entry.getValue();\n\t\t\tClassPathNode preMappedPath = preMappingPaths.get(preMappedName);\n\t\t\tClassPathNode postMappedPath = postMappingPaths.get(postMappedName);\n\t\t\tif (preMappedPath != null && postMappedPath != null) {\n\t\t\t\tapplicationEntries.add(new ApplicationEntry(preMappedPath, postMappedPath, () -> {\n\t\t\t\t\tClassBundle<ClassInfo> bundle = (ClassBundle<ClassInfo>) postMappedPath.getValueOfType(Bundle.class);\n\t\t\t\t\tif (bundle == null)\n\t\t\t\t\t\tthrow new IllegalStateException(\"Cannot apply mapping for '\" + preMappedName + \"', path missing bundle\");\n\n\t\t\t\t\t// Put mapped class into bundle\n\t\t\t\t\tClassInfo postMappedClass = postMappedPath.getValue();\n\t\t\t\t\tbundle.put(postMappedClass);\n\n\t\t\t\t\t// Remove old classes if they have been renamed and do not occur\n\t\t\t\t\t// in a set of newly applied names\n\t\t\t\t\tif (!preMappedName.equals(postMappedName))\n\t\t\t\t\t\tbundle.remove(preMappedName);\n\t\t\t\t}));\n\t\t\t}\n\t\t}\n\n\t\t// Apply changes in sorted order.\n\t\tfor (ApplicationEntry entry : applicationEntries)\n\t\t\tentry.applicationRunnable().run();\n\n\t\t// Log in console how many classes got mapped.\n\t\tlogger.info(\"Applied mapping to {} classes\", preMappingPaths.size());\n\n\t\t// Pass to handler again to notify of application of mappings has completed/\n\t\tif (applicationHandler != null)\n\t\t\ttry {\n\t\t\t\tapplicationHandler.onPostApply(workspace, this);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Mapping application handler failed on post-application\", t);\n\t\t\t}\n\t}\n\n\t/**\n\t * @return The mappings implementation used in the operation.\n\t */\n\t@Nonnull\n\tpublic Mappings getMappings() {\n\t\treturn mappings;\n\t}\n\n\t/**\n\t * @param preMappedName\n\t * \t\tPre-mapping name.\n\t *\n\t * @return {@code true} when the class was affected by the mapping operation.\n\t */\n\tpublic boolean wasMapped(@Nonnull String preMappedName) {\n\t\treturn mappedClasses.containsKey(preMappedName);\n\t}\n\n\t/**\n\t * @param postMappingName\n\t * \t\tPost-mapping name.\n\t *\n\t * @return Name of the class before the mapping operation.\n\t * May be {@code null} if the post-mapping name was not renamed during the mapping operation.\n\t */\n\t@Nullable\n\tpublic String getPreMappingName(@Nonnull String postMappingName) {\n\t\treturn mappedClassesReverse.get(postMappingName);\n\t}\n\n\t/**\n\t * @param preMappingName\n\t * \t\tPre-mapping name.\n\t *\n\t * @return Post-mapped class info.\n\t * May be {@code null} if no the given pre-mapped name was not affected by the mapping operation.\n\t */\n\t@Nullable\n\tpublic ClassInfo getPostMappingClass(@Nonnull String preMappingName) {\n\t\tClassPathNode postMappingPath = getPostMappingPath(preMappingName);\n\t\tif (postMappingPath == null) return null;\n\t\treturn postMappingPath.getValue();\n\t}\n\n\t/**\n\t * @param preMappingName\n\t * \t\tPre-mapping name.\n\t *\n\t * @return Path node of post-mapped class.\n\t * May be {@code null} if no the given pre-mapped name was not affected by the mapping operation.\n\t */\n\t@Nullable\n\tpublic ClassPathNode getPostMappingPath(@Nonnull String preMappingName) {\n\t\tString postMappingName = mappedClasses.get(preMappingName);\n\t\tif (postMappingName == null) return null;\n\t\treturn postMappingPaths.get(postMappingName);\n\t}\n\n\t/**\n\t * @param postMappingName\n\t * \t\tPost-mapping name.\n\t *\n\t * @return Pre-mapped class info.\n\t * May be {@code null} if no the given post-mapped name was not present in the mapping operation output.\n\t */\n\t@Nullable\n\tpublic ClassInfo getPreMappingClass(@Nonnull String postMappingName) {\n\t\tClassPathNode preMappingPath = getPreMappingPath(postMappingName);\n\t\tif (preMappingPath == null) return null;\n\t\treturn preMappingPath.getValue();\n\t}\n\n\t/**\n\t * @param postMappingName\n\t * \t\tPost-mapping name.\n\t *\n\t * @return Path node of pre-mapped class.\n\t * May be {@code null} if no the given post-mapped name was not present in the mapping operation output.\n\t */\n\t@Nullable\n\tpublic ClassPathNode getPreMappingPath(@Nonnull String postMappingName) {\n\t\tString preMappedName = getPreMappingName(postMappingName);\n\t\treturn preMappedName == null ? null : preMappingPaths.get(preMappedName);\n\t}\n\n\t/**\n\t * @return Stream of mapped path pairs.\n\t * The {@link Pair#getLeft()} holds the pre-mapped path.\n\t * The {@link Pair#getRight()} holds the post-mapped path, which may be {@code null} in some cases.\n\t */\n\t@Nonnull\n\tpublic Stream<Pair<ClassPathNode, ClassPathNode>> streamPreToPostMappingPaths() {\n\t\treturn getPreMappingPaths().values().stream()\n\t\t\t\t.map(p -> new Pair<>(p, getPostMappingPath(p.getValue().getName())));\n\t}\n\n\t/**\n\t * @return Mapping of affected classes, to their new names.\n\t * If a class was affected, but the name not changed, the key and value for that entry will be the same.\n\t */\n\t@Nonnull\n\tpublic Map<String, String> getMappedClasses() {\n\t\treturn mappedClasses;\n\t}\n\n\t/**\n\t * @return Mapping of pre-mapped names to their path nodes.\n\t */\n\t@Nonnull\n\tpublic Map<String, ClassPathNode> getPreMappingPaths() {\n\t\treturn preMappingPaths;\n\t}\n\n\t/**\n\t * @return Mapping of post-mapped names to their path nodes.\n\t */\n\t@Nonnull\n\tpublic Map<String, ClassPathNode> getPostMappingPaths() {\n\t\treturn postMappingPaths;\n\t}\n\n\t/**\n\t * This class exists to facilitate sorting the order of which classes get updated in the workspace.\n\t * The preferred order is:\n\t * <ol>\n\t *     <li>Classes with NEW names</li>\n\t *     <li>Classes with LOW complexity</li>\n\t * \t   <li>Anything else</li>\n\t * </ol>\n\t * The reason for NEW classes being first is so that existing classes, when updated, can see the NEW classes\n\t * in the workspace.\n\t * <br>\n\t * Then LOW complexity classes are next. Statistically speaking it is typical for complex classes to rely on many\n\t * more less complex classes. Following the principle of making new types available first, we make changes to the\n\t * LOW complexity classes so that HIGH complexity classes can pull data from them.\n\t *\n\t * @param pre\n\t * \t\tPre mapped path.\n\t * @param post\n\t * \t\tPost mapped path.\n\t * @param applicationRunnable\n\t * \t\tRunnable that applies the mapping to the associated workspace.\n\t */\n\tprivate record ApplicationEntry(@Nonnull ClassPathNode pre,\n\t                                @Nonnull ClassPathNode post,\n\t                                @Nonnull Runnable applicationRunnable) implements Comparable<ApplicationEntry> {\n\t\t/**\n\t\t * @return {@code true} when pre-and-post mapping names are the same.\n\t\t * Indicates the class was not mapped, but some references within it to others have been.\n\t\t */\n\t\tprivate boolean isNameIdentity() {\n\t\t\treturn pre.getValue().getName().equals(post.getValue().getName());\n\t\t}\n\n\t\t/**\n\t\t * @return Rough level of complexity of the class in terms of how many types it references.\n\t\t */\n\t\tpublic int complexity() {\n\t\t\tClassInfo classInfo = post.getValue();\n\t\t\tif (classInfo.isJvmClass())\n\t\t\t\treturn classInfo.asJvmClass().getReferencedClasses().size();\n\t\t\treturn -1;\n\t\t}\n\n\t\t@Override\n\t\tpublic int compareTo(@Nonnull ApplicationEntry o) {\n\t\t\tboolean identity = isNameIdentity();\n\t\t\tboolean identityOther = o.isNameIdentity();\n\n\t\t\t// Entries with new names go first.\n\t\t\tif (identity && !identityOther)\n\t\t\t\t// Other class got renamed, we want it to go first.\n\t\t\t\treturn 1;\n\t\t\telse if (!identity && identityOther)\n\t\t\t\t// We got renamed, we want to go first.\n\t\t\t\treturn -1;\n\n\t\t\t// We want more complex classes to go last.\n\t\t\tint cmp = Integer.compare(complexity(), o.complexity());\n\t\t\tif (cmp != 0) return cmp;\n\n\t\t\t// Always want a unique ordering, so as a last resort we will compare by name.\n\t\t\treturn post().getValue().getName().compareTo(o.post().getValue().getName());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/Mappings.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.mapping.format.MappingFileFormat;\n\n/**\n * Outline of intermediate mappings, allowing for clear retrieval regardless of internal storage of mappings.\n * <br>\n * <h2>Relevant noteworthy points</h2>\n * <b>Incomplete mappings</b>: When imported from a {@link MappingFileFormat} not all formats are made equal.\n * Some contain less information than others. See the note in {@link MappingFileFormat} for more information.\n * <br><br>\n * <b>Member references pointing to child sub-types</b>: References to class members can point to child sub-types of\n * the class that defines the member. You may need to check the owner's type hierarchy to see if the field or method\n * is actually defined by a parent class.\n *\n * @author Matt Coley\n */\npublic interface Mappings {\n\t/**\n\t * Some mapping formats do not include field types since name overloading is illegal at the source level of Java.\n\t * It's valid in the bytecode but the mapping omits this info since it isn't necessary information for mapping\n\t * that does not support name overloading.\n\t * <p>\n\t * This is mostly only relevant for usage of {@link MappingsAdapter} which\n\t *\n\t * @return {@code true} when field mappings include the type descriptor in their lookup information.\n\t */\n\tdefault boolean doesSupportFieldTypeDifferentiation() {\n\t\treturn true;\n\t}\n\n\t/**\n\t * Some mapping formats do not include variable types since name overloading is illegal at the source level of Java.\n\t * Variable names are not used by the JVM at all so their names can be anything at the bytecode level. So including\n\t * the type makes it easier to reverse mappings.\n\t *\n\t * @return {@code true} when variable mappings include the type descriptor in their lookup information.\n\t */\n\tdefault boolean doesSupportVariableTypeDifferentiation() {\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param classInfo\n\t * \t\tClass to lookup.\n\t *\n\t * @return Mapped name of the class, or {@code null} if no mapping exists.\n\t */\n\t@Nullable\n\tdefault String getMappedClassName(@Nonnull ClassInfo classInfo) {\n\t\treturn getMappedClassName(classInfo.getName());\n\t}\n\n\t/**\n\t * @param internalName\n\t * \t\tOriginal class's internal name.\n\t *\n\t * @return Mapped name of the class, or {@code null} if no mapping exists.\n\t */\n\t@Nullable\n\tString getMappedClassName(@Nonnull String internalName);\n\n\t/**\n\t * @param owner\n\t * \t\tClass declaring the field.<br>\n\t * \t\t<b>NOTE</b>: References to class members can point to child sub-types of the class that defines the member.\n\t * \t\tYou may need to check the owner's type hierarchy to see if the field is actually defined in a parent class.\n\t * @param field\n\t * \t\tField to lookup.\n\t *\n\t * @return Mapped name of the field, or {@code null} if no mapping exists.\n\t */\n\t@Nullable\n\tdefault String getMappedFieldName(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\treturn getMappedFieldName(owner.getName(), field.getName(), field.getDescriptor());\n\t}\n\n\t/**\n\t * @param ownerName\n\t * \t\tInternal name of the class defining the field.<br>\n\t * \t\t<b>NOTE</b>: References to class members can point to child sub-types of the class that defines the member.\n\t * \t\tYou may need to check the owner's type hierarchy to see if the field is actually defined in a parent class.\n\t * @param fieldName\n\t * \t\tName of the field.\n\t * @param fieldDesc\n\t * \t\tDescriptor of the field.\n\t *\n\t * @return Mapped name of the field, or {@code null} if no mapping exists.\n\t */\n\t@Nullable\n\tString getMappedFieldName(@Nonnull String ownerName, @Nonnull String fieldName, @Nonnull String fieldDesc);\n\n\t/**\n\t * @param owner\n\t * \t\tClass declaring the method.<br>\n\t * \t\t<b>NOTE</b>: References to class members can point to child sub-types of the class that defines the member.\n\t * \t\tYou may need to check the owner's type hierarchy to see if the field is actually defined in a parent class.\n\t * @param method\n\t * \t\tMethod to lookup.\n\t *\n\t * @return Mapped name of the method, or {@code null} if no mapping exists.\n\t */\n\t@Nullable\n\tdefault String getMappedMethodName(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\treturn getMappedMethodName(owner.getName(), method.getName(), method.getDescriptor());\n\t}\n\n\t/**\n\t * @param ownerName\n\t * \t\tInternal name of the class defining the method.<br>\n\t * \t\t<b>NOTE</b>: References to class members can point to child sub-types of the class that defines the member.\n\t * \t\tYou may need to check the owner's type hierarchy to see if the field is actually defined in a parent class.\n\t * @param methodName\n\t * \t\tName of the method.\n\t * @param methodDesc\n\t * \t\tDescriptor of the method.\n\t *\n\t * @return Mapped name of the method, or {@code null} if no mapping exists.\n\t */\n\t@Nullable\n\tString getMappedMethodName(@Nonnull String ownerName, @Nonnull String methodName, @Nonnull String methodDesc);\n\n\t/**\n\t * @param className\n\t * \t\tInternal name of the class defining the method the variable resides in.\n\t * @param methodName\n\t * \t\tName of the method.\n\t * @param methodDesc\n\t * \t\tDescriptor of the method.\n\t * @param name\n\t * \t\tName of the variable.\n\t * @param desc\n\t * \t\tDescriptor of the variable.\n\t * @param index\n\t * \t\tIndex of the variable.\n\t *\n\t * @return Mapped name of the variable, or {@code null} if no mapping exists.\n\t */\n\t@Nullable\n\tString getMappedVariableName(@Nonnull String className, @Nonnull String methodName, @Nonnull String methodDesc,\n\t\t\t\t\t\t\t\t @Nullable String name, @Nullable String desc, int index);\n\n\t/**\n\t * Generally this is implemented under the assumption that {@link Mappings} is used to model data explicitly.\n\t * For instance, if we have a workspace with a class {@code Person} using this we can see the {@code Person}\n\t * in the resulting {@link IntermediateMappings#getClasses()}.\n\t * <br>\n\t * However, when {@link Mappings} is used to pattern-match and replace <i>(Like replacing a prefix/package\n\t * in a class name)</i> then there is no way to model this since we don't know all possible matches beforehand.\n\t * In such cases, we should <i>avoid using this method</i>.\n\t * But for API consistency an empty {@link IntermediateMappings} should be returned.\n\t *\n\t * @return Object representation of mappings.\n\t */\n\t@Nonnull\n\tIntermediateMappings exportIntermediate();\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/MappingsAdapter.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceVertex;\nimport software.coley.recaf.services.mapping.data.*;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.TreeMap;\nimport java.util.function.Function;\n\n/**\n * A {@link Mappings} implementation with a number of additional operations to support usage beyond basic mapping info storage.\n * <b>Enhancements</b>\n * <ol>\n * <li>Import mapping entries from a {@link IntermediateMappings} instance.</li>\n * <li>Enhance field/method lookups with inheritance info from {@link InheritanceGraph}, see {@link #enableHierarchyLookup(InheritanceGraph)}.</li>\n * <li>Enhance inner/outer class mapping edge cases via {@link #enableClassLookup(Workspace)}.</li>\n * <li>Adapt keys in cases where fields/vars do not have type info associated with them <i>(for formats that suck)</i>.</li>\n * </ol>\n *\n * @author Matt Coley\n */\npublic class MappingsAdapter implements Mappings {\n\tprivate final Map<MappingKey, String> mappings = new HashMap<>();\n\tprivate final boolean supportFieldTypeDifferentiation;\n\tprivate final boolean supportVariableTypeDifferentiation;\n\tprivate InheritanceGraph inheritanceGraph;\n\tprivate Workspace workspace;\n\n\t/**\n\t * @param supportFieldTypeDifferentiation\n\t *        {@code true} if the mapping format implementation includes type descriptors in field mappings.\n\t * @param supportVariableTypeDifferentiation\n\t *        {@code true} if the mapping format implementation includes type descriptors in variable mappings.\n\t */\n\tpublic MappingsAdapter(boolean supportFieldTypeDifferentiation,\n\t                       boolean supportVariableTypeDifferentiation) {\n\t\tthis.supportFieldTypeDifferentiation = supportFieldTypeDifferentiation;\n\t\tthis.supportVariableTypeDifferentiation = supportVariableTypeDifferentiation;\n\t}\n\n\t/**\n\t * Adds all the entries in the given mappings to the current mappings.\n\t *\n\t * @param mappings\n\t * \t\tIntermediate mappings to add to the current mappings.\n\t */\n\tpublic void importIntermediate(@Nonnull IntermediateMappings mappings) {\n\t\tfor (String className : mappings.getClassesWithMappings()) {\n\t\t\tClassMapping classMapping = mappings.getClassMapping(className);\n\t\t\tif (classMapping != null) {\n\t\t\t\tString oldClassName = classMapping.getOldName();\n\t\t\t\tString newClassName = classMapping.getNewName();\n\t\t\t\tif (!oldClassName.equals(newClassName))\n\t\t\t\t\taddClass(oldClassName, newClassName);\n\t\t\t}\n\t\t\tfor (FieldMapping fieldMapping : mappings.getClassFieldMappings(className)) {\n\t\t\t\tString oldName = fieldMapping.getOldName();\n\t\t\t\tString newName = fieldMapping.getNewName();\n\t\t\t\tif (!oldName.equals(newName)) {\n\t\t\t\t\tif (doesSupportFieldTypeDifferentiation()) {\n\t\t\t\t\t\taddField(fieldMapping.getOwnerName(), oldName,\n\t\t\t\t\t\t\t\tfieldMapping.getDesc(), newName);\n\t\t\t\t\t} else {\n\t\t\t\t\t\taddField(fieldMapping.getOwnerName(), oldName,\n\t\t\t\t\t\t\t\tnewName);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (MethodMapping methodMapping : mappings.getClassMethodMappings(className)) {\n\t\t\t\tString oldMethodName = methodMapping.getOldName();\n\t\t\t\tString oldMethodDesc = methodMapping.getDesc();\n\t\t\t\tString newMethodName = methodMapping.getNewName();\n\t\t\t\tif (!oldMethodName.equals(newMethodName))\n\t\t\t\t\taddMethod(methodMapping.getOwnerName(), oldMethodName,\n\t\t\t\t\t\t\toldMethodDesc, newMethodName);\n\t\t\t\tfor (VariableMapping variableMapping :\n\t\t\t\t\t\tmappings.getMethodVariableMappings(className, oldMethodName, oldMethodDesc)) {\n\t\t\t\t\taddVariable(className, oldMethodName, oldMethodDesc,\n\t\t\t\t\t\t\tvariableMapping.getOldName(), variableMapping.getDesc(), variableMapping.getIndex(),\n\t\t\t\t\t\t\tvariableMapping.getNewName());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMappedClassName(@Nonnull String internalName) {\n\t\tString mapped = mappings.get(getClassKey(internalName));\n\t\tif (mapped == null) {\n\t\t\tif (workspace != null) {\n\t\t\t\t// Pull the actual outer class name from the class-info in the workspace if available.\n\t\t\t\tClassPathNode classPath = workspace.findClass(internalName);\n\t\t\t\tif (classPath != null) {\n\t\t\t\t\tClassInfo info = classPath.getValue();\n\t\t\t\t\tString name = info.getName();\n\t\t\t\t\tString outerName = info.getOuterClassName();\n\t\t\t\t\tif (outerName != null && outerName.length() < name.length()) {\n\t\t\t\t\t\tString inner = name.substring(outerName.length() + 1);\n\t\t\t\t\t\tString outerMapped = getMappedClassName(outerName);\n\t\t\t\t\t\tif (outerMapped != null) {\n\t\t\t\t\t\t\tmapped = outerMapped + \"$\" + inner;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (isInner(internalName)) {\n\t\t\t\t// We don't have a workspace, so the best we can do is assume standard 'Outer$Inner' conventions.\n\t\t\t\tint split = internalName.lastIndexOf(\"$\");\n\t\t\t\tString inner = internalName.substring(split + 1);\n\t\t\t\tString outer = internalName.substring(0, split);\n\t\t\t\tString outerMapped = getMappedClassName(outer);\n\t\t\t\tif (outerMapped != null) {\n\t\t\t\t\tmapped = outerMapped + \"$\" + inner;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn mapped;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMappedFieldName(@Nonnull String ownerName, @Nonnull String fieldName, @Nonnull String fieldDesc) {\n\t\tMappingKey key = getFieldKey(ownerName, fieldName, fieldDesc);\n\t\tString mapped = mappings.get(key);\n\t\tif (mapped == null && inheritanceGraph != null) {\n\t\t\tmapped = findInParent(ownerName, parent -> getFieldKey(parent, fieldName, fieldDesc));\n\t\t}\n\t\treturn mapped;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMappedMethodName(@Nonnull String ownerName, @Nonnull String methodName, @Nonnull String methodDesc) {\n\t\tMappingKey key = getMethodKey(ownerName, methodName, methodDesc);\n\t\tString mapped = mappings.get(key);\n\t\tif (mapped == null && inheritanceGraph != null) {\n\t\t\tmapped = findInParent(ownerName, parent -> getMethodKey(parent, methodName, methodDesc));\n\t\t}\n\t\treturn mapped;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMappedVariableName(@Nonnull String className, @Nonnull String methodName, @Nonnull String methodDesc,\n\t                                    @Nullable String name, @Nullable String desc, int index) {\n\t\tMappingKey key = getVariableKey(className, methodName, methodDesc, name, desc, index);\n\t\treturn mappings.get(key);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IntermediateMappings exportIntermediate() {\n\t\tIntermediateMappings intermediate = new IntermediateMappings();\n\t\tfor (Map.Entry<MappingKey, String> entry : new TreeMap<>(mappings).entrySet()) {\n\t\t\tMappingKey key = entry.getKey();\n\t\t\tString newName = entry.getValue();\n\t\t\tif (key instanceof ClassMappingKey ck) {\n\t\t\t\tintermediate.addClass(ck.getName(), newName);\n\t\t\t} else if (key instanceof MethodMappingKey mk) {\n\t\t\t\tintermediate.addMethod(mk.getOwner(), mk.getDesc(), mk.getName(), newName);\n\t\t\t} else if (key instanceof FieldMappingKey fk) {\n\t\t\t\tString oldOwner = fk.getOwner();\n\t\t\t\tString oldName = fk.getName();\n\t\t\t\tString oldDesc = fk.getDesc();\n\t\t\t\tintermediate.addField(oldOwner, oldDesc, oldName, newName);\n\t\t\t}\n\t\t}\n\t\treturn intermediate;\n\t}\n\n\t@Override\n\tpublic boolean doesSupportFieldTypeDifferentiation() {\n\t\treturn supportFieldTypeDifferentiation;\n\t}\n\n\t@Override\n\tpublic boolean doesSupportVariableTypeDifferentiation() {\n\t\treturn supportVariableTypeDifferentiation;\n\t}\n\n\t/**\n\t * @param owner\n\t * \t\tInternal name of the class <i>\"defining\"</i> the member.\n\t * \t\t<i>(Location in reference may not be where the member is actually defined, hence this lookup)</i>\n\t * @param lookup\n\t * \t\tFunction that takes in the parent names of the given member owner class,\n\t * \t\tand converts it to a member lookup key via {@link #getFieldKey(String, String, String)} or\n\t *        {@link #getMethodKey(String, String, String)}.\n\t *\n\t * @return The first mapping match in a parent class found by the lookup function.\n\t */\n\tprivate String findInParent(String owner, Function<String, ? extends MappingKey> lookup) {\n\t\t// TODO: Diamond class hierarchy doesn't work with this.\n\t\t//  We need to visit the whole family tree.\n\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(owner);\n\t\tif (vertex == null)\n\t\t\treturn null;\n\t\tIterator<InheritanceVertex> iterator = vertex.allParents().iterator();\n\t\twhile (iterator.hasNext()) {\n\t\t\tvertex = iterator.next();\n\t\t\tMappingKey key = lookup.apply(vertex.getName());\n\t\t\tString result = mappings.get(key);\n\t\t\tif (result != null) {\n\t\t\t\treturn result;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param internalName\n\t * \t\tSome class name.\n\t *\n\t * @return {@code true} when the class by the given name is an inner class of another class.\n\t */\n\tprivate boolean isInner(String internalName) {\n\t\tint splitIndex = internalName.lastIndexOf(\"$\");\n\t\t// Ensure there is text before and after the split\n\t\treturn splitIndex > 1 && splitIndex < internalName.length() - 1;\n\t}\n\n\t/**\n\t * Allows the mappings to use class inheritance graphs to check for key matches where the field or method is\n\t * defined in a parent class.<br>\n\t * An example of this would be if you were looking to map {@link Map#size()} but the reference in the bytecode\n\t * was to {@link TreeMap#size()}. If you only have the {@link Map} entry you need the type hierarchy to find that\n\t * {@link TreeMap} is a child of {@link Map} and thus should <i>\"inherit\"</i> the mapping of {@link Map#size()}.\n\t *\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph to use.\n\t */\n\tpublic void enableHierarchyLookup(@Nonnull InheritanceGraph inheritanceGraph) {\n\t\tthis.inheritanceGraph = inheritanceGraph;\n\t}\n\n\t/**\n\t * Allows the mappings to use data from classes in the workspace to better handle some edge cases.\n\t * For example, inner class name handling in {@link #getMappedClassName(String)}.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to pull from.\n\t */\n\tpublic void enableClassLookup(@Nonnull Workspace workspace) {\n\t\tthis.workspace = workspace;\n\t}\n\n\t/**\n\t * Add mapping for class name.\n\t *\n\t * @param originalName\n\t * \t\tOriginal name.\n\t * @param renamedName\n\t * \t\tNew name.\n\t */\n\tpublic void addClass(@Nonnull String originalName, @Nonnull String renamedName) {\n\t\tmappings.put(getClassKey(originalName), renamedName);\n\t}\n\n\t/**\n\t * Add mapping for field name. <br>\n\t * Used when {@link #doesSupportFieldTypeDifferentiation()} is {@code true}.\n\t *\n\t * @param owner\n\t * \t\tClass name defining the field.\n\t * @param originalName\n\t * \t\tOriginal name of the field.\n\t * @param desc\n\t * \t\tType descriptor of the field.\n\t * @param renamedName\n\t * \t\tNew name of the method.\n\t */\n\tpublic void addField(@Nonnull String owner, @Nonnull String originalName, @Nonnull String desc, @Nonnull String renamedName) {\n\t\tif (doesSupportFieldTypeDifferentiation()) {\n\t\t\tmappings.put(getFieldKey(owner, originalName, desc), renamedName);\n\t\t} else {\n\t\t\tthrow new IllegalStateException(\"The current mapping implementation does not support \" +\n\t\t\t\t\t\"field type differentiation\");\n\t\t}\n\t}\n\n\t/**\n\t * Add mapping for field name.<br>\n\t * Used when {@link #doesSupportFieldTypeDifferentiation()} is {@code false}.\n\t *\n\t * @param owner\n\t * \t\tClass name defining the field.\n\t * @param originalName\n\t * \t\tOriginal name of the field.\n\t * @param renamedName\n\t * \t\tNew name of the field.\n\t */\n\tpublic void addField(@Nonnull String owner, @Nonnull String originalName, @Nonnull String renamedName) {\n\t\tif (doesSupportFieldTypeDifferentiation()) {\n\t\t\tthrow new IllegalStateException(\"The current mapping implementation requires \" +\n\t\t\t\t\t\"specifying field descriptors\");\n\t\t} else {\n\t\t\tmappings.put(getFieldKey(owner, originalName, null), renamedName);\n\t\t}\n\t}\n\n\t/**\n\t * Add mapping for method name.\n\t *\n\t * @param owner\n\t * \t\tClass name defining the method.\n\t * @param originalName\n\t * \t\tOriginal name of the method.\n\t * @param desc\n\t * \t\tType descriptor of the method.\n\t * @param renamedName\n\t * \t\tNew name of the method.\n\t */\n\tpublic void addMethod(@Nonnull String owner, @Nonnull String originalName, @Nonnull String desc, @Nonnull String renamedName) {\n\t\tmappings.put(getMethodKey(owner, originalName, desc), renamedName);\n\t}\n\n\t/**\n\t * Add mapping for variable name.\n\t *\n\t * @param className\n\t * \t\tClass name defining the method.\n\t * @param methodName\n\t * \t\tMethod name.\n\t * @param methodDesc\n\t * \t\tMethod descriptor.\n\t * @param originalName\n\t * \t\tVariable original name.\n\t * @param desc\n\t * \t\tVariable descriptor.\n\t * @param index\n\t * \t\tVariable index.\n\t * @param renamedName\n\t * \t\tNew name of the variable.\n\t */\n\tpublic void addVariable(@Nonnull String className, @Nonnull String methodName, @Nonnull String methodDesc,\n\t                        @Nonnull String originalName, @Nullable String desc, int index, @Nonnull String renamedName) {\n\t\tMappingKey key = getVariableKey(className, methodName, methodDesc, originalName, desc, index);\n\t\tmappings.put(key, renamedName);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tClass name.\n\t *\n\t * @return Key format for class.\n\t */\n\t@Nonnull\n\tprotected MappingKey getClassKey(@Nonnull String name) {\n\t\treturn new ClassMappingKey(name);\n\t}\n\n\t/**\n\t * @param ownerName\n\t * \t\tClass defining the field.\n\t * @param fieldName\n\t * \t\tName of field.\n\t * @param fieldDesc\n\t * \t\tType descriptor of field.\n\t *\n\t * @return Key format for field.\n\t */\n\t@Nonnull\n\tprotected MappingKey getFieldKey(@Nonnull String ownerName, @Nonnull String fieldName, @Nullable String fieldDesc) {\n\t\treturn new FieldMappingKey(ownerName, fieldName, supportFieldTypeDifferentiation ? fieldDesc : null);\n\t}\n\n\t/**\n\t * @param ownerName\n\t * \t\tClass defining the method.\n\t * @param methodName\n\t * \t\tName of method.\n\t * @param methodDesc\n\t * \t\tType descriptor of method.\n\t *\n\t * @return Key format for method.\n\t */\n\t@Nonnull\n\tprotected MappingKey getMethodKey(@Nonnull String ownerName, @Nonnull String methodName, @Nonnull String methodDesc) {\n\t\treturn new MethodMappingKey(ownerName, methodName, methodDesc);\n\t}\n\n\t/**\n\t * @param className\n\t * \t\tClass defining the method.\n\t * @param methodName\n\t * \t\tName of method.\n\t * @param methodDesc\n\t * \t\tType descriptor of method.\n\t * @param name\n\t * \t\tName of variable.\n\t * @param desc\n\t * \t\tType descriptor of variable.\n\t * @param index\n\t * \t\tLocal variable table index.\n\t *\n\t * @return Key format for variable.\n\t */\n\t@Nonnull\n\tprotected MappingKey getVariableKey(@Nonnull String className, @Nonnull String methodName, @Nonnull String methodDesc,\n\t                                    @Nullable String name, @Nullable String desc, int index) {\n\t\treturn new VariableMappingKey(className, methodName, methodDesc, name, desc);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/UniqueKeyMappings.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * A simple {@link Mappings} implementation that assumes all classes, fields, and methods are uniquely identified.\n * Mapping outputs are keyed to these unique identifiers, rather than a full {@code name + desc} pair.\n * <p>\n * This mappings implementation can be useful to pass to {@link MappingApplier} for some scenarios like\n * Minecraft mappings, where each class, field, and method are uniquely identified. Minecraft's Forge, Fabric-Yarn and\n * Mod-Coder-Pack all have <i>\"intermediate\"</i> names that follow this pattern.\n * <p>\n * Example use case:\n * <pre>{@code\n * MappingApplier applier = ...;\n * EnigmaMappings enigma = ...;\n *\n * // Read minecraft yarn mappings from a directory\n * IntermediateMappings mappings = enigma.parse(Paths.get(\"fabric/yarn/1.20.1\"));\n *\n * // Convert to unique-keyed mappings\n * UniqueKeyMappings unique = new UniqueKeyMappings(mappings);\n *\n * // Use the unique-keyed mappings with a mapping-applier on a fabric mod\n * applier.applyToPrimaryResource(unique);\n * }</pre>\n *\n * @author Matt Coley\n */\npublic class UniqueKeyMappings implements Mappings {\n\tprivate final Map<String, String> mappings = new HashMap<>();\n\tprivate final IntermediateMappings backing;\n\n\t/**\n\t * @param backing\n\t * \t\tBacking intermediate mappings to adapt into unique keyed mappings.\n\t */\n\tpublic UniqueKeyMappings(@Nonnull IntermediateMappings backing) {\n\t\t// We need to explicitly keep the input mappings for 'exportIntermediate()' support.\n\t\tthis.backing = backing;\n\n\t\tpopulateLookup();\n\t}\n\n\t/**\n\t * Copies all values from the backing mappings into this instance.\n\t */\n\tprivate void populateLookup() {\n\t\tbacking.classes.values()\n\t\t\t\t.forEach(mapping -> mappings.put(mapping.getOldName(), mapping.getNewName()));\n\t\tbacking.fields.values().stream()\n\t\t\t\t.flatMap(Collection::stream)\n\t\t\t\t.forEach(mapping -> mappings.put(mapping.getOldName(), mapping.getNewName()));\n\t\tbacking.methods.values().stream()\n\t\t\t\t.flatMap(Collection::stream)\n\t\t\t\t.forEach(mapping -> mappings.put(mapping.getOldName(), mapping.getNewName()));\n\t\tbacking.variables.values().stream()\n\t\t\t\t.flatMap(Collection::stream)\n\t\t\t\t.forEach(mapping -> {\n\t\t\t\t\tif (mapping.getOldName() != null)\n\t\t\t\t\t\tmappings.put(mapping.getOldName(), mapping.getNewName());\n\t\t\t\t\telse\n\t\t\t\t\t\tmappings.put(mapping.getMethodName() + \".\" + mapping.getIndex(), mapping.getNewName());\n\t\t\t\t});\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMappedClassName(@Nonnull String internalName) {\n\t\treturn mappings.get(internalName);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMappedFieldName(@Nonnull String ownerName, @Nonnull String fieldName, @Nonnull String fieldDesc) {\n\t\treturn mappings.get(fieldName);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMappedMethodName(@Nonnull String ownerName, @Nonnull String methodName, @Nonnull String methodDesc) {\n\t\treturn mappings.get(methodName);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getMappedVariableName(@Nonnull String className, @Nonnull String methodName, @Nonnull String methodDesc,\n\t                                    @Nullable String name, @Nullable String desc, int index) {\n\t\tString mapped = null;\n\t\tif (name != null)\n\t\t\tmapped = mappings.get(name);\n\t\tif (mapped == null)\n\t\t\tmapped = mappings.get(methodName + \".\" + index);\n\t\treturn mapped;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IntermediateMappings exportIntermediate() {\n\t\t// Yield the backing intermediate mappings.\n\t\treturn backing;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/WorkspaceBackedRemapper.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.util.Handles;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Enhanced {@link BasicMappingsRemapper} for cases where additional information\n * needs to be pulled from a {@link Workspace}.\n *\n * @author Matt Coley\n */\npublic class WorkspaceBackedRemapper extends BasicMappingsRemapper {\n\tprivate final Workspace workspace;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull class info from when additional context is needed.\n\t * @param mappings\n\t * \t\tMappings wrapper to pull values from.\n\t */\n\tpublic WorkspaceBackedRemapper(@Nonnull Workspace workspace,\n\t                               @Nonnull Mappings mappings) {\n\t\tsuper(mappings);\n\t\tthis.workspace = workspace;\n\t}\n\n\t@Override\n\tpublic String mapAnnotationAttributeName(String descriptor, String name) {\n\t\tString annotationName = Type.getType(descriptor).getInternalName();\n\t\tClassPathNode classPath = workspace.findClass(annotationName);\n\n\t\t// Not found, probably not intended to be renamed.\n\t\tif (classPath == null)\n\t\t\treturn name;\n\n\t\t// Get the declaration and, if found, treat as normal method mapping.\n\t\tClassInfo info = classPath.getValue();\n\t\tMethodMember attributeMethod = info.getMethods().stream()\n\t\t\t\t.filter(method -> method.getName().equals(name))\n\t\t\t\t.findFirst().orElse(null);\n\n\t\t// Not found, shouldn't generally happen.\n\t\tif (attributeMethod == null)\n\t\t\treturn name;\n\n\t\t// Use the method mapping from the annotation class's declared methods.\n\t\treturn mapMethodName(annotationName, name, attributeMethod.getDescriptor());\n\t}\n\n\t@Override\n\t@SuppressWarnings(\"deprecation\")\n\tpublic String mapInvokeDynamicMethodName(String name, String descriptor) {\n\t\t// Deprecated in ASM 9.9 - Keeping this here for a bit to ensure nobody accidentally calls it.\n\t\tthrow new IllegalStateException(\"Enhanced 'mapInvokeDynamicMethodName(...)' usage required, missing handle arg\");\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tThe name of the method.\n\t * @param descriptor\n\t * \t\tThe descriptor of the method.\n\t * @param bsm\n\t * \t\tThe bootstrap method handle.\n\t * @param bsmArguments\n\t * \t\tThe arguments to the bsm.\n\t *\n\t * @return New name of the method.\n\t */\n\t@Nonnull\n\t@Override\n\tpublic String mapInvokeDynamicMethodName(@Nonnull String name, @Nonnull String descriptor, @Nonnull Handle bsm, @Nonnull Object[] bsmArguments) {\n\t\tif (bsm.equals(Handles.META_FACTORY)) {\n\t\t\t// Get the interface from the descriptor return type.\n\t\t\tString interfaceOwner = Type.getReturnType(descriptor).getInternalName();\n\n\t\t\t// Get the method descriptor from the implementation handle (2nd arg value)\n\t\t\tif (bsmArguments[1] instanceof Handle implementationHandle) {\n\t\t\t\tString interfaceMethodDesc = implementationHandle.getDesc();\n\t\t\t\treturn mapMethodName(interfaceOwner, name, interfaceMethodDesc);\n\t\t\t}\n\t\t}\n\n\t\t// Not a known method handle type, so we do not know how to bootstrapMethodHandle renaming it.\n\t\treturn name;\n\t}\n\n\t/**\n\t * @param className\n\t * \t\tInternal name of the class defining the method the variable resides in.\n\t * @param methodName\n\t * \t\tName of the method.\n\t * @param methodDesc\n\t * \t\tDescriptor of the method.\n\t * @param name\n\t * \t\tName of the variable.\n\t * @param desc\n\t * \t\tDescriptor of the variable.\n\t * @param index\n\t * \t\tIndex of the variable.\n\t *\n\t * @return Mapped name of the variable, or the existing name if no mapping exists.\n\t */\n\t@Nonnull\n\tpublic String mapVariableName(@Nonnull String className, @Nonnull String methodName, @Nonnull String methodDesc,\n\t                              String name, String desc, int index) {\n\t\tString mapped = mappings.getMappedVariableName(className, methodName, methodDesc, name, desc, index);\n\t\tif (mapped != null) {\n\t\t\tmarkModified();\n\t\t\treturn mapped;\n\t\t}\n\t\t// Use existing variable name.\n\t\treturn name;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/WorkspaceClassRemapper.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.commons.ClassRemapper;\nimport org.objectweb.asm.commons.MethodRemapper;\nimport org.objectweb.asm.commons.Remapper;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * A {@link ClassRemapper} implementation that delegates to a provided {@link Mappings} via {@link WorkspaceBackedRemapper}.\n * <br>\n * When applied to a class you can check if any modifications have been made via {@link #hasMappingBeenApplied()}.\n *\n * @author Matt Coley\n */\npublic class WorkspaceClassRemapper extends ClassRemapper {\n\tprivate final WorkspaceBackedRemapper workspaceRemapper;\n\n\t/**\n\t * @param cv\n\t * \t\tClass to visit and rename mapped items of.\n\t * @param workspace\n\t * \t\tWorkspace to pull class info from when additional context is needed.\n\t * @param mappings\n\t * \t\tMappings to apply.\n\t */\n\tpublic WorkspaceClassRemapper(@Nullable ClassVisitor cv, @Nonnull Workspace workspace, @Nonnull Mappings mappings) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv, new WorkspaceBackedRemapper(workspace, mappings));\n\t\t// Shadow the parent type's remapper locally,\n\t\t// allowing us to use our more specific methods with additional context.\n\t\tthis.workspaceRemapper = ((WorkspaceBackedRemapper) super.remapper);\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, @Nonnull String name, @Nonnull String descriptor,\n\t                                 @Nullable String signature, @Nullable String[] exceptions) {\n\t\t// Adapted from base ClassRemapper implementation.\n\t\t// This allows us to skip calls to super 'visitMethod' to bypass calls to 'createMethodRemapper'\n\t\t// since our visitor we want to make needs information accessible here, but not in that method.\n\t\tString remappedDescriptor = remapper.mapMethodDesc(descriptor);\n\t\tMethodVisitor mv =\n\t\t\t\tcv == null ? null : cv.visitMethod(\n\t\t\t\t\t\taccess,\n\t\t\t\t\t\tremapper.mapMethodName(className, name, descriptor),\n\t\t\t\t\t\tremappedDescriptor,\n\t\t\t\t\t\tremapper.mapSignature(signature, false),\n\t\t\t\t\t\texceptions == null ? null : remapper.mapTypes(exceptions));\n\t\treturn mv == null ? null : new VariableRenamingMethodVisitor(className, name, descriptor, mv, workspaceRemapper);\n\t}\n\n\t@Override\n\tprotected MethodVisitor createMethodRemapper(@Nullable MethodVisitor mv) {\n\t\tthrow new IllegalStateException(\"Enhanced 'visitMethod(...)' usage required, 'createMethodMapper(...)' should never be called\");\n\t}\n\n\t/**\n\t * @return {@code true} when any mapping has been found and used.\n\t */\n\tpublic boolean hasMappingBeenApplied() {\n\t\treturn workspaceRemapper.hasMappingBeenApplied();\n\t}\n\n\t/**\n\t * {@link MethodRemapper} pointing to enhanced remapping methods to allow for more context-sensitive behavior.\n\t * Method visitor that functions as an adapter for the\n\t * {@link #visitMethod(int, String, String, String, String[]) standard method remapping visitor}\n\t * that additionally supports variable renaming.\n\t */\n\tprivate class VariableRenamingMethodVisitor extends MethodRemapper {\n\t\tprivate final String methodOwner;\n\t\tprivate final String methodName;\n\t\tprivate final String methodDesc;\n\n\t\tpublic VariableRenamingMethodVisitor(@Nonnull String methodOwner, @Nonnull String methodName, @Nonnull String methodDesc,\n\t\t                                     @Nullable MethodVisitor mv, @Nonnull Remapper remapper) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), mv, remapper);\n\t\t\tthis.methodOwner = methodOwner;\n\t\t\tthis.methodName = methodName;\n\t\t\tthis.methodDesc = methodDesc;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {\n\t\t\t// Modified variant of impl in 'MethodRemapper' to call our 'workspaceRemapper' specific methods\n\t\t\tObject[] remappedBootstrapMethodArguments = new Object[bootstrapMethodArguments.length];\n\t\t\tfor (int i = 0; i < bootstrapMethodArguments.length; ++i) {\n\t\t\t\tremappedBootstrapMethodArguments[i] = remapper.mapValue(bootstrapMethodArguments[i]);\n\t\t\t}\n\t\t\tmv.visitInvokeDynamicInsn(\n\t\t\t\t\tworkspaceRemapper.mapInvokeDynamicMethodName(name, descriptor,\n\t\t\t\t\t\t\tbootstrapMethodHandle, remappedBootstrapMethodArguments),\n\t\t\t\t\tremapper.mapMethodDesc(descriptor),\n\t\t\t\t\t(Handle) remapper.mapValue(bootstrapMethodHandle),\n\t\t\t\t\tremappedBootstrapMethodArguments);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {\n\t\t\tString mappedName = workspaceRemapper.mapVariableName(methodOwner, methodName, methodDesc, name, desc, index);\n\t\t\tsuper.visitLocalVariable(mappedName, desc, signature, start, end, index);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/aggregate/AggregateMappingManager.java",
    "content": "package software.coley.recaf.services.mapping.aggregate;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.cdi.EagerInitialization;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.workspace.WorkspaceCloseListener;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.services.workspace.WorkspaceOpenListener;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * Manages tracking the state of mappings over time.\n *\n * @author Matt Coley\n * @author Marius Renner\n */\n@EagerInitialization\n@ApplicationScoped\npublic class AggregateMappingManager implements Service {\n\tpublic static final String SERVICE_ID = \"mapping-aggregator\";\n\tprivate static final Logger logger = Logging.get(AggregateMappingManager.class);\n\tprivate final List<AggregatedMappingsListener> aggregateListeners = new CopyOnWriteArrayList<>();\n\tprivate final AggregateMappingManagerConfig config;\n\tprivate AggregatedMappings aggregatedMappings;\n\n\t@Inject\n\tpublic AggregateMappingManager(@Nonnull AggregateMappingManagerConfig config,\n\t                               @Nonnull WorkspaceManager workspaceManager) {\n\t\tthis.config = config;\n\n\t\tListenerHost host = new ListenerHost();\n\t\tworkspaceManager.addWorkspaceOpenListener(host);\n\t\tworkspaceManager.addWorkspaceCloseListener(host);\n\t}\n\n\t/**\n\t * Update the aggregate mappings for the workspace.\n\t *\n\t * @param newMappings\n\t * \t\tThe additional mappings that were added.\n\t */\n\tpublic void updateAggregateMappings(@Nonnull Mappings newMappings) {\n\t\tif (aggregatedMappings == null)\n\t\t\treturn;\n\t\taggregatedMappings.update(newMappings);\n\t\tUnchecked.checkedForEach(aggregateListeners, listener -> listener.onAggregatedMappingsUpdated(getAggregatedMappings()),\n\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when updating aggregate mappings\", t));\n\t}\n\n\t/**\n\t * Clears all mapping information.\n\t */\n\tprivate void clearAggregated() {\n\t\tif (aggregatedMappings == null)\n\t\t\treturn;\n\t\taggregatedMappings.clear();\n\t\tUnchecked.checkedForEach(aggregateListeners, listener -> listener.onAggregatedMappingsUpdated(getAggregatedMappings()),\n\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when updating aggregate mappings\", t));\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tpublic void addAggregatedMappingsListener(@Nonnull AggregatedMappingsListener listener) {\n\t\tPrioritySortable.add(aggregateListeners, listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t *\n\t * @return {@code true} when the listener was removed.\n\t * {@code false} if the listener was not in the list.\n\t */\n\tpublic boolean removeAggregatedMappingListener(@Nonnull AggregatedMappingsListener listener) {\n\t\treturn aggregateListeners.remove(listener);\n\t}\n\n\t/**\n\t * @return Current aggregated mappings in the ASM format. Will be {@code null} if no workspace is open.\n\t */\n\t@Nullable\n\tpublic AggregatedMappings getAggregatedMappings() {\n\t\treturn aggregatedMappings;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic AggregateMappingManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\tprivate class ListenerHost implements WorkspaceOpenListener, WorkspaceCloseListener {\n\t\t@Override\n\t\tpublic void onWorkspaceOpened(@Nonnull Workspace workspace) {\n\t\t\taggregatedMappings = new AggregatedMappings(workspace);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onWorkspaceClosed(@Nonnull Workspace workspace) {\n\t\t\taggregatedMappings = null;\n\t\t\taggregateListeners.clear();\n\t\t\tclearAggregated();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/aggregate/AggregateMappingManagerConfig.java",
    "content": "package software.coley.recaf.services.mapping.aggregate;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link AggregateMappingManager}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class AggregateMappingManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic AggregateMappingManagerConfig() {\n\t\tsuper(ConfigGroups.SERVICE_MAPPING, AggregateMappingManager.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/aggregate/AggregatedMappings.java",
    "content": "package software.coley.recaf.services.mapping.aggregate;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.mapping.WorkspaceBackedRemapper;\nimport software.coley.recaf.services.mapping.data.ClassMapping;\nimport software.coley.recaf.services.mapping.data.FieldMapping;\nimport software.coley.recaf.services.mapping.data.MemberMapping;\nimport software.coley.recaf.services.mapping.data.MethodMapping;\nimport software.coley.recaf.services.mapping.data.VariableMapping;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Mappings implementation for internal tracking of aggregated mappings.\n * <br>\n * This will handle transitive renames such as ({@code a -> b -> c} by\n * compressing them down to their ultimate result ({@code a -> c}).\n * We will be referring to classes in these stages of naming as \"a\", \"b\", and \"c\" in the logic to keep this\n * example as the basis of all operations.\n * <br>\n * When this is done for every update of the mappings, the resulting mapping can be applied to the original\n * class files to achieve the same result again.\n *\n * @author Matt Coley\n * @author Marius Renner\n */\npublic class AggregatedMappings extends IntermediateMappings {\n\tprivate final Map<String, String> reverseOrderClassMapping = new ConcurrentHashMap<>();\n\tprivate final WorkspaceBackedRemapper reverseMapper;\n\tprivate boolean missingFieldDescriptors;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace associated with the aggregate mappings.\n\t */\n\tpublic AggregatedMappings(@Nonnull Workspace workspace) {\n\t\treverseMapper = new WorkspaceBackedRemapper(workspace, this) {\n\t\t\t@Override\n\t\t\tpublic String map(String internalName) {\n\t\t\t\tString reverseName = reverseOrderClassMapping.get(internalName);\n\t\t\t\tif (reverseName != null) {\n\t\t\t\t\tmarkModified();\n\t\t\t\t\treturn reverseName;\n\t\t\t\t}\n\t\t\t\treturn internalName;\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Lookup the original name of a mapped class.\n\t *\n\t * @param name\n\t * \t\tCurrent class name.\n\t *\n\t * @return Original class name.\n\t * {@code null} if the class has not been mapped.\n\t */\n\t@Nullable\n\tpublic String getReverseClassMapping(@Nonnull String name) {\n\t\treturn reverseOrderClassMapping.get(name);\n\t}\n\n\t/**\n\t * @param owner\n\t * \t\tCurrent class name.\n\t * @param fieldName\n\t * \t\tCurrent field name.\n\t * @param fieldDesc\n\t * \t\tCurrent field descriptor.\n\t *\n\t * @return Original name of the field if any mappings exist.\n\t * {@code null} if the field has not been mapped.\n\t */\n\t@Nullable\n\tpublic String getReverseFieldMapping(@Nonnull String owner, @Nonnull String fieldName, @Nonnull String fieldDesc) {\n\t\tString originalOwnerName = getReverseClassMapping(owner);\n\t\tif (originalOwnerName == null)\n\t\t\toriginalOwnerName = owner;\n\n\t\t// Get fields in the original class\n\t\tList<FieldMapping> fieldMappings = fields.get(originalOwnerName);\n\t\tif (fieldMappings == null || fieldMappings.isEmpty())\n\t\t\treturn null;\n\n\t\t// Find a matching field\n\t\tString originalDesc = reverseMapper.mapDesc(fieldDesc);\n\t\tfor (FieldMapping fieldMapping : fieldMappings) {\n\t\t\t// The current name must match the mappings \"new\" name\n\t\t\tif (!fieldName.equals(fieldMapping.getNewName()))\n\t\t\t\tcontinue;\n\n\t\t\t// The original field descriptor must match the mapping key's\n\t\t\tif (originalDesc.equals(fieldMapping.getDesc()))\n\t\t\t\treturn fieldMapping.getOldName();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param owner\n\t * \t\tCurrent class name.\n\t * @param methodName\n\t * \t\tCurrent method name.\n\t * @param methodDesc\n\t * \t\tCurrent method descriptor.\n\t *\n\t * @return Original name of the method if any mappings exist.\n\t * {@code null} if the method has not been mapped.\n\t */\n\t@Nullable\n\tpublic String getReverseMethodMapping(@Nonnull String owner, @Nonnull String methodName, @Nonnull String methodDesc) {\n\t\tString originalOwnerName = getReverseClassMapping(owner);\n\t\tif (originalOwnerName == null)\n\t\t\toriginalOwnerName = owner;\n\n\t\t// Get methods in the original class\n\t\tList<MethodMapping> methodMappings = methods.get(originalOwnerName);\n\t\tif (methodMappings == null || methodMappings.isEmpty())\n\t\t\treturn null;\n\n\t\t// Find a matching method\n\t\tString originalDesc = reverseMapper.mapDesc(methodDesc);\n\t\tfor (MethodMapping methodMapping : methodMappings) {\n\t\t\t// The current name must match the mappings \"new\" name\n\t\t\tif (!methodName.equals(methodMapping.getNewName()))\n\t\t\t\tcontinue;\n\n\t\t\t// The original method descriptor must match the mapping key's\n\t\t\tif (originalDesc.equals(methodMapping.getDesc()))\n\t\t\t\treturn methodMapping.getOldName();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param owner\n\t * \t\tCurrent class name.\n\t * @param methodName\n\t * \t\tCurrent method name.\n\t * @param methodDesc\n\t * \t\tCurrent method descriptor.\n\t * @param varName\n\t * \t\tCurrent variable name.\n\t * @param varDesc\n\t * \t\tCurrent variable descriptor.\n\t * @param varIndex\n\t * \t\tVariable index.\n\t *\n\t * @return Original name of the variable if any mappings exist.\n\t * {@code null} if the variable has not been mapped.\n\t */\n\t@Nullable\n\tpublic String getReverseVariableMapping(@Nonnull String owner, @Nonnull String methodName, @Nonnull String methodDesc,\n\t                                        @Nonnull String varName, @Nonnull String varDesc, int varIndex) {\n\t\tString originalOwnerName = getReverseClassMapping(owner);\n\t\tif (originalOwnerName == null)\n\t\t\toriginalOwnerName = owner;\n\n\t\t// Get methods in the original class\n\t\tList<MethodMapping> methodMappings = methods.get(originalOwnerName);\n\t\tif (methodMappings == null || methodMappings.isEmpty())\n\t\t\treturn null;\n\n\t\tString originalMethodDesc = reverseMapper.mapDesc(methodDesc);\n\t\tString originalVarDesc = reverseMapper.mapDesc(varDesc);\n\t\tfor (MethodMapping methodMapping : methodMappings) {\n\t\t\t// The current name must match the mappings \"new\" name\n\t\t\tif (!methodName.equals(methodMapping.getNewName()))\n\t\t\t\tcontinue;\n\n\t\t\t// The original method descriptor must match the mapping key's\n\t\t\tif (originalMethodDesc.equals(methodMapping.getDesc())) {\n\t\t\t\t// Get the variables that were mapped under the original name\n\t\t\t\tList<VariableMapping> variableMappings = variables.get(varKey(originalOwnerName, methodMapping.getOldName(), originalMethodDesc));\n\t\t\t\tfor (VariableMapping variableMapping : variableMappings) {\n\t\t\t\t\t// If the variable index, name, and descriptor match, yield the variable mapping's original name\n\t\t\t\t\tif (variableMapping.getIndex() == varIndex && variableMapping.getNewName().equals(varName)) {\n\t\t\t\t\t\tif (varDesc.equals(originalVarDesc)) {\n\t\t\t\t\t\t\treturn variableMapping.getOldName();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param desc\n\t * \t\tDescriptor with remapped type names.\n\t *\n\t * @return Descriptor with pre-mapped/original type names.\n\t */\n\t@Nullable\n\tpublic String applyReverseMappings(@Nullable String desc) {\n\t\tif (desc == null)\n\t\t\treturn null;\n\t\telse if (desc.charAt(0) == '(')\n\t\t\treturn reverseMapper.mapMethodDesc(desc);\n\t\telse\n\t\t\treturn reverseMapper.mapDesc(desc);\n\t}\n\n\t@Override\n\tpublic void addClass(@Nonnull String oldName, @Nonnull String newName) {\n\t\tsuper.addClass(oldName, newName);\n\t\treverseOrderClassMapping.put(newName, oldName);\n\t}\n\n\t/**\n\t * Some mapping formats do not include the descriptor of fields since they assume all fields are uniquely identified by name.\n\t * This kinda sucks because unless we do a lot of additional lookup work <i>(Which may not even be successful)</i>,\n\t * we're missing out on data. If this is ever the case formats that do require this data cannot be exported to.\n\t *\n\t * @return {@code true} when there are field entries in these mapping which do not have descriptors associated with them.\n\t */\n\tpublic boolean isMissingFieldDescriptors() {\n\t\treturn missingFieldDescriptors;\n\t}\n\n\t/**\n\t * Clears the mapping entries.\n\t */\n\tpublic void clear() {\n\t\tmissingFieldDescriptors = false;\n\t\tclasses.clear();\n\t\tfields.clear();\n\t\tmethods.clear();\n\t\tvariables.clear();\n\t}\n\n\t/**\n\t * Updates aggregated mappings with new values.\n\t *\n\t * @param newMappings\n\t * \t\tThe additional mappings that were added. They will be in the form of {@code b -> c}.\n\t * \t\tWe need to make sure we map the key type {@code b} to {@code a}.\n\t *\n\t * @return {@code true} when the mapping operation required bridging a current class name to its original name.\n\t */\n\tpublic boolean update(@Nonnull Mappings newMappings) {\n\t\t// ORIGINAL:\n\t\t//  a -> b\n\t\t//  a.f1 -> b.f2\n\t\t// MAPPING:\n\t\t//  b -> c\n\t\t//  b.f2 -> c.f3\n\t\t// AGGREGATED:\n\t\t//  a -> c\n\t\t//  a.f1 -> f3\n\t\tboolean bridged;\n\t\tIntermediateMappings intermediate = newMappings.exportIntermediate();\n\t\tbridged = updateClasses(intermediate.getClasses());\n\t\tbridged |= updateMembers(intermediate.getFields().values());\n\t\tbridged |= updateMembers(intermediate.getMethods().values());\n\t\tbridged |= updateVariables(intermediate.getVariables().values());\n\t\treturn bridged;\n\t}\n\n\tprivate boolean updateClasses(@Nonnull Map<String, ClassMapping> classes) {\n\t\tboolean bridged = false;\n\t\tfor (ClassMapping newMapping : classes.values()) {\n\t\t\tString cName = newMapping.getNewName();\n\t\t\tString bName = newMapping.getOldName();\n\t\t\tString aName = reverseOrderClassMapping.get(bName);\n\t\t\tif (aName != null) {\n\t\t\t\t// There is a prior entry of the class, 'aName' thus we use it as the key\n\t\t\t\t// and not 'bName' since that was the prior value the mapping for 'aName'.\n\t\t\t\tbridged = true;\n\t\t\t\taddClass(aName, cName);\n\t\t\t} else {\n\t\t\t\t// No prior entry of the class.\n\t\t\t\taddClass(bName, cName);\n\t\t\t}\n\t\t}\n\t\treturn bridged;\n\t}\n\n\tprivate <M extends MemberMapping> boolean updateMembers(@Nonnull Collection<List<M>> newMappings) {\n\t\t// With members, we need to take special care, for example:\n\t\t// 1. a --> b\n\t\t// 2. b.x --> b.y\n\t\t// Now we need to ensure the mapping \"a.x --> b.y\" exists.\n\t\tboolean bridged = false;\n\t\tfor (List<M> members : newMappings) {\n\t\t\tfor (MemberMapping newMemberMapping : members) {\n\t\t\t\tString bOwnerName = newMemberMapping.getOwnerName();\n\t\t\t\tString aOwnerName = reverseOrderClassMapping.get(bOwnerName);\n\t\t\t\tString oldMemberName = newMemberMapping.getOldName();\n\t\t\t\tString newMemberName = newMemberMapping.getNewName();\n\t\t\t\tString desc = newMemberMapping.getDesc();\n\t\t\t\tString owner = bOwnerName;\n\t\t\t\tif (aOwnerName != null) {\n\t\t\t\t\t// We need to map the member current mapped owner name to the\n\t\t\t\t\t// original owner's name.\n\t\t\t\t\tbridged = true;\n\t\t\t\t\towner = aOwnerName;\n\t\t\t\t\toldMemberName = findPriorMemberName(aOwnerName, newMemberMapping);\n\t\t\t\t}\n\n\t\t\t\t// Desc must always be checked for updates\n\t\t\t\tdesc = applyReverseMappings(desc);\n\n\t\t\t\t// Add bridged entry\n\t\t\t\tif (newMemberMapping.isField()) {\n\t\t\t\t\tmissingFieldDescriptors |= desc == null;\n\t\t\t\t\taddField(owner, desc, oldMemberName, newMemberName);\n\t\t\t\t} else if (desc != null) {\n\t\t\t\t\taddMethod(owner, desc, oldMemberName, newMemberName);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn bridged;\n\t}\n\n\tprivate boolean updateVariables(@Nonnull Collection<List<VariableMapping>> newMappings) {\n\t\t// a.foo() var x\n\t\t//   ...\n\t\t// a.foo() --> b.foo()\n\t\t// b.foo() --> b.bar()\n\t\t// b.bar() var x --> var z\n\t\t//   ...\n\t\t// a.foo() var x --> var z\n\t\tboolean bridged = false;\n\n\t\t/*\n\t\tTODO: Aggregate variable mappings\n\t\tfor (List<VariableMapping> variableMappings : newMappings) {\n\t\t\tfor (VariableMapping newVariableMapping : variableMappings) {\n\t\t\t\tString bOwner = newVariableMapping.getOwnerName();\n\t\t\t\tString bMethodName = newVariableMapping.getMethodName();\n\t\t\t\tString bMethodDesc = newVariableMapping.getMethodDesc();\n\t\t\t}\n\t\t}\n\t\t */\n\t\treturn bridged;\n\t}\n\n\t@Nonnull\n\tprivate String findPriorMemberName(@Nonnull String oldClassName, @Nonnull MemberMapping memberMapping) {\n\t\tif (memberMapping.isField()) {\n\t\t\treturn findPriorName(memberMapping, getClassFieldMappings(oldClassName));\n\t\t} else {\n\t\t\treturn findPriorName(memberMapping, getClassMethodMappings(oldClassName));\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate String findPriorName(@Nonnull MemberMapping newMethodMapping, @Nonnull List<? extends MemberMapping> members) {\n\t\t// If the old name not previously mapped, then it's the same as what the new mapping has given.\n\t\t// So the passed new mapping is a safe default.\n\t\tMemberMapping target = newMethodMapping;\n\t\tString unmappedDesc = applyReverseMappings(newMethodMapping.getDesc());\n\t\tfor (MemberMapping oldMethodMapping : members) {\n\t\t\t// The old name must be the new mapping's base name.\n\t\t\t// The descriptor types must also match.\n\t\t\tif (oldMethodMapping.getNewName().equals(newMethodMapping.getOldName()) &&\n\t\t\t\t\tObjects.equals(oldMethodMapping.getDesc(), unmappedDesc)) {\n\t\t\t\ttarget = oldMethodMapping;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Remove old mapping entry\n\t\tmembers.remove(target);\n\t\treturn target.getOldName();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/aggregate/AggregatedMappingsListener.java",
    "content": "package software.coley.recaf.services.mapping.aggregate;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\n\n/**\n * Listener for when the {@link AggregatedMappings aggregated mappings} are updated.\n *\n * @author Matt Coley\n */\npublic interface AggregatedMappingsListener extends PrioritySortable {\n\t/**\n\t * Any update to the aggregated mappings will call this.\n\t *\n\t * @param mappings\n\t * \t\tCurrent aggregated mappings.\n\t */\n\tvoid onAggregatedMappingsUpdated(@Nonnull AggregatedMappings mappings);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/data/AbstractMappingKey.java",
    "content": "package software.coley.recaf.services.mapping.data;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Base mapping key.\n *\n * @author xDark\n */\npublic abstract class AbstractMappingKey implements MappingKey {\n\tprivate String text;\n\n\t@Nonnull\n\t@Override\n\tpublic String getAsText() {\n\t\tString text = this.text;\n\t\tif (text == null) {\n\t\t\treturn this.text = toText();\n\t\t}\n\t\treturn text;\n\t}\n\n\t@Override\n\tpublic int compareTo(MappingKey o) {\n\t\treturn getAsText().compareTo(o.getAsText());\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn getAsText();\n\t}\n\n\t@Nonnull\n\tprotected abstract String toText();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/data/ClassMapping.java",
    "content": "package software.coley.recaf.services.mapping.data;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Objects;\n\n/**\n * Outlines mappings for a class.\n *\n * @author Matt Coley\n */\npublic class ClassMapping {\n\tprivate final String oldName;\n\tprivate final String newName;\n\n\t/**\n\t * @param oldName\n\t * \t\tPre-mapping name.\n\t * @param newName\n\t * \t\tPost-mapping name.\n\t */\n\tpublic ClassMapping(@Nonnull String oldName, @Nonnull String newName) {\n\t\tthis.oldName = oldName;\n\t\tthis.newName = newName;\n\t}\n\n\t/**\n\t * @return Pre-mapping name.\n\t */\n\t@Nonnull\n\tpublic String getOldName() {\n\t\treturn oldName;\n\t}\n\n\t/**\n\t * @return Post-mapping name.\n\t */\n\t@Nonnull\n\tpublic String getNewName() {\n\t\treturn newName;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\t\tClassMapping that = (ClassMapping) o;\n\t\treturn oldName.equals(that.oldName) && newName.equals(that.newName);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn Objects.hash(oldName, newName);\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn oldName + \" ==> \" + newName;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/data/ClassMappingKey.java",
    "content": "package software.coley.recaf.services.mapping.data;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Mapping key for classes.\n *\n * @author xDark\n */\npublic class ClassMappingKey extends AbstractMappingKey {\n\tprivate final String name;\n\n\t/**\n\t * @param name\n\t * \t\tClass name.\n\t */\n\tpublic ClassMappingKey(@Nonnull String name) {\n\t\tthis.name = name;\n\t}\n\n\t/**\n\t * @return Class name.\n\t */\n\t@Nonnull\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected String toText() {\n\t\treturn name;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (!(o instanceof ClassMappingKey)) return false;\n\n\t\tClassMappingKey that = (ClassMappingKey) o;\n\n\t\treturn name.equals(that.name);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn name.hashCode();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/data/FieldMapping.java",
    "content": "package software.coley.recaf.services.mapping.data;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport java.util.Objects;\n\n/**\n * Outlines mappings for a field.\n *\n * @author Matt Coley\n */\npublic class FieldMapping implements MemberMapping {\n\tprivate final String ownerName;\n\tprivate final String desc;\n\tprivate final String oldName;\n\tprivate final String newName;\n\n\t/**\n\t * @param ownerName\n\t * \t\tName of class defining the field.\n\t * @param oldName\n\t * \t\tPre-mapping field name.\n\t * @param newName\n\t * \t\tPost-mapping field name.\n\t */\n\tpublic FieldMapping(@Nonnull String ownerName, @Nonnull String oldName, @Nonnull String newName) {\n\t\tthis(ownerName, oldName, null, newName);\n\t}\n\n\t/**\n\t * @param ownerName\n\t * \t\tName of class defining the field.\n\t * @param oldName\n\t * \t\tPre-mapping field name.\n\t * @param desc\n\t * \t\tDescriptor type of the field.\n\t * \t\tMay be {@code null} since not all formats use it.\n\t * @param newName\n\t * \t\tPost-mapping field name.\n\t */\n\tpublic FieldMapping(@Nonnull String ownerName, @Nonnull String oldName, @Nullable String desc, @Nonnull String newName) {\n\t\tthis.ownerName = ownerName;\n\t\tthis.oldName = oldName;\n\t\tthis.newName = newName;\n\t\tthis.desc = desc;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getOwnerName() {\n\t\treturn ownerName;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getDesc() {\n\t\treturn desc;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getOldName() {\n\t\treturn oldName;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getNewName() {\n\t\treturn newName;\n\t}\n\n\t@Override\n\tpublic boolean isField() {\n\t\treturn true;\n\t}\n\n\t/**\n\t * @return {@code true} when there is no associated type from {@link #getDesc()}.\n\t */\n\tpublic boolean hasType() {\n\t\treturn desc != null;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\t\tFieldMapping that = (FieldMapping) o;\n\t\treturn ownerName.equals(that.ownerName) && Objects.equals(desc, that.desc)\n\t\t\t\t&& oldName.equals(that.oldName) && newName.equals(that.newName);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn Objects.hash(ownerName, desc, oldName, newName);\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn oldName + \" ==> \" + newName;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/data/FieldMappingKey.java",
    "content": "package software.coley.recaf.services.mapping.data;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Objects;\n\n/**\n * Mapping key for fields.\n *\n * @author xDark\n */\npublic class FieldMappingKey extends AbstractMappingKey {\n\tprivate final String owner;\n\tprivate final String name;\n\tprivate final String desc;\n\n\t/**\n\t * @param owner\n\t * \t\tClass name.\n\t * @param name\n\t * \t\tField name.\n\t * @param desc\n\t * \t\tField descriptor.\n\t */\n\tpublic FieldMappingKey(String owner, String name, String desc) {\n\t\tthis.owner = owner;\n\t\tthis.name = name;\n\t\tthis.desc = desc;\n\t}\n\n\t/**\n\t * @return Class owner.\n\t */\n\tpublic String getOwner() {\n\t\treturn owner;\n\t}\n\n\t/**\n\t * @return Field name.\n\t */\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t/**\n\t * @return Field descriptor.\n\t */\n\tpublic String getDesc() {\n\t\treturn desc;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected String toText() {\n\t\tString owner = this.owner;\n\t\tString name = this.name;\n\t\tString desc = this.desc;\n\t\tif (desc == null) {\n\t\t\treturn owner + '\\t' + name;\n\t\t}\n\t\treturn owner + '\\t' + name + '\\t' + desc;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (!(o instanceof FieldMappingKey)) return false;\n\n\t\tFieldMappingKey that = (FieldMappingKey) o;\n\n\t\treturn owner.equals(that.owner) && name.equals(that.name) && Objects.equals(desc, that.desc);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = owner.hashCode();\n\t\tresult = 31 * result + name.hashCode();\n\t\tresult = 31 * result + Objects.hashCode(desc);\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/data/MappingKey.java",
    "content": "package software.coley.recaf.services.mapping.data;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Mapping key, may be a class, method,\n * field or a local variable.\n *\n * @author xDark\n */\npublic interface MappingKey extends Comparable<MappingKey> {\n\t@Nonnull\n\tString getAsText();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/data/MemberMapping.java",
    "content": "package software.coley.recaf.services.mapping.data;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\npublic interface MemberMapping {\n\t/**\n\t * @return Name of class defining the member.\n\t */\n\t@Nonnull\n\tString getOwnerName();\n\n\t/**\n\t * @return Descriptor type of the member.\n\t * May be {@code null} for fields with some mapping implementations.\n\t */\n\t@Nullable\n\tString getDesc();\n\n\t/**\n\t * @return Pre-mapping member name.\n\t */\n\t@Nonnull\n\tString getOldName();\n\n\t/**\n\t * @return Post-mapping member name.\n\t */\n\t@Nonnull\n\tString getNewName();\n\n\t/**\n\t * @return {@code true} when the member is a field.\n\t * {@code false} for methods.\n\t */\n\tboolean isField();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/data/MethodMapping.java",
    "content": "package software.coley.recaf.services.mapping.data;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Objects;\n\n/**\n * Outlines mappings for a method.\n *\n * @author Matt Coley\n */\npublic class MethodMapping implements MemberMapping {\n\tprivate final String ownerName;\n\tprivate final String desc;\n\tprivate final String oldName;\n\tprivate final String newName;\n\n\t/**\n\t * @param ownerName\n\t * \t\tName of class defining the method.\n\t * @param oldName\n\t * \t\tPre-mapping method name.\n\t * @param desc\n\t * \t\tDescriptor type of the method.\n\t * @param newName\n\t * \t\tPost-mapping method name.\n\t */\n\tpublic MethodMapping(@Nonnull String ownerName, @Nonnull String oldName,\n\t\t\t\t\t\t @Nonnull String desc, @Nonnull String newName) {\n\t\tthis.ownerName = ownerName;\n\t\tthis.desc = desc;\n\t\tthis.oldName = oldName;\n\t\tthis.newName = newName;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getOwnerName() {\n\t\treturn ownerName;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getDesc() {\n\t\treturn desc;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getOldName() {\n\t\treturn oldName;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getNewName() {\n\t\treturn newName;\n\t}\n\n\t@Override\n\tpublic boolean isField() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\t\tMethodMapping that = (MethodMapping) o;\n\t\treturn ownerName.equals(that.ownerName) && desc.equals(that.desc)\n\t\t\t\t&& oldName.equals(that.oldName) && newName.equals(that.newName);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn Objects.hash(ownerName, desc, oldName, newName);\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn oldName + \" ==> \" + newName;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/data/MethodMappingKey.java",
    "content": "package software.coley.recaf.services.mapping.data;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Objects;\n\n/**\n * Mapping key for methods.\n *\n * @author xDark\n */\npublic class MethodMappingKey extends AbstractMappingKey {\n\tprivate final String owner;\n\tprivate final String name;\n\tprivate final String desc;\n\n\t/**\n\t * @param owner\n\t * \t\tClass name.\n\t * @param name\n\t * \t\tMethod name.\n\t * @param desc\n\t * \t\tMethod descriptor.\n\t */\n\tpublic MethodMappingKey(String owner, String name, String desc) {\n\t\tthis.owner = owner;\n\t\tthis.name = name;\n\t\tthis.desc = desc;\n\t}\n\n\t/**\n\t * @return Class owner.\n\t */\n\tpublic String getOwner() {\n\t\treturn owner;\n\t}\n\n\t/**\n\t * @return Method name.\n\t */\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t/**\n\t * @return Method descriptor.\n\t */\n\tpublic String getDesc() {\n\t\treturn desc;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected String toText() {\n\t\tString owner = this.owner;\n\t\tString name = this.name;\n\t\tString desc = this.desc;\n\t\tif (desc == null) {\n\t\t\treturn owner + '\\t' + name;\n\t\t}\n\t\treturn owner + '\\t' + name + '\\t' + desc;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (!(o instanceof MethodMappingKey)) return false;\n\n\t\tMethodMappingKey that = (MethodMappingKey) o;\n\n\t\treturn owner.equals(that.owner) && name.equals(that.name) && Objects.equals(desc, that.desc);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = owner.hashCode();\n\t\tresult = 31 * result + name.hashCode();\n\t\tresult = 31 * result + Objects.hashCode(desc);\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/data/VariableMapping.java",
    "content": "package software.coley.recaf.services.mapping.data;\n\nimport java.util.Objects;\n\n/**\n * Outlines mappings for a variable.\n *\n * @author Matt Coley\n */\npublic class VariableMapping {\n\tprivate final String ownerName;\n\tprivate final String methodName;\n\tprivate final String methodDesc;\n\t// Variable data\n\tprivate final String oldName;\n\tprivate final String desc;\n\tprivate final int index;\n\tprivate final String newName;\n\n\t/**\n\t * @param ownerName\n\t * \t\tName of class defining the method.\n\t * @param methodName\n\t * \t\tPre-mapping method name.\n\t * @param methodDesc\n\t * \t\tDescriptor type of the method.\n\t * @param desc\n\t * \t\tVariable descriptor.\n\t * @param oldName\n\t * \t\tVariable old name.\n\t * @param index\n\t * \t\tVariable index.\n\t * @param newName\n\t * \t\tPost-mapping method name.\n\t */\n\tpublic VariableMapping(String ownerName, String methodName, String methodDesc,\n\t\t\t\t\t\t   String desc, String oldName, int index,\n\t\t\t\t\t\t   String newName) {\n\t\tthis.ownerName = Objects.requireNonNull(ownerName, \"Mapping entries cannot be null\");\n\t\tthis.methodDesc = Objects.requireNonNull(methodDesc, \"Mapping entries cannot be null\");\n\t\tthis.methodName = Objects.requireNonNull(methodName, \"Mapping entries cannot be null\");\n\t\tthis.newName = Objects.requireNonNull(newName, \"Mapping entries cannot be null\");\n\t\t// Variable info, may be null\n\t\tthis.desc = desc;\n\t\tthis.oldName = oldName;\n\t\tthis.index = index;\n\t}\n\n\t/**\n\t * @return Name of class defining the method.\n\t */\n\tpublic String getOwnerName() {\n\t\treturn ownerName;\n\t}\n\n\t/**\n\t * @return Method name.\n\t */\n\tpublic String getMethodName() {\n\t\treturn methodName;\n\t}\n\n\t/**\n\t * @return Method descriptor.\n\t */\n\tpublic String getMethodDesc() {\n\t\treturn methodDesc;\n\t}\n\n\t/**\n\t * @return Old variable name. May be {@code null}.\n\t */\n\tpublic String getOldName() {\n\t\treturn oldName;\n\t}\n\n\t/**\n\t * @return Variable descriptor. May be {@code null}.\n\t */\n\tpublic String getDesc() {\n\t\treturn desc;\n\t}\n\n\t/**\n\t * @return Variable descriptor. May be {@code -1} for unknown values.\n\t */\n\tpublic int getIndex() {\n\t\treturn index;\n\t}\n\n\t/**\n\t * @return New variable name.\n\t */\n\tpublic String getNewName() {\n\t\treturn newName;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn oldName + \" ==> \" + newName;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/data/VariableMappingKey.java",
    "content": "package software.coley.recaf.services.mapping.data;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Objects;\n\n/**\n * Mapping key for variable.\n *\n * @author xDark\n */\npublic class VariableMappingKey extends AbstractMappingKey {\n\tprivate final String owner;\n\tprivate final String methodName;\n\tprivate final String methodDesc;\n\tprivate final String variableName;\n\tprivate final String variableDesc;\n\n\t/**\n\t * @param owner\n\t * \t\tClass name.\n\t * @param methodName\n\t * \t\tMethod name.\n\t * @param methodDesc\n\t * \t\tMethod descriptor.\n\t * @param variableName\n\t * \t\tVariable name.\n\t * @param variableDesc\n\t * \t\tVariable descriptor.\n\t */\n\tpublic VariableMappingKey(String owner, String methodName, String methodDesc, String variableName, String variableDesc) {\n\t\tthis.owner = owner;\n\t\tthis.methodName = methodName;\n\t\tthis.methodDesc = methodDesc;\n\t\tthis.variableName = variableName;\n\t\tthis.variableDesc = variableDesc;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected String toText() {\n\t\tString owner = this.owner;\n\t\tString methodName = this.methodName;\n\t\tString methodDesc = Objects.toString(this.methodDesc);\n\t\tString variableName = this.variableName;\n\t\tString variableDesc = this.variableDesc;\n\t\tStringBuilder builder = new StringBuilder(owner.length() + methodName.length() +\n\t\t\t\tmethodDesc.length() + variableName.length() + 5);\n\t\tbuilder.append(owner).append('\\t');\n\t\tbuilder.append(methodName).append('\\t');\n\t\tbuilder.append(methodDesc).append('\\t');\n\t\tbuilder.append(variableName);\n\t\tif (variableDesc != null) {\n\t\t\tbuilder.append('\\t').append(variableDesc);\n\t\t}\n\t\treturn builder.toString();\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (!(o instanceof VariableMappingKey)) return false;\n\n\t\tVariableMappingKey that = (VariableMappingKey) o;\n\n\t\treturn owner.equals(that.owner) && methodName.equals(that.methodName)\n\t\t\t\t&& Objects.equals(methodDesc, that.methodDesc)\n\t\t\t\t&& variableName.equals(that.variableName)\n\t\t\t\t&& Objects.equals(variableDesc, that.variableDesc);\n\t}\n\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = owner.hashCode();\n\t\tresult = 31 * result + methodName.hashCode();\n\t\tresult = 31 * result + Objects.hashCode(methodDesc);\n\t\tresult = 31 * result + variableName.hashCode();\n\t\tresult = 31 * result + Objects.hashCode(variableDesc);\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/format/AbstractMappingFileFormat.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Common base for mapping file format values.\n *\n * @author Matt Coley\n */\npublic abstract class AbstractMappingFileFormat implements MappingFileFormat {\n\tprivate final String implementationName;\n\tprivate final boolean supportFieldTypeDifferentiation;\n\tprivate final boolean supportVariableTypeDifferentiation;\n\n\tprotected AbstractMappingFileFormat(String implementationName,\n\t\t\t\t\t\t\t\t\t\tboolean supportFieldTypeDifferentiation,\n\t\t\t\t\t\t\t\t\t\tboolean supportVariableTypeDifferentiation) {\n\t\tthis.implementationName = implementationName;\n\t\tthis.supportFieldTypeDifferentiation = supportFieldTypeDifferentiation;\n\t\tthis.supportVariableTypeDifferentiation = supportVariableTypeDifferentiation;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String implementationName() {\n\t\treturn implementationName;\n\t}\n\n\t@Override\n\tpublic boolean doesSupportFieldTypeDifferentiation() {\n\t\treturn supportFieldTypeDifferentiation;\n\t}\n\n\t@Override\n\tpublic boolean doesSupportVariableTypeDifferentiation() {\n\t\treturn supportVariableTypeDifferentiation;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/format/EnigmaMappings.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.slf4j.Logger;\nimport software.coley.collections.tuple.Pair;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.mapping.data.ClassMapping;\nimport software.coley.recaf.services.mapping.data.FieldMapping;\nimport software.coley.recaf.services.mapping.data.MethodMapping;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayDeque;\nimport java.util.Deque;\nimport java.util.function.Supplier;\nimport java.util.stream.Stream;\n\n/**\n * Enigma mappings file implementation.\n * <p>\n * Specification: <a href=\"https://wiki.fabricmc.net/documentation:enigma_mappings\">enigma_mappings</a>\n *\n * @author Janmm14\n * @author Matt Coley\n */\n@Dependent\npublic class EnigmaMappings extends AbstractMappingFileFormat {\n\tpublic static final String NAME = \"Enigma\";\n\tprivate static final String FAIL = \"Invalid Enigma mappings, \";\n\tprivate static final Logger LOGGER = Logging.get(EnigmaMappings.class);\n\t// Parser phase constants\n\tprivate static final int PHASE_IGNORE_LINE = 0;\n\tprivate static final int PHASE_FIND_TYPE = 1;\n\tprivate static final int PHASE_TYPE_CLASS = 2;\n\tprivate static final int PHASE_TYPE_FIELD = 3;\n\tprivate static final int PHASE_TYPE_METHOD = 4;\n\t// The finishing flag needs to be higher than the highest phase, as it is an additive flag\n\tprivate static final int PHASE_TYPE_FLAG_FINISH = 8;\n\n\t/**\n\t * New enigma instance.\n\t */\n\tpublic EnigmaMappings() {\n\t\tsuper(NAME, true, true);\n\t}\n\n\t/**\n\t * Parses an Enigma file, or Enigma directory containing multiple enigma mapping files, suffixed with {@code .mapping}.\n\t * <br>\n\t * See for instance: <a href=\"https://github.com/FabricMC/yarn/mappings\">FabricMC/yarn</a>\n\t *\n\t * @param path\n\t * \t\tRoot file/directory of enigma mappings.\n\t *\n\t * @return Intermediate mappings from parsed enigma file/directory.\n\t *\n\t * @throws InvalidMappingException\n\t * \t\tWhen reading the mappings encounters any failure.\n\t */\n\t@Nonnull\n\tpublic IntermediateMappings parse(@Nonnull Path path) throws InvalidMappingException {\n\t\tif (Files.isRegularFile(path)) {\n\t\t\ttry {\n\t\t\t\treturn parse(Files.readString(path));\n\t\t\t} catch (IOException ex) {\n\t\t\t\tthrow new InvalidMappingException(ex);\n\t\t\t}\n\t\t}\n\n\t\tIntermediateMappings sum = new IntermediateMappings();\n\t\ttry (Stream<Path> files = Files.walk(path).filter(p -> p.getFileName().toString().endsWith(\".mapping\"))) {\n\t\t\tfiles.forEach(p -> {\n\t\t\t\ttry {\n\t\t\t\t\tString fileContents = Files.readString(p);\n\t\t\t\t\tIntermediateMappings mappings = parse(fileContents);\n\t\t\t\t\tsum.putAll(mappings);\n\t\t\t\t} catch (Exception ex) {\n\t\t\t\t\t// Rethrow so outer catch will handle\n\t\t\t\t\tthrow new IllegalStateException(ex);\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (Throwable ex) {\n\t\t\tthrow new InvalidMappingException(\"Failed to walk enigma directory: \" + path, ex);\n\t\t}\n\t\treturn sum;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IntermediateMappings parse(@Nonnull String mappingsText) throws InvalidMappingException {\n\t\treturn parseEnigma(mappingsText);\n\t}\n\n\t/**\n\t * @param mappingsText\n\t * \t\tText of the mappings to parse.\n\t *\n\t * @return Intermediate mappings from parsed text.\n\t *\n\t * @throws InvalidMappingException\n\t * \t\tWhen reading the mappings encounters any failure.\n\t */\n\t@Nonnull\n\tpublic static IntermediateMappings parseEnigma(@Nonnull String mappingsText) throws InvalidMappingException {\n\t\t// COMMENT comment #ignored\n\t\t// CLASS BaseClass TargetClass\n\t\t//     FIELD baseField targetField baseDesc\n\t\t//     METHOD baseMethod targetMethod baseMethodDesc\n\t\t//         ARG baseArg targetArg #ignored\n\t\t//     CLASS 1 InnerClass\n\t\t//         FIELD innerField targetField innerDesc\n\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\tDeque<Pair<String, String>> currentClass = new ArrayDeque<>();\n\n\t\tint line = 1;\n\t\tfor (int i = 0, len = mappingsText.length(); i < len; ) { // i incremented inside the loop\n\t\t\t// count \\t\n\t\t\tint indent = 0;\n\t\t\tfor (; i < len; i++) {\n\t\t\t\tchar c = mappingsText.charAt(i);\n\t\t\t\tif (c == '\\t') {\n\t\t\t\t\tindent++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// parse line\n\t\t\ti = handleLine(line, indent, i, mappingsText, currentClass, mappings);\n\n\t\t\t// go to next line\n\t\t\tif (i < len) {\n\t\t\t\tchar c = mappingsText.charAt(i);\n\t\t\t\tassert c == '\\n' || c == '\\r' : \"Expected newline, got <\" + c + \"> (\" + ((int) c) + \") @line \" + line + \" @char \" + i;\n\t\t\t\tline++;\n\t\t\t\tif (c == '\\r') {\n\t\t\t\t\tint ip1 = i + 1;\n\t\t\t\t\tif (ip1 < len && mappingsText.charAt(ip1) == '\\n') {\n\t\t\t\t\t\ti++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ti++;\n\t\t\t}\n\t\t}\n\t\treturn mappings;\n\t}\n\n\t/**\n\t * @param line\n\t * \t\tCurrent line number in the mappings file <i>(1 based)</i>\n\t * @param indent\n\t * \t\tCurrent level of indentation. Should be equal to or less than the size of the {@code currentClass} {@link Deque}.\n\t * @param i\n\t * \t\tCurrent offset into the mappings file.\n\t * @param mappingsText\n\t * \t\tMappings file contents.\n\t * @param currentClass\n\t * \t\tDeque of the current 'context' <i>(what class are we building mappings for)</i>.\n\t * @param mappings\n\t * \t\tOutput mappings.\n\t *\n\t * @return Updated offset into the mappings file.\n\t *\n\t * @throws InvalidMappingException\n\t * \t\tWhen reading the mappings encounters any failure.\n\t */\n\tprivate static int handleLine(int line, int indent, int i, @Nonnull String mappingsText, @Nonnull Deque<Pair<String, String>> currentClass,\n\t                              @Nonnull IntermediateMappings mappings) throws InvalidMappingException {\n\t\t// read next token\n\t\tString lineType = mappingsText.substring(i = skipSpace(i, mappingsText), i = readToken(i, mappingsText));\n\t\tswitch (lineType) {\n\t\t\tcase \"CLASS\" -> {\n\t\t\t\tupdateIndent(currentClass, indent, () -> (\"Invalid Enigma mappings, CLASS indent level \" + indent + \" too deep (expected max. \"\n\t\t\t\t\t\t+ currentClass.size() + \", \" + currentClass + \") @line \" + line + \" @char \"), i);\n\n\t\t\t\tString classNameA = mappingsText.substring(i = skipSpace(i, mappingsText), i = readToken(i, mappingsText));\n\t\t\t\tclassNameA = removeNonePackage(classNameA);\n\t\t\t\tclassNameA = qualifyWithOuterClassesA(currentClass, classNameA);\n\n\t\t\t\tString classNameB = mappingsText.substring(i = skipSpace(i, mappingsText), i = readToken(i, mappingsText));\n\t\t\t\tif (classNameB.isEmpty() || \"-\".equals(classNameB) || classNameB.startsWith(\"ACC:\")) {\n\t\t\t\t\t// no mapping for class, but need to include for context for following members\n\t\t\t\t\tclassNameB = classNameA;\n\t\t\t\t} else {\n\t\t\t\t\tclassNameB = removeNonePackage(classNameB);\n\t\t\t\t\tclassNameB = qualifyWithOuterClassesB(currentClass, classNameB);\n\t\t\t\t\tmappings.addClass(classNameA, classNameB);\n\t\t\t\t}\n\t\t\t\tcurrentClass.push(new Pair<>(classNameA, classNameB));\n\t\t\t}\n\t\t\tcase \"FIELD\" ->\n\t\t\t\t\ti = handleClassMemberMapping(line, indent, i, mappingsText, currentClass, \"FIELD\", mappings::addField);\n\t\t\tcase \"METHOD\" ->\n\t\t\t\t\ti = handleClassMemberMapping(line, indent, i, mappingsText, currentClass, \"METHOD\", mappings::addMethod);\n\t\t}\n\t\ti = skipLineRest(i, mappingsText);\n\t\treturn i;\n\t}\n\n\t/**\n\t * @param line\n\t * \t\tCurrent line number in the mappings file <i>(1 based)</i>\n\t * @param indent\n\t * \t\tCurrent level of indentation. Should be equal to or less than the size of the {@code currentClass} {@link Deque}.\n\t * @param i\n\t * \t\tCurrent offset into the mappings file.\n\t * @param mappingsText\n\t * \t\tMappings file contents.\n\t * @param currentClass\n\t * \t\tDeque of the current 'context' <i>(what class are we building mappings for)</i>.\n\t * @param type\n\t * \t\tThe expected type of content we're handling. IE, {@code CLASS}, {@code FIELD}, or {@code METHOD}.\n\t * @param consumer\n\t * \t\tConsumer to record parsed mappings into.\n\t *\n\t * @return Updated offset into the mappings file.\n\t *\n\t * @throws InvalidMappingException\n\t * \t\tWhen reading the mappings encounters any failure.\n\t */\n\tprivate static int handleClassMemberMapping(int line, int indent, int i, @Nonnull String mappingsText, @Nonnull Deque<Pair<String, String>> currentClass,\n\t                                            @Nonnull String type, @Nonnull MemberMappingsConsumer consumer) throws InvalidMappingException {\n\t\t// <name-a> <name-b> <formatted-access-modifier> <desc> <eol>\n\t\t// <name-b> = '' | '-' | <space> <name>\n\t\tupdateIndent(currentClass, indent, () -> FAIL + type + \" indent level \" + indent + \" too deep (expected max. \"\n\t\t\t\t+ currentClass.size() + \", \" + currentClass + \") @line \" + line + \" @char \", i);\n\t\tif (currentClass.isEmpty()) {\n\t\t\tthrow new InvalidMappingException(FAIL + type + \" without class context @line \" + line + \" @char \" + i);\n\t\t}\n\n\t\tString nameA = mappingsText.substring(i = skipSpace(i, mappingsText), i = readToken(i, mappingsText));\n\t\tString nameB = mappingsText.substring(i = skipSpace(i, mappingsText), i = readToken(i, mappingsText));\n\n\t\t// If can have mapping\n\t\tif (!nameB.isEmpty() && !\"-\".equals(nameB) && !nameB.startsWith(\"ACC:\")) {\n\t\t\tString desc = mappingsText.substring(i = skipSpace(i, mappingsText), i = readToken(i, mappingsText));\n\t\t\tif (desc.startsWith(\"ACC:\")) { // skip optional access modifier\n\t\t\t\tdesc = mappingsText.substring(i = skipSpace(i, mappingsText), i = readToken(i, mappingsText));\n\t\t\t}\n\t\t\tif (desc.isEmpty()) {\n\t\t\t\t// no desc found = line contained only one name and optional access modifier = no mapping\n\t\t\t\treturn i;\n\t\t\t}\n\t\t\tassert currentClass.peek() != null; // checked above\n\t\t\tconsumer.accept(currentClass.peek().getLeft(), desc, nameA, nameB);\n\t\t}\n\t\treturn i;\n\t}\n\n\t/**\n\t * @param currentClass\n\t * \t\tDeque of the current 'context' <i>(what class are we building mappings for)</i>.\n\t * @param classNameA\n\t * \t\tInitial class name.\n\t *\n\t * @return Fully qualified class name based on the current context in the deque.\n\t */\n\t@Nonnull\n\tprivate static String qualifyWithOuterClassesA(@Nonnull Deque<Pair<String, String>> currentClass, @Nonnull String classNameA) {\n\t\tif (currentClass.isEmpty()) {\n\t\t\treturn classNameA;\n\t\t}\n\t\tStringBuilder sb = new StringBuilder();\n\t\tfor (Pair<String, String> pair : currentClass) {\n\t\t\tsb.append(pair.getLeft()).append('$');\n\t\t}\n\t\tclassNameA = sb.append(classNameA).toString();\n\t\treturn classNameA;\n\t}\n\n\t/**\n\t * @param currentClass\n\t * \t\tDeque of the current 'context' <i>(what class are we building mappings for)</i>.\n\t * @param classNameB\n\t * \t\tDestination class name.\n\t *\n\t * @return Fully qualified class name based on the current context in the deque.\n\t */\n\t@Nonnull\n\tprivate static String qualifyWithOuterClassesB(@Nonnull Deque<Pair<String, String>> currentClass, @Nonnull String classNameB) {\n\t\tif (currentClass.isEmpty()) {\n\t\t\treturn classNameB;\n\t\t}\n\t\tStringBuilder sb = new StringBuilder();\n\t\tfor (Pair<String, String> pair : currentClass) {\n\t\t\tsb.append(pair.getRight()).append('$');\n\t\t}\n\t\tclassNameB = sb.append(classNameB).toString();\n\t\treturn classNameB;\n\t}\n\n\t/**\n\t * @param currentClass\n\t * \t\tDeque of the current 'context' <i>(what class are we building mappings for)</i>.\n\t * @param indent\n\t * \t\tCurrent level of indentation. Should be equal to or less than the size of the {@code currentClass} {@link Deque}.\n\t * @param failStr\n\t * \t\tMessage to include in the thrown invalid mapping exception if the indentation state is invalid.\n\t * @param i\n\t * \t\tCurrent offset into the mappings file.\n\t *\n\t * @throws InvalidMappingException\n\t * \t\tThrown when the indentation state does not match the current class context.\n\t */\n\tprivate static void updateIndent(@Nonnull Deque<Pair<String, String>> currentClass, int indent, @Nonnull Supplier<String> failStr, int i) throws InvalidMappingException {\n\t\tif (indent > currentClass.size()) {\n\t\t\tthrow new InvalidMappingException(failStr.get() + i);\n\t\t}\n\t\twhile (currentClass.size() > indent) {\n\t\t\tcurrentClass.pop();\n\t\t}\n\t}\n\n\t/**\n\t * @param i\n\t * \t\tCurrent offset into the mappings file.\n\t * @param mappingsText\n\t * \t\tMappings file contents.\n\t *\n\t * @return Updated offset into the mappings file, skipping to the end of the line.\n\t */\n\tprivate static int skipLineRest(int i, @Nonnull String mappingsText) {\n\t\tfor (int len = mappingsText.length(); i < len; i++) {\n\t\t\tchar c = mappingsText.charAt(i);\n\t\t\tif (c == '\\r' || c == '\\n') {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn i;\n\t}\n\n\t/**\n\t * @param i\n\t * \t\tCurrent offset into the mappings file.\n\t * @param mappingsText\n\t * \t\tMappings file contents.\n\t *\n\t * @return Updated offset into the mappings file, skipping to the next non-space character.\n\t */\n\tprivate static int skipSpace(int i, @Nonnull String mappingsText) {\n\t\tfor (int len = mappingsText.length(); i < len; i++) {\n\t\t\tchar c = mappingsText.charAt(i);\n\t\t\tif (c != ' ') {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn i;\n\t}\n\n\t/**\n\t * @param i\n\t * \t\tCurrent offset into the mappings file.\n\t * @param mappingsText\n\t * \t\tMappings file contents.\n\t *\n\t * @return Updated offset into the mappings file, skipping to the end of the token.\n\t *\n\t * @throws InvalidMappingException\n\t * \t\tWhen a tab is encountered <i>(Unexpected indentation)</i>.\n\t */\n\tprivate static int readToken(int i, @Nonnull String mappingsText) throws InvalidMappingException {\n\t\t// read until next space, newline, or comment\n\t\tfor (int len = mappingsText.length(); i < len; i++) {\n\t\t\tchar c = mappingsText.charAt(i);\n\t\t\tif (c == '\\n' || c == '\\r' || c == ' ' || c == '#') {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (c == '\\t') {\n\t\t\t\tthrow new InvalidMappingException(\"Unexpected tab character @char \" + i);\n\t\t\t}\n\t\t}\n\t\treturn i;\n\t}\n\n\t@Override\n\tpublic String exportText(@Nonnull Mappings mappings) {\n\t\t//TODO: Fix inner class handling\n\t\t// - Currently we export inner classes as top-level classes\n\t\t// - We should match the spec and have inner-classes indented beneath their outer classes\n\n\t\tStringBuilder sb = new StringBuilder();\n\t\tIntermediateMappings intermediate = mappings.exportIntermediate();\n\t\tfor (String oldClassName : intermediate.getClassesWithMappings()) {\n\t\t\tClassMapping classMapping = intermediate.getClassMapping(oldClassName);\n\t\t\tif (classMapping != null) {\n\t\t\t\tString newClassName = classMapping.getNewName();\n\t\t\t\t// CLASS BaseClass TargetClass\n\t\t\t\tsb.append(\"CLASS \")\n\t\t\t\t\t\t.append(oldClassName).append(' ')\n\t\t\t\t\t\t.append(newClassName).append(\"\\n\");\n\t\t\t} else {\n\t\t\t\t// Not mapped, but need to include for context for following members\n\t\t\t\tsb.append(\"CLASS \")\n\t\t\t\t\t\t.append(oldClassName).append(\"\\n\");\n\t\t\t}\n\t\t\tfor (FieldMapping fieldMapping : intermediate.getClassFieldMappings(oldClassName)) {\n\t\t\t\tString oldFieldName = fieldMapping.getOldName();\n\t\t\t\tString newFieldName = fieldMapping.getNewName();\n\t\t\t\tString fieldDesc = fieldMapping.getDesc();\n\t\t\t\t// FIELD baseField targetField baseDesc\n\t\t\t\tsb.append(\"\\tFIELD \")\n\t\t\t\t\t\t.append(oldFieldName).append(' ')\n\t\t\t\t\t\t.append(newFieldName).append(' ')\n\t\t\t\t\t\t.append(fieldDesc).append(\"\\n\");\n\t\t\t}\n\t\t\tfor (MethodMapping methodMapping : intermediate.getClassMethodMappings(oldClassName)) {\n\t\t\t\tString oldMethodName = methodMapping.getOldName();\n\t\t\t\tString newMethodName = methodMapping.getNewName();\n\t\t\t\tString methodDesc = methodMapping.getDesc();\n\t\t\t\t// METHOD baseMethod targetMethod baseMethodDesc\n\t\t\t\tsb.append(\"\\tMETHOD \")\n\t\t\t\t\t\t.append(oldMethodName).append(' ')\n\t\t\t\t\t\t.append(newMethodName).append(' ')\n\t\t\t\t\t\t.append(methodDesc).append(\"\\n\");\n\t\t\t}\n\t\t}\n\t\treturn sb.toString();\n\t}\n\n\t@Nonnull\n\tprivate static String removeNonePackage(@Nonnull String text) {\n\t\treturn text.replaceAll(\"(?:^|(?<=L))none/\", \"\");\n\t}\n\n\tprivate interface MemberMappingsConsumer {\n\t\tvoid accept(String oldClassName, String desc, String oldName, String newName);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/format/InvalidMappingException.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Wrapper to encompass any error encountered during mapping format reading / writing.\n *\n * @author Matt Coley\n */\npublic class InvalidMappingException extends Exception {\n\t/**\n\t * @param cause\n\t * \t\tCause for mapping parse/write failure.\n\t */\n\tpublic InvalidMappingException(@Nonnull Throwable cause) {\n\t\tsuper(cause);\n\t}\n\n\t/**\n\t * @param message\n\t * \t\tDetail message for why the mappings are invalid.\n\t */\n\tpublic InvalidMappingException(@Nonnull String message) {\n\t\tsuper(message);\n\t}\n\n\t/**\n\t * @param message\n\t * \t\tDetail message for why the mappings are invalid.\n\t * @param cause\n\t * \t\tCause for mapping parse/write failure.\n\t */\n\tpublic InvalidMappingException(@Nonnull String message, @Nonnull Throwable cause) {\n\t\tsuper(message, cause);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/format/JadxMappings.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.mapping.data.ClassMapping;\nimport software.coley.recaf.services.mapping.data.FieldMapping;\nimport software.coley.recaf.services.mapping.data.MethodMapping;\nimport software.coley.recaf.util.StringUtil;\n\n/**\n * Jadx mappings file implementation.\n * <br>\n * This format type is no longer used as of Jadx 1.4.2.\n * Instead, Jadx now uses Engima & TinyV2.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class JadxMappings extends AbstractMappingFileFormat {\n\tpublic static final String NAME = \"Jadx (Legacy)\";\n\n\t/**\n\t * New jadx instance.\n\t */\n\tpublic JadxMappings() {\n\t\tsuper(NAME, true, true);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IntermediateMappings parse(@Nonnull String mappingText) {\n\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\tString[] lines = StringUtil.splitNewline(mappingText);\n\t\t// Example:\n\t\t// c android.support.a.b.a = C0005a\n\t\t// f android.support.a.b.a.a:Ljava/lang/Object; = f3a\n\t\t// m android.support.a.a.a.a(Landroid/app/Activity;[Ljava/lang/String;I)V = m0a\n\t\tint line = 0;\n\t\tfor (String lineStr : lines) {\n\t\t\tline++;\n\t\t\tString[] args = lineStr.trim().split(\"[\\\\s=:]+\");\n\t\t\tString type = args[0];\n\t\t\ttry {\n\t\t\t\tswitch (type) {\n\t\t\t\t\tcase \"c\":\n\t\t\t\t\t\t// 1: class-name\n\t\t\t\t\t\t// 2: renamed class (does not include package)\n\t\t\t\t\t\t// Replace \".\" in class name\n\t\t\t\t\t\tString original = args[1].replace('.', '/');\n\t\t\t\t\t\tString packageName = original.substring(0, original.lastIndexOf('/') + 1);\n\t\t\t\t\t\t// The new value is always in the same package.\n\t\t\t\t\t\t// Only the class is renamed, not the package.\n\t\t\t\t\t\tString renamed = packageName + args[2];\n\t\t\t\t\t\tmappings.addClass(original, renamed);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"f\":\n\t\t\t\t\t\t// 1: class-name.field-name\n\t\t\t\t\t\t// 2: field-type\n\t\t\t\t\t\t// 3: renamed\n\t\t\t\t\t\tString f1 = args[1].replaceAll(\"\\\\.(?=.+\\\\..+$)\", \"/\");\n\t\t\t\t\t\tString fieldOwner = f1.substring(0, f1.indexOf('.'));\n\t\t\t\t\t\tString fieldName = f1.substring(f1.indexOf('.') + 1);\n\t\t\t\t\t\tString fieldType = args[2];\n\t\t\t\t\t\tString renamedField = args[3];\n\t\t\t\t\t\t// Replace all \".\" except last one\n\t\t\t\t\t\tmappings.addField(fieldOwner, fieldType, fieldName, renamedField);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"m\":\n\t\t\t\t\t\t// 1: class-name.method-name + method-desc\n\t\t\t\t\t\t// 2: renamed\n\t\t\t\t\t\tString m1 = args[1].replaceAll(\"\\\\.(?=.+\\\\..+$)\", \"/\");\n\t\t\t\t\t\tString methodOwner = m1.substring(0, m1.indexOf('.'));\n\t\t\t\t\t\tString methodName = m1.substring(m1.indexOf('.') + 1, m1.indexOf('('));\n\t\t\t\t\t\tString methodType = m1.substring(m1.indexOf('('));\n\t\t\t\t\t\tString renamedMethod = args[2];\n\t\t\t\t\t\t// Replace all \".\" except last one\n\t\t\t\t\t\tmappings.addMethod(methodOwner, methodType, methodName, renamedMethod);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} catch (IndexOutOfBoundsException ex) {\n\t\t\t\tthrow new IllegalArgumentException(\"Invalid jadx mappings, failed parsing line \" + line, ex);\n\t\t\t}\n\t\t}\n\t\treturn mappings;\n\t}\n\n\t@Override\n\tpublic String exportText(@Nonnull Mappings mappings) {\n\t\tStringBuilder sb = new StringBuilder();\n\t\tIntermediateMappings intermediate = mappings.exportIntermediate();\n\t\tfor (String oldClassName : intermediate.getClassesWithMappings()) {\n\t\t\tClassMapping classMapping = intermediate.getClassMapping(oldClassName);\n\t\t\tif (classMapping != null) {\n\t\t\t\tString newClassName = classMapping.getNewName();\n\t\t\t\t// c android.support.a.b.a = C0005a\n\t\t\t\tsb.append(\"c \")\n\t\t\t\t\t\t.append(oldClassName.replace('/', '.')).append(\" = \")\n\t\t\t\t\t\t.append(newClassName.substring(newClassName.lastIndexOf('/') + 1)).append(\"\\n\");\n\t\t\t}\n\t\t\tfor (FieldMapping fieldMapping : intermediate.getClassFieldMappings(oldClassName)) {\n\t\t\t\tString oldFieldName = fieldMapping.getOldName();\n\t\t\t\tString newFieldName = fieldMapping.getNewName();\n\t\t\t\tString fieldDesc = fieldMapping.getDesc();\n\t\t\t\t// f android.support.a.b.a.a:Ljava/lang/Object; = f3a\n\t\t\t\tsb.append(\"f \")\n\t\t\t\t\t\t.append(oldClassName.replace('/', '.')).append('.')\n\t\t\t\t\t\t.append(oldFieldName).append(':').append(fieldDesc).append(\" = \")\n\t\t\t\t\t\t.append(newFieldName).append(\"\\n\");\n\n\t\t\t}\n\t\t\tfor (MethodMapping methodMapping : intermediate.getClassMethodMappings(oldClassName)) {\n\t\t\t\tString oldMethodName = methodMapping.getOldName();\n\t\t\t\tString newMethodName = methodMapping.getNewName();\n\t\t\t\tString methodDesc = methodMapping.getDesc();\n\t\t\t\t// m android.support.a.a.a.a(Landroid/app/Activity;[Ljava/lang/String;I)V = m0a\n\t\t\t\tsb.append(\"m \")\n\t\t\t\t\t\t.append(oldClassName.replace('/', '.')).append('.')\n\t\t\t\t\t\t.append(oldMethodName)\n\t\t\t\t\t\t.append(methodDesc).append(\" = \")\n\t\t\t\t\t\t.append(newMethodName).append(\"\\n\");\n\t\t\t}\n\t\t}\n\t\treturn sb.toString();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/format/MappingFileFormat.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport net.fabricmc.mappingio.MappedElementKind;\nimport net.fabricmc.mappingio.MappingVisitor;\nimport net.fabricmc.mappingio.tree.MappingTree;\nimport net.fabricmc.mappingio.tree.MemoryMappingTree;\nimport net.fabricmc.mappingio.tree.VisitOrder;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.mapping.data.ClassMapping;\nimport software.coley.recaf.services.mapping.data.FieldMapping;\nimport software.coley.recaf.services.mapping.data.MethodMapping;\n\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.io.StringWriter;\nimport java.util.List;\nimport java.util.function.Function;\n\n/**\n * Interface to use for explicit file format implementations of {@link Mappings}.\n * <br>\n * <h2>Relevant noteworthy points</h2>\n * <b>Incomplete mappings</b>: Not all mapping formats are complete in their representation. Some may omit the\n * descriptor of fields <i>(Because at the source level, overloaded names are illegal within the same class)</i>.\n * So while the methods defined here will always be provided all of this information, each implementation may have to\n * do more than a flat one-to-one lookup in these cases.\n * <br><br>\n * <b>Implementations do not need to be complete to partially work</b>: Some mapping formats do not support renaming\n * for variable names in methods. This is fine, because any method in this interface can be implemented as a no-op by\n * returning {@code null}.\n *\n * @author Matt Coley\n */\npublic interface MappingFileFormat {\n\t/**\n\t * @return Name of the mapping format implementation.\n\t */\n\t@Nonnull\n\tString implementationName();\n\n\t/**\n\t * @param mappingsText\n\t * \t\tText of the mappings to parse.\n\t *\n\t * @return Intermediate mappings from parsed text.\n\t *\n\t * @throws InvalidMappingException\n\t * \t\tWhen reading the mappings encounters any failure.\n\t */\n\t@Nonnull\n\tIntermediateMappings parse(@Nonnull String mappingsText) throws InvalidMappingException;\n\n\t/**\n\t * Some mapping formats do not include field types since name overloading is illegal at the source level of Java.\n\t * It's valid in the bytecode but the mapping omits this info since it isn't necessary information for mapping\n\t * that does not support name overloading.\n\t *\n\t * @return {@code true} when field mappings include the type descriptor in their lookup information.\n\t */\n\tboolean doesSupportFieldTypeDifferentiation();\n\n\t/**\n\t * Some mapping forats do not include variable types since name overloading is illegal at the source level of Java.\n\t * Variable names are not used by the JVM at all so their names can be anything at the bytecode level. So including\n\t * the type makes it easier to reverse mappings.\n\t *\n\t * @return {@code true} when variable mappings include the type descriptor in their lookup information.\n\t */\n\tboolean doesSupportVariableTypeDifferentiation();\n\n\t/**\n\t * @return {@code true} when exporting the current mappings to text is supported.\n\t *\n\t * @see #exportText(Mappings)\n\t */\n\tdefault boolean supportsExportText() {\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param mappings\n\t * \t\tMappings to write with the current format.\n\t *\n\t * @return Exported mapping text in the current format. {@code null} if exporting to the format is unsupported.\n\t *\n\t * @throws InvalidMappingException\n\t * \t\tWhen writing the mappings encounters any failure.\n\t */\n\t@Nullable\n\tdefault String exportText(@Nonnull Mappings mappings) throws InvalidMappingException {\n\t\treturn null;\n\t}\n\n\t/**\n\t * A utility for utilizing mapping-io to parse mapping text formats.\n\t *\n\t * @param mappingText\n\t * \t\tText of mapping to parse.\n\t * @param visitor\n\t * \t\tVisitor pointing to a mapping-io format reader.\n\t *\n\t * @return Intermediate mapping representation of the parsed text.\n\t *\n\t * @throws InvalidMappingException\n\t * \t\tWhen reading the mappings encounters any failure.\n\t */\n\t@Nonnull\n\tstatic IntermediateMappings parse(@Nonnull String mappingText, @Nonnull MappingTreeReader visitor) throws InvalidMappingException {\n\t\t// Populate the mapping-io model\n\t\tMemoryMappingTree tree = new MemoryMappingTree();\n\t\tStringReader reader = new StringReader(mappingText);\n\t\ttry {\n\t\t\tvisitor.read(reader, tree);\n\t\t} catch (IOException ex) {\n\t\t\tthrow new InvalidMappingException(ex);\n\t\t}\n\n\t\t// Create our mapping model.\n\t\tIntermediateMappings mappings = new IntermediateMappings();\n\n\t\t// Mapping IO supports multiple namespaces for outputs.\n\t\t// This is only really used in the 'tiny' format. Generally speaking the input columns look like:\n\t\t//   obfuscated, intermediate, clean\n\t\t// or:\n\t\t//   intermediate, clean\n\t\t// We want everything to map to the final column, rather than their notion of the first\n\t\t// column mapping to one of the following columns.\n\t\tint namespaceCount = tree.getDstNamespaces().size();\n\t\tint finalNamespace = namespaceCount - 1;\n\t\tfor (MappingTree.ClassMapping cm : tree.getClasses()) {\n\t\t\tString finalClassName = cm.getDstName(finalNamespace);\n\t\t\tif (finalClassName != null) {\n\t\t\t\t// Add the base case: input --> final output name\n\t\t\t\tmappings.addClass(cm.getSrcName(), finalClassName);\n\n\t\t\t\t// Add destination[n] --> final output name, where n < destinations.length - 1.\n\t\t\t\t// This is how we handle cases like 'intermediate --> clean' despite both of those\n\t\t\t\t// being \"output\" columns.\n\t\t\t\tif (namespaceCount > 1)\n\t\t\t\t\tfor (int i = 0; i < finalNamespace; i++) {\n\t\t\t\t\t\tString intermediateClassName = cm.getDstName(i);\n\t\t\t\t\t\tif (intermediateClassName != null)\n\t\t\t\t\t\t\tmappings.addClass(intermediateClassName, finalClassName);\n\t\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (MappingTree.FieldMapping fm : cm.getFields()) {\n\t\t\t\tString finalFieldName = fm.getDstName(finalNamespace);\n\t\t\t\tif (finalFieldName == null)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Base case, like before.\n\t\t\t\tString fieldDesc = fm.getSrcDesc();\n\t\t\t\tmappings.addField(cm.getSrcName(), fieldDesc, fm.getSrcName(), finalFieldName);\n\n\t\t\t\t// Support extra namespaces, like before.\n\t\t\t\tif (namespaceCount > 1)\n\t\t\t\t\tfor (int i = 0; i < finalNamespace; i++) {\n\t\t\t\t\t\tString intermediateClassName = cm.getDstName(i);\n\t\t\t\t\t\tString intermediateFieldDesc = fm.getDstDesc(i);\n\t\t\t\t\t\tString intermediateFieldName = fm.getDstName(i);\n\t\t\t\t\t\tif (intermediateClassName != null && intermediateFieldName != null)\n\t\t\t\t\t\t\tmappings.addField(intermediateClassName, intermediateFieldDesc, intermediateFieldName, finalFieldName);\n\t\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (MappingTree.MethodMapping mm : cm.getMethods()) {\n\t\t\t\tString finalMethodName = mm.getDstName(finalNamespace);\n\t\t\t\tif (finalMethodName == null)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Base case, like before.\n\t\t\t\tString methodDesc = mm.getSrcDesc();\n\t\t\t\tif (methodDesc != null)\n\t\t\t\t\tmappings.addMethod(cm.getSrcName(), methodDesc, mm.getSrcName(), finalMethodName);\n\n\t\t\t\t// Support extra namespaces, like before.\n\t\t\t\tif (namespaceCount > 1)\n\t\t\t\t\tfor (int i = 0; i < finalNamespace; i++) {\n\t\t\t\t\t\tString intermediateClassName = cm.getDstName(i);\n\t\t\t\t\t\tString intermediateMethodDesc = mm.getDstDesc(i);\n\t\t\t\t\t\tString intermediateMethodName = mm.getDstName(i);\n\t\t\t\t\t\tif (intermediateClassName != null && intermediateMethodDesc != null && intermediateMethodName != null)\n\t\t\t\t\t\t\tmappings.addMethod(intermediateClassName, intermediateMethodDesc, intermediateMethodName, finalMethodName);\n\t\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn mappings;\n\t}\n\n\t/**\n\t * A utility for utilizing mapping-io to write mapping text formats.\n\t *\n\t * @param mappings\n\t * \t\tMappings to export to text.\n\t * @param writerFactory\n\t * \t\tFactory to create a mapping-io format writer.\n\t *\n\t * @return Text representation of mappings in the format provided by the writer factory.\n\t *\n\t * @throws InvalidMappingException\n\t * \t\tWhen writing the mappings encounters any failure.\n\t */\n\t@Nonnull\n\tstatic String export(@Nonnull Mappings mappings, @Nonnull Function<StringWriter, MappingVisitor> writerFactory) throws InvalidMappingException {\n\t\treturn export(mappings, \"in\", List.of(\"out\"), writerFactory);\n\t}\n\n\t/**\n\t * A utility for utilizing mapping-io to write mapping text formats.\n\t *\n\t * @param mappings\n\t * \t\tMappings to export to text.\n\t * @param inputNamespace\n\t * \t\tInput column name.\n\t * @param outputNamespaces\n\t * \t\tOutput column names.\n\t * @param writerFactory\n\t * \t\tFactory to create a mapping-io format writer.\n\t *\n\t * @return Text representation of mappings in the format provided by the writer factory.\n\t *\n\t * @throws InvalidMappingException\n\t * \t\tWhen writing the mappings encounters any failure.\n\t */\n\t@Nonnull\n\tstatic String export(@Nonnull Mappings mappings, @Nonnull String inputNamespace,\n\t                     @Nonnull List<String> outputNamespaces, @Nonnull Function<StringWriter, MappingVisitor> writerFactory) throws InvalidMappingException {\n\t\tMemoryMappingTree tree = new MemoryMappingTree();\n\t\tIntermediateMappings intermediate = mappings.exportIntermediate();\n\t\ttry {\n\t\t\ttree.visitNamespaces(inputNamespace, outputNamespaces);\n\t\t\tfor (ClassMapping classMapping : intermediate.getClasses().values()) {\n\t\t\t\tString classOriginalName = classMapping.getOldName();\n\t\t\t\ttree.visitClass(classOriginalName);\n\t\t\t\ttree.visitDstName(MappedElementKind.CLASS, 0, classMapping.getNewName());\n\n\t\t\t\tList<FieldMapping> fieldMappings = intermediate.getClassFieldMappings(classOriginalName);\n\t\t\t\tfor (FieldMapping fieldMapping : fieldMappings) {\n\t\t\t\t\ttree.visitField(fieldMapping.getOldName(), fieldMapping.getDesc());\n\t\t\t\t\ttree.visitDstName(MappedElementKind.FIELD, 0, fieldMapping.getNewName());\n\t\t\t\t}\n\n\t\t\t\tList<MethodMapping> methodMappings = intermediate.getClassMethodMappings(classOriginalName);\n\t\t\t\tfor (MethodMapping methodMapping : methodMappings) {\n\t\t\t\t\ttree.visitMethod(methodMapping.getOldName(), methodMapping.getDesc());\n\t\t\t\t\ttree.visitDstName(MappedElementKind.METHOD, 0, methodMapping.getNewName());\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Write the mappings in natural sorted order by name.\n\t\t\t// Intermediate mappings are *typically* sorted, but it is not a guarantee.\n\t\t\tVisitOrder order = VisitOrder.createByName();\n\t\t\tStringWriter sw = new StringWriter();\n\t\t\tMappingVisitor writer = writerFactory.apply(sw);\n\t\t\ttree.accept(writer, order);\n\t\t\treturn sw.toString();\n\t\t} catch (Throwable t) {\n\t\t\tthrow new InvalidMappingException(t);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/format/MappingFormatManager.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.services.Service;\n\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.TreeMap;\nimport java.util.function.Supplier;\n\n/**\n * Manager of supported {@link MappingFileFormat} implementations.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class MappingFormatManager implements Service {\n\tpublic static final String SERVICE_ID = \"mapping-formats\";\n\tprivate final Map<String, Supplier<MappingFileFormat>> formatProviderMap = new TreeMap<>();\n\tprivate final MappingFormatManagerConfig config;\n\n\t/**\n\t * @param config\n\t * \t\tConfig to pull values from.\n\t * @param implementations\n\t * \t\tCDI provider of mapping format implementations.\n\t */\n\t@Inject\n\tpublic MappingFormatManager(MappingFormatManagerConfig config,\n\t\t\t\t\t\t\t\tInstance<MappingFileFormat> implementations) {\n\t\tthis.config = config;\n\n\t\t// Register implementations from CDI\n\t\t// The formats themselves are @Dependent meaning we get will use the handle's as suppliers\n\t\tfor (Instance.Handle<MappingFileFormat> handle : implementations.handles()) {\n\t\t\tMappingFileFormat format = handle.get();\n\t\t\tregisterFormat(format.implementationName(), handle::get);\n\t\t}\n\t}\n\n\t/**\n\t * @return Set of all known file formats by name.\n\t */\n\t@Nonnull\n\tpublic Set<String> getMappingFileFormats() {\n\t\treturn formatProviderMap.keySet();\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName of format.\n\t *\n\t * @return Instance of the file format, or {@code null} if none were found matching the name.\n\t */\n\t@Nullable\n\tpublic MappingFileFormat createFormatInstance(String name) {\n\t\tSupplier<MappingFileFormat> supplier = formatProviderMap.get(name);\n\t\tif (supplier != null)\n\t\t\treturn supplier.get();\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tThe format name.\n\t * @param supplier\n\t * \t\tA supplier to provide new instances of the format.\n\t */\n\tpublic void registerFormat(@Nonnull String name, @Nonnull Supplier<MappingFileFormat> supplier) {\n\t\tformatProviderMap.put(name, supplier);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic MappingFormatManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/format/MappingFormatManagerConfig.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link MappingFormatManager}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class MappingFormatManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic MappingFormatManagerConfig() {\n\t\tsuper(ConfigGroups.SERVICE_MAPPING, MappingFormatManager.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/format/MappingTreeReader.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport jakarta.annotation.Nonnull;\nimport net.fabricmc.mappingio.MappingVisitor;\nimport net.fabricmc.mappingio.format.tiny.Tiny1FileReader;\n\nimport java.io.IOException;\nimport java.io.Reader;\n\n/**\n * Outlines the read process from a given reader into the given visitor.\n * Should point to a file-reader method from mapping-io.\n * For instance: {@link Tiny1FileReader#read(Reader, MappingVisitor)}.\n *\n * @author Matt Coley\n * @see MappingFileFormat#parse(String, MappingTreeReader)\n */\npublic interface MappingTreeReader {\n\t/**\n\t * @param reader\n\t * \t\tReader containing the mapping file text.\n\t * @param visitor\n\t * \t\tMapping output visitor.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any mapping parse errors occur.\n\t */\n\tvoid read(@Nonnull Reader reader, @Nonnull MappingVisitor visitor) throws IOException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/format/ProguardMappings.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport net.fabricmc.mappingio.format.proguard.ProGuardFileWriter;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Proguard mappings file implementation.\n *\n * @author xDark\n */\n@Dependent\npublic class ProguardMappings extends AbstractMappingFileFormat {\n\tpublic static final String NAME = \"Proguard\";\n\tprivate static final String SPLITTER = \" -> \";\n\n\t/**\n\t * New proguard instance.\n\t */\n\tpublic ProguardMappings() {\n\t\tsuper(NAME, true, false);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IntermediateMappings parse(@Nonnull String mappingsText) {\n\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\tList<String> lines = Arrays.asList(StringUtil.splitNewline(mappingsText));\n\t\tMap<String, ProguardClassInfo> classMap = new HashMap<>(16384);\n\t\tStringBuilder firstCache = new StringBuilder();\n\t\tStringBuilder secondCache = new StringBuilder();\n\t\t{\n\t\t\t// Collect class mappings\n\t\t\tProguardClassInfo classInfo = null;\n\t\t\tint definitionStart = -1;\n\t\t\tfor (int i = 0, j = lines.size(); i < j; i++) {\n\t\t\t\tString line = lines.get(i);\n\t\t\t\tif (line.isEmpty() || line.trim().charAt(0) == '#') {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tint index = line.indexOf(SPLITTER);\n\t\t\t\tString left = line.substring(0, index);\n\t\t\t\tString right = line.substring(index + SPLITTER.length());\n\t\t\t\t// Class mapping lines end with ':'\n\t\t\t\tif (right.charAt(right.length() - 1) == ':') {\n\t\t\t\t\tString originalClassName = left.replace('.', '/');\n\t\t\t\t\tString obfuscatedName = right.substring(0, right.length() - 1).replace('.', '/');\n\t\t\t\t\tmappings.addClass(obfuscatedName, originalClassName);\n\t\t\t\t\tif (classInfo != null) {\n\t\t\t\t\t\t// Record the lines that need to be processed for the prior classInfo entry\n\t\t\t\t\t\t//  - These lines should include field/method mappings\n\t\t\t\t\t\tclassInfo.toProcess = lines.subList(definitionStart + 1, i);\n\t\t\t\t\t}\n\t\t\t\t\tclassInfo = new ProguardClassInfo(obfuscatedName);\n\t\t\t\t\tclassMap.put(originalClassName, classInfo);\n\t\t\t\t\tdefinitionStart = i;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Handle case for recording lines for the last class in the mappings file\n\t\t\tif (classInfo != null)\n\t\t\t\tclassInfo.toProcess = lines.subList(definitionStart + 1, lines.size());\n\t\t}\n\t\t// Second pass for recording fields and methods\n\t\tfor (ProguardClassInfo info : classMap.values()) {\n\t\t\tList<String> toProcess = info.toProcess;\n\t\t\tfor (String line : toProcess) {\n\t\t\t\tif (line.isEmpty() || line.trim().charAt(0) == '#') {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tint index = line.indexOf(SPLITTER);\n\t\t\t\tString left = line.substring(0, index);\n\t\t\t\tString right = line.substring(index + SPLITTER.length());\n\t\t\t\tif (left.charAt(left.length() - 1) == ')') {\n\t\t\t\t\tint idx = left.indexOf(':');\n\t\t\t\t\tif (idx != -1) {\n\t\t\t\t\t\tidx = left.indexOf(':', idx + 1);\n\t\t\t\t\t}\n\t\t\t\t\tString methodInfo = idx == -1 ? left : left.substring(idx + 1);\n\t\t\t\t\tint offset = 0;\n\t\t\t\t\twhile (methodInfo.charAt(offset) == ' ') {\n\t\t\t\t\t\toffset++;\n\t\t\t\t\t}\n\t\t\t\t\tString returnType = denormalizeType(methodInfo.substring(offset, offset = methodInfo.indexOf(' ', offset)), firstCache, classMap);\n\t\t\t\t\tfirstCache.setLength(0);\n\t\t\t\t\tfirstCache.append('(');\n\t\t\t\t\tString methodName = methodInfo.substring(offset + 1, offset = methodInfo.indexOf('('));\n\t\t\t\t\tint endOffset = methodInfo.indexOf(')', offset);\n\t\t\t\t\tparseDescriptor:\n\t\t\t\t\t{\n\t\t\t\t\t\tint typeStartOffset = methodInfo.indexOf(',', offset);\n\t\t\t\t\t\tif (typeStartOffset == -1) {\n\t\t\t\t\t\t\tif (endOffset == offset + 1) {\n\t\t\t\t\t\t\t\tbreak parseDescriptor;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttypeStartOffset = offset + 1;\n\t\t\t\t\t\tboolean anyLeft = true;\n\t\t\t\t\t\tdo {\n\t\t\t\t\t\t\tint typeEndOfsset = methodInfo.indexOf(',', typeStartOffset);\n\t\t\t\t\t\t\tif (typeEndOfsset == -1) {\n\t\t\t\t\t\t\t\tanyLeft = false;\n\t\t\t\t\t\t\t\ttypeEndOfsset = endOffset;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tString type = denormalizeType(methodInfo.substring(typeStartOffset, typeEndOfsset), secondCache, classMap);\n\t\t\t\t\t\t\tfirstCache.append(type);\n\t\t\t\t\t\t\ttypeStartOffset = anyLeft ? methodInfo.indexOf(',', typeEndOfsset) + 1 : -1;\n\t\t\t\t\t\t} while (anyLeft);\n\t\t\t\t\t}\n\t\t\t\t\tfirstCache.append(')').append(returnType);\n\t\t\t\t\tmappings.addMethod(info.mappedName, firstCache.toString(), right, methodName);\n\t\t\t\t} else {\n\t\t\t\t\tString fieldInfo = left;\n\t\t\t\t\tint offset = 0;\n\t\t\t\t\twhile (fieldInfo.charAt(offset) == ' ') {\n\t\t\t\t\t\toffset++;\n\t\t\t\t\t}\n\t\t\t\t\tString fieldType = denormalizeType(fieldInfo.substring(offset, offset = fieldInfo.indexOf(' ', offset)), firstCache, classMap);\n\t\t\t\t\tString fieldName = fieldInfo.substring(offset + 1);\n\t\t\t\t\tmappings.addField(info.mappedName, fieldType, right, fieldName);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn mappings;\n\t}\n\n\tprivate static String denormalizeType(String type, StringBuilder stringCache, Map<String, ProguardClassInfo> map) {\n\t\tint dimensions = 0;\n\t\tint offset = 1;\n\t\tint idx;\n\t\twhile (type.charAt((idx = type.length() - offset)) == ']') {\n\t\t\tdimensions++;\n\t\t\toffset += 2;\n\t\t}\n\t\tstringCache.setLength(0);\n\t\ttype = type.substring(0, idx + 1);\n\t\tswitch (type) {\n\t\t\tcase \"void\" -> type = \"V\";\n\t\t\tcase \"long\" -> type = \"J\";\n\t\t\tcase \"double\" -> type = \"D\";\n\t\t\tcase \"int\" -> type = \"I\";\n\t\t\tcase \"float\" -> type = \"F\";\n\t\t\tcase \"char\" -> type = \"C\";\n\t\t\tcase \"short\" -> type = \"S\";\n\t\t\tcase \"byte\" -> type = \"B\";\n\t\t\tcase \"boolean\" -> type = \"Z\";\n\t\t\tdefault -> {\n\t\t\t\ttype = type.replace('.', '/');\n\t\t\t\tProguardClassInfo classInfo = map.get(type);\n\t\t\t\tif (classInfo != null) {\n\t\t\t\t\ttype = classInfo.mappedName;\n\t\t\t\t}\n\t\t\t\tstringCache.append('L').append(type).append(';');\n\t\t\t}\n\t\t}\n\t\tif (dimensions != 0 || stringCache.length() != 0) {\n\t\t\tif (stringCache.length() == 0) {\n\t\t\t\tstringCache.append(type);\n\t\t\t}\n\t\t\twhile (dimensions-- != 0) {\n\t\t\t\tstringCache.insert(0, '[');\n\t\t\t}\n\t\t\ttype = stringCache.toString();\n\t\t}\n\t\treturn type;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String exportText(@Nonnull Mappings mappings) throws InvalidMappingException {\n\t\treturn MappingFileFormat.export(mappings, ProGuardFileWriter::new);\n\t}\n\n\tprivate static final class ProguardClassInfo {\n\t\tprivate List<String> toProcess = List.of();\n\t\tprivate final String mappedName;\n\n\t\tProguardClassInfo(String mappedName) {\n\t\t\tthis.mappedName = mappedName;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/format/SimpleMappings.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.mapping.data.ClassMapping;\nimport software.coley.recaf.services.mapping.data.FieldMapping;\nimport software.coley.recaf.services.mapping.data.MethodMapping;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.Map;\n\nimport static software.coley.recaf.util.EscapeUtil.escapeStandardAndUnicodeWhitespace;\nimport static software.coley.recaf.util.EscapeUtil.unescapeStandardAndUnicodeWhitespace;\n\n/**\n * Simple mappings file implementation where the old/new names are split by a space.\n * The input format of the mappings is based on the format outlined by\n * {@link org.objectweb.asm.commons.SimpleRemapper#SimpleRemapper(int, Map)}.\n * <br>\n * Differences include:\n * <ul>\n *     <li>Support for {@code #comment} lines</li>\n *     <li>Support for unicode escape sequences ({@code \\\\uXXXX})</li>\n *     <li>Support for fields specified by their name <i>and descriptor</i></li>\n * </ul>\n *\n * @author Matt Coley\n * @author Wolfie / win32kbase\n */\n@Dependent\npublic class SimpleMappings extends AbstractMappingFileFormat {\n\tpublic static final String NAME = \"Simple\";\n\n\t/**\n\t * New simple instance.\n\t */\n\tpublic SimpleMappings() {\n\t\tsuper(NAME, true, true);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IntermediateMappings parse(@Nonnull String mappingText) {\n\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\tString[] lines = StringUtil.splitNewline(mappingText);\n\t\t// # Comment\n\t\t// BaseClass TargetClass\n\t\t// BaseClass.baseField targetField\n\t\t// BaseClass.baseField baseDesc targetField\n\t\t// BaseClass.baseMethod(BaseMethodDesc) targetMethod\n\t\tfor (String line : lines) {\n\t\t\t// Skip comments and empty lines\n\t\t\tif (line.trim().startsWith(\"#\") || line.trim().isEmpty())\n\t\t\t\tcontinue;\n\t\t\tString[] args = line.split(\" \");\n\t\t\tString oldBaseName = unescapeStandardAndUnicodeWhitespace(args[0]);\n\t\t\tif (args.length >= 3) {\n\t\t\t\t// Descriptor qualified field format\n\t\t\t\tString desc = unescapeStandardAndUnicodeWhitespace(args[1]);\n\t\t\t\tString targetName = unescapeStandardAndUnicodeWhitespace(args[2]);\n\t\t\t\tint dot = oldBaseName.lastIndexOf('.');\n\t\t\t\tString oldClassName = oldBaseName.substring(0, dot);\n\t\t\t\tString oldFieldName = oldBaseName.substring(dot + 1);\n\t\t\t\tmappings.addField(oldClassName, desc, oldFieldName, targetName);\n\t\t\t} else {\n\t\t\t\tString newName = unescapeStandardAndUnicodeWhitespace(args[1]);\n\t\t\t\tint dot = oldBaseName.lastIndexOf('.');\n\t\t\t\tif (dot > 0) {\n\t\t\t\t\t// Indicates a member\n\t\t\t\t\tString oldClassName = oldBaseName.substring(0, dot);\n\t\t\t\t\tString oldIdentifier = oldBaseName.substring(dot + 1);\n\t\t\t\t\tint methodDescStart = oldIdentifier.lastIndexOf(\"(\");\n\t\t\t\t\tif (methodDescStart > 0) {\n\t\t\t\t\t\t// Method descriptor part of ID, split it up\n\t\t\t\t\t\tString methodName = oldIdentifier.substring(0, methodDescStart);\n\t\t\t\t\t\tString methodDesc = oldIdentifier.substring(methodDescStart);\n\t\t\t\t\t\tmappings.addMethod(oldClassName, methodDesc, methodName, newName);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Likely a field without linked descriptor\n\t\t\t\t\t\tmappings.addField(oldClassName, null, oldIdentifier, newName);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tmappings.addClass(oldBaseName, newName);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn mappings;\n\t}\n\n\t@Override\n\tpublic String exportText(@Nonnull Mappings mappings) {\n\t\tStringBuilder sb = new StringBuilder();\n\t\tIntermediateMappings intermediate = mappings.exportIntermediate();\n\t\tfor (String oldClassName : intermediate.getClassesWithMappings()) {\n\t\t\tClassMapping classMapping = intermediate.getClassMapping(oldClassName);\n\t\t\tString escapedOldClassName = escapeStandardAndUnicodeWhitespace(oldClassName);\n\t\t\tif (classMapping != null) {\n\t\t\t\tString newClassName = classMapping.getNewName();\n\t\t\t\t// BaseClass TargetClass\n\t\t\t\tsb.append(escapedOldClassName).append(' ').append(newClassName).append(\"\\n\");\n\t\t\t}\n\t\t\tfor (FieldMapping fieldMapping : intermediate.getClassFieldMappings(oldClassName)) {\n\t\t\t\tString oldFieldName = escapeStandardAndUnicodeWhitespace(fieldMapping.getOldName());\n\t\t\t\tString newFieldName = escapeStandardAndUnicodeWhitespace(fieldMapping.getNewName());\n\t\t\t\tString fieldDesc = escapeStandardAndUnicodeWhitespace(fieldMapping.getDesc());\n\t\t\t\tif (fieldDesc != null) {\n\t\t\t\t\t// BaseClass.baseField baseDesc targetField\n\t\t\t\t\tsb.append(escapedOldClassName).append('.').append(oldFieldName)\n\t\t\t\t\t\t\t.append(' ').append(fieldDesc)\n\t\t\t\t\t\t\t.append(' ').append(newFieldName).append(\"\\n\");\n\t\t\t\t} else {\n\t\t\t\t\t// BaseClass.baseField targetField\n\t\t\t\t\tsb.append(escapedOldClassName).append('.').append(oldFieldName)\n\t\t\t\t\t\t\t.append(' ').append(newFieldName).append(\"\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (MethodMapping methodMapping : intermediate.getClassMethodMappings(oldClassName)) {\n\t\t\t\tString oldMethodName = escapeStandardAndUnicodeWhitespace(methodMapping.getOldName());\n\t\t\t\tString newMethodName = escapeStandardAndUnicodeWhitespace(methodMapping.getNewName());\n\t\t\t\tString methodDesc = escapeStandardAndUnicodeWhitespace(methodMapping.getDesc());\n\t\t\t\t// BaseClass.baseMethod(BaseMethodDesc) targetMethod\n\t\t\t\tsb.append(escapedOldClassName).append('.').append(oldMethodName)\n\t\t\t\t\t\t.append(methodDesc)\n\t\t\t\t\t\t.append(' ').append(newMethodName).append(\"\\n\");\n\t\t\t}\n\t\t}\n\t\treturn sb.toString();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/format/SrgMappings.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.commons.Remapper;\nimport org.slf4j.Logger;\nimport software.coley.collections.tuple.Pair;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.mapping.BasicMappingsRemapper;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.mapping.data.ClassMapping;\nimport software.coley.recaf.services.mapping.data.FieldMapping;\nimport software.coley.recaf.services.mapping.data.MethodMapping;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * The MCP SRG format.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class SrgMappings extends AbstractMappingFileFormat {\n\tpublic static final String NAME = \"SRG\";\n\tprivate final Logger logger = Logging.get(TinyV1Mappings.class);\n\n\t/**\n\t * New SRG instance.\n\t */\n\tpublic SrgMappings() {\n\t\tsuper(NAME, false, false);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IntermediateMappings parse(@Nonnull String mappingText) {\n\t\tList<Pair<String, String>> packages = new ArrayList<>();\n\t\tIntermediateMappings mappings = new SrgIntermediateMappings(packages);\n\t\tString[] lines = StringUtil.splitNewline(mappingText);\n\t\tint line = 0;\n\t\tfor (String lineStr : lines) {\n\t\t\tline++;\n\t\t\tString[] args = lineStr.trim().split(\" \");\n\t\t\tString type = args[0];\n\t\t\ttry {\n\t\t\t\tswitch (type) {\n\t\t\t\t\tcase \"PK:\" -> {\n\t\t\t\t\t\tString obfPackage = args[1];\n\t\t\t\t\t\tString renamedPackage = args[2];\n\t\t\t\t\t\tpackages.add(new Pair<>(obfPackage, renamedPackage));\n\t\t\t\t\t}\n\t\t\t\t\tcase \"CL:\" -> {\n\t\t\t\t\t\tString obfClass = args[1];\n\t\t\t\t\t\tString renamedClass = args[2];\n\t\t\t\t\t\tmappings.addClass(obfClass, renamedClass);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"FD:\" -> {\n\t\t\t\t\t\t// Common format:\n\t\t\t\t\t\t// 0  1\n\t\t\t\t\t\t// FD obf-owner/obf-name\n\t\t\t\t\t\tString obfKey = args[1];\n\t\t\t\t\t\tint splitPos = obfKey.lastIndexOf('/');\n\t\t\t\t\t\tString obfOwner = obfKey.substring(0, splitPos);\n\t\t\t\t\t\tString obfName = obfKey.substring(splitPos + 1);\n\n\t\t\t\t\t\t// Handle SRG variants\n\t\t\t\t\t\tif (args.length == 5) {\n\t\t\t\t\t\t\t// XSRG format:\n\t\t\t\t\t\t\t// 0  1                  2        3                      4\n\t\t\t\t\t\t\t// FD obf-owner/obf-name obf-desc clean-owner/clean-name clean-desc\n\t\t\t\t\t\t\tString obfDesc = args[2];\n\t\t\t\t\t\t\tString renamedKey = args[3];\n\t\t\t\t\t\t\tsplitPos = renamedKey.lastIndexOf('/');\n\t\t\t\t\t\t\tString renamedName = renamedKey.substring(splitPos + 1);\n\t\t\t\t\t\t\tmappings.addField(obfOwner, obfDesc, obfName, renamedName);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// SRG format:\n\t\t\t\t\t\t\t// FD obf-owner/obf-name clean-owner/clean-name\n\t\t\t\t\t\t\tString renamedKey = args[2];\n\t\t\t\t\t\t\tsplitPos = renamedKey.lastIndexOf('/');\n\t\t\t\t\t\t\tString renamedName = renamedKey.substring(splitPos + 1);\n\t\t\t\t\t\t\tmappings.addField(obfOwner, null, obfName, renamedName);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcase \"MD:\" -> {\n\t\t\t\t\t\t// Common format:\n\t\t\t\t\t\t// 0  1                  3\n\t\t\t\t\t\t// MD obf-owner/obf-name obf-desc\n\t\t\t\t\t\tString obfKey = args[1];\n\t\t\t\t\t\tint splitPos = obfKey.lastIndexOf('/');\n\t\t\t\t\t\tString obfOwner = obfKey.substring(0, splitPos);\n\t\t\t\t\t\tString obfName = obfKey.substring(splitPos + 1);\n\t\t\t\t\t\tString obfDesc = args[2];\n\n\t\t\t\t\t\t// Handle SRG variants\n\t\t\t\t\t\tif (args.length == 5) {\n\t\t\t\t\t\t\t// XSRG format:\n\t\t\t\t\t\t\t// 0  1                  2        3                      4\n\t\t\t\t\t\t\t// MD obf-owner/obf-name obf-desc clean-owner/clean-name clean-desc\n\t\t\t\t\t\t\tString renamedKey = args[3];\n\t\t\t\t\t\t\tsplitPos = renamedKey.lastIndexOf('/');\n\t\t\t\t\t\t\tString renamedName = renamedKey.substring(splitPos + 1);\n\t\t\t\t\t\t\tmappings.addMethod(obfOwner, obfDesc, obfName, renamedName);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// SRG format:\n\t\t\t\t\t\t\t// 0  1                  2        3\n\t\t\t\t\t\t\t// MD obf-owner/obf-name obf-desc clean-owner/clean-name\n\t\t\t\t\t\t\tString renamedKey = args[3];\n\t\t\t\t\t\t\tsplitPos = renamedKey.lastIndexOf('/');\n\t\t\t\t\t\t\tString renamedName = renamedKey.substring(splitPos + 1);\n\t\t\t\t\t\t\tmappings.addMethod(obfOwner, obfDesc, obfName, renamedName);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdefault -> logger.trace(\"Unknown SRG mappings line type: \\\"{}\\\" @line {}\", type, line);\n\t\t\t\t}\n\t\t\t} catch (IndexOutOfBoundsException ex) {\n\t\t\t\tthrow new IllegalArgumentException(\"Failed parsing line \" + line, ex);\n\t\t\t}\n\t\t}\n\t\treturn mappings;\n\t}\n\n\t@Override\n\tpublic String exportText(@Nonnull Mappings mappings) {\n\t\tStringBuilder sb = new StringBuilder();\n\t\tRemapper remapper = new BasicMappingsRemapper(mappings);\n\t\tIntermediateMappings intermediate = mappings.exportIntermediate();\n\t\tfor (String oldClassName : intermediate.getClassesWithMappings()) {\n\t\t\tClassMapping classMapping = intermediate.getClassMapping(oldClassName);\n\t\t\tif (classMapping != null) {\n\t\t\t\tString newClassName = classMapping.getNewName();\n\t\t\t\t// CL: BaseClass TargetClass\n\t\t\t\tsb.append(\"CL: \").append(oldClassName).append(' ')\n\t\t\t\t\t\t.append(newClassName).append(\"\\n\");\n\t\t\t}\n\t\t\tString newClassName = classMapping == null ? oldClassName : classMapping.getNewName();\n\t\t\tfor (FieldMapping fieldMapping : intermediate.getClassFieldMappings(oldClassName)) {\n\t\t\t\tString oldFieldName = fieldMapping.getOldName();\n\t\t\t\tString newFieldName = fieldMapping.getNewName();\n\t\t\t\t// FD: BaseClass/baseField TargetClass/targetField\n\t\t\t\tsb.append(\"FD: \")\n\t\t\t\t\t\t.append(oldClassName).append('/').append(oldFieldName)\n\t\t\t\t\t\t.append(' ')\n\t\t\t\t\t\t.append(newClassName).append('/').append(newFieldName).append(\"\\n\");\n\t\t\t}\n\t\t\tfor (MethodMapping methodMapping : intermediate.getClassMethodMappings(oldClassName)) {\n\t\t\t\tString oldMethodName = methodMapping.getOldName();\n\t\t\t\tString newMethodName = methodMapping.getNewName();\n\t\t\t\tString methodDesc = methodMapping.getDesc();\n\t\t\t\tString mappedDesc = remapper.mapDesc(methodDesc);\n\t\t\t\t// MD: BaseClass/baseMethod baseDesc TargetClass/targetMethod targetDesc\n\t\t\t\tsb.append(\"MD: \")\n\t\t\t\t\t\t.append(oldClassName).append('/').append(oldMethodName)\n\t\t\t\t\t\t.append(' ')\n\t\t\t\t\t\t.append(methodDesc)\n\t\t\t\t\t\t.append(' ')\n\t\t\t\t\t\t.append(newClassName).append('/').append(newMethodName)\n\t\t\t\t\t\t.append(' ')\n\t\t\t\t\t\t.append(mappedDesc).append('\\n');\n\t\t\t}\n\t\t}\n\t\treturn sb.toString();\n\t}\n\n\t/**\n\t * Extension of intermediate mappings to support {@code PK} entries in the mapping file.\n\t */\n\tprivate static class SrgIntermediateMappings extends IntermediateMappings {\n\t\tprivate final List<Pair<String, String>> packageMappings;\n\n\t\tpublic SrgIntermediateMappings(List<Pair<String, String>> packageMappings) {\n\t\t\tsuper();\n\t\t\tthis.packageMappings = packageMappings;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean doesSupportFieldTypeDifferentiation() {\n\t\t\t// SRG fields do not include type info.\n\t\t\treturn false;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean doesSupportVariableTypeDifferentiation() {\n\t\t\t// See above.\n\t\t\treturn false;\n\t\t}\n\n\t\t@Nullable\n\t\t@Override\n\t\tpublic ClassMapping getClassMapping(@Nonnull String name) {\n\t\t\tClassMapping classMapping = super.getClassMapping(name);\n\t\t\tif (classMapping == null && !packageMappings.isEmpty()) {\n\t\t\t\tfor (Pair<String, String> packageMapping : packageMappings) {\n\t\t\t\t\tString oldPackage = packageMapping.getLeft();\n\t\t\t\t\tif (name.startsWith(oldPackage)) {\n\t\t\t\t\t\tString newPackage = packageMapping.getRight();\n\t\t\t\t\t\treturn new ClassMapping(name, newPackage + name.substring(oldPackage.length()));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn classMapping;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/format/TinyV1Mappings.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport net.fabricmc.mappingio.format.tiny.Tiny1FileReader;\nimport net.fabricmc.mappingio.format.tiny.Tiny1FileWriter;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\n\nimport java.util.List;\n\n/**\n * Tiny-V1 mappings file implementation.\n *\n * @author Matt Coley\n * @author Wolfie / win32kbase\n */\n@Dependent\npublic class TinyV1Mappings extends AbstractMappingFileFormat {\n\tpublic static final String NAME = \"Tiny-V1\";\n\n\t/**\n\t * New tiny v1 instance.\n\t */\n\tpublic TinyV1Mappings() {\n\t\tsuper(NAME, true, true);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IntermediateMappings parse(@Nonnull String mappingText) throws InvalidMappingException {\n\t\treturn MappingFileFormat.parse(mappingText, Tiny1FileReader::read);\n\t}\n\n\t@Override\n\tpublic String exportText(@Nonnull Mappings mappings) throws InvalidMappingException {\n\t\treturn MappingFileFormat.export(mappings, \"intermediary\", List.of(\"named\"), Tiny1FileWriter::new);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/format/TinyV2Mappings.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport net.fabricmc.mappingio.format.tiny.Tiny2FileReader;\nimport net.fabricmc.mappingio.format.tiny.Tiny2FileWriter;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\n\nimport java.util.List;\n\n/**\n * Tiny-V2 mappings file implementation.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class TinyV2Mappings extends AbstractMappingFileFormat {\n\tpublic static final String NAME = \"Tiny-V2\";\n\n\t/**\n\t * New tiny v2 instance.\n\t */\n\tpublic TinyV2Mappings() {\n\t\tsuper(NAME, true, true);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IntermediateMappings parse(@Nonnull String mappingText) throws InvalidMappingException {\n\t\treturn MappingFileFormat.parse(mappingText, Tiny2FileReader::read);\n\t}\n\n\t@Override\n\tpublic String exportText(@Nonnull Mappings mappings) throws InvalidMappingException {\n\t\treturn MappingFileFormat.export(mappings, \"intermediary\", List.of(\"named\"), writer -> new Tiny2FileWriter(writer, true));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/MappingGenerator.java",
    "content": "package software.coley.recaf.services.mapping.gen;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceVertex;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.mapping.MappingsAdapter;\nimport software.coley.recaf.services.mapping.gen.filter.ExcludeEnumMethodsFilter;\nimport software.coley.recaf.services.mapping.gen.filter.NameGeneratorFilter;\nimport software.coley.recaf.services.mapping.gen.naming.NameGenerator;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.SortedMap;\nimport java.util.TreeMap;\n\n/**\n * Mapping generator.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class MappingGenerator implements Service {\n\tpublic static final String SERVICE_ID = \"mapping-generator\";\n\tprivate final MappingGeneratorConfig config;\n\n\t@Inject\n\tpublic MappingGenerator(@Nonnull MappingGeneratorConfig config) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull class information from.\n\t * \t\tCan be {@code null} but some assumptions will be made about inner-class names.\n\t * @param resource\n\t * \t\tResource to generate mappings for.\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph to determine class hierarchies.\n\t * @param generator\n\t * \t\tName generation implementation.\n\t * @param filter\n\t * \t\tName generation filter, used to limit which classes and members get renamed.\n\t *\n\t * @return Newly generated mappings.\n\t */\n\t@Nonnull\n\tpublic Mappings generate(@Nullable Workspace workspace,\n\t                         @Nonnull WorkspaceResource resource,\n\t                         @Nonnull InheritanceGraph inheritanceGraph,\n\t                         @Nonnull NameGenerator generator,\n\t                         @Nullable NameGeneratorFilter filter) {\n\t\t// Adapt filter to handle baseline cases.\n\t\tfilter = new ExcludeEnumMethodsFilter(filter);\n\n\t\t// Setup adapter to store our mappings in.\n\t\tMappingsAdapter mappings = new MappingsAdapter(true, true);\n\t\tmappings.enableHierarchyLookup(inheritanceGraph);\n\t\tif (workspace != null)\n\t\t\tmappings.enableClassLookup(workspace);\n\t\tSortedMap<String, ClassInfo> classMap = new TreeMap<>();\n\t\tresource.jvmAllClassBundleStreamRecursive()\n\t\t\t\t.flatMap(Bundle::stream)\n\t\t\t\t.forEach(c -> classMap.putIfAbsent(c.getName(), c));\n\n\t\t// Pull a class, create mappings for its inheritance family, then remove those classes from the map.\n\t\t// When the map is empty everything has been run through the mapping generation process.\n\t\twhile (!classMap.isEmpty()) {\n\t\t\t// Get family from the class.\n\t\t\tString className = classMap.firstKey();\n\t\t\tSet<InheritanceVertex> family = inheritanceGraph.getVertexFamily(className, false);\n\n\t\t\t// Create mappings for the family\n\t\t\tgenerateFamilyMappings(mappings, family, generator, filter);\n\n\t\t\t// Remove all family members from the class map.\n\t\t\tif (family.isEmpty())\n\t\t\t\tclassMap.remove(className);\n\t\t\telse\n\t\t\t\tfamily.forEach(vertex -> classMap.remove(vertex.getName()));\n\t\t}\n\t\treturn mappings;\n\t}\n\n\tprivate void generateFamilyMappings(@Nonnull MappingsAdapter mappings, @Nonnull Set<InheritanceVertex> family,\n\t                                    @Nonnull NameGenerator generator, @Nonnull NameGeneratorFilter filter) {\n\t\t// Collect the members in the family that are inheritable, and methods that are library implementations.\n\t\t// We want this information so that for these members we give them a single name throughout the family.\n\t\t//  - Methods can be indirectly linked by two interfaces describing the same signature,\n\t\t//    and a child type implementing both types. So we have to be strict with naming with cases like this.\n\t\t//  - Fields do not have such a concern, but can still be accessed by child type owners.\n\t\tSet<MemberKey> inheritableFields = new HashSet<>();\n\t\tSet<MemberKey> inheritableMethods = new HashSet<>();\n\t\tSet<MemberKey> libraryMethods = new HashSet<>();\n\t\tfamily.forEach(vertex -> {\n\t\t\t// Skip module-info classes\n\t\t\tif (vertex.isModule())\n\t\t\t\treturn;\n\n\t\t\t// Record fields/methods, skipping private items since they cannot span the hierarchy.\n\t\t\tfor (FieldMember field : vertex.getValue().getFields()) {\n\t\t\t\tif (field.hasPrivateModifier())\n\t\t\t\t\tcontinue;\n\t\t\t\tinheritableFields.add(MemberKey.of(field));\n\t\t\t}\n\t\t\tfor (MethodMember method : vertex.getValue().getMethods()) {\n\t\t\t\tif (method.hasPrivateModifier())\n\t\t\t\t\tcontinue;\n\t\t\t\tMemberKey key = MemberKey.of(method);\n\t\t\t\tinheritableMethods.add(key);\n\n\t\t\t\t// Need to track which methods we cannot remap due to them being overrides of libraries\n\t\t\t\t// rather than being declared solely in our resource.\n\t\t\t\tif (vertex.isLibraryMethod(method.getName(), method.getDescriptor()))\n\t\t\t\t\tlibraryMethods.add(key);\n\t\t\t}\n\t\t});\n\t\t// Create mappings for members.\n\t\tfamily.forEach(vertex -> {\n\t\t\t// Skip libraries in the family.\n\t\t\tif (vertex.isLibraryVertex())\n\t\t\t\treturn;\n\n\t\t\t// Skip module-info classes\n\t\t\tif (vertex.isModule())\n\t\t\t\treturn;\n\n\t\t\tClassInfo owner = vertex.getValue();\n\t\t\tString ownerName = owner.getName();\n\t\t\tfor (FieldMember field : owner.getFields()) {\n\t\t\t\tString fieldName = field.getName();\n\t\t\t\tString fieldDesc = field.getDescriptor();\n\n\t\t\t\t// Skip if filtered.\n\t\t\t\tif (!filter.shouldMapField(owner, field))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Skip if already mapped.\n\t\t\t\tif (mappings.getMappedFieldName(ownerName, fieldName, fieldDesc) != null)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Create mapped name and record into mappings.\n\t\t\t\tMemberKey key = MemberKey.of(field);\n\t\t\t\tString mappedFieldName = generator.mapField(owner, field);\n\t\t\t\tif (inheritableFields.contains(key)) {\n\t\t\t\t\t// Field is 'inheritable' meaning it needs to have a consistent name\n\t\t\t\t\t// for all children and parents of this vertex.\n\t\t\t\t\tSet<InheritanceVertex> targetFamilyMembers = new HashSet<>();\n\t\t\t\t\ttargetFamilyMembers.add(vertex);\n\t\t\t\t\ttargetFamilyMembers.addAll(vertex.getAllChildren());\n\t\t\t\t\ttargetFamilyMembers.addAll(vertex.getAllParents());\n\t\t\t\t\ttargetFamilyMembers.forEach(immediateTreeVertex -> {\n\t\t\t\t\t\tif (immediateTreeVertex.hasField(fieldName, fieldDesc)) {\n\t\t\t\t\t\t\tString treeOwner = immediateTreeVertex.getName();\n\t\t\t\t\t\t\tmappings.addField(treeOwner, fieldName, fieldDesc, mappedFieldName);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t// Not 'inheritable' so an independent mapping is all we need.\n\t\t\t\t\tmappings.addField(ownerName, fieldName, fieldDesc, mappedFieldName);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (MethodMember method : owner.getMethods()) {\n\t\t\t\tString methodName = method.getName();\n\t\t\t\tString methodDesc = method.getDescriptor();\n\n\t\t\t\t// Skip if reserved method name.\n\t\t\t\tif (!methodName.isEmpty() && methodName.charAt(0) == '<')\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Skip if filtered.\n\t\t\t\tif (!filter.shouldMapMethod(owner, method))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Skip if method is a library method, or is already mapped.\n\t\t\t\tMemberKey key = MemberKey.of(method);\n\t\t\t\tif (libraryMethods.contains(key) || mappings.getMappedMethodName(ownerName, methodName, methodDesc) != null)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Create variable mappings\n\t\t\t\tfor (LocalVariable variable : method.getLocalVariables()) {\n\t\t\t\t\tString variableName = variable.getName();\n\n\t\t\t\t\t// Do not rename 'this' local variable... Unless its not \"this\" then force it to be \"this\"\n\t\t\t\t\tif (variable.getIndex() == 0 && !method.hasStaticModifier()) {\n\t\t\t\t\t\tif (!\"this\".equals(variableName))\n\t\t\t\t\t\t\tmappings.addVariable(ownerName, methodName, methodDesc, variableName, variable.getDescriptor(), variable.getIndex(), \"this\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (filter.shouldMapLocalVariable(owner, method, variable)) {\n\t\t\t\t\t\tString mappedVariableName = generator.mapVariable(owner, method, variable);\n\t\t\t\t\t\tif (!mappedVariableName.equals(variableName)) {\n\t\t\t\t\t\t\tmappings.addVariable(ownerName, methodName, methodDesc, variableName, variable.getDescriptor(), variable.getIndex(), mappedVariableName);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Create mapped name and record into mappings.\n\t\t\t\tString mappedMethodName = generator.mapMethod(owner, method);\n\n\t\t\t\t// Skip if the name generator yields the same name back.\n\t\t\t\tif (methodName.equals(mappedMethodName))\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif (inheritableMethods.contains(key)) {\n\t\t\t\t\t// Method is 'inheritable' meaning it needs to have a consistent name for the entire family.\n\t\t\t\t\t// But if one of the members of the family is filtered, then we cannot map anything.\n\t\t\t\t\tboolean shouldMapFamily = true;\n\t\t\t\t\tList<Runnable> pendingMapAdditions = new ArrayList<>();\n\t\t\t\t\tfor (InheritanceVertex familyVertex : family) {\n\t\t\t\t\t\tif (familyVertex.hasMethod(methodName, methodDesc)) {\n\t\t\t\t\t\t\tif (filter.shouldMapMethod(familyVertex.getValue(), method)) {\n\t\t\t\t\t\t\t\tpendingMapAdditions.add(() ->\n\t\t\t\t\t\t\t\t\t\tmappings.addMethod(familyVertex.getName(), methodName, methodDesc, mappedMethodName));\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tshouldMapFamily = false;\n\t\t\t\t\t\t\t\tpendingMapAdditions.clear();\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Nothing in the family was filtered, we can add the method mappings.\n\t\t\t\t\tif (shouldMapFamily)\n\t\t\t\t\t\tpendingMapAdditions.forEach(Runnable::run);\n\t\t\t\t} else {\n\t\t\t\t\t// Not 'inheritable' so an independent mapping is all we need.\n\t\t\t\t\tmappings.addMethod(ownerName, methodName, methodDesc, mappedMethodName);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\t// Create mappings for classes.\n\t\tfamily.forEach(vertex -> {\n\t\t\t// Skip libraries in the family.\n\t\t\tif (vertex.isLibraryVertex())\n\t\t\t\treturn;\n\t\t\t// Skip module-info classes\n\t\t\tif (vertex.isModule())\n\t\t\t\treturn;\n\n\t\t\t// Skip if filtered.\n\t\t\tClassInfo classInfo = vertex.getValue();\n\t\t\tif (!filter.shouldMapClass(classInfo))\n\t\t\t\treturn;\n\n\t\t\t// Add mapping.\n\t\t\tString name = vertex.getName();\n\t\t\tString mapped = generator.mapClass(classInfo);\n\t\t\tmappings.addClass(name, mapped);\n\t\t});\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic MappingGeneratorConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\t/**\n\t * Local record to use as set entries that are simpler than {@link ClassMember} implementations.\n\t * <p>\n\t * Most importantly the {@link Object#hashCode()} of this type is based only on the name and descriptor.\n\t * This ensures additional data like local variable or generic signature data doesn't interfere with operations\n\t * such as {@link #generateFamilyMappings(MappingsAdapter, Set, NameGenerator, NameGeneratorFilter)}.\n\t *\n\t * @param name\n\t * \t\tField/method name.\n\t * @param descriptor\n\t * \t\tField/method descriptor.\n\t */\n\tprivate record MemberKey(@Nonnull String name, @Nonnull String descriptor) {\n\t\t@Nonnull\n\t\tstatic MemberKey of(@Nonnull ClassMember member) {\n\t\t\treturn new MemberKey(member.getName(), member.getDescriptor());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/MappingGeneratorConfig.java",
    "content": "package software.coley.recaf.services.mapping.gen;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link MappingGenerator}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class MappingGeneratorConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic MappingGeneratorConfig() {\n\t\tsuper(ConfigGroups.SERVICE_MAPPING, MappingGenerator.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeClassesFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.search.match.StringPredicate;\n\n/**\n * Filter that excludes classes <i>(and their members)</i>.\n *\n * @author Matt Coley\n * @see IncludeClassesFilter\n */\npublic class ExcludeClassesFilter extends NameGeneratorFilter {\n\tprivate final StringPredicate namePredicate;\n\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t * @param namePredicate\n\t * \t\tClass name predicate for excluded names.\n\t */\n\tpublic ExcludeClassesFilter(@Nullable NameGeneratorFilter next, @Nonnull StringPredicate namePredicate) {\n\t\tsuper(next, true);\n\t\tthis.namePredicate = namePredicate;\n\t}\n\n\t@Override\n\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\treturn super.shouldMapClass(info) &&\n\t\t\t\t!(namePredicate.match(info.getName()));\n\t}\n\n\t@Override\n\tpublic boolean shouldMapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\t// Consider owner type, we do not want to map fields if they are inside the exclusion filter\n\t\treturn shouldMapClass(owner) && super.shouldMapField(owner, field);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\t// Consider owner type, we do not want to map methods if they are inside the exclusion filter\n\t\treturn shouldMapClass(owner) && super.shouldMapMethod(owner, method);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\treturn shouldMapClass(owner)\n\t\t\t\t&& super.shouldMapMethod(owner, declaringMethod)\n\t\t\t\t&& super.shouldMapLocalVariable(owner, declaringMethod, variable);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeEnumMethodsFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\n\n/**\n * Filter to prevent renaming of {@code Enum.values()} and {@code Enum.valueOf(String)} implementations.\n *\n * @author Matt Coley\n */\npublic class ExcludeEnumMethodsFilter extends NameGeneratorFilter {\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t */\n\tpublic ExcludeEnumMethodsFilter(@Nullable NameGeneratorFilter next) {\n\t\tsuper(next, true);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\tif (owner.hasEnumModifier()) {\n\t\t\tString ownerName = owner.getName();\n\t\t\tString name = method.getName();\n\t\t\tString desc = method.getDescriptor();\n\t\t\tif (name.equals(\"values\") && desc.equals(\"()[L\" + ownerName + \";\")) {\n\t\t\t\treturn false;\n\t\t\t} else if (name.equals(\"valueOf\") && desc.equals(\"(Ljava/lang/String;)L\" + ownerName + \";\")) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn super.shouldMapMethod(owner, method);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeExistingMappedFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.mapping.aggregate.AggregatedMappings;\n\n/**\n * Filter that excludes names that have already been specified by {@link AggregatedMappings}.\n *\n * @author Matt Coley\n */\npublic class ExcludeExistingMappedFilter extends NameGeneratorFilter {\n\tprivate final AggregatedMappings aggregate;\n\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t * @param aggregate\n\t * \t\tAggregate mappings instance to use when checking for existing mapping entries.\n\t */\n\tpublic ExcludeExistingMappedFilter(@Nullable NameGeneratorFilter next, @Nonnull AggregatedMappings aggregate) {\n\t\tsuper(next, true);\n\t\tthis.aggregate = aggregate;\n\t}\n\n\t@Override\n\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\tif (aggregate.getReverseClassMapping(info.getName()) != null)\n\t\t\treturn false;\n\t\treturn super.shouldMapClass(info);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\tif (aggregate.getReverseFieldMapping(owner.getName(), field.getName(), field.getDescriptor()) != null)\n\t\t\treturn false;\n\t\treturn super.shouldMapField(owner, field);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\tif (aggregate.getReverseMethodMapping(owner.getName(), method.getName(), method.getDescriptor()) != null)\n\t\t\treturn false;\n\t\treturn super.shouldMapMethod(owner, method);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\tif (aggregate.getReverseVariableMapping(owner.getName(), declaringMethod.getName(),\n\t\t\t\tdeclaringMethod.getDescriptor(), variable.getName(), variable.getDescriptor(), variable.getIndex()) != null)\n\t\t\treturn false;\n\t\treturn super.shouldMapLocalVariable(owner, declaringMethod, variable);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeModifiersNameFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport java.util.Collection;\n\n/**\n * Filter that excludes classes and members that match the given access modifiers.\n *\n * @author Matt Coley\n * @see IncludeModifiersNameFilter\n */\npublic class ExcludeModifiersNameFilter extends NameGeneratorFilter {\n\tprivate final int[] flags;\n\tprivate final boolean targetClasses;\n\tprivate final boolean targetFields;\n\tprivate final boolean targetMethods;\n\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t * @param flags\n\t * \t\tAccess flags to check for.\n\t * @param targetClasses\n\t * \t\tCheck against classes.\n\t * @param targetFields\n\t * \t\tCheck against fields.\n\t * @param targetMethods\n\t * \t\tCheck against methods.\n\t */\n\tpublic ExcludeModifiersNameFilter(@Nullable NameGeneratorFilter next, @Nonnull Collection<Integer> flags,\n\t\t\t\t\t\t\t\t\t  boolean targetClasses, boolean targetFields, boolean targetMethods) {\n\t\tsuper(next, true);\n\t\tthis.flags = flags.stream().mapToInt(i -> i).toArray();\n\t\tthis.targetClasses = targetClasses;\n\t\tthis.targetFields = targetFields;\n\t\tthis.targetMethods = targetMethods;\n\t}\n\n\t@Override\n\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\tif (targetClasses && info.hasAnyModifiers(flags))\n\t\t\treturn false;\n\t\treturn super.shouldMapClass(info);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\tif (targetFields && field.hasAnyModifiers(flags))\n\t\t\treturn false;\n\t\treturn super.shouldMapField(owner, field);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\tif (targetMethods && method.hasAnyModifiers(flags))\n\t\t\treturn false;\n\t\treturn super.shouldMapMethod(owner, method);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\t// Variables are not targeted, so delegate to next filter\n\t\treturn super.shouldMapLocalVariable(owner, declaringMethod, variable);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeNameFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.search.match.StringPredicate;\n\n/**\n * Filter that excludes classes, fields, methods, and variables by their names.\n *\n * @author Matt Coley\n * @see IncludeNameFilter\n */\npublic class ExcludeNameFilter extends NameGeneratorFilter {\n\tprivate final StringPredicate classPredicate;\n\tprivate final StringPredicate fieldPredicate;\n\tprivate final StringPredicate methodPredicate;\n\tprivate final StringPredicate variablePredicate;\n\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t * @param classPredicate\n\t * \t\tClass name predicate for included names.\n\t *        {@code null} to skip filtering for class names.\n\t * @param fieldPredicate\n\t * \t\tField name predicate for included names.\n\t *        {@code null} to skip filtering for field names.\n\t * @param methodPredicate\n\t * \t\tMethod name predicate for included names.\n\t *        {@code null} to skip filtering for method names.\n\t * @param variablePredicate\n\t * \t\tVariable name predicate for included names.\n\t *        {@code null} to skip filtering for variable names.\n\t */\n\tpublic ExcludeNameFilter(@Nullable NameGeneratorFilter next, @Nullable StringPredicate classPredicate,\n\t                         @Nullable StringPredicate fieldPredicate, @Nullable StringPredicate methodPredicate,\n\t                         @Nullable StringPredicate variablePredicate) {\n\t\tsuper(next, true);\n\t\tthis.classPredicate = classPredicate;\n\t\tthis.fieldPredicate = fieldPredicate;\n\t\tthis.methodPredicate = methodPredicate;\n\t\tthis.variablePredicate = variablePredicate;\n\t}\n\n\t@Override\n\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\treturn super.shouldMapClass(info) &&\n\t\t\t\t!(classPredicate != null && classPredicate.match(info.getName()));\n\t}\n\n\t@Override\n\tpublic boolean shouldMapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\treturn super.shouldMapField(owner, field) &&\n\t\t\t\t!(fieldPredicate != null && fieldPredicate.match(field.getName()));\n\t}\n\n\t@Override\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\treturn super.shouldMapMethod(owner, method) &&\n\t\t\t\t!(methodPredicate != null && methodPredicate.match(method.getName()));\n\t}\n\n\t@Override\n\tpublic boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\treturn super.shouldMapLocalVariable(owner, declaringMethod, variable) &&\n\t\t\t\t!(variablePredicate != null && variablePredicate.match(variable.getName()));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeClassesFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.search.match.StringPredicate;\n\n/**\n * Filter that includes classes <i>(and their members)</i>.\n *\n * @author Matt Coley\n * @see ExcludeClassesFilter\n */\npublic class IncludeClassesFilter extends NameGeneratorFilter {\n\tprivate final StringPredicate namePredicate;\n\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t * @param namePredicate\n\t * \t\tClass name predicate for included names.\n\t */\n\tpublic IncludeClassesFilter(@Nullable NameGeneratorFilter next, @Nonnull StringPredicate namePredicate) {\n\t\tsuper(next, true);\n\t\tthis.namePredicate = namePredicate;\n\t}\n\n\t@Override\n\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\treturn super.shouldMapClass(info) &&\n\t\t\t\t(namePredicate.match(info.getName()));\n\t}\n\n\t@Override\n\tpublic boolean shouldMapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\t// Consider owner type, we do not want to map fields if they are outside the inclusion filter\n\t\treturn shouldMapClass(owner) && super.shouldMapField(owner, field);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\t// Consider owner type, we do not want to map methods if they are outside the inclusion filter\n\t\treturn shouldMapClass(owner) && super.shouldMapMethod(owner, method);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod,\n\t                                      @Nonnull LocalVariable variable) {\n\t\t// Consider owner type and method, we do not want to map variables if they are outside the inclusion filter\n\t\treturn shouldMapClass(owner) && super.shouldMapMethod(owner, declaringMethod)\n\t\t\t\t&& super.shouldMapLocalVariable(owner, declaringMethod, variable);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeKeywordNameFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport static software.coley.recaf.util.Keywords.getKeywords;\n\n/**\n * Filter that includes names that contain <i>(when split by boundary characters)</i> reserved Java keywords.\n *\n * @author Matt Coley\n */\npublic class IncludeKeywordNameFilter extends NameGeneratorFilter {\n\tprivate static final Set<String> methodExemptions = Set.of(\"record\");\n\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t */\n\tpublic IncludeKeywordNameFilter(@Nullable NameGeneratorFilter next) {\n\t\tsuper(next, false);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\tString name = info.getName();\n\t\tif (containsKeyword(name))\n\t\t\treturn true;\n\t\treturn super.shouldMapClass(info);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapField(@Nonnull ClassInfo owner, @Nonnull FieldMember info) {\n\t\tif (containsKeyword(info.getName()))\n\t\t\treturn true;\n\t\treturn super.shouldMapField(owner, info);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember info) {\n\t\tString name = info.getName();\n\t\tif (containsKeyword(name) && !methodExemptions.contains(name))\n\t\t\treturn true;\n\t\treturn super.shouldMapMethod(owner, info);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\t// Edge case: 'this' is allowed only as local variable slot 0 on non-static methods.\n\t\tif (!declaringMethod.hasStaticModifier() && variable.getIndex() == 0 && \"this\".equals(variable.getName()))\n\t\t\treturn false;\n\t\tif (containsKeyword(variable.getName()))\n\t\t\treturn true;\n\t\treturn super.shouldMapLocalVariable(owner, declaringMethod, variable);\n\t}\n\n\tprivate static boolean containsKeyword(@Nonnull String name) {\n\t\tSet<String> keywords = getKeywords();\n\t\tString filtered = name.indexOf('-') > 0 ?\n\t\t\t\tname.replace(\"package-info\", \"package_info\").replace(\"module-info\", \"module_info\") :\n\t\t\t\tname;\n\t\tList<String> parts = StringUtil.fastSplitNonIdentifier(filtered);\n\t\tfor (String part : parts) {\n\t\t\tif (keywords.contains(part))\n\t\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeLongNameFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\n\n/**\n * Filter that includes names that are longer than a given size.\n *\n * @author Matt Coley\n */\npublic class IncludeLongNameFilter extends NameGeneratorFilter {\n\tprivate final int maxNameLength;\n\tprivate final boolean targetClasses;\n\tprivate final boolean targetFields;\n\tprivate final boolean targetMethods;\n\tprivate final boolean targetVariables;\n\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t * @param maxNameLength\n\t * \t\tMax length of names allowed.\n\t * @param targetClasses\n\t * \t\tCheck against classes.\n\t * @param targetFields\n\t * \t\tCheck against fields.\n\t * @param targetMethods\n\t * \t\tCheck against methods.\n\t * @param targetVariables\n\t * \t\tCheck against variables.\n\t */\n\tpublic IncludeLongNameFilter(@Nullable NameGeneratorFilter next, int maxNameLength,\n\t                             boolean targetClasses, boolean targetFields, boolean targetMethods, boolean targetVariables) {\n\t\tsuper(next, false);\n\t\tthis.maxNameLength = maxNameLength;\n\t\tthis.targetClasses = targetClasses;\n\t\tthis.targetFields = targetFields;\n\t\tthis.targetMethods = targetMethods;\n\t\tthis.targetVariables = targetVariables;\n\t}\n\n\t@Override\n\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\tif (targetClasses && shouldMap(info))\n\t\t\treturn true;\n\t\treturn super.shouldMapClass(info);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\tif (targetFields && shouldMap(field))\n\t\t\treturn true;\n\t\treturn super.shouldMapField(owner, field);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\tif (targetMethods && shouldMap(method))\n\t\t\treturn true;\n\t\treturn super.shouldMapMethod(owner, method);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\tif (targetVariables && shouldMap(variable))\n\t\t\treturn true;\n\t\treturn super.shouldMapLocalVariable(owner, declaringMethod, variable);\n\t}\n\n\tprivate boolean shouldMap(ClassInfo info) {\n\t\treturn shouldMap(info.getName());\n\t}\n\n\tprivate boolean shouldMap(ClassMember info) {\n\t\treturn shouldMap(info.getName());\n\t}\n\n\tprivate boolean shouldMap(LocalVariable info) {\n\t\treturn shouldMap(info.getName());\n\t}\n\n\tprivate boolean shouldMap(String name) {\n\t\treturn name.length() > maxNameLength;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeModifiersNameFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport java.util.Collection;\n\n/**\n * Filter that includes classes and members that match the given access modifiers.\n *\n * @author Matt Coley\n * @see ExcludeModifiersNameFilter\n */\npublic class IncludeModifiersNameFilter extends NameGeneratorFilter {\n\tprivate final int[] flags;\n\tprivate final boolean targetClasses;\n\tprivate final boolean targetFields;\n\tprivate final boolean targetMethods;\n\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t * @param flags\n\t * \t\tAccess flags to check for.\n\t * @param targetClasses\n\t * \t\tCheck against classes.\n\t * @param targetFields\n\t * \t\tCheck against fields.\n\t * @param targetMethods\n\t * \t\tCheck against methods.\n\t */\n\tpublic IncludeModifiersNameFilter(@Nullable NameGeneratorFilter next, @Nonnull Collection<Integer> flags,\n\t\t\t\t\t\t\t\t\t  boolean targetClasses, boolean targetFields, boolean targetMethods) {\n\t\tsuper(next, false);\n\t\tthis.flags = flags.stream().mapToInt(i -> i).toArray();\n\t\tthis.targetClasses = targetClasses;\n\t\tthis.targetFields = targetFields;\n\t\tthis.targetMethods = targetMethods;\n\t}\n\n\t@Override\n\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\tif (targetClasses && info.hasAnyModifiers(flags))\n\t\t\treturn true;\n\t\treturn super.shouldMapClass(info);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\tif (targetFields && field.hasAnyModifiers(flags))\n\t\t\treturn true;\n\t\treturn super.shouldMapField(owner, field);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\tif (targetMethods && method.hasAnyModifiers(flags))\n\t\t\treturn true;\n\t\treturn super.shouldMapMethod(owner, method);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\t// Variables are not targeted, so delegate to next filter\n\t\treturn super.shouldMapLocalVariable(owner, declaringMethod, variable);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeNameFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.search.match.StringPredicate;\n\n/**\n * Filter that includes classes, fields, and methods by their names.\n *\n * @author Matt Coley\n * @see ExcludeNameFilter\n */\npublic class IncludeNameFilter extends NameGeneratorFilter {\n\tprivate final StringPredicate classPredicate;\n\tprivate final StringPredicate fieldPredicate;\n\tprivate final StringPredicate methodPredicate;\n\tprivate final StringPredicate variablePredicate;\n\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t * @param classPredicate\n\t * \t\tClass name predicate for excluded names.\n\t *        {@code null} to skip filtering for class names.\n\t * @param fieldPredicate\n\t * \t\tField name predicate for excluded names.\n\t *        {@code null} to skip filtering for field names.\n\t * @param methodPredicate\n\t * \t\tMethod name predicate for excluded names.\n\t *        {@code null} to skip filtering for method names.\n\t * @param variablePredicate\n\t * \t\tVariable name predicate for excluded names.\n\t *        {@code null} to skip filtering for variable names.\n\t */\n\tpublic IncludeNameFilter(@Nullable NameGeneratorFilter next, @Nullable StringPredicate classPredicate,\n\t\t\t\t\t\t\t @Nullable StringPredicate fieldPredicate, @Nullable StringPredicate methodPredicate,\n\t\t\t\t\t\t\t @Nullable StringPredicate variablePredicate) {\n\t\tsuper(next, false);\n\t\tthis.classPredicate = classPredicate;\n\t\tthis.fieldPredicate = fieldPredicate;\n\t\tthis.methodPredicate = methodPredicate;\n\t\tthis.variablePredicate = variablePredicate;\n\t}\n\n\t@Override\n\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\tif (classPredicate != null && classPredicate.match(info.getName()))\n\t\t\treturn true;\n\t\treturn super.shouldMapClass(info);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\tif (fieldPredicate != null && fieldPredicate.match(field.getName()))\n\t\t\treturn true;\n\t\treturn super.shouldMapField(owner, field);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\tif (methodPredicate != null && methodPredicate.match(method.getName()))\n\t\t\treturn true;\n\t\treturn super.shouldMapMethod(owner, method);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\tif (variablePredicate != null && variablePredicate.match(variable.getName()))\n\t\t\treturn true;\n\t\treturn super.shouldMapLocalVariable(owner, declaringMethod, variable);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeNonAsciiNameFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\n\n/**\n * Filter that includes names that are outside the standard ASCII range used for normal class/member names.\n *\n * @author Matt Coley\n */\npublic class IncludeNonAsciiNameFilter extends NameGeneratorFilter {\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t */\n\tpublic IncludeNonAsciiNameFilter(@Nullable NameGeneratorFilter next) {\n\t\tsuper(next, false);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\tif (shouldMap(info))\n\t\t\treturn true;\n\t\treturn super.shouldMapClass(info);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\tif (shouldMap(field))\n\t\t\treturn true;\n\t\treturn super.shouldMapField(owner, field);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\tif (shouldMap(method))\n\t\t\treturn true;\n\t\treturn super.shouldMapMethod(owner, method);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\tif (shouldMap(variable))\n\t\t\treturn true;\n\t\treturn super.shouldMapLocalVariable(owner, declaringMethod, variable);\n\t}\n\n\tprivate static boolean shouldMap(ClassInfo info) {\n\t\treturn shouldMap(info.getName());\n\t}\n\n\tprivate static boolean shouldMap(ClassMember info) {\n\t\treturn shouldMap(info.getName());\n\t}\n\n\tprivate static boolean shouldMap(LocalVariable info) {\n\t\treturn shouldMap(info.getName());\n\t}\n\n\tprivate static boolean shouldMap(String name) {\n\t\treturn name.codePoints()\n\t\t\t\t.anyMatch(code -> (code < 0x21 || code > 0x7A));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeNonJavaIdentifierNameFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * Filter that includes names that do not comply with {@link Character#isJavaIdentifierStart(char)} and {@link Character#isJavaIdentifierPart(char)}.\n *\n * @author Matt Coley\n */\npublic class IncludeNonJavaIdentifierNameFilter extends NameGeneratorFilter {\n\tprivate static final Set<String> classExemptions = Set.of(\"package-info\", \"module-info\");\n\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t */\n\tpublic IncludeNonJavaIdentifierNameFilter(@Nullable NameGeneratorFilter next) {\n\t\tsuper(next, false);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\tString name = info.getName();\n\n\t\t// Filter out package/module-info classes\n\t\tif (name.endsWith(\"package-info\"))\n\t\t\tname = name.substring(0, name.length() - \"package-info\".length());\n\t\telse if (name.endsWith(\"module-info\"))\n\t\t\tname = name.substring(0, name.length() - \"module-info\".length());\n\n\t\tif (isInvalidName(name))\n\t\t\treturn true;\n\t\treturn super.shouldMapClass(info);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapField(@Nonnull ClassInfo owner, @Nonnull FieldMember info) {\n\t\tif (isInvalidName(info.getName()))\n\t\t\treturn true;\n\t\treturn super.shouldMapField(owner, info);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember info) {\n\t\tif (isInvalidName(info.getName()))\n\t\t\treturn true;\n\t\treturn super.shouldMapMethod(owner, info);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\tif (isInvalidName(variable.getName()))\n\t\t\treturn true;\n\t\treturn super.shouldMapLocalVariable(owner, declaringMethod, variable);\n\t}\n\n\tprivate static boolean isInvalidName(@Nonnull String name) {\n\t\tList<String> parts = StringUtil.fastSplitNonIdentifier(name);\n\t\tfor (String part : parts) {\n\t\t\tint length = part.length();\n\t\t\tif (length == 0)\n\t\t\t\treturn true;\n\t\t\telse if (length == 1)\n\t\t\t\treturn !Character.isJavaIdentifierStart(part.charAt(0));\n\t\t\telse {\n\t\t\t\tchar[] chars = part.toCharArray();\n\t\t\t\tif (!Character.isJavaIdentifierStart(chars[0]))\n\t\t\t\t\treturn true;\n\t\t\t\tfor (int i = 1; i < chars.length; i++) {\n\t\t\t\t\tif (!Character.isJavaIdentifierPart(chars[i]))\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeWhitespaceNameFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.util.EscapeUtil;\n\n/**\n * Filter that includes names that contain whitespaces, which are illegal in standard Java source.\n *\n * @author Matt Coley\n */\npublic class IncludeWhitespaceNameFilter extends NameGeneratorFilter {\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t */\n\tpublic IncludeWhitespaceNameFilter(@Nullable NameGeneratorFilter next) {\n\t\tsuper(next, false);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\tif (shouldMap(info))\n\t\t\treturn true;\n\t\treturn super.shouldMapClass(info);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\tif (shouldMap(field))\n\t\t\treturn true;\n\t\treturn super.shouldMapField(owner, field);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\tif (shouldMap(method))\n\t\t\treturn true;\n\t\treturn super.shouldMapMethod(owner, method);\n\t}\n\n\t@Override\n\tpublic boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\tif (shouldMap(variable))\n\t\t\treturn true;\n\t\treturn super.shouldMapLocalVariable(owner, declaringMethod, variable);\n\t}\n\n\tprivate static boolean shouldMap(ClassInfo info) {\n\t\treturn shouldMap(info.getName());\n\t}\n\n\tprivate static boolean shouldMap(ClassMember member) {\n\t\treturn shouldMap(member.getName());\n\t}\n\n\tprivate static boolean shouldMap(LocalVariable variable) {\n\t\treturn shouldMap(variable.getName());\n\t}\n\n\tprivate static boolean shouldMap(String name) {\n\t\treturn EscapeUtil.containsWhitespace(name);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/NameGeneratorFilter.java",
    "content": "package software.coley.recaf.services.mapping.gen.filter;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\n\n/**\n * Base filter outline. An implementation of a filter would expand or limit the scope of the generated mappings.\n *\n * @author Matt Coley\n */\npublic abstract class NameGeneratorFilter {\n\tprivate final NameGeneratorFilter next;\n\tprivate final boolean defaultMap;\n\n\t/**\n\t * @param next\n\t * \t\tNext filter to link. Chaining filters allows for {@code thisFilter && nextFilter}.\n\t * @param defaultMap\n\t *        {@code true} to make renaming things the default, treating chains as limitations on the baseline.\n\t *        {@code false} to make keeping names the default, treating chains as expansions on the baseline.\n\t */\n\tprotected NameGeneratorFilter(@Nullable NameGeneratorFilter next, boolean defaultMap) {\n\t\tthis.next = next;\n\t\tthis.defaultMap = defaultMap;\n\t}\n\n\t/**\n\t * @return {@code true} to make renaming things the default, treating chains as limitations on the baseline.\n\t * {@code false} to make keeping names the default, treating chains as expansions on the baseline.\n\t */\n\tpublic boolean isDefaultMap() {\n\t\treturn defaultMap;\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass to check.\n\t *\n\t * @return {@code true} if the generator should create a new name for the class.\n\t */\n\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\tif (defaultMap)\n\t\t\treturn next == null || next.shouldMapClass(info);\n\t\telse\n\t\t\treturn next != null && next.shouldMapClass(info);\n\t}\n\n\t/**\n\t * @param owner\n\t * \t\tClass the field is defined in.\n\t * @param field\n\t * \t\tField to check.\n\t *\n\t * @return {@code true} if the generator should create a new name for the field.\n\t */\n\tpublic boolean shouldMapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\tif (defaultMap)\n\t\t\treturn next == null || next.shouldMapField(owner, field);\n\t\telse\n\t\t\treturn next != null && next.shouldMapField(owner, field);\n\t}\n\n\t/**\n\t * @param owner\n\t * \t\tClass the method is defined in.\n\t * @param method\n\t * \t\tMethod to check.\n\t *\n\t * @return {@code true} if the generator should create a new name for the method.\n\t */\n\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\tif (defaultMap)\n\t\t\treturn next == null || next.shouldMapMethod(owner, method);\n\t\telse\n\t\t\treturn next != null && next.shouldMapMethod(owner, method);\n\t}\n\n\t/**\n\t * @param owner\n\t * \t\tClass the method is defined in.\n\t * @param declaringMethod\n\t * \t\tMethod the variable is defined in.\n\t * @param variable Variable to check.\n\t *\n\t * @return {@code true} if the generator should create a new name for the method.\n\t */\n\tpublic boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\tif (defaultMap)\n\t\t\treturn next == null || next.shouldMapLocalVariable(owner, declaringMethod, variable);\n\t\telse\n\t\t\treturn next != null && next.shouldMapLocalVariable(owner, declaringMethod, variable);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/AbstractNameGeneratorProvider.java",
    "content": "package software.coley.recaf.services.mapping.gen.naming;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.config.BasicConfigContainer;\n\n/**\n * Abstract base provider for a {@link NameGenerator} implementation.\n *\n * @param <T>\n * \t\tName generator implementation type.\n *\n * @author Matt Coley\n */\npublic abstract class AbstractNameGeneratorProvider<T extends NameGenerator>\n\t\textends BasicConfigContainer implements NameGeneratorProvider<T> {\n\t/**\n\t * @param id\n\t * \t\tName generator ID.\n\t */\n\tpublic AbstractNameGeneratorProvider(@Nonnull String id) {\n\t\tsuper(GROUP_ID, id);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/AlphabetNameGenerator.java",
    "content": "package software.coley.recaf.services.mapping.gen.naming;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Basic name generator using a given alphabet of characters to generate pseudo-random names with.\n * Names will always yield the same value for the same input.\n *\n * @author Matt Coley\n */\npublic class AlphabetNameGenerator implements DeconflictingNameGenerator {\n\tprivate Workspace workspace;\n\tprivate final String alphabet;\n\tprivate final int length;\n\n\t/**\n\t * @param alphabet\n\t * \t\tAlphabet to use.\n\t * @param length\n\t * \t\tLength of output names.\n\t */\n\tpublic AlphabetNameGenerator(@Nonnull String alphabet, int length) {\n\t\tthis.alphabet = alphabet;\n\t\tthis.length = length;\n\t}\n\n\t@Nonnull\n\tprivate String name(@Nullable String original) {\n\t\tint seed = original == null ? alphabet.hashCode() : original.hashCode();\n\t\tString name = StringUtil.generateName(alphabet, length, seed);\n\t\tif (workspace != null) {\n\t\t\twhile (workspace.findClass(name) != null)\n\t\t\t\tname = StringUtil.generateName(alphabet, length, seed++);\n\t\t}\n\t\treturn name;\n\t}\n\n\t@Override\n\tpublic void setWorkspace(@Nullable Workspace workspace) {\n\t\tthis.workspace = workspace;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String mapClass(@Nonnull ClassInfo info) {\n\t\tif (info.isInDefaultPackage())\n\t\t\treturn name(info.getName());\n\n\t\t// Ensure classes in the same package are kept together\n\t\treturn name(info.getPackageName()) + \"/\" + name(info.getName());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String mapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\treturn name(owner.getName() + \"#\" + field.getName());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String mapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\treturn name(owner.getName() + \"#\" + method.getName());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String mapVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\treturn name(owner.getName() + \"#\" + declaringMethod.getName() + \"#\" + variable.getIndex() +\n\t\t\t\t\"#\" + variable.getName() + \"#\" + variable.getDescriptor());\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/AlphabetNameGeneratorProvider.java",
    "content": "package software.coley.recaf.services.mapping.gen.naming;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.observables.ObservableString;\nimport software.coley.recaf.config.BasicConfigValue;\n\n/**\n * Name generator provider for {@link AlphabetNameGenerator}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class AlphabetNameGeneratorProvider extends AbstractNameGeneratorProvider<AlphabetNameGenerator> {\n\tpublic static final String ID = \"alphabet\";\n\tprivate final ObservableString alphabet = new ObservableString(\"abcdefghijklmnopqrstuvwxyz\");\n\tprivate final ObservableInteger length = new ObservableInteger(3);\n\n\t@Inject\n\tpublic AlphabetNameGeneratorProvider() {\n\t\tsuper(ID);\n\t\taddValue(new BasicConfigValue<>(\"alphabet\", String.class, alphabet));\n\t\taddValue(new BasicConfigValue<>(\"length\", int.class, length));\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic AlphabetNameGenerator createGenerator() {\n\t\treturn new AlphabetNameGenerator(alphabet.getValue(), length.getValue());\n\t}\n\n\t/**\n\t * @return Alphabet of characters to use when creating names.\n\t */\n\t@Nonnull\n\tpublic ObservableString getAlphabet() {\n\t\treturn alphabet;\n\t}\n\n\t/**\n\t * @return Length of output names.\n\t */\n\t@Nonnull\n\tpublic ObservableInteger getLength() {\n\t\treturn length;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/DeconflictingNameGenerator.java",
    "content": "package software.coley.recaf.services.mapping.gen.naming;\n\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Name generation outline that supports deconflicting cases where two items may create the same name.\n *\n * @author Matt Coley\n */\npublic interface DeconflictingNameGenerator extends NameGenerator {\n\t/**\n\t * Enables name deconfliction.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to assign, to deconflict names.\n\t */\n\tvoid setWorkspace(@Nullable Workspace workspace);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/IncrementingNameGenerator.java",
    "content": "package software.coley.recaf.services.mapping.gen.naming;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Basic name generator using a given alphabet of characters to generate pseudo-random names with.\n * Names will always yield the same value for the same input.\n *\n * @author Matt Coley\n */\npublic class IncrementingNameGenerator implements DeconflictingNameGenerator {\n\tprivate Workspace workspace;\n\tprivate long classIndex = 1;\n\tprivate long fieldIndex = 1;\n\tprivate long methodIndex = 1;\n\tprivate long varIndex = 1;\n\n\t@Nonnull\n\tprivate String nextClassName() {\n\t\treturn \"mapped/Class\" + classIndex++;\n\t}\n\n\t@Nonnull\n\tprivate String nextFieldName() {\n\t\treturn \"field\" + fieldIndex++;\n\t}\n\n\t@Nonnull\n\tprivate String nextMethodName() {\n\t\treturn \"method\" + methodIndex++;\n\t}\n\n\t@Nonnull\n\tprivate String nextVarName() {\n\t\treturn \"var\" + varIndex++;\n\t}\n\n\t@Override\n\tpublic void setWorkspace(@Nullable Workspace workspace) {\n\t\tthis.workspace = workspace;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String mapClass(@Nonnull ClassInfo info) {\n\t\tString name = nextClassName();\n\t\tif (workspace != null) {\n\t\t\twhile (workspace.findClass(name) != null)\n\t\t\t\tname = nextClassName();\n\t\t}\n\t\treturn name;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String mapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\tString name = nextFieldName();\n\t\tString descriptor = field.getDescriptor();\n\t\twhile (owner.getDeclaredField(name, descriptor) != null)\n\t\t\tname = nextFieldName();\n\t\treturn name;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String mapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\tString name = nextMethodName();\n\t\tString descriptor = method.getDescriptor();\n\t\twhile (owner.getDeclaredMethod(name, descriptor) != null)\n\t\t\tname = nextMethodName();\n\t\treturn name;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String mapVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\treturn nextVarName();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/IncrementingNameGeneratorProvider.java",
    "content": "package software.coley.recaf.services.mapping.gen.naming;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\n\n/**\n * Name generator provider for {@link IncrementingNameGenerator}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class IncrementingNameGeneratorProvider extends AbstractNameGeneratorProvider<IncrementingNameGenerator> {\n\tpublic static final String ID = \"incrementing\";\n\n\t@Inject\n\tpublic IncrementingNameGeneratorProvider() {\n\t\tsuper(ID);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IncrementingNameGenerator createGenerator() {\n\t\treturn new IncrementingNameGenerator();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/NameGenerator.java",
    "content": "package software.coley.recaf.services.mapping.gen.naming;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\n\n/**\n * Base name generation outline.\n *\n * @author Matt Coley\n */\npublic interface NameGenerator {\n\t/**\n\t * @param info\n\t * \t\tClass to rename.\n\t *\n\t * @return New class name.\n\t */\n\t@Nonnull\n\tString mapClass(@Nonnull ClassInfo info);\n\n\t/**\n\t * @param owner\n\t * \t\tClass the field is defined in.\n\t * @param field\n\t * \t\tField to rename.\n\t *\n\t * @return New field name.\n\t */\n\t@Nonnull\n\tString mapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field);\n\n\t/**\n\t * @param owner\n\t * \t\tClass the method is defined in.\n\t * @param method\n\t * \t\tMethod to rename.\n\t *\n\t * @return New method name.\n\t */\n\t@Nonnull\n\tString mapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method);\n\n\t/**\n\t * @param owner\n\t * \t\tClass the method is defined in.\n\t * @param declaringMethod\n\t * \t\tMethod the variable is defined in.\n\t * @param variable\n\t * \t\tVariable to rename.\n\t *\n\t * @return New variable name.\n\t */\n\t@Nonnull\n\tString mapVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/NameGeneratorProvider.java",
    "content": "package software.coley.recaf.services.mapping.gen.naming;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\n\n/**\n * Provider for a {@link NameGenerator} implementation.\n * Each provider should be configured as a {@link ConfigContainer}.\n *\n * @param <T>\n * \t\tName generator implementation type.\n *\n * @author Matt Coley\n * @see AbstractNameGeneratorProvider Base abstract implementation.\n */\npublic interface NameGeneratorProvider<T extends NameGenerator> extends ConfigContainer {\n\t/**\n\t * Group ID for {@link ConfigContainer#getGroup()}.\n\t */\n\tString GROUP_ID = ConfigGroups.SERVICE_MAPPING + ConfigGroups.PACKAGE_SPLIT + \"name-gen-provider\";\n\n\t/**\n\t * @return New instance of {@link NameGenerator} implementation.\n\t */\n\t@Nonnull\n\tT createGenerator();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/NameGeneratorProviders.java",
    "content": "package software.coley.recaf.services.mapping.gen.naming;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.services.Service;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Service managing available {@link NameGeneratorProvider} types.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class NameGeneratorProviders implements Service {\n\tpublic static final String SERVICE_ID = \"name-gen-providers\";\n\tprivate final NameGeneratorProvidersConfig config;\n\tprivate final Map<String, NameGeneratorProvider<?>> providerMap = new HashMap<>();\n\n\t@Inject\n\tpublic NameGeneratorProviders(@Nonnull NameGeneratorProvidersConfig config,\n\t                              @Nonnull Instance<NameGeneratorProvider<?>> providers) {\n\t\tthis.config = config;\n\n\t\tfor (NameGeneratorProvider<?> provider : providers)\n\t\t\tproviderMap.put(provider.getId(), provider);\n\t}\n\n\t/**\n\t * @param provider\n\t * \t\tNew provider to add.\n\t *\n\t * @throws IllegalStateException\n\t * \t\tWhen a provider with the given ID already is registered.z\n\t */\n\tpublic void registerProvider(@Nonnull NameGeneratorProvider<?> provider) {\n\t\tString id = provider.getId();\n\t\tif (providerMap.get(id) != null)\n\t\t\tthrow new IllegalStateException(\"A provider with the given id '\" + id + \"' already exists!\");\n\t\tproviderMap.put(id, provider);\n\t}\n\n\t/**\n\t * @return Map of available providers.\n\t */\n\t@Nonnull\n\tpublic Map<String, NameGeneratorProvider<?>> getProviders() {\n\t\treturn Collections.unmodifiableMap(providerMap);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic NameGeneratorProvidersConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/NameGeneratorProvidersConfig.java",
    "content": "package software.coley.recaf.services.mapping.gen.naming;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link NameGeneratorProviders}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class NameGeneratorProvidersConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic NameGeneratorProvidersConfig() {\n\t\tsuper(ConfigGroups.SERVICE_MAPPING, NameGeneratorProviders.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/phantom/GeneratedPhantomWorkspaceResource.java",
    "content": "package software.coley.recaf.services.phantom;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.VersionedJvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.BasicWorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder;\n\nimport java.util.Map;\nimport java.util.NavigableMap;\n\n/**\n * A special sub-type of a workspace resource indicating it originates from {@link PhantomGenerator}.\n *\n * @author Matt Coley\n */\npublic class GeneratedPhantomWorkspaceResource extends BasicWorkspaceResource {\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic GeneratedPhantomWorkspaceResource(@Nonnull WorkspaceResourceBuilder builder) {\n\t\tsuper(builder);\n\t}\n\n\t/**\n\t * @param jvmClassBundle\n\t * \t\tImmediate classes.\n\t * @param fileBundle\n\t * \t\tImmediate files.\n\t * @param versionedJvmClassBundles\n\t * \t\tVersion specific classes.\n\t * @param androidClassBundles\n\t * \t\tAndroid bundles.\n\t * @param embeddedResources\n\t * \t\tEmbedded resources <i>(like JAR in JAR)</i>\n\t * @param containingResource\n\t * \t\tParent resource <i>(If we are the JAR within a JAR)</i>.\n\t */\n\tpublic GeneratedPhantomWorkspaceResource(JvmClassBundle jvmClassBundle,\n\t\t\t\t\t\t\t\t\t\t\t FileBundle fileBundle,\n\t\t\t\t\t\t\t\t\t\t\t NavigableMap<Integer, VersionedJvmClassBundle> versionedJvmClassBundles,\n\t\t\t\t\t\t\t\t\t\t\t Map<String, AndroidClassBundle> androidClassBundles,\n\t\t\t\t\t\t\t\t\t\t\t Map<String, WorkspaceFileResource> embeddedResources,\n\t\t\t\t\t\t\t\t\t\t\t WorkspaceResource containingResource) {\n\t\tsuper(jvmClassBundle, fileBundle, versionedJvmClassBundles, androidClassBundles, embeddedResources, containingResource);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/phantom/JPhantomGenerator.java",
    "content": "package software.coley.recaf.services.phantom;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.clyze.jphantom.ClassMembers;\nimport org.clyze.jphantom.JPhantom;\nimport org.clyze.jphantom.Options;\nimport org.clyze.jphantom.Phantoms;\nimport org.clyze.jphantom.access.ClassAccessStateMachine;\nimport org.clyze.jphantom.access.FieldAccessStateMachine;\nimport org.clyze.jphantom.access.MethodAccessStateMachine;\nimport org.clyze.jphantom.adapters.ClassPhantomExtractor;\nimport org.clyze.jphantom.hier.ClassHierarchy;\nimport org.clyze.jphantom.hier.IncrementalClassHierarchy;\nimport org.objectweb.asm.*;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.slf4j.Logger;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.cdi.EagerInitialization;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.util.ReflectUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder;\n\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * An implementation of {@link PhantomGenerator} using {@link JPhantom}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\n@EagerInitialization\npublic class JPhantomGenerator implements PhantomGenerator {\n\tpublic static final String SERVICE_ID = \"jphantom-generator\";\n\tprivate static final Logger logger = Logging.get(JPhantomGenerator.class);\n\tprivate final JPhantomGeneratorConfig config;\n\n\t@Inject\n\tpublic JPhantomGenerator(@Nonnull JPhantomGeneratorConfig config, @Nonnull WorkspaceManager workspaceManager) {\n\t\tthis.config = config;\n\n\t\t// When new workspaces are opened, generate & append the generated phantoms if the config is enabled.\n\t\tworkspaceManager.addWorkspaceOpenListener(workspace -> {\n\t\t\tif (!config.getGenerateWorkspacePhantoms().getValue())\n\t\t\t\treturn;\n\t\t\tCompletableFuture.supplyAsync(() -> {\n\t\t\t\ttry {\n\t\t\t\t\treturn createPhantomsForWorkspace(workspace);\n\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t// Workspace-level phantoms are useful for some graphing operations and can be used to enhance compile tasks.\n\t\t\t\t\t// Though, if workspace-level phantoms are not made the compiler will create class-level phantoms anyways,\n\t\t\t\t\t// so this failing is not a big deal. Happens fairly regularly in obfuscated inputs.\n\t\t\t\t\tlogger.warn(\"Failed to generate phantoms for workspace. Some graphing operations may be slightly less effective.\");\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t}).thenAccept(generatedResource -> {\n\t\t\t\tif (generatedResource != null)\n\t\t\t\t\tworkspace.addSupportingResource(generatedResource);\n\t\t\t});\n\t\t});\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic GeneratedPhantomWorkspaceResource createPhantomsForWorkspace(@Nonnull Workspace workspace) throws PhantomGenerationException {\n\t\t// Extract all JVM classes from workspace\n\t\tMap<String, JvmClassInfo> classMap = workspace.getPrimaryResource().jvmAllClassBundleStreamRecursive()\n\t\t\t\t.flatMap(Bundle::stream)\n\t\t\t\t.collect(Collectors.toMap(Info::getName, Function.identity()));\n\n\t\t// Generate phantoms for them and wrap into resource\n\t\ttry {\n\t\t\tMap<String, byte[]> generated = generate(workspace, classMap);\n\t\t\treturn wrap(generated);\n\t\t} catch (IOException ex) {\n\t\t\tthrow new PhantomGenerationException(ex, \"JPhantom encountered a problem\");\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic GeneratedPhantomWorkspaceResource createPhantomsForClasses(@Nonnull Workspace workspace, @Nonnull Collection<JvmClassInfo> classes)\n\t\t\tthrows PhantomGenerationException {\n\t\t// Convert collection to map\n\t\tMap<String, JvmClassInfo> classMap = classes.stream()\n\t\t\t\t.collect(Collectors.toMap(Info::getName, Function.identity(), (a, b) -> a));\n\n\t\t// Generate phantoms for them and wrap into resource\n\t\ttry {\n\t\t\tMap<String, byte[]> generated = generate(workspace, classMap);\n\t\t\treturn wrap(generated);\n\t\t} catch (IOException ex) {\n\t\t\tthrow new PhantomGenerationException(ex, \"JPhantom encountered a problem\");\n\t\t}\n\t}\n\n\t/**\n\t * @param generated\n\t * \t\tMap of generated classes.\n\t *\n\t * @return Wrapping resource.\n\t */\n\t@Nonnull\n\tpublic static GeneratedPhantomWorkspaceResource wrap(@Nonnull Map<String, byte[]> generated) {\n\t\t// Wrap into resource\n\t\tBasicJvmClassBundle bundle = new BasicJvmClassBundle();\n\t\tgenerated.forEach((name, phantom) -> {\n\t\t\tJvmClassInfo phantomClassInfo = new JvmClassInfoBuilder(phantom).build();\n\t\t\tbundle.initialPut(phantomClassInfo);\n\t\t});\n\t\tbundle.markInitialState();\n\t\treturn new GeneratedPhantomWorkspaceResource(new WorkspaceResourceBuilder()\n\t\t\t\t.withJvmClassBundle(bundle));\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to check for class existence within.\n\t * @param inputMap\n\t * \t\tInput map of classes to create phantoms for.\n\t *\n\t * @return Map of phantom classes.\n\t *\n\t * @throws IOException\n\t * \t\tWhen {@link JPhantom#run()} fails.\n\t */\n\t@Nonnull\n\tpublic static Map<String, byte[]> generate(@Nonnull Workspace workspace,\n\t                                           @Nonnull Map<String, JvmClassInfo> inputMap) throws IOException {\n\t\tMap<String, byte[]> out = new HashMap<>();\n\n\t\t// Write the parameter passed classes to a temp jar\n\t\tMap<String, byte[]> classMap = new HashMap<>();\n\t\tMap<Type, ClassNode> nodes = new HashMap<>();\n\t\tinputMap.forEach((name, info) -> {\n\t\t\tClassReader cr = info.getClassReader();\n\t\t\tClassNode node = new ClassNode();\n\t\t\tcr.accept(node, ClassReader.SKIP_FRAMES);\n\t\t\tclassMap.put(name + \".class\", info.getBytecode());\n\t\t\tnodes.put(Type.getObjectType(node.name), node);\n\t\t});\n\n\t\t// Read into JPhantom\n\t\tOptions.V().setSoftFail(true);\n\t\tOptions.V().setJavaVersion(8);\n\t\tClassHierarchy hierarchy = createHierarchy(classMap);\n\t\tClassMembers members = createMembers(classMap, hierarchy);\n\t\tclassMap.forEach((name, raw) -> {\n\t\t\tif (name.contains(\"$\"))\n\t\t\t\treturn;\n\t\t\ttry {\n\t\t\t\tClassReader cr = new ClassReader(raw);\n\t\t\t\tcr.accept(new ClassPhantomExtractor(hierarchy, members), 0);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.debug(\"Phantom extraction failed: {}\", name, t);\n\t\t\t}\n\t\t});\n\n\t\t// Remove duplicate constraints for faster analysis\n\t\tSet<String> existingConstraints = new HashSet<>();\n\t\tClassAccessStateMachine.v().getConstraints().removeIf(c -> !existingConstraints.add(c.toString()));\n\n\t\t// Execute and populate the current resource with generated classes\n\t\ttry {\n\t\t\tJPhantom phantom = new JPhantom(nodes, hierarchy, members);\n\t\t\tphantom.run();\n\t\t\tphantom.getGenerated().forEach((k, v) -> {\n\t\t\t\t// Only put items not found in the workspace.\n\t\t\t\t// We may call the generator on a small scope, and thus create phantoms of classes that\n\t\t\t\t// exist in the workspace, but were not in the provided scope.\n\t\t\t\tString name = k.getInternalName();\n\t\t\t\tif (workspace.findJvmClass(name) == null)\n\t\t\t\t\tout.put(name, decorate(v));\n\t\t\t});\n\t\t\tlogger.debug(\"Phantom analysis complete, generated {} classes\", out.size());\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Phantom analysis encountered an exception.\", t);\n\t\t} finally {\n\t\t\t// Cleanup\n\t\t\tPhantoms.refresh();\n\t\t\tPhantoms.V().getLookupTable().clear();\n\t\t\tClassAccessStateMachine.refresh();\n\t\t\tFieldAccessStateMachine.refresh();\n\t\t\tMethodAccessStateMachine.refresh();\n\t\t}\n\t\treturn out;\n\t}\n\n\t/**\n\t * @param classMap\n\t * \t\tMap to pull classes from.\n\t * @param hierarchy\n\t * \t\tHierarchy to pass to {@link ClassMembers} constructor.\n\t *\n\t * @return Members instance.\n\t */\n\t@Nonnull\n\tpublic static ClassMembers createMembers(@Nonnull Map<String, byte[]> classMap, @Nonnull ClassHierarchy hierarchy) {\n\t\tClass<?>[] argTypes = new Class[]{ClassHierarchy.class};\n\t\tObject[] argVals = new Object[]{hierarchy};\n\t\tClassMembers repo = ReflectUtil.quietNew(ClassMembers.class, argTypes, argVals);\n\t\ttry {\n\t\t\tnew ClassReader(\"java/lang/Object\").accept(repo.new Feeder(), 0);\n\t\t} catch (IOException ex) {\n\t\t\tlogger.error(\"Failed to get initial reader ClassMembers, could not lookup 'java/lang/Object'\");\n\t\t\tthrow new IllegalStateException();\n\t\t}\n\t\tfor (Map.Entry<String, byte[]> e : classMap.entrySet()) {\n\t\t\ttry {\n\t\t\t\tnew ClassReader(e.getValue()).accept(repo.new Feeder(), 0);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.debug(\"Could not supply {} to ClassMembers feeder\", e.getKey(), t);\n\t\t\t}\n\t\t}\n\t\treturn repo;\n\t}\n\n\t/**\n\t * @param classMap\n\t * \t\tMap to pull classes from.\n\t *\n\t * @return Class hierarchy.\n\t */\n\t@Nonnull\n\tpublic static ClassHierarchy createHierarchy(@Nonnull Map<String, byte[]> classMap) {\n\t\tClassHierarchy hierarchy = new IncrementalClassHierarchy();\n\t\tfor (Map.Entry<String, byte[]> e : classMap.entrySet()) {\n\t\t\ttry {\n\t\t\t\tClassReader reader = new ClassReader(e.getValue());\n\t\t\t\tString[] ifaceNames = reader.getInterfaces();\n\t\t\t\tType clazz = Type.getObjectType(reader.getClassName());\n\t\t\t\tType superclass = reader.getSuperName() == null ?\n\t\t\t\t\t\tType.getObjectType(\"java/lang/Object\") : Type.getObjectType(reader.getSuperName());\n\t\t\t\tType[] ifaces = new Type[ifaceNames.length];\n\t\t\t\tfor (int i = 0; i < ifaces.length; i++)\n\t\t\t\t\tifaces[i] = Type.getObjectType(ifaceNames[i]);\n\n\t\t\t\t// Add type to hierarchy\n\t\t\t\tboolean isInterface = (reader.getAccess() & Opcodes.ACC_INTERFACE) != 0;\n\t\t\t\tif (isInterface) {\n\t\t\t\t\thierarchy.addInterface(clazz, ifaces);\n\t\t\t\t} else {\n\t\t\t\t\thierarchy.addClass(clazz, superclass, ifaces);\n\t\t\t\t}\n\t\t\t} catch (Exception ex) {\n\t\t\t\tlogger.error(\"JPhantom: Hierarchy failure for: {}\", e.getKey(), ex);\n\t\t\t}\n\t\t}\n\t\treturn hierarchy;\n\t}\n\n\n\t/**\n\t * Adds a note to the given class that it has been auto-generated.\n\t *\n\t * @param generated\n\t * \t\tInput generated JPhantom class.\n\t *\n\t * @return Modified class that clearly indicates it is generated.\n\t */\n\t@Nonnull\n\tprivate static byte[] decorate(@Nonnull byte[] generated) {\n\t\tClassReader reader = new ClassReader(generated);\n\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\tClassVisitor annotationInserter = new ClassVisitor(RecafConstants.getAsmVersion(), writer) {\n\t\t\t@Override\n\t\t\tpublic void visitEnd() {\n\t\t\t\tvisitAnnotation(\"LAutoGenerated;\", true)\n\t\t\t\t\t\t.visit(\"msg\", \"Recaf/JPhantom automatically generated this class\");\n\t\t\t\tsuper.visitEnd();\n\t\t\t}\n\t\t};\n\t\treader.accept(annotationInserter, ClassReader.SKIP_FRAMES);\n\t\treturn writer.toByteArray();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic JPhantomGeneratorConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/phantom/JPhantomGeneratorConfig.java",
    "content": "package software.coley.recaf.services.phantom;\n\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link JPhantomGenerator}\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class JPhantomGeneratorConfig extends BasicConfigContainer implements ServiceConfig {\n\tprivate final ObservableBoolean generateWorkspacePhantoms = new ObservableBoolean(false);\n\n\t@Inject\n\tpublic JPhantomGeneratorConfig() {\n\t\tsuper(ConfigGroups.SERVICE_ANALYSIS, JPhantomGenerator.SERVICE_ID + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"generate-workspace-phantoms\", boolean.class, generateWorkspacePhantoms));\n\t}\n\n\t/**\n\t * @return {@code true} to create and register {@link GeneratedPhantomWorkspaceResource} to newly opened workspaces.\n\t */\n\t@Nullable\n\tpublic ObservableBoolean getGenerateWorkspacePhantoms() {\n\t\treturn generateWorkspacePhantoms;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/phantom/PhantomGenerationException.java",
    "content": "package software.coley.recaf.services.phantom;\n\n/**\n * Exception thrown when {@link PhantomGenerator} operations fail.\n *\n * @author Matt Coley\n */\npublic class PhantomGenerationException extends Exception {\n\t/**\n\t * @param cause\n\t * \t\tRoot cause of the failure.\n\t * @param message\n\t * \t\tAdditional detail message.\n\t */\n\tpublic PhantomGenerationException(Throwable cause, String message) {\n\t\tsuper(message, cause);\n\t}\n\n\t/**\n\t * @param cause\n\t * \t\tRoot cause of the failure.\n\t */\n\tpublic PhantomGenerationException(Throwable cause) {\n\t\tsuper(cause);\n\t}\n\n\t/**\n\t * @param message\n\t * \t\tAdditional detail message.\n\t */\n\tpublic PhantomGenerationException(String message) {\n\t\tsuper(message);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/phantom/PhantomGenerator.java",
    "content": "package software.coley.recaf.services.phantom;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Collection;\n\n/**\n * Outline for phantom class generation targeting different levels of scope.\n *\n * @author Matt Coley\n */\npublic interface PhantomGenerator extends Service {\n\t/**\n\t * Generates a resource containing the phantom classes necessary to compile the JVM classes in the\n\t * primary resource of the given workspace.\n\t * <p>\n\t * Do note that this is a largely more computationally complex task than creating phantoms for\n\t * {@link #createPhantomsForClasses(Workspace, Collection) one or a few classes at a time}.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to scan for classes with missing references.\n\t *\n\t * @return Resource containing generated phantoms, targeting missing references across all classes.\n\t *\n\t * @throws PhantomGenerationException\n\t * \t\tWhen generating phantoms failed.\n\t */\n\t@Nonnull\n\tGeneratedPhantomWorkspaceResource createPhantomsForWorkspace(@Nonnull Workspace workspace)\n\t\t\tthrows PhantomGenerationException;\n\n\t/**\n\t * Generates a resource containing the phantom classes necessary to compile the given classes.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to pull class information from.\n\t * \t\tIf a class that is required for compilation of a given class is in the workspace, then no phantom\n\t * \t\tfor it will be generated in the resulting created resource.\n\t * @param classes\n\t * \t\tClasses to scan for missing references.\n\t *\n\t * @return Resource containing generated phantoms, targeting only the specified classes.\n\t *\n\t * @throws PhantomGenerationException\n\t * \t\tWhen generating phantoms failed.\n\t */\n\t@Nonnull\n\tGeneratedPhantomWorkspaceResource createPhantomsForClasses(@Nonnull Workspace workspace, @Nonnull Collection<JvmClassInfo> classes)\n\t\t\tthrows PhantomGenerationException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/AllocationException.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Wrapper exception for any potential error thrown by the implementation logic of a {@link ClassAllocator}\n *\n * @author Matt Coley\n */\npublic class AllocationException extends Exception {\n\tprivate final Class<?> type;\n\n\t/**\n\t * @param type\n\t * \t\tType that failed to be allocated.\n\t * @param cause\n\t * \t\tReason for allocation failure.\n\t */\n\tpublic AllocationException(@Nonnull Class<?> type, @Nonnull Throwable cause) {\n\t\tsuper(cause);\n\t\tthis.type = type;\n\t}\n\n\t/**\n\t * @return Type that failed to be allocated.\n\t */\n\t@Nonnull\n\tpublic Class<?> getType() {\n\t\treturn type;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/BasicPluginManager.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.cdi.EagerInitialization;\nimport software.coley.recaf.plugin.*;\nimport software.coley.recaf.services.plugin.discovery.DiscoveredPluginSource;\nimport software.coley.recaf.services.plugin.discovery.PluginDiscoverer;\nimport software.coley.recaf.services.plugin.zip.ZipPluginLoader;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * Basic implementation of {@link PluginManager}.\n *\n * @author xDark\n */\n@ApplicationScoped\n@EagerInitialization\npublic class BasicPluginManager implements PluginManager {\n\tprivate final List<PluginLoader> loaders = new ArrayList<>();\n\tprivate final PluginGraph mainGraph;\n\tprivate final ClassAllocator classAllocator;\n\tprivate final PluginManagerConfig config;\n\n\t@Inject\n\tpublic BasicPluginManager(PluginManagerConfig config, CdiClassAllocator classAllocator) {\n\t\tthis.classAllocator = classAllocator;\n\t\tthis.config = config;\n\t\tmainGraph = new PluginGraph(classAllocator);\n\n\t\t// Add ZIP/JAR loading\n\t\tregisterLoader(new ZipPluginLoader());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassAllocator getAllocator() {\n\t\treturn classAllocator;\n\t}\n\n\t@Override\n\t@Nullable\n\t@SuppressWarnings(\"unchecked\")\n\tpublic <T extends Plugin> PluginContainer<T> getPlugin(@Nonnull String id) {\n\t\treturn (PluginContainer<T>) mainGraph.getContainer(id);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<PluginContainer<?>> getPlugins() {\n\t\treturn mainGraph.plugins();\n\t}\n\n\t@Override\n\tpublic void registerLoader(@Nonnull PluginLoader loader) {\n\t\tloaders.add(loader);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<PluginContainer<?>> loadPlugins(@Nonnull PluginDiscoverer discoverer) throws PluginException {\n\t\tList<DiscoveredPluginSource> discoveredPlugins = discoverer.findSources();\n\t\tList<PluginLoader> loaders = this.loaders;\n\t\tList<PreparedPlugin> prepared = new ArrayList<>(discoveredPlugins.size());\n\t\tfor (DiscoveredPluginSource plugin : discoveredPlugins) {\n\t\t\tfor (PluginLoader loader : loaders) {\n\t\t\t\tPreparedPlugin preparedPlugin = loader.prepare(plugin.source());\n\t\t\t\tif (preparedPlugin == null)\n\t\t\t\t\tcontinue;\n\t\t\t\tprepared.add(preparedPlugin);\n\t\t\t}\n\t\t}\n\t\treturn mainGraph.apply(prepared);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PluginUnloader unloaderFor(@Nonnull String id) {\n\t\treturn mainGraph.unloaderFor(id);\n\t}\n\n\t@Override\n\tpublic boolean isPluginLoaded(@Nonnull String id) {\n\t\treturn mainGraph.getContainer(id) != null;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PluginManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/CdiClassAllocator.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.context.spi.CreationalContext;\nimport jakarta.enterprise.inject.spi.*;\nimport jakarta.inject.Inject;\n\nimport java.util.IdentityHashMap;\nimport java.util.Map;\n\n/**\n * Allocator instance that ties into the CDI container.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class CdiClassAllocator implements ClassAllocator {\n\tprivate final Map<Class<?>, Bean<?>> classBeanMap = new IdentityHashMap<>();\n\tprivate final BeanManager beanManager;\n\n\t@Inject\n\tpublic CdiClassAllocator(@Nonnull BeanManager beanManager) {\n\t\tthis.beanManager = beanManager;\n\t}\n\n\t@Nonnull\n\t@Override\n\t@SuppressWarnings(\"unchecked\")\n\tpublic <T> T instance(@Nonnull Class<T> cls) throws AllocationException {\n\t\ttry {\n\t\t\t// Create bean\n\t\t\tBean<T> bean = (Bean<T>) classBeanMap.computeIfAbsent(cls, c -> {\n\t\t\t\tAnnotatedType<T> annotatedClass = beanManager.createAnnotatedType(cls);\n\t\t\t\tBeanAttributes<T> attributes = beanManager.createBeanAttributes(annotatedClass);\n\t\t\t\tInjectionTargetFactory<T> factory = beanManager.getInjectionTargetFactory(annotatedClass);\n\t\t\t\treturn beanManager.createBean(attributes, cls, factory);\n\t\t\t});\n\t\t\tCreationalContext<T> creationalContext = beanManager.createCreationalContext(bean);\n\n\t\t\t// Allocate instance of bean\n\t\t\treturn bean.create(creationalContext);\n\t\t} catch (Throwable t) {\n\t\t\tthrow new AllocationException(cls, t);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/ClassAllocator.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Responsible for creating new class instances from a {@link Class} reference.\n *\n * @author Matt Coley\n */\npublic interface ClassAllocator {\n\t/**\n\t * @param cls\n\t * \t\tClass to allocate an instance of.\n\t * @param <T>\n\t * \t\tType of class.\n\t *\n\t * @return Instance of T type.\n\t *\n\t * @throws AllocationException\n\t * \t\tWhen any error prevents a new instance from being provided.\n\t */\n\t@Nonnull\n\t<T> T instance(@Nonnull Class<T> cls) throws AllocationException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/LoadedPlugin.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Model of a loaded plugin and its dependence for {@link PluginGraph}.\n *\n * @author xDark\n */\nfinal class LoadedPlugin {\n\tprivate final Set<LoadedPlugin> dependencies = HashSet.newHashSet(4);\n\tprivate final PluginContainerImpl<?> container;\n\n\tLoadedPlugin(@Nonnull PluginContainerImpl<?> container) {\n\t\tthis.container = container;\n\t}\n\n\t/**\n\t * @return Mutable set of dependencies this plugin relies on.\n\t */\n\t@Nonnull\n\tpublic Set<LoadedPlugin> getDependencies() {\n\t\treturn dependencies;\n\t}\n\n\t/**\n\t * @return Container the loaded plugin belongs to.\n\t */\n\t@Nonnull\n\tpublic PluginContainerImpl<?> getContainer() {\n\t\treturn container;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"LoadedPlugin{\" + container.info().id() + '}';\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PluginClassLoader.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.io.ByteSource;\n\n/**\n * Plugin class loader interface.\n */\npublic interface PluginClassLoader {\n\n\t/**\n\t * @param name\n\t * \t\tResource path.\n\t *\n\t * @return Resource source or {@code null} if not found.\n\t */\n\t@Nullable\n\tByteSource lookupResource(@Nonnull String name);\n\n\t/**\n\t * @param name\n\t * \t\tClass name.\n\t *\n\t * @return Class.\n\t *\n\t * @throws ClassNotFoundException\n\t * \t\tIf class was not found.\n\t */\n\t@Nonnull\n\tClass<?> lookupClass(@Nonnull String name) throws ClassNotFoundException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PluginClassLoaderImpl.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.io.ByteSource;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.*;\n\n/**\n * Classloader for plugin content.\n *\n * @author xDark\n */\nfinal class PluginClassLoaderImpl extends ClassLoader implements PluginClassLoader {\n\tprivate final PluginGraph graph;\n\tprivate final PluginSource source;\n\tprivate final String id;\n\n\tPluginClassLoaderImpl(@Nonnull ClassLoader classLoader, @Nonnull PluginGraph graph, @Nonnull PluginSource source, @Nonnull String id) {\n\t\tsuper(classLoader);\n\t\tthis.graph = graph;\n\t\tthis.source = source;\n\t\tthis.id = id;\n\t}\n\n\t@Override\n\tprotected URL findResource(String name) {\n\t\tByteSource source = this.source.findResource(name);\n\t\tif (source == null) {\n\t\t\treturn null;\n\t\t}\n\t\ttry {\n\t\t\tURI uri = new URI(\"recaf\", \"/\", name);\n\t\t\treturn URL.of(uri, new URLStreamHandler() {\n\t\t\t\t@Override\n\t\t\t\tprotected URLConnection openConnection(URL u) {\n\t\t\t\t\treturn new URLConnection(u) {\n\t\t\t\t\t\tInputStream in;\n\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tpublic void connect() {\n\t\t\t\t\t\t\t// no-op\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tpublic InputStream getInputStream() throws IOException {\n\t\t\t\t\t\t\tInputStream in = this.in;\n\t\t\t\t\t\t\tif (in == null) {\n\t\t\t\t\t\t\t\tin = source.openStream();\n\t\t\t\t\t\t\t\tthis.in = in;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn in;\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (MalformedURLException | URISyntaxException ex) {\n\t\t\tthrow new IllegalStateException(ex);\n\t\t}\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic ByteSource lookupResource(@Nonnull String name) {\n\t\treturn source.findResource(name);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Class<?> lookupClass(@Nonnull String name) throws ClassNotFoundException {\n\t\tClass<?> cls = lookupClassImpl(name);\n\t\tif (cls == null) {\n\t\t\tthrow new ClassNotFoundException(name);\n\t\t}\n\t\treturn cls;\n\t}\n\n\t@Override\n\tprotected Class<?> findClass(String name) throws ClassNotFoundException {\n\t\tClass<?> cls = lookupClassImpl(name);\n\t\tif (cls != null)\n\t\t\treturn cls;\n\t\tvar dependencyLoaders = graph.getDependencyClassloaders(id);\n\t\twhile (dependencyLoaders.hasNext()) {\n\t\t\tif ((cls = dependencyLoaders.next().findClass(name)) != null)\n\t\t\t\treturn cls;\n\t\t}\n\t\tthrow new ClassNotFoundException(name);\n\t}\n\n\t@Nullable\n\tClass<?> lookupClassImpl(@Nonnull String name) throws ClassNotFoundException {\n\t\tClass<?> cls = findLoadedClass(name);\n\t\tif (cls != null)\n\t\t\treturn cls;\n\t\tByteSource classBytes = source.findResource(name.replace('.', '/') + \".class\");\n\t\tif (classBytes != null) {\n\t\t\tbyte[] bytes;\n\t\t\ttry {\n\t\t\t\tbytes = classBytes.readAll();\n\t\t\t} catch (IOException ex) {\n\t\t\t\tthrow new ClassNotFoundException(name, ex);\n\t\t\t}\n\t\t\treturn defineClass(name, bytes, 0, bytes.length);\n\t\t}\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PluginContainer.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.plugin.Plugin;\n\n/**\n * Object that holds reference to a plugin and it's information.\n *\n * @param <P>\n * \t\tPlugin instance type.\n *\n * @author xDark\n * @see PluginInfo\n * @see PluginLoader\n */\npublic interface PluginContainer<P extends Plugin> {\n\t/**\n\t * @return Plugin information.\n\t */\n\t@Nonnull\n\tPluginInfo info();\n\n\t/**\n\t * @return Plugin instance.\n\t */\n\t@Nonnull\n\tP plugin();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PluginContainerImpl.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.plugin.Plugin;\n\n/**\n * Plugin container implementation.\n *\n * @param <P>\n * \t\tPlugin instance type.\n *\n * @author xDark\n */\nfinal class PluginContainerImpl<P extends Plugin> implements PluginContainer<P> {\n\tfinal PreparedPlugin preparedPlugin;\n\tfinal PluginClassLoader classLoader;\n\tP plugin;\n\n\tPluginContainerImpl(@Nonnull PreparedPlugin preparedPlugin, @Nonnull PluginClassLoader classLoader) {\n\t\tthis.preparedPlugin = preparedPlugin;\n\t\tthis.classLoader = classLoader;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PluginInfo info() {\n\t\treturn preparedPlugin.info();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic P plugin() {\n\t\tP plugin = this.plugin;\n\t\tif (plugin == null) {\n\t\t\tthrow new IllegalStateException(\"Uninitialized plugin\");\n\t\t}\n\t\treturn plugin;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PluginException.java",
    "content": "package software.coley.recaf.services.plugin;\n\n/**\n * Exception thrown when action involving plugins fail.\n *\n * @author xDark\n */\npublic final class PluginException extends Exception {\n\t/**\n\t * Constructs a new exception.\n\t */\n\tpublic PluginException() {\n\t}\n\n\t/**\n\t * Constructs a new exception.\n\t *\n\t * @param message\n\t * \t\tThe detail message.\n\t */\n\tpublic PluginException(String message) {\n\t\tsuper(message);\n\t}\n\n\t/**\n\t * Constructs a new exception.\n\t *\n\t * @param message\n\t * \t\tThe detail message.\n\t * @param cause\n\t * \t\tThe cause of the exception.\n\t */\n\tpublic PluginException(String message, Throwable cause) {\n\t\tsuper(message, cause);\n\t}\n\n\t/**\n\t * Constructs a new exception.\n\t *\n\t * @param cause\n\t * \t\tThe cause of the exception.\n\t */\n\tpublic PluginException(Throwable cause) {\n\t\tsuper(cause);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PluginGraph.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport com.google.common.collect.Collections2;\nimport com.google.common.collect.Iterators;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.plugin.*;\n\nimport java.util.*;\nimport java.util.stream.Stream;\n\n/**\n * Plugin dependency graph.\n *\n * @author xDark\n */\nfinal class PluginGraph {\n\tfinal Map<String, LoadedPlugin> plugins = HashMap.newHashMap(16);\n\tprivate final ClassAllocator classAllocator;\n\n\t/**\n\t * @param classAllocator\n\t * \t\tAllocator to construct plugin instances with.\n\t */\n\tPluginGraph(@Nonnull ClassAllocator classAllocator) {\n\t\tthis.classAllocator = classAllocator;\n\t}\n\n\t/**\n\t * Primary plugin load action.\n\t *\n\t * @param preparedPlugins\n\t * \t\tIntermediate plugin data to load.\n\t *\n\t * @return Collection of loaded plugin containers.\n\t *\n\t * @throws PluginException\n\t * \t\tIf the plugins could not be loaded for any reason.\n\t */\n\t@Nonnull\n\tCollection<PluginContainer<?>> apply(@Nonnull List<PreparedPlugin> preparedPlugins) throws PluginException {\n\t\tMap<String, LoadedPlugin> temp = LinkedHashMap.newLinkedHashMap(preparedPlugins.size());\n\t\tvar plugins = this.plugins;\n\t\tfor (var preparedPlugin : preparedPlugins) {\n\t\t\tString id = preparedPlugin.info().id();\n\t\t\tif (plugins.containsKey(id)) {\n\t\t\t\tthrow new PluginException(\"Plugin %s is already loaded\".formatted(id));\n\t\t\t}\n\t\t\tvar threadContextClassLoader = Thread.currentThread().getContextClassLoader();\n\t\t\tvar parentLoader = threadContextClassLoader != null ? threadContextClassLoader : ClassLoader.getSystemClassLoader();\n\t\t\tvar classLoader = new PluginClassLoaderImpl(parentLoader, this, preparedPlugin.pluginSource(), id);\n\t\t\tLoadedPlugin loadedPlugin = new LoadedPlugin(new PluginContainerImpl<>(preparedPlugin, classLoader));\n\n\t\t\tif (temp.putIfAbsent(id, loadedPlugin) != null) {\n\t\t\t\tthrow new PluginException(\"Duplicate plugin %s\".formatted(id));\n\t\t\t}\n\t\t}\n\t\tfor (LoadedPlugin plugin : temp.values()) {\n\t\t\tPluginInfo info = plugin.getContainer().info();\n\t\t\tfor (String dependencyId : info.dependencies()) {\n\t\t\t\tLoadedPlugin dep = temp.get(dependencyId);\n\t\t\t\tif (dep == null) {\n\t\t\t\t\tdep = plugins.get(dependencyId);\n\t\t\t\t}\n\t\t\t\tif (dep == null) {\n\t\t\t\t\tthrow new PluginException(\"Plugin %s is missing dependency %s\".formatted(info.id(), dependencyId));\n\t\t\t\t}\n\t\t\t\tplugin.getDependencies().add(dep);\n\t\t\t}\n\t\t\tfor (String dependencyId : info.softDependencies()) {\n\t\t\t\tLoadedPlugin dep = temp.get(dependencyId);\n\t\t\t\tif (dep == null && (dep = plugins.get(dependencyId)) == null) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tplugin.getDependencies().add(dep);\n\t\t\t}\n\t\t}\n\t\tfor (LoadedPlugin loadedPlugin : temp.values()) {\n\t\t\ttry {\n\t\t\t\tenable(loadedPlugin);\n\t\t\t} catch (PluginException ex) {\n\t\t\t\tfor (LoadedPlugin pl : temp.values()) {\n\t\t\t\t\tPluginContainerImpl<?> container = pl.getContainer();\n\t\t\t\t\ttry {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tPlugin maybeEnabled = container.plugin;\n\t\t\t\t\t\t\tif (maybeEnabled != null) {\n\t\t\t\t\t\t\t\tmaybeEnabled.onDisable();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\tcontainer.preparedPlugin.reject();\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (Exception ex1) {\n\t\t\t\t\t\tex.addSuppressed(ex1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthrow ex;\n\t\t\t}\n\t\t}\n\t\tplugins.putAll(temp);\n\t\treturn Collections2.transform(temp.values(), input -> input.getContainer());\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tPlugin identifier.\n\t *\n\t * @return Plugin unload action.\n\t */\n\t@Nonnull\n\tPluginUnloader unloaderFor(@Nonnull String id) {\n\t\tLoadedPlugin plugin = plugins.get(id);\n\t\tif (plugin == null) {\n\t\t\tthrow new IllegalStateException(\"Plugin %s is not loaded\".formatted(id));\n\t\t}\n\t\tMap<LoadedPlugin, Set<LoadedPlugin>> dependants = HashMap.newHashMap(8);\n\t\tcollectDependants(plugin, dependants);\n\t\treturn new PluginUnloader() {\n\t\t\t@Override\n\t\t\tpublic void commit() throws PluginException {\n\t\t\t\tPluginException ex = unload(plugin, dependants);\n\t\t\t\tif (ex != null)\n\t\t\t\t\tthrow ex;\n\t\t\t}\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic PluginInfo unloadingPlugin() {\n\t\t\t\treturn plugin.getContainer().info();\n\t\t\t}\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic Stream<PluginInfo> dependants() {\n\t\t\t\treturn dependants.values()\n\t\t\t\t\t\t.stream()\n\t\t\t\t\t\t.flatMap(Collection::stream)\n\t\t\t\t\t\t.map(plugin -> plugin.getContainer().info());\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Attempts to unload the given plugin, along with its dependants.\n\t *\n\t * @param plugin\n\t * \t\tPlugin to unload.\n\t * @param dependants\n\t * \t\tMap of plugin dependents.\n\t *\n\t * @return Exception to be thrown if the plugin could not be unloaded.\n\t */\n\t@Nullable\n\tprivate PluginException unload(@Nonnull LoadedPlugin plugin, @Nonnull Map<LoadedPlugin, Set<LoadedPlugin>> dependants) {\n\t\tString id = plugin.getContainer().info().id();\n\t\tif (!plugins.remove(id, plugin)) {\n\t\t\tthrow new IllegalStateException(\"Plugin %s was already removed, recursion?\".formatted(id));\n\t\t}\n\t\tPluginException exception = null;\n\t\tfor (LoadedPlugin dependant : dependants.get(plugin)) {\n\t\t\tPluginException inner = unload(dependant, dependants);\n\t\t\tif (inner != null) {\n\t\t\t\tif (exception == null) {\n\t\t\t\t\texception = inner;\n\t\t\t\t} else {\n\t\t\t\t\texception.addSuppressed(inner);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\tPluginContainerImpl<?> container = plugin.getContainer();\n\t\t\ttry {\n\t\t\t\tcontainer.plugin().onDisable();\n\t\t\t} finally {\n\t\t\t\tif (container.classLoader instanceof AutoCloseable ac) {\n\t\t\t\t\tac.close();\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (Exception ex) {\n\t\t\tPluginException pex = new PluginException(ex);\n\t\t\tif (exception == null) {\n\t\t\t\texception = pex;\n\t\t\t} else {\n\t\t\t\texception.addSuppressed(pex);\n\t\t\t}\n\t\t}\n\t\treturn exception;\n\t}\n\n\t/**\n\t * Iterates over which plugins depend on the given plugin, and stores them in the provided map.\n\t *\n\t * @param plugin\n\t * \t\tPlugin to collect dependants of.\n\t * @param dependants\n\t * \t\tMap to store results in.\n\t */\n\tprivate void collectDependants(@Nonnull LoadedPlugin plugin, @Nonnull Map<LoadedPlugin, Set<LoadedPlugin>> dependants) {\n\t\tSet<LoadedPlugin> dependantsSet = dependants.computeIfAbsent(plugin, __ -> HashSet.newHashSet(4));\n\t\tfor (LoadedPlugin pl : plugins.values()) {\n\t\t\tif (plugin == pl) continue;\n\t\t\tif (pl.getDependencies().contains(plugin)) {\n\t\t\t\tdependantsSet.add(pl);\n\t\t\t\tcollectDependants(pl, dependants);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tPlugin identifier.\n\t *\n\t * @return Plugin container if the item was found.\n\t */\n\t@Nullable\n\tPluginContainer<?> getContainer(@Nonnull String id) {\n\t\tLoadedPlugin plugin = plugins.get(id);\n\t\tif (plugin == null)\n\t\t\treturn null;\n\t\treturn plugin.getContainer();\n\t}\n\n\t/**\n\t * @return Collection of plugin containers.\n\t */\n\t@Nonnull\n\tCollection<PluginContainer<?>> plugins() {\n\t\treturn Collections2.transform(plugins.values(), LoadedPlugin::getContainer);\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tPlugin identifier.\n\t *\n\t * @return Iterator of dependency classloaders.\n\t */\n\t@Nonnull\n\tIterator<PluginClassLoaderImpl> getDependencyClassloaders(@Nonnull String id) {\n\t\tvar loaded = plugins.get(id);\n\t\tif (loaded == null) {\n\t\t\treturn Collections.emptyIterator();\n\t\t}\n\t\treturn Iterators.transform(loaded.getDependencies().iterator(), input -> ((PluginClassLoaderImpl) input.getContainer().plugin().getClass().getClassLoader()));\n\t}\n\n\t/**\n\t * Enables the given plugin, initializing if necessary.\n\t *\n\t * @param loadedPlugin\n\t * \t\tPlugin to enable.\n\t *\n\t * @throws PluginException\n\t * \t\tIf the plugin could not be initialized or enabled.\n\t */\n\t@SuppressWarnings({\"rawtypes\", \"unchecked\"})\n\tprivate void enable(@Nonnull LoadedPlugin loadedPlugin) throws PluginException {\n\t\t// Enable dependent plugins\n\t\tfor (LoadedPlugin dependency : loadedPlugin.getDependencies())\n\t\t\tenable(dependency);\n\n\t\t// Check if the plugin is already initialized.\n\t\tPluginContainerImpl container = loadedPlugin.getContainer();\n\t\tPlugin plugin = container.plugin;\n\t\tif (plugin != null) return; // Already initialized, skip.\n\n\t\t// Initialize and enable the plugin.\n\t\ttry {\n\t\t\tClass<? extends Plugin> pluginClass = (Class<? extends Plugin>) container.classLoader.lookupClass(container.preparedPlugin.pluginClassName());\n\t\t\tplugin = classAllocator.instance(pluginClass);\n\t\t\tplugin.onEnable();\n\t\t\tcontainer.plugin = plugin;\n\t\t} catch (Throwable t) {\n\t\t\tthrow new PluginException(t);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PluginId.java",
    "content": "package software.coley.recaf.services.plugin;\n\nrecord PluginId(String id) {}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PluginInfo.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.plugin.Plugin;\nimport software.coley.recaf.plugin.PluginInformation;\n\nimport java.util.Set;\n\n/**\n * Object containing necessary information about a plugin.\n *\n * @param id               ID of the plugin.\n * @param name             Name of the plugin.\n * @param version          Plugin version.\n * @param author           Author of the plugin.\n * @param description      Plugin description.\n * @param dependencies     Plugin dependencies.\n * @param softDependencies Plugin soft dependencies.\n * @author xDark\n * @see PluginInformation Annotation containing this information applied to {@link Plugin} implementations.\n */\npublic record PluginInfo(\n\t\t@Nonnull String id,\n\t\t@Nonnull String name,\n\t\t@Nonnull String version,\n\t\t@Nonnull String author,\n\t\t@Nonnull String description,\n\t\t@Nonnull Set<String> dependencies,\n\t\t@Nonnull Set<String> softDependencies\n) {\n\n\t@Nonnull\n\tpublic static PluginInfo empty() {\n\t\treturn new PluginInfo(\"\", \"\", \"\", \"\", \"\", Set.of(), Set.of());\n\t}\n\n\t@Nonnull\n\tpublic PluginInfo withId(@Nonnull String id) {\n\t\treturn new PluginInfo(id, name, version, author, description, dependencies, softDependencies);\n\t}\n\n\t@Nonnull\n\tpublic PluginInfo withName(@Nonnull String name) {\n\t\treturn new PluginInfo(id, name, version, author, description, dependencies, softDependencies);\n\t}\n\n\t@Nonnull\n\tpublic PluginInfo withVersion(@Nonnull String version) {\n\t\treturn new PluginInfo(id, name, version, author, description, dependencies, softDependencies);\n\t}\n\n\t@Nonnull\n\tpublic PluginInfo withAuthor(@Nonnull String author) {\n\t\treturn new PluginInfo(id, name, version, author, description, dependencies, softDependencies);\n\t}\n\n\t@Nonnull\n\tpublic PluginInfo withDescription(@Nonnull String description) {\n\t\treturn new PluginInfo(id, name, version, author, description, dependencies, softDependencies);\n\t}\n\n\t@Nonnull\n\tpublic PluginInfo withDependencies(@Nonnull Set<String> dependencies) {\n\t\treturn new PluginInfo(id, name, version, author, description, dependencies, softDependencies);\n\t}\n\n\t@Nonnull\n\tpublic PluginInfo withSoftDependencies(@Nonnull Set<String> softDependencies) {\n\t\treturn new PluginInfo(id, name, version, author, description, dependencies, softDependencies);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PluginLoader.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.services.plugin.PluginException;\nimport software.coley.recaf.services.plugin.PreparedPlugin;\nimport software.coley.recaf.util.io.ByteSource;\n\n/**\n * The plugin loader is responsible for loading plugins from different sources.\n *\n * @author xDark\n */\npublic interface PluginLoader {\n\n\t/**\n\t * @param source Content to read from.\n\t * @return Prepared plugin or {@code null}, if source is not supported.\n\t * @throws PluginException When plugin cannot be prepared.\n\t */\n\t@Nullable\n\tPreparedPlugin prepare(@Nonnull ByteSource source)\n\t\t\tthrows PluginException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PluginManager.java",
    "content": "package software.coley.recaf.services.plugin;\n\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.plugin.*;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.plugin.discovery.PluginDiscoverer;\n\nimport java.util.Collection;\nimport java.util.stream.Collectors;\n\n/**\n * Outline of a plugin manager.\n *\n * @author xDark\n */\npublic interface PluginManager extends Service {\n\tString SERVICE_ID = \"plugin-manager\";\n\n\t/**\n\t * @return Allocator to use for creating plugin instances.\n\t */\n\t@Nonnull\n\tClassAllocator getAllocator();\n\n\t/**\n\t * @param id\n\t * \t\tID of the plugin.\n\t * @param <T>\n\t * \t\tContainer plugin type.\n\t *\n\t * @return Plugin by its ID or {@code null}, if not found.\n\t */\n\t@Nullable\n\t<T extends Plugin> PluginContainer<T> getPlugin(@Nonnull String id);\n\n\t/**\n\t * @return Collection of plugins.\n\t */\n\t@Nonnull\n\tCollection<PluginContainer<?>> getPlugins();\n\n\t/**\n\t * @param type\n\t * \t\tSome type to look for.\n\t * @param <T>\n\t * \t\tRequested type.\n\t *\n\t * @return Collection of plugins of the given type.\n\t */\n\t@Nonnull\n\t@SuppressWarnings(\"unchecked\")\n\tdefault <T> Collection<T> getPluginsOfType(@Nonnull Class<T> type) {\n\t\treturn (Collection<T>) getPlugins().stream()\n\t\t\t\t.map(PluginContainer::plugin)\n\t\t\t\t.filter(plugin -> type.isAssignableFrom(plugin.getClass()))\n\t\t\t\t.collect(Collectors.toList());\n\t}\n\n\t/**\n\t * Checks if a plugin is loaded.\n\t *\n\t * @param id\n\t * \t\tID of plugin to check for.\n\t *\n\t * @return {@code true} if the plugin has been registered/loaded by this manager.\n\t */\n\tboolean isPluginLoaded(@Nonnull String id);\n\n\t/**\n\t * Registers new loader.\n\t *\n\t * @param loader\n\t *        {@link PluginLoader} to register.\n\t */\n\tvoid registerLoader(@Nonnull PluginLoader loader);\n\n\t/**\n\t * @param discoverer\n\t * \t\tPlugin discoverer.\n\t *\n\t * @return Loaded plugins.\n\t *\n\t * @throws PluginException\n\t * \t\tIf plugins fail to load.\n\t */\n\t@Nonnull\n\tCollection<PluginContainer<?>> loadPlugins(@Nonnull PluginDiscoverer discoverer) throws PluginException;\n\n\n\t/**\n\t * @param id\n\t * \t\tID of the plugin to be unloaded.\n\t *\n\t * @return Plugin unload action.\n\t *\n\t * @throws IllegalStateException\n\t * \t\tIf plugin to be unloaded was not found.\n\t * @see PluginUnloader\n\t */\n\t@Nonnull\n\tPluginUnloader unloaderFor(@Nonnull String id);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PluginManagerConfig.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link PluginManager}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class PluginManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\tprivate final ObservableBoolean scanOnStartup = new ObservableBoolean(true);\n\n\t@Inject\n\tpublic PluginManagerConfig() {\n\t\tsuper(ConfigGroups.SERVICE_PLUGIN, PluginManager.SERVICE_ID + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"scan-on-start\", boolean.class, scanOnStartup));\n\t}\n\n\t/**\n\t * @return {@code true} when local plugins should be scanned when the plugin manager implementation initializes.\n\t * {@code false} to disable local automatic plugin loading.\n\t */\n\tpublic boolean doScanOnStartup() {\n\t\treturn scanOnStartup.getValue();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PluginSource.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.io.ByteSource;\n\n/**\n * A functional mapping of internal paths <i>(Like paths in a ZIP file)</i> to the contents of the plugin.\n *\n * @author xDark\n */\npublic interface PluginSource {\n\n\t/**\n\t * @param name\n\t * \t\tResource path name.\n\t *\n\t * @return Resource content or {@code null}, if not found.\n\t */\n\t@Nullable\n\tByteSource findResource(String name);\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PluginUnloader.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.stream.Stream;\n\n/**\n * Plugin unload action.\n *\n * @author xDark\n */\npublic interface PluginUnloader {\n\t/**\n\t * Unloads the plugins.\n\t *\n\t * @throws PluginException\n\t * \t\tIf any of the plugins failed to unload.\n\t */\n\tvoid commit() throws PluginException;\n\n\t/**\n\t * @return Plugin to be unloaded.\n\t */\n\t@Nonnull\n\tPluginInfo unloadingPlugin();\n\n\t/**\n\t * @return A collection of plugins that depend on the plugin to be unloaded.\n\t *\n\t * @apiNote These plugins will also get unloaded.\n\t */\n\t@Nonnull\n\tStream<PluginInfo> dependants();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/PreparedPlugin.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.services.plugin.discovery.DiscoveredPluginSource;\nimport software.coley.recaf.services.plugin.discovery.PluginDiscoverer;\n\n/**\n * Prepared plugin. Used as an intermediate step in plugin loading between {@link DiscoveredPluginSource}\n * and {@link PluginContainer}.\n *\n * @author xDark\n * @see BasicPluginManager#loadPlugins(PluginDiscoverer)\n */\npublic interface PreparedPlugin {\n\n\t/**\n\t * @return Plugin information.\n\t */\n\t@Nonnull\n\tPluginInfo info();\n\n\t/**\n\t * @return Plugin source.\n\t */\n\t@Nonnull\n\tPluginSource pluginSource();\n\n\t/**\n\t * @return Plugin class name.\n\t */\n\t@Nonnull\n\tString pluginClassName();\n\n\t/**\n\t * Called if {@link PluginManager} rejects this plugin.\n\t *\n\t * @throws PluginException\n\t * \t\tIf any exception occurs.\n\t */\n\tvoid reject() throws PluginException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/discovery/DirectoryPluginDiscoverer.java",
    "content": "package software.coley.recaf.services.plugin.discovery;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.services.plugin.PluginException;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.stream.Stream;\n\n/**\n * A plugin discoverer that searches a directory for plugin sources.\n * Subdirectories are not searched.\n *\n * @author xDark\n */\npublic final class DirectoryPluginDiscoverer extends PathPluginDiscoverer {\n\tprivate final Path directory;\n\n\t/**\n\t * @param directory Directory to iterate over for plugins.\n\t */\n\tpublic DirectoryPluginDiscoverer(@Nonnull Path directory) {\n\t\tthis.directory = directory;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected Stream<Path> stream() throws PluginException {\n\t\ttry {\n\t\t\treturn Files.find(directory, 1,\n\t\t\t\t\t(path, attributes) -> attributes.isRegularFile() && path.toString().toLowerCase().endsWith(\".jar\"));\n\t\t} catch (IOException ex) {\n\t\t\tthrow new PluginException(ex);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/discovery/DiscoveredPluginSource.java",
    "content": "package software.coley.recaf.services.plugin.discovery;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.io.ByteSource;\n\n/**\n * Provider for a {@link ByteSource} to point to a newly discovered plugin file.\n *\n * @author xDark\n */\npublic interface DiscoveredPluginSource {\n\t/**\n\t * @return Source to load from the plugin file.\n\t */\n\t@Nonnull\n\tByteSource source();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/discovery/PathPluginDiscoverer.java",
    "content": "package software.coley.recaf.services.plugin.discovery;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.services.plugin.PluginException;\nimport software.coley.recaf.util.io.ByteSources;\n\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.stream.Stream;\n\n/**\n * A common base for {@link Path} backed plugin discoverers.\n *\n * @author xDark\n */\npublic abstract class PathPluginDiscoverer implements PluginDiscoverer {\n\t@Nonnull\n\t@Override\n\tpublic final List<DiscoveredPluginSource> findSources() throws PluginException {\n\t\ttry (Stream<Path> s = stream()) {\n\t\t\treturn s.map(path -> (DiscoveredPluginSource) () -> ByteSources.forPath(path))\n\t\t\t\t\t.toList();\n\t\t}\n\t}\n\n\t/**\n\t * @return A stream of paths to treat as plugin sources.\n\t *\n\t * @throws PluginException\n\t * \t\tIf discovery fails.\n\t */\n\t@Nonnull\n\tprotected abstract Stream<Path> stream() throws PluginException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/discovery/PluginDiscoverer.java",
    "content": "package software.coley.recaf.services.plugin.discovery;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.services.plugin.PluginException;\n\nimport java.util.List;\n\n/**\n * Plugin source discoverer.\n *\n * @author xDark\n */\npublic interface PluginDiscoverer {\n\t/**\n\t * @return A list of discovered plugin sources.\n\t *\n\t * @throws PluginException\n\t * \t\tIf discovery fails.\n\t */\n\t@Nonnull\n\tList<DiscoveredPluginSource> findSources() throws PluginException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/zip/ZipArchiveView.java",
    "content": "package software.coley.recaf.services.plugin.zip;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.lljzip.format.model.CentralDirectoryFileHeader;\nimport software.coley.lljzip.format.model.LocalFileHeader;\nimport software.coley.lljzip.format.model.ZipArchive;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Exposes a {@link ZipArchive}'s contents as a {@code Map<String, LocalFileHeader>}.\n *\n * @author xDark\n */\nfinal class ZipArchiveView implements AutoCloseable {\n\tprivate final ZipArchive archive; // keep alive\n\tprivate final Map<String, LocalFileHeader> names;\n\tprivate volatile boolean closed;\n\n\tZipArchiveView(@Nonnull ZipArchive archive) {\n\t\tthis.archive = archive;\n\t\tMap<String, LocalFileHeader> names;\n\n\t\t// Populate view from authoritative central directory entries if possible.\n\t\tList<CentralDirectoryFileHeader> centralDirectories = archive.getCentralDirectories();\n\t\tif (!centralDirectories.isEmpty()) {\n\t\t\tnames = HashMap.newHashMap(centralDirectories.size());\n\t\t\tfor (CentralDirectoryFileHeader cdf : centralDirectories) {\n\t\t\t\tString name = cdf.getFileNameAsString();\n\t\t\t\tnames.putIfAbsent(name, cdf.getLinkedFileHeader());\n\t\t\t}\n\t\t} else {\n\t\t\t// Fall back to local file entries id central directory entries do not exist.\n\t\t\tList<LocalFileHeader> localFiles = archive.getLocalFiles();\n\t\t\tnames = HashMap.newHashMap(localFiles.size());\n\t\t\tfor (LocalFileHeader localFile : localFiles) {\n\t\t\t\tnames.putIfAbsent(localFile.getFileNameAsString(), localFile);\n\t\t\t}\n\t\t}\n\t\tthis.names = names;\n\t}\n\n\t@Override\n\tpublic void close() throws IOException {\n\t\tif (closed) return;\n\t\tsynchronized (this) {\n\t\t\tif (closed) return;\n\t\t\tclosed = true;\n\t\t}\n\n\t\t// Try-with to auto-close the archive when complete.\n\t\ttry (archive) {\n\t\t\tnames.clear();\n\t\t}\n\t}\n\n\t/**\n\t * @return {@code true} when the backing archive is released.\n\t */\n\tpublic boolean isClosed() {\n\t\treturn closed;\n\t}\n\n\t/**\n\t * @return Backing archive.\n\t */\n\t@Nonnull\n\tpublic ZipArchive getArchive() {\n\t\treturn archive;\n\t}\n\n\t/**\n\t * @return Archive entries as a map of internal paths.\n\t */\n\t@Nonnull\n\tpublic Map<String, LocalFileHeader> getEntries() {\n\t\treturn names;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/zip/ZipPluginLoader.java",
    "content": "package software.coley.recaf.services.plugin.zip;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.cafedude.InvalidClassException;\nimport software.coley.cafedude.classfile.ClassFile;\nimport software.coley.cafedude.classfile.annotation.Annotation;\nimport software.coley.cafedude.classfile.annotation.ArrayElementValue;\nimport software.coley.cafedude.classfile.annotation.ElementValue;\nimport software.coley.cafedude.classfile.annotation.Utf8ElementValue;\nimport software.coley.cafedude.classfile.attribute.AnnotationsAttribute;\nimport software.coley.cafedude.classfile.attribute.Attribute;\nimport software.coley.cafedude.io.ClassFileReader;\nimport software.coley.collections.Unchecked;\nimport software.coley.lljzip.ZipIO;\nimport software.coley.lljzip.format.model.ZipArchive;\nimport software.coley.recaf.plugin.*;\nimport software.coley.recaf.services.plugin.PluginException;\nimport software.coley.recaf.services.plugin.PluginInfo;\nimport software.coley.recaf.services.plugin.PluginLoader;\nimport software.coley.recaf.services.plugin.PreparedPlugin;\nimport software.coley.recaf.util.IOUtil;\nimport software.coley.recaf.util.io.ByteSource;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * Zip plugin loader.\n *\n * @author xDark\n */\npublic final class ZipPluginLoader implements PluginLoader {\n\tprivate static final String PLUGIN_INFORMATION_DESC = Type.getDescriptor(PluginInformation.class);\n\tpublic static final String SERVICE_PATH = servicePath();\n\n\t@Nullable\n\t@Override\n\tpublic PreparedPlugin prepare(@Nonnull ByteSource source) throws PluginException {\n\t\tZipArchive zip;\n\t\ttry {\n\t\t\tzip = ZipIO.readJvm(source.mmap());\n\t\t} catch (IOException ex) {\n\t\t\treturn null;\n\t\t}\n\t\tZipArchiveView archiveView;\n\t\ttry {\n\t\t\tarchiveView = new ZipArchiveView(zip);\n\t\t} catch (Throwable t) {\n\t\t\ttry {\n\t\t\t\tzip.close();\n\t\t\t} catch (IOException ignored) {}\n\t\t\tthrow t;\n\t\t}\n\t\tvar zs = new ZipSource(archiveView);\n\t\ttry {\n\t\t\tByteSource resPluginImplementation = zs.findResource(SERVICE_PATH);\n\t\t\tif (resPluginImplementation == null) {\n\t\t\t\tthrow new PluginException(\"Cannot find %s resource\".formatted(SERVICE_PATH));\n\t\t\t}\n\n\t\t\t// Cannot use ServiceLoader here, it will attempt to instantiate the plugin,\n\t\t\t// which is what we *don't* want at this point.\n\t\t\tString pluginClassName;\n\t\t\ttry (BufferedReader reader = IOUtil.toBufferedReader(resPluginImplementation.openStream())) {\n\t\t\t\tpluginClassName = reader.readLine();\n\t\t\t\t{\n\t\t\t\t\tString extraLine;\n\t\t\t\t\twhile ((extraLine = reader.readLine()) != null) {\n\t\t\t\t\t\tif (!extraLine.isEmpty())\n\t\t\t\t\t\t\tthrow new PluginException(\"Extra line %s\".formatted(extraLine));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Find plugin class.\n\t\t\tByteSource resPluginClass = zs.findResource(pluginClassName.replace('.', '/') + \".class\");\n\t\t\tif (resPluginClass == null) {\n\t\t\t\tthrow new PluginException(\"Plugin class %s doesn't exist\".formatted(pluginClassName));\n\t\t\t}\n\n\t\t\t// Find @PluginInformation annotation.\n\t\t\tClassFile cf;\n\t\t\ttry (InputStream in = resPluginClass.openStream()) {\n\t\t\t\tClassFileReader reader = new ClassFileReader();\n\t\t\t\tcf = reader.read(in.readAllBytes());\n\t\t\t} catch (InvalidClassException ex) {\n\t\t\t\tthrow new PluginException(ex);\n\t\t\t}\n\t\t\tPluginInfo info = null;\n\t\t\tfor (Attribute attr : cf.getAttributes()) {\n\t\t\t\tif (!(attr instanceof AnnotationsAttribute annotations)) continue;\n\t\t\t\tif (!annotations.isVisible()) continue;\n\t\t\t\tfor (Annotation annotation : annotations.getAnnotations()) {\n\t\t\t\t\tif (!PLUGIN_INFORMATION_DESC.equals(annotation.getType().getText())) continue;\n\t\t\t\t\t// Collect information.\n\t\t\t\t\tinfo = parsePluginInfo(annotation);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (info == null) {\n\t\t\t\tthrow new PluginException(\"Missing @PluginInformation annotation\");\n\t\t\t}\n\t\t\tPreparedPlugin preparedPlugin = new ZipPreparedPlugin(info, pluginClassName, zs);\n\t\t\tzs = null;\n\t\t\treturn preparedPlugin;\n\t\t} catch (IOException ex) {\n\t\t\tthrow new PluginException(ex);\n\t\t} finally {\n\t\t\tif (zs != null) {\n\t\t\t\ttry {\n\t\t\t\t\tzs.close();\n\t\t\t\t} catch (IOException ignored) {}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate static PluginInfo parsePluginInfo(@Nonnull Annotation annotation) throws PluginException {\n\t\tPluginInfo info = PluginInfo.empty();\n\t\tfor (var e : annotation.getValues().entrySet()) {\n\t\t\tString name = e.getKey().getText();\n\t\t\tElementValue value = e.getValue();\n\t\t\tinfo = switch (name) {\n\t\t\t\tcase \"id\" -> info.withId(string(value));\n\t\t\t\tcase \"name\" -> info.withName(string(value));\n\t\t\t\tcase \"version\" -> info.withVersion(string(value));\n\t\t\t\tcase \"author\" -> info.withAuthor(string(value));\n\t\t\t\tcase \"description\" -> info.withDescription(string(value));\n\t\t\t\tcase \"dependencies\" -> info.withDependencies(stringSet(value));\n\t\t\t\tcase \"softDependencies\" -> info.withSoftDependencies(stringSet(value));\n\t\t\t\tdefault -> info;\n\t\t\t};\n\t\t}\n\t\treturn info;\n\t}\n\n\t@Nonnull\n\tprivate static String servicePath() {\n\t\treturn \"META-INF/services/%s\".formatted(Plugin.class.getName());\n\t}\n\n\t@SafeVarargs\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static <V extends ElementValue, R> R extractValue(@Nonnull ElementValue value, Function<V, R> extractor, V... typeHint) throws PluginException {\n\t\tClass<?> type = typeHint.getClass().getComponentType();\n\t\tV v;\n\t\ttry {\n\t\t\tv = ((Class<V>) type).cast(value);\n\t\t} catch (ClassCastException ex) {\n\t\t\tthrow new PluginException(\"%s is not an instance of %s\".formatted(value, type.getSimpleName()));\n\t\t}\n\t\treturn extractor.apply(v);\n\t}\n\n\t@Nonnull\n\tprivate static String string(@Nonnull ElementValue value) throws PluginException {\n\t\treturn extractValue(value, (Utf8ElementValue elem) -> elem.getValue().getText());\n\t}\n\n\t@Nonnull\n\tprivate static Set<String> stringSet(@Nonnull ElementValue value) throws PluginException {\n\t\treturn extractValue(value, (ArrayElementValue array) -> array\n\t\t\t\t.getArray()\n\t\t\t\t.stream()\n\t\t\t\t.map(Unchecked.function(ZipPluginLoader::string))\n\t\t\t\t.collect(Collectors.toUnmodifiableSet()));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/zip/ZipPreparedPlugin.java",
    "content": "package software.coley.recaf.services.plugin.zip;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.services.plugin.PluginException;\nimport software.coley.recaf.services.plugin.PluginInfo;\nimport software.coley.recaf.services.plugin.PluginSource;\nimport software.coley.recaf.services.plugin.PreparedPlugin;\n\nimport java.io.IOException;\n\n/**\n * ZIP backed prepared plugin implementation.\n *\n * @author xDark\n */\nfinal class ZipPreparedPlugin implements PreparedPlugin {\n\tprivate final PluginInfo pluginInfo;\n\tprivate final String pluginClassName;\n\tprivate final ZipSource classLoader;\n\n\tZipPreparedPlugin(@Nonnull PluginInfo pluginInfo, @Nonnull String pluginClassName, @Nonnull ZipSource classLoader) {\n\t\tthis.pluginInfo = pluginInfo;\n\t\tthis.pluginClassName = pluginClassName;\n\t\tthis.classLoader = classLoader;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PluginInfo info() {\n\t\treturn pluginInfo;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PluginSource pluginSource() {\n\t\treturn classLoader;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String pluginClassName() {\n\t\treturn pluginClassName;\n\t}\n\n\t@Override\n\tpublic void reject() throws PluginException {\n\t\ttry {\n\t\t\tclassLoader.close();\n\t\t} catch (IOException ex) {\n\t\t\tthrow new PluginException(ex);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/plugin/zip/ZipSource.java",
    "content": "package software.coley.recaf.services.plugin.zip;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.lljzip.format.model.LocalFileHeader;\nimport software.coley.recaf.services.plugin.PluginSource;\nimport software.coley.recaf.util.io.ByteSource;\nimport software.coley.recaf.util.io.LocalFileHeaderSource;\n\nimport java.io.IOException;\n\n/**\n * ZIP backed plugin source.\n *\n * @author xDark\n */\nfinal class ZipSource implements PluginSource, AutoCloseable {\n\tprivate final ZipArchiveView archiveView;\n\n\tZipSource(@Nonnull ZipArchiveView archiveView) {\n\t\tthis.archiveView = archiveView;\n\t}\n\n\t@Override\n\tpublic ByteSource findResource(String name) {\n\t\tZipArchiveView archiveView = this.archiveView;\n\t\tif (archiveView.isClosed())\n\t\t\treturn null;\n\t\tsynchronized (this) {\n\t\t\tif (archiveView.isClosed()) return null;\n\t\t\tLocalFileHeader file = archiveView.getEntries().get(name);\n\t\t\tif (file == null) return null;\n\t\t\treturn new LocalFileHeaderSource(file);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void close() throws IOException {\n\t\tarchiveView.close();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/script/GenerateResult.java",
    "content": "package software.coley.recaf.services.script;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.services.compile.CompilerDiagnostic;\n\nimport java.util.List;\n\n/**\n * Wrapper for results of {@link ScriptEngine#compile(String)} calls.\n *\n * @param cls\n * \t\tCompiled class reference. May be {@code null} when compilation failed.\n * @param diagnostics\n * \t\tCompiler diagnostic messages.\n *\n * @author Matt Coley\n */\npublic record GenerateResult(@Nullable Class<?> cls, @Nonnull List<CompilerDiagnostic> diagnostics) {\n\t/**\n\t * @return {@code true} when compilation was a success.\n\t */\n\tpublic boolean wasSuccess() {\n\t\treturn cls != null;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/script/JavacScriptEngine.java",
    "content": "package software.coley.recaf.services.script;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport regexodus.Matcher;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.compile.*;\nimport software.coley.recaf.services.plugin.CdiClassAllocator;\nimport software.coley.recaf.util.ClassDefiner;\nimport software.coley.recaf.util.ReflectUtil;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\n\nimport java.lang.reflect.Method;\nimport java.util.*;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.stream.Collectors;\n\n/**\n * Basic implementation of {@link ScriptEngine} using {@link JavacCompiler}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class JavacScriptEngine implements ScriptEngine {\n\tprivate static final DebuggingLogger logger = Logging.get(JavacScriptEngine.class);\n\tprivate static final String SCRIPT_PACKAGE_NAME = \"software.coley.recaf.generated\";\n\tprivate static final String PATTERN_PACKAGE = \"package ([\\\\w\\\\.\\\\*]+);?\";\n\tprivate static final String PATTERN_IMPORT = \"import ([\\\\w\\\\.\\\\*]+);?\";\n\tprivate static final String PATTERN_CLASS_NAME = \"(?<=class)\\\\s+(\\\\w+)\\\\s+(?:implements|extends|\\\\{)\";\n\tprivate static final List<String> DEFAULT_IMPORTS = Arrays.asList(\n\t\t\t\"java.io.*\",\n\t\t\t\"java.nio.file.*\",\n\t\t\t\"java.util.*\",\n\t\t\t\"software.coley.recaf.*\",\n\t\t\t\"software.coley.recaf.analytics.logging.*\",\n\t\t\t\"software.coley.recaf.info.*\",\n\t\t\t\"software.coley.recaf.info.annotation.*\",\n\t\t\t\"software.coley.recaf.info.builder.*\",\n\t\t\t\"software.coley.recaf.info.member.*\",\n\t\t\t\"software.coley.recaf.info.properties.*\",\n\t\t\t\"software.coley.recaf.services.*\",\n\t\t\t// \"software.coley.recaf.services.assemble.*\",\n\t\t\t\"software.coley.recaf.services.attach.*\",\n\t\t\t\"software.coley.recaf.services.callgraph.*\",\n\t\t\t\"software.coley.recaf.services.compile.*\",\n\t\t\t\"software.coley.recaf.services.config.*\",\n\t\t\t\"software.coley.recaf.services.decompile.*\",\n\t\t\t\"software.coley.recaf.services.file.*\",\n\t\t\t\"software.coley.recaf.services.inheritance.*\",\n\t\t\t\"software.coley.recaf.services.mapping.*\",\n\t\t\t\"software.coley.recaf.services.plugin.*\",\n\t\t\t\"software.coley.recaf.services.script.*\",\n\t\t\t\"software.coley.recaf.services.search.*\",\n\t\t\t\"software.coley.recaf.services.workspace.*\",\n\t\t\t\"software.coley.recaf.services.workspace.io.*\",\n\t\t\t\"software.coley.recaf.workspace.model.*\",\n\t\t\t\"software.coley.recaf.workspace.model.bundle.*\",\n\t\t\t// \"software.coley.recaf.services.ssvm.*\",\n\t\t\t\"software.coley.recaf.util.*\",\n\t\t\t\"software.coley.recaf.util.android.*\",\n\t\t\t\"software.coley.recaf.util.io.*\",\n\t\t\t\"software.coley.recaf.util.threading.*\",\n\t\t\t\"software.coley.recaf.util.visitors.*\",\n\t\t\t\"org.objectweb.asm.*\",\n\t\t\t\"org.objectweb.asm.tree.*\",\n\t\t\t\"jakarta.annotation.*\",\n\t\t\t\"jakarta.enterprise.context.*\",\n\t\t\t\"jakarta.inject.*\",\n\t\t\t\"org.slf4j.Logger\"\n\t);\n\tprivate final Map<Integer, GenerateResult> generateResultMap = new HashMap<>();\n\tprivate final ExecutorService compileAndRunPool = ThreadPoolFactory.newSingleThreadExecutor(\"script-loader\");\n\tprivate final JavacCompiler compiler;\n\tprivate final CdiClassAllocator allocator;\n\tprivate final ScriptEngineConfig config;\n\n\t@Inject\n\tpublic JavacScriptEngine(JavacCompiler compiler, CdiClassAllocator allocator, ScriptEngineConfig config) {\n\t\tthis.compiler = compiler;\n\t\tthis.allocator = allocator;\n\t\tthis.config = config;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ScriptEngineConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic CompletableFuture<ScriptResult> run(@Nonnull String script) {\n\t\treturn CompletableFuture.supplyAsync(() -> handleExecute(script), compileAndRunPool);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic CompletableFuture<GenerateResult> compile(@Nonnull String scriptSource) {\n\t\treturn CompletableFuture.supplyAsync(() -> generate(scriptSource), compileAndRunPool);\n\t}\n\n\t/**\n\t * Compiles and executes the script.\n\t * If the same script has already been compiled previously, the prior class reference will be used\n\t * to reduce duplicate compilations.\n\t *\n\t * @param script\n\t * \t\tScript to execute.\n\t *\n\t * @return Result of script execution.\n\t */\n\t@Nonnull\n\tprivate ScriptResult handleExecute(@Nonnull String script) {\n\t\tGenerateResult result = generate(script);\n\t\tif (result.cls() != null) {\n\t\t\ttry {\n\t\t\t\tlogger.debugging(l -> l.info(\"Allocating script instance\"));\n\t\t\t\tObject instance = allocator.instance(result.cls());\n\t\t\t\tMethod run = ReflectUtil.getDeclaredMethod(instance.getClass(), \"run\");\n\t\t\t\trun.setAccessible(true);\n\t\t\t\trun.invoke(instance);\n\t\t\t\tlogger.debugging(l -> l.info(\"Successfully ran script\"));\n\t\t\t\treturn new ScriptResult(result.diagnostics());\n\t\t\t} catch (Exception ex) {\n\t\t\t\tlogger.error(\"Failed to execute script\", ex);\n\t\t\t\treturn new ScriptResult(result.diagnostics(), ex);\n\t\t\t}\n\t\t} else {\n\t\t\tlogger.error(\"Failed to compile script\");\n\t\t\treturn new ScriptResult(result.diagnostics());\n\t\t}\n\t}\n\n\t/**\n\t * Maps an input script to a full Java source file, and compiles it.\n\t * Delegates to either:\n\t * <ul>\n\t *     <li>{@link #generateScriptClass(String, String)}</li>\n\t *     <li>{@link #generateStandardClass(String)}</li>\n\t * </ul>\n\t *\n\t * @param script\n\t * \t\tInitial source of the script.\n\t *\n\t * @return Compiler result wrapper containing the loaded class reference.\n\t */\n\tprivate GenerateResult generate(@Nonnull String script) {\n\t\tint hash = script.hashCode();\n\t\tGenerateResult result;\n\t\tif (RegexUtil.matchesAny(PATTERN_CLASS_NAME, script)) {\n\t\t\tlogger.debugging(l -> l.info(\"Compiling script as class\"));\n\t\t\tresult = generateResultMap.computeIfAbsent(hash, n -> generateStandardClass(script));\n\t\t} else {\n\t\t\tlogger.debugging(l -> l.info(\"Compiling script as function\"));\n\t\t\tString className = \"Script\" + Math.abs(hash);\n\t\t\tresult = generateResultMap.computeIfAbsent(hash, n -> generateScriptClass(className, script));\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Used when the script contains a class definition in itself.\n\t * Adds the default script package name, if no package is defined.\n\t *\n\t * @param source\n\t * \t\tInitial source of the script.\n\t *\n\t * @return Compiler result wrapper containing the loaded class reference.\n\t */\n\t@Nonnull\n\tprivate GenerateResult generateStandardClass(@Nonnull String source) {\n\t\tString originalSource = source;\n\n\t\t// Extract package name\n\t\tString packageName = SCRIPT_PACKAGE_NAME;\n\t\tMatcher matcher = RegexUtil.getMatcher(PATTERN_PACKAGE, source);\n\t\tif (matcher.find())\n\t\t\tpackageName = matcher.group(1);\n\t\telse\n\t\t\tsource = \"package \" + packageName + \";\\n\" + source;\n\n\t\t// Add default imports\n\t\tString imports = \"\\nimport \" + String.join(\";\\nimport \", DEFAULT_IMPORTS) + \";\\n\";\n\t\tsource = StringUtil.insert(source, source.indexOf(packageName + \";\") + packageName.length() + 1, imports);\n\n\t\t// Normalize package name\n\t\tpackageName = packageName.replace('.', '/');\n\n\t\t// Extract class name\n\t\tString className;\n\t\tmatcher = RegexUtil.getMatcher(PATTERN_CLASS_NAME, source);\n\t\tif (matcher.find()) {\n\t\t\tString originalName = matcher.group(1);\n\t\t\tString modifiedName = originalName + Math.abs(source.hashCode());\n\t\t\tclassName = packageName + \"/\" + modifiedName;\n\n\t\t\t// Replace name in script\n\t\t\t//  - Class definition\n\t\t\t//  - Constructors\n\t\t\tsource = StringUtil.replaceRange(source, matcher.start(1), matcher.end(1), modifiedName);\n\t\t\tsource = source.replace(\" \" + originalName + \"(\", \" \" + modifiedName + \"(\");\n\t\t\tsource = source.replace(\"\\t\" + originalName + \"(\", \"\\t\" + modifiedName + \"(\");\n\t\t} else {\n\t\t\treturn new GenerateResult(null, List.of(\n\t\t\t\t\tnew CompilerDiagnostic(-1, -1, 0,\n\t\t\t\t\t\t\t\"Could not determine name of class\", CompilerDiagnostic.Level.ERROR)));\n\t\t}\n\n\t\t// Compile the class\n\t\treturn generate(className, originalSource, source);\n\t}\n\n\t/**\n\t * Used when the script immediately starts with the code.\n\t * This will wrap that content in a basic class.\n\t *\n\t * @param className\n\t * \t\tName of the script class.\n\t * @param script\n\t * \t\tInitial source of the script.\n\t *\n\t * @return Compiler result wrapper containing the loaded class reference.\n\t */\n\t@Nonnull\n\tprivate GenerateResult generateScriptClass(@Nonnull String className, @Nonnull String script) {\n\t\tString originalSource = script;\n\t\tSet<String> imports = new HashSet<>(DEFAULT_IMPORTS);\n\t\tMatcher matcher = RegexUtil.getMatcher(PATTERN_IMPORT, script);\n\t\twhile (matcher.find()) {\n\t\t\t// Record import statement\n\t\t\tString importIdentifier = matcher.group(1);\n\t\t\timports.add(importIdentifier);\n\n\t\t\t// Replace text with spaces to maintain script character offsets\n\t\t\tString importMatch = script.substring(matcher.start(), matcher.end());\n\t\t\tscript = script.replace(importMatch, \" \".repeat(importMatch.length()));\n\t\t}\n\n\t\t// Create code (just a basic class with a static 'run' method)\n\t\tStringBuilder code = new StringBuilder(\n\t\t\t\t\"@Dependent public class \" + className + \" implements Runnable, Opcodes { \" +\n\t\t\t\t\t\t\"private static final Logger log = Logging.get(\\\"script\\\"); \" +\n\t\t\t\t\t\t\"Workspace workspace; \" +\n\t\t\t\t\t\t\"@Inject \" + className +\"(Workspace workspace) { this.workspace = workspace; } \" +\n\t\t\t\t\t\t\"public void run() {\\n\" + script + \"\\n\" + \"}\" +\n\t\t\t\t\t\t\"}\");\n\t\tfor (String imp : imports)\n\t\t\tcode.insert(0, \"import \" + imp + \"; \");\n\t\tcode.insert(0, \"package \" + SCRIPT_PACKAGE_NAME + \"; \");\n\t\tclassName = SCRIPT_PACKAGE_NAME.replace('.', '/') + \"/\" + className;\n\n\t\t// Compile the class\n\t\treturn generate(className, originalSource, code.toString());\n\t}\n\n\t/**\n\t * @param className\n\t * \t\tName of the script class.\n\t * @param originalSource\n\t * \t\tOriginal source provided by the user.\n\t * @param compileSource\n\t * \t\tFull source of the script to pass to the compiler.\n\t *\n\t * @return Compiler result wrapper containing the loaded class reference.\n\t */\n\t@Nonnull\n\tprivate GenerateResult generate(@Nonnull String className,\n\t\t\t\t\t\t\t\t\t@Nonnull String originalSource,\n\t\t\t\t\t\t\t\t\t@Nonnull String compileSource) {\n\t\tJavacArguments args = new JavacArgumentsBuilder()\n\t\t\t\t.withClassName(className)\n\t\t\t\t.withClassSource(compileSource)\n\t\t\t\t.build();\n\t\tCompilerResult result = compiler.compile(args, null, null);\n\t\tif (result.wasSuccess()) {\n\t\t\ttry {\n\t\t\t\tMap<String, byte[]> classes = result.getCompilations().entrySet().stream()\n\t\t\t\t\t\t.collect(Collectors.toMap(e -> e.getKey().replace('/', '.'), Map.Entry::getValue));\n\t\t\t\tClassDefiner definer = new ClassDefiner(classes);\n\t\t\t\tClass<?> cls = definer.findClass(className.replace('/', '.'));\n\t\t\t\treturn new GenerateResult(cls, mapDiagnostics(originalSource, compileSource, result.getDiagnostics()));\n\t\t\t} catch (Exception ex) {\n\t\t\t\tlogger.error(\"Failed to define generated script class\", ex);\n\t\t\t}\n\t\t}\n\t\treturn new GenerateResult(null, mapDiagnostics(originalSource, compileSource, result.getDiagnostics()));\n\t}\n\n\t/**\n\t * @param originalSource\n\t * \t\tOriginal source provided by the user.\n\t * @param compileSource\n\t * \t\tFull source of the script to pass to the compiler.\n\t * @param diagnostics\n\t * \t\tDiagnostics to map position of.\n\t *\n\t * @return List of updated diagnostics.\n\t */\n\tprivate List<CompilerDiagnostic> mapDiagnostics(@Nonnull String originalSource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull String compileSource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull List<CompilerDiagnostic> diagnostics) {\n\n\t\tint syntheticLineCount = StringUtil.count(\"\\n\", StringUtil.cutOffAtFirst(compileSource, originalSource));\n\t\treturn diagnostics.stream()\n\t\t\t\t.map(d -> d.withLine(d.line() - syntheticLineCount))\n\t\t\t\t.toList();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/script/ScriptEngine.java",
    "content": "package software.coley.recaf.services.script;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.services.Service;\n\nimport java.util.concurrent.CompletableFuture;\n\n/**\n * Outline for script execution and compilation.\n *\n * @author Matt Coley\n */\npublic interface ScriptEngine extends Service {\n\tString SERVICE_ID = \"script-engine\";\n\n\t/**\n\t * @param scriptSource\n\t * \t\tScript source to execute.\n\t *\n\t * @return Future of script execution.\n\t */\n\t@Nonnull\n\tCompletableFuture<ScriptResult> run(@Nonnull String scriptSource);\n\n\t/**\n\t * @param scriptSource\n\t * \t\tScript source to compile.\n\t *\n\t * @return Future of script compilation.\n\t */\n\t@Nonnull\n\tCompletableFuture<GenerateResult> compile(@Nonnull String scriptSource);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/script/ScriptEngineConfig.java",
    "content": "package software.coley.recaf.services.script;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link ScriptEngine}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ScriptEngineConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic ScriptEngineConfig() {\n\t\tsuper(ConfigGroups.SERVICE_PLUGIN, ScriptEngine.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/script/ScriptFile.java",
    "content": "package software.coley.recaf.services.script;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\n\n/**\n * Wrapper of a script path, with meta-data.\n *\n * @param path\n * \t\tPath to script file.\n * @param tags\n * \t\tScript's metadata tags.\n *\n * @author Matt Coley\n */\npublic record ScriptFile(@Nonnull Path path, @Nonnull String source,\n\t\t\t\t\t\t @Nonnull Map<String, String> tags) implements Comparable<ScriptFile> {\n\tpublic static final String KEY_NAME = \"name\";\n\tpublic static final String KEY_DESCRIPTION = \"description\";\n\tpublic static final String KEY_VERSION = \"version\";\n\tpublic static final String KEY_AUTHOR = \"author\";\n\n\t/**\n\t * Executes the script's content in the given engine.\n\t *\n\t * @param engine\n\t * \t\tEngine to execute with.\n\t *\n\t * @return Script execution future.\n\t */\n\t@Nonnull\n\tpublic CompletableFuture<ScriptResult> execute(@Nonnull ScriptEngine engine) {\n\t\treturn engine.run(source());\n\t}\n\n\t/**\n\t * @return Script name.\n\t */\n\t@Nonnull\n\tpublic String name() {\n\t\treturn getTagValue(KEY_NAME);\n\t}\n\n\t/**\n\t * @return Script description.\n\t */\n\t@Nonnull\n\tpublic String description() {\n\t\treturn getTagValue(KEY_DESCRIPTION);\n\t}\n\n\t/**\n\t * @return Script version.\n\t */\n\t@Nonnull\n\tpublic String version() {\n\t\treturn getTagValue(KEY_VERSION);\n\t}\n\n\t/**\n\t * @return Script author.\n\t */\n\t@Nonnull\n\tpublic String author() {\n\t\treturn getTagValue(KEY_AUTHOR);\n\t}\n\n\t/**\n\t * @param tag\n\t * \t\tName of tag.\n\t *\n\t * @return Value of tag, or empty string if no tag exists.\n\t */\n\t@Nonnull\n\tpublic String getTagValue(@Nonnull String tag) {\n\t\treturn tags.getOrDefault(tag, \"\");\n\t}\n\n\t@Override\n\tpublic int compareTo(ScriptFile o) {\n\t\treturn path().compareTo(o.path());\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/script/ScriptManager.java",
    "content": "package software.coley.recaf.services.script;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport regexodus.Matcher;\nimport org.slf4j.Logger;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableCollection;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\n\nimport java.io.IOException;\nimport java.nio.file.*;\nimport java.util.*;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\n\n/**\n * Manages local script files.\n *\n * @author Matt Coley\n * @see ScriptEngine Executor for scripts.\n * @see ScriptFile Local script file type.\n */\n@ApplicationScoped\npublic class ScriptManager implements Service {\n\tpublic static final String SERVICE_ID = \"script-manager\";\n\tprivate static final String TAG_PATTERN = \"//(\\\\s+)?@({key}\\\\S+)\\\\s+({value}.+)\";\n\tprivate static final Logger logger = Logging.get(ScriptManager.class);\n\tprivate final ObservableCollection<ScriptFile, List<ScriptFile>> scriptFiles = new ObservableCollection<>(ArrayList::new);\n\tprivate final ScriptManagerConfig config;\n\tprivate final WatchTask watchTask;\n\n\t@Inject\n\tpublic ScriptManager(@Nonnull ScriptManagerConfig config) {\n\t\tthis.config = config;\n\t\twatchTask = new WatchTask();\n\n\t\t// Start watching files in scripts directory\n\t\tObservableBoolean fileWatching = config.getFileWatching();\n\t\tif (fileWatching.getValue())\n\t\t\twatchTask.start();\n\n\t\t// When the watch flag is re-enabled, re-submit the watch-pool task.\n\t\tfileWatching.addChangeListener((ob, old, cur) -> {\n\t\t\tif (cur)\n\t\t\t\twatchTask.start();\n\t\t\telse\n\t\t\t\twatchTask.stop();\n\t\t});\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to script file.\n\t *\n\t * @return Wrapper of script.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the script file cannot be read from.\n\t */\n\t@Nonnull\n\tpublic ScriptFile read(@Nonnull Path path) throws IOException {\n\t\tString text = Files.readString(path);\n\n\t\t// Parse tags from beginning of file\n\t\tMap<String, String> tags = new HashMap<>();\n\t\tint metaEnd = Math.max(0, text.indexOf(\"==/Metadata==\"));\n\t\tint lineMetaEnd = StringUtil.count(\"\\n\", text.substring(0, metaEnd));\n\t\ttext.lines().limit(lineMetaEnd).forEach(line -> {\n\t\t\tif (line.startsWith(\"//\")) {\n\t\t\t\tMatcher matcher = RegexUtil.getMatcher(TAG_PATTERN, line);\n\t\t\t\tif (matcher.matches()) {\n\t\t\t\t\tString key = matcher.group(\"key\").toLowerCase();\n\t\t\t\t\tString value = matcher.group(\"value\");\n\t\t\t\t\ttags.put(key, value);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\treturn new ScriptFile(path, text, tags);\n\t}\n\n\n\t/**\n\t * @param path\n\t * \t\tPath of file newly created.\n\t */\n\tprivate void onScriptCreate(@Nonnull Path path) {\n\t\ttry {\n\t\t\tlogger.debug(\"Script created: {}\", path);\n\t\t\tScriptFile file = read(path);\n\t\t\tscriptFiles.add(file);\n\t\t} catch (IOException ex) {\n\t\t\tlogger.error(\"Could not load script from path: {}\", path, ex);\n\t\t}\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath of file modified.\n\t */\n\tprivate void onScriptUpdated(@Nonnull Path path) {\n\t\ttry {\n\t\t\t// Read updated script content from path.\n\t\t\tScriptFile updated = read(path);\n\n\t\t\t// Replace old file wrapper with new wrapper.\n\t\t\t// Only do so if they are not equal. There are some odd situations where you will get duplicate\n\t\t\t// file-watcher events on the same file even if the contents are not modified.\n\t\t\tif (scriptFiles.removeIf(file -> path.equals(file.path()) && !file.equals(updated)))\n\t\t\t\tscriptFiles.add(updated);\n\t\t} catch (IOException ex) {\n\t\t\tlogger.error(\"Could not load script from path: {}\", path, ex);\n\t\t}\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath of file removed.\n\t */\n\tprivate void onScriptRemoved(@Nonnull Path path) {\n\t\tlogger.debug(\"Script removed: {}\", path);\n\t\tscriptFiles.removeIf(file -> path.equals(file.path()));\n\t}\n\n\t/**\n\t * @return Collection of local available script files.\n\t */\n\t@Nonnull\n\tpublic ObservableCollection<ScriptFile, List<ScriptFile>> getScriptFiles() {\n\t\treturn scriptFiles;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ScriptManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\t/**\n\t * Wrapper to manage threaded watch service on the script directory.\n\t */\n\tprivate class WatchTask {\n\t\tprivate final Path scriptsDirectory = config.getScriptsDirectory();\n\t\tprivate final ExecutorService watchPool = ThreadPoolFactory.newSingleThreadExecutor(SERVICE_ID);\n\t\tprivate Future<?> watchFuture;\n\t\tprivate WatchService watchService;\n\n\t\tprivate void start() {\n\t\t\tscanExisting();\n\n\t\t\t// Only start a new thread when the old one is complete, or if no prior one exists.\n\t\t\tif (watchFuture == null || watchFuture.isDone()) {\n\t\t\t\tlogger.debug(\"Starting script directory watch task\");\n\t\t\t\twatchFuture = watchPool.submit(this::watch);\n\t\t\t}\n\t\t}\n\n\t\tprivate void stop() {\n\t\t\t// Calling 'close()' on the service will make the loop on the service event 'take()' break.\n\t\t\tif (watchService != null) {\n\t\t\t\ttry {\n\t\t\t\t\tlogger.debug(\"Stopping script directory watch task\");\n\t\t\t\t\twatchService.close();\n\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\tlogger.error(\"Failed to stop script directory watch service\");\n\t\t\t\t}\n\t\t\t\twatchFuture.cancel(true);\n\t\t\t\twatchService = null;\n\t\t\t}\n\t\t}\n\n\t\tprivate void scanExisting() {\n\t\t\ttry {\n\t\t\t\t// Walk the directory, create or update scripts that exist.\n\t\t\t\tSet<ScriptFile> scriptsCopy = new HashSet<>(scriptFiles);\n\t\t\t\tFiles.walk(scriptsDirectory).forEach(path -> {\n\t\t\t\t\tif (Files.isRegularFile(path)) {\n\t\t\t\t\t\tOptional<ScriptFile> matchingScript = scriptsCopy.stream()\n\t\t\t\t\t\t\t\t.filter(script -> script.path().equals(path))\n\t\t\t\t\t\t\t\t.findFirst();\n\t\t\t\t\t\tif (matchingScript.isPresent()) {\n\t\t\t\t\t\t\tscriptsCopy.remove(matchingScript.get());\n\t\t\t\t\t\t\tonScriptUpdated(path);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tonScriptCreate(path);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// Any remaining items in the set do not exist in the directory, so we remove them.\n\t\t\t\tscriptFiles.removeAll(scriptsCopy);\n\t\t\t} catch (IOException ex) {\n\t\t\t\tlogger.error(\"Failed to scan existing scripts in script directory\", ex);\n\t\t\t}\n\t\t}\n\n\t\tprivate void watch() {\n\t\t\ttry (WatchService watchService = FileSystems.getDefault().newWatchService()) {\n\t\t\t\tthis.watchService = watchService;\n\t\t\t\tWatchKey watchKey = scriptsDirectory.register(watchService,\n\t\t\t\t\t\tStandardWatchEventKinds.ENTRY_CREATE,\n\t\t\t\t\t\tStandardWatchEventKinds.ENTRY_MODIFY,\n\t\t\t\t\t\tStandardWatchEventKinds.ENTRY_DELETE);\n\t\t\t\tWatchKey key;\n\t\t\t\twhile ((key = watchService.take()) != null) {\n\t\t\t\t\tfor (WatchEvent<?> event : key.pollEvents()) {\n\t\t\t\t\t\tPath eventPath = scriptsDirectory.resolve((Path) event.context());\n\t\t\t\t\t\tWatchEvent.Kind<?> kind = event.kind();\n\t\t\t\t\t\tif (Files.isRegularFile(eventPath)) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t// We are only interested in 'ENTRY_MODIFY' events since that is when file content is written.\n\t\t\t\t\t\t\t\t// A script file created via 'ENTRY_CREATE' will always be empty, so reading from it at\n\t\t\t\t\t\t\t\t// that point would be useless.\n\t\t\t\t\t\t\t\tif (kind == StandardWatchEventKinds.ENTRY_MODIFY) {\n\t\t\t\t\t\t\t\t\tSet<ScriptFile> scriptsCopy = new HashSet<>(scriptFiles);\n\t\t\t\t\t\t\t\t\tOptional<ScriptFile> matchingScript = scriptsCopy.stream()\n\t\t\t\t\t\t\t\t\t\t\t.filter(script -> script.path().equals(eventPath))\n\t\t\t\t\t\t\t\t\t\t\t.findFirst();\n\t\t\t\t\t\t\t\t\tif (matchingScript.isPresent()) {\n\t\t\t\t\t\t\t\t\t\tscriptsCopy.remove(matchingScript.get());\n\t\t\t\t\t\t\t\t\t\tonScriptUpdated(eventPath);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tonScriptCreate(eventPath);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\t\t\tlogger.error(\"Unhandled exception updating available scripts\", t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {\n\t\t\t\t\t\t\tonScriptRemoved(eventPath);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!key.reset())\n\t\t\t\t\t\tlogger.warn(\"Key was unregistered: {}\", key);\n\t\t\t\t}\n\t\t\t\twatchKey.cancel();\n\t\t\t\tlogger.info(\"Stopped watching script directory for updates\");\n\t\t\t} catch (IOException ex) {\n\t\t\t\tlogger.error(\"IO exception when handling file watch on scripts directory\", ex);\n\t\t\t} catch (InterruptedException ex) {\n\t\t\t\tlogger.error(\"File watch on scripts directory was interrupted\", ex);\n\t\t\t} catch (ClosedWatchServiceException ignored) {\n\t\t\t\t// expected when watch service is closed\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/script/ScriptManagerConfig.java",
    "content": "package software.coley.recaf.services.script;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\nimport software.coley.recaf.services.file.RecafDirectoriesConfig;\n\nimport java.nio.file.Path;\n\n/**\n * Config for {@link ScriptManager}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ScriptManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\tprivate final ObservableBoolean fileWatching = new ObservableBoolean(true);\n\tprivate final RecafDirectoriesConfig directories;\n\n\t@Inject\n\tpublic ScriptManagerConfig(RecafDirectoriesConfig directories) {\n\t\tsuper(ConfigGroups.SERVICE_PLUGIN, ScriptManager.SERVICE_ID + CONFIG_SUFFIX);\n\t\tthis.directories = directories;\n\t\taddValue(new BasicConfigValue<>(\"file-watching\", boolean.class, fileWatching));\n\t}\n\n\t/**\n\t * @return Directory containing local scripts.\n\t */\n\t@Nonnull\n\tpublic Path getScriptsDirectory() {\n\t\treturn directories.getScriptsDirectory();\n\t}\n\n\t/**\n\t * @return {@code true} to enable file watching in {@link ScriptManager} to automatically update available scripts.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getFileWatching() {\n\t\treturn fileWatching;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/script/ScriptResult.java",
    "content": "package software.coley.recaf.services.script;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.services.compile.CompilerDiagnostic;\n\nimport java.util.List;\n\n/**\n * Wrapper for results of {@link ScriptEngine#run(String)} calls.\n *\n * @author Matt Coley\n */\npublic class ScriptResult {\n\tprivate final List<CompilerDiagnostic> diagnostics;\n\tprivate final Throwable throwable;\n\n\t/**\n\t * @param diagnostics\n\t * \t\tCompiler error list.\n\t */\n\tpublic ScriptResult(@Nonnull List<CompilerDiagnostic> diagnostics) {\n\t\tthis(diagnostics, null);\n\t}\n\n\t/**\n\t * @param diagnostics\n\t * \t\tCompiler error list.\n\t * @param throwable\n\t * \t\tRuntime error value.\n\t */\n\tpublic ScriptResult(@Nonnull List<CompilerDiagnostic> diagnostics, @Nullable Throwable throwable) {\n\t\tthis.diagnostics = diagnostics;\n\t\tthis.throwable = throwable;\n\t}\n\n\t/**\n\t * @return {@code true} when there were no compiler or runtime errors.\n\t */\n\tpublic boolean wasSuccess() {\n\t\treturn !wasCompileFailure() && !wasRuntimeError();\n\t}\n\n\t/**\n\t * @return {@code true} when {@link #getCompileDiagnostics()} has content.\n\t */\n\tpublic boolean wasCompileFailure() {\n\t\treturn !getCompileDiagnostics().isEmpty();\n\t}\n\n\t/**\n\t * @return {@code true} when {@link #getRuntimeThrowable()} is present.\n\t */\n\tpublic boolean wasRuntimeError() {\n\t\treturn throwable != null;\n\t}\n\n\t/**\n\t * @return List of compiler diagnostics.\n\t */\n\t@Nonnull\n\tpublic List<CompilerDiagnostic> getCompileDiagnostics() {\n\t\treturn diagnostics;\n\t}\n\n\t/**\n\t * @return Exception thrown when running the generated script method.\n\t */\n\t@Nullable\n\tpublic Throwable getRuntimeThrowable() {\n\t\treturn throwable;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/AndroidClassSearchVisitor.java",
    "content": "package software.coley.recaf.services.search;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.search.result.Results;\n\n/**\n * Visitor for {@link AndroidClassInfo}\n *\n * @author Matt Coley\n */\npublic interface AndroidClassSearchVisitor extends SearchVisitor {\n\t/**\n\t * Visits an Android class.\n\t *\n\t * @param resultSink\n\t * \t\tConsumer to feed result values into, typically populating a {@link Results} instance.\n\t * @param classPath\n\t * \t\tPath to class being visited.\n\t * @param classInfo\n\t * \t\tClass to visit.\n\t */\n\tvoid visit(@Nonnull ResultSink resultSink,\n\t\t\t   @Nonnull ClassPathNode classPath,\n\t\t\t   @Nonnull AndroidClassInfo classInfo);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/CancellableSearchFeedback.java",
    "content": "package software.coley.recaf.services.search;\n\n/**\n * Feedback that allows cancelling a search.\n *\n * @author Matt Coley\n */\npublic class CancellableSearchFeedback implements SearchFeedback {\n\tprivate boolean canceled;\n\n\t/**\n\t * Mark search as cancelled.\n\t */\n\tpublic void cancel() {\n\t\tcanceled = true;\n\t}\n\n\t@Override\n\tpublic boolean hasRequestedCancellation() {\n\t\treturn canceled;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/FileSearchVisitor.java",
    "content": "package software.coley.recaf.services.search;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.services.search.result.Results;\n\n/**\n * Visitor for {@link FileInfo}\n *\n * @author Matt Coley\n */\npublic interface FileSearchVisitor extends SearchVisitor {\n\t/**\n\t * Visits a generic file.\n\t *\n\t * @param resultSink\n\t * \t\tConsumer to feed result values into, typically populating a {@link Results} instance.\n\t * @param filePath\n\t * \t\tPath to file being visited.\n\t * @param fileInfo\n\t * \t\tFile to visit.\n\t */\n\tvoid visit(@Nonnull ResultSink resultSink,\n\t\t\t   @Nonnull FilePathNode filePath,\n\t\t\t   @Nonnull FileInfo fileInfo);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/JvmClassSearchVisitor.java",
    "content": "package software.coley.recaf.services.search;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.search.result.Results;\n\n/**\n * Visitor for {@link JvmClassInfo}\n *\n * @author Matt Coley\n */\npublic interface JvmClassSearchVisitor extends SearchVisitor {\n\t/**\n\t * Visits an JVM class.\n\t *\n\t * @param resultSink\n\t * \t\tConsumer to feed result values into, typically populating a {@link Results} instance.\n\t * @param classPath\n\t * \t\tPath to class being visited.\n\t * @param classInfo\n\t * \t\tClass to visit.\n\t */\n\tvoid visit(@Nonnull ResultSink resultSink,\n\t\t\t   @Nonnull ClassPathNode classPath,\n\t\t\t   @Nonnull JvmClassInfo classInfo);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/ResultSink.java",
    "content": "package software.coley.recaf.services.search;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.path.PathNode;\n\n/**\n * Outline of a sink for {@link SearchVisitor} implementations to feed data into.\n *\n * @author Matt Coley\n */\npublic interface ResultSink {\n\t/**\n\t * @param path\n\t * \t\tPath of found value.\n\t * @param value\n\t * \t\tFound value.\n\t */\n\tvoid accept(@Nonnull PathNode<?> path, @Nonnull Object value);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/SearchFeedback.java",
    "content": "package software.coley.recaf.services.search;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.services.search.result.Result;\nimport software.coley.recaf.services.search.result.Results;\n\n/**\n * Outline of search feedback capabilities. Allows for:\n * <ul>\n *     <li>In-progress search cancellation</li>\n *     <li>Filter classes and files visited by the search</li>\n * </ul>\n *\n * @author Matt Coley\n * @see CancellableSearchFeedback Basic cancellable implementation.\n */\npublic interface SearchFeedback {\n\t/**\n\t * Default implementation that runs searches to completion, without any filtering.\n\t */\n\tSearchFeedback DEFAULT = new SearchFeedback() {\n\t};\n\n\t/**\n\t * @return {@code true} to request {@link SearchService} stops handling input to end the search early.\n\t * {@code false} to continue the search.\n\t */\n\tdefault boolean hasRequestedCancellation() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * Called before checking a given class's contents against some search query.\n\t *\n\t * @param cls\n\t * \t\tClass to consider for visitation.\n\t *\n\t * @return {@code true} to visit a class in search operations.\n\t * {@code false} to skip.\n\t */\n\tdefault boolean doVisitClass(@Nonnull ClassInfo cls) {\n\t\treturn true;\n\t}\n\n\t/**\n\t * Called before checking a given file's contents against some search query.\n\t *\n\t * @param file\n\t * \t\tFile to consider for visitation.\n\t *\n\t * @return {@code true} to visit a file in search operations.\n\t * {@code false} to skip.\n\t */\n\tdefault boolean doVisitFile(@Nonnull FileInfo file) {\n\t\treturn true;\n\t}\n\n\t/**\n\t * Called when a search query finds a matching result.\n\t *\n\t * @param result\n\t * \t\tResult to consider.\n\t *\n\t * @return {@code true} to accept the result into the final {@link Results} collection.\n\t * {@code false} to drop it.\n\t */\n\tdefault boolean doAcceptResult(@Nonnull Result<?> result) {\n\t\treturn true;\n\t}\n\n\t/**\n\t * Called when the search query completes.\n\t */\n\tdefault void onCompletion() {}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/SearchService.java",
    "content": "package software.coley.recaf.services.search;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.path.BundlePathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.path.ResourcePathNode;\nimport software.coley.recaf.path.WorkspacePathNode;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.search.query.AndroidClassQuery;\nimport software.coley.recaf.services.search.query.DeclarationQuery;\nimport software.coley.recaf.services.search.query.FileQuery;\nimport software.coley.recaf.services.search.query.JvmClassQuery;\nimport software.coley.recaf.services.search.query.NumberQuery;\nimport software.coley.recaf.services.search.query.Query;\nimport software.coley.recaf.services.search.query.ReferenceQuery;\nimport software.coley.recaf.services.search.query.StringQuery;\nimport software.coley.recaf.services.search.result.ClassReference;\nimport software.coley.recaf.services.search.result.ClassReferenceResult;\nimport software.coley.recaf.services.search.result.MemberReference;\nimport software.coley.recaf.services.search.result.MemberReferenceResult;\nimport software.coley.recaf.services.search.result.NumberResult;\nimport software.coley.recaf.services.search.result.Result;\nimport software.coley.recaf.services.search.result.Results;\nimport software.coley.recaf.services.search.result.StringResult;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.util.threading.ThreadUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.ExecutorService;\n\n/**\n * Outline for running various searches.\n *\n * @author Matt Coley\n * @see NumberQuery\n * @see ReferenceQuery\n * @see DeclarationQuery\n * @see StringQuery\n */\n@ApplicationScoped\npublic class SearchService implements Service {\n\tpublic static final String SERVICE_ID = \"search\";\n\tprivate final SearchServiceConfig config;\n\n\t@Inject\n\tpublic SearchService(SearchServiceConfig config) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to search in.\n\t * @param query\n\t * \t\tQuery of search parameters.\n\t *\n\t * @return Results of search.\n\t */\n\t@Nonnull\n\tpublic Results search(@Nonnull Workspace workspace, @Nonnull Query query) {\n\t\treturn search(workspace, Collections.singletonList(query));\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to search in.\n\t * @param query\n\t * \t\tQuery of search parameters.\n\t * @param feedback\n\t * \t\tSearch visitation feedback. Allows early cancellation of searches.\n\t *\n\t * @return Results of search.\n\t */\n\t@Nonnull\n\tpublic Results search(@Nonnull Workspace workspace, @Nonnull Query query, @Nonnull SearchFeedback feedback) {\n\t\treturn search(workspace, Collections.singletonList(query), feedback);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to search in.\n\t * @param queries\n\t * \t\tMultiple queries of search parameters.\n\t *\n\t * @return Results of search.\n\t */\n\t@Nonnull\n\tpublic Results search(@Nonnull Workspace workspace, @Nonnull List<Query> queries) {\n\t\treturn search(workspace, queries, SearchFeedback.DEFAULT);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to search in.\n\t * @param queries\n\t * \t\tMultiple queries of search parameters.\n\t * @param feedback\n\t * \t\tSearch visitation feedback. Allows early cancellation of searches.\n\t *\n\t * @return Results of search.\n\t */\n\t@Nonnull\n\tpublic Results search(@Nonnull Workspace workspace, @Nonnull List<Query> queries, @Nonnull SearchFeedback feedback) {\n\t\tResults results = new Results();\n\n\t\t// Build visitors\n\t\tAndroidClassSearchVisitor androidClassVisitorTemp = null;\n\t\tJvmClassSearchVisitor jvmClassVisitorTemp = null;\n\t\tFileSearchVisitor fileVisitorTemp = null;\n\t\tfor (Query query : queries) {\n\t\t\tif (query instanceof AndroidClassQuery androidClassQuery)\n\t\t\t\tandroidClassVisitorTemp = androidClassQuery.visitor(androidClassVisitorTemp);\n\t\t\tif (query instanceof JvmClassQuery jvmClassQuery)\n\t\t\t\tjvmClassVisitorTemp = jvmClassQuery.visitor(jvmClassVisitorTemp);\n\t\t\tif (query instanceof FileQuery fileQuery)\n\t\t\t\tfileVisitorTemp = fileQuery.visitor(fileVisitorTemp);\n\t\t}\n\t\tAndroidClassSearchVisitor androidClassVisitor = androidClassVisitorTemp;\n\t\tJvmClassSearchVisitor jvmClassVisitor = jvmClassVisitorTemp;\n\t\tFileSearchVisitor fileVisitor = fileVisitorTemp;\n\n\t\t// Run visitors on contents of workspace\n\t\tExecutorService service = ThreadPoolFactory.newFixedThreadPool(SERVICE_ID + \":\" + queries.hashCode());\n\t\tWorkspacePathNode workspaceNode = PathNodes.workspacePath(workspace);\n\t\tfor (WorkspaceResource resource : workspace.getAllResources(false))\n\t\t\tsearchResource(results, service, feedback, resource, workspaceNode,\n\t\t\t\t\tandroidClassVisitor, jvmClassVisitor, fileVisitor);\n\t\tThreadUtil.blockUntilComplete(service);\n\n\t\t// Notify feedback of search completion\n\t\tfeedback.onCompletion();\n\n\t\treturn results;\n\t}\n\n\t/**\n\t * @param results\n\t * \t\tResult container to dump into.\n\t * @param service\n\t * \t\tThread scheduler service.\n\t * @param feedback\n\t * \t\tSearch feedback mechanism <i>(To allow user cancellation and such)</i>\n\t * @param resource\n\t * \t\tResource to search within.\n\t * @param workspacePath\n\t * \t\tRoot workspace path node.\n\t * @param androidClassVisitor\n\t * \t\tAndroid class search visitor.\n\t * \t\tCan be {@code null} to skip searching respective content.\n\t * @param jvmClassVisitor\n\t * \t\tJVM class search visitor.\n\t * \t\tCan be {@code null} to skip searching respective content.\n\t * @param fileVisitor\n\t * \t\tFile search visitor.\n\t * \t\tCan be {@code null} to skip searching respective content.\n\t */\n\tprivate static void searchResource(@Nonnull Results results,\n\t                                   @Nonnull ExecutorService service,\n\t                                   @Nonnull SearchFeedback feedback,\n\t                                   @Nonnull WorkspaceResource resource,\n\t                                   @Nonnull WorkspacePathNode workspacePath,\n\t                                   @Nullable AndroidClassSearchVisitor androidClassVisitor,\n\t                                   @Nullable JvmClassSearchVisitor jvmClassVisitor,\n\t                                   @Nullable FileSearchVisitor fileVisitor) {\n\t\t// Recursively search embedded resources.\n\t\tfor (WorkspaceFileResource embeddedResource : resource.getEmbeddedResources().values()) {\n\t\t\tsearchResource(results, service, feedback, embeddedResource, workspacePath,\n\t\t\t\t\tandroidClassVisitor, jvmClassVisitor, fileVisitor);\n\t\t}\n\n\t\t// Visit android content\n\t\tResourcePathNode resourcePath = workspacePath.child(resource);\n\t\tif (androidClassVisitor != null) {\n\t\t\tfor (AndroidClassBundle bundle : resource.getAndroidClassBundles().values()) {\n\t\t\t\tBundlePathNode bundlePath = resourcePath.child(bundle);\n\t\t\t\tfor (AndroidClassInfo classInfo : bundle) {\n\t\t\t\t\tif (feedback.hasRequestedCancellation())\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tif (!feedback.doVisitClass(classInfo))\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tClassPathNode classPath = bundlePath\n\t\t\t\t\t\t\t.child(classInfo.getPackageName())\n\t\t\t\t\t\t\t.child(classInfo);\n\t\t\t\t\tservice.submit(() -> {\n\t\t\t\t\t\tif (feedback.hasRequestedCancellation())\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\tandroidClassVisitor.visit(getResultSink(results, feedback), classPath, classInfo);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Visit JVM content\n\t\tif (jvmClassVisitor != null) {\n\t\t\tresource.jvmAllClassBundleStream().forEach(bundle -> {\n\t\t\t\tBundlePathNode bundlePath = resourcePath.child(bundle);\n\t\t\t\tfor (JvmClassInfo classInfo : bundle) {\n\t\t\t\t\tif (feedback.hasRequestedCancellation())\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tif (!feedback.doVisitClass(classInfo))\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tClassPathNode classPath = bundlePath\n\t\t\t\t\t\t\t.child(classInfo.getPackageName())\n\t\t\t\t\t\t\t.child(classInfo);\n\t\t\t\t\tservice.submit(() -> {\n\t\t\t\t\t\tif (feedback.hasRequestedCancellation())\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\tjvmClassVisitor.visit(getResultSink(results, feedback), classPath, classInfo);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\t// Visit file content\n\t\tif (fileVisitor != null) {\n\t\t\tFileBundle fileBundle = resource.getFileBundle();\n\t\t\tBundlePathNode bundlePath = resourcePath.child(fileBundle);\n\t\t\tfor (FileInfo fileInfo : fileBundle) {\n\t\t\t\tif (feedback.hasRequestedCancellation())\n\t\t\t\t\tbreak;\n\t\t\t\tif (!feedback.doVisitFile(fileInfo))\n\t\t\t\t\tcontinue;\n\t\t\t\tFilePathNode filePath = bundlePath\n\t\t\t\t\t\t.child(fileInfo.getDirectoryName())\n\t\t\t\t\t\t.child(fileInfo);\n\t\t\t\tservice.submit(() -> {\n\t\t\t\t\tif (feedback.hasRequestedCancellation())\n\t\t\t\t\t\treturn;\n\t\t\t\t\tfileVisitor.visit(getResultSink(results, feedback), filePath, fileInfo);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate static ResultSink getResultSink(@Nonnull Results results, @Nullable SearchFeedback feedback) {\n\t\treturn (path, value) -> {\n\t\t\tResult<?> result = createResult(path, value);\n\t\t\tif (feedback == null || feedback.doAcceptResult(result))\n\t\t\t\tresults.add(result);\n\t\t};\n\t}\n\n\t@Nonnull\n\tprivate static Result<?> createResult(@Nonnull PathNode<?> path, @Nonnull Object value) {\n\t\tif (value instanceof Number number)\n\t\t\treturn new NumberResult(path, number);\n\t\tif (value instanceof String string)\n\t\t\treturn new StringResult(path, string);\n\t\tif (value instanceof ClassReference reference)\n\t\t\treturn new ClassReferenceResult(path, reference);\n\t\tif (value instanceof MemberReference reference)\n\t\t\treturn new MemberReferenceResult(path, reference);\n\n\t\t// Unknown value type\n\t\tthrow new UnsupportedOperationException(\"Unsupported search result value type: \" + value.getClass().getName());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic SearchServiceConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/SearchServiceConfig.java",
    "content": "package software.coley.recaf.services.search;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link SearchService}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class SearchServiceConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic SearchServiceConfig() {\n\t\tsuper(ConfigGroups.SERVICE_ANALYSIS, SearchService.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/SearchVisitor.java",
    "content": "package software.coley.recaf.services.search;\n\n/**\n * Common search visitor type.\n *\n * @author Matt Coley\n * @see AndroidClassSearchVisitor\n * @see JvmClassSearchVisitor\n * @see FileSearchVisitor\n */\npublic interface SearchVisitor {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/match/BiNumberMatcher.java",
    "content": "package software.coley.recaf.services.search.match;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Matcher outline for comparing one number to another.\n *\n * @author Matt Coley\n */\npublic interface BiNumberMatcher {\n\t/**\n\t * @param key\n\t * \t\tTarget value to match against.\n\t * @param target\n\t * \t\tValue to check.\n\t *\n\t * @return {@code true} when the target value matches the key value.\n\t */\n\tboolean test(@Nonnull Number key, @Nonnull Number target);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/match/BiStringMatcher.java",
    "content": "package software.coley.recaf.services.search.match;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Matcher outline for comparing one string to another.\n *\n * @author Matt Coley\n */\npublic interface BiStringMatcher {\n\t/**\n\t * @param key\n\t * \t\tTarget value to match against.\n\t * @param target\n\t * \t\tValue to check.\n\t *\n\t * @return {@code true} when the target value matches the key value.\n\t */\n\tboolean test(@Nonnull String key, @Nonnull String target);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/match/MultiNumberMatcher.java",
    "content": "package software.coley.recaf.services.search.match;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Collection;\n\n/**\n * Matcher outline for comparing one number to multiple numbers.\n *\n * @author Matt Coley\n */\npublic interface MultiNumberMatcher {\n\t/**\n\t * @param keys\n\t * \t\tTarget values to match against.\n\t * @param target\n\t * \t\tValue to check.\n\t *\n\t * @return {@code true} when the target value matches the key value(s).\n\t */\n\tboolean test(@Nonnull Collection<Number> keys, @Nonnull Number target);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/match/MultiStringMatcher.java",
    "content": "package software.coley.recaf.services.search.match;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Collection;\n\n/**\n * Matcher outline for comparing one string to multiple strings.\n *\n * @author Matt Coley\n */\npublic interface MultiStringMatcher {\n\t/**\n\t * @param keys\n\t * \t\tTarget values to match against.\n\t * @param target\n\t * \t\tValue to check.\n\t *\n\t * @return {@code true} when the target value matches the key value(s).\n\t */\n\tboolean test(@Nonnull Collection<String> keys, @Nonnull String target);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/match/NumberPredicate.java",
    "content": "package software.coley.recaf.services.search.match;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.function.Predicate;\n\n/**\n * Matcher implementations for numeric values.\n *\n * @author Matt Coley\n */\npublic class NumberPredicate {\n\t/** Translation key prefix */\n\tpublic static final String TRANSLATION_PREFIX = \"number.match.\";\n\tprivate final Predicate<Number> delegate;\n\tprivate final String id;\n\n\t/**\n\t * @param id\n\t * \t\tPredicate ID.\n\t * @param delegate\n\t * \t\tMatcher predicate implementation.\n\t */\n\tpublic NumberPredicate(@Nonnull String id, @Nonnull Predicate<Number> delegate) {\n\t\tthis.delegate = delegate;\n\t\tthis.id = id;\n\t}\n\n\t/**\n\t * @return Predicate ID.\n\t */\n\t@Nonnull\n\tpublic String getId() {\n\t\treturn id;\n\t}\n\n\t/**\n\t * @return Translation key for predicate.\n\t */\n\t@Nonnull\n\tpublic String getTranslationKey() {\n\t\treturn TRANSLATION_PREFIX + getId();\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to test for a match.\n\t *\n\t * @return {@code true} if the given value matches.\n\t */\n\tpublic boolean match(@Nonnull Number value) {\n\t\treturn delegate.test(value);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/match/NumberPredicateProvider.java",
    "content": "package software.coley.recaf.services.search.match;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport static software.coley.recaf.util.NumberUtil.cmp;\n\n/**\n * Provider of {@link NumberPredicate} instances.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class NumberPredicateProvider {\n\t/**\n\t * Key in {@link #newBiNumberPredicate(String, Number)} for equality matching.\n\t */\n\tpublic static String KEY_EQUAL = \"equal\";\n\t/**\n\t * Key in {@link #newBiNumberPredicate(String, Number)} for inequality matching.\n\t */\n\tpublic static String KEY_NOT = \"not\";\n\t/**\n\t * Key in {@link #newBiNumberPredicate(String, Number)} for greater-than matching.\n\t */\n\tpublic static String KEY_GREATER_THAN = \"gt\";\n\t/**\n\t * Key in {@link #newBiNumberPredicate(String, Number)} for greater-than or equal matching.\n\t */\n\tpublic static String KEY_GREATER_EQUAL_THAN = \"gte\";\n\t/**\n\t * Key in {@link #newBiNumberPredicate(String, Number)} for less-than matching.\n\t */\n\tpublic static String KEY_LESS_THAN = \"lt\";\n\t/**\n\t * Key in {@link #newBiNumberPredicate(String, Number)} for less-than or equal matching.\n\t */\n\tpublic static String KEY_LESS_EQUAL_THAN = \"lte\";\n\t/**\n\t * Key in {@link #newRangePredicate(Number, Number, boolean, boolean)} for {@code (min, max)} matching.\n\t */\n\tpublic static String KEY_RANGE_GT_LT = \"gt-lt\";\n\t/**\n\t * Key in {@link #newRangePredicate(Number, Number, boolean, boolean)} for {@code [min, max)} matching.\n\t */\n\tpublic static String KEY_RANGE_GTE_LT = \"gte-lt\";\n\t/**\n\t * Key in {@link #newRangePredicate(Number, Number, boolean, boolean)} for {@code (min, max]} matching.\n\t */\n\tpublic static String KEY_RANGE_GT_LTE = \"gt-lte\";\n\t/**\n\t * Key in {@link #newRangePredicate(Number, Number, boolean, boolean)} for {@code [min, max]} matching.\n\t */\n\tpublic static String KEY_RANGE_GTE_LTE = \"gte-lte\";\n\t/**\n\t * Key in {@link #newMultiNumberPredicate(String, Collection)} for any-single item matching.\n\t */\n\tpublic static String KEY_ANY_OF = \"any-of\";\n\n\tprivate final Map<String, BiNumberMatcher> biNumberMatchers = new ConcurrentHashMap<>();\n\tprivate final Map<String, RangeNumberMatcher> rangeNumberMatchers = new ConcurrentHashMap<>();\n\tprivate final Map<String, MultiNumberMatcher> multiNumberMatchers = new ConcurrentHashMap<>();\n\n\t@Inject\n\tpublic NumberPredicateProvider() {\n\t\tregisterBiMatcher(KEY_EQUAL, (key, value) -> cmp(key, value) == 0);\n\t\tregisterBiMatcher(KEY_NOT, (key, value) -> cmp(key, value) != 0);\n\t\tregisterBiMatcher(KEY_GREATER_THAN, (key, value) -> cmp(key, value) < 0);\n\t\tregisterBiMatcher(KEY_GREATER_EQUAL_THAN, (key, value) -> cmp(key, value) <= 0);\n\t\tregisterBiMatcher(KEY_LESS_THAN, (key, value) -> cmp(key, value) > 0);\n\t\tregisterBiMatcher(KEY_LESS_EQUAL_THAN, (key, value) -> cmp(key, value) >= 0);\n\t\tregisterRangeMatcher(KEY_RANGE_GT_LT, (lower, upper, value) -> cmp(lower, value) < 0 && cmp(upper, value) > 0);\n\t\tregisterRangeMatcher(KEY_RANGE_GTE_LT, (lower, upper, value) -> cmp(lower, value) <= 0 && cmp(upper, value) > 0);\n\t\tregisterRangeMatcher(KEY_RANGE_GT_LTE, (lower, upper, value) -> cmp(lower, value) < 0 && cmp(upper, value) >= 0);\n\t\tregisterRangeMatcher(KEY_RANGE_GTE_LTE, (lower, upper, value) -> cmp(lower, value) <= 0 && cmp(upper, value) >= 0);\n\t\tregisterMultiMatcher(KEY_ANY_OF, (keys, value) -> {\n\t\t\tfor (Number key : keys)\n\t\t\t\tif (cmp(key, value) == 0)\n\t\t\t\t\treturn true;\n\t\t\treturn false;\n\t\t});\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tUnique ID to register with.\n\t * @param matcher\n\t * \t\tMatcher implementation.\n\t *\n\t * @return {@code true} on success. {@code false} if the ID is already in-use.\n\t */\n\tpublic boolean registerBiMatcher(@Nonnull String id, @Nonnull BiNumberMatcher matcher) {\n\t\treturn biNumberMatchers.putIfAbsent(id, matcher) == null;\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tUnique ID to register with.\n\t * @param matcher\n\t * \t\tMatcher implementation.\n\t *\n\t * @return {@code true} on success. {@code false} if the ID is already in-use.\n\t */\n\tpublic boolean registerMultiMatcher(@Nonnull String id, @Nonnull MultiNumberMatcher matcher) {\n\t\treturn multiNumberMatchers.putIfAbsent(id, matcher) == null;\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tUnique ID to register with.\n\t * @param matcher\n\t * \t\tMatcher implementation.\n\t *\n\t * @return {@code true} on success. {@code false} if the ID is already in-use.\n\t */\n\tpublic boolean registerRangeMatcher(@Nonnull String id, @Nonnull RangeNumberMatcher matcher) {\n\t\treturn rangeNumberMatchers.putIfAbsent(id, matcher) == null;\n\t}\n\n\t/**\n\t * @param numbers\n\t * \t\tArray of numbers to match.\n\t *\n\t * @return Predicate to target the given numbers.\n\t */\n\t@Nonnull\n\tpublic NumberPredicate newAnyOfPredicate(@Nonnull Number... numbers) {\n\t\tList<Number> numbersList = List.of(numbers);\n\t\treturn newAnyOfPredicate(numbersList);\n\t}\n\n\t/**\n\t * @param numbers\n\t * \t\tCollection of numbers to match.\n\t *\n\t * @return Predicate to target the given numbers.\n\t */\n\t@Nonnull\n\tpublic NumberPredicate newAnyOfPredicate(@Nonnull Collection<Number> numbers) {\n\t\treturn Objects.requireNonNull(newMultiNumberPredicate(\"any-of\", numbers));\n\t}\n\n\t/**\n\t * @param lower\n\t * \t\tLower inclusive bound to match.\n\t * @param upper\n\t * \t\tUpper inclusive bound to match.\n\t *\n\t * @return Predicate to target the numbers in the given range.\n\t */\n\t@Nonnull\n\tpublic NumberPredicate newRangePredicate(@Nonnull Number lower, @Nonnull Number upper) {\n\t\treturn newRangePredicate(lower, upper, true, true);\n\t}\n\n\t/**\n\t * @param lower\n\t * \t\tLower bound to match.\n\t * @param upper\n\t * \t\tUpper bound to match.\n\t * @param inclusiveLower\n\t *        {@code true} to make the lower bound inclusive.\n\t * @param inclusiveUpper\n\t *        {@code true} to make the upper bound inclusive.\n\t *\n\t * @return Predicate to target the numbers in the given range.\n\t */\n\t@Nonnull\n\tpublic NumberPredicate newRangePredicate(@Nonnull Number lower, @Nonnull Number upper,\n\t                                         boolean inclusiveLower, boolean inclusiveUpper) {\n\t\tString id = (inclusiveLower ? \"gte\" : \"gt\") + \"-\" + (inclusiveUpper ? \"lte\" : \"lt\");\n\t\treturn Objects.requireNonNull(newRangeNumberPredicate(id, lower, upper));\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tNumber to match against.\n\t *\n\t * @return Predicate to target the given number.\n\t */\n\t@Nonnull\n\tpublic NumberPredicate newEqualsPredicate(@Nonnull Number key) {\n\t\treturn Objects.requireNonNull(newBiNumberPredicate(\"equal\", key));\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tNumber to match against.\n\t *\n\t * @return Predicate to target anything but the given number.\n\t */\n\t@Nonnull\n\tpublic NumberPredicate newNotEqualsPredicate(@Nonnull Number key) {\n\t\treturn Objects.requireNonNull(newBiNumberPredicate(\"not\", key));\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tNumber to match against.\n\t *\n\t * @return Predicate to target any number greater than the given number.\n\t */\n\t@Nonnull\n\tpublic NumberPredicate newGreaterThanPredicate(@Nonnull Number key) {\n\t\treturn Objects.requireNonNull(newBiNumberPredicate(\"gt\", key));\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tNumber to match against.\n\t *\n\t * @return Predicate to target any number greater than or equal to the given number.\n\t */\n\t@Nonnull\n\tpublic NumberPredicate newGreaterThanOrEqualPredicate(@Nonnull Number key) {\n\t\treturn Objects.requireNonNull(newBiNumberPredicate(\"gte\", key));\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tNumber to match against.\n\t *\n\t * @return Predicate to target any number less than the given number.\n\t */\n\t@Nonnull\n\tpublic NumberPredicate newLessThanPredicate(@Nonnull Number key) {\n\t\treturn Objects.requireNonNull(newBiNumberPredicate(\"lt\", key));\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tNumber to match against.\n\t *\n\t * @return Predicate to target any number less than or equal to the given number.\n\t */\n\t@Nonnull\n\tpublic NumberPredicate newLessThanOrEqualPredicate(@Nonnull Number key) {\n\t\treturn Objects.requireNonNull(newBiNumberPredicate(\"lte\", key));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMatcher unique ID.\n\t * @param key\n\t * \t\tNumber to match against.\n\t *\n\t * @return Predicate to target the given number.\n\t *\n\t * @throws NoSuchElementException\n\t * \t\tWhen no matcher implementation is registered with the given ID.\n\t */\n\t@Nullable\n\tpublic NumberPredicate newBiNumberPredicate(@Nonnull String id, @Nonnull Number key) throws NoSuchElementException {\n\t\tBiNumberMatcher matcher = biNumberMatchers.get(id);\n\t\tif (matcher != null)\n\t\t\treturn new NumberPredicate(id, target -> matcher.test(key, target));\n\t\tthrow new NoSuchElementException(\"No such single-parameter matcher: \" + id);\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMatcher unique ID.\n\t * @param lower\n\t * \t\tLower bound number to match against.\n\t * @param upper\n\t * \t\tUpper bound number to match against.\n\t *\n\t * @return Predicate to target the numbers in the given range.\n\t *\n\t * @throws NoSuchElementException\n\t * \t\tWhen no matcher implementation is registered with the given ID.\n\t */\n\t@Nullable\n\tpublic NumberPredicate newRangeNumberPredicate(@Nonnull String id, @Nonnull Number lower, @Nonnull Number upper) throws NoSuchElementException {\n\t\tRangeNumberMatcher matcher = rangeNumberMatchers.get(id);\n\t\tif (matcher != null)\n\t\t\treturn new NumberPredicate(id, target -> matcher.test(lower, upper, target));\n\t\tthrow new NoSuchElementException(\"No such ranged-parameter matcher: \" + id);\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMatcher unique ID.\n\t * @param keys\n\t * \t\tCollection of numbers to match against.\n\t *\n\t * @return Predicate to target the given numbers.\n\t *\n\t * @throws NoSuchElementException\n\t * \t\tWhen no matcher implementation is registered with the given ID.\n\t */\n\t@Nullable\n\tpublic NumberPredicate newMultiNumberPredicate(@Nonnull String id, @Nonnull Collection<Number> keys) throws NoSuchElementException {\n\t\tMultiNumberMatcher matcher = multiNumberMatchers.get(id);\n\t\tif (matcher != null)\n\t\t\treturn new NumberPredicate(id, target -> matcher.test(keys, target));\n\t\tthrow new NoSuchElementException(\"No such multi-parameter matcher: \" + id);\n\t}\n\n\t/**\n\t * @return Map of matcher keys to implementations.\n\t */\n\t@Nonnull\n\tpublic Map<String, BiNumberMatcher> getBiNumberMatchers() {\n\t\treturn Collections.unmodifiableMap(biNumberMatchers);\n\t}\n\n\t/**\n\t * @return Map of matcher keys to implementations.\n\t */\n\t@Nonnull\n\tpublic Map<String, RangeNumberMatcher> getRangeNumberMatchers() {\n\t\treturn Collections.unmodifiableMap(rangeNumberMatchers);\n\t}\n\n\t/**\n\t * @return Map of matcher keys to implementations.\n\t */\n\t@Nonnull\n\tpublic Map<String, MultiNumberMatcher> getMultiNumberMatchers() {\n\t\treturn Collections.unmodifiableMap(multiNumberMatchers);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/match/RangeNumberMatcher.java",
    "content": "package software.coley.recaf.services.search.match;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Matcher outline for comparing one number to a range of numbers.\n *\n * @author Matt Coley\n */\npublic interface RangeNumberMatcher {\n\t/**\n\t * @param lower\n\t * \t\tLower target value range to match against.\n\t * @param upper\n\t * \t\tUpper target value range to match against.\n\t * @param target\n\t * \t\tValue to check.\n\t *\n\t * @return {@code true} when the target value matches the given range.\n\t */\n\tboolean test(@Nonnull Number lower, @Nonnull Number upper, @Nonnull Number target);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/match/StringPredicate.java",
    "content": "package software.coley.recaf.services.search.match;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.function.Predicate;\n\n/**\n * Matcher implementations for string values.\n *\n * @author Matt Coley\n */\npublic class StringPredicate {\n\t/** Translation key prefix */\n\tpublic static String TRANSLATION_PREFIX = \"string.match.\";\n\tprivate final Predicate<String> delegate;\n\tprivate final String id;\n\n\t/**\n\t * @param id\n\t * \t\tPredicate ID.\n\t * @param delegate\n\t * \t\tMatcher predicate implementation.\n\t */\n\tpublic StringPredicate(@Nonnull String id, @Nonnull Predicate<String> delegate) {\n\t\tthis.delegate = delegate;\n\t\tthis.id = id;\n\t}\n\n\t/**\n\t * @return Predicate ID.\n\t */\n\t@Nonnull\n\tpublic String getId() {\n\t\treturn id;\n\t}\n\n\t/**\n\t * @return Translation key for predicate.\n\t */\n\t@Nonnull\n\tpublic String getTranslationKey() {\n\t\treturn TRANSLATION_PREFIX + getId();\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tText to test for a match.\n\t *\n\t * @return {@code true} if the given string matches with a given key value.\n\t */\n\tpublic boolean match(@Nonnull String text) {\n\t\treturn delegate.test(text);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/match/StringPredicateProvider.java",
    "content": "package software.coley.recaf.services.search.match;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.util.RegexUtil;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Provider of {@link StringPredicate} instances.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class StringPredicateProvider {\n\t/**\n\t * Key in {@link #newBiStringPredicate(String, String)} for equality matching.\n\t */\n\tpublic static final String KEY_ANYTHING = \"anything\";\n\t/**\n\t * Key in {@link #newBiStringPredicate(String, String)} for equality matching.\n\t */\n\tpublic static final String KEY_NOTHING = \"zilch\"; // Use 'zilch' instead of 'nothing' so that the natural key ordering puts it last\n\t/**\n\t * Key in {@link #newBiStringPredicate(String, String)} for equality matching.\n\t */\n\tpublic static final String KEY_EQUALS = \"equal\";\n\t/**\n\t * Key in {@link #newBiStringPredicate(String, String)} for case-insensitive equality matching.\n\t */\n\tpublic static final String KEY_EQUALS_IGNORE_CASE = \"equal-ic\";\n\t/**\n\t * Key in {@link #newBiStringPredicate(String, String)} for containment matching.\n\t */\n\tpublic static final String KEY_CONTAINS = \"contains\";\n\t/**\n\t * Key in {@link #newBiStringPredicate(String, String)} for case-insensitive containment matching.\n\t */\n\tpublic static final String KEY_CONTAINS_IGNORE_CASE = \"contains-ic\";\n\t/**\n\t * Key in {@link #newBiStringPredicate(String, String)} for prefix matching.\n\t */\n\tpublic static final String KEY_STARTS_WITH = \"starts\";\n\t/**\n\t * Key in {@link #newBiStringPredicate(String, String)} for case-insensitive prefix matching.\n\t */\n\tpublic static final String KEY_STARTS_WITH_IGNORE_CASE = \"starts-ic\";\n\t/**\n\t * Key in {@link #newBiStringPredicate(String, String)} for suffix matching.\n\t */\n\tpublic static final String KEY_ENDS_WITH = \"ends\";\n\t/**\n\t * Key in {@link #newBiStringPredicate(String, String)} for case-insensitive suffix matching.\n\t */\n\tpublic static final String KEY_ENDS_WITH_IGNORE_CASE = \"ends-ic\";\n\t/**\n\t * Key in {@link #newBiStringPredicate(String, String)} for partial regex matching.\n\t */\n\tpublic static final String KEY_REGEX_PARTIAL = \"regex-partial\";\n\t/**\n\t * Key in {@link #newBiStringPredicate(String, String)} for full regex matching.\n\t */\n\tpublic static final String KEY_REFEX_FULL = \"regex-full\";\n\tprivate static final BiStringMatcher MATHER_ANYTHING = (a, b) -> true;\n\tprivate static final BiStringMatcher MATHER_NOTHING = (a, b) -> false;\n\tprivate static final StringPredicate PREDICATE_ANYTHING = new StringPredicate(KEY_ANYTHING, a -> true);\n\tprivate static final StringPredicate PREDICATE_NOTHING = new StringPredicate(KEY_NOTHING, a -> false);\n\tprivate final Map<String, BiStringMatcher> biStringMatchers = new ConcurrentHashMap<>();\n\tprivate final Map<String, MultiStringMatcher> multiStringMatchers = new ConcurrentHashMap<>();\n\n\t@Inject\n\tpublic StringPredicateProvider() {\n\t\tregisterBiMatcher(KEY_ANYTHING, MATHER_ANYTHING);\n\t\tregisterBiMatcher(KEY_NOTHING, MATHER_NOTHING);\n\t\tregisterBiMatcher(KEY_EQUALS, String::equals);\n\t\tregisterBiMatcher(KEY_EQUALS_IGNORE_CASE, String::equalsIgnoreCase);\n\t\tregisterBiMatcher(KEY_CONTAINS, (key, value) -> value.contains(key));\n\t\tregisterBiMatcher(KEY_CONTAINS_IGNORE_CASE, (key, value) -> value.toLowerCase().contains(key.toLowerCase()));\n\t\tregisterBiMatcher(KEY_STARTS_WITH, (key, value) -> value.startsWith(key));\n\t\tregisterBiMatcher(KEY_STARTS_WITH_IGNORE_CASE, (key, value) -> value.toLowerCase().startsWith(key.toLowerCase()));\n\t\tregisterBiMatcher(KEY_ENDS_WITH, (key, value) -> value.endsWith(key));\n\t\tregisterBiMatcher(KEY_ENDS_WITH_IGNORE_CASE, (key, value) -> value.toLowerCase().endsWith(key.toLowerCase()));\n\t\tregisterBiMatcher(KEY_REGEX_PARTIAL, (key, value) -> {\n\t\t\ttry {\n\t\t\t\treturn RegexUtil.getMatcher(key, value).find();\n\t\t\t} catch (Throwable t) {\n\t\t\t\t// Invalid regex pattern, logged by regex-util\n\t\t\t\treturn false;\n\t\t\t}\n\t\t});\n\t\tregisterBiMatcher(KEY_REFEX_FULL, (key, value) -> {\n\t\t\ttry {\n\t\t\t\treturn RegexUtil.getMatcher(key, value).matches();\n\t\t\t} catch (Throwable t) {\n\t\t\t\t// Invalid regex pattern, logged by regex-util\n\t\t\t\treturn false;\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tUnique ID to register with.\n\t * @param matcher\n\t * \t\tMatcher implementation.\n\t *\n\t * @return {@code true} on success. {@code false} if the ID is already in-use.\n\t */\n\tpublic boolean registerBiMatcher(@Nonnull String id, @Nonnull BiStringMatcher matcher) {\n\t\treturn biStringMatchers.putIfAbsent(id, matcher) == null;\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tUnique ID to register with.\n\t * @param matcher\n\t * \t\tMatcher implementation.\n\t *\n\t * @return {@code true} on success. {@code false} if the ID is already in-use.\n\t */\n\tpublic boolean registerMultiMatcher(@Nonnull String id, @Nonnull MultiStringMatcher matcher) {\n\t\treturn multiStringMatchers.putIfAbsent(id, matcher) == null;\n\t}\n\n\t/**\n\t * @return Predicate that matches anything.\n\t */\n\t@Nonnull\n\tpublic StringPredicate newAnythingPredicate() {\n\t\treturn PREDICATE_ANYTHING;\n\t}\n\n\t/**\n\t * @return Predicate that matches nothing.\n\t */\n\t@Nonnull\n\tpublic StringPredicate newNothingPredicate() {\n\t\treturn PREDICATE_NOTHING;\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tString to match against, case-sensitive.\n\t *\n\t * @return Predicate to target the given string.\n\t */\n\t@Nonnull\n\tpublic StringPredicate newEqualPredicate(@Nonnull String key) {\n\t\treturn newEqualPredicate(key, true);\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tString to match against.\n\t * @param caseSensitive\n\t * \t\tWhether the match should be case-sensitive or not.\n\t *\n\t * @return Predicate to target the given string.\n\t */\n\t@Nonnull\n\tpublic StringPredicate newEqualPredicate(@Nonnull String key, boolean caseSensitive) {\n\t\treturn Objects.requireNonNull(newBiStringPredicate(caseSensitive ? \"equal\" : \"equal-ic\", key));\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tString to match against, case-sensitive.\n\t *\n\t * @return Predicate to target the given string.\n\t */\n\t@Nonnull\n\tpublic StringPredicate newContainsPredicate(@Nonnull String key) {\n\t\treturn newContainsPredicate(key, true);\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tString to match against.\n\t * @param caseSensitive\n\t * \t\tWhether the match should be case-sensitive or not.\n\t *\n\t * @return Predicate to target the given string.\n\t */\n\t@Nonnull\n\tpublic StringPredicate newContainsPredicate(@Nonnull String key, boolean caseSensitive) {\n\t\treturn Objects.requireNonNull(newBiStringPredicate(caseSensitive ? \"contains\" : \"contains-ic\", key));\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tString to match against, case-sensitive.\n\t *\n\t * @return Predicate to target the given string.\n\t */\n\t@Nonnull\n\tpublic StringPredicate newStartsWithPredicate(@Nonnull String key) {\n\t\treturn newStartsWithPredicate(key, true);\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tString to match against.\n\t * @param caseSensitive\n\t * \t\tWhether the match should be case-sensitive or not.\n\t *\n\t * @return Predicate to target the given string.\n\t */\n\t@Nonnull\n\tpublic StringPredicate newStartsWithPredicate(@Nonnull String key, boolean caseSensitive) {\n\t\treturn Objects.requireNonNull(newBiStringPredicate(caseSensitive ? \"starts\" : \"starts-ic\", key));\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tString to match against, case-sensitive.\n\t *\n\t * @return Predicate to target the given string.\n\t */\n\t@Nonnull\n\tpublic StringPredicate newEndsWithPredicate(@Nonnull String key) {\n\t\treturn newEndsWithPredicate(key, true);\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tString to match against.\n\t * @param caseSensitive\n\t * \t\tWhether the match should be case-sensitive or not.\n\t *\n\t * @return Predicate to target the given string.\n\t */\n\t@Nonnull\n\tpublic StringPredicate newEndsWithPredicate(@Nonnull String key, boolean caseSensitive) {\n\t\treturn Objects.requireNonNull(newBiStringPredicate(caseSensitive ? \"ends\" : \"ends-ic\", key));\n\t}\n\n\t/**\n\t * @param regex\n\t * \t\tPattern to match against. Only part of the target string needs to match.\n\t *\n\t * @return Predicate to target the given string.\n\t */\n\t@Nonnull\n\tpublic StringPredicate newPartialRegexPredicate(@Nonnull String regex) {\n\t\treturn Objects.requireNonNull(newBiStringPredicate(\"regex-partial\", regex));\n\t}\n\n\t/**\n\t * @param regex\n\t * \t\tPattern to match against. The entire target string needs to match.\n\t *\n\t * @return Predicate to target the given string.\n\t */\n\t@Nonnull\n\tpublic StringPredicate newFullRegexPredicate(@Nonnull String regex) {\n\t\treturn Objects.requireNonNull(newBiStringPredicate(\"regex-full\", regex));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMatcher unique ID.\n\t * @param key\n\t * \t\tString to match against.\n\t *\n\t * @return Predicate to target the given string.\n\t *\n\t * @throws NoSuchElementException\n\t * \t\tWhen no matcher implementation is registered with the given ID.\n\t */\n\t@Nullable\n\tpublic StringPredicate newBiStringPredicate(@Nonnull String id, @Nonnull String key) throws NoSuchElementException {\n\t\tBiStringMatcher matcher = biStringMatchers.get(id);\n\t\tif (matcher != null)\n\t\t\treturn new StringPredicate(id, target -> matcher.test(key, target));\n\t\tthrow new NoSuchElementException(\"No such single-parameter matcher: \" + id);\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMatcher unique ID.\n\t * @param keys\n\t * \t\tCollection of strings to match against.\n\t *\n\t * @return Predicate to target the given strings.\n\t *\n\t * @throws NoSuchElementException\n\t * \t\tWhen no matcher implementation is registered with the given ID.\n\t */\n\t@Nullable\n\tpublic StringPredicate newMultiStringPredicate(@Nonnull String id, @Nonnull Collection<String> keys) throws NoSuchElementException {\n\t\tMultiStringMatcher matcher = multiStringMatchers.get(id);\n\t\tif (matcher != null)\n\t\t\treturn new StringPredicate(id, target -> matcher.test(keys, target));\n\t\tthrow new NoSuchElementException(\"No such multi-parameter matcher: \" + id);\n\t}\n\n\t/**\n\t * @return Map of matcher keys to implementations.\n\t */\n\t@Nonnull\n\tpublic Map<String, BiStringMatcher> getBiStringMatchers() {\n\t\treturn Collections.unmodifiableMap(biStringMatchers);\n\t}\n\n\t/**\n\t * @return Map of matcher keys to implementations.\n\t */\n\t@Nonnull\n\tpublic Map<String, MultiStringMatcher> getMultiStringMatchers() {\n\t\treturn Collections.unmodifiableMap(multiStringMatchers);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/query/AbstractValueQuery.java",
    "content": "package software.coley.recaf.services.search.query;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.*;\nimport org.objectweb.asm.tree.InsnNode;\nimport org.objectweb.asm.tree.IntInsnNode;\nimport org.objectweb.asm.tree.InvokeDynamicInsnNode;\nimport org.objectweb.asm.tree.LdcInsnNode;\nimport org.slf4j.Logger;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.annotation.BasicAnnotationInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.AnnotationPathNode;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.search.JvmClassSearchVisitor;\nimport software.coley.recaf.services.search.ResultSink;\nimport software.coley.recaf.util.visitors.IndexCountingMethodVisitor;\n\n/**\n * General value search.\n *\n * @author Matt Coley\n * @see StringQuery\n * @see NumberQuery\n */\npublic abstract class AbstractValueQuery implements JvmClassQuery, FileQuery {\n\tprivate static final Number[] OP_TO_VALUE = {\n\t\t\t0, // NOP\n\t\t\t0, // NULL\n\t\t\t-1, 0, 1, 2, 3, 4, 5, // ICONST_X\n\t\t\t0L, 1L, // LCONST_X\n\t\t\t0F, 1F, 2F, // FCONST_X\n\t\t\t0D, 1D // DCONST_X\n\t};\n\n\t// TODO: Implement android query when android capabilities are fleshed out enough to have comparable\n\t//    search capabilities in method code\n\n\tprotected abstract boolean isMatch(Object value);\n\n\t@Nonnull\n\t@Override\n\tpublic JvmClassSearchVisitor visitor(@Nullable JvmClassSearchVisitor delegate) {\n\t\treturn new JvmVisitor(delegate);\n\t}\n\n\t/**\n\t * Points {@link #visitor(JvmClassSearchVisitor)} to {@link AsmClassValueVisitor}\n\t */\n\tprivate class JvmVisitor implements JvmClassSearchVisitor {\n\t\tprivate final JvmClassSearchVisitor delegate;\n\n\t\tprivate JvmVisitor(@Nullable JvmClassSearchVisitor delegate) {\n\t\t\tthis.delegate = delegate;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visit(@Nonnull ResultSink resultSink,\n\t\t\t\t\t\t  @Nonnull ClassPathNode classPath,\n\t\t\t\t\t\t  @Nonnull JvmClassInfo classInfo) {\n\t\t\tif (delegate != null) delegate.visit(resultSink, classPath, classInfo);\n\n\t\t\tclassInfo.getClassReader().accept(new AsmClassValueVisitor(resultSink, classPath, classInfo), 0);\n\t\t}\n\t}\n\n\t/**\n\t * Visits values in classes.\n\t */\n\tprivate class AsmClassValueVisitor extends ClassVisitor {\n\t\tprivate final Logger logger = Logging.get(AsmClassValueVisitor.class);\n\t\tprivate final ResultSink resultSink;\n\t\tprivate final ClassPathNode classPath;\n\t\tprivate final JvmClassInfo classInfo;\n\n\t\tprotected AsmClassValueVisitor(@Nonnull ResultSink resultSink,\n\t\t\t\t\t\t\t\t\t   @Nonnull ClassPathNode classPath,\n\t\t\t\t\t\t\t\t\t   @Nonnull JvmClassInfo classInfo) {\n\t\t\tsuper(RecafConstants.getAsmVersion());\n\t\t\tthis.resultSink = resultSink;\n\t\t\tthis.classPath = classPath;\n\t\t\tthis.classInfo = classInfo;\n\t\t}\n\n\t\t@Override\n\t\tpublic FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {\n\t\t\tFieldVisitor fv = super.visitField(access, name, desc, signature, value);\n\t\t\tFieldMember fieldMember = classInfo.getDeclaredField(name, desc);\n\t\t\tif (fieldMember != null) {\n\t\t\t\tif (isMatch(value))\n\t\t\t\t\tresultSink.accept(classPath.child(fieldMember), value);\n\t\t\t\treturn new AsmFieldValueVisitor(fv, fieldMember, resultSink, classPath);\n\t\t\t} else {\n\t\t\t\tlogger.error(\"Failed to lookup field for query: {}.{} {}\", classInfo.getName(), name, desc);\n\t\t\t\treturn fv;\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {\n\t\t\tMethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);\n\t\t\tMethodMember methodMember = classInfo.getDeclaredMethod(name, desc);\n\t\t\tif (methodMember != null) {\n\t\t\t\treturn new AsmMethodValueVisitor(mv, methodMember, resultSink, classPath);\n\t\t\t} else {\n\t\t\t\tlogger.error(\"Failed to lookup method for query: {}.{}{}\", classInfo.getName(), name, desc);\n\t\t\t\treturn mv;\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String desc, boolean visible) {\n\t\t\tAnnotationVisitor av = super.visitAnnotation(desc, visible);\n\t\t\tAnnotationInfo annotationInfo = classInfo.getAnnotations().stream()\n\t\t\t\t\t.filter(ai -> ai.getDescriptor().equals(desc))\n\t\t\t\t\t.findFirst()\n\t\t\t\t\t.orElseGet(() -> new BasicAnnotationInfo(visible, desc));\n\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink,\n\t\t\t\t\tclassPath.child(annotationInfo));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\tAnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, desc, visible);\n\t\t\tAnnotationInfo annotationInfo = classInfo.getAnnotations().stream()\n\t\t\t\t\t.filter(ai -> ai.getDescriptor().equals(desc))\n\t\t\t\t\t.findFirst()\n\t\t\t\t\t.orElseGet(() -> new BasicAnnotationInfo(visible, desc));\n\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink,\n\t\t\t\t\tclassPath.child(annotationInfo\n\t\t\t\t\t\t\t.withTypeInfo(typeRef, typePath)));\n\t\t}\n\t}\n\n\t/**\n\t * Visits values in fields.\n\t */\n\tprivate class AsmFieldValueVisitor extends FieldVisitor {\n\t\tprivate final ResultSink resultSink;\n\t\tprivate final ClassMemberPathNode memberPath;\n\n\t\tpublic AsmFieldValueVisitor(@Nullable FieldVisitor delegate,\n\t\t\t\t\t\t\t\t\t@Nonnull FieldMember fieldMember,\n\t\t\t\t\t\t\t\t\t@Nonnull ResultSink resultSink,\n\t\t\t\t\t\t\t\t\t@Nonnull ClassPathNode classLocation) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), delegate);\n\t\t\tthis.resultSink = resultSink;\n\t\t\tthis.memberPath = classLocation.child(fieldMember);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String desc, boolean visible) {\n\t\t\tAnnotationVisitor av = super.visitAnnotation(desc, visible);\n\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink,\n\t\t\t\t\tmemberPath.childAnnotation(new BasicAnnotationInfo(visible, desc)));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\tAnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink,\n\t\t\t\t\tmemberPath.childAnnotation(new BasicAnnotationInfo(visible, desc)\n\t\t\t\t\t\t\t.withTypeInfo(typeRef, typePath)));\n\t\t}\n\t}\n\n\t/**\n\t * Visits values in methods.\n\t */\n\tprivate class AsmMethodValueVisitor extends IndexCountingMethodVisitor {\n\t\tprivate final ResultSink resultSink;\n\t\tprivate final ClassMemberPathNode memberPath;\n\n\t\tpublic AsmMethodValueVisitor(@Nullable MethodVisitor delegate,\n\t\t\t\t\t\t\t\t\t @Nonnull MethodMember methodMember,\n\t\t\t\t\t\t\t\t\t @Nonnull ResultSink resultSink,\n\t\t\t\t\t\t\t\t\t @Nonnull ClassPathNode classLocation) {\n\t\t\tsuper(delegate);\n\t\t\tthis.resultSink = resultSink;\n\t\t\tthis.memberPath = classLocation.child(methodMember);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitInvokeDynamicInsn(String name, String desc, Handle bsmHandle,\n\t\t\t\t\t\t\t\t\t\t   Object... bsmArgs) {\n\t\t\tsuper.visitInvokeDynamicInsn(name, desc, bsmHandle, bsmArgs);\n\t\t\tfor (Object bsmArg : bsmArgs) {\n\t\t\t\tif (isMatch(bsmArg)) {\n\t\t\t\t\tInvokeDynamicInsnNode indy = new InvokeDynamicInsnNode(name, desc, bsmHandle, bsmArgs);\n\t\t\t\t\tresultSink.accept(memberPath.childInsn(indy, index), bsmArg);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitInsn(int opcode) {\n\t\t\tsuper.visitInsn(opcode);\n\t\t\tif (opcode >= Opcodes.ICONST_M1 && opcode <= Opcodes.DCONST_1) {\n\t\t\t\tNumber value = OP_TO_VALUE[opcode];\n\t\t\t\tif (isMatch(value))\n\t\t\t\t\tresultSink.accept(memberPath.childInsn(new InsnNode(opcode), index), value);\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitIntInsn(int opcode, int operand) {\n\t\t\tsuper.visitIntInsn(opcode, operand);\n\t\t\tif (opcode != Opcodes.NEWARRAY && isMatch(operand))\n\t\t\t\tresultSink.accept(memberPath.childInsn(new IntInsnNode(opcode, operand), index), operand);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLdcInsn(Object value) {\n\t\t\tsuper.visitLdcInsn(value);\n\t\t\tif (isMatch(value))\n\t\t\t\tresultSink.accept(memberPath.childInsn(new LdcInsnNode(value), index), value);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {\n\t\t\tsuper.visitLookupSwitchInsn(dflt, keys, labels);\n\t\t\tfor (int key : keys) {\n\t\t\t\tif (isMatch(key)) {\n\t\t\t\t\tresultSink.accept(memberPath.childInsn(new InsnNode(Opcodes.LOOKUPSWITCH), index), key);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {\n\t\t\tsuper.visitTableSwitchInsn(min, max, dflt, labels);\n\t\t\tfor (int i = min; i <= max; i++) {\n\t\t\t\tif (isMatch(i)) {\n\t\t\t\t\tresultSink.accept(memberPath.childInsn(new InsnNode(Opcodes.TABLESWITCH), index), i);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String desc, boolean visible) {\n\t\t\tAnnotationVisitor av = super.visitAnnotation(desc, visible);\n\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink,\n\t\t\t\t\tmemberPath.childAnnotation(new BasicAnnotationInfo(visible, desc)));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\tAnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink,\n\t\t\t\t\tmemberPath.childAnnotation(new BasicAnnotationInfo(visible, desc)\n\t\t\t\t\t\t\t.withTypeInfo(typeRef, typePath)));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotationDefault() {\n\t\t\tAnnotationVisitor av = super.visitAnnotationDefault();\n\t\t\treturn new AnnotationValueVisitor(av, true, resultSink, memberPath);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {\n\t\t\tAnnotationVisitor av = super.visitParameterAnnotation(parameter, desc, visible);\n\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink,\n\t\t\t\t\tmemberPath.childAnnotation(new BasicAnnotationInfo(visible, desc)));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\tAnnotationVisitor av = super.visitInsnAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink,\n\t\t\t\t\tmemberPath.childAnnotation(new BasicAnnotationInfo(visible, desc)\n\t\t\t\t\t\t\t.withTypeInfo(typeRef, typePath)));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t boolean visible) {\n\t\t\tAnnotationVisitor av = super.visitTryCatchAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink,\n\t\t\t\t\tmemberPath.childAnnotation(new BasicAnnotationInfo(visible, desc)\n\t\t\t\t\t\t\t.withTypeInfo(typeRef, typePath)));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  Label[] start, Label[] end, int[] index,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  String desc, boolean visible) {\n\t\t\tAnnotationVisitor av = super.visitLocalVariableAnnotation(typeRef, typePath, start, end,\n\t\t\t\t\tindex, desc, visible);\n\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink,\n\t\t\t\t\tmemberPath.childAnnotation(new BasicAnnotationInfo(visible, desc)\n\t\t\t\t\t\t\t.withTypeInfo(typeRef, typePath)));\n\t\t}\n\t}\n\n\t/**\n\t * Visits values in annotations.\n\t */\n\tprivate class AnnotationValueVisitor extends AnnotationVisitor {\n\t\tprivate final ResultSink resultSink;\n\t\tprivate final PathNode<?> currentAnnoLocation;\n\t\tprivate final boolean visible;\n\n\t\tpublic AnnotationValueVisitor(@Nullable AnnotationVisitor delegate,\n\t\t\t\t\t\t\t\t\t  boolean visible,\n\t\t\t\t\t\t\t\t\t  @Nonnull ResultSink resultSink,\n\t\t\t\t\t\t\t\t\t  @Nonnull PathNode<?> currentAnnoLocation) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), delegate);\n\t\t\tthis.visible = visible;\n\t\t\tthis.resultSink = resultSink;\n\t\t\tthis.currentAnnoLocation = currentAnnoLocation;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String name, String descriptor) {\n\t\t\tAnnotationVisitor av = super.visitAnnotation(name, descriptor);\n\t\t\tif (currentAnnoLocation.getValue() instanceof Annotated annotated) {\n\t\t\t\tAnnotationInfo annotationInfo = annotated.getAnnotations().stream()\n\t\t\t\t\t\t.filter(ai -> ai.getDescriptor().equals(descriptor))\n\t\t\t\t\t\t.findFirst()\n\t\t\t\t\t\t.orElseGet(() -> new BasicAnnotationInfo(visible, descriptor));\n\t\t\t\tif (currentAnnoLocation instanceof ClassPathNode classPath) {\n\t\t\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink,\n\t\t\t\t\t\t\tclassPath.child(annotationInfo));\n\t\t\t\t} else if (currentAnnoLocation instanceof ClassMemberPathNode memberPath) {\n\t\t\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink,\n\t\t\t\t\t\t\tmemberPath.childAnnotation(annotationInfo));\n\t\t\t\t} else if (currentAnnoLocation instanceof AnnotationPathNode annotationPath) {\n\t\t\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink,\n\t\t\t\t\t\t\tannotationPath.child(annotationInfo));\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow new IllegalStateException(\"Unsupported non-annotatable path: \" + currentAnnoLocation);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitArray(String name) {\n\t\t\tAnnotationVisitor av = super.visitArray(name);\n\t\t\treturn new AnnotationValueVisitor(av, visible, resultSink, currentAnnoLocation);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visit(String name, Object value) {\n\t\t\tsuper.visit(name, value);\n\t\t\tif (isMatch(value))\n\t\t\t\tresultSink.accept(currentAnnoLocation, value);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/query/AndroidClassQuery.java",
    "content": "package software.coley.recaf.services.search.query;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.services.search.AndroidClassSearchVisitor;\n\n/**\n * Query targeting {@link AndroidClassInfo}.\n *\n * @author Matt Coley\n */\npublic interface AndroidClassQuery extends Query {\n\t@Nonnull\n\tAndroidClassSearchVisitor visitor(@Nullable AndroidClassSearchVisitor delegate);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/query/DeclarationQuery.java",
    "content": "package software.coley.recaf.services.search.query;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.search.AndroidClassSearchVisitor;\nimport software.coley.recaf.services.search.JvmClassSearchVisitor;\nimport software.coley.recaf.services.search.ResultSink;\nimport software.coley.recaf.services.search.match.StringPredicate;\nimport software.coley.recaf.services.search.result.MemberReference;\nimport software.coley.recaf.util.StringUtil;\n\n/**\n * Declaration search implementation.\n *\n * @author Matt Coley\n */\npublic class DeclarationQuery implements JvmClassQuery, AndroidClassQuery {\n\tprivate final StringPredicate ownerPredicate;\n\tprivate final StringPredicate namePredicate;\n\tprivate final StringPredicate descriptorPredicate;\n\n\t/**\n\t * Member declaration query.\n\t * <p>\n\t * Do note that each target value is nullable/optional.\n\t * Including only the owner and {@code null} for the name/desc will yield declarations to all members in the class.\n\t * Including only the desc will yield declarations of all members with that desc in all classes.\n\t *\n\t * @param ownerPredicate\n\t * \t\tString matching predicate for comparison against declared member owners.\n\t *        {@code null} to ignore matching against owner names.\n\t * @param namePredicate\n\t * \t\tString matching predicate for comparison against declared member names.\n\t *        {@code null} to ignore matching against member names.\n\t * @param descriptorPredicate\n\t * \t\tString matching predicate for comparison against declared member descriptors.\n\t *        {@code null} to ignore matching against member descriptors.\n\t */\n\tpublic DeclarationQuery(@Nullable StringPredicate ownerPredicate,\n\t                        @Nullable StringPredicate namePredicate,\n\t                        @Nullable StringPredicate descriptorPredicate) {\n\t\tthis.ownerPredicate = ownerPredicate;\n\t\tthis.namePredicate = namePredicate;\n\t\tthis.descriptorPredicate = descriptorPredicate;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic AndroidClassSearchVisitor visitor(@Nullable AndroidClassSearchVisitor delegate) {\n\t\treturn (resultSink, currentLocation, classInfo) -> {\n\t\t\tif (delegate != null)\n\t\t\t\tdelegate.visit(resultSink, currentLocation, classInfo);\n\t\t\tscan(resultSink, currentLocation);\n\t\t};\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic JvmClassSearchVisitor visitor(@Nullable JvmClassSearchVisitor delegate) {\n\t\treturn (resultSink, currentLocation, classInfo) -> {\n\t\t\tif (delegate != null)\n\t\t\t\tdelegate.visit(resultSink, currentLocation, classInfo);\n\t\t\tscan(resultSink, currentLocation);\n\t\t};\n\t}\n\n\tprivate boolean isMemberRefMatch(@Nonnull String owner, @Nonnull String name, @Nonnull String desc) {\n\t\t// If our query predicates are null, that field can skip comparison, and we move on to the next.\n\t\t// If all of our non-null query arguments match the given parameters, we have a match.\n\t\tif (ownerPredicate == null || StringUtil.isNullOrEmpty(owner) || ownerPredicate.match(owner))\n\t\t\tif (namePredicate == null || StringUtil.isNullOrEmpty(name) || namePredicate.match(name))\n\t\t\t\treturn descriptorPredicate == null || StringUtil.isNullOrEmpty(desc) || descriptorPredicate.match(desc);\n\n\t\treturn false;\n\t}\n\n\tprivate void scan(@Nonnull ResultSink resultSink, @Nonnull ClassPathNode classPath) {\n\t\tClassInfo classInfo = classPath.getValue();\n\t\tfor (FieldMember field : classInfo.getFields()) {\n\t\t\tString owner = classInfo.getName();\n\t\t\tString name = field.getName();\n\t\t\tString desc = field.getDescriptor();\n\t\t\tif (isMemberRefMatch(owner, name, desc))\n\t\t\t\tresultSink.accept(classPath.child(field), new MemberReference(owner, name, desc));\n\t\t}\n\t\tfor (MethodMember method : classInfo.getMethods()) {\n\t\t\tString owner = classInfo.getName();\n\t\t\tString name = method.getName();\n\t\t\tString desc = method.getDescriptor();\n\t\t\tif (isMemberRefMatch(owner, name, desc))\n\t\t\t\tresultSink.accept(classPath.child(method), new MemberReference(owner, name, desc));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/query/FileQuery.java",
    "content": "package software.coley.recaf.services.search.query;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.services.search.FileSearchVisitor;\n\n/**\n * Query targeting {@link FileInfo}.\n *\n * @author Matt Coley\n */\npublic interface FileQuery extends Query {\n\t@Nonnull\n\tFileSearchVisitor visitor(@Nullable FileSearchVisitor delegate);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/query/InstructionQuery.java",
    "content": "package software.coley.recaf.services.search.query;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.InstructionPathNode;\nimport software.coley.recaf.services.search.JvmClassSearchVisitor;\nimport software.coley.recaf.services.search.match.StringPredicate;\nimport software.coley.recaf.util.BlwUtil;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Instruction text search implementation.\n *\n * @author Matt Coley\n */\npublic class InstructionQuery implements JvmClassQuery {\n\tprivate final List<StringPredicate> predicates;\n\n\t/**\n\t * @param predicates\n\t * \t\tList of predicates, where each entry matches a single line of disassembled instruction text.\n\t */\n\tpublic InstructionQuery(@Nonnull List<StringPredicate> predicates) {\n\t\tthis.predicates = predicates;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic JvmClassSearchVisitor visitor(@Nullable JvmClassSearchVisitor delegate) {\n\t\treturn (resultSink, classPath, classInfo) -> {\n\t\t\tClassNode node = new ClassNode();\n\t\t\tclassInfo.getClassReader().accept(node, ClassReader.SKIP_FRAMES);\n\t\t\tList<String> matched = new ArrayList<>(predicates.size());\n\t\t\tfor (MethodNode method : node.methods) {\n\t\t\t\tif (method.instructions == null)\n\t\t\t\t\tcontinue;\n\t\t\t\tClassMemberPathNode memberPath = classPath.child(method.name, method.desc);\n\t\t\t\tif (memberPath == null)\n\t\t\t\t\tcontinue;\n\t\t\t\tmatched.clear();\n\t\t\t\tfor (int i = 0; i < method.instructions.size() - predicates.size(); i++) {\n\t\t\t\t\tfor (int j = 0; j < predicates.size(); j++) {\n\t\t\t\t\t\tint line = i + j;\n\n\t\t\t\t\t\t// This utility call maps instructions to BLW ones, and passes them to JASM\n\t\t\t\t\t\t// so the format should match what you see in the assembler, barring labels\n\t\t\t\t\t\t// and other debug info.\n\t\t\t\t\t\tString disassembled = BlwUtil.toString(method.instructions.get(line));\n\t\t\t\t\t\tif (!predicates.get(j).match(disassembled)) {\n\t\t\t\t\t\t\tmatched.clear();\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tmatched.add(disassembled);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Add result if we matched all predicates.\n\t\t\t\t\tif (matched.size() == predicates.size()) {\n\t\t\t\t\t\tInstructionPathNode path = memberPath.childInsn(method.instructions.get(i), i);\n\t\t\t\t\t\tresultSink.accept(path, String.join(\"\\n\", matched));\n\t\t\t\t\t\tmatched.clear();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/query/JvmClassQuery.java",
    "content": "package software.coley.recaf.services.search.query;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.search.JvmClassSearchVisitor;\n\n/**\n * Query targeting {@link JvmClassInfo}.\n *\n * @author Matt Coley\n */\npublic interface JvmClassQuery extends Query {\n\t@Nonnull\n\tJvmClassSearchVisitor visitor(@Nullable JvmClassSearchVisitor delegate);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/query/NumberQuery.java",
    "content": "package software.coley.recaf.services.search.query;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport regexodus.Matcher;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.services.search.FileSearchVisitor;\nimport software.coley.recaf.services.search.ResultSink;\nimport software.coley.recaf.services.search.match.NumberPredicate;\nimport software.coley.recaf.util.NumberUtil;\n\nimport static software.coley.recaf.util.RegexUtil.getMatcher;\n\n/**\n * Number search implementation.\n *\n * @author Matt Coley\n */\npublic class NumberQuery extends AbstractValueQuery {\n\tprivate final NumberPredicate predicate;\n\n\t/**\n\t * @param predicate\n\t * \t\tNumber matching predicate.\n\t */\n\tpublic NumberQuery(@Nonnull NumberPredicate predicate) {\n\t\tthis.predicate = predicate;\n\t}\n\n\t@Override\n\tprotected boolean isMatch(Object value) {\n\t\tif (value instanceof Number number)\n\t\t\treturn predicate.match(number);\n\t\treturn false;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic FileSearchVisitor visitor(@Nullable FileSearchVisitor delegate) {\n\t\treturn new FileVisitor(delegate);\n\t}\n\n\t/**\n\t * Points {@link #visitor(FileSearchVisitor)} to file content.\n\t */\n\tprivate class FileVisitor implements FileSearchVisitor {\n\t\tprivate final FileSearchVisitor delegate;\n\n\t\tprivate FileVisitor(FileSearchVisitor delegate) {\n\t\t\tthis.delegate = delegate;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visit(@Nonnull ResultSink resultSink,\n\t\t\t\t\t\t  @Nonnull FilePathNode filePath,\n\t\t\t\t\t\t  @Nonnull FileInfo fileInfo) {\n\t\t\tif (delegate != null) delegate.visit(resultSink, filePath, fileInfo);\n\n\t\t\t// Search text files text content on a line by line basis\n\t\t\tif (fileInfo.isTextFile()) {\n\t\t\t\tString text = fileInfo.asTextFile().getText();\n\n\t\t\t\t// Split by single newline (including goofy carriage returns)\n\t\t\t\tString[] lines = text.split(\"\\\\r?\\\\n\\\\r?\");\n\t\t\t\tfor (int i = 0; i < lines.length; i++) {\n\t\t\t\t\tString lineText = lines[i];\n\n\t\t\t\t\t// Extract numbers (decimal, hex) from line, check if match\n\t\t\t\t\tMatcher matcher = getMatcher(\"(?:\\\\b|-)(?:\\\\d+(?:.\\\\d+[DdFf]?)?|0[xX][0-9a-fA-F]+)\\\\b\", lineText);\n\t\t\t\t\twhile (matcher.find()) {\n\t\t\t\t\t\tString group = matcher.group(0);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tNumber value = NumberUtil.parse(group);\n\t\t\t\t\t\t\tif (isMatch(value))\n\t\t\t\t\t\t\t\tresultSink.accept(filePath.child(i + 1), value);\n\t\t\t\t\t\t} catch (NumberFormatException ignored) {\n\t\t\t\t\t\t\t// Invalid match\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/query/Query.java",
    "content": "package software.coley.recaf.services.search.query;\n\n/**\n * Common query type.\n *\n * @author Matt Coley\n * @see AndroidClassQuery\n * @see JvmClassQuery\n * @see FileQuery\n */\npublic interface Query {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/query/ReferenceQuery.java",
    "content": "package software.coley.recaf.services.search.query;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.ConstantDynamic;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.TypePath;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.FieldInsnNode;\nimport org.objectweb.asm.tree.InvokeDynamicInsnNode;\nimport org.objectweb.asm.tree.LdcInsnNode;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport org.objectweb.asm.tree.MultiANewArrayInsnNode;\nimport org.objectweb.asm.tree.TypeInsnNode;\nimport org.slf4j.Logger;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.annotation.BasicAnnotationInfo;\nimport software.coley.recaf.info.member.BasicLocalVariable;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.AnnotationPathNode;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.search.JvmClassSearchVisitor;\nimport software.coley.recaf.services.search.ResultSink;\nimport software.coley.recaf.services.search.match.StringPredicate;\nimport software.coley.recaf.services.search.result.ClassReference;\nimport software.coley.recaf.services.search.result.MemberReference;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.visitors.IndexCountingMethodVisitor;\n\n/**\n * Reference search implementation.\n *\n * @author Matt Coley\n */\npublic class ReferenceQuery implements JvmClassQuery {\n\tprivate final StringPredicate ownerPredicate;\n\tprivate final StringPredicate namePredicate;\n\tprivate final StringPredicate descriptorPredicate;\n\tprivate final boolean classRefOnly;\n\n\n\t/**\n\t * Class reference query.\n\t *\n\t * @param ownerPredicate\n\t * \t\tString matching predicate for comparison against reference owners.\n\t */\n\tpublic ReferenceQuery(@Nonnull StringPredicate ownerPredicate) {\n\t\tthis.ownerPredicate = ownerPredicate;\n\t\tthis.namePredicate = null;\n\t\tthis.descriptorPredicate = null;\n\t\tclassRefOnly = true;\n\t}\n\n\t/**\n\t * Member reference query.\n\t * <p>\n\t * Do note that each target value is nullable/optional.\n\t * Including only the owner and {@code null} for the name/desc will yield references to all members in the class.\n\t * Including only the desc will yield references to all members of that desc in all classes.\n\t *\n\t * @param ownerPredicate\n\t * \t\tString matching predicate for comparison against reference owners.\n\t *        {@code null} to ignore matching against reference owner names.\n\t * @param namePredicate\n\t * \t\tString matching predicate for comparison against reference names.\n\t *        {@code null} to ignore matching against reference names.\n\t * @param descriptorPredicate\n\t * \t\tString matching predicate for comparison against reference descriptors.\n\t *        {@code null} to ignore matching against reference descriptors.\n\t */\n\tpublic ReferenceQuery(@Nullable StringPredicate ownerPredicate,\n\t                      @Nullable StringPredicate namePredicate,\n\t                      @Nullable StringPredicate descriptorPredicate) {\n\t\tthis.ownerPredicate = ownerPredicate;\n\t\tthis.namePredicate = namePredicate;\n\t\tthis.descriptorPredicate = descriptorPredicate;\n\t\tclassRefOnly = false;\n\t}\n\n\tprivate boolean isClassRefMatch(@Nullable String className) {\n\t\tif (!classRefOnly || className == null || ownerPredicate == null) return false;\n\t\treturn StringUtil.isNullOrEmpty(className) || ownerPredicate.match(className);\n\t}\n\n\tprivate boolean isMemberRefMatch(@Nullable String owner, @Nullable String name, @Nullable String desc) {\n\t\tif (classRefOnly) return false;\n\n\t\t// The parameters are null if we only are searching against a type.\n\t\t// In these cases since we're comparing to a type, then any name/desc comparison should be ignored.\n\t\tif (name == null && namePredicate != null) return false;\n\t\tif (desc == null && descriptorPredicate != null) return false;\n\n\t\t// Check if match modes succeed.\n\t\t// If our query predicates are null, that field can skip comparison, and we move on to the next.\n\t\t// If all of our non-null query arguments match the given parameters, we have a match.\n\t\tif (ownerPredicate == null || StringUtil.isNullOrEmpty(owner) || ownerPredicate.match(owner))\n\t\t\tif (namePredicate == null || StringUtil.isNullOrEmpty(name) || namePredicate.match(name))\n\t\t\t\treturn descriptorPredicate == null || StringUtil.isNullOrEmpty(desc) || descriptorPredicate.match(desc);\n\n\t\treturn false;\n\t}\n\n\t@Nonnull\n\tprivate static String getInternalName(@Nonnull String classDesc) {\n\t\treturn Type.getType(classDesc).getInternalName();\n\t}\n\n\t@Nonnull\n\tprivate static ClassReference cref(@Nonnull String name) {\n\t\treturn new ClassReference(name);\n\t}\n\n\t@Nonnull\n\tprivate static MemberReference mref(@Nonnull String owner, @Nonnull String name, @Nonnull String desc) {\n\t\treturn new MemberReference(owner, name, desc);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic JvmClassSearchVisitor visitor(@Nullable JvmClassSearchVisitor delegate) {\n\t\treturn (resultSink, currentLocation, classInfo) -> {\n\t\t\tif (delegate != null)\n\t\t\t\tdelegate.visit(resultSink, currentLocation, classInfo);\n\t\t\tclassInfo.getClassReader().accept(new AsmReferenceClassVisitor(resultSink, currentLocation, classInfo), 0);\n\t\t};\n\t}\n\n\t/**\n\t * Visits references in classes.\n\t */\n\tprivate class AsmReferenceClassVisitor extends ClassVisitor {\n\t\tprivate final Logger logger = Logging.get(AsmReferenceClassVisitor.class);\n\t\tprivate final ResultSink resultSink;\n\t\tprivate final ClassPathNode classPath;\n\t\tprivate final JvmClassInfo classInfo;\n\n\t\tpublic AsmReferenceClassVisitor(@Nonnull ResultSink resultSink,\n\t\t                                @Nonnull ClassPathNode classPath,\n\t\t                                @Nonnull JvmClassInfo classInfo) {\n\t\t\tsuper(RecafConstants.getAsmVersion());\n\t\t\tthis.resultSink = resultSink;\n\t\t\tthis.classPath = classPath;\n\t\t\tthis.classInfo = classInfo;\n\t\t}\n\n\t\t@Override\n\t\tpublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {\n\t\t\tMethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);\n\t\t\tMethodMember methodMember = classInfo.getDeclaredMethod(name, desc);\n\t\t\tif (methodMember != null) {\n\t\t\t\tClassMemberPathNode memberPath = classPath.child(methodMember);\n\n\t\t\t\t// Check exceptions\n\t\t\t\tif (exceptions != null)\n\t\t\t\t\tfor (String exception : exceptions)\n\t\t\t\t\t\tif (isClassRefMatch(exception))\n\t\t\t\t\t\t\tresultSink.accept(memberPath.childThrows(exception), cref(exception));\n\n\t\t\t\t// Check descriptor components\n\t\t\t\t// - Only yield one match even if there are multiple class-refs in the desc\n\t\t\t\tType methodType = Type.getMethodType(desc);\n\t\t\t\tString methodRetType = methodType.getReturnType().getInternalName();\n\t\t\t\tif (isClassRefMatch(methodRetType))\n\t\t\t\t\tresultSink.accept(memberPath, cref(methodRetType));\n\t\t\t\telse for (Type argumentType : methodType.getArgumentTypes())\n\t\t\t\t\tif (isClassRefMatch(argumentType.getInternalName())) {\n\t\t\t\t\t\tresultSink.accept(memberPath, cref(argumentType.getInternalName()));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t// Visit method\n\t\t\t\treturn new AsmReferenceMethodVisitor(mv, methodMember, resultSink, classPath);\n\t\t\t} else {\n\t\t\t\tlogger.error(\"Failed to lookup method for query: {}.{}{}\", classInfo.getName(), name, desc);\n\t\t\t\treturn mv;\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {\n\t\t\tFieldVisitor fv = super.visitField(access, name, desc, signature, value);\n\t\t\tFieldMember fieldMember = classInfo.getDeclaredField(name, desc);\n\t\t\tif (fieldMember != null) {\n\t\t\t\tClassMemberPathNode memberPath = classPath.child(fieldMember);\n\n\t\t\t\t// Check descriptor\n\t\t\t\tString fieldType = getInternalName(desc);\n\t\t\t\tif (isClassRefMatch(fieldType))\n\t\t\t\t\tresultSink.accept(memberPath, cref(fieldType));\n\n\t\t\t\t// Visit field\n\t\t\t\treturn new AsmReferenceFieldVisitor(fv, resultSink, memberPath);\n\t\t\t} else {\n\t\t\t\tlogger.error(\"Failed to lookup field for query: {}.{}{}\", classInfo.getName(), name, desc);\n\t\t\t\treturn fv;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Visits references in methods.\n\t */\n\tprivate class AsmReferenceMethodVisitor extends IndexCountingMethodVisitor {\n\t\tprivate final ResultSink resultSink;\n\t\tprivate final ClassMemberPathNode memberPath;\n\t\tprivate final String ownerType;\n\n\n\t\tpublic AsmReferenceMethodVisitor(@Nullable MethodVisitor delegate,\n\t\t                                 @Nonnull MethodMember methodMember,\n\t\t                                 @Nonnull ResultSink resultSink,\n\t\t                                 @Nonnull ClassPathNode classLocation) {\n\t\t\tsuper(delegate);\n\t\t\tthis.resultSink = resultSink;\n\t\t\tthis.memberPath = classLocation.child(methodMember);\n\n\t\t\townerType = classLocation.getValue().getName();\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitTypeInsn(int opcode, String type) {\n\t\t\tif (isClassRefMatch(type)) {\n\t\t\t\tTypeInsnNode insn = new TypeInsnNode(opcode, type);\n\t\t\t\tresultSink.accept(memberPath.childInsn(insn, index), cref(type));\n\t\t\t}\n\t\t\tsuper.visitTypeInsn(opcode, type);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitFieldInsn(int opcode, String owner, String name, String desc) {\n\t\t\tFieldInsnNode insn = new FieldInsnNode(opcode, owner, name, desc);\n\n\t\t\t// Check method ref\n\t\t\tif (isMemberRefMatch(owner, name, desc))\n\t\t\t\tresultSink.accept(memberPath.childInsn(insn, index), mref(owner, name, desc));\n\n\t\t\t// Check types used in ref\n\t\t\tString fieldType = getInternalName(desc);\n\t\t\tif (isClassRefMatch(fieldType))\n\t\t\t\tresultSink.accept(memberPath.childInsn(insn, index), cref(fieldType));\n\n\t\t\tsuper.visitFieldInsn(opcode, owner, name, desc);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitMethodInsn(int opcode, String owner, String name, String desc, boolean isInterface) {\n\t\t\tMethodInsnNode insn = new MethodInsnNode(opcode, owner, name, desc, isInterface);\n\n\t\t\tvisitMethodLikeInsn(owner, name, desc, insn);\n\n\t\t\tsuper.visitMethodInsn(opcode, owner, name, desc, isInterface);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitInvokeDynamicInsn(String name, String desc, Handle bsmHandle, Object... bsmArgs) {\n\t\t\tInvokeDynamicInsnNode insn = new InvokeDynamicInsnNode(name, desc, bsmHandle, bsmArgs);\n\n\t\t\tvisitMethodLikeInsn(ownerType, name, desc, insn);\n\t\t\tvisitBsm(bsmHandle, bsmArgs, insn);\n\n\t\t\tsuper.visitInvokeDynamicInsn(name, desc, bsmHandle, bsmArgs);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLdcInsn(Object value) {\n\t\t\tLdcInsnNode insn = new LdcInsnNode(value);\n\n\t\t\tvisitArg(insn.cst, insn);\n\n\t\t\tsuper.visitLdcInsn(value);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitMultiANewArrayInsn(String desc, int numDimensions) {\n\t\t\tif (Types.isValidDesc(desc)) {\n\t\t\t\tString type = getInternalName(desc);\n\t\t\t\tif (isClassRefMatch(type)) {\n\t\t\t\t\tMultiANewArrayInsnNode insn = new MultiANewArrayInsnNode(desc, numDimensions);\n\t\t\t\t\tresultSink.accept(memberPath.childInsn(insn, index), cref(type));\n\t\t\t\t}\n\t\t\t}\n\t\t\tsuper.visitMultiANewArrayInsn(desc, numDimensions);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitTryCatchBlock(Label start, Label end, Label handler, String type) {\n\t\t\tif (isClassRefMatch(type)) {\n\t\t\t\tresultSink.accept(memberPath.childCatch(type), cref(type));\n\t\t\t}\n\t\t\tsuper.visitTryCatchBlock(start, end, handler, type);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {\n\t\t\tif (!Types.isValidDesc(desc) || Types.isPrimitive(desc)) {\n\t\t\t\tsuper.visitLocalVariable(name, desc, signature, start, end, index);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tString type = getInternalName(desc);\n\n\t\t\t// Skip 'this' variables. Nobody cares that virtual methods have a type reference to themselves...\n\t\t\tif (index == 0 && name.equals(\"this\") && type.equals(ownerType)) {\n\t\t\t\tsuper.visitLocalVariable(name, desc, signature, start, end, index);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (isClassRefMatch(type)) {\n\t\t\t\tLocalVariable variable = new BasicLocalVariable(index, name, desc, signature);\n\t\t\t\tresultSink.accept(memberPath.childVariable(variable), cref(type));\n\t\t\t}\n\n\t\t\tsuper.visitLocalVariable(name, desc, signature, start, end, index);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotationDefault() {\n\t\t\treturn new AnnotationReferenceVisitor(super.visitAnnotationDefault(), true, resultSink, memberPath);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String desc, boolean visible) {\n\t\t\t// Match annotation\n\t\t\tString type = getInternalName(desc);\n\t\t\tif (isClassRefMatch(type))\n\t\t\t\tresultSink.accept(memberPath, cref(type));\n\n\t\t\tAnnotationVisitor av = super.visitAnnotation(desc, visible);\n\t\t\treturn new AnnotationReferenceVisitor(av, visible, resultSink, memberPath);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\t// Match annotation\n\t\t\tString type = getInternalName(desc);\n\t\t\tif (isClassRefMatch(type))\n\t\t\t\tresultSink.accept(memberPath, cref(type));\n\n\t\t\tAnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn new AnnotationReferenceVisitor(av, visible, resultSink, memberPath);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {\n\t\t\t// Match annotation\n\t\t\tString type = getInternalName(desc);\n\t\t\tif (isClassRefMatch(type))\n\t\t\t\tresultSink.accept(memberPath, cref(type));\n\n\t\t\tAnnotationVisitor av = super.visitParameterAnnotation(parameter, desc, visible);\n\t\t\treturn new AnnotationReferenceVisitor(av, visible, resultSink, memberPath);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\t// Match annotation\n\t\t\tString type = getInternalName(desc);\n\t\t\tif (isClassRefMatch(type))\n\t\t\t\tresultSink.accept(memberPath, cref(type));\n\n\t\t\tAnnotationVisitor av = super.visitInsnAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn new AnnotationReferenceVisitor(av, visible, resultSink, memberPath);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\t// Match annotation\n\t\t\tString type = getInternalName(desc);\n\t\t\tif (isClassRefMatch(type))\n\t\t\t\tresultSink.accept(memberPath, cref(type));\n\n\t\t\tAnnotationVisitor av = super.visitTryCatchAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn new AnnotationReferenceVisitor(av, visible, resultSink, memberPath);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) {\n\t\t\t// Match annotation\n\t\t\tString type = getInternalName(desc);\n\t\t\tif (isClassRefMatch(type))\n\t\t\t\tresultSink.accept(memberPath, cref(type));\n\n\t\t\tAnnotationVisitor av = super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, desc, visible);\n\t\t\treturn new AnnotationReferenceVisitor(av, visible, resultSink, memberPath);\n\t\t}\n\n\t\tprivate void visitBsm(@Nonnull Handle bsmHandle, @Nonnull Object[] bsmArgs, @Nonnull AbstractInsnNode insn) {\n\t\t\t// Visit the handle\n\t\t\tvisitHandle(bsmHandle, insn);\n\n\t\t\t// Then all the args\n\t\t\tfor (Object bsmArg : bsmArgs)\n\t\t\t\tvisitArg(bsmArg, insn);\n\t\t}\n\n\t\tprivate void visitArg(@Nonnull Object arg, @Nonnull AbstractInsnNode insn) {\n\t\t\tswitch (arg) {\n\t\t\t\tcase Type typeArg -> visitType(typeArg, insn);\n\t\t\t\tcase Handle handleArg -> visitHandle(handleArg, insn);\n\t\t\t\tcase ConstantDynamic dynamicArg -> {\n\t\t\t\t\tint argCount = dynamicArg.getBootstrapMethodArgumentCount();\n\t\t\t\t\tObject[] args = new Object[argCount];\n\t\t\t\t\tfor (int i = 0; i < argCount; i++)\n\t\t\t\t\t\targs[i] = dynamicArg.getBootstrapMethodArgument(i);\n\t\t\t\t\tvisitBsm(dynamicArg.getBootstrapMethod(), args, insn);\n\t\t\t\t}\n\t\t\t\tdefault -> {\n\t\t\t\t\t// no-op\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tprivate void visitHandle(@Nonnull Handle handle, @Nonnull AbstractInsnNode insn) {\n\t\t\t// Check handle ref\n\t\t\tString handleDesc = handle.getDesc();\n\t\t\tif (isMemberRefMatch(handle.getOwner(), handle.getName(), handleDesc)) {\n\t\t\t\tresultSink.accept(memberPath.childInsn(insn, index),\n\t\t\t\t\t\tmref(handle.getOwner(), handle.getName(), handleDesc));\n\t\t\t}\n\n\t\t\t// Check types used in ref\n\t\t\tvisitType(Type.getType(handle.getDesc()), insn);\n\t\t}\n\n\t\tprivate void visitMethodLikeInsn(@Nonnull String owner, @Nonnull String name, @Nonnull String desc, @Nonnull AbstractInsnNode insn) {\n\t\t\t// Check method ref\n\t\t\tif (isMemberRefMatch(owner, name, desc))\n\t\t\t\tresultSink.accept(memberPath.childInsn(insn, index), mref(owner, name, desc));\n\n\t\t\t// Check types used in ref\n\t\t\tType methodType = Type.getMethodType(desc);\n\t\t\tvisitType(methodType, insn);\n\t\t}\n\n\t\tprivate void visitType(@Nonnull Type type, @Nonnull AbstractInsnNode insn) {\n\t\t\tif (type.getSort() == Type.METHOD) {\n\t\t\t\tString methodRetType = type.getReturnType().getInternalName();\n\t\t\t\tif (isClassRefMatch(methodRetType))\n\t\t\t\t\tresultSink.accept(memberPath.childInsn(insn, index), cref(methodRetType));\n\t\t\t\tfor (Type argumentType : type.getArgumentTypes()) {\n\t\t\t\t\tif (isClassRefMatch(argumentType.getInternalName()))\n\t\t\t\t\t\tresultSink.accept(memberPath.childInsn(insn, index), cref(argumentType.getInternalName()));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tString internalName = type.getInternalName();\n\t\t\t\tif (isClassRefMatch(internalName)) {\n\t\t\t\t\tresultSink.accept(memberPath.childInsn(insn, index), cref(internalName));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Visits references in fields.\n\t */\n\tprivate class AsmReferenceFieldVisitor extends FieldVisitor {\n\t\tprivate final ResultSink resultSink;\n\t\tprivate final ClassMemberPathNode memberPath;\n\n\t\tpublic AsmReferenceFieldVisitor(@Nullable FieldVisitor delegate,\n\t\t                                @Nonnull ResultSink resultSink,\n\t\t                                @Nonnull ClassMemberPathNode memberPath) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), delegate);\n\t\t\tthis.resultSink = resultSink;\n\t\t\tthis.memberPath = memberPath;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String desc, boolean visible) {\n\t\t\t// Match annotation\n\t\t\tString type = getInternalName(desc);\n\t\t\tif (isClassRefMatch(type))\n\t\t\t\tresultSink.accept(memberPath, cref(type));\n\n\t\t\tAnnotationVisitor av = super.visitAnnotation(desc, visible);\n\t\t\treturn new AnnotationReferenceVisitor(av, visible, resultSink, memberPath);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\t// Match annotation\n\t\t\tString type = getInternalName(desc);\n\t\t\tif (isClassRefMatch(type))\n\t\t\t\tresultSink.accept(memberPath, cref(type));\n\n\t\t\tAnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn new AnnotationReferenceVisitor(av, visible, resultSink, memberPath);\n\t\t}\n\t}\n\n\t/**\n\t * Visits references in annotations.\n\t */\n\tprivate class AnnotationReferenceVisitor extends AnnotationVisitor {\n\t\tprivate final ResultSink resultSink;\n\t\tprivate final PathNode<?> currentAnnoLocation;\n\t\tprivate final boolean visible;\n\n\t\tpublic AnnotationReferenceVisitor(@Nullable AnnotationVisitor delegate,\n\t\t                                  boolean visible,\n\t\t                                  @Nonnull ResultSink resultSink,\n\t\t                                  @Nonnull PathNode<?> currentAnnoLocation) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), delegate);\n\t\t\tthis.visible = visible;\n\t\t\tthis.resultSink = resultSink;\n\t\t\tthis.currentAnnoLocation = currentAnnoLocation;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String name, String descriptor) {\n\t\t\tAnnotationVisitor av = super.visitAnnotation(name, descriptor);\n\n\t\t\t// Match sub-annotation\n\t\t\tString type = getInternalName(descriptor);\n\t\t\tif (isClassRefMatch(type))\n\t\t\t\tresultSink.accept(currentAnnoLocation, cref(type));\n\n\t\t\t// Visit sub-annotation\n\t\t\tif (currentAnnoLocation.getValue() instanceof Annotated annotated) {\n\t\t\t\tAnnotationInfo annotationInfo = annotated.getAnnotations().stream()\n\t\t\t\t\t\t.filter(ai -> ai.getDescriptor().equals(descriptor))\n\t\t\t\t\t\t.findFirst()\n\t\t\t\t\t\t.orElseGet(() -> new BasicAnnotationInfo(visible, descriptor));\n\t\t\t\tif (currentAnnoLocation instanceof ClassPathNode classPath) {\n\t\t\t\t\treturn new AnnotationReferenceVisitor(av, visible, resultSink,\n\t\t\t\t\t\t\tclassPath.child(annotationInfo));\n\t\t\t\t} else if (currentAnnoLocation instanceof ClassMemberPathNode memberPath) {\n\t\t\t\t\treturn new AnnotationReferenceVisitor(av, visible, resultSink,\n\t\t\t\t\t\t\tmemberPath.childAnnotation(annotationInfo));\n\t\t\t\t} else if (currentAnnoLocation instanceof AnnotationPathNode annotationPath) {\n\t\t\t\t\treturn new AnnotationReferenceVisitor(av, visible, resultSink,\n\t\t\t\t\t\t\tannotationPath.child(annotationInfo));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthrow new IllegalStateException(\"Unsupported non-annotatable path: \" + currentAnnoLocation);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitArray(String name) {\n\t\t\tAnnotationVisitor av = super.visitArray(name);\n\t\t\treturn new AnnotationReferenceVisitor(av, visible, resultSink, currentAnnoLocation);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitEnum(String name, String descriptor, String value) {\n\t\t\tsuper.visitEnum(name, descriptor, value);\n\n\t\t\t// Match enum reference\n\t\t\tString owner = getInternalName(descriptor);\n\t\t\tif (isMemberRefMatch(owner, descriptor, value))\n\t\t\t\tresultSink.accept(currentAnnoLocation, mref(owner, value, descriptor));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/query/StringQuery.java",
    "content": "package software.coley.recaf.services.search.query;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.services.search.FileSearchVisitor;\nimport software.coley.recaf.services.search.ResultSink;\nimport software.coley.recaf.services.search.match.StringPredicate;\n\n/**\n * String search implementation.\n *\n * @author Matt Coley\n */\npublic class StringQuery extends AbstractValueQuery {\n\tprivate final StringPredicate predicate;\n\n\t/**\n\t * @param predicate\n\t * \t\tString matching predicate.\n\t */\n\tpublic StringQuery(@Nonnull StringPredicate predicate) {\n\t\tthis.predicate = predicate;\n\t}\n\n\t@Override\n\tprotected boolean isMatch(Object value) {\n\t\tif (value instanceof String text)\n\t\t\treturn predicate.match(text);\n\t\treturn false;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic FileSearchVisitor visitor(@Nullable FileSearchVisitor delegate) {\n\t\treturn new FileVisitor(delegate);\n\t}\n\n\t/**\n\t * Points {@link #visitor(FileSearchVisitor)} to file content.\n\t */\n\tprivate class FileVisitor implements FileSearchVisitor {\n\t\tprivate final FileSearchVisitor delegate;\n\n\t\tprivate FileVisitor(FileSearchVisitor delegate) {\n\t\t\tthis.delegate = delegate;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visit(@Nonnull ResultSink resultSink,\n\t\t\t\t\t\t  @Nonnull FilePathNode filePath,\n\t\t\t\t\t\t  @Nonnull FileInfo fileInfo) {\n\t\t\tif (delegate != null) delegate.visit(resultSink, filePath, fileInfo);\n\n\t\t\t// Search text files text content on a line by line basis\n\t\t\tif (fileInfo.isTextFile()) {\n\t\t\t\tString[] lines = fileInfo.asTextFile().getTextLines();\n\n\t\t\t\t// Split by single newline (including goofy carriage returns)\n\t\t\t\tfor (int i = 0; i < lines.length; i++) {\n\t\t\t\t\tString lineText = lines[i];\n\t\t\t\t\tif (isMatch(lineText))\n\t\t\t\t\t\tresultSink.accept(filePath.child(i + 1), lineText);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/result/ClassReference.java",
    "content": "package software.coley.recaf.services.search.result;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Class reference outline.\n *\n * @param name\n * \t\tClass name.\n *\n * @author Matt Coley\n */\npublic record ClassReference(@Nonnull String name) {}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/result/ClassReferenceResult.java",
    "content": "package software.coley.recaf.services.search.result;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.path.PathNode;\n\n/**\n * Result of a class reference match.\n *\n * @author Matt Coley\n */\npublic class ClassReferenceResult extends Result<ClassReference> {\n\tprivate final ClassReference ref;\n\n\t/**\n\t * @param path\n\t * \t\tPath to item containing the result.\n\t * @param name\n\t * \t\tClass name.\n\t */\n\tpublic ClassReferenceResult(@Nonnull PathNode<?> path, @Nonnull String name) {\n\t\tthis(path, new ClassReference(name));\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to item containing the result.\n\t * @param ref\n\t * \t\tClass reference.\n\t */\n\tpublic ClassReferenceResult(@Nonnull PathNode<?> path, @Nonnull ClassReference ref) {\n\t\tsuper(path);\n\t\tthis.ref = ref;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ClassReference getValue() {\n\t\treturn ref;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/result/MemberReference.java",
    "content": "package software.coley.recaf.services.search.result;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Member reference outline.\n *\n * @param owner\n * \t\tName of class declaring the member.\n * @param name\n * \t\tMember name.\n * @param desc\n * \t\tMember descriptor.\n *\n * @author Matt Coley\n */\npublic record MemberReference(@Nonnull String owner, @Nonnull String name, @Nonnull String desc) {\n\t/**\n\t * @return {@code true} when this is a reference to a field member.\n\t */\n\tpublic boolean isFieldReference() {\n\t\treturn !isMethodReference();\n\t}\n\n\t/**\n\t * @return {@code true} when this is a reference to a method member.\n\t */\n\tpublic boolean isMethodReference() {\n\t\treturn desc.charAt(0) == '(';\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/result/MemberReferenceResult.java",
    "content": "package software.coley.recaf.services.search.result;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.path.PathNode;\n\n/**\n * Result of a class reference match.\n *\n * @author Matt Coley\n */\npublic class MemberReferenceResult extends Result<MemberReference> {\n\tprivate final MemberReference ref;\n\n\t/**\n\t * @param path\n\t * \t\tPath to item containing the result.\n\t * @param owner\n\t * \t\tName of class declaring the member.\n\t * @param name\n\t * \t\tMember name.\n\t * @param desc\n\t * \t\tMember descriptor.\n\t */\n\tpublic MemberReferenceResult(@Nonnull PathNode<?> path,\n\t                             @Nonnull String owner, @Nonnull String name, @Nonnull String desc) {\n\t\tthis(path, new MemberReference(owner, name, desc));\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to item containing the result.\n\t * @param ref\n\t * \t\tMember reference.\n\t */\n\tpublic MemberReferenceResult(@Nonnull PathNode<?> path, @Nonnull MemberReference ref) {\n\t\tsuper(path);\n\t\tthis.ref = ref;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected MemberReference getValue() {\n\t\treturn ref;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/result/NumberResult.java",
    "content": "package software.coley.recaf.services.search.result;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.path.PathNode;\n\n/**\n * Result of a string match.\n *\n * @author Matt Coley\n */\npublic class NumberResult extends Result<Number> {\n\tprivate final Number value;\n\n\t/**\n\t * @param path\n\t * \t\tPath to item containing the result.\n\t * @param value\n\t * \t\tMatched value.\n\t */\n\tpublic NumberResult(@Nonnull PathNode<?> path, @Nonnull Number value) {\n\t\tsuper(path);\n\t\tthis.value = value;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected Number getValue() {\n\t\treturn value;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/result/Result.java",
    "content": "package software.coley.recaf.services.search.result;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.path.PathNode;\n\nimport java.util.Objects;\n\n/**\n * The base result contains path information of the matched value.\n *\n * @author Matt Coley\n */\npublic abstract class Result<T> implements Comparable<Result<?>> {\n\tprivate final PathNode<?> path;\n\n\t/**\n\t * @param path\n\t * \t\tPath to item containing the result.\n\t */\n\tpublic Result(@Nonnull PathNode<?> path) {\n\t\tthis.path = path;\n\t}\n\n\t/**\n\t * @return Wrapped value, used internally for {@link #toString()}.\n\t */\n\t@Nonnull\n\tprotected abstract T getValue();\n\n\t/**\n\t * @return Path to item containing the result.\n\t */\n\t@Nonnull\n\tpublic PathNode<?> getPath() {\n\t\treturn path;\n\t}\n\n\t@Override\n\tpublic int compareTo(@Nonnull Result<?> o) {\n\t\tif (o == this)\n\t\t\treturn 0;\n\n\t\t// Base comparison by path.\n\t\tint cmp = path.compareTo(o.path);\n\n\t\t// Disambiguate if path is the same, but values differ.\n\t\tif (cmp == 0)\n\t\t\tcmp = Integer.compare(getValue().hashCode(), o.getValue().hashCode());\n\n\t\treturn cmp;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"Result{value=\" + getValue() + \", path=\" + path + '}';\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\t\tResult<?> result = (Result<?>) o;\n\t\treturn Objects.equals(path, result.path) &&\n\t\t\t\tObjects.equals(getValue(), result.getValue());\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = path.hashCode();\n\t\tresult = 31 * result + Objects.hashCode(getValue());\n\t\treturn result;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/result/Results.java",
    "content": "package software.coley.recaf.services.search.result;\n\nimport software.coley.collections.delegate.DelegatingSortedSet;\n\nimport java.util.Collections;\nimport java.util.TreeSet;\n\n/**\n * Results wrapper for a search operation.\n *\n * @author Matt Coley\n */\npublic class Results extends DelegatingSortedSet<Result<?>> {\n\t/**\n\t * New results backed by tree-set.\n\t */\n\tpublic Results() {\n\t\tsuper(Collections.synchronizedNavigableSet(new TreeSet<>()));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/search/result/StringResult.java",
    "content": "package software.coley.recaf.services.search.result;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.path.PathNode;\n\n/**\n * Result of a string match.\n *\n * @author Matt Coley\n */\npublic class StringResult extends Result<String> {\n\tprivate final String value;\n\n\t/**\n\t * @param path\n\t * \t\tPath to item containing the result.\n\t * @param value\n\t * \t\tMatched value.\n\t */\n\tpublic StringResult(@Nonnull PathNode<?> path, @Nonnull String value) {\n\t\tsuper(path);\n\t\tthis.value = value;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected String getValue() {\n\t\treturn value;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/source/AstMapper.java",
    "content": "package software.coley.recaf.services.source;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.sourcesolver.model.ClassModel;\nimport software.coley.sourcesolver.model.CompilationUnitModel;\nimport software.coley.sourcesolver.model.ImportModel;\nimport software.coley.sourcesolver.model.MethodModel;\nimport software.coley.sourcesolver.model.Model;\nimport software.coley.sourcesolver.model.NameHoldingModel;\nimport software.coley.sourcesolver.model.NamedModel;\nimport software.coley.sourcesolver.model.VariableModel;\nimport software.coley.sourcesolver.resolve.Resolver;\nimport software.coley.sourcesolver.resolve.entry.ClassEntry;\nimport software.coley.sourcesolver.resolve.entry.ClassMemberPair;\nimport software.coley.sourcesolver.resolve.entry.FieldEntry;\nimport software.coley.sourcesolver.resolve.entry.MemberEntry;\nimport software.coley.sourcesolver.resolve.entry.MethodEntry;\nimport software.coley.sourcesolver.resolve.result.ClassResolution;\nimport software.coley.sourcesolver.resolve.result.FieldResolution;\nimport software.coley.sourcesolver.resolve.result.MethodResolution;\nimport software.coley.sourcesolver.resolve.result.MultiClassResolution;\nimport software.coley.sourcesolver.resolve.result.MultiMemberResolution;\nimport software.coley.sourcesolver.resolve.result.PrimitiveResolution;\nimport software.coley.sourcesolver.resolve.result.Resolution;\nimport software.coley.sourcesolver.util.Range;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * Replaces identifiers in a {@link CompilationUnitModel} with new names based on provided {@link Mappings}.\n *\n * @author Matt Coley\n */\n@SuppressWarnings(\"IfCanBeSwitch\")\npublic class AstMapper {\n\tprivate final CompilationUnitModel unit;\n\tprivate final Resolver resolver;\n\tprivate final Mappings mappings;\n\n\t/**\n\t * @param unit\n\t * \t\tUnit to map.\n\t * @param resolver\n\t * \t\tResolver to analyze the unit with.\n\t * @param mappings\n\t * \t\tMappings to apply.\n\t */\n\tpublic AstMapper(@Nonnull CompilationUnitModel unit, @Nonnull Resolver resolver, @Nonnull Mappings mappings) {\n\t\tthis.unit = unit;\n\t\tthis.resolver = resolver;\n\t\tthis.mappings = mappings;\n\t}\n\n\t/**\n\t * @return Modified source code based on the provided mappings.\n\t */\n\t@Nonnull\n\tpublic String apply() {\n\t\tString source = unit.getInputSource();\n\n\t\t// Get all named resolutions and replace them with their mapped alternatives in reverse order.\n\t\tList<NamedResolutions> pairs = new ArrayList<>();\n\t\tunit.visit(model -> {\n\t\t\tif (!model.getRange().isUnknown() && model instanceof NamedModel named) {\n\t\t\t\tResolution resolution = model.resolve(resolver);\n\t\t\t\tif (!resolution.isUnknown() && !(resolution instanceof PrimitiveResolution))\n\t\t\t\t\tpairs.add(new NamedResolutions(named, resolution));\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\t\tfor (int i = pairs.size() - 1; i >= 0; i--) {\n\t\t\tNamedResolutions pair = pairs.get(i);\n\t\t\tNamedModel named = pair.named();\n\t\t\tResolution resolution = pair.resolution();\n\t\t\tif (resolution instanceof ClassResolution classResolution) {\n\t\t\t\tsource = replacePatternIn(named, source, getSimpleName(classResolution), getSimpleName(getMappedClass(classResolution)));\n\t\t\t} else if (resolution instanceof FieldResolution fieldResolution) {\n\t\t\t\tsource = replacePatternIn(named, source, fieldResolution.getFieldEntry().getName(), getSimpleName(getMappedField(fieldResolution)));\n\t\t\t} else if (resolution instanceof MethodResolution methodResolution) {\n\t\t\t\tif (methodResolution.getMethodEntry().getName().equals(\"<init>\")) {\n\t\t\t\t\t// Constructors get replaced as the owner name\n\t\t\t\t\tClassResolution ownerResolution = methodResolution.getOwnerResolution();\n\t\t\t\t\tsource = replacePatternIn(named, source, getSimpleName(ownerResolution), getSimpleName(getMappedClass(ownerResolution)));\n\t\t\t\t} else {\n\t\t\t\t\tsource = replacePatternIn(named, source, methodResolution.getMethodEntry().getName(), getSimpleName(getMappedMethod(methodResolution)));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Replace imports of mapped classes & members.\n\t\tIntermediateMappings intermediateMappings = mappings.exportIntermediate();\n\t\tfor (int i = unit.getImports().size() - 1; i >= 0; i--) {\n\t\t\tImportModel importModel = unit.getImports().get(i);\n\t\t\tResolution resolution = importModel.resolve(resolver);\n\t\t\tif (resolution instanceof ClassResolution classResolution) {\n\t\t\t\t// Single class to map.\n\t\t\t\tString mappedName = getMappedClass(classResolution);\n\t\t\t\tif (mappedName != null) {\n\t\t\t\t\tString baseName = classResolution.getClassEntry().getName().replace('/', '.');\n\t\t\t\t\tsource = replacePatternIn(importModel, source, baseName, mappedName.replace('/', '.'));\n\t\t\t\t}\n\t\t\t} else if (resolution instanceof MultiClassResolution multiClassResolution) {\n\t\t\t\t// Multiple classes to consider with a 'package.*' import.\n\t\t\t\tList<String> classesInPackage = multiClassResolution.getClassEntries().stream()\n\t\t\t\t\t\t.map(ClassEntry::getName)\n\t\t\t\t\t\t.toList();\n\t\t\t\tSet<String> unmappedClasses = new HashSet<>();\n\t\t\t\tMap<String, String> mappedClasses = new HashMap<>();\n\t\t\t\tfor (String className : classesInPackage) {\n\t\t\t\t\tString mappedClassName = mappings.getMappedClassName(className);\n\t\t\t\t\tif (mappedClassName != null)\n\t\t\t\t\t\tmappedClasses.put(className, mappedClassName);\n\t\t\t\t\telse\n\t\t\t\t\t\tunmappedClasses.add(className);\n\t\t\t\t}\n\n\t\t\t\t// No classes were mapped, so we're good to go\n\t\t\t\tif (mappedClasses.isEmpty())\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Determine what new package names we need to import.\n\t\t\t\tint begin = importModel.getRange().begin();\n\t\t\t\tboolean removeExistingPackageImport = unmappedClasses.isEmpty();\n\t\t\t\tif (removeExistingPackageImport)\n\t\t\t\t\tsource = source.replace(importModel.getSource(unit), \"\");\n\t\t\t\tfor (String mappedClass : mappedClasses.values()) {\n\t\t\t\t\tif (mappedClass.indexOf('$') < 0) // Skip inner classes\n\t\t\t\t\t\tsource = StringUtil.insert(source, begin, \"import \" + mappedClass.replace('/', '.') + \";\\n\");\n\t\t\t\t}\n\t\t\t} else if (resolution instanceof MultiMemberResolution multiMemberResolution) {\n\t\t\t\tClassEntry ownerEntry = multiMemberResolution.getMemberEntries().getFirst().ownerEntry();\n\n\t\t\t\t// Rename the imported field/method names if they were mapped.\n\t\t\t\tif (importModel.getName().indexOf('*') < 0) {\n\t\t\t\t\tfor (ClassMemberPair pair : multiMemberResolution.getMemberEntries()) {\n\t\t\t\t\t\tMemberEntry memberEntry = pair.memberEntry();\n\t\t\t\t\t\tString mappedMemberName;\n\t\t\t\t\t\tif (memberEntry instanceof FieldEntry fieldEntry) {\n\t\t\t\t\t\t\tmappedMemberName = getMappedField(ownerEntry, fieldEntry);\n\t\t\t\t\t\t} else if (memberEntry instanceof MethodEntry methodEntry) {\n\t\t\t\t\t\t\tmappedMemberName = getMappedMethod(ownerEntry, methodEntry);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tmappedMemberName = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (mappedMemberName != null) {\n\t\t\t\t\t\t\tsource = replacePatternIn(importModel, source, \".\" + memberEntry.getName() + \";\", \".\" + mappedMemberName + \";\");\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// If the owning class was renamed, then rename that.\n\t\t\t\tString mappedClass = getMappedClass(ownerEntry);\n\t\t\t\tif (mappedClass != null) {\n\t\t\t\t\tsource = replacePatternIn(importModel, source,\n\t\t\t\t\t\t\townerEntry.getName().replace('/', '.'),\n\t\t\t\t\t\t\tmappedClass.replace('/', '.'));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Replace package if the class got moved to a different package.\n\t\tif (unit.getDeclaredClasses().getFirst().resolve(resolver) instanceof ClassResolution resolution) {\n\t\t\tString mappedClass = getMappedClass(resolution);\n\t\t\tif (mappedClass != null) {\n\t\t\t\tint slashIndex = mappedClass.lastIndexOf('/');\n\t\t\t\tif (slashIndex > 0) {\n\t\t\t\t\tString mappedPackageName = mappedClass.substring(0, slashIndex);\n\t\t\t\t\tString packageName = unit.getPackage().getName().replace('.', '/');\n\t\t\t\t\tif (!packageName.equals(mappedPackageName)) {\n\t\t\t\t\t\tsource = source.replace(\"package \" + unit.getPackage().getName() + \";\",\n\t\t\t\t\t\t\t\t\"package \" + mappedPackageName.replace('/', '.') + \";\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn source;\n\t}\n\n\t@Nonnull\n\tprivate String replacePatternIn(@Nonnull Model named, @Nonnull String source,\n\t                                @Nullable String before, @Nullable String after) {\n\t\tif (before != null && after != null && !before.equals(after)) {\n\t\t\tRange namedRange = extractRelevantRange(named, source);\n\t\t\tif (namedRange.end() > source.length() || namedRange.isUnknown())\n\t\t\t\treturn source;\n\n\t\t\tString namedSource = getSource(namedRange, source);\n\t\t\tString prefix = source.substring(0, namedRange.begin());\n\t\t\tString suffix = source.substring(namedRange.end());\n\t\t\tString replaced = namedSource.replace(before, after);\n\n\t\t\t// TODO: We should ensure only one replacement ever happens.\n\t\t\t//  - We could only replace if the content replaced is surrounded by boundaries.\n\t\t\t//  - Or just validate our range only has one instance of the 'before' text in it\n\n\t\t\tif (!replaced.equals(namedSource))\n\t\t\t\treturn prefix + replaced + suffix;\n\t\t}\n\t\treturn source;\n\t}\n\n\t@Nonnull\n\tprivate Range extractRelevantRange(@Nonnull Model model, @Nonnull String source) {\n\t\tif (model instanceof ClassModel classModel) {\n\t\t\t// The class model is often the FULL range of the file, and we only want the named section.\n\t\t\tString name = classModel.getName();\n\t\t\tint begin = classModel.getRange().begin();\n\t\t\tint end = source.indexOf(name, begin) + name.length();\n\t\t\tif (end < begin)\n\t\t\t\treturn Range.UNKNOWN;\n\t\t\treturn new Range(begin, end);\n\t\t} else if (model instanceof VariableModel variableModel) {\n\t\t\t// The variable range should include only the variable name.\n\t\t\t// The name doesn't have an associated model, but is after the declared type.\n\t\t\tString name = variableModel.getName();\n\t\t\tint begin = variableModel.getType().getRange().end();\n\n\t\t\t// Enum constants don't have an AST model for their type, so the range is \"unknown\".\n\t\t\t// If we're confident this is an enum constant, then we'll make the beginning range the start of the field name instead.\n\t\t\tif (begin == -1) {\n\t\t\t\tClassModel declaringClass = variableModel.getParentOfType(ClassModel.class);\n\t\t\t\tif (declaringClass != null) {\n\t\t\t\t\tif (declaringClass.resolve(resolver) instanceof ClassResolution declaringResolution\n\t\t\t\t\t\t\t&& AccessFlag.isEnum(declaringResolution.getClassEntry().getAccess())) {\n\t\t\t\t\t\tResolution type = variableModel.getType().resolve(resolver);\n\t\t\t\t\t\tif (type.matches(declaringResolution)) {\n\t\t\t\t\t\t\tbegin = variableModel.getRange().begin();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tint end = source.indexOf(name, begin) + name.length();\n\t\t\tif (end < begin)\n\t\t\t\treturn Range.UNKNOWN;\n\t\t\treturn new Range(begin, end);\n\t\t} else if (model instanceof MethodModel methodModel) {\n\t\t\t// The method range should include only the method name.\n\t\t\t// The name doesn't have an associated model, but is between the return type and first '(' for parameters.\n\t\t\tString name = methodModel.getName();\n\t\t\tint begin = name.equals(\"<init>\") ? methodModel.getRange().begin() : methodModel.getReturnType().getRange().end();\n\t\t\tint end = source.indexOf('(', begin);\n\t\t\tif (end < begin)\n\t\t\t\treturn Range.UNKNOWN;\n\t\t\treturn new Range(begin, end);\n\t\t} else if (model instanceof NameHoldingModel nameHoldingModel) {\n\t\t\t// If the holder has an associated model, yield that model's range.\n\t\t\tif (nameHoldingModel.getNameModel() != null && !nameHoldingModel.getNameModel().getRange().isUnknown())\n\t\t\t\treturn nameHoldingModel.getNameModel().getRange();\n\n\t\t\t// Otherwise limit the size of the range based on the first appearance of the named model's name\n\t\t\t// starting from its reported range beginning point. Also limit by the source length.\n\t\t\tString name = nameHoldingModel.getName();\n\t\t\tRange range = nameHoldingModel.getRange();\n\t\t\tint nameBegin = source.indexOf(name, range.begin());\n\t\t\tint nameEnd = Math.min(nameBegin + name.length(), source.length());\n\t\t\treturn new Range(nameBegin, nameEnd);\n\t\t}\n\n\t\treturn model.getRange();\n\t}\n\n\t@Nullable\n\tprivate String getMappedClass(@Nonnull ClassResolution resolution) {\n\t\treturn getMappedClass(resolution.getClassEntry());\n\t}\n\n\t@Nullable\n\tprivate String getMappedClass(@Nonnull ClassEntry classEntry) {\n\t\tString name = classEntry.getName();\n\t\treturn mappings.getMappedClassName(name);\n\t}\n\n\t@Nullable\n\tprivate String getMappedField(@Nonnull FieldResolution resolution) {\n\t\treturn getMappedField(resolution.getOwnerEntry(), resolution.getFieldEntry());\n\t}\n\n\t@Nullable\n\tprivate String getMappedField(@Nonnull ClassEntry ownerEntry, @Nonnull FieldEntry fieldEntry) {\n\t\tString owner = ownerEntry.getName();\n\t\tString name = fieldEntry.getName();\n\t\tString desc = fieldEntry.getDescriptor();\n\t\treturn mappings.getMappedFieldName(owner, name, desc);\n\t}\n\n\t@Nullable\n\tprivate String getMappedMethod(@Nonnull MethodResolution resolution) {\n\t\treturn getMappedMethod(resolution.getOwnerEntry(), resolution.getMethodEntry());\n\t}\n\n\t@Nullable\n\tprivate String getMappedMethod(@Nonnull ClassEntry ownerEntry, @Nonnull MethodEntry methodEntry) {\n\t\tString owner = ownerEntry.getName();\n\t\tString name = methodEntry.getName();\n\t\tString desc = methodEntry.getDescriptor();\n\t\treturn mappings.getMappedMethodName(owner, name, desc);\n\t}\n\n\t@Nonnull\n\tprivate static String getSimpleName(@Nonnull ClassResolution resolution) {\n\t\treturn getSimpleName(resolution.getClassEntry());\n\t}\n\n\t@Nonnull\n\tprivate static String getSimpleName(@Nonnull ClassEntry entry) {\n\t\treturn Objects.requireNonNull(getSimpleName(entry.getName()));\n\t}\n\n\t@Nullable\n\tprivate static String getSimpleName(@Nullable String name) {\n\t\tif (name == null)\n\t\t\treturn null;\n\t\treturn StringUtil.shortenPath(name);\n\t}\n\n\t@Nonnull\n\tprivate static String getSource(@Nonnull Range range, @Nonnull String source) {\n\t\tint end = Math.min(source.length(), range.end());\n\t\treturn source.substring(range.begin(), end);\n\t}\n\n\tprivate record NamedResolutions(@Nonnull NamedModel named, @Nonnull Resolution resolution) {}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/source/AstResolveResult.java",
    "content": "package software.coley.recaf.services.source;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.sourcesolver.resolve.result.Resolution;\n\n/**\n * Wrapper for {@link Resolution} values.\n *\n * @param isDeclaration\n * \t\tFlag indicating if resolved item is a declaration or reference.\n * @param path\n * \t\tResolved value.\n *\n * @author Matt Coley\n * @see ResolverAdapter\n */\npublic record AstResolveResult(boolean isDeclaration, @Nonnull PathNode<?> path) {\n\t/**\n\t * @param path\n\t * \t\tPath to wrap.\n\t *\n\t * @return Result of declaration for the given path.\n\t */\n\t@Nonnull\n\tpublic static AstResolveResult declared(@Nonnull PathNode<?> path) {\n\t\treturn new AstResolveResult(true, path);\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to wrap.\n\t *\n\t * @return Result of reference to the given path.\n\t */\n\t@Nonnull\n\tpublic static AstResolveResult reference(@Nonnull PathNode<?> path) {\n\t\treturn new AstResolveResult(false, path);\n\t}\n\n\t/**\n\t * @return Copy of self, as a declaration.\n\t */\n\t@Nonnull\n\tpublic AstResolveResult asDeclaration() {\n\t\treturn new AstResolveResult(true, path());\n\t}\n\n\t/**\n\t * @return Copy of self, as a reference.\n\t */\n\t@Nonnull\n\tpublic AstResolveResult asReference() {\n\t\treturn new AstResolveResult(false, path());\n\t}\n\n\t/**\n\t * @param other\n\t * \t\tOther result to match {@link #isDeclaration()} state of.\n\t *\n\t * @return Copy of self, as matching state.\n\t */\n\t@Nonnull\n\tpublic AstResolveResult matchDeclarationState(@Nonnull AstResolveResult other) {\n\t\tif (this == other)\n\t\t\treturn this;\n\t\tif (isDeclaration == other.isDeclaration)\n\t\t\treturn this;\n\t\treturn other.isDeclaration ? asDeclaration() : asReference();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/source/AstService.java",
    "content": "package software.coley.recaf.services.source;\n\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.workspace.WorkspaceCloseListener;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.services.workspace.WorkspaceOpenListener;\nimport software.coley.recaf.util.ReflectUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.ResourceAndroidClassListener;\nimport software.coley.recaf.workspace.model.resource.ResourceJvmClassListener;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\nimport software.coley.sourcesolver.Parser;\nimport software.coley.sourcesolver.model.CompilationUnitModel;\nimport software.coley.sourcesolver.resolve.Resolver;\nimport software.coley.sourcesolver.resolve.entry.BasicClassEntry;\nimport software.coley.sourcesolver.resolve.entry.BasicFieldEntry;\nimport software.coley.sourcesolver.resolve.entry.BasicMethodEntry;\nimport software.coley.sourcesolver.resolve.entry.ClassEntry;\nimport software.coley.sourcesolver.resolve.entry.EntryPool;\nimport software.coley.sourcesolver.resolve.entry.FieldEntry;\nimport software.coley.sourcesolver.resolve.entry.MethodEntry;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.SortedSet;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Stream;\n\n/**\n * Service for tracking shared data for AST parsing.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class AstService implements Service {\n\tpublic static final String ID = \"ast\";\n\t/**\n\t * Max number of referenced classes to build out from.\n\t * See {@link WorkspaceBackedEntryPool#computeEntry(ClassInfo, int)}\n\t */\n\tprivate static final int DEFAULT_TTL = 3;\n\t/**\n\t * It's rare that we'll need more than one parser, so having a shared reference to a single one for re-use is nice.\n\t */\n\tprivate static final Parser sharedParser;\n\tprivate final AstServiceConfig config;\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final Cache<Workspace, EntryPool> entryPoolCache = CacheBuilder.newBuilder()\n\t\t\t.weakKeys() // Intended for the side effect of using '==' for key comparisons over '.equals()'\n\t\t\t.maximumSize(1) // Users will only ever operate on one workspace, so this is free pruning when they switch\n\t\t\t.expireAfterWrite(20, TimeUnit.MINUTES)\n\t\t\t.build();\n\tprivate EntryPool currentWorkspacePool;\n\n\tstatic {\n\t\t// We need to have this reflection patch call before we create a parser because of the\n\t\t// tight coupling with the 'jdk.compiler' module internals that the parser has.\n\t\tReflectUtil.patch();\n\t\tsharedParser = new Parser();\n\t}\n\n\t@Inject\n\tpublic AstService(@Nonnull AstServiceConfig config,\n\t                  @Nonnull WorkspaceManager workspaceManager) {\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.config = config;\n\n\t\tListenerHost host = new ListenerHost();\n\t\tworkspaceManager.addWorkspaceOpenListener(host);\n\t\tworkspaceManager.addWorkspaceCloseListener(host);\n\t}\n\n\t/**\n\t * @return New Java source parser.\n\t *\n\t * @see #getSharedJavaParser() Shared parser instance.\n\t */\n\t@Nonnull\n\tpublic Parser newJavaParser() {\n\t\treturn new Parser();\n\t}\n\n\t/**\n\t * @return Shared parser instance.\n\t */\n\t@Nonnull\n\tpublic Parser getSharedJavaParser() {\n\t\treturn sharedParser;\n\t}\n\n\t/**\n\t * @param parser\n\t * \t\tParser to use.\n\t * @param source\n\t * \t\tJava source to parse.\n\t *\n\t * @return Parsed model of Java source file.\n\t */\n\t@Nonnull\n\tpublic CompilationUnitModel parseJava(@Nonnull Parser parser, @Nonnull String source) {\n\t\treturn parser.parse(source);\n\t}\n\n\t/**\n\t * @param unit\n\t * \t\tUnit to resolve references within.\n\t *\n\t * @return Resolver to link results to {@link PathNode paths in the current workspace}.\n\t */\n\t@Nonnull\n\tpublic ResolverAdapter newJavaResolver(@Nonnull CompilationUnitModel unit) {\n\t\tWorkspace workspace = workspaceManager.getCurrent();\n\t\treturn newJavaResolver(workspace, poolFromWorkspace(workspace), unit);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace with classes to link resolved references to.\n\t * @param unit\n\t * \t\tUnit to resolve references within.\n\t *\n\t * @return Resolver to link results to {@link PathNode paths in the provided workspace}.\n\t */\n\t@Nonnull\n\tpublic ResolverAdapter newJavaResolver(@Nonnull Workspace workspace, @Nonnull CompilationUnitModel unit) {\n\t\treturn newJavaResolver(workspace, poolFromWorkspace(workspace), unit);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace with classes to link resolved references to.\n\t * @param pool\n\t * \t\tPool containing class models used by the resolver.\n\t * @param unit\n\t * \t\tUnit to resolve references within.\n\t *\n\t * @return Resolver to link results to {@link PathNode paths in the provided workspace}.\n\t */\n\t@Nonnull\n\tprivate ResolverAdapter newJavaResolver(@Nonnull Workspace workspace, @Nonnull EntryPool pool, @Nonnull CompilationUnitModel unit) {\n\t\tprefillReferencedClasses(workspace, pool, unit);\n\t\treturn new ResolverAdapter(workspace, unit, pool);\n\t}\n\n\t/**\n\t * @param unit\n\t * \t\tUnit to map.\n\t * @param resolver\n\t * \t\tResolver to analyze the unit with.\n\t * @param mappings\n\t * \t\tMappings to apply.\n\t *\n\t * @return Modified source code based on the provided mappings.\n\t */\n\t@Nonnull\n\tpublic String applyMappings(@Nonnull CompilationUnitModel unit, @Nonnull Resolver resolver, @Nonnull Mappings mappings) {\n\t\treturn new AstMapper(unit, resolver, mappings).apply();\n\t}\n\n\t/**\n\t * Takes classes from the same package that the given unit is from and populates them in the provided entry pool.\n\t * <br>\n\t * This is a very important step we must take before using the pool for content resolving.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to pull classes from.\n\t * @param pool\n\t * \t\tPool to dump class models into.\n\t * @param unit\n\t * \t\tUnit to prefill classes for.\n\t */\n\tprivate void prefillReferencedClasses(@Nonnull Workspace workspace, @Nonnull EntryPool pool, @Nonnull CompilationUnitModel unit) {\n\t\tif (pool instanceof WorkspaceBackedEntryPool workspacePool) {\n\t\t\tString unitPackage = unit.getPackage().getName().replace('.', '/');\n\t\t\tSortedSet<ClassPathNode> classesInPackage = unitPackage.isEmpty() ?\n\t\t\t\t\tworkspace.findClasses(c -> c.getPackageName() == null) :\n\t\t\t\t\tworkspace.findClasses(c -> unitPackage.equals(c.getPackageName()));\n\t\t\tfor (ClassPathNode classPath : classesInPackage)\n\t\t\t\tworkspacePool.computeEntry(classPath.getValue(), DEFAULT_TTL);\n\t\t}\n\t}\n\n\t/**\n\t * Maps a workspace to an entry pool instance.\n\t * <br>\n\t * It is very important that we re-use pools so that we do not waste time\n\t * repetitively filling new pools with the same data.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to pull class information from.\n\t *\n\t * @return Entry pool to store class information for context resolution purposes.\n\t */\n\t@Nonnull\n\tprivate EntryPool poolFromWorkspace(@Nonnull Workspace workspace) {\n\t\tEntryPool pool = entryPoolCache.getIfPresent(workspace);\n\t\tif (pool == null) {\n\t\t\tpool = new WorkspaceBackedEntryPool(workspace);\n\t\t\tentryPoolCache.put(workspace, pool);\n\t\t}\n\t\treturn pool;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic AstServiceConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\t/**\n\t * Empty pool that yields nothing.\n\t */\n\tprivate static class EmptyEntryPool implements EntryPool {\n\t\tprivate static final EmptyEntryPool INSTANCE = new EmptyEntryPool();\n\n\t\t@Override\n\t\tpublic void register(@Nonnull ClassEntry entry) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Nullable\n\t\t@Override\n\t\tpublic ClassEntry getClass(@Nonnull String name) {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic List<ClassEntry> getClassesInPackage(@Nullable String packageName) {\n\t\t\treturn Collections.emptyList();\n\t\t}\n\t}\n\n\t/**\n\t * Pool that pulls classes from a {@link Workspace}.\n\t */\n\tprivate static class WorkspaceBackedEntryPool implements EntryPool, ResourceJvmClassListener, ResourceAndroidClassListener {\n\t\tprivate final Map<String, ClassEntry> cache = new ConcurrentHashMap<>();\n\t\tprivate final Workspace workspace;\n\n\t\tprivate WorkspaceBackedEntryPool(@Nonnull Workspace workspace) {\n\t\t\tthis.workspace = workspace;\n\n\t\t\t// TODO: When we have classes update, we will want to invalidate their child classes\n\t\t\t//  in the cache as well.\n\t\t\tworkspace.getPrimaryResource().addListener(this);\n\t\t}\n\n\t\t@Override\n\t\tpublic void register(@Nonnull ClassEntry entry) {\n\t\t\tcache.put(entry.getName(), entry);\n\t\t}\n\n\t\t@Nullable\n\t\t@Override\n\t\tpublic ClassEntry getClass(@Nonnull String name) {\n\t\t\treturn getClass(name, DEFAULT_TTL);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic List<ClassEntry> getClassesInPackage(@Nullable String packageName) {\n\t\t\tStream<ClassEntry> workspaceEntries = workspace.findClasses(c -> Objects.equals(packageName, c.getPackageName())).stream()\n\t\t\t\t\t.map(p -> computeEntry(p.getValue(), DEFAULT_TTL));\n\t\t\tStream<ClassEntry> cacheEntries = cache.values().stream()\n\t\t\t\t\t.filter(e -> Objects.equals(packageName, e.getPackageName()));\n\t\t\treturn Stream.concat(workspaceEntries, cacheEntries).toList();\n\t\t}\n\n\t\t/**\n\t\t * Lookup a class entry by name, creating it if possible and not already cached.\n\t\t *\n\t\t * @param name\n\t\t * \t\tName of class.\n\t\t * @param ttl\n\t\t * \t\tTime-to-live, delegated to {@link #computeEntry(ClassInfo, int)}.\n\t\t *\n\t\t * @return Class entry by the given name, if cached or discoverable in the workspace.\n\t\t */\n\t\t@Nullable\n\t\tprivate ClassEntry getClass(@Nonnull String name, int ttl) {\n\t\t\tClassEntry entry = cache.get(name);\n\t\t\tif (entry != null)\n\t\t\t\treturn entry;\n\n\t\t\tClassPathNode path = workspace.findClass(name);\n\t\t\tif (path == null)\n\t\t\t\treturn null;\n\n\t\t\tClassInfo info = path.getValue();\n\t\t\treturn computeEntry(info, ttl);\n\t\t}\n\n\t\t/**\n\t\t * @param info\n\t\t * \t\tClass model in the workspace to map to a form for context resolution.\n\t\t * @param ttl\n\t\t * \t\tTime-to-live, which will prevent construction of new entries when it reaches 0.\n\t\t *\n\t\t * @return Newly created entry modeling the given class.\n\t\t */\n\t\t@Nullable\n\t\tprivate ClassEntry computeEntry(@Nonnull ClassInfo info, int ttl) {\n\t\t\tString className = info.getName();\n\t\t\tClassEntry entry = cache.get(className);\n\t\t\tif (entry != null)\n\t\t\t\treturn entry;\n\n\t\t\t// Decrement TTL and if it reaches 0 we abort.\n\t\t\tif (--ttl <= 0)\n\t\t\t\treturn null;\n\n\t\t\t// Construct the class entry model.\n\t\t\t//   NOTE: Parent types are fully computed regardless of TTL. The TTL reduction is used further below.\n\t\t\tClassEntry superClass = info.getSuperName() == null ? null : getClass(info.getSuperName());\n\t\t\tList<FieldEntry> fields = info.getFields().stream()\n\t\t\t\t\t.map(f -> (FieldEntry) new BasicFieldEntry(f.getName(), f.getDescriptor(), f.getAccess()))\n\t\t\t\t\t.toList();\n\t\t\tList<MethodEntry> methods = info.getMethods().stream()\n\t\t\t\t\t.map(m -> (MethodEntry) new BasicMethodEntry(m.getName(), m.getDescriptor(), m.getAccess()))\n\t\t\t\t\t.toList();\n\t\t\tList<ClassEntry> innerClasses = new ArrayList<>();\n\t\t\tList<ClassEntry> interfaces = new ArrayList<>();\n\t\t\tString outerClassName = info.getOuterClassName();\n\t\t\tClassEntry outerClass = outerClassName != null && outerClassName.startsWith(className + '$') ? cache.get(outerClassName) : null;\n\t\t\tentry = new BasicClassEntry(className, info.getAccess(), superClass, interfaces, innerClasses, outerClass, fields, methods);\n\t\t\tregister(entry);\n\n\t\t\t// Lists of other classes are populated after we put the entry in the pool to prevent entry building cycles.\n\t\t\tfor (InnerClassInfo innerClass : info.getInnerClasses()) {\n\t\t\t\tif (innerClass.isExternalReference())\n\t\t\t\t\tcontinue;\n\t\t\t\tClassEntry innerClassEntry = getClass(innerClass.getInnerClassName());\n\t\t\t\tif (innerClassEntry != null)\n\t\t\t\t\tinnerClasses.add(innerClassEntry);\n\t\t\t}\n\t\t\tfor (String implemented : info.getInterfaces()) {\n\t\t\t\tClassEntry interfaceEntry = getClass(implemented);\n\t\t\t\tif (interfaceEntry != null)\n\t\t\t\t\tinterfaces.add(interfaceEntry);\n\t\t\t}\n\n\t\t\t// Ensure all referenced classes are populated in the pool.\n\t\t\t// Because we only branch out based off a decrementing TTL counter, we should only end up mapping a few levels outwards.\n\t\t\t// This ensures that when we do any resolving logic with this pool, associated classes are readily available in the pool.\n\t\t\t//\n\t\t\t// There is a concern that the edges which fall on TTL==1 won't have their contents \"readily available\"\n\t\t\t// but in practice when those missing items are loaded it kicks off another round of pre-emptive loading.\n\t\t\t// This should result in a UX that is largely smoother overall, especially if the user is interacting with\n\t\t\t// classes that are \"nearby\" each other in terms of inheritance or external references.\n\t\t\tif (info instanceof JvmClassInfo jvmClassInfo)\n\t\t\t\tfor (String referencedClass : jvmClassInfo.getReferencedClasses())\n\t\t\t\t\tgetClass(referencedClass, ttl);\n\n\t\t\treturn entry;\n\t\t}\n\n\t\t@Override\n\t\tpublic void onNewClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle,\n\t\t                       @Nonnull AndroidClassInfo cls) {\n\t\t\tcache.remove(cls.getName());\n\t\t}\n\n\t\t@Override\n\t\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle,\n\t\t                          @Nonnull AndroidClassInfo oldCls, @Nonnull AndroidClassInfo newCls) {\n\t\t\tcache.remove(newCls.getName());\n\t\t}\n\n\t\t@Override\n\t\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle,\n\t\t                          @Nonnull AndroidClassInfo cls) {\n\t\t\tcache.remove(cls.getName());\n\t\t}\n\n\t\t@Override\n\t\tpublic void onNewClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t\t                       @Nonnull JvmClassInfo cls) {\n\t\t\tcache.remove(cls.getName());\n\t\t}\n\n\t\t@Override\n\t\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t\t                          @Nonnull JvmClassInfo oldCls, @Nonnull JvmClassInfo newCls) {\n\t\t\tcache.remove(newCls.getName());\n\t\t}\n\n\t\t@Override\n\t\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t\t                          @Nonnull JvmClassInfo cls) {\n\t\t\tcache.remove(cls.getName());\n\t\t}\n\t}\n\n\tprivate class ListenerHost implements WorkspaceOpenListener, WorkspaceCloseListener {\n\t\t@Override\n\t\tpublic void onWorkspaceOpened(@Nonnull Workspace workspace) {\n\t\t\tcurrentWorkspacePool = poolFromWorkspace(workspace);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onWorkspaceClosed(@Nonnull Workspace workspace) {\n\t\t\tentryPoolCache.invalidate(workspace);\n\t\t\tcurrentWorkspacePool = EmptyEntryPool.INSTANCE;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/source/AstServiceConfig.java",
    "content": "package software.coley.recaf.services.source;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link AstService}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class AstServiceConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic AstServiceConfig() {\n\t\tsuper(ConfigGroups.SERVICE_ANALYSIS, AstService.ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/source/ResolverAdapter.java",
    "content": "package software.coley.recaf.services.source;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.DirectoryPathNode;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.sourcesolver.model.AnnotationExpressionModel;\nimport software.coley.sourcesolver.model.AssignmentExpressionModel;\nimport software.coley.sourcesolver.model.ClassModel;\nimport software.coley.sourcesolver.model.CompilationUnitModel;\nimport software.coley.sourcesolver.model.ErroneousModel;\nimport software.coley.sourcesolver.model.MethodBodyModel;\nimport software.coley.sourcesolver.model.MethodModel;\nimport software.coley.sourcesolver.model.Model;\nimport software.coley.sourcesolver.model.ModifiersModel;\nimport software.coley.sourcesolver.model.TypeModel;\nimport software.coley.sourcesolver.model.VariableModel;\nimport software.coley.sourcesolver.resolve.BasicResolver;\nimport software.coley.sourcesolver.resolve.entry.ClassEntry;\nimport software.coley.sourcesolver.resolve.entry.ClassMemberPair;\nimport software.coley.sourcesolver.resolve.entry.EntryPool;\nimport software.coley.sourcesolver.resolve.entry.FieldEntry;\nimport software.coley.sourcesolver.resolve.entry.MethodEntry;\nimport software.coley.sourcesolver.resolve.result.ClassResolution;\nimport software.coley.sourcesolver.resolve.result.FieldResolution;\nimport software.coley.sourcesolver.resolve.result.MethodResolution;\nimport software.coley.sourcesolver.resolve.result.MultiClassResolution;\nimport software.coley.sourcesolver.resolve.result.MultiMemberResolution;\nimport software.coley.sourcesolver.resolve.result.PackageResolution;\nimport software.coley.sourcesolver.resolve.result.Resolution;\nimport software.coley.sourcesolver.resolve.result.Resolutions;\n\nimport java.util.List;\n\n/**\n * Adapts {@link Resolution} values into our {@link AstResolveResult}.\n *\n * @author Matt Coley\n */\npublic class ResolverAdapter extends BasicResolver {\n\tprivate final Workspace workspace;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull classes from in order to adapt {@link Resolution} values into our {@link AstResolveResult} model.\n\t * @param unit\n\t * \t\tRoot element model.\n\t * @param pool\n\t * \t\tPool to access class metadata.\n\t */\n\tpublic ResolverAdapter(@Nonnull Workspace workspace, @Nonnull CompilationUnitModel unit, @Nonnull EntryPool pool) {\n\t\tsuper(unit, pool);\n\t\tthis.workspace = workspace;\n\t}\n\n\t/**\n\t * Marks the declared class in the compilation unit as being resolved to the given class.\n\t *\n\t * @param cls\n\t * \t\tClass that represents the code outlined by the compilation unit.\n\t */\n\tpublic void setClassContext(@Nonnull ClassInfo cls) {\n\t\tClassModel model = getUnit().getDeclaredClasses().getFirst();\n\t\tClassEntry entry = getPool().getClass(cls.getName());\n\t\tif (model != null && entry != null)\n\t\t\tsetDeclaredClass(model, entry);\n\t}\n\n\t/**\n\t * @param position\n\t * \t\tAbsolute position in the source code of the item we want to resolve.\n\t *\n\t * @return Our mapped resolution result which points to a path in the workspace for the resolved content.\n\t */\n\t@Nullable\n\tpublic AstResolveResult resolveThenAdapt(int position) {\n\t\t// Find the deepest model at position.\n\t\tModel model = getUnit();\n\t\twhile (true) {\n\t\t\tit:\n\t\t\t{\n\t\t\t\tfor (Model child : model.getChildren()) {\n\t\t\t\t\tif (child.getRange().isWithin(position) && !(child instanceof ErroneousModel)) {\n\t\t\t\t\t\tmodel = child;\n\t\t\t\t\t\tbreak it;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Resolve the content at the given position then adapt it.\n\t\tResolution resolution = resolveAt(position, model);\n\t\treturn adapt(resolution, model);\n\t}\n\n\t/**\n\t * @param resolution\n\t * \t\tResolution to adapt.\n\t * @param target\n\t * \t\tTarget model that was the item being resolved.\n\t *\n\t * @return Our mapped resolution result which points to a path in the workspace for the resolved content.\n\t */\n\t@Nullable\n\tpublic AstResolveResult adapt(@Nonnull Resolution resolution, @Nonnull Model target) {\n\t\tif (resolution.isUnknown())\n\t\t\treturn null;\n\t\telse if (resolution instanceof ClassResolution classResolution) {\n\t\t\tString name = classResolution.getClassEntry().getName();\n\t\t\tClassPathNode path = workspace.findClass(name);\n\t\t\tif (path == null)\n\t\t\t\treturn null;\n\n\t\t\t// If the target *is the class* then it is a declaration.\n\t\t\tif (target instanceof ClassModel && target.resolve(this).matches(resolution))\n\t\t\t\treturn AstResolveResult.declared(path);\n\n\t\t\t// If the target is within a method body, it is always a reference.\n\t\t\t// Same for:\n\t\t\t//  - Contents of annotation expressions like \"@MyAnno(foo = bar)\"\n\t\t\t//  - Contents of assignments in places like fields\n\t\t\t//  - Contents of type names\n\t\t\t//    - The class name is just a name model, not a type model so\n\t\t\tif (target.getParentOfType(MethodBodyModel.class) != null)\n\t\t\t\treturn AstResolveResult.reference(path);\n\t\t\tif (target.getParentOfType(AnnotationExpressionModel.class) != null)\n\t\t\t\treturn AstResolveResult.reference(path);\n\t\t\tif (target.getParentOfType(AssignmentExpressionModel.class) != null)\n\t\t\t\treturn AstResolveResult.reference(path);\n\t\t\tif (target.getParentOfType(TypeModel.class) != null)\n\t\t\t\treturn AstResolveResult.reference(path);\n\n\t\t\tClassModel parentClassDeclaration = target.getParentOfType(ClassModel.class);\n\t\t\tif (parentClassDeclaration != null && parentClassDeclaration.resolve(this).matches(resolution))\n\t\t\t\treturn AstResolveResult.declared(path);\n\t\t\treturn AstResolveResult.reference(path);\n\t\t} else if (resolution instanceof FieldResolution fieldResolution) {\n\t\t\tString ownerName = fieldResolution.getOwnerEntry().getName();\n\t\t\tClassPathNode ownerPath = workspace.findClass(ownerName);\n\t\t\tif (ownerPath == null)\n\t\t\t\treturn null;\n\t\t\tFieldEntry fieldEntry = fieldResolution.getFieldEntry();\n\t\t\tClassMemberPathNode fieldPath = ownerPath.child(fieldEntry.getName(), fieldEntry.getDescriptor());\n\t\t\tif (fieldPath == null)\n\t\t\t\treturn null;\n\n\t\t\t// Determine if it's a declaration or reference.\n\t\t\t//  - Check if any declared class's fields have the target model in their range\n\t\t\tfor (ClassModel declaredClass : getUnit().getRecursiveChildrenOfType(ClassModel.class))\n\t\t\t\tfor (VariableModel field : declaredClass.getFields())\n\t\t\t\t\tif (field.getRange().isWithin(target.getRange().begin()))\n\t\t\t\t\t\treturn AstResolveResult.declared(fieldPath);\n\n\t\t\treturn AstResolveResult.reference(fieldPath);\n\t\t} else if (resolution instanceof MethodResolution methodResolution) {\n\t\t\tString ownerName = methodResolution.getOwnerEntry().getName();\n\t\t\tClassPathNode ownerPath = workspace.findClass(ownerName);\n\t\t\tif (ownerPath == null)\n\t\t\t\treturn null;\n\t\t\tMethodEntry methodEntry = methodResolution.getMethodEntry();\n\t\t\tClassMemberPathNode methodPath = ownerPath.child(methodEntry.getName(), methodEntry.getDescriptor());\n\t\t\tif (methodPath == null)\n\t\t\t\treturn null;\n\n\t\t\t// The model we resolved is a declaration if:\n\t\t\t//  - It is a 'MethodModel' that resolves to the same method\n\t\t\t//  - The declaring class must define a method of the same name/type\n\t\t\tfor (ClassModel declaredClass : getUnit().getRecursiveChildrenOfType(ClassModel.class))\n\t\t\t\tif (target instanceof MethodModel targetMethod\n\t\t\t\t\t\t&& targetMethod.resolve(this).equals(methodResolution)\n\t\t\t\t\t\t&& declaredClass.resolve(this) instanceof ClassResolution declaredClassResolution\n\t\t\t\t\t\t&& declaredClassResolution.matches(methodResolution.getOwnerResolution())\n\t\t\t\t\t\t&& methodResolution.matches(declaredClassResolution.getDeclaredMemberResolution(methodEntry))) {\n\t\t\t\t\treturn AstResolveResult.declared(methodPath);\n\t\t\t\t} else if (target instanceof ModifiersModel\n\t\t\t\t\t\t&& target.getParent() instanceof MethodModel parentMethod\n\t\t\t\t\t\t&& parentMethod.isStaticInitializer()) {\n\t\t\t\t\treturn AstResolveResult.declared(methodPath);\n\t\t\t\t}\n\t\t\treturn AstResolveResult.reference(methodPath);\n\t\t} else if (resolution instanceof MultiMemberResolution multiMemberResolution) {\n\t\t\t// Used in static star import contexts such as 'Math.*' or single method static imports such as 'Math.min'.\n\t\t\t// For stars, yield the class. For single members, yield the member.\n\t\t\tList<ClassMemberPair> memberEntries = multiMemberResolution.getMemberEntries();\n\t\t\tClassMemberPair firstMember = memberEntries.getFirst();\n\t\t\tString firstClassName = firstMember.ownerEntry().getName();\n\t\t\tif (memberEntries.size() == 1) {\n\t\t\t\t// Single member\n\t\t\t\treturn adapt(Resolutions.ofMember(firstMember), target);\n\t\t\t} else if (memberEntries.size() > 1) {\n\t\t\t\t// Multiple members\n\t\t\t\tClassPathNode path = workspace.findClass(firstClassName);\n\t\t\t\tif (path != null)\n\t\t\t\t\treturn AstResolveResult.reference(path);\n\t\t\t}\n\t\t} else if (resolution instanceof MultiClassResolution multiClassResolution) {\n\t\t\t// Used in start import contexts such as 'java.util.*' so yield the package they're all residing within.\n\t\t\tString firstClassName = multiClassResolution.getClassEntries().getFirst().getName();\n\t\t\tint slashIndex = firstClassName.lastIndexOf('/');\n\t\t\tif (slashIndex > 0) {\n\t\t\t\tString packageName = firstClassName.substring(0, slashIndex);\n\t\t\t\tDirectoryPathNode path = workspace.findPackage(packageName);\n\t\t\t\tif (path != null)\n\t\t\t\t\treturn AstResolveResult.reference(path);\n\t\t\t}\n\t\t} else if (resolution instanceof PackageResolution packageResolution) {\n\t\t\tString packageName = packageResolution.getPackageName();\n\t\t\tif (packageName != null) {\n\t\t\t\tDirectoryPathNode path = workspace.findPackage(packageName);\n\t\t\t\tif (path != null)\n\t\t\t\t\treturn AstResolveResult.reference(path);\n\t\t\t}\n\t\t}\n\t\t// TODO: To support operating on method parameters we need to update source-solver\n\t\t//  to have a resolution model on parameters. Then we may also want to have a generic\n\t\t//  local variable solver for similar capabilities. This would let us create mappings\n\t\t//  in the UI for variables which would be nice.\n\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/text/TextFormatConfig.java",
    "content": "package software.coley.recaf.services.text;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.util.EscapeUtil;\nimport software.coley.recaf.util.StringUtil;\n\n/**\n * Config for text formatting.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class TextFormatConfig extends BasicConfigContainer {\n\tpublic static final String ID = \"text-format\";\n\tprivate final ObservableBoolean escape = new ObservableBoolean(true);\n\tprivate final ObservableBoolean shorten = new ObservableBoolean(true);\n\tprivate final ObservableInteger maxLength = new ObservableInteger(120);\n\n\t@Inject\n\tpublic TextFormatConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, ID + CONFIG_SUFFIX);\n\t\t// Add values\n\t\taddValue(new BasicConfigValue<>(\"escape\", boolean.class, escape));\n\t\taddValue(new BasicConfigValue<>(\"shorten\", boolean.class, shorten));\n\t\taddValue(new BasicConfigValue<>(\"max-length\", int.class, maxLength));\n\t}\n\n\t/**\n\t * @return {@code true} to escape text with {@link #filter(String)}.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getDoEscape() {\n\t\treturn escape;\n\t}\n\n\t/**\n\t * @return {@code true} to shorten path text with {@link #filter(String)}.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getDoShortenPaths() {\n\t\treturn shorten;\n\t}\n\n\t/**\n\t * @return {@code true} to limit the length of text with {@link #filter(String)}.\n\t */\n\t@Nonnull\n\tpublic ObservableInteger getMaxLength() {\n\t\treturn maxLength;\n\t}\n\n\t/**\n\t * @param string\n\t * \t\tSome text to filter.\n\t *\n\t * @return Filtered text based on current config.\n\t */\n\tpublic String filter(@Nullable String string) {\n\t\treturn filter(string, true, true, true);\n\t}\n\n\t/**\n\t * @param string\n\t * \t\tSome text to filter.\n\t * @param shortenPath\n\t * \t\tApply path shortening filtering.\n\t * @param escape\n\t * \t\tApply escaping.\n\t * @param maxLength\n\t * \t\tApply max length cap.\n\t *\n\t * @return Filtered text based on current config.\n\t */\n\tpublic String filter(@Nullable String string, boolean shortenPath, boolean escape, boolean maxLength) {\n\t\tif (string == null) return null;\n\t\tif (shortenPath) string = filterShorten(string);\n\t\tif (escape) string = filterEscape(string);\n\t\tif (maxLength) string = filterMaxLength(string);\n\t\treturn string;\n\t}\n\n\t/**\n\t * @param string\n\t * \t\tSome text to filter.\n\t *\n\t * @return Filtered text based on current config.\n\t */\n\tpublic String filterShorten(@Nullable String string) {\n\t\tif (string != null && shorten.getValue())\n\t\t\treturn StringUtil.shortenPath(string);\n\t\treturn string;\n\t}\n\n\t/**\n\t * @param string\n\t * \t\tSome text to filter.\n\t *\n\t * @return Filtered text based on current config.\n\t */\n\tpublic String filterEscape(@Nullable String string) {\n\t\tif (string != null && escape.getValue())\n\t\t\treturn EscapeUtil.escapeStandardAndUnicodeWhitespace(string);\n\t\treturn string;\n\t}\n\n\t/**\n\t * @param string\n\t * \t\tSome text to filter.\n\t *\n\t * @return Filtered text based on current config.\n\t */\n\tpublic String filterMaxLength(@Nullable String string) {\n\t\tif (string != null && maxLength.getValue() != null) {\n\t\t\tint maxLengthPrim = maxLength.getValue();\n\t\t\tif (string.length() > maxLengthPrim)\n\t\t\t\tstring = string.substring(0, maxLengthPrim) + \"…\";\n\t\t}\n\t\treturn string;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/transform/CancellableTransformationFeedback.java",
    "content": "package software.coley.recaf.services.transform;\n\n/**\n * Feedback that allows cancelling a transformation.\n *\n * @author Matt Coley\n */\npublic class CancellableTransformationFeedback implements TransformationFeedback {\n\tprivate boolean canceled;\n\n\t/**\n\t * Mark transformation as cancelled.\n\t */\n\tpublic void cancel() {\n\t\tcanceled = true;\n\t}\n\n\t@Override\n\tpublic boolean hasRequestedCancellation() {\n\t\treturn canceled;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/transform/ClassTransformer.java",
    "content": "package software.coley.recaf.services.transform;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Collections;\nimport java.util.Set;\n\n/**\n * Outlines base transformation information such as the name and list of any dependencies.\n *\n * @author Matt Coley\n */\npublic interface ClassTransformer {\n\t/**\n\t * @return Name of the transformer.\n\t */\n\t@Nonnull\n\tString name();\n\n\t/**\n\t * @return {@code true} if this transformer should not be applied to following passes if in the current pass it reports no work being done.\n\t * {@code false} if this transformer should run in all passes.\n\t */\n\tdefault boolean pruneAfterNoWork() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @return Set of transformer classes that are recommended to be run before this one, but not strictly required.\n\t *\n\t * @see #recommendedSuccessors()\n\t * @see #dependencies()\n\t */\n\t@Nonnull\n\tdefault Set<Class<? extends ClassTransformer>> recommendedPredecessors() {\n\t\treturn Collections.emptySet();\n\t}\n\n\t/**\n\t * @return Set of transformer classes that are recommended to be run after this one, but not strictly required.\n\t *\n\t * @see #recommendedPredecessors()\n\t */\n\t@Nonnull\n\tdefault Set<Class<? extends ClassTransformer>> recommendedSuccessors() {\n\t\treturn Collections.emptySet();\n\t}\n\n\t/**\n\t * @return Set of transformer classes that must run before this one.\n\t *\n\t * @see #recommendedPredecessors()\n\t */\n\t@Nonnull\n\tdefault Set<Class<? extends ClassTransformer>> dependencies() {\n\t\treturn Collections.emptySet();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/transform/JvmClassTransformer.java",
    "content": "package software.coley.recaf.services.transform;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport org.objectweb.asm.tree.ClassNode;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Outlines the base JVM transformation contract.\n * <p>\n * <b>NOTE:</b> Internal transformers must be {@link Dependent} scoped so that they do not get proxied by CDI.\n * See {@link JvmTransformerContext#getJvmTransformer(Class)}.\n *\n * @author Matt Coley\n */\npublic interface JvmClassTransformer extends ClassTransformer {\n\t/**\n\t * Used to do any workspace-scope setup actions before transformations occur.\n\t *\n\t * @param context\n\t * \t\tTransformation context for access to other transformers and recording class changes.\n\t * @param workspace\n\t * \t\tWorkspace containing classes to transform.\n\t */\n\tdefault void setup(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace) {}\n\n\t/**\n\t * Implementations can {@link #dependencies() depend on other transformers} and access them\n\t * via {@link JvmTransformerContext#getJvmTransformer(Class)}. This may be useful in cases where you want to have\n\t * one transformer act as a shared data-storage between multiple transformers.\n\t * <p>\n\t * To record changes to the given {@code classInfo} you can:\n\t * <ul>\n\t *     <li>Record a {@link ClassNode} via {@link JvmTransformerContext#setNode(JvmClassBundle, JvmClassInfo, ClassNode)}</li>\n\t *     <li>Record bytecode via {@link JvmTransformerContext#setBytecode(JvmClassBundle, JvmClassInfo, byte[])}</li>\n\t * </ul>\n\t *\n\t * @param context\n\t * \t\tTransformation context for access to other transformers and recording class changes.\n\t * @param workspace\n\t * \t\tWorkspace containing the class.\n\t * @param resource\n\t * \t\tResource containing the class.\n\t * @param bundle\n\t * \t\tBundle containing the class.\n\t * @param initialClassState\n\t * \t\tThe initial state of the class to transform.\n\t * \t\tDo not use this as the base of any transformation.\n\t * \t\tUse {@link JvmTransformerContext#getNode(JvmClassBundle, JvmClassInfo)}\n\t * \t\tor {@link JvmTransformerContext#getBytecode(JvmClassBundle, JvmClassInfo)}\n\t * \t\tto look up the current transformed state of the class.\n\t *\n\t * @throws TransformationException\n\t * \t\tWhen the class cannot be transformed for any reason.\n\t */\n\tvoid transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t               @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t               @Nonnull JvmClassInfo initialClassState) throws TransformationException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/transform/JvmTransformResult.java",
    "content": "package software.coley.recaf.services.transform;\n\nimport software.coley.recaf.info.JvmClassInfo;\n\n/**\n * Intermediate holder of transformations of workspace JVM classes.\n *\n * @author Matt Coley\n */\npublic interface JvmTransformResult extends TransformResult<JvmClassTransformer, JvmClassInfo> {}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/transform/JvmTransformerContext.java",
    "content": "package software.coley.recaf.services.transform;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.analysis.Frame;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.path.ResourcePathNode;\nimport software.coley.recaf.services.deobfuscation.transform.generic.DeadCodeRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.FrameRemovingTransformer;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.mapping.aggregate.AggregatedMappings;\nimport software.coley.recaf.util.analysis.ReAnalyzer;\nimport software.coley.recaf.util.analysis.ReInterpreter;\nimport software.coley.recaf.util.analysis.lookup.BasicGetStaticLookup;\nimport software.coley.recaf.util.analysis.lookup.BasicInvokeStaticLookup;\nimport software.coley.recaf.util.analysis.lookup.BasicInvokeVirtualLookup;\nimport software.coley.recaf.util.analysis.lookup.GetFieldLookup;\nimport software.coley.recaf.util.analysis.lookup.GetStaticLookup;\nimport software.coley.recaf.util.analysis.lookup.InvokeStaticLookup;\nimport software.coley.recaf.util.analysis.lookup.InvokeVirtualLookup;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.visitors.FrameSkippingVisitor;\nimport software.coley.recaf.util.visitors.WorkspaceClassWriter;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Supplier;\n\n/**\n * Context for holding a number of JVM class transformers and shared state for transformation.\n *\n * @author Matt Coley\n */\npublic class JvmTransformerContext {\n\tprivate static final Logger logger = Logging.get(JvmTransformerContext.class);\n\tprivate final Map<Class<? extends JvmClassTransformer>, JvmClassTransformer> transformerMap;\n\tprivate final AggregatedMappings mappings;\n\tprivate final Set<String> classesToRemove = ConcurrentHashMap.newKeySet();\n\tprivate final Map<String, JvmClassData> classData = new ConcurrentHashMap<>();\n\tprivate final Set<String> recomputeFrameClasses = ConcurrentHashMap.newKeySet();\n\tprivate final ThreadLocal<Boolean> transformerDidWork = ThreadLocal.withInitial(() -> false);\n\tprivate final Workspace workspace;\n\tprivate final WorkspaceResource resource;\n\tprivate Supplier<GetFieldLookup> getFieldLookupSupplier = () -> null;\n\tprivate Supplier<GetStaticLookup> getStaticLookupSupplier = BasicGetStaticLookup::new;\n\tprivate Supplier<InvokeVirtualLookup> invokeVirtualLookupSupplier = BasicInvokeVirtualLookup::new;\n\tprivate Supplier<InvokeStaticLookup> invokeStaticLookupSupplier = BasicInvokeStaticLookup::new;\n\tprivate boolean dropFaultyClasses; // For debugging, not exposed publicly.\n\n\t/**\n\t * Constructs a new context from an array of transformers.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace containing the classes to transform.\n\t * @param resource\n\t * \t\tResource in the workspace containing classes to transform. Should always be the {@link Workspace#getPrimaryResource()}.\n\t * @param transformers\n\t * \t\tTransformers to associate with this context.\n\t */\n\tpublic JvmTransformerContext(@Nonnull Workspace workspace, @Nonnull WorkspaceResource resource, @Nonnull JvmClassTransformer... transformers) {\n\t\tthis(workspace, resource, Arrays.asList(transformers));\n\t}\n\n\t/**\n\t * Constructs a new context from a collection of transformers.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace containing the classes to transform.\n\t * @param resource\n\t * \t\tResource in the workspace containing classes to transform. Should always be the {@link Workspace#getPrimaryResource()}.\n\t * @param transformers\n\t * \t\tTransformers to associate with this context.\n\t */\n\tpublic JvmTransformerContext(@Nonnull Workspace workspace, @Nonnull WorkspaceResource resource, @Nonnull Collection<? extends JvmClassTransformer> transformers) {\n\t\tthis.transformerMap = buildMap(transformers);\n\t\tthis.workspace = workspace;\n\t\tthis.resource = resource;\n\n\t\t// We will use aggregated mappings for the reverse-mapping utility it offers.\n\t\t// Some transformers that aim to provide mappings will find this very handy.\n\t\tmappings = new AggregatedMappings(workspace);\n\t}\n\n\t/**\n\t * Builds the map of initial transformed class paths to their final transformed states.\n\t * <br>\n\t * The map keys are existing workspace paths the respective classes.\n\t * <br>\n\t * The map values are classes post-transformation, without any mappings applied.\n\t *\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph tied to the workspace the transformed classes belong to.\n\t *\n\t * @throws TransformationException\n\t * \t\tWhen the classes cannot be written back to {@code byte[]} likely due to frame computation problems.\n\t */\n\t@Nonnull\n\tprotected Map<ClassPathNode, JvmClassInfo> buildChangeMap(@Nonnull InheritanceGraph inheritanceGraph) throws TransformationException {\n\t\tResourcePathNode resourcePath = PathNodes.resourcePath(workspace, resource);\n\t\tMap<ClassPathNode, JvmClassInfo> map = new IdentityHashMap<>();\n\t\tfor (JvmClassData data : classData.values()) {\n\t\t\tif (data.isDirty()) {\n\t\t\t\tif (data.node != null) {\n\t\t\t\t\t// Emit bytecode from the current node\n\t\t\t\t\tboolean recompute = recomputeFrameClasses.contains(data.node.name);\n\t\t\t\t\tint flags = recompute && !dropFaultyClasses ? ClassWriter.COMPUTE_FRAMES : 0;\n\t\t\t\t\tClassReader reader = data.initialClass.getClassReader(); // Copy const-pool + bootstrap methods\n\t\t\t\t\tClassWriter writer = new WorkspaceClassWriter(inheritanceGraph, reader, flags);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (recompute)\n\t\t\t\t\t\t\tdata.node.accept(new FrameSkippingVisitor(writer));\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tdata.node.accept(writer);\n\n\t\t\t\t\t\t// Update output map\n\t\t\t\t\t\tbyte[] modifiedBytes = writer.toByteArray();\n\t\t\t\t\t\tJvmClassInfo modifiedClass = data.initialClass.toJvmClassBuilder()\n\t\t\t\t\t\t\t\t.adaptFrom(modifiedBytes)\n\t\t\t\t\t\t\t\t.build();\n\t\t\t\t\t\tClassPathNode classPath = resourcePath.child(data.bundle)\n\t\t\t\t\t\t\t\t.child(modifiedClass.getPackageName())\n\t\t\t\t\t\t\t\t.child(modifiedClass);\n\t\t\t\t\t\tmap.put(classPath, modifiedClass);\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tif (dropFaultyClasses) {\n\t\t\t\t\t\t\tlogger.warn(\"Error writing class '{}', skipping\", data.initialClass.getName(), t);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow new TransformationException(\"ClassNode --> byte[] failed for class '\" + data.node.name + \"'\", t);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Update output map if the bytecode is not the same as the initial state\n\t\t\t\t\tbyte[] bytecode = data.getBytecode();\n\t\t\t\t\tif (!Arrays.equals(bytecode, data.initialClass.getBytecode())) {\n\t\t\t\t\t\tJvmClassInfo modifiedClass = data.initialClass.toJvmClassBuilder()\n\t\t\t\t\t\t\t\t.adaptFrom(bytecode)\n\t\t\t\t\t\t\t\t.build();\n\t\t\t\t\t\tClassPathNode classPath = resourcePath.child(data.bundle)\n\t\t\t\t\t\t\t\t.child(modifiedClass.getPackageName())\n\t\t\t\t\t\t\t\t.child(modifiedClass);\n\t\t\t\t\t\tmap.put(classPath, modifiedClass);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn map;\n\t}\n\n\t/**\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph of workspace.\n\t * @param cls\n\t * \t\tName of class defining a method to analyze.\n\t * @param method\n\t * \t\tMethod to analyze.\n\t *\n\t * @return Analyzed frames of the given method.\n\t *\n\t * @throws TransformationException\n\t * \t\tWhen the analyzer throws an exception when computing the frames of the given method.\n\t */\n\t@Nonnull\n\tpublic Frame<ReValue>[] analyze(@Nonnull InheritanceGraph inheritanceGraph,\n\t                                @Nonnull ClassNode cls,\n\t                                @Nonnull MethodNode method) throws TransformationException {\n\t\ttry {\n\t\t\tReAnalyzer analyzer = newAnalyzer(inheritanceGraph, cls, method);\n\t\t\treturn analyzer.analyze(cls.name, method);\n\t\t} catch (Throwable t) {\n\t\t\tthrow new TransformationException(\"Error encountered when computing method frames\", t);\n\t\t}\n\t}\n\n\t/**\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph of workspace.\n\t * @param cls\n\t * \t\tName of class defining a method to analyze.\n\t * @param method\n\t * \t\tMethod to analyze.\n\t *\n\t * @return An analyzer for the given method.\n\t */\n\t@Nonnull\n\tpublic ReAnalyzer newAnalyzer(@Nonnull InheritanceGraph inheritanceGraph,\n\t                              @Nonnull ClassNode cls,\n\t                              @Nonnull MethodNode method) {\n\t\tReInterpreter interpreter = newInterpreter(inheritanceGraph);\n\t\treturn new ReAnalyzer(interpreter);\n\t}\n\n\t/**\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph of workspace.\n\t *\n\t * @return An interpreter for handling instruction execution.\n\t */\n\t@Nonnull\n\tpublic ReInterpreter newInterpreter(@Nonnull InheritanceGraph inheritanceGraph) {\n\t\tReInterpreter interpreter = new ReInterpreter(inheritanceGraph);\n\t\tinterpreter.setGetFieldLookup(getFieldLookupSupplier.get());\n\t\tinterpreter.setGetStaticLookup(getStaticLookupSupplier.get());\n\t\tinterpreter.setInvokeVirtualLookup(invokeVirtualLookupSupplier.get());\n\t\tinterpreter.setInvokeStaticLookup(invokeStaticLookupSupplier.get());\n\t\treturn interpreter;\n\t}\n\n\t/**\n\t * Utility to invoke {@link DeadCodeRemovingTransformer} for a given method.\n\t * Requires the transformer to be provided to this context.\n\t *\n\t * @param declaringClass\n\t * \t\tClass declaring the method to clean up.\n\t * @param method\n\t * \t\tMethod with dead code to remove.\n\t *\n\t * @return {@code true} when there were changes as a result of dead code removal.\n\t * {@code false} for no changes being made to the passed method.\n\t *\n\t * @throws TransformationException\n\t * \t\tWhen the {@link DeadCodeRemovingTransformer} was not provided to this context,\n\t * \t\tor if dead code removal encountered an error.\n\t */\n\tpublic boolean pruneDeadCode(@Nonnull ClassNode declaringClass, @Nonnull MethodNode method) throws TransformationException {\n\t\treturn getJvmTransformer(DeadCodeRemovingTransformer.class).prune(declaringClass, method);\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tBundle containing the class.\n\t * @param info\n\t * \t\tThe class's model in the workspace.\n\t *\n\t * @return {@code true} when the context currently has the class represented as a node <i>(vs raw {@code byte[]})</i.>\n\t */\n\tpublic boolean isNode(@Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo info) {\n\t\tJvmClassData data = getJvmClassData(bundle, info);\n\t\treturn data.node != null;\n\t}\n\n\t/**\n\t * Gets the current ASM node representation of the given class.\n\t * <p>\n\t * Transformers can update the <i>\"current\"</i> state of the node via\n\t * {@link #setBytecode(JvmClassBundle, JvmClassInfo, byte[])} or\n\t * {@link #setNode(JvmClassBundle, JvmClassInfo, ClassNode)}.\n\t *\n\t * @param bundle\n\t * \t\tBundle containing the class.\n\t * @param info\n\t * \t\tThe class's model in the workspace.\n\t *\n\t * @return The current tracked/transformed {@link ClassNode} for the associated class.\n\t *\n\t * @see #setNode(JvmClassBundle, JvmClassInfo, ClassNode)\n\t */\n\t@Nonnull\n\tpublic ClassNode getNode(@Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo info) {\n\t\treturn getJvmClassData(bundle, info).getOrCreateNode();\n\t}\n\n\t/**\n\t * Gets the current bytecode of the given class.\n\t * <p>\n\t * Transformers can update the <i>\"current\"</i> state of the bytecode via\n\t * {@link #setBytecode(JvmClassBundle, JvmClassInfo, byte[])} or\n\t * {@link #setNode(JvmClassBundle, JvmClassInfo, ClassNode)}.\n\t *\n\t * @param bundle\n\t * \t\tBundle containing the class.\n\t * @param info\n\t * \t\tThe class's model in the workspace.\n\t *\n\t * @return The current tracked/transformed bytecode for the associated class.\n\t *\n\t * @see #setBytecode(JvmClassBundle, JvmClassInfo, byte[])\n\t */\n\t@Nonnull\n\tpublic byte[] getBytecode(@Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo info) {\n\t\treturn getJvmClassData(bundle, info).getBytecode();\n\t}\n\n\t/**\n\t * Updates the transformed state of a class by recording an ASM node representation of the class.\n\t *\n\t * @param bundle\n\t * \t\tBundle containing the class.\n\t * @param info\n\t * \t\tThe class's model in the workspace.\n\t * @param node\n\t * \t\tASM node representation of the class to store.\n\t *\n\t * @see #getNode(JvmClassBundle, JvmClassInfo)\n\t */\n\tpublic void setNode(@Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo info, @Nonnull ClassNode node) {\n\t\ttransformerDidWork.set(true);\n\t\tgetJvmClassData(bundle, info).setNode(node);\n\t}\n\n\t/**\n\t * Updates the transformed state of a class by recording new bytecode of the class.\n\t *\n\t * @param bundle\n\t * \t\tBundle containing the class.\n\t * @param info\n\t * \t\tThe class's model in the workspace.\n\t * \t\tThis does not need to reflect the updated state of the bytecode, it is strictly used for keying/lookups.\n\t * @param bytecode\n\t * \t\tBytecode of the class to store.\n\t *\n\t * @see #getBytecode(JvmClassBundle, JvmClassInfo)\n\t */\n\tpublic void setBytecode(@Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo info, @Nonnull byte[] bytecode) {\n\t\ttransformerDidWork.set(true);\n\t\tgetJvmClassData(bundle, info).setBytecode(bytecode);\n\t}\n\n\t/**\n\t * Marks a class for removal in the workspace.\n\t *\n\t * @param info\n\t * \t\tThe class model in the workspace.\n\t *\n\t * @see #getClassesToRemove()\n\t */\n\tpublic void markClassForRemoval(@Nonnull JvmClassInfo info) {\n\t\tmarkClassForRemoval(info.getName());\n\t}\n\n\t/**\n\t * Marks a class for removal in the workspace.\n\t *\n\t * @param name\n\t * \t\tInternal class name.\n\t *\n\t * @see #getClassesToRemove()\n\t */\n\tpublic void markClassForRemoval(@Nonnull String name) {\n\t\tclassesToRemove.add(name);\n\t}\n\n\t/**\n\t * @return Names of classes marked for removal.\n\t *\n\t * @see #markClassForRemoval(JvmClassInfo)\n\t */\n\t@Nonnull\n\tpublic Set<String> getClassesToRemove() {\n\t\treturn Collections.unmodifiableSet(classesToRemove);\n\t}\n\n\t/**\n\t * Clears any transformations applied to the given class.\n\t *\n\t * @param bundle\n\t * \t\tBundle containing the class.\n\t * @param info\n\t * \t\tThe class's model in the workspace.\n\t */\n\tpublic void clear(@Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo info) {\n\t\tJvmClassData data = getJvmClassData(bundle, info);\n\t\tdata.setBytecode(data.initialClass.getBytecode());\n\t\tdata.dirty = false;\n\t}\n\n\t/**\n\t * Called by transformers that have more thorough changes applied to classes that likely violate existing frames.\n\t *\n\t * @param className\n\t * \t\tName of class to recompute frames for when building the change map.\n\t */\n\tpublic void setRecomputeFrames(@Nonnull String className) {\n\t\trecomputeFrameClasses.add(className);\n\t}\n\n\t/**\n\t * Transformers that aim to rename classes, fields, and methods should register the desired mappings\n\t * here, and they will be applied after all other transformations are applied.\n\t *\n\t * @return Mappings to apply upon transformation completion.\n\t */\n\t@Nonnull\n\tpublic AggregatedMappings getMappings() {\n\t\treturn mappings;\n\t}\n\n\t/**\n\t * @return Workspace containing the classes to transform.\n\t */\n\t@Nonnull\n\tpublic Workspace getWorkspace() {\n\t\treturn workspace;\n\t}\n\n\t/**\n\t * Get the {@link JvmClassTransformer} instance associated with this context, or throw an exception if no such\n\t * transformer is registered. If you are looking for an optional lookup use: {@link #getOptionalJvmTransformer(Class)}.\n\t *\n\t * @param key\n\t * \t\tTransformer class.\n\t * @param <T>\n\t * \t\tTransformer type.\n\t *\n\t * @return Shared instance of the transformer within this context.\n\t *\n\t * @throws TransformationException\n\t * \t\tWhen the transformer was not found within this context.\n\t */\n\t@Nonnull\n\tpublic <T extends JvmClassTransformer> T getJvmTransformer(Class<T> key) throws TransformationException {\n\t\tT transformer = getOptionalJvmTransformer(key);\n\t\tif (transformer == null)\n\t\t\tthrow new TransformationException(\"Transformation context attempted lookup of class '\"\n\t\t\t\t\t+ key.getSimpleName() + \"' but did not have an associated entry\");\n\t\treturn transformer;\n\t}\n\n\t/**\n\t * Get the {@link JvmClassTransformer} instance associated with this context, if it is registered.\n\t *\n\t * @param key\n\t * \t\tTransformer class.\n\t * @param <T>\n\t * \t\tTransformer type.\n\t *\n\t * @return Shared instance of the transformer within this context,\n\t * or {@code null} if no such transformer is registered to this context.\n\t */\n\t@Nullable\n\t@SuppressWarnings(\"unchecked\")\n\tpublic <T extends JvmClassTransformer> T getOptionalJvmTransformer(Class<T> key) {\n\t\t// NOTE: Any Recaf-defined transformer must be @Dependent so that CDI doesn't give you proxy wrappers\n\t\t// of the class. Our map is identity based, and if you do 'get(MyClass.class)' and we end up storing the\n\t\t// proxy wrapper, then the lookup will fail even though the transformer is seemingly registered.\n\t\tJvmClassTransformer transformer = transformerMap.get(key);\n\t\tif (transformer == null)\n\t\t\treturn null;\n\t\treturn (T) transformer;\n\t}\n\n\t/**\n\t * @param supplier\n\t * \t\tSupplier for {@link GetFieldLookup} to be used with {@link #newAnalyzer(InheritanceGraph, ClassNode, MethodNode)}.\n\t */\n\tpublic void setGetFieldLookupSupplier(@Nullable Supplier<GetFieldLookup> supplier) {\n\t\tif (supplier == null)\n\t\t\tsupplier = () -> null;\n\t\tgetFieldLookupSupplier = supplier;\n\t}\n\n\t/**\n\t * @param supplier\n\t * \t\tSupplier for {@link GetStaticLookup} to be used with {@link #newAnalyzer(InheritanceGraph, ClassNode, MethodNode)}.\n\t */\n\tpublic void setGetStaticLookupSupplier(@Nullable Supplier<GetStaticLookup> supplier) {\n\t\tif (supplier == null)\n\t\t\tsupplier = () -> null;\n\t\tgetStaticLookupSupplier = supplier;\n\t}\n\n\t/**\n\t * @param supplier\n\t * \t\tSupplier for {@link InvokeVirtualLookup} to be used with {@link #newAnalyzer(InheritanceGraph, ClassNode, MethodNode)}.\n\t */\n\tpublic void setInvokeVirtualLookupSupplier(@Nullable Supplier<InvokeVirtualLookup> supplier) {\n\t\tif (supplier == null)\n\t\t\tsupplier = () -> null;\n\t\tinvokeVirtualLookupSupplier = supplier;\n\t}\n\n\t/**\n\t * @param supplier\n\t * \t\tSupplier for {@link InvokeStaticLookup} to be used with {@link #newAnalyzer(InheritanceGraph, ClassNode, MethodNode)}.\n\t */\n\tpublic void setInvokeStaticLookupSupplier(@Nullable Supplier<InvokeStaticLookup> supplier) {\n\t\tif (supplier == null)\n\t\t\tsupplier = () -> null;\n\t\tinvokeStaticLookupSupplier = supplier;\n\t}\n\n\t/**\n\t * Called before any transformer operates with this context.\n\t * <br>\n\t * Clears any state associated with the operation of transformers.\n\t *\n\t * @see #didTransformerDoWork()\n\t */\n\tprotected void resetTransformerTracking() {\n\t\t// Any transformation application should call this before the transformer methods operate on data.\n\t\ttransformerDidWork.set(false);\n\t}\n\n\t/**\n\t * Used to check if a {@link ClassTransformer} did work after its {@code transform} has been executed with\n\t * this context being used as a parameter.\n\t *\n\t * @return {@code true} if the last transformer ran did work with this context.\n\t */\n\tprotected boolean didTransformerDoWork() {\n\t\treturn transformerDidWork.get();\n\t}\n\n\t@Nonnull\n\tprivate JvmClassData getJvmClassData(@Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo info) {\n\t\treturn classData.computeIfAbsent(info.getName(), ignored -> new JvmClassData(bundle, info));\n\t}\n\n\t@Nonnull\n\tprivate static Map<Class<? extends JvmClassTransformer>, JvmClassTransformer> buildMap(@Nonnull Collection<? extends JvmClassTransformer> transformers) {\n\t\tMap<Class<? extends JvmClassTransformer>, JvmClassTransformer> map = new IdentityHashMap<>();\n\t\tfor (JvmClassTransformer transformer : transformers)\n\t\t\tmap.put(transformer.getClass(), transformer);\n\t\treturn Collections.unmodifiableMap(map);\n\t}\n\n\t/**\n\t * Container of per-class transformation state.\n\t */\n\tprivate class JvmClassData {\n\t\tprivate final JvmClassBundle bundle;\n\t\tprivate final JvmClassInfo initialClass;\n\t\tprivate volatile byte[] bytecode;\n\t\tprivate volatile ClassNode node;\n\t\tprivate boolean dirty;\n\n\t\t/**\n\t\t * @param bundle\n\t\t * \t\tBundle containing the class.\n\t\t * @param initialClass\n\t\t * \t\tInitial state of the class before transformation.\n\t\t */\n\t\tpublic JvmClassData(@Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo initialClass) {\n\t\t\tthis.initialClass = initialClass;\n\t\t\tthis.bundle = bundle;\n\t\t\tbytecode = initialClass.getBytecode();\n\t\t}\n\n\t\t/**\n\t\t * @return Node representation of the {@link #getBytecode() current bytecode}.\n\t\t */\n\t\t@Nonnull\n\t\tpublic ClassNode getOrCreateNode() {\n\t\t\tif (node == null) {\n\t\t\t\tsynchronized (this) {\n\t\t\t\t\tif (node == null) {\n\t\t\t\t\t\tnode = new ClassNode();\n\t\t\t\t\t\tint readerFlags = getOptionalJvmTransformer(FrameRemovingTransformer.class) == null ?\n\t\t\t\t\t\t\t\t0 : ClassReader.SKIP_FRAMES; // Can bypass reading frames if this transformer is active.\n\t\t\t\t\t\tnew ClassReader(bytecode).accept(node, readerFlags);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// We always give back a copy so actions taken on this node are not affecting the cached instance\n\t\t\t// unless a transformer explicitly commits the change.\n\t\t\tClassNode nodeCopy = new ClassNode();\n\t\t\tnode.accept(nodeCopy);\n\t\t\treturn nodeCopy;\n\t\t}\n\n\t\t/**\n\t\t * @return Current bytecode of the class.\n\t\t */\n\t\t@Nonnull\n\t\tpublic byte[] getBytecode() {\n\t\t\tif (bytecode == null) {\n\t\t\t\tsynchronized (this) {\n\t\t\t\t\tif (bytecode == null) {\n\t\t\t\t\t\tClassWriter writer = new ClassWriter(0);\n\t\t\t\t\t\tnode.accept(writer);\n\t\t\t\t\t\tbytecode = writer.toByteArray();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn bytecode;\n\t\t}\n\n\t\t/**\n\t\t * @param node\n\t\t * \t\tCurrent node representation to set for this class.\n\t\t */\n\t\tpublic void setNode(@Nonnull ClassNode node) {\n\t\t\tsynchronized (this) {\n\t\t\t\tthis.node = node;\n\t\t\t\tbytecode = null; // Invalidate bytecode state\n\t\t\t\tdirty = true;\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * @param bytecode\n\t\t * \t\tCurrent bytecode to set for this class.\n\t\t */\n\t\tpublic void setBytecode(@Nonnull byte[] bytecode) {\n\t\t\tsynchronized (this) {\n\t\t\t\tthis.bytecode = bytecode;\n\t\t\t\tnode = null; // Invalidate node state\n\t\t\t\tdirty = true;\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * @return {@code true} when changes have been applied to this class.\n\t\t */\n\t\tpublic boolean isDirty() {\n\t\t\treturn dirty;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/transform/TransformResult.java",
    "content": "package software.coley.recaf.services.transform;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\n\nimport java.util.Collection;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * Intermediate holder of transformations of workspace classes.\n *\n * @author Matt Coley\n */\npublic interface TransformResult<CT extends ClassTransformer, CI extends ClassInfo> {\n\t/**\n\t * Puts the transformed classes into the associated workspace.\n\t * You can inspect the state of transformed classes before this via {@link #getTransformedClasses()}.\n\t */\n\tvoid apply();\n\n\t/**\n\t * @return Mappings to apply.\n\t */\n\t@Nonnull\n\tIntermediateMappings getMappingsToApply();\n\n\t/**\n\t * Shows which classes have been modified by which transformers.\n\t * <p>\n\t * The paths to the classes in the map are to the <i>original</i>\n\t * state of the class and do not have modified {@link ClassInfo} contents.\n\t *\n\t * @return Map of transformers to the paths of classes they have modified.\n\t */\n\t@Nonnull\n\tMap<Class<? extends CT>, Collection<ClassPathNode>> getModifiedClassesPerTransformer();\n\n\t/**\n\t * @return Map of classes, to their maps of transformer-associated exceptions.\n\t * Empty if transformation was a complete success <i>(no failures)</i>.\n\t */\n\t@Nonnull\n\tMap<ClassPathNode, Map<Class<? extends CT>, Throwable>> getTransformerFailures();\n\n\t/**\n\t * This map associates workspace paths to classes to the resulting transformed class models.\n\t * The transformed models do not have {@link #getMappingsToApply() mappings} applied to them,\n\t * as that process occurs during the {@link #apply()} operation.\n\t *\n\t * @return Map of class paths to the original classes, to the resulting transformed class models.\n\t */\n\t@Nonnull\n\tMap<ClassPathNode, CI> getTransformedClasses();\n\n\t/**\n\t * @return Set of paths to classes that will be removed.\n\t */\n\t@Nonnull\n\tSet<ClassPathNode> getClassesToRemove();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/transform/TransformationApplier.java",
    "content": "package software.coley.recaf.services.transform;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.collections.Sets;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.path.BundlePathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.path.ResourcePathNode;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.MappingApplier;\nimport software.coley.recaf.services.mapping.MappingResults;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.stream.Collectors;\n\nimport static software.coley.collections.Unchecked.cast;\nimport static software.coley.collections.Unchecked.checkedForEach;\n\n/**\n * Applies transformations to workspaces.\n *\n * @author Matt Coley\n * @see TransformationManager\n */\npublic class TransformationApplier {\n\tprivate static final DebuggingLogger logger = Logging.get(TransformationApplier.class);\n\tprivate final TransformationManager transformationManager;\n\tprivate final TransformationApplierConfig transformApplyConfig;\n\tprivate final InheritanceGraph inheritanceGraph;\n\tprivate final MappingApplier mappingApplier;\n\tprivate final Workspace workspace;\n\tprivate int maxPasses = 1;\n\n\t/**\n\t * @param transformationManager\n\t * \t\tManager to pull transformer instances from.\n\t * @param transformApplyConfig\n\t * \t\tTransformation applier config.\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph to use for frame computation <i>(Some transformers will trigger this)</i>.\n\t * @param mappingApplier\n\t * \t\tMapping applier to update workspace with mappings registered by transformers.\n\t * @param workspace\n\t * \t\tWorkspace with classes to transform.\n\t */\n\tpublic TransformationApplier(@Nonnull TransformationManager transformationManager,\n\t                             @Nonnull TransformationApplierConfig transformApplyConfig,\n\t                             @Nonnull InheritanceGraph inheritanceGraph,\n\t                             @Nonnull MappingApplier mappingApplier,\n\t                             @Nonnull Workspace workspace) {\n\t\tthis.transformationManager = transformationManager;\n\t\tthis.transformApplyConfig = transformApplyConfig;\n\t\tthis.inheritanceGraph = inheritanceGraph;\n\t\tthis.mappingApplier = mappingApplier;\n\t\tthis.workspace = workspace;\n\t}\n\n\t/**\n\t * @return Maximum number of times to repeat transformations.\n\t */\n\tpublic int getMaxPasses() {\n\t\treturn Math.max(1, maxPasses);\n\t}\n\n\t/**\n\t * @param maxPasses\n\t * \t\tMaximum number of times to repeat transformations\n\t */\n\tpublic void setMaxPasses(int maxPasses) {\n\t\tthis.maxPasses = maxPasses;\n\t}\n\n\t/**\n\t * @param transformerClasses\n\t * \t\tJVM class transformers to run.\n\t *\n\t * @return Result container with details about the transformation, including any failures, the transformed classes,\n\t * and the option to apply the transformations to the workspace.\n\t *\n\t * @throws TransformationException\n\t * \t\tWhen transformation cannot be run for any reason.\n\t */\n\t@Nonnull\n\tpublic JvmTransformResult transformJvm(@Nonnull List<Class<? extends JvmClassTransformer>> transformerClasses) throws TransformationException {\n\t\treturn transformJvm(transformerClasses, TransformationFeedback.DEFAULT);\n\t}\n\n\t/**\n\t * @param transformerClasses\n\t * \t\tJVM class transformers to run.\n\t * @param feedback\n\t * \t\tFeedback to report transformation progress to, and control which JVM classes are transformed.\n\t *\n\t * @return Result container with details about the transformation, including any failures, the transformed classes,\n\t * and the option to apply the transformations to the workspace.\n\t *\n\t * @throws TransformationException\n\t * \t\tWhen transformation cannot be run for any reason.\n\t */\n\t@Nonnull\n\tpublic JvmTransformResult transformJvm(@Nonnull List<Class<? extends JvmClassTransformer>> transformerClasses,\n\t                                       @Nonnull TransformationFeedback feedback) throws TransformationException {\n\t\t// Build transformer visitation order.\n\t\tTransformerQueue queue = buildQueue(cast(transformerClasses));\n\n\t\t// Map to hold transformation errors for each class:transformer.\n\t\tMap<ClassPathNode, Map<Class<? extends JvmClassTransformer>, Throwable>> transformJvmFailures = Collections.synchronizedMap(new IdentityHashMap<>());\n\n\t\t// Map to hold transformers to the paths of classes they have modified.\n\t\tMap<Class<? extends JvmClassTransformer>, Collection<ClassPathNode>> transformerToModifiedClasses = Collections.synchronizedMap(new IdentityHashMap<>());\n\n\t\t// Build the transformer context and apply all transformations in order.\n\t\tList<JvmClassTransformer> transformers = queue.getTransformers();\n\t\tWorkspaceResource resource = workspace.getPrimaryResource();\n\t\tResourcePathNode resourcePath = PathNodes.resourcePath(workspace, resource);\n\t\tJvmTransformerContext context = new JvmTransformerContext(workspace, resource, transformers);\n\t\tfor (JvmClassTransformer transformer : transformers) {\n\t\t\ttry {\n\t\t\t\ttransformer.setup(context, workspace);\n\t\t\t} catch (Throwable t) {\n\t\t\t\t// If setup fails, abort the transformation\n\t\t\t\tString message = \"Transformer '\" + transformer.name() + \"' failed on setup\";\n\t\t\t\tlogger.error(message, t);\n\t\t\t\tthrow new TransformationException(message, t);\n\t\t\t}\n\t\t}\n\t\tAtomicInteger finalPass = new AtomicInteger();\n\t\tList<JvmClassTransformer> prunedTransformers = new ArrayList<>();\n\t\ttry (ExecutorService service = transformApplyConfig.doParallelize().getValue() ?\n\t\t\t\tThreadPoolFactory.newFixedThreadPool(\"transform-apply\") :\n\t\t\t\tThreadPoolFactory.newSingleThreadExecutor(\"transform-apply\")) {\n\t\t\tresource.jvmAllClassBundleStreamRecursive().forEach(bundle -> {\n\t\t\t\tList<Callable<Void>> tasks = new ArrayList<>(bundle.size());\n\t\t\t\tBundlePathNode bundlePathNode = resourcePath.child(bundle);\n\t\t\t\tfor (int pass = 1; pass <= getMaxPasses(); pass++) {\n\t\t\t\t\tfinalPass.set(pass);\n\t\t\t\t\tAtomicBoolean anyWorkDone = new AtomicBoolean(false);\n\t\t\t\t\tfor (JvmClassTransformer transformer : transformers) {\n\t\t\t\t\t\tAtomicBoolean transformerWorkDone = new AtomicBoolean(false);\n\t\t\t\t\t\tfinal int currentPass = pass;\n\n\t\t\t\t\t\t// Transformers can be run in parallel per each pass across all classes in the bundle.\n\t\t\t\t\t\ttasks.clear();\n\t\t\t\t\t\tfor (JvmClassInfo cls : bundle)\n\t\t\t\t\t\t\ttasks.add(() -> {\n\t\t\t\t\t\t\t\t// Skip if transformation has been cancelled\n\t\t\t\t\t\t\t\tif (feedback.hasRequestedCancellation())\n\t\t\t\t\t\t\t\t\treturn null;\n\n\t\t\t\t\t\t\t\t// Skip if the class does not pass the predicate\n\t\t\t\t\t\t\t\tif (!feedback.shouldTransform(workspace, resource, bundle, cls, transformer, currentPass))\n\t\t\t\t\t\t\t\t\treturn null;\n\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tcontext.resetTransformerTracking();\n\t\t\t\t\t\t\t\t\ttransformer.transform(context, workspace, resource, bundle, cls);\n\t\t\t\t\t\t\t\t\tboolean didWork = context.didTransformerDoWork();\n\t\t\t\t\t\t\t\t\tif (didWork) {\n\t\t\t\t\t\t\t\t\t\t// Transformer modified this class, record the interaction\n\t\t\t\t\t\t\t\t\t\tanyWorkDone.set(true);\n\t\t\t\t\t\t\t\t\t\ttransformerWorkDone.set(true);\n\t\t\t\t\t\t\t\t\t\tCollection<ClassPathNode> paths = transformerToModifiedClasses.computeIfAbsent(transformer.getClass(),\n\t\t\t\t\t\t\t\t\t\t\t\tt -> Collections.synchronizedSet(Collections.newSetFromMap(new IdentityHashMap<>())));\n\n\t\t\t\t\t\t\t\t\t\t// Only keep one path (since we may have repeated passes)\n\t\t\t\t\t\t\t\t\t\tsynchronized (paths) {\n\t\t\t\t\t\t\t\t\t\t\tif (paths.stream().noneMatch(p -> p.getValue().getName().equals(cls.getName()))) {\n\t\t\t\t\t\t\t\t\t\t\t\tClassPathNode path = bundlePathNode.child(cls.getPackageName()).child(cls);\n\t\t\t\t\t\t\t\t\t\t\t\tpaths.add(path);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tfeedback.onTransformed(workspace, resource, bundle, cls, transformer, currentPass);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tfeedback.onTransformedWithoutWork(workspace, resource, bundle, cls, transformer, currentPass);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tlogger.debugging(l -> l.debug(\"Pass {}: Transformer {} didWork={}\",\n\t\t\t\t\t\t\t\t\t\t\tcurrentPass, transformer.getClass().getSimpleName(), didWork));\n\n\t\t\t\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\t\t\t\tlogger.error(\"Transformer '{}' failed on class '{}'\", transformer.name(), cls.getName(), t);\n\t\t\t\t\t\t\t\t\tfeedback.onTransformFailure(workspace, resource, bundle, cls,  transformer,currentPass, t);\n\t\t\t\t\t\t\t\t\tClassPathNode path = bundlePathNode.child(cls.getPackageName()).child(cls);\n\t\t\t\t\t\t\t\t\tvar transformerToThrowable = transformJvmFailures.computeIfAbsent(path, p -> Collections.synchronizedMap(new IdentityHashMap<>()));\n\t\t\t\t\t\t\t\t\ttransformerToThrowable.put(transformer.getClass(), t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Invoke and wait for all classes in this bundle to be visited/transformed.\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tservice.invokeAll(tasks);\n\t\t\t\t\t\t} catch (InterruptedException ex) {\n\t\t\t\t\t\t\tthrow new RuntimeException(\"Interrupt\", ex);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If a transformer is prunable (they no longer execute after a full pass without any work completed)\n\t\t\t\t\t\t// schedule it for removal so that it will not be executed in following passes.\n\t\t\t\t\t\tif (!transformerWorkDone.get() && transformer.pruneAfterNoWork()) {\n\t\t\t\t\t\t\tlogger.debug(\"Pruning transformer '{}' after pass {} completed with no work done\", transformer.name(), pass);\n\t\t\t\t\t\t\tprunedTransformers.add(transformer);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remove pruned transformers.\n\t\t\t\t\ttransformers.removeAll(prunedTransformers);\n\n\t\t\t\t\t// Break if this transformer has done no work has been done this pass.\n\t\t\t\t\tif (!anyWorkDone.get())\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t});\n\t\t\tfeedback.onCompletion();\n\t\t} catch (RuntimeException ex) {\n\t\t\t// Handle the interrupt runtime exception seen a few lines up.\n\t\t\tthrow new TransformationException(\"Unexpected runtime exception\", ex);\n\t\t}\n\n\t\t// Update the workspace contents with the transformation results\n\t\tMap<ClassPathNode, JvmClassInfo> transformedJvmClasses = context.buildChangeMap(inheritanceGraph);\n\t\tlogger.debug(\"Computed transformations with {} transformers, affecting {} classes after {} passes\",\n\t\t\t\ttransformerClasses.size(), transformedJvmClasses.size(), finalPass.get());\n\t\treturn new JvmTransformResult() {\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic Map<ClassPathNode, Map<Class<? extends JvmClassTransformer>, Throwable>> getTransformerFailures() {\n\t\t\t\treturn transformJvmFailures;\n\t\t\t}\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic Map<ClassPathNode, JvmClassInfo> getTransformedClasses() {\n\t\t\t\treturn transformedJvmClasses;\n\t\t\t}\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic Set<ClassPathNode> getClassesToRemove() {\n\t\t\t\treturn context.getClassesToRemove().stream()\n\t\t\t\t\t\t.map(workspace::findJvmClass)\n\t\t\t\t\t\t.filter(Objects::nonNull)\n\t\t\t\t\t\t.collect(Collectors.toSet());\n\t\t\t}\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic IntermediateMappings getMappingsToApply() {\n\t\t\t\treturn context.getMappings();\n\t\t\t}\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic Map<Class<? extends JvmClassTransformer>, Collection<ClassPathNode>> getModifiedClassesPerTransformer() {\n\t\t\t\treturn transformerToModifiedClasses;\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void apply() {\n\t\t\t\t// Dump transformed classes into the workspace\n\t\t\t\tcheckedForEach(transformedJvmClasses, (path, cls) -> {\n\t\t\t\t\tJvmClassBundle bundle = path.getValueOfType(JvmClassBundle.class);\n\t\t\t\t\tif (bundle != null)\n\t\t\t\t\t\tbundle.put(cls);\n\t\t\t\t}, (path, cls, t) -> logger.error(\"Exception thrown handling transform application\", t));\n\n\t\t\t\t// Delete classes that are marked for removal\n\t\t\t\tfor (ClassPathNode path : getClassesToRemove()) {\n\t\t\t\t\tJvmClassBundle bundle = path.getValueOfType(JvmClassBundle.class);\n\t\t\t\t\tif (bundle != null)\n\t\t\t\t\t\tbundle.remove(path.getValue().getName());\n\t\t\t\t}\n\n\t\t\t\t// Apply mappings if they exist\n\t\t\t\tIntermediateMappings mappings = context.getMappings();\n\t\t\t\tif (!mappings.isEmpty()) {\n\t\t\t\t\tMappingResults results = mappingApplier.applyToPrimaryResource(mappings);\n\t\t\t\t\tresults.apply();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\t@Nonnull\n\tprivate TransformerQueue buildQueue(@Nonnull List<Class<? extends ClassTransformer>> transformerClasses) throws TransformationException {\n\t\tTransformerQueue queue = new TransformerQueue();\n\t\tfor (Class<? extends ClassTransformer> transformerClass : transformerClasses)\n\t\t\tinsert(queue, transformerClass, Collections.emptySet());\n\t\treturn queue;\n\t}\n\n\tprivate void insert(@Nonnull TransformerQueue queue, @Nonnull Class<? extends ClassTransformer> transformerClass,\n\t                    @Nonnull Set<Class<? extends ClassTransformer>> dependants) throws TransformationException {\n\t\t// Abort if a cycle is detected\n\t\tif (dependants.contains(transformerClass))\n\t\t\tthrow new TransformationException(\"Transformer dependency cycle detected with '\" + transformerClass.getSimpleName() + \"'\");\n\n\t\t// Create the transformer and its dependencies\n\t\t//  - Dependencies first\n\t\t//  - Then the transformer\n\t\tClassTransformer transformer;\n\t\tif (JvmClassTransformer.class.isAssignableFrom(transformerClass)) {\n\t\t\tClass<? extends JvmClassTransformer> jvmTransformerClass = cast(transformerClass);\n\t\t\ttransformer = transformationManager.newJvmTransformer(jvmTransformerClass);\n\t\t} else {\n\t\t\tthrow new TransformationException(\"Unsupported transformer class type: \" + transformerClass);\n\t\t}\n\t\tfor (Class<? extends ClassTransformer> dependency : transformer.dependencies())\n\t\t\tif (!queue.containsType(dependency))\n\t\t\t\tinsert(queue, dependency, Sets.add(dependants, transformerClass));\n\t\tqueue.add(transformer);\n\t}\n\n\t/**\n\t * Wrapper holding which transformers to run.\n\t */\n\tprivate static class TransformerQueue {\n\t\tprivate final List<ClassTransformer> transformers = new ArrayList<>();\n\t\tprivate final List<Class<? extends ClassTransformer>> transformerTypes = new ArrayList<>();\n\n\t\t/**\n\t\t * @param transformer\n\t\t * \t\tTransformer to add to the queue.\n\t\t */\n\t\tprivate void add(@Nonnull ClassTransformer transformer) {\n\t\t\ttransformers.add(transformer);\n\t\t\ttransformerTypes.add(transformer.getClass());\n\t\t}\n\n\t\t/**\n\t\t * @param transformerClass\n\t\t * \t\tTransformer type to check for,\n\t\t *\n\t\t * @return {@code true} when the queue already has a transformer of that type registered.\n\t\t */\n\t\tprivate boolean containsType(@Nonnull Class<? extends ClassTransformer> transformerClass) {\n\t\t\treturn transformerTypes.contains(transformerClass);\n\t\t}\n\n\t\t/**\n\t\t * @param <T>\n\t\t * \t\tInferred transformer type.\n\t\t *\n\t\t * @return List of registered transformers.\n\t\t */\n\t\t@Nonnull\n\t\tprivate <T extends ClassTransformer> List<T> getTransformers() {\n\t\t\treturn cast(transformers);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/transform/TransformationApplierConfig.java",
    "content": "package software.coley.recaf.services.transform;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link TransformationApplierService}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class TransformationApplierConfig extends BasicConfigContainer implements ServiceConfig {\n\tprivate final ObservableBoolean parallelize = new ObservableBoolean(true);\n\n\t@Inject\n\tpublic TransformationApplierConfig() {\n\t\tsuper(ConfigGroups.SERVICE_TRANSFORM, TransformationApplierService.SERVICE_ID + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"parallelize\", boolean.class, parallelize));\n\t}\n\n\t/**\n\t * @return {@code true} to enable parallelization of transformer applications.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean doParallelize() {\n\t\treturn parallelize;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/transform/TransformationApplierService.java",
    "content": "package software.coley.recaf.services.transform;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.mapping.MappingApplier;\nimport software.coley.recaf.services.mapping.MappingApplierService;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Objects;\n\n/**\n * Service offering the creation of {@link TransformationApplier transformation appliers} for workspaces.\n *\n * @author Matt Coley\n * @see TransformationManager\n * @see TransformationApplier\n */\n@ApplicationScoped\npublic class TransformationApplierService implements Service {\n\tpublic static final String SERVICE_ID = \"transformation-applier\";\n\tprivate static final Logger logger = Logging.get(TransformationApplierService.class);\n\tprivate final TransformationManager transformationManager;\n\tprivate final InheritanceGraphService graphService;\n\tprivate final MappingApplierService mappingService;\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final TransformationApplierConfig config;\n\n\t@Inject\n\tpublic TransformationApplierService(@Nonnull TransformationManager transformationManager,\n\t                                    @Nonnull InheritanceGraphService graphService,\n\t                                    @Nonnull MappingApplierService mappingService,\n\t                                    @Nonnull WorkspaceManager workspaceManager,\n\t                                    @Nonnull TransformationApplierConfig config) {\n\t\tthis.graphService = graphService;\n\t\tthis.mappingService = mappingService;\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.transformationManager = transformationManager;\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to apply transformations within.\n\t *\n\t * @return Transformation applier for the given workspace.\n\t */\n\t@Nonnull\n\tpublic TransformationApplier newApplier(@Nonnull Workspace workspace) {\n\t\t// Optimal case for current workspace using the shared workspace inheritance graph\n\t\tif (workspace == workspaceManager.getCurrent()) {\n\t\t\tInheritanceGraph graphNotCreated = Objects.requireNonNull(graphService.getCurrentWorkspaceInheritanceGraph(), \"Graph not created\");\n\t\t\tMappingApplier mappingApplier = Objects.requireNonNull(mappingService.inCurrentWorkspace(), \"Mapping applier not created\");\n\t\t\treturn newApplier(workspace, graphNotCreated, mappingApplier);\n\t\t}\n\n\t\t// Need to make a new graph for the given workspace\n\t\tInheritanceGraph inheritanceGraph = graphService.newInheritanceGraph(workspace);\n\t\tMappingApplier mappingApplier = mappingService.inWorkspace(workspace);\n\t\treturn newApplier(workspace, inheritanceGraph, mappingApplier);\n\t}\n\n\t/**\n\t * @return Transformation applier for the {@link WorkspaceManager#getCurrent() current workspace}\n\t * or {@code null} if no workspace is currently open.\n\t */\n\t@Nullable\n\tpublic TransformationApplier newApplierForCurrentWorkspace() {\n\t\tif (!workspaceManager.hasCurrentWorkspace())\n\t\t\treturn null;\n\t\tInheritanceGraph inheritanceGraph = Objects.requireNonNull(graphService.getCurrentWorkspaceInheritanceGraph(), \"Graph not created\");\n\t\tMappingApplier mappingApplier = Objects.requireNonNull(mappingService.inCurrentWorkspace(), \"Mapping applier not created\");\n\t\tWorkspace workspace = workspaceManager.getCurrent();\n\t\treturn newApplier(workspace, inheritanceGraph, mappingApplier);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to apply transformations within.\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph for the given workspace.\n\t * @param mappingApplier\n\t * \t\tMapping applier for the given workspace.\n\t *\n\t * @return Transformation applier for the given workspace.\n\t */\n\t@Nonnull\n\tprivate TransformationApplier newApplier(@Nonnull Workspace workspace, @Nonnull InheritanceGraph inheritanceGraph,\n\t                                         @Nonnull MappingApplier mappingApplier) {\n\t\treturn new TransformationApplier(transformationManager, config, inheritanceGraph, mappingApplier, workspace);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic TransformationApplierConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/transform/TransformationException.java",
    "content": "package software.coley.recaf.services.transform;\n\n/**\n * Thrown for any problem that prevents transformation operations.\n *\n * @author Matt Coley\n */\npublic class TransformationException extends Exception {\n\t/**\n\t * @param message\n\t * \t\tDetail message explaining the transformation failure.\n\t */\n\tpublic TransformationException(String message) {\n\t\tsuper(message);\n\t}\n\n\t/**\n\t * @param message\n\t * \t\tDetail message explaining the transformation failure.\n\t * @param cause\n\t * \t\tRoot problem/cause.\n\t */\n\tpublic TransformationException(String message, Throwable cause) {\n\t\tsuper(message, cause);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/transform/TransformationFeedback.java",
    "content": "package software.coley.recaf.services.transform;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Outline of transformation feedback capabilities. Allows for:\n * <ul>\n *     <li>In-progress transformation cancellation</li>\n *     <li>Filter classes transformed</li>\n *     <li>Notifying transformation progress</li>\n * </ul>\n *\n * @author Matt Coley\n * @see CancellableTransformationFeedback Basic cancellable implementation.\n */\npublic interface TransformationFeedback {\n\t/**\n\t * Default implementation that runs transformations to completion.\n\t */\n\tTransformationFeedback DEFAULT = new TransformationFeedback() {\n\t};\n\n\t/**\n\t * @return {@code true} to request {@link TransformationApplier} stops handling input to end the transformation early.\n\t * {@code false} to continue the transformation.\n\t */\n\tdefault boolean hasRequestedCancellation() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * Called before a class is transformed.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace containing the class.\n\t * @param resource\n\t * \t\tResource containing the class.\n\t * @param bundle\n\t * \t\tBundle containing the class.\n\t * @param classInfo\n\t * \t\tThe class to transform.\n\t * @param transformer\n\t * \t\tTransformer to apply.\n\t * @param pass\n\t * \t\tThe current transformation pass.\n\t *\n\t * @return {@code true} to allow the class to be transformed.\n\t * {@code false} to skip transforming the given class.\n\t */\n\tdefault boolean shouldTransform(@Nonnull Workspace workspace, @Nonnull WorkspaceResource resource,\n\t                                @Nonnull ClassBundle<?> bundle, @Nonnull ClassInfo classInfo,\n\t                                @Nonnull ClassTransformer transformer, int pass) {\n\t\treturn true;\n\t}\n\n\t/**\n\t * Called when a class has been successfully transformed.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace containing the class.\n\t * @param resource\n\t * \t\tResource containing the class.\n\t * @param bundle\n\t * \t\tBundle containing the class.\n\t * @param classInfo\n\t * \t\tThe original class transformed.\n\t * @param transformer\n\t * \t\tTransformer applied.\n\t * @param pass\n\t * \t\tThe current transformation pass.\n\t */\n\tdefault void onTransformed(@Nonnull Workspace workspace, @Nonnull WorkspaceResource resource,\n\t                           @Nonnull ClassBundle<?> bundle, @Nonnull ClassInfo classInfo,\n\t                           @Nonnull ClassTransformer transformer, int pass) {}\n\n\t/**\n\t * Called when a class passed transformation, but no work was done.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace containing the class.\n\t * @param resource\n\t * \t\tResource containing the class.\n\t * @param bundle\n\t * \t\tBundle containing the class.\n\t * @param classInfo\n\t * \t\tThe original class transformed.\n\t * @param transformer\n\t * \t\tTransformer applied.\n\t * @param pass\n\t * \t\tThe current transformation pass.\n\t */\n\tdefault void onTransformedWithoutWork(@Nonnull Workspace workspace, @Nonnull WorkspaceResource resource,\n\t                                      @Nonnull ClassBundle<?> bundle, @Nonnull ClassInfo classInfo,\n\t                                      @Nonnull ClassTransformer transformer, int pass) {}\n\n\t/**\n\t * Called when a transformation on a class fails.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace containing the class.\n\t * @param resource\n\t * \t\tResource containing the class.\n\t * @param bundle\n\t * \t\tBundle containing the class.\n\t * @param classInfo\n\t * \t\tThe original class transformed.\n\t * @param transformer\n\t * \t\tTransformer applied.\n\t * @param pass\n\t * \t\tThe current transformation pass.\n\t * @param error\n\t * \t\tThe exception thrown during transformation.\n\t */\n\tdefault void onTransformFailure(@Nonnull Workspace workspace, @Nonnull WorkspaceResource resource,\n\t                                @Nonnull ClassBundle<?> bundle, @Nonnull ClassInfo classInfo,\n\t                                @Nonnull ClassTransformer transformer, int pass, @Nonnull Throwable error) {}\n\n\t/**\n\t * Called when the transformation completes.\n\t */\n\tdefault void onCompletion() {}\n\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/transform/TransformationManager.java",
    "content": "package software.coley.recaf.services.transform;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.enterprise.inject.spi.Bean;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.Bootstrap;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.Service;\n\nimport java.util.HashSet;\nimport java.util.IdentityHashMap;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Supplier;\n\n/**\n * Manager for tracking transformers.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class TransformationManager implements Service {\n\tpublic static final String SERVICE_ID = \"transformation-manager\";\n\tprivate static final Logger logger = Logging.get(TransformationManager.class);\n\tprivate final Map<Class<? extends JvmClassTransformer>, Supplier<JvmClassTransformer>> jvmTransformerSuppliers = new IdentityHashMap<>();\n\tprivate final Set<Class<? extends JvmClassTransformer>> thirdPartyJvmTransformers = new HashSet<>();\n\tprivate final TransformationManagerConfig config;\n\n\t/**\n\t * Constructor for pulling transformer instances from the Recaf CDI container.\n\t *\n\t * @param config\n\t * \t\tManager config.\n\t * @param jvmTransformers\n\t * \t\tCDI provider of JVM transformer implementations.\n\t */\n\t@Inject\n\tpublic TransformationManager(@Nonnull TransformationManagerConfig config, @Nonnull Instance<JvmClassTransformer> jvmTransformers) {\n\t\tthis.config = config;\n\t\tfor (Instance.Handle<JvmClassTransformer> handle : jvmTransformers.handles()) {\n\t\t\tBean<JvmClassTransformer> bean = handle.getBean();\n\t\t\tClass<? extends JvmClassTransformer> transformerClass = Unchecked.cast(bean.getBeanClass());\n\n\t\t\t// To differentiate our built-in transformers from any plugin-provided ones, we register them directly here\n\t\t\t// rather than using the register method below.\n\t\t\tjvmTransformerSuppliers.put(transformerClass, () -> {\n\t\t\t\t// Even though our transformers may be @Dependent scoped, we need to do a new lookup each time we want\n\t\t\t\t// a new instance to get our desired scope behavior. If we re-use the instance handle that is injected\n\t\t\t\t// here then even @Dependent scoped beans will yield the same instance again and again.\n\t\t\t\treturn Bootstrap.get().get(transformerClass);\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Constructor for testing with pre-defined sets of transformers.\n\t *\n\t * @param jvmTransformerSuppliers\n\t * \t\tMap of transformer classes to suppliers that generate instances of those classes.\n\t */\n\t@VisibleForTesting\n\tpublic TransformationManager(@Nonnull Map<Class<? extends JvmClassTransformer>, Supplier<JvmClassTransformer>> jvmTransformerSuppliers) {\n\t\tthis.jvmTransformerSuppliers.putAll(jvmTransformerSuppliers);\n\t\tthis.config = new TransformationManagerConfig();\n\t}\n\n\t/**\n\t * Register a new {@link JvmClassTransformer}.\n\t *\n\t * @param transformerClass\n\t * \t\tClass of transformer to register.\n\t * @param transformerSupplier\n\t * \t\tSupplier of transformer instances.\n\t * @param <T>\n\t * \t\tTransformer type.\n\t */\n\tpublic <T extends JvmClassTransformer> void registerJvmClassTransformer(@Nonnull Class<T> transformerClass, @Nonnull Supplier<T> transformerSupplier) {\n\t\t// Only update the map if this is a new transformer.\n\t\tif (thirdPartyJvmTransformers.add(transformerClass))\n\t\t\tjvmTransformerSuppliers.put(transformerClass, Unchecked.cast(transformerSupplier));\n\t}\n\n\t/**\n\t * Unregister a previously-registered {@link JvmClassTransformer}.\n\t *\n\t * @param transformerClass\n\t * \t\tClass of transformer to unregister.\n\t * @param <T>\n\t * \t\tTransformer type.\n\t */\n\tpublic <T extends JvmClassTransformer> void unregisterJvmClassTransformer(@Nonnull Class<T> transformerClass) {\n\t\t// Only allow unregistering of third-party transformers.\n\t\tif (thirdPartyJvmTransformers.remove(transformerClass))\n\t\t\tjvmTransformerSuppliers.remove(transformerClass);\n\t}\n\n\t/**\n\t * @return Set of registered {@link JvmClassTransformer} classes.\n\t */\n\t@Nonnull\n\tpublic Set<Class<? extends JvmClassTransformer>> getJvmClassTransformers() {\n\t\treturn jvmTransformerSuppliers.keySet();\n\t}\n\n\t/**\n\t * @return Set of third-party registered {@link JvmClassTransformer} classes.\n\t */\n\t@Nonnull\n\tpublic Set<Class<? extends JvmClassTransformer>> getThirdPartyJvmTransformers() {\n\t\treturn thirdPartyJvmTransformers;\n\t}\n\n\t@Nonnull\n\t@SuppressWarnings(\"unchecked\")\n\tpublic <T extends JvmClassTransformer> T newJvmTransformer(@Nonnull Class<T> type) throws TransformationException {\n\t\ttry {\n\t\t\tSupplier<JvmClassTransformer> supplier = jvmTransformerSuppliers.get(type);\n\t\t\tif (supplier == null)\n\t\t\t\tthrow new TransformationException(\"Requested transformer supplier for type '\"\n\t\t\t\t\t\t+ type.getSimpleName() + \"' but no associated supplier was registered\");\n\t\t\treturn (T) supplier.get();\n\t\t} catch (Throwable t) {\n\t\t\tthrow new TransformationException(\"Requested transformer supplier for type '\"\n\t\t\t\t\t+ type.getSimpleName() + \"' could not be instantiated\", t);\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic TransformationManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/transform/TransformationManagerConfig.java",
    "content": "package software.coley.recaf.services.transform;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link TransformationManager}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class TransformationManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic TransformationManagerConfig() {\n\t\tsuper(ConfigGroups.SERVICE_TRANSFORM, TransformationManager.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/tutorial/TutorialConfig.java",
    "content": "package software.coley.recaf.services.tutorial;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.util.DevDetection;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\n\n/**\n * Config for in-application tutorials.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\n@ExcludeFromJacocoGeneratedReport(justification = \"Tutorial is for UI usage only, and is not testable in a meaningful way.\")\npublic class TutorialConfig extends BasicConfigContainer {\n\tprivate final ObservableBoolean acknowledgedSaveWithErrors = new ObservableBoolean(DevDetection.isDevEnv());\n\tprivate final ObservableBoolean finishedTutorial = new ObservableBoolean(DevDetection.isDevEnv());\n\n\t@Inject\n\tpublic TutorialConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, \"tutorial\" + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"acknowledged-save-with-errors\", boolean.class, acknowledgedSaveWithErrors, true));\n\t\taddValue(new BasicConfigValue<>(\"finished-tutorial\", boolean.class, finishedTutorial, true));\n\t}\n\n\t/**\n\t * @return Flag indicating if the user has acknowledged they cannot save with errors.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getAcknowledgedSaveWithErrors() {\n\t\treturn acknowledgedSaveWithErrors;\n\t}\n\n\t/**\n\t * @return Flag indicating if the user has finished the interactive tutorial.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getFinishedTutorial() {\n\t\treturn finishedTutorial;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/tutorial/TutorialWorkspace.java",
    "content": "package software.coley.recaf.services.tutorial;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\n\n/**\n * Specialized tutorial workspace.\n *\n * @author Matt Coley\n */\n@ExcludeFromJacocoGeneratedReport(justification = \"Tutorial is for UI usage only, and is not testable in a meaningful way.\")\npublic class TutorialWorkspace extends BasicWorkspace {\n\t/**\n\t * @param primary\n\t * \t\tTutorial resource.\n\t */\n\tpublic TutorialWorkspace(@Nonnull TutorialWorkspaceResource primary) {\n\t\tsuper(primary);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/tutorial/TutorialWorkspaceResource.java",
    "content": "package software.coley.recaf.services.tutorial;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\nimport software.coley.recaf.workspace.model.resource.BasicWorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder;\n\n/**\n * Specialized tutorial resource.\n *\n * @author Matt Coley\n */\n@ExcludeFromJacocoGeneratedReport(justification = \"Tutorial is for UI usage only, and is not testable in a meaningful way.\")\npublic class TutorialWorkspaceResource extends BasicWorkspaceResource {\n\tpublic static final String COMMENT_KEY = \"tutorial-resource-key\";\n\n\t/**\n\t * @param builder\n\t * \t\tResource builder.\n\t */\n\tpublic TutorialWorkspaceResource(@Nonnull WorkspaceResourceBuilder builder) {\n\t\tsuper(builder);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/BasicWorkspaceManager.java",
    "content": "package software.coley.recaf.services.workspace;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Produces;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.collections.Lists;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.workspace.model.EmptyWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.WorkspaceModificationListener;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * Basic workspace manager implementation.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicWorkspaceManager implements WorkspaceManager {\n\tprivate static final Logger logger = Logging.get(BasicWorkspaceManager.class);\n\tprivate final List<WorkspaceCloseCondition> closeConditions = new CopyOnWriteArrayList<>();\n\tprivate final List<WorkspaceOpenListener> openListeners = new CopyOnWriteArrayList<>();\n\tprivate final List<WorkspaceCloseListener> closeListeners = new CopyOnWriteArrayList<>();\n\tprivate final List<WorkspaceModificationListener> defaultModificationListeners = new CopyOnWriteArrayList<>();\n\tprivate final WorkspaceManagerConfig config;\n\tprivate Workspace current;\n\n\t@Inject\n\tpublic BasicWorkspaceManager(@Nonnull WorkspaceManagerConfig config) {\n\t\tthis.config = config;\n\t}\n\n\t@Nonnull\n\t@Override\n\t@Produces\n\t@Dependent\n\tpublic Workspace getCurrent() {\n\t\tif (current == null)\n\t\t\treturn EmptyWorkspace.get();\n\t\treturn current;\n\t}\n\n\t@Override\n\tpublic boolean hasCurrentWorkspace() {\n\t\treturn current != null;\n\t}\n\n\t@Override\n\tpublic void setCurrentIgnoringConditions(Workspace workspace) {\n\t\tWorkspace currentWorkspace = current;\n\t\tif (currentWorkspace != null) {\n\t\t\tcurrentWorkspace.close();\n\t\t\tUnchecked.checkedForEach(closeListeners, listener -> listener.onWorkspaceClosed(currentWorkspace),\n\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when closing workspace\", t));\n\t\t}\n\t\tcurrent = workspace;\n\t\tif (workspace != null) {\n\t\t\tdefaultModificationListeners.forEach(workspace::addWorkspaceModificationListener);\n\t\t\tUnchecked.checkedForEach(openListeners, listener -> listener.onWorkspaceOpened(workspace),\n\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown by when opening workspace\", t));\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<WorkspaceCloseCondition> getWorkspaceCloseConditions() {\n\t\treturn closeConditions;\n\t}\n\n\t@Override\n\tpublic void addWorkspaceCloseCondition(@Nonnull WorkspaceCloseCondition condition) {\n\t\tPrioritySortable.add(closeConditions, condition);\n\t}\n\n\t@Override\n\tpublic void removeWorkspaceCloseCondition(@Nonnull WorkspaceCloseCondition condition) {\n\t\tcloseConditions.remove(condition);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<WorkspaceOpenListener> getWorkspaceOpenListeners() {\n\t\treturn openListeners;\n\t}\n\n\t@Override\n\tpublic void addWorkspaceOpenListener(@Nonnull WorkspaceOpenListener listener) {\n\t\tPrioritySortable.add(openListeners, listener);\n\t}\n\n\t@Override\n\tpublic void removeWorkspaceOpenListener(@Nonnull WorkspaceOpenListener listener) {\n\t\topenListeners.remove(listener);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<WorkspaceCloseListener> getWorkspaceCloseListeners() {\n\t\treturn closeListeners;\n\t}\n\n\t@Override\n\tpublic void addWorkspaceCloseListener(@Nonnull WorkspaceCloseListener listener) {\n\t\tPrioritySortable.add(closeListeners, listener);\n\t}\n\n\t@Override\n\tpublic void removeWorkspaceCloseListener(@Nonnull WorkspaceCloseListener listener) {\n\t\tcloseListeners.remove(listener);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<WorkspaceModificationListener> getDefaultWorkspaceModificationListeners() {\n\t\treturn defaultModificationListeners;\n\t}\n\n\t@Override\n\tpublic void addDefaultWorkspaceModificationListeners(@Nonnull WorkspaceModificationListener listener) {\n\t\tPrioritySortable.add(defaultModificationListeners, listener);\n\t}\n\n\t@Override\n\tpublic void removeDefaultWorkspaceModificationListeners(@Nonnull WorkspaceModificationListener listener) {\n\t\tdefaultModificationListeners.remove(listener);\n\n\t\taddDefaultWorkspaceModificationListeners(new WorkspaceModificationListener() {\n\t\t\t@Override\n\t\t\tpublic void onAddLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library) {\n\t\t\t\t// Supporting library added to workspace\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onRemoveLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library) {\n\t\t\t\t// Supporting library removed from workspace\n\t\t\t}\n\t\t});\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic WorkspaceManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/WorkspaceCloseCondition.java",
    "content": "package software.coley.recaf.services.workspace;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Condition applied to {@link WorkspaceManager} to prevent closure of an active workspace for when\n * {@link WorkspaceManager#setCurrent(Workspace)} is called.\n *\n * @author Matt Coley\n */\npublic interface WorkspaceCloseCondition extends PrioritySortable {\n\t/**\n\t * @param current\n\t * \t\tCurrent workspace.\n\t *\n\t * @return {@code true} when the operation is allowed.\n\t */\n\tboolean canClose(@Nonnull Workspace current);\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/WorkspaceCloseListener.java",
    "content": "package software.coley.recaf.services.workspace;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Listener for when old workspaces are closed.\n *\n * @author Matt Coley\n */\npublic interface WorkspaceCloseListener extends PrioritySortable {\n\t/**\n\t * Called when {@link WorkspaceManager#setCurrent(Workspace)} passes and a prior workspace is removed.\n\t *\n\t * @param workspace\n\t * \t\tNew workspace assigned.\n\t */\n\tvoid onWorkspaceClosed(@Nonnull Workspace workspace);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/WorkspaceManager.java",
    "content": "package software.coley.recaf.services.workspace;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.enterprise.inject.Produces;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\nimport software.coley.recaf.workspace.model.EmptyWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.WorkspaceModificationListener;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Service outline for managing workspace importing/exporting and the currently open workspace.\n *\n * @author Matt Coley\n */\npublic interface WorkspaceManager extends Service {\n\tString SERVICE_ID = \"workspace-manager\";\n\n\t/**\n\t * Exposes the current workspace directly and through CDI.\n\t * Any {@link Instance<Workspace>} in the Recaf application should point to this value.\n\t *\n\t * @return The current active workspace. Will be {@link EmptyWorkspace} when no active workspace is open.\n\t *\n\t * @see #hasCurrentWorkspace()\n\t */\n\t@Nonnull\n\t@Produces\n\t@Dependent\n\tWorkspace getCurrent();\n\n\t/**\n\t * The {@link #getCurrent()} method will yield an empty workspace when nothing is currently open.\n\t * This method should be used to check if no workspace is actually open.\n\t *\n\t * @return {@code true} when there is a valid current workspace.\n\t * {@code false} when no workspace is open.\n\t */\n\tboolean hasCurrentWorkspace();\n\n\t/**\n\t * @param workspace\n\t * \t\tNew workspace to set as the active workspace.\n\t *\n\t * @return {@code true} when the workspace assignment is a success.\n\t * {@code false} if the assignment was blocked for some reason.\n\t */\n\tdefault boolean setCurrent(@Nullable Workspace workspace) {\n\t\tWorkspace current = getCurrent();\n\t\tif (!hasCurrentWorkspace()) {\n\t\t\t// If there is no current workspace, then just assign it.\n\t\t\tsetCurrentIgnoringConditions(workspace);\n\t\t\treturn true;\n\t\t} else if (getWorkspaceCloseConditions().stream()\n\t\t\t\t.allMatch(condition -> condition.canClose(current))) {\n\t\t\t// Otherwise, check if the conditions allow for closing the prior workspace.\n\t\t\t// If so, then assign the new workspace.\n\t\t\tsetCurrentIgnoringConditions(workspace);\n\t\t\treturn true;\n\t\t}\n\t\t// Workspace closure conditions not met, assignment denied.\n\t\treturn false;\n\t}\n\n\t/**\n\t * Effectively {@link #setCurrent(Workspace)} except any blocking conditions are bypassed.\n\t * <br>\n\t * Listeners for open/close events must be called when implementing this.\n\t *\n\t * @param workspace\n\t * \t\tNew workspace to set as the active workspace.\n\t */\n\tvoid setCurrentIgnoringConditions(@Nullable Workspace workspace);\n\n\t/**\n\t * Closes the current workspace.\n\t *\n\t * @return {@code true} on success.\n\t */\n\tdefault boolean closeCurrent() {\n\t\tif (hasCurrentWorkspace())\n\t\t\treturn setCurrent(null);\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param primary\n\t * \t\tPrimary resource for editing.\n\t *\n\t * @return New workspace of resource.\n\t */\n\t@Nonnull\n\tdefault Workspace createWorkspace(@Nonnull WorkspaceResource primary) {\n\t\treturn createWorkspace(primary, Collections.emptyList());\n\t}\n\n\t/**\n\t * @param primary\n\t * \t\tPrimary resource for editing.\n\t * @param libraries\n\t * \t\tSupporting resources.\n\t *\n\t * @return New workspace of resources\n\t */\n\t@Nonnull\n\tdefault Workspace createWorkspace(@Nonnull WorkspaceResource primary, @Nonnull List<WorkspaceResource> libraries) {\n\t\treturn new BasicWorkspace(primary, libraries);\n\t}\n\n\t/**\n\t * @return Conditions in the manager that can prevent {@link #setCurrent(Workspace)} from going through.\n\t */\n\t@Nonnull\n\tList<WorkspaceCloseCondition> getWorkspaceCloseConditions();\n\n\t/**\n\t * @param condition\n\t * \t\tCondition to add.\n\t */\n\tvoid addWorkspaceCloseCondition(@Nonnull WorkspaceCloseCondition condition);\n\n\t/**\n\t * @param condition\n\t * \t\tCondition to remove.\n\t */\n\tvoid removeWorkspaceCloseCondition(@Nonnull WorkspaceCloseCondition condition);\n\n\t/**\n\t * @return Listeners for when a new workspace is assigned as the current one.\n\t */\n\t@Nonnull\n\tList<WorkspaceOpenListener> getWorkspaceOpenListeners();\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tvoid addWorkspaceOpenListener(@Nonnull WorkspaceOpenListener listener);\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tvoid removeWorkspaceOpenListener(@Nonnull WorkspaceOpenListener listener);\n\n\t/**\n\t * @return Listeners for when the current workspace is removed as being current.\n\t */\n\t@Nonnull\n\tList<WorkspaceCloseListener> getWorkspaceCloseListeners();\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tvoid addWorkspaceCloseListener(@Nonnull WorkspaceCloseListener listener);\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tvoid removeWorkspaceCloseListener(@Nonnull WorkspaceCloseListener listener);\n\n\t/**\n\t * @return Listeners to add to any workspace passed to {@link #setCurrent(Workspace)}.\n\t */\n\t@Nonnull\n\tList<WorkspaceModificationListener> getDefaultWorkspaceModificationListeners();\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tvoid addDefaultWorkspaceModificationListeners(@Nonnull WorkspaceModificationListener listener);\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tvoid removeDefaultWorkspaceModificationListeners(@Nonnull WorkspaceModificationListener listener);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/WorkspaceManagerConfig.java",
    "content": "package software.coley.recaf.services.workspace;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link WorkspaceManager}\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class WorkspaceManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic WorkspaceManagerConfig() {\n\t\tsuper(ConfigGroups.SERVICE_IO, WorkspaceManager.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/WorkspaceOpenListener.java",
    "content": "package software.coley.recaf.services.workspace;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Listener for when new workspaces are opened.\n *\n * @author Matt Coley\n */\npublic interface WorkspaceOpenListener extends PrioritySortable {\n\t/**\n\t * Called when {@link WorkspaceManager#setCurrent(Workspace)} passes.\n\t *\n\t * @param workspace\n\t * \t\tNew workspace assigned.\n\t */\n\tvoid onWorkspaceOpened(@Nonnull Workspace workspace);\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/WorkspaceProcessingConfig.java",
    "content": "package software.coley.recaf.services.workspace;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link WorkspaceProcessingService}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class WorkspaceProcessingConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic WorkspaceProcessingConfig() {\n\t\tsuper(ConfigGroups.SERVICE_TRANSFORM, WorkspaceProcessingService.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/WorkspaceProcessingService.java",
    "content": "package software.coley.recaf.services.workspace;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.enterprise.inject.spi.Bean;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.Bootstrap;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.cdi.EagerInitialization;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.IdentityHashMap;\nimport java.util.Map;\nimport java.util.function.Supplier;\n\n/**\n * Applies all discovered {@link WorkspaceProcessor} instances to {@link Workspace} instances upon loading them via\n * {@link WorkspaceManager#setCurrent(Workspace)}.\n *\n * @author Matt Coley\n * @see WorkspaceProcessor Processor type to implement.\n */\n@EagerInitialization\n@ApplicationScoped\npublic class WorkspaceProcessingService implements Service {\n\tpublic static final String SERVICE_ID = \"workspace-processing\";\n\tprivate static final Logger logger = Logging.get(WorkspaceProcessingService.class);\n\tprivate final Map<Class<? extends WorkspaceProcessor>, Supplier<WorkspaceProcessor>> processorSuppliers = new IdentityHashMap<>();\n\tprivate final WorkspaceProcessingConfig config;\n\n\t/**\n\t * @param workspaceManager\n\t * \t\tManager to facilitate listening to new opened workspaces.\n\t * @param config\n\t * \t\tService config.\n\t * @param processors\n\t * \t\tDiscovered processors to apply.\n\t */\n\t@Inject\n\tpublic WorkspaceProcessingService(@Nonnull WorkspaceManager workspaceManager,\n\t                                  @Nonnull WorkspaceProcessingConfig config,\n\t                                  @Nonnull Instance<WorkspaceProcessor> processors) {\n\t\tthis.config = config;\n\t\tfor (Instance.Handle<WorkspaceProcessor> handle : processors.handles()) {\n\t\t\tBean<WorkspaceProcessor> bean = handle.getBean();\n\t\t\tClass<? extends WorkspaceProcessor> processorClass = Unchecked.cast(bean.getBeanClass());\n\t\t\tprocessorSuppliers.put(processorClass, () -> {\n\t\t\t\t// Even though our processors may be @Dependent scoped, we need to do a new lookup each time we want\n\t\t\t\t// a new instance to get our desired scope behavior. If we re-use the instance handle that is injected\n\t\t\t\t// here then even @Dependent scoped beans will yield the same instance again and again.\n\t\t\t\treturn Bootstrap.get().get(processorClass);\n\t\t\t});\n\t\t}\n\n\t\t// Apply processors when new workspace is opened\n\t\tworkspaceManager.addWorkspaceOpenListener(this::processWorkspace);\n\t}\n\n\t/**\n\t * @param processorClass\n\t * \t\tClass of processor to register.\n\t * @param processorSupplier\n\t * \t\tSupplier of processor instances.\n\t * @param <T>\n\t * \t\tProcessor type.\n\t */\n\tpublic <T extends WorkspaceProcessor> void register(@Nonnull Class<T> processorClass, @Nonnull Supplier<T> processorSupplier) {\n\t\tprocessorSuppliers.put(Unchecked.cast(processorClass), Unchecked.cast(processorSupplier));\n\t}\n\n\t/**\n\t * @param processorClass\n\t * \t\tClass of processor to unregister.\n\t * @param <T>\n\t * \t\tProcessor type.\n\t */\n\tpublic <T extends WorkspaceProcessor> void unregister(@Nonnull Class<T> processorClass) {\n\t\tprocessorSuppliers.remove(Unchecked.cast(processorClass));\n\t}\n\n\t/**\n\t * Applies all processors to the given workspace.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to process.\n\t */\n\tpublic void processWorkspace(@Nonnull Workspace workspace) {\n\t\tfor (Supplier<WorkspaceProcessor> processorSupplier : processorSuppliers.values()) {\n\t\t\tWorkspaceProcessor processor = processorSupplier.get();\n\n\t\t\tlogger.trace(\"Applying workspace processor: {}\", processor.name());\n\t\t\tprocessor.processWorkspace(workspace);\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic WorkspaceProcessingConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/WorkspaceProcessor.java",
    "content": "package software.coley.recaf.services.workspace;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Generic processor for use in {@link WorkspaceProcessingService}.\n *\n * @author Matt Coley\n * @see WorkspaceProcessingService Manages calling implementations of this type.\n */\npublic interface WorkspaceProcessor {\n\t/**\n\t * Called when {@link WorkspaceManager#setCurrent(Workspace)} passes.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to process.\n\t */\n\tvoid processWorkspace(@Nonnull Workspace workspace);\n\n\t/**\n\t * @return Post processing task name.\n\t */\n\t@Nonnull\n\tString name();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/BasicClassPatcher.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport org.slf4j.Logger;\nimport software.coley.cafedude.InvalidClassException;\nimport software.coley.cafedude.classfile.ClassFile;\nimport software.coley.cafedude.classfile.attribute.BootstrapMethodsAttribute;\nimport software.coley.cafedude.classfile.behavior.AttributeHolder;\nimport software.coley.cafedude.classfile.constant.ConstDynamic;\nimport software.coley.cafedude.classfile.constant.CpEntry;\nimport software.coley.cafedude.classfile.constant.CpUtf8;\nimport software.coley.cafedude.io.ClassBuilder;\nimport software.coley.cafedude.io.ClassFileReader;\nimport software.coley.cafedude.io.ClassFileWriter;\nimport software.coley.cafedude.io.FallbackInstructionReader;\nimport software.coley.cafedude.transform.IllegalRewritingInstructionsReader;\nimport software.coley.cafedude.transform.IllegalStrippingTransformer;\nimport software.coley.cafedude.transform.Transformer;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.util.Types;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Basic patcher implementation with CafeDude.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicClassPatcher implements ClassPatcher {\n\tprivate static final Logger logger = Logging.get(BasicClassPatcher.class);\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] patch(@Nullable String name, @Nonnull byte[] code) throws IOException {\n\t\ttry {\n\t\t\t// Patch via CafeDude\n\t\t\tClassFileReader reader = new ClassFileReaderExt();\n\t\t\tClassFile classFile = reader.read(code);\n\t\t\tif (name == null) name = classFile.getName();\n\t\t\tnew BootstrapSpamTransformer(classFile).transform();\n\t\t\tnew IllegalStrippingTransformerExt(classFile).transform();\n\t\t\treturn new ClassFileWriter().write(classFile);\n\t\t} catch (InvalidClassException ex) {\n\t\t\tif (name == null) name = \"<no-name-given>\";\n\t\t\tlogger.error(\"CafeDude failed to parse '{}'\", name, ex);\n\t\t\tthrow new IOException(ex);\n\t\t} catch (Throwable t) {\n\t\t\tif (name == null) name = \"<no-name-given>\";\n\t\t\tlogger.error(\"CafeDude failed to patch '{}'\", name, t);\n\t\t\tthrow new IOException(t);\n\t\t}\n\t}\n\n\t/**\n\t * Extended class file reader that plugs into {@link IllegalRewritingInstructionsReader}.\n\t */\n\tprivate static class ClassFileReaderExt extends ClassFileReader {\n\t\tprivate FallbackInstructionReader fallback;\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic FallbackInstructionReader getFallbackInstructionReader(@Nonnull ClassBuilder builder) {\n\t\t\tif (fallback == null)\n\t\t\t\tfallback = new IllegalRewritingInstructionsReader(builder.getPool(), builder.getVersionMajor());\n\t\t\treturn fallback;\n\t\t}\n\t}\n\n\t/**\n\t * When {@link ClassWriter} initializes from an existing {@link ClassReader} it populates a {@code SymbolTable}.\n\t * This process will copy the {@link BootstrapMethodsAttribute} which can be filled with entries that have\n\t * thousands of arguments. Arguments can refer to other bootstrap methods with their own arguments,\n\t * and this can be stacked to any arbitrary level of depth.\n\t * <ul>\n\t *     <li>If you have 1,000 arguments referring to another BSM, with 1,000 arguments of its own,\n\t *     you get 1,000,000 arguments total.</li>\n\t *     <li>If you have 1 argument referring to another BSM which has 2 arguments,\n\t *     each which refer to another BSM which has 2 arguments,\n\t *     repeat this 20 times and you get 1,048,576 values.</li>\n\t * </ul>\n\t * The way ASM copies this data is <i>incredibly slow</i>.\n\t */\n\tprivate static class BootstrapSpamTransformer extends Transformer {\n\t\tprivate static final int ARG_THRESHOLD = 100;\n\t\tprivate final Map<BootstrapMethodsAttribute.BootstrapMethod, Integer> argCount = new IdentityHashMap<>();\n\n\t\tpublic BootstrapSpamTransformer(@Nonnull ClassFile clazz) {\n\t\t\tsuper(clazz);\n\t\t}\n\n\t\t@Override\n\t\tpublic void transform() {\n\t\t\tBootstrapMethodsAttribute attribute = clazz.getAttribute(BootstrapMethodsAttribute.class);\n\t\t\tif (attribute == null)\n\t\t\t\treturn;\n\t\t\tfor (BootstrapMethodsAttribute.BootstrapMethod bootstrapMethod : attribute.getBootstrapMethods()) {\n\t\t\t\tif (computeTotalArgs(attribute, bootstrapMethod) >= ARG_THRESHOLD) {\n\t\t\t\t\tbootstrapMethod.setArgs(Collections.emptyList());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tprivate int computeTotalArgs(@Nonnull BootstrapMethodsAttribute bsmAttribute,\n\t\t                             @Nonnull BootstrapMethodsAttribute.BootstrapMethod bsm) {\n\t\t\t// Get cached count if visited before.\n\t\t\tInteger cachedValue = argCount.get(bsm);\n\t\t\tif (cachedValue != null)\n\t\t\t\treturn cachedValue;\n\n\t\t\t// Get direct arg count.\n\t\t\tList<CpEntry> args = bsm.getArgs();\n\t\t\tint total = args.size();\n\n\t\t\t// Put the arg count in the map for now, we will update it later.\n\t\t\t// We just need it here already to handle short-circuiting with the indirect-argument counting.\n\t\t\targCount.put(bsm, total);\n\n\t\t\t// Only sum indirect-arguments if we're under the threshold.\n\t\t\tif (total < ARG_THRESHOLD) {\n\t\t\t\tfor (CpEntry arg : args) {\n\t\t\t\t\ttotal += countIndirectArgs(bsmAttribute, arg);\n\t\t\t\t\tif (total > ARG_THRESHOLD)\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\targCount.put(bsm, total);\n\t\t\treturn total;\n\t\t}\n\n\t\tprivate int countIndirectArgs(@Nonnull BootstrapMethodsAttribute bsmAttribute, @Nonnull CpEntry arg) {\n\t\t\tif (arg instanceof ConstDynamic dynamic) {\n\t\t\t\tint index = dynamic.getBsmIndex();\n\t\t\t\tvar bootstrapMethods = bsmAttribute.getBootstrapMethods();\n\t\t\t\tif (index < 0 || index >= bootstrapMethods.size())\n\t\t\t\t\treturn ARG_THRESHOLD; // Short-circuit loops.\n\n\t\t\t\t// Yield the arg count of the referenced bootstrap method.\n\t\t\t\tBootstrapMethodsAttribute.BootstrapMethod bsm = bootstrapMethods.get(dynamic.getBsmIndex());\n\t\t\t\treturn computeTotalArgs(bsmAttribute, bsm);\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\t/**\n\t * Extended illegal stripping transformer that also drops invalid signatures.\n\t */\n\tprivate static class IllegalStrippingTransformerExt extends IllegalStrippingTransformer {\n\t\tprivate IllegalStrippingTransformerExt(@Nonnull ClassFile clazz) {\n\t\t\tsuper(clazz);\n\t\t}\n\n\t\t@Override\n\t\tprotected boolean matchSignature(@Nonnull CpUtf8 e, @Nonnull AttributeHolder context) {\n\t\t\treturn switch (context.getHolderType()) {\n\t\t\t\tcase CLASS -> Types.isValidSignature(e.getText(), Types.SignatureContext.CLASS);\n\t\t\t\tcase FIELD, RECORD_COMPONENT -> Types.isValidSignature(e.getText(), Types.SignatureContext.FIELD);\n\t\t\t\tcase METHOD -> Types.isValidSignature(e.getText(), Types.SignatureContext.METHOD);\n\t\t\t\tcase ATTRIBUTE -> false;\n\t\t\t};\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/BasicInfoImporter.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.objectweb.asm.ClassReader;\nimport org.slf4j.Logger;\nimport software.coley.cafedude.classfile.VersionConstants;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.builder.ArscFileInfoBuilder;\nimport software.coley.recaf.info.builder.AudioFileInfoBuilder;\nimport software.coley.recaf.info.builder.BinaryXmlFileInfoBuilder;\nimport software.coley.recaf.info.builder.DexFileInfoBuilder;\nimport software.coley.recaf.info.builder.FileInfoBuilder;\nimport software.coley.recaf.info.builder.ImageFileInfoBuilder;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.info.builder.ModulesFileInfoBuilder;\nimport software.coley.recaf.info.builder.NativeLibraryFileInfoBuilder;\nimport software.coley.recaf.info.builder.VideoFileInfoBuilder;\nimport software.coley.recaf.info.builder.ZipFileInfoBuilder;\nimport software.coley.recaf.info.properties.builtin.IllegalClassSuspectProperty;\nimport software.coley.recaf.info.properties.builtin.ZipMarkerProperty;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.util.ByteHeaderUtil;\nimport software.coley.recaf.util.IOUtil;\nimport software.coley.recaf.util.android.AndroidXmlUtil;\nimport software.coley.recaf.util.io.ByteSource;\n\nimport java.io.IOException;\n\n/**\n * Basic implementation of the info importer.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicInfoImporter implements InfoImporter {\n\tprivate static final Logger logger = Logging.get(BasicInfoImporter.class);\n\tprivate final ClassPatcher classPatcher;\n\tprivate final InfoImporterConfig config;\n\tprivate final TextFormatConfig formatConfig;\n\n\t@Inject\n\tpublic BasicInfoImporter(@Nonnull InfoImporterConfig config, @Nonnull TextFormatConfig formatConfig, @Nonnull ClassPatcher classPatcher) {\n\t\tthis.config = config;\n\t\tthis.formatConfig = formatConfig;\n\t\tthis.classPatcher = classPatcher;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Info readInfo(@Nonnull String name, @Nonnull ByteSource source) throws IOException {\n\t\tbyte[] data = source.readAll();\n\n\t\t// Check for Java classes\n\t\tif (matchesClass(data)) {\n\t\t\ttry {\n\t\t\t\treturn readClass(name, data);\n\t\t\t} catch (Throwable t) {\n\t\t\t\t// Invalid class. There are a few possibilities here:\n\t\t\t\t// - The user has disabled patching in their settings and opened an obfuscated file that kills ASM.\n\t\t\t\t// - There is a pattern in the file very similar to a class file, but it is not actually a class file.\n\t\t\t\t// - There is an edge case we need to add to CafeDude to allow complete patching.\n\t\t\t\treturn new FileInfoBuilder<>()\n\t\t\t\t\t\t.withRawContent(data)\n\t\t\t\t\t\t.withName(name)\n\t\t\t\t\t\t.withProperty(IllegalClassSuspectProperty.INSTANCE)\n\t\t\t\t\t\t.build();\n\t\t\t}\n\t\t}\n\n\t\t// Comparing against known file types.\n\t\tboolean hasZipMarker = ByteHeaderUtil.matchAtAnyOffset(data, ByteHeaderUtil.ZIP);\n\t\tFileInfo info = readAsSpecializedFile(name, data);\n\t\tif (info != null) {\n\t\t\tif (hasZipMarker)\n\t\t\t\tZipMarkerProperty.set(info);\n\t\t\treturn info;\n\t\t}\n\n\t\t// Check for ZIP containers (For ZIP/JAR/JMod/WAR)\n\t\t//  - While this is more common, some of the known file types may match 'ZIP' with\n\t\t//    our 'any-offset' condition we have here.\n\t\t//  - We need 'any-offset' to catch all ZIP cases, but it can match some of the file types\n\t\t//    above in some conditions, which means we have to check for it last.\n\t\tif (hasZipMarker) {\n\t\t\tZipFileInfoBuilder builder = new ZipFileInfoBuilder()\n\t\t\t\t\t.withProperty(new ZipMarkerProperty())\n\t\t\t\t\t.withRawContent(data)\n\t\t\t\t\t.withName(name);\n\n\t\t\t// Record name, handle extension to determine info-type\n\t\t\tString extension = IOUtil.getExtension(name);\n\t\t\tif (extension == null) return builder.build();\n\t\t\treturn switch (extension.toUpperCase()) {\n\t\t\t\tcase \"JAR\" -> builder.asJar().build();\n\t\t\t\tcase \"APK\" -> builder.asApk().build();\n\t\t\t\tcase \"WAR\" -> builder.asWar().build();\n\t\t\t\tcase \"JMOD\" -> builder.asJMod().build();\n\t\t\t\tdefault -> builder.build();\n\t\t\t};\n\t\t}\n\n\t\t// No special case known for file, treat as generic file\n\t\t// Will be automatically mapped to a text file if the contents are all mappable characters.\n\t\treturn new FileInfoBuilder<>()\n\t\t\t\t.withRawContent(data)\n\t\t\t\t.withName(name)\n\t\t\t\t.build();\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName of file.\n\t * @param data\n\t * \t\tFile content.\n\t *\n\t * @return The {@link FileInfo} subtype of matched special cases <i>(Media, executables, etc.)</i>\n\t * or {@code null} if no special case is matched.\n\t */\n\t@Nullable\n\tprivate static FileInfo readAsSpecializedFile(@Nonnull String name, byte[] data) {\n\t\tif (ByteHeaderUtil.match(data, ByteHeaderUtil.DEX)) {\n\t\t\treturn new DexFileInfoBuilder()\n\t\t\t\t\t.withRawContent(data)\n\t\t\t\t\t.withName(name)\n\t\t\t\t\t.build();\n\t\t} else if (ByteHeaderUtil.match(data, ByteHeaderUtil.MODULES)) {\n\t\t\treturn new ModulesFileInfoBuilder()\n\t\t\t\t\t.withRawContent(data)\n\t\t\t\t\t.withName(name)\n\t\t\t\t\t.build();\n\t\t} else if (name.toUpperCase().endsWith(\".ARSC\") &&\n\t\t\t\tByteHeaderUtil.match(data, ByteHeaderUtil.ARSC)) {\n\t\t\treturn new ArscFileInfoBuilder()\n\t\t\t\t\t.withRawContent(data)\n\t\t\t\t\t.withName(name)\n\t\t\t\t\t.build();\n\t\t} else if (name.toUpperCase().endsWith(\".XML\") &&\n\t\t\t\t(ByteHeaderUtil.match(data, ByteHeaderUtil.BINARY_XML) || AndroidXmlUtil.hasXmlIndicators(data))) {\n\t\t\treturn new BinaryXmlFileInfoBuilder()\n\t\t\t\t\t.withRawContent(data)\n\t\t\t\t\t.withName(name)\n\t\t\t\t\t.build();\n\t\t} else if (ByteHeaderUtil.matchAny(data, ByteHeaderUtil.IMAGE_HEADERS)) {\n\t\t\treturn new ImageFileInfoBuilder()\n\t\t\t\t\t.withRawContent(data)\n\t\t\t\t\t.withName(name)\n\t\t\t\t\t.build();\n\t\t} else if (ByteHeaderUtil.matchAny(data, ByteHeaderUtil.AUDIO_HEADERS)) {\n\t\t\treturn new AudioFileInfoBuilder()\n\t\t\t\t\t.withRawContent(data)\n\t\t\t\t\t.withName(name)\n\t\t\t\t\t.build();\n\t\t} else if (ByteHeaderUtil.matchAny(data, ByteHeaderUtil.VIDEO_HEADERS)) {\n\t\t\treturn new VideoFileInfoBuilder()\n\t\t\t\t\t.withRawContent(data)\n\t\t\t\t\t.withName(name)\n\t\t\t\t\t.build();\n\t\t} else if (ByteHeaderUtil.matchAny(data, ByteHeaderUtil.PROGRAM_HEADERS)) {\n\t\t\treturn new NativeLibraryFileInfoBuilder()\n\t\t\t\t\t.withRawContent(data)\n\t\t\t\t\t.withName(name)\n\t\t\t\t\t.build();\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Nonnull\n\tprivate Info readClass(@Nonnull String name, @Nonnull byte[] data) throws Throwable {\n\t\tvar patchingMode = config.getClassPatchMode();\n\n\t\t// If we're skipping validation just parse the class file as-is and don't run validation checks.\n\t\t// Because the validation steps are skipped problems that would otherwise be caught and patched with\n\t\t// higher tier patch modes will occur when opening the class later. Users must accept this responsibility\n\t\t// if they want the boost in workspace load speeds.\n\t\tif (patchingMode == InfoImporterConfig.ClassPatchMode.SKIP_FILTER)\n\t\t\t// We still do not use 'SKIP_CODE' since we want the info models to have things like variable metadata.\n\t\t\treturn new JvmClassInfoBuilder(data, 0).build();\n\n\t\t// If we're always validating, patch the class and try and parse the patched output.\n\t\t// Any ASM parse failures imply patching has failed, and the class will be treated as a file instead (see catch block in calling methods)\n\t\tif (patchingMode == InfoImporterConfig.ClassPatchMode.ALWAYS_FILTER) {\n\t\t\tbyte[] patched = classPatcher.patch(name, data);\n\t\t\treturn new JvmClassInfoBuilder(patched, 0)\n\t\t\t\t\t.skipValidationChecks(false)\n\t\t\t\t\t.build();\n\t\t}\n\n\t\t// We're doing a check-then-filter. If ASM reads the class as-is without issue, keep the result.\n\t\t// Otherwise, patch when we encounter parse problems and try again.\n\t\tint readerFlags = patchingMode == InfoImporterConfig.ClassPatchMode.CHECK_ADVANCED_THEN_FILTER ? ClassReader.SKIP_CODE : 0;\n\t\ttry {\n\t\t\treturn new JvmClassInfoBuilder()\n\t\t\t\t\t.skipValidationChecks(false)\n\t\t\t\t\t.adaptFrom(data, readerFlags)\n\t\t\t\t\t.build();\n\t\t} catch (Throwable t) {\n\t\t\t// Patch if not compatible with ASM\n\t\t\tbyte[] patched = classPatcher.patch(name, data);\n\t\t\ttry {\n\t\t\t\tJvmClassInfo patchedClassInfo = new JvmClassInfoBuilder(patched, readerFlags)\n\t\t\t\t\t\t.skipValidationChecks(false)\n\t\t\t\t\t\t.build();\n\t\t\t\tlogger.debug(\"CafeDude patched class: {}\", name);\n\t\t\t\treturn patchedClassInfo;\n\t\t\t} catch (Throwable t1) {\n\t\t\t\tlogger.error(\"CafeDude patching output is still non-compliant with ASM for file: {}\", formatConfig.filter(name));\n\t\t\t\tthrow t1;\n\t\t\t}\n\t\t}\n\t}\n\n\n\t/**\n\t * Check if the byte array is prefixed by the class file magic header.\n\t *\n\t * @param content\n\t * \t\tFile content.\n\t *\n\t * @return If the content seems to be a class at a first glance.\n\t */\n\tprivate static boolean matchesClass(byte[] content) {\n\t\t// Null and size check\n\t\t// The smallest valid class possible that is verifiable is 37 bytes AFAIK, but we'll be generous here.\n\t\tif (content == null || content.length <= 16)\n\t\t\treturn false;\n\n\t\t// We want to make sure the 'magic' is correct.\n\t\tif (!ByteHeaderUtil.match(content, ByteHeaderUtil.CLASS))\n\t\t\treturn false;\n\n\t\t// 'dylib' files can also have CAFEBABE as a magic header... Gee, thanks Apple :/\n\t\t// Because of this we'll employ some more sanity checks.\n\t\t// Version number must be non-zero\n\t\tint version = ((content[6] & 0xFF) << 8) + (content[7] & 0xFF);\n\t\tif (version < VersionConstants.JAVA1)\n\t\t\treturn false;\n\n\t\t// Must include some constant pool entries.\n\t\t// The smallest number includes:\n\t\t//  - utf8  - name of current class\n\t\t//  - class - wrapper of prior\n\t\t//  - utf8  - name of object class\n\t\t//  - class - wrapper of prior`\n\t\tint cpSize = ((content[8] & 0xFF) << 8) + (content[9] & 0xFF);\n\t\treturn cpSize >= 4;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic InfoImporterConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/BasicResourceImporter.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.lljzip.format.model.CentralDirectoryFileHeader;\nimport software.coley.lljzip.format.model.LocalFileHeader;\nimport software.coley.lljzip.format.model.ZipArchive;\nimport software.coley.lljzip.util.ExtraFieldTime;\nimport software.coley.lljzip.util.MemorySegmentUtil;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.DexFileInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.JarFileInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.ModulesFileInfo;\nimport software.coley.recaf.info.ZipFileInfo;\nimport software.coley.recaf.info.builder.FileInfoBuilder;\nimport software.coley.recaf.info.builder.ZipFileInfoBuilder;\nimport software.coley.recaf.info.properties.builtin.InputFilePathProperty;\nimport software.coley.recaf.info.properties.builtin.PathOriginalNameProperty;\nimport software.coley.recaf.info.properties.builtin.PathPrefixProperty;\nimport software.coley.recaf.info.properties.builtin.PathSuffixProperty;\nimport software.coley.recaf.info.properties.builtin.VersionedClassProperty;\nimport software.coley.recaf.info.properties.builtin.ZipAccessTimeProperty;\nimport software.coley.recaf.info.properties.builtin.ZipCommentProperty;\nimport software.coley.recaf.info.properties.builtin.ZipCompressionProperty;\nimport software.coley.recaf.info.properties.builtin.ZipCreationTimeProperty;\nimport software.coley.recaf.info.properties.builtin.ZipEntryIndexProperty;\nimport software.coley.recaf.info.properties.builtin.ZipMarkerProperty;\nimport software.coley.recaf.info.properties.builtin.ZipModificationTimeProperty;\nimport software.coley.recaf.info.properties.builtin.ZipPrefixDataProperty;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.util.IOUtil;\nimport software.coley.recaf.util.ModulesIOUtil;\nimport software.coley.recaf.util.ShortcutUtil;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.android.DexIOUtil;\nimport software.coley.recaf.util.io.ByteSource;\nimport software.coley.recaf.util.io.ByteSources;\nimport software.coley.recaf.util.io.LocalFileHeaderSource;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicFileBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicVersionedJvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.VersionedJvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceDirectoryResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResourceBuilder;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.lang.foreign.MemorySegment;\nimport java.net.URI;\nimport java.net.URL;\nimport java.nio.file.FileVisitOption;\nimport java.nio.file.FileVisitResult;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.SimpleFileVisitor;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NavigableMap;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.TreeMap;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutorService;\nimport java.util.function.Consumer;\n\n/**\n * Basic implementation of the resource importer.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicResourceImporter implements ResourceImporter, Service {\n\tprivate static final Logger logger = Logging.get(BasicResourceImporter.class);\n\tprivate static final int MAX_WALK_DEPTH = 100;\n\tprivate final InfoImporter infoImporter;\n\tprivate final ResourceImporterConfig config;\n\n\t@Inject\n\tpublic BasicResourceImporter(@Nonnull InfoImporter infoImporter,\n\t                             @Nonnull ResourceImporterConfig config) {\n\t\tthis.infoImporter = infoImporter;\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * General read handling for any single-file resource kind. Delegates to others when needed.\n\t *\n\t * @param builder\n\t * \t\tBuilder to work with.\n\t * @param pathName\n\t * \t\tName of input file / content.\n\t * @param source\n\t * \t\tAccess to content / data.\n\t *\n\t * @return Read resource.\n\t */\n\tprivate WorkspaceResource handleSingle(@Nonnull WorkspaceFileResourceBuilder builder,\n\t                                       @Nonnull String pathName, @Nonnull ByteSource source) throws IOException {\n\t\t// Read input as raw info in order to determine file-type.\n\t\tPathAndName pathAndName = PathAndName.fromString(pathName);\n\t\tString name = pathAndName.name;\n\t\tPath localPath = pathAndName.path;\n\t\tInfo readInfo = infoImporter.readInfo(name, source);\n\n\t\t// Check if it is a single class.\n\t\tif (readInfo.isClass()) {\n\t\t\t// If it is a class, we know it MUST be a single JVM class since Android classes do not exist\n\t\t\t// in single file form. They only come bundled in DEX files.\n\t\t\tJvmClassInfo readAsJvmClass = readInfo.asClass().asJvmClass();\n\t\t\tBasicJvmClassBundle bundle = new BasicJvmClassBundle();\n\t\t\tbundle.initialPut(readAsJvmClass);\n\n\t\t\t// To satisfy our file-info requirement for the file resource we can create a wrapper file-info\n\t\t\t// using the JVM class's bytecode.\n\t\t\tFileInfo fileInfo = new FileInfoBuilder<>()\n\t\t\t\t\t.withName(readAsJvmClass.getName() + \".class\")\n\t\t\t\t\t.withRawContent(readAsJvmClass.getBytecode())\n\t\t\t\t\t.build();\n\t\t\tif (localPath != null)\n\t\t\t\tInputFilePathProperty.set(fileInfo, localPath); // Associate input path with the read value.\n\t\t\treturn builder.withFileInfo(fileInfo)\n\t\t\t\t\t.withJvmClassBundle(bundle)\n\t\t\t\t\t.build();\n\t\t}\n\n\t\t// Associate input path with the read value.\n\t\tif (localPath != null) InputFilePathProperty.set(readInfo, localPath);\n\n\t\t// Must be some non-class type of file.\n\t\tFileInfo readInfoAsFile = readInfo.asFile();\n\t\tbuilder = builder.withFileInfo(readInfoAsFile);\n\n\t\t// Check for general ZIP container format (ZIP/JAR/WAR/APK/JMod)\n\t\tif (readInfoAsFile.isZipFile()) {\n\t\t\tZipFileInfo readInfoAsZip = readInfoAsFile.asZipFile();\n\t\t\treturn handleZip(builder, readInfoAsZip, source);\n\t\t} else if (ZipMarkerProperty.get(readInfoAsFile)) {\n\t\t\t// In some cases the file may have been matched as something else (like an executable)\n\t\t\t// but also count as a ZIP container. Applications that bundle Java applications into native exe files\n\t\t\t// tend to do this.\n\t\t\ttry {\n\t\t\t\treturn handleZip(builder, new ZipFileInfoBuilder(readInfoAsFile.toFileBuilder()).build(), source);\n\t\t\t} catch (Throwable t) {\n\t\t\t\t// Some files will just so happen to have a ZIP marker in their bytes but not represent an actual ZIP.\n\t\t\t\t// This is fine because by this point we have an info-type to fall back on.\n\t\t\t\tlogger.debug(\"Saw ZIP marker in file {} but could not parse as ZIP.\", name);\n\t\t\t}\n\t\t}\n\n\t\t// Check for DEX file format.\n\t\tif (readInfoAsFile instanceof DexFileInfo) {\n\t\t\tString dexName = readInfoAsFile.getName();\n\t\t\tAndroidClassBundle dexBundle = DexIOUtil.read(readInfoAsFile.getRawContent());\n\t\t\treturn builder.withAndroidClassBundles(Map.of(dexName, dexBundle))\n\t\t\t\t\t.build();\n\t\t}\n\n\t\t// Must be some edge case type: Modules, or an unknown file type\n\t\tif (readInfoAsFile instanceof ModulesFileInfo) {\n\t\t\treturn handleModules(builder, (ModulesFileInfo) readInfoAsFile);\n\t\t}\n\n\t\t// Unknown file type\n\t\tBasicFileBundle bundle = new BasicFileBundle();\n\t\tbundle.initialPut(readInfoAsFile);\n\t\treturn builder\n\t\t\t\t.withFileBundle(bundle)\n\t\t\t\t.build();\n\t}\n\n\t@Nonnull\n\tprivate WorkspaceFileResource handleZip(@Nonnull WorkspaceFileResourceBuilder builder,\n\t                                        @Nonnull ZipFileInfo zipInfo,\n\t                                        @Nonnull ByteSource source) throws IOException {\n\t\tlogger.info(\"Reading input from ZIP container '{}'\", zipInfo.getName());\n\t\tbuilder.withFileInfo(zipInfo);\n\t\tBasicJvmClassBundle classes = new BasicJvmClassBundle();\n\t\tBasicFileBundle files = new BasicFileBundle();\n\t\tMap<String, AndroidClassBundle> androidClassBundles = new ConcurrentHashMap<>();\n\t\tNavigableMap<Integer, VersionedJvmClassBundle> versionedJvmClassBundles = Collections.synchronizedNavigableMap(new TreeMap<>());\n\t\tMap<String, WorkspaceFileResource> embeddedResources = new ConcurrentHashMap<>();\n\n\t\t// Read ZIP\n\t\tboolean isAndroid = zipInfo.getName().toLowerCase().endsWith(\".apk\");\n\t\tZipArchive archive = config.mapping().apply(source.readAll());\n\n\t\t// Sanity check, if there's data at the head of the file AND its otherwise empty its probably junk.\n\t\tMemorySegment prefixData = archive.getPrefixData();\n\t\tif (prefixData != null && archive.getEnd() != null && archive.getParts().size() == 1) {\n\t\t\t// We'll throw as the caller should catch this case and handle it based on their needs.\n\t\t\tthrow new IOException(\"Content matched ZIP header but had no file entries\");\n\t\t}\n\n\t\t// Record prefix data to attribute held by the zip file info.\n\t\tif (prefixData != null) {\n\t\t\tZipPrefixDataProperty.set(zipInfo, MemorySegmentUtil.toByteArray(prefixData));\n\t\t}\n\n\t\t// Build model from the contained files in the ZIP\n\t\tint maxZipDepth = config.getMaxEmbeddedZipDepth().getValue();\n\t\ttry (ExecutorService service = config.doParallelize().getValue() ?\n\t\t\t\tThreadPoolFactory.newFixedThreadPool(\"zip-import\") :\n\t\t\t\tThreadPoolFactory.newSingleThreadExecutor(\"zip-import\")) {\n\t\t\tList<Callable<Void>> tasks = new ArrayList<>();\n\t\t\tList<LocalFileHeader> localFiles = archive.getLocalFiles();\n\t\t\tfor (int i = 0; i < localFiles.size(); i++) {\n\t\t\t\tint entryIndex = i;\n\t\t\t\tLocalFileHeader header = localFiles.get(i);\n\t\t\t\ttasks.add(() -> {\n\t\t\t\t\tLocalFileHeaderSource headerSource = new LocalFileHeaderSource(header, isAndroid);\n\t\t\t\t\tString entryName = header.getFileNameAsString();\n\n\t\t\t\t\t// Skip directories. There is no such thing as a 'directory' entry in ZIP files.\n\t\t\t\t\t// The only thing we can say is that if it ends with a '/' and has no data associated with it,\n\t\t\t\t\t// then it is probably a directory.\n\t\t\t\t\tif (entryName.endsWith(\"/\") && Unchecked.getOr(headerSource::isEmpty, false))\n\t\t\t\t\t\treturn null;\n\n\t\t\t\t\t// Read the value of the entry to figure out how to handle adding it to the resource builder.\n\t\t\t\t\tInfo info;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tinfo = infoImporter.readInfo(entryName, headerSource);\n\t\t\t\t\t\tZipEntryIndexProperty.set(info, entryIndex);\n\t\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\t\tlogger.error(\"IO error reading ZIP entry '{}' - skipping\", entryName, ex);\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Record common entry attributes\n\t\t\t\t\tZipCompressionProperty.set(info, header.getCompressionMethod());\n\t\t\t\t\tExtraFieldTime.TimeWrapper extraTimes = ExtraFieldTime.read(header);\n\t\t\t\t\tCentralDirectoryFileHeader centralHeader = header.getLinkedDirectoryFileHeader();\n\t\t\t\t\tif (centralHeader != null) {\n\t\t\t\t\t\tif (centralHeader.getFileCommentLength() > 0)\n\t\t\t\t\t\t\tZipCommentProperty.set(info, centralHeader.getFileCommentAsString());\n\t\t\t\t\t\tif (extraTimes == null)\n\t\t\t\t\t\t\textraTimes = ExtraFieldTime.read(centralHeader);\n\t\t\t\t\t}\n\t\t\t\t\tif (extraTimes != null) {\n\t\t\t\t\t\tZipCreationTimeProperty.set(info, extraTimes.getCreationMs());\n\t\t\t\t\t\tZipModificationTimeProperty.set(info, extraTimes.getModifyMs());\n\t\t\t\t\t\tZipAccessTimeProperty.set(info, extraTimes.getAccessMs());\n\t\t\t\t\t}\n\n\t\t\t\t\t// Skipping ZIP bombs\n\t\t\t\t\tif (info.isFile() && info.asFile().isZipFile()) {\n\t\t\t\t\t\tZipFileInfo zipFile = info.asFile().asZipFile();\n\t\t\t\t\t\tif (Arrays.equals(zipFile.getRawContent(), zipInfo.getRawContent())) {\n\t\t\t\t\t\t\tlogger.warn(\"Skip self-extracting ZIP bomb: {}\", entryName);\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t} else if (Arrays.stream(Thread.currentThread().getStackTrace())\n\t\t\t\t\t\t\t\t.filter(trace -> trace.getMethodName().equals(\"handleZip\"))\n\t\t\t\t\t\t\t\t.count() > maxZipDepth) {\n\t\t\t\t\t\t\tlogger.warn(\"Skip extracting embedded ZIP after {} levels: {}\", maxZipDepth, entryName);\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Add the info to the appropriate bundle\n\t\t\t\t\taddInfo(classes, files, androidClassBundles, versionedJvmClassBundles, embeddedResources,\n\t\t\t\t\t\t\theaderSource, entryName, info);\n\t\t\t\t\treturn null;\n\t\t\t\t});\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tservice.invokeAll(tasks);\n\t\t\t} catch (InterruptedException ex) {\n\t\t\t\tthrow new IOException(\"Zip import interrupted\", ex);\n\t\t\t}\n\t\t}\n\t\treturn builder\n\t\t\t\t.withJvmClassBundle(classes)\n\t\t\t\t.withAndroidClassBundles(androidClassBundles)\n\t\t\t\t.withVersionedJvmClassBundles(versionedJvmClassBundles)\n\t\t\t\t.withFileBundle(files)\n\t\t\t\t.withEmbeddedResources(embeddedResources)\n\t\t\t\t.withFileInfo(zipInfo)\n\t\t\t\t.build();\n\t}\n\n\t@Nonnull\n\tprivate WorkspaceDirectoryResource handleDirectory(@Nonnull WorkspaceResourceBuilder builder, @Nonnull Path directoryPath) throws IOException {\n\t\tlogger.info(\"Reading input from directory '{}'\", directoryPath);\n\t\tBasicJvmClassBundle classes = new BasicJvmClassBundle();\n\t\tBasicFileBundle files = new BasicFileBundle();\n\t\tMap<String, AndroidClassBundle> androidClassBundles = new ConcurrentHashMap<>();\n\t\tNavigableMap<Integer, VersionedJvmClassBundle> versionedJvmClassBundles = Collections.synchronizedNavigableMap(new TreeMap<>());\n\t\tMap<String, WorkspaceFileResource> embeddedResources = new ConcurrentHashMap<>();\n\n\t\t// Walk the directory\n\t\ttry (ExecutorService service = config.doParallelize().getValue() ?\n\t\t\t\tThreadPoolFactory.newFixedThreadPool(\"directory-import\") :\n\t\t\t\tThreadPoolFactory.newSingleThreadExecutor(\"directory-import\")) {\n\t\t\tList<Callable<Void>> tasks = new ArrayList<>();\n\t\t\tFiles.walkFileTree(directoryPath, Set.of(FileVisitOption.FOLLOW_LINKS), MAX_WALK_DEPTH, new SymlinkFollowingVisitor(directoryPath, MAX_WALK_DEPTH, path -> {\n\t\t\t\ttasks.add(() -> {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tPath file = ShortcutUtil.follow(path, MAX_WALK_DEPTH);\n\n\t\t\t\t\t\t// Read info from file (relative to the root directory)\n\t\t\t\t\t\t// - NIO insists both paths be of the same kind (relative vs absolute) so make both absolute.\n\t\t\t\t\t\tByteSource source = ByteSources.forPath(file);\n\t\t\t\t\t\tString fileName = directoryPath.toAbsolutePath().relativize(file.toAbsolutePath()).toString();\n\t\t\t\t\t\tif (File.separator.equals(\"\\\\\"))\n\t\t\t\t\t\t\tfileName = fileName.replace('\\\\', '/');\n\t\t\t\t\t\tInfo info = infoImporter.readInfo(fileName, source);\n\n\t\t\t\t\t\t// Add the info to the appropriate bundle\n\t\t\t\t\t\taddInfo(classes, files, androidClassBundles, versionedJvmClassBundles, embeddedResources,\n\t\t\t\t\t\t\t\tsource, fileName, info);\n\t\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\t\tlogger.error(\"IO error walking directory entry '{}' - skipping\", path, ex);\n\t\t\t\t\t}\n\t\t\t\t\treturn null;\n\t\t\t\t});\n\t\t\t}));\n\t\t\ttry {\n\t\t\t\tservice.invokeAll(tasks);\n\t\t\t} catch (InterruptedException ex) {\n\t\t\t\tthrow new IOException(\"Directory import interrupted\", ex);\n\t\t\t}\n\t\t}\n\t\treturn builder\n\t\t\t\t.withJvmClassBundle(classes)\n\t\t\t\t.withAndroidClassBundles(androidClassBundles)\n\t\t\t\t.withVersionedJvmClassBundles(versionedJvmClassBundles)\n\t\t\t\t.withFileBundle(files)\n\t\t\t\t.withEmbeddedResources(embeddedResources)\n\t\t\t\t.withDirectoryPath(directoryPath)\n\t\t\t\t.build();\n\t}\n\n\tprivate void addInfo(@Nonnull BasicJvmClassBundle classes,\n\t                     @Nonnull BasicFileBundle files,\n\t                     @Nonnull Map<String, AndroidClassBundle> androidClassBundles,\n\t                     @Nonnull NavigableMap<Integer, VersionedJvmClassBundle> versionedJvmClassBundles,\n\t                     @Nonnull Map<String, WorkspaceFileResource> embeddedResources,\n\t                     @Nonnull ByteSource infoSource,\n\t                     @Nonnull String pathName,\n\t                     @Nonnull Info info) {\n\t\tif (info.isClass()) {\n\t\t\taddClassInfo(classes, files, versionedJvmClassBundles, pathName, info);\n\t\t} else if (info.isFile()) {\n\t\t\taddFileInfo(files, androidClassBundles, embeddedResources, infoSource, pathName, info);\n\t\t} else {\n\t\t\tthrow new IllegalStateException(\"Unknown info type: \" + info);\n\t\t}\n\t}\n\n\n\tprivate void addClassInfo(@Nonnull BasicJvmClassBundle classes,\n\t                          @Nonnull BasicFileBundle files,\n\t                          @Nonnull NavigableMap<Integer, VersionedJvmClassBundle> versionedJvmClassBundles,\n\t                          @Nonnull String pathName,\n\t                          @Nonnull Info info) {\n\t\t// Must be a JVM class since Android classes do not exist in single-file form.\n\t\tJvmClassInfo classInfo = info.asClass().asJvmClass();\n\t\tString className = classInfo.getName();\n\n\t\t// JVM edge case allows trailing '/' for class entries in JARs.\n\t\t// We're going to normalize that away.\n\t\tif (pathName.endsWith(\".class/\")) {\n\t\t\tpathName = pathName.replace(\".class/\", \".class\");\n\t\t}\n\n\t\t// Record the class name, including path suffix/prefix.\n\t\t// If the name is totally different, record the original path name.\n\t\tint index = pathName.indexOf(className);\n\t\tif (index >= 0) {\n\t\t\t// Class name is within the entry name.\n\t\t\t// Record the prefix before the class name, and suffix after it (extension).\n\t\t\tif (index > 0) {\n\t\t\t\tString prefix = pathName.substring(0, index);\n\t\t\t\tPathPrefixProperty.set(classInfo, prefix);\n\t\t\t}\n\t\t\tint suffixIndex = index + className.length();\n\t\t\tif (suffixIndex < pathName.length()) {\n\t\t\t\tString suffix = pathName.substring(suffixIndex);\n\t\t\t\tPathSuffixProperty.set(classInfo, suffix);\n\t\t\t}\n\t\t} else {\n\t\t\t// Class name doesn't match entry name.\n\t\t\tPathOriginalNameProperty.set(classInfo, pathName);\n\t\t}\n\n\t\t// First we must handle edge cases. Up first, we'll look at multi-release jar prefixes.\n\t\tif (pathName.startsWith(JarFileInfo.MULTI_RELEASE_PREFIX) &&\n\t\t\t\t!className.startsWith(JarFileInfo.MULTI_RELEASE_PREFIX)) {\n\t\t\tString versionName = \"<null>\";\n\t\t\ttry {\n\t\t\t\t// Extract version from '<prefix>/version/<class-name>' pattern\n\t\t\t\tint startOffset = JarFileInfo.MULTI_RELEASE_PREFIX.length();\n\t\t\t\tint slashIndex = pathName.indexOf('/', startOffset);\n\t\t\t\tif (slashIndex < 0)\n\t\t\t\t\tthrow new NumberFormatException(\"Version name is null\");\n\t\t\t\tversionName = pathName.substring(startOffset, slashIndex);\n\n\t\t\t\t// Only add if the names match\n\t\t\t\tint classStart = slashIndex + 1;\n\t\t\t\tint classEnd = pathName.length() - \".class\".length();\n\t\t\t\tif (classEnd > classStart) {\n\t\t\t\t\tString classPath = pathName.substring(classStart, classEnd);\n\t\t\t\t\tif (!classPath.equals(className))\n\t\t\t\t\t\tthrow new IllegalArgumentException(\"Class in multi-release directory\" +\n\t\t\t\t\t\t\t\t\" does not match it's declared class name: \" + classPath);\n\t\t\t\t} else {\n\t\t\t\t\tthrow new IllegalArgumentException(\"Class in multi-release directory \" +\n\t\t\t\t\t\t\t\"does not end in '.class'\");\n\t\t\t\t}\n\n\t\t\t\t// Put it into the correct versioned class bundle.\n\t\t\t\tint version = Integer.parseInt(versionName);\n\t\t\t\tBasicJvmClassBundle bundle = (BasicJvmClassBundle) versionedJvmClassBundles\n\t\t\t\t\t\t.computeIfAbsent(version, BasicVersionedJvmClassBundle::new);\n\n\t\t\t\t// Handle duplicate classes.\n\t\t\t\t// noinspection all\n\t\t\t\tsynchronized (bundle) {\n\t\t\t\t\tJvmClassInfo existingClass = bundle.get(className);\n\t\t\t\t\tif (existingClass != null) {\n\t\t\t\t\t\tdeduplicateClass(existingClass, classInfo, bundle, files);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tVersionedClassProperty.set(classInfo, version);\n\t\t\t\t\t\tbundle.initialPut(classInfo);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (NumberFormatException ex) {\n\t\t\t\t// Version is invalid, record it as a file instead.\n\t\t\t\tlogger.warn(\"Class entry seemed to be for multi-release jar, \" +\n\t\t\t\t\t\t\"but version is non-numeric value: \" + versionName);\n\n\t\t\t\t// Override the prior value.\n\t\t\t\t// The JVM always selects the last option if there are duplicates.\n\t\t\t\tfiles.initialPut(new FileInfoBuilder<>()\n\t\t\t\t\t\t.withName(pathName)\n\t\t\t\t\t\t.withRawContent(classInfo.getBytecode())\n\t\t\t\t\t\t.build());\n\t\t\t} catch (IllegalArgumentException ex) {\n\t\t\t\t// Class name doesn't match what is declared locally in the versioned folder.\n\t\t\t\tlogger.warn(\"Class entry seemed to be for multi-release jar, \" +\n\t\t\t\t\t\t\"but the name doesn't align with the declared type: \" + pathName);\n\n\t\t\t\t// Override the prior value.\n\t\t\t\t// The JVM always selects the last option if there are duplicates.\n\t\t\t\tfiles.initialPut(new FileInfoBuilder<>()\n\t\t\t\t\t\t.withName(pathName)\n\t\t\t\t\t\t.withRawContent(classInfo.getBytecode())\n\t\t\t\t\t\t.build());\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle duplicate classes.\n\t\t// noinspection all\n\t\tsynchronized (classes) {\n\t\t\tJvmClassInfo existingClass = classes.get(className);\n\t\t\tif (existingClass != null) {\n\t\t\t\tdeduplicateClass(existingClass, classInfo, classes, files);\n\t\t\t} else {\n\t\t\t\tclasses.initialPut(classInfo);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void addFileInfo(@Nonnull BasicFileBundle files,\n\t                         @Nonnull Map<String, AndroidClassBundle> androidClassBundles,\n\t                         @Nonnull Map<String, WorkspaceFileResource> embeddedResources,\n\t                         @Nonnull ByteSource infoSource,\n\t                         @Nonnull String pathName,\n\t                         @Nonnull Info info) {\n\t\tFileInfo fileInfo = info.asFile();\n\n\t\t// Check for special file cases (Currently just DEX)\n\t\tif (fileInfo instanceof DexFileInfo) {\n\t\t\ttry {\n\t\t\t\tAndroidClassBundle dexBundle = DexIOUtil.read(infoSource);\n\t\t\t\tandroidClassBundles.put(pathName, dexBundle);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Failed to read embedded DEX '{}'\", pathName, t);\n\t\t\t\tfiles.initialPut(fileInfo);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for container file cases (Any ZIP type, JAR/WAR/etc)\n\t\tif (fileInfo.isZipFile()) {\n\t\t\ttry {\n\t\t\t\tWorkspaceFileResourceBuilder embeddedResourceBuilder = new WorkspaceFileResourceBuilder()\n\t\t\t\t\t\t.withFileInfo(fileInfo);\n\t\t\t\tWorkspaceFileResource embeddedResource = handleZip(embeddedResourceBuilder,\n\t\t\t\t\t\tfileInfo.asZipFile(), infoSource);\n\t\t\t\tembeddedResources.put(pathName, embeddedResource);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Failed to read embedded ZIP '{}'\", pathName, t);\n\t\t\t\tfiles.initialPut(fileInfo);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for other edge case types containing embedded content.\n\t\tif (fileInfo instanceof ModulesFileInfo) {\n\t\t\ttry {\n\t\t\t\tWorkspaceResourceBuilder embeddedResourceBuilder = new WorkspaceResourceBuilder()\n\t\t\t\t\t\t.withFileInfo(fileInfo);\n\t\t\t\tWorkspaceFileResource embeddedResource =\n\t\t\t\t\t\t(WorkspaceFileResource) handleModules(embeddedResourceBuilder, (ModulesFileInfo) fileInfo);\n\t\t\t\tembeddedResources.put(pathName, embeddedResource);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Failed to read embedded ZIP '{}'\", pathName, t);\n\t\t\t\tfiles.initialPut(fileInfo);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Warn if there are duplicate file entries.\n\t\t// Same cases for why this may occur are described above when handling classes.\n\t\t// The JVM will always use the last item for duplicate entries anyways.\n\t\tsynchronized (files) {\n\t\t\tFileInfo existingFile = files.get(pathName);\n\t\t\tif (existingFile != null) {\n\t\t\t\tint existingIndex = ZipEntryIndexProperty.getOr(existingFile, -1);\n\t\t\t\tint newIndex = ZipEntryIndexProperty.getOr(fileInfo, -1);\n\t\t\t\tif (newIndex < existingIndex)\n\t\t\t\t\treturn;\n\t\t\t\tlogger.warn(\"Multiple duplicate entries for file '{}', dropping older entry\", pathName);\n\t\t\t}\n\n\t\t\t// Store in bundle.\n\t\t\tfiles.initialPut(fileInfo);\n\t\t}\n\t}\n\n\t/**\n\t * Should <i>ONLY</i> be called if there is an existing duplicate/conflict in the given JVM class bundle.\n\t *\n\t * @param existingClass\n\t * \t\tPrior class entry in the class bundle.\n\t * @param currentClass\n\t * \t\tNew entry to de-duplicate.\n\t * @param classes\n\t * \t\tTarget class bundle.\n\t * @param files\n\t * \t\tTarget file bundle for fallback item placement.\n\t */\n\tprivate void deduplicateClass(@Nonnull JvmClassInfo existingClass,\n\t                              @Nonnull JvmClassInfo currentClass,\n\t                              @Nonnull BasicJvmClassBundle classes,\n\t                              @Nonnull BasicFileBundle files) {\n\t\tString className = currentClass.getName();\n\t\tString existingPrefix = PathPrefixProperty.get(existingClass);\n\t\tString existingSuffix = PathSuffixProperty.get(existingClass);\n\t\tString existingOriginal = PathOriginalNameProperty.get(existingClass);\n\t\tString currentPrefix = PathPrefixProperty.get(currentClass);\n\t\tString currentSuffix = PathSuffixProperty.get(currentClass);\n\t\tString currentOriginal = PathOriginalNameProperty.get(currentClass);\n\n\t\t// The target names to use should we want to store the items as files\n\t\tString existingName = existingOriginal != null ? existingOriginal :\n\t\t\t\t(existingPrefix != null ? existingPrefix : \"\") + className +\n\t\t\t\t\t\t(existingSuffix != null ? existingSuffix : \"\");\n\t\tString currentName = currentOriginal != null ? currentOriginal :\n\t\t\t\t(currentPrefix != null ? currentPrefix : \"\") + className +\n\t\t\t\t\t\t(currentSuffix != null ? currentSuffix : \"\");\n\n\t\t// Check for literal duplicate ZIP entries.\n\t\tif (existingName.equals(currentName)) {\n\t\t\t// The new name is an exact match, but occurs later in the file.\n\t\t\t// Since the JVM prefers the last entry of a set of duplicates we will drop the prior value.\n\t\t\tlogger.warn(\"Dropping prior class duplicate, matched exact file path: {}\", className);\n\t\t\tclasses.initialPut(currentClass);\n\t\t\treturn;\n\t\t}\n\n\t\t// Ok, so the path names aren't the same.\n\t\t// We'll want to normalize the paths and compare them. Whichever is best fit to be the JVM class will be kept\n\t\t// in the classes bundle. The worse fit goes to the files bundle. If we aren't sure then the newest entry\n\t\t// lands in the JVM bundle.\n\n\t\t// Normalize prefix/suffix\n\t\tif (Objects.equals(existingPrefix, currentPrefix)) {\n\t\t\texistingPrefix = null;\n\t\t\tcurrentPrefix = null;\n\t\t}\n\t\tif (Objects.equals(existingSuffix, currentSuffix)) {\n\t\t\texistingSuffix = null;\n\t\t\tcurrentSuffix = null;\n\t\t}\n\n\t\t// Names to use for comparison purposes\n\t\tString cmpExistingName = existingOriginal != null ? existingOriginal :\n\t\t\t\t(existingPrefix != null ? existingPrefix : \"\") + className +\n\t\t\t\t\t\t(existingSuffix != null ? existingSuffix : \"\");\n\t\tString cmpCurrentName = currentOriginal != null ? currentOriginal :\n\t\t\t\t(currentPrefix != null ? currentPrefix : \"\") + className +\n\t\t\t\t\t\t(currentSuffix != null ? currentSuffix : \"\");\n\n\t\t// Try and get class names via the file paths and determine which is the best fit to the real class name.\n\t\tString commonPrefix = StringUtil.getCommonPrefix(cmpExistingName, cmpCurrentName);\n\t\tif (commonPrefix.startsWith(JarFileInfo.MULTI_RELEASE_PREFIX)) {\n\t\t\t// Class names start at the '<prefix>/<version>/'\n\t\t\tint i = commonPrefix.indexOf('/', JarFileInfo.MULTI_RELEASE_PREFIX.length()) + 1;\n\t\t\tcmpExistingName = cmpExistingName.substring(i);\n\t\t\tcmpCurrentName = cmpCurrentName.substring(i);\n\t\t} else if (!commonPrefix.isEmpty()) {\n\t\t\t// Class names should start at the common prefix minus the intersection of the class name\n\t\t\tcmpExistingName = commonPrefix + cmpExistingName.substring(commonPrefix.length());\n\t\t\tcmpCurrentName = commonPrefix + cmpCurrentName.substring(commonPrefix.length());\n\t\t}\n\n\t\t// Best fit checking\n\t\tif (cmpExistingName.equals(className + \".class\")) {\n\t\t\t// The existing class entry name IS the class name. Thus, the other (current) one does not match.\n\t\t\t// We will add the current one as a file instead, and keep the prior as a class.\n\t\t\tlogger.warn(\"Duplicate class '{}' found. The prior entry better aligns to class name so the new one \" +\n\t\t\t\t\t\"will be tracked as a file instead: {}\", className, currentName);\n\t\t\tfiles.initialPut(new FileInfoBuilder<>()\n\t\t\t\t\t.withName(currentName)\n\t\t\t\t\t.withRawContent(currentClass.getBytecode())\n\t\t\t\t\t.build());\n\t\t} else if (cmpCurrentName.equals(className + \".class\")) {\n\t\t\t// The current class entry name IS the class name. Thus, the other (prior) one does not match.\n\t\t\t// We will add the prior one as a file, and record this new one as a class\n\t\t\tlogger.warn(\"Duplicate class '{}' found. The new entry better aligns to class name so the prior one \" +\n\t\t\t\t\t\"will be tracked as a file instead: {}\", className, existingName);\n\t\t\tVersionedClassProperty.remove(existingClass);\n\t\t\tfiles.initialPut(new FileInfoBuilder<>()\n\t\t\t\t\t.withName(existingName)\n\t\t\t\t\t.withRawContent(existingClass.getBytecode())\n\t\t\t\t\t.build());\n\t\t\tclasses.initialPut(currentClass);\n\t\t} else {\n\t\t\t// Neither of them really follow the class name accurately. We'll just record the last one as the JVM class\n\t\t\t// because that more accurately follows JVM behavior.\n\t\t\tlogger.warn(\"Duplicate class '{}' found. Neither entry match their class names,\" +\n\t\t\t\t\t\" tracking the newer item as the JVM class and retargeting the old item as a file: {}\", className, existingName);\n\t\t\tVersionedClassProperty.remove(existingClass);\n\t\t\tfiles.initialPut(new FileInfoBuilder<>()\n\t\t\t\t\t.withName(existingName)\n\t\t\t\t\t.withRawContent(existingClass.getBytecode())\n\t\t\t\t\t.build());\n\t\t\tclasses.initialPut(currentClass);\n\t\t}\n\t}\n\n\tprivate WorkspaceResource handleModules(WorkspaceResourceBuilder builder, ModulesFileInfo moduleInfo) throws IOException {\n\t\tBasicJvmClassBundle classes = new BasicJvmClassBundle();\n\t\tBasicFileBundle files = new BasicFileBundle();\n\n\t\t// The file-info should have the absolute path set as a property.\n\t\t// We have to use a path because unless we implement our own module reader, the internal API\n\t\t// only provides reader access via a path item.\n\t\tPath pathToModuleFile = InputFilePathProperty.get(moduleInfo);\n\t\tif (pathToModuleFile == null)\n\t\t\tthrow new IOException(\"Content of modules can only be read from path, which has not been set for this model\");\n\t\tModulesIOUtil.stream(pathToModuleFile)\n\t\t\t\t.forEach(entry -> {\n\t\t\t\t\t// Follows the pattern: /<module-name>/<file-name>\n\t\t\t\t\t//  - entry extracts these values\n\t\t\t\t\tModulesIOUtil.Entry moduleEntry = entry.getElement();\n\t\t\t\t\tByteSource moduleFileSource = entry.getByteSource();\n\t\t\t\t\tInfo info;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tinfo = infoImporter.readInfo(moduleEntry.getFileName(), moduleFileSource);\n\t\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\t\tlogger.error(\"IO error reading modules entry '{}' - skipping\", moduleEntry.getOriginalPath());\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\t// Add to appropriate bundle.\n\t\t\t\t\t// Modules file only has two expected kinds of content, classes and generic files.\n\t\t\t\t\tif (info.isClass()) {\n\t\t\t\t\t\t// Modules file only contains JVM classes\n\t\t\t\t\t\tclasses.initialPut(info.asClass().asJvmClass());\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Anything else should be a general file\n\t\t\t\t\t\tfiles.initialPut(info.asFile());\n\t\t\t\t\t}\n\n\t\t\t\t\t// Record the original prefix '/<module-name>/' for the input\n\t\t\t\t\tPathPrefixProperty.set(info, \"/\" + moduleEntry.getModuleName() + \"/\");\n\t\t\t\t});\n\n\t\treturn builder\n\t\t\t\t.withJvmClassBundle(classes)\n\t\t\t\t.withFileBundle(files)\n\t\t\t\t.build();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic WorkspaceResource importResource(@Nonnull ByteSource source) throws IOException {\n\t\treturn handleSingle(new WorkspaceFileResourceBuilder(), \"unknown.dat\", source);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic WorkspaceResource importResource(@Nonnull Path path) throws IOException {\n\t\t// Load name/data from path, parse into resource.\n\t\tString absolutePath = StringUtil.pathToAbsoluteString(path);\n\t\tif (Files.isDirectory(path)) {\n\t\t\treturn handleDirectory(new WorkspaceFileResourceBuilder(), path);\n\t\t} else {\n\t\t\tByteSource byteSource = ByteSources.forPath(path);\n\t\t\treturn handleSingle(new WorkspaceFileResourceBuilder(), absolutePath, byteSource);\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic WorkspaceResource importResource(@Nonnull URL url) throws IOException {\n\t\t// Extract name from URL\n\t\tString path;\n\t\tif (url.getProtocol().equals(\"file\")) {\n\t\t\tpath = url.getFile();\n\t\t\tif (path.isEmpty())\n\t\t\t\tpath = url.toString();\n\t\t\tif (path.charAt(0) == '/')\n\t\t\t\tpath = path.substring(1);\n\t\t} else {\n\t\t\tpath = url.toString();\n\t\t}\n\n\t\t// Load content, parse into resource.\n\t\tbyte[] bytes = IOUtil.toByteArray(url.openStream());\n\t\tByteSource byteSource = ByteSources.wrap(bytes);\n\t\treturn handleSingle(new WorkspaceFileResourceBuilder(), path, byteSource);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ResourceImporterConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\t/**\n\t * Using {@link FileVisitOption#FOLLOW_LINKS} doesn't actually work for directories that are symlinks.\n\t * This visitor implementation manually handles directory symlinks by resolving them to their real paths\n\t * and walking them separately.\n\t */\n\tprivate static class SymlinkFollowingVisitor extends SimpleFileVisitor<Path> {\n\t\tprivate final Path rootDir;\n\t\tprivate final int maxDepthFromRoot;\n\t\tprivate final Consumer<Path> filePathConsumer;\n\n\t\tpublic SymlinkFollowingVisitor(@Nonnull Path rootDir, int maxDepthFromRoot, Consumer<Path> filePathConsumer) {\n\t\t\tthis.rootDir = rootDir;\n\t\t\tthis.maxDepthFromRoot = maxDepthFromRoot;\n\t\t\tthis.filePathConsumer = filePathConsumer;\n\t\t}\n\n\t\t@Override\n\t\tpublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\n\t\t\tif (!dir.equals(dir.toRealPath())) {\n\t\t\t\t// Java NIO is stupid and will not follow symbolic links of directories.\n\t\t\t\tint depth = rootDir.relativize(dir).getNameCount();\n\t\t\t\tint newDepth = maxDepthFromRoot - depth;\n\t\t\t\tif (newDepth > 0) {\n\t\t\t\t\tPath realDir = dir.toRealPath();\n\t\t\t\t\tFiles.walkFileTree(realDir, Set.of(FileVisitOption.FOLLOW_LINKS), newDepth, new SymlinkFollowingVisitor(realDir, newDepth, filePathConsumer));\n\t\t\t\t\treturn FileVisitResult.SKIP_SUBTREE;\n\t\t\t\t} else {\n\t\t\t\t\treturn FileVisitResult.SKIP_SUBTREE;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn FileVisitResult.CONTINUE;\n\t\t}\n\n\t\t@Override\n\t\tpublic FileVisitResult visitFile(@Nonnull Path path, @Nonnull BasicFileAttributes attrs) {\n\t\t\tfilePathConsumer.accept(path);\n\t\t\treturn FileVisitResult.CONTINUE;\n\t\t}\n\t}\n\n\tprivate record PathAndName(@Nullable Path path, @Nonnull String name) {\n\t\t@Nonnull\n\t\tprivate static PathAndName fromString(@Nonnull String pathName) {\n\t\t\tif (pathName.contains(\"://\")) {\n\t\t\t\t// Absolute URI paths\n\t\t\t\tif (pathName.startsWith(\"file://\")) {\n\t\t\t\t\treturn fromUriString(pathName);\n\t\t\t\t} else {\n\t\t\t\t\t// Probably something like \"https://foo.com/bar.zip\"\n\t\t\t\t\t// Try normalizing a simple name out of it if possible.\n\t\t\t\t\twhile (pathName.endsWith(\"/\")) pathName = pathName.substring(0, pathName.length() - 1);\n\t\t\t\t\tString name = pathName.substring(pathName.lastIndexOf('/') + 1);\n\t\t\t\t\tif (!name.matches(\"\\\\w+\")) name = \"remote\";\n\t\t\t\t\treturn new PathAndName(null, name);\n\t\t\t\t}\n\t\t\t} else if (pathName.startsWith(\"file:./\")) {\n\t\t\t\t// Relative URI paths\n\t\t\t\treturn fromUriString(pathName);\n\t\t\t} else {\n\t\t\t\t// Probably local file paths\n\t\t\t\tPath localPath;\n\t\t\t\ttry {\n\t\t\t\t\t// Try and resolve a file path to the give path-name.\n\t\t\t\t\t// In some cases the input name is a remote resource not covered by the block above,\n\t\t\t\t\t// so we don't really care if it fails. That just means it is a remote resource of some kind.\n\t\t\t\t\tlocalPath = Paths.get(pathName);\n\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\tlocalPath = null;\n\t\t\t\t}\n\t\t\t\treturn new PathAndName(localPath, pathName.substring(pathName.lastIndexOf('/') + 1));\n\t\t\t}\n\t\t}\n\n\t\t@Nonnull\n\t\tprivate static PathAndName fromUriString(@Nonnull String pathName) {\n\t\t\tString name;\n\t\t\tPath localPath;\n\t\t\tname = pathName.substring(pathName.lastIndexOf('/') + 1);\n\t\t\ttry {\n\t\t\t\t// Try and resolve a file path to the give path-name.\n\t\t\t\t// It should be an absolute path.\n\t\t\t\tlocalPath = Paths.get(URI.create(pathName));\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlocalPath = null;\n\t\t\t}\n\t\t\treturn new PathAndName(localPath, name);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/ByteArrayWorkspaceExportConsumer.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport java.io.IOException;\n\n/**\n * Export consumer to write to a {@code byte[]}. Only supports {@link WorkspaceOutputType#FILE}.\n *\n * @author Matt Coley\n */\npublic class ByteArrayWorkspaceExportConsumer implements WorkspaceExportConsumer {\n\tprivate byte[] output;\n\n\t@Override\n\tpublic void write(@Nonnull byte[] bytes) throws IOException {\n\t\tif (output == null)\n\t\t\toutput = bytes;\n\t\telse {\n\t\t\tint existingContentLength = output.length;\n\t\t\tint newContentLength = bytes.length;\n\t\t\tint mergedLength = existingContentLength + newContentLength;\n\t\t\tif (mergedLength < 0) // Overflow check\n\t\t\t\tthrow new IllegalStateException(\"Content too large to write to a single byte[]\");\n\t\t\tbyte[] newOutput = new byte[mergedLength];\n\t\t\tSystem.arraycopy(output, 0, newOutput, 0, existingContentLength);\n\t\t\tSystem.arraycopy(bytes, 0, newOutput, existingContentLength, newContentLength);\n\t\t\toutput = newOutput;\n\t\t}\n\t}\n\n\t@Override\n\tpublic void writeRelative(@Nonnull String relative, @Nonnull byte[] bytes) {\n\t\tthrow new IllegalStateException(\"Directory export not supported in byte-array export consumer\");\n\t}\n\n\t@Override\n\tpublic void commit() throws IOException {\n\t\t// no-op\n\t}\n\n\t/**\n\t * @return Output content. May be {@code null} if nothing was in the workspace to write.\n\t */\n\t@Nullable\n\tpublic byte[] getOutput() {\n\t\treturn output;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/ClassPatcher.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport java.io.IOException;\n\n/**\n * Service outline for patching intentionally malformed Java bytecode to be compliant with ASM.\n *\n * @author Matt Coley\n */\npublic interface ClassPatcher {\n\t/**\n\t * @param name\n\t * \t\tName given by user for logging purposes.\n\t * @param code\n\t * \t\tInput bytecode.\n\t *\n\t * @return Output filtered bytecode.\n\t *\n\t * @throws IOException\n\t * \t\tWhen an exception patching the bytecode occurs.\n\t */\n\t@Nonnull\n\tbyte[] patch(@Nullable String name, @Nonnull byte[] code) throws IOException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/InfoImporter.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.util.io.ByteSource;\n\nimport java.io.IOException;\n\n/**\n * Service outline for creating various {@link Info} types from a basic name, {@link ByteSource} pair.\n *\n * @author Matt Coley\n */\npublic interface InfoImporter extends Service {\n\tString SERVICE_ID = \"info-importer\";\n\n\t/**\n\t * @param name\n\t * \t\tName to pass for {@link Info#getName()} if it cannot be inferred from the content source.\n\t * @param source\n\t * \t\tSource of content to read data from.\n\t *\n\t * @return Info instance.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the content cannot be read.\n\t */\n\t@Nonnull\n\tInfo readInfo(@Nonnull String name, @Nonnull ByteSource source) throws IOException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/InfoImporterConfig.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link InfoImporter}\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class InfoImporterConfig extends BasicConfigContainer implements ServiceConfig {\n\tprivate final ObservableObject<ClassPatchMode> classPatchMode = new ObservableObject<>(ClassPatchMode.CHECK_BASIC_THEN_FILTER);\n\n\t@Inject\n\tpublic InfoImporterConfig() {\n\t\tsuper(ConfigGroups.SERVICE_IO, InfoImporter.SERVICE_ID + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"class-patch-mode\", ClassPatchMode.class, classPatchMode));\n\t}\n\n\t/**\n\t * You better know what you're doing if you choose a lower tier value on this.\n\t * The default is {@link ClassPatchMode#CHECK_BASIC_THEN_FILTER} because otherwise\n\t * there will be errors when invalid classes are found. However, in some cases\n\t * it may be beneficial to use {@link ClassPatchMode#ALWAYS_FILTER}.\n\t * Only use {@link ClassPatchMode#SKIP_FILTER} when looking at unobfuscated classes.\n\t *\n\t * @return Class patch validation mode.\n\t */\n\t@Nonnull\n\tpublic ClassPatchMode getClassPatchMode() {\n\t\treturn classPatchMode.getValue();\n\t}\n\n\t/**\n\t * Level of class pre-processing to take when importing {@link ClassInfo} types.\n\t */\n\tpublic enum ClassPatchMode {\n\t\t/**\n\t\t * Always pre-process classes.\n\t\t */\n\t\tALWAYS_FILTER,\n\t\t/**\n\t\t * Check thoroughly for problems in class files before pre-processing them.\n\t\t */\n\t\tCHECK_ADVANCED_THEN_FILTER,\n\t\t/**\n\t\t * Check quickly for problems in class files before pre-processing them.\n\t\t */\n\t\tCHECK_BASIC_THEN_FILTER,\n\t\t/**\n\t\t * Do not pre-process classes.\n\t\t */\n\t\tSKIP_FILTER\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/PathWorkspaceExportConsumer.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\n\n/**\n * Export consumer to write to a given {@link Path}, either as a single file or as the root of a directory of items.\n *\n * @author Matt Coley\n */\npublic class PathWorkspaceExportConsumer implements WorkspaceExportConsumer {\n\tprivate final Path path;\n\tprivate boolean firstSingleWrite = true;\n\n\t/**\n\t * @param path\n\t * \t\tPath to write to.\n\t */\n\tpublic PathWorkspaceExportConsumer(@Nonnull Path path) {\n\t\tthis.path = path;\n\t}\n\n\t@Override\n\tpublic void write(@Nonnull byte[] bytes) throws IOException {\n\t\tif (firstSingleWrite) {\n\t\t\tFiles.write(path, bytes);\n\t\t\tfirstSingleWrite = false;\n\t\t} else {\n\t\t\tFiles.write(path, bytes, StandardOpenOption.APPEND);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void writeRelative(@Nonnull String relativePath, @Nonnull byte[] bytes) throws IOException {\n\t\tPath destination = path.resolve(relativePath);\n\t\tPath parent = destination.getParent();\n\t\tif (!Files.isDirectory(parent))\n\t\t\tFiles.createDirectories(parent);\n\t\tFiles.write(destination, bytes);\n\t}\n\n\t@Override\n\tpublic void commit() throws IOException {\n\t\t// No-need to do anything\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/ResourceImporter.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.io.ByteSource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URL;\nimport java.nio.file.Path;\n\n/**\n * Service outline for supporting creation of {@link WorkspaceResource} instances.\n *\n * @author Matt Coley\n */\npublic interface ResourceImporter {\n\tString SERVICE_ID = \"resource-importer\";\n\n\t/**\n\t * @param source\n\t * \t\tSome generic content source.\n\t *\n\t * @return Workspace resource representing the content.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the content cannot be read from.\n\t */\n\t@Nonnull\n\tWorkspaceResource importResource(@Nonnull ByteSource source) throws IOException;\n\n\t/**\n\t * @param file\n\t * \t\tFile/directory to import from.\n\t *\n\t * @return Workspace resource representing the file/directory.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the content at the file path cannot be read from.\n\t */\n\t@Nonnull\n\tdefault WorkspaceResource importResource(@Nonnull File file) throws IOException {\n\t\treturn importResource(file.toPath());\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tFile/directory path to import from.\n\t *\n\t * @return Workspace resource representing the file/directory.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the content at the file path cannot be read from.\n\t */\n\t@Nonnull\n\tWorkspaceResource importResource(@Nonnull Path path) throws IOException;\n\n\t/**\n\t * @param url\n\t * \t\tURL to content to import from.\n\t *\n\t * @return Workspace resource representing the remote content.\n\t *\n\t * @throws IOException\n\t * \t\tWhen content from the URL cannot be accessed.\n\t */\n\t@Nonnull\n\tWorkspaceResource importResource(@Nonnull URL url) throws IOException;\n\n\t/**\n\t * @param uri\n\t * \t\tURI to content to import from.\n\t *\n\t * @return Workspace resource representing the remote content.\n\t *\n\t * @throws IOException\n\t * \t\tWhen reading from the URI fails either due to a malformed URI,\n\t * \t\tor the content being inaccessible.\n\t */\n\t@Nonnull\n\tdefault WorkspaceResource importResource(@Nonnull URI uri) throws IOException {\n\t\treturn importResource(uri.toURL());\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/ResourceImporterConfig.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.collections.func.UncheckedFunction;\nimport software.coley.lljzip.ZipIO;\nimport software.coley.lljzip.format.ZipPatterns;\nimport software.coley.lljzip.format.model.CentralDirectoryFileHeader;\nimport software.coley.lljzip.format.model.LocalFileHeader;\nimport software.coley.lljzip.format.model.ZipArchive;\nimport software.coley.lljzip.format.read.ForwardScanZipReader;\nimport software.coley.lljzip.format.read.JvmZipReader;\nimport software.coley.lljzip.format.read.NaiveLocalFileZipReader;\nimport software.coley.lljzip.format.read.SimpleZipPartAllocator;\nimport software.coley.lljzip.format.read.ZipPartAllocator;\nimport software.coley.lljzip.util.MemorySegmentUtil;\nimport software.coley.lljzip.util.data.MemorySegmentData;\nimport software.coley.lljzip.util.data.StringData;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\nimport java.lang.foreign.MemorySegment;\n\nimport static software.coley.lljzip.util.MemorySegmentUtil.readLongSlice;\n\n/**\n * Config for {@link ResourceImporter}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ResourceImporterConfig extends BasicConfigContainer implements ServiceConfig {\n\tprivate final ObservableObject<ZipStrategy> zipStrategy = new ObservableObject<>(ZipStrategy.JVM);\n\tprivate final ObservableBoolean skipRevisitedCenToLocalLinks = new ObservableBoolean(true);\n\tprivate final ObservableBoolean allowBasicJvmBaseOffsetZeroCheck = new ObservableBoolean(true);\n\tprivate final ObservableBoolean ignoreFileLengths = new ObservableBoolean(false);\n\tprivate final ObservableBoolean adoptStandardCenFileNames = new ObservableBoolean(false);\n\tprivate final ObservableInteger maxEmbeddedZipDepth = new ObservableInteger(3);\n\tprivate final ObservableBoolean parallelize = new ObservableBoolean(true);\n\n\t@Inject\n\tpublic ResourceImporterConfig() {\n\t\tsuper(ConfigGroups.SERVICE_IO, ResourceImporter.SERVICE_ID + CONFIG_SUFFIX);\n\n\t\taddValue(new BasicConfigValue<>(\"zip-strategy\", ZipStrategy.class, zipStrategy));\n\t\taddValue(new BasicConfigValue<>(\"skip-revisited-cen-to-local-links\", boolean.class, skipRevisitedCenToLocalLinks));\n\t\taddValue(new BasicConfigValue<>(\"allow-basic-base-offset-zero-check\", boolean.class, allowBasicJvmBaseOffsetZeroCheck));\n\t\taddValue(new BasicConfigValue<>(\"ignore-file-lengths\", boolean.class, ignoreFileLengths));\n\t\taddValue(new BasicConfigValue<>(\"adapt-standard-cen-file-names\", boolean.class, adoptStandardCenFileNames));\n\t\taddValue(new BasicConfigValue<>(\"max-embedded-zip-depth\", int.class, maxEmbeddedZipDepth));\n\t\taddValue(new BasicConfigValue<>(\"parallelize\", boolean.class, parallelize));\n\t}\n\n\t/**\n\t * @return ZIP strategy.\n\t */\n\t@Nonnull\n\tpublic ObservableObject<ZipStrategy> getZipStrategy() {\n\t\treturn zipStrategy;\n\t}\n\n\t/**\n\t * When the {@link #getZipStrategy() ZIP strategy} is {@link ZipStrategy#JVM} this allows toggling\n\t * skipping <i>\"duplicate\"</i> entries where multiple {@link CentralDirectoryFileHeader} can point to the\n\t * same offset <i>({@link LocalFileHeader})</i>. Skipping is {@code true} by default.\n\t *\n\t * @return {@code true} when skipping N-to-1 mapping of\n\t * {@link CentralDirectoryFileHeader} to {@link LocalFileHeader} for {@link ZipStrategy#JVM}.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getSkipRevisitedCenToLocalLinks() {\n\t\treturn skipRevisitedCenToLocalLinks;\n\t}\n\n\t/**\n\t * When the {@link #getZipStrategy() ZIP strategy} is {@link ZipStrategy#JVM} this allows toggling\n\t * how the JVM base offset of the zip file is calculated. Normally the start of a ZIP file is calculated\n\t * based off the logic in {@code ZipFile.Source#findEND()} which looks like:\n\t * <pre>{@code\n\t *  // ENDSIG matched, however the size of file comment in it does\n\t *  // not match the real size. One \"common\" cause for this problem\n\t *  // is some \"extra\" bytes are padded at the end of the zipfile.\n\t *  // Let's do some extra verification, we don't care about the\n\t *  // performance in this situation.\n\t *  byte[] sbuf = new byte[4];\n\t *  long cenpos = end.endpos - end.cenlen;\n\t *  long locpos = cenpos - end.cenoff;\n\t * }</pre>\n\t * In some edge cases this results in {@code locpos} being {@code > 0} even when the file has no prefix/padding.\n\t *\n\t * @return {@code true} when defaulting to check for zero being the base JVM zip offset instead of the lookup\n\t * based on the code in {@code ZipFile.Source#findEND()}.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getAllowBasicJvmBaseOffsetZeroCheck() {\n\t\treturn allowBasicJvmBaseOffsetZeroCheck;\n\t}\n\n\t/**\n\t * Post-processes naively read zip archives to expand file data to the next zip structure boundary.\n\t * This is necessary in some cases where an archive composed entirely of local file headers has bogus\n\t * file length values <i>(such as zero)</i> when file data appears after the file name in the zip structure.\n\t *\n\t * @return {@code true} to ignore the reported file lengths when using {@link ZipStrategy#NAIVE}.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getIgnoreFileLengths() {\n\t\treturn ignoreFileLengths;\n\t}\n\n\t/**\n\t * @return Maximum level of embedded resources to populate.\n\t * Any further embedded contents will be treated as arbitrary binary files.\n\t */\n\t@Nonnull\n\tpublic ObservableInteger getMaxEmbeddedZipDepth() {\n\t\treturn maxEmbeddedZipDepth;\n\t}\n\n\t/**\n\t * @return {@code true} to enable parallelization of import logic.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean doParallelize() {\n\t\treturn parallelize;\n\t}\n\n\t/**\n\t * @return Mapping of input bytes to a ZIP archive model.\n\t */\n\t@Nonnull\n\tpublic UncheckedFunction<byte[], ZipArchive> mapping() {\n\t\tZipStrategy strategy = zipStrategy.getValue();\n\t\tif (strategy == ZipStrategy.JVM)\n\t\t\treturn newJvmMapping();\n\t\tif (strategy == ZipStrategy.STANDARD)\n\t\t\treturn newStandardMapping();\n\t\treturn newNaiveMapping();\n\t}\n\n\t@Nonnull\n\tprivate UncheckedFunction<byte[], ZipArchive> newNaiveMapping() {\n\t\treturn input -> ZipIO.read(input, new NaiveLocalFileZipReader(newPartAllocator()));\n\t}\n\n\t@Nonnull\n\tprivate UncheckedFunction<byte[], ZipArchive> newStandardMapping() {\n\t\treturn input -> ZipIO.read(input, new ForwardScanZipReader(newPartAllocator()) {\n\t\t\t@Override\n\t\t\tpublic void postProcessLocalFileHeader(@Nonnull LocalFileHeader file) {\n\t\t\t\tif (adoptStandardCenFileNames.getValue()) {\n\t\t\t\t\tCentralDirectoryFileHeader directoryFileHeader = file.getLinkedDirectoryFileHeader();\n\t\t\t\t\tif (directoryFileHeader != null)\n\t\t\t\t\t\tfile.setFileName(StringData.of(directoryFileHeader.getFileNameAsString()));\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t@Nonnull\n\tprivate UncheckedFunction<byte[], ZipArchive> newJvmMapping() {\n\t\treturn input -> ZipIO.read(input, new JvmZipReader(skipRevisitedCenToLocalLinks.getValue(), allowBasicJvmBaseOffsetZeroCheck.getValue()));\n\t}\n\n\t/**\n\t * @return Part allocator for Naive/Standard strategies.\n\t */\n\t@Nonnull\n\tprivate ZipPartAllocator newPartAllocator() {\n\t\tif (ignoreFileLengths.getValue()) {\n\t\t\treturn new SimpleZipPartAllocator() {\n\t\t\t\t@Nonnull\n\t\t\t\t@Override\n\t\t\t\tpublic LocalFileHeader newLocalFileHeader() {\n\t\t\t\t\treturn new LocalFileHeader() {\n\t\t\t\t\t\t@Nonnull\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tprotected MemorySegmentData readFileData(@Nonnull MemorySegment data, long headerOffset) {\n\t\t\t\t\t\t\tlong localOffset = MIN_FIXED_SIZE + getFileNameLength() + getExtraFieldLength();\n\t\t\t\t\t\t\tlong nextStart = MemorySegmentUtil.indexOfQuad(data, headerOffset + localOffset, ZipPatterns.LOCAL_FILE_HEADER_QUAD);\n\t\t\t\t\t\t\tlong fileDataLength = nextStart > headerOffset ?\n\t\t\t\t\t\t\t\t\tnextStart - (headerOffset + localOffset) :\n\t\t\t\t\t\t\t\t\tdata.byteSize() - (headerOffset + localOffset);\n\t\t\t\t\t\t\treturn MemorySegmentData.of(readLongSlice(data, headerOffset, localOffset, fileDataLength));\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\treturn new SimpleZipPartAllocator();\n\t}\n\n\t/**\n\t * Mirrors strategies available in {@link ZipIO}.\n\t */\n\tpublic enum ZipStrategy {\n\t\tJVM,\n\t\tSTANDARD,\n\t\tNAIVE\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/WorkspaceCompressType.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.builtin.ZipCompressionProperty;\n\n/**\n * Compression option for ZIP/JAR outputs in {@link WorkspaceExportOptions}.\n *\n * @author Matt Coley\n */\npublic enum WorkspaceCompressType {\n\t/**\n\t * Match the original compression of a {@link Info} item by checking {@link ZipCompressionProperty}.\n\t * When unknown, defaults to enabling compression.\n\t */\n\tMATCH_ORIGINAL,\n\t/**\n\t * Compress items only when if it will yield more compact data.\n\t * Some smaller files do not compress well due to the overhead cost of the compression.\n\t */\n\tSMART,\n\t/**\n\t * Compress all items in the output.\n\t */\n\tALWAYS,\n\t/**\n\t * Do not compress any items in the output.\n\t */\n\tNEVER,\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/WorkspaceExportConsumer.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.io.IOException;\n\n/**\n * Outline of IO writing for {@link WorkspaceExporter} output.\n *\n * @author Matt Coley\n */\npublic interface WorkspaceExportConsumer {\n\t/**\n\t * Called when writing content to a single given location based on the implementation.\n\t * This may be called multiple times before {@link #commit()} is invoked.\n\t *\n\t * @param bytes\n\t * \t\tBytes to write/append to the output.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the content cannot be written to.\n\t */\n\tvoid write(@Nonnull byte[] bytes) throws IOException;\n\n\t/**\n\t * Called when writing content to a relative location based on the implementation.\n\t * This may be called multiple times for a given relative path before {@link #commit()} is invoked.\n\t *\n\t * @param relative\n\t * \t\tRelative path of content.\n\t * @param bytes\n\t * \t\tBytes to write/append to the given relative path.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the content cannot be written to.\n\t */\n\tvoid writeRelative(@Nonnull String relative, @Nonnull byte[] bytes) throws IOException;\n\n\t/**\n\t * Called when the export process is completed.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the content couldn't be committed.\n\t */\n\tvoid commit() throws IOException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/WorkspaceExportOptions.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.JarFileInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.ZipFileInfo;\nimport software.coley.recaf.info.properties.builtin.PathOriginalNameProperty;\nimport software.coley.recaf.info.properties.builtin.PathPrefixProperty;\nimport software.coley.recaf.info.properties.builtin.PathSuffixProperty;\nimport software.coley.recaf.info.properties.builtin.ZipAccessTimeProperty;\nimport software.coley.recaf.info.properties.builtin.ZipCommentProperty;\nimport software.coley.recaf.info.properties.builtin.ZipCompressionProperty;\nimport software.coley.recaf.info.properties.builtin.ZipCreationTimeProperty;\nimport software.coley.recaf.info.properties.builtin.ZipModificationTimeProperty;\nimport software.coley.recaf.info.properties.builtin.ZipPrefixDataProperty;\nimport software.coley.recaf.util.ZipCreationUtils;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.VersionedJvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.TreeMap;\nimport java.util.zip.DeflaterOutputStream;\n\nimport static software.coley.lljzip.format.compression.ZipCompressions.DEFLATED;\nimport static software.coley.lljzip.format.compression.ZipCompressions.STORED;\n\n/**\n * Options for configuring / preparing a {@link WorkspaceExporter}.\n *\n * @author Matt Coley\n */\npublic class WorkspaceExportOptions {\n\tprivate final WorkspaceCompressType compressType;\n\tprivate final WorkspaceOutputType outputType;\n\tprivate final WorkspaceExportConsumer consumer;\n\tprivate boolean bundleSupporting;\n\tprivate boolean createZipDirEntries;\n\n\t/**\n\t * @param outputType\n\t * \t\tType of output for contents.\n\t * @param consumer\n\t * \t\tConsumer to write to.\n\t */\n\tpublic WorkspaceExportOptions(@Nonnull WorkspaceOutputType outputType, @Nonnull WorkspaceExportConsumer consumer) {\n\t\tthis(WorkspaceCompressType.MATCH_ORIGINAL, outputType, consumer);\n\t}\n\n\t/**\n\t * @param compressType\n\t * \t\tCompression option for contents exported.\n\t * @param outputType\n\t * \t\tType of output for contents.\n\t * @param consumer\n\t * \t\tConsumer to write to.\n\t */\n\tpublic WorkspaceExportOptions(@Nonnull WorkspaceCompressType compressType, @Nonnull WorkspaceOutputType outputType,\n\t                              @Nonnull WorkspaceExportConsumer consumer) {\n\t\tthis.compressType = compressType;\n\t\tthis.outputType = outputType;\n\t\tthis.consumer = consumer;\n\t}\n\n\t/**\n\t * @param bundleSupporting\n\t *        {@code true} to bundle all {@link Workspace#getSupportingResources()} into the output.\n\t */\n\tpublic void setBundleSupporting(boolean bundleSupporting) {\n\t\tthis.bundleSupporting = bundleSupporting;\n\t}\n\n\t/**\n\t * @param createZipDirEntries\n\t *        {@code true} to create directory entries in the output ZIP.\n\t * \t\tDoes nothing when output type is a directory.\n\t */\n\tpublic void setCreateZipDirEntries(boolean createZipDirEntries) {\n\t\tthis.createZipDirEntries = createZipDirEntries;\n\t}\n\n\t/**\n\t * @return New exporter from current options.\n\t */\n\t@Nonnull\n\tpublic WorkspaceExporter create() {\n\t\treturn new WorkspaceExporterImpl();\n\t}\n\n\t/**\n\t * Basic implementation of {@link WorkspaceExporter} that pulls from the options defined here.\n\t */\n\tprivate class WorkspaceExporterImpl implements WorkspaceExporter {\n\t\tprivate final Map<String, byte[]> contents = new TreeMap<>();\n\t\tprivate final Map<String, Integer> compression = new HashMap<>();\n\t\tprivate final Map<String, String> comments = new HashMap<>();\n\t\tprivate final Map<String, Long> modifyTimes = new HashMap<>();\n\t\tprivate final Map<String, Long> createTimes = new HashMap<>();\n\t\tprivate final Map<String, Long> accessTimes = new HashMap<>();\n\t\tprivate byte[] prefix;\n\n\t\t@Override\n\t\tpublic void export(@Nonnull Workspace workspace) throws IOException {\n\t\t\tpopulate(workspace);\n\t\t\tswitch (outputType) {\n\t\t\t\tcase FILE:\n\t\t\t\t\t// Test if we're supposed to just write the file as-is instead of bundling it in an archive.\n\t\t\t\t\t//  - Must only have one thing to write\n\t\t\t\t\t//  - Workspace input must be a single non-archive file\n\t\t\t\t\tif (contents.size() == 1 &&\n\t\t\t\t\t\t\tworkspace.getPrimaryResource() instanceof WorkspaceFileResource primaryFileResource &&\n\t\t\t\t\t\t\t!(primaryFileResource.getFileInfo() instanceof ZipFileInfo)) {\n\t\t\t\t\t\tbyte[] data = contents.values().iterator().next();\n\t\t\t\t\t\tif (prefix != null)\n\t\t\t\t\t\t\tconsumer.write(prefix);\n\t\t\t\t\t\tconsumer.write(data);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Otherwise, lets make an archive.\n\t\t\t\t\tZipCreationUtils.ZipBuilder zipBuilder = ZipCreationUtils.builder();\n\t\t\t\t\tif (createZipDirEntries)\n\t\t\t\t\t\tzipBuilder = zipBuilder.createDirectories();\n\n\t\t\t\t\t// Final copy for lambda, write all contents to ZIP buffer.\n\t\t\t\t\tZipCreationUtils.ZipBuilder finalZipBuilder = zipBuilder;\n\t\t\t\t\tcontents.forEach((name, content) -> {\n\t\t\t\t\t\t// Cannot mirror exact compression type, so we'll just do binary \"is this compressed or nah?\"\n\t\t\t\t\t\tboolean compress = compression.getOrDefault(name, STORED) > STORED;\n\n\t\t\t\t\t\t// Other properties\n\t\t\t\t\t\tString comment = comments.getOrDefault(name, null);\n\t\t\t\t\t\tlong modifyTime = modifyTimes.getOrDefault(name, -1L);\n\t\t\t\t\t\tlong createTime = createTimes.getOrDefault(name, -1L);\n\t\t\t\t\t\tlong accessTime = accessTimes.getOrDefault(name, -1L);\n\n\t\t\t\t\t\t// Adding the entry\n\t\t\t\t\t\tfinalZipBuilder.add(name, content, compress, comment, createTime, modifyTime, accessTime);\n\t\t\t\t\t});\n\n\t\t\t\t\t// Write buffer to path\n\t\t\t\t\tif (prefix != null) {\n\t\t\t\t\t\tconsumer.write(prefix);\n\t\t\t\t\t\tconsumer.write(zipBuilder.bytes());\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsumer.write(zipBuilder.bytes());\n\t\t\t\t\t}\n\t\t\t\t\tconsumer.commit();\n\t\t\t\t\tbreak;\n\t\t\t\tcase DIRECTORY:\n\t\t\t\t\tfor (Map.Entry<String, byte[]> entry : contents.entrySet()) {\n\t\t\t\t\t\t// Write everything relative to the path\n\t\t\t\t\t\tString relativePath = entry.getKey();\n\t\t\t\t\t\tbyte[] content = entry.getValue();\n\t\t\t\t\t\tconsumer.writeRelative(relativePath, content);\n\t\t\t\t\t}\n\t\t\t\t\tconsumer.commit();\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * @param workspace\n\t\t * \t\tWorkspace to pull data from.\n\t\t */\n\t\tprivate void populate(@Nonnull Workspace workspace) {\n\t\t\t// If shading libs, they go first so the primary content will be the authoritative copy for\n\t\t\t// any duplicate paths held by both resources.\n\t\t\tif (bundleSupporting) {\n\t\t\t\tfor (WorkspaceResource supportingResource : workspace.getSupportingResources()) {\n\t\t\t\t\tmapInto(contents, supportingResource);\n\t\t\t\t}\n\t\t\t}\n\t\t\tWorkspaceResource primary = workspace.getPrimaryResource();\n\t\t\tmapInto(contents, primary);\n\n\t\t\t// If the resource had prefix data, get it here so that we can write it back later.\n\t\t\tif (primary instanceof WorkspaceFileResource resource)\n\t\t\t\tprefix = ZipPrefixDataProperty.get(resource.getFileInfo());\n\t\t}\n\n\t\t/**\n\t\t * Takes the contents of the given resource and puts them into the map.\n\t\t *\n\t\t * @param map\n\t\t * \t\tMap to collect values into.\n\t\t * @param resource\n\t\t * \t\tResource to pull values from.\n\t\t */\n\t\tprivate void mapInto(@Nonnull Map<String, byte[]> map, @Nonnull WorkspaceResource resource) {\n\t\t\t// Place classes into map\n\t\t\tresource.jvmClassBundleStream().forEach(bundle -> {\n\t\t\t\tfor (JvmClassInfo classInfo : bundle) {\n\t\t\t\t\tString key;\n\t\t\t\t\tString originalName = PathOriginalNameProperty.get(classInfo);\n\t\t\t\t\tif (originalName == null) {\n\t\t\t\t\t\tString pathPrefix = PathPrefixProperty.get(classInfo);\n\t\t\t\t\t\tString pathSuffix = Objects.requireNonNullElse(PathSuffixProperty.get(classInfo), \".class\");\n\t\t\t\t\t\tkey = classInfo.getName() + pathSuffix;\n\t\t\t\t\t\tif (pathPrefix != null)\n\t\t\t\t\t\t\tkey = pathPrefix + key;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tkey = originalName;\n\t\t\t\t\t}\n\t\t\t\t\tmap.put(key, classInfo.getBytecode());\n\t\t\t\t\tupdateProperties(key, classInfo);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Place versioned files into map\n\t\t\tfor (Map.Entry<Integer, VersionedJvmClassBundle> entry : resource.getVersionedJvmClassBundles().entrySet()) {\n\t\t\t\tString versionPath = JarFileInfo.MULTI_RELEASE_PREFIX + entry.getKey() + \"/\";\n\t\t\t\tfor (Map.Entry<String, JvmClassInfo> classEntry : entry.getValue().entrySet()) {\n\t\t\t\t\tString key = versionPath + classEntry.getKey() + \".class\";\n\t\t\t\t\tJvmClassInfo value = classEntry.getValue();\n\t\t\t\t\tmap.put(key, value.getBytecode());\n\t\t\t\t\tupdateProperties(key, value);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Rebuild Android DEX files and place into map\n\t\t\tfor (Map.Entry<String, AndroidClassBundle> entry : resource.getAndroidClassBundles().entrySet()) {\n\t\t\t\t// TODO: Need to write back DEX files\n\t\t\t}\n\n\t\t\t// Place files into map\n\t\t\tfor (FileInfo fileInfo : resource.getFileBundle()) {\n\t\t\t\tmap.put(fileInfo.getName(), fileInfo.getRawContent());\n\t\t\t\tupdateProperties(fileInfo.getName(), fileInfo);\n\t\t\t}\n\n\t\t\t// Recreate embedded resources as ZIP files with the original file paths\n\t\t\tfor (Map.Entry<String, WorkspaceFileResource> entry : resource.getEmbeddedResources().entrySet()) {\n\t\t\t\tString embeddedFilePath = entry.getKey();\n\t\t\t\tWorkspaceFileResource embeddedResource = entry.getValue();\n\t\t\t\tMap<String, byte[]> embeddedMap = new TreeMap<>();\n\t\t\t\tmapInto(embeddedMap, embeddedResource);\n\t\t\t\tbyte[] embeddedBytes = Unchecked.get(() -> ZipCreationUtils.createZip(embeddedMap));\n\t\t\t\tmap.put(embeddedFilePath, embeddedBytes);\n\t\t\t\tFileInfo embeddedFile = embeddedResource.getFileInfo();\n\t\t\t\tupdateProperties(embeddedFilePath, embeddedFile);\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * @param name\n\t\t * \t\tMap key.\n\t\t * @param info\n\t\t * \t\tInfo to pull properties from.\n\t\t */\n\t\tprivate void updateProperties(@Nonnull String name, @Nonnull Info info) {\n\t\t\tcompression.put(name, getCompression(info));\n\n\t\t\tLong createTime = ZipCreationTimeProperty.get(info);\n\t\t\tif (createTime != null)\n\t\t\t\tcreateTimes.put(name, createTime);\n\n\t\t\tLong modifyTime = ZipModificationTimeProperty.get(info);\n\t\t\tif (modifyTime != null)\n\t\t\t\tmodifyTimes.put(name, modifyTime);\n\n\t\t\tLong accessTime = ZipAccessTimeProperty.get(info);\n\t\t\tif (accessTime != null)\n\t\t\t\taccessTimes.put(name, accessTime);\n\n\t\t\tString comment = ZipCommentProperty.get(info);\n\t\t\tif (comment != null)\n\t\t\t\tcomments.put(name, comment);\n\t\t}\n\n\t\t/**\n\t\t * @param info\n\t\t * \t\tInfo to get compression for.\n\t\t *\n\t\t * @return Compression type for into value.\n\t\t */\n\t\tprivate int getCompression(@Nonnull Info info) {\n\t\t\tswitch (compressType) {\n\t\t\t\tcase ALWAYS:\n\t\t\t\t\treturn DEFLATED;\n\t\t\t\tcase NEVER:\n\t\t\t\t\treturn STORED;\n\t\t\t\tcase SMART:\n\t\t\t\t\t// Get content from info\n\t\t\t\t\tbyte[] content = null;\n\t\t\t\t\tif (info.isFile())\n\t\t\t\t\t\tcontent = info.asFile().getRawContent();\n\t\t\t\t\telse if (info.isClass()) {\n\t\t\t\t\t\tClassInfo classInfo = info.asClass();\n\t\t\t\t\t\tif (classInfo.isJvmClass())\n\t\t\t\t\t\t\tcontent = classInfo.asJvmClass().getBytecode();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Validate\n\t\t\t\t\tif (content == null)\n\t\t\t\t\t\tthrow new IllegalStateException(\"Unhandled info type, cannot get byte[]: \" + info.getClass().getName());\n\n\t\t\t\t\t// Check if deflate would be more optimal.\n\t\t\t\t\tInputStream in = new ByteArrayInputStream(content);\n\t\t\t\t\tByteArrayOutputStream out = new ByteArrayOutputStream();\n\t\t\t\t\ttry (DeflaterOutputStream deflate = new DeflaterOutputStream(out)) {\n\t\t\t\t\t\tbyte[] buffer = new byte[2048];\n\t\t\t\t\t\tint len;\n\t\t\t\t\t\twhile ((len = in.read(buffer)) > 0) {\n\t\t\t\t\t\t\tdeflate.write(buffer, 0, len);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdeflate.finish();\n\t\t\t\t\t\tint inputSize = content.length;\n\t\t\t\t\t\tint compressedSize = out.size();\n\t\t\t\t\t\tif (compressedSize < inputSize)\n\t\t\t\t\t\t\treturn DEFLATED;\n\t\t\t\t\t} catch (IOException ignored) {\n\t\t\t\t\t\t// Cannot compress\n\t\t\t\t\t}\n\t\t\t\t\treturn STORED;\n\t\t\t\tcase MATCH_ORIGINAL:\n\t\t\t\tdefault:\n\t\t\t\t\treturn ZipCompressionProperty.getOr(info, DEFLATED);\n\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/WorkspaceExporter.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.IOException;\n\n/**\n * Outline for supporting exporting of {@link Workspace} back into files.\n *\n * @author Matt Coley\n * @see WorkspaceExportOptions\n */\npublic interface WorkspaceExporter {\n\t/**\n\t * The actions of exporting are configured by {@link WorkspaceExportOptions}.\n\t *\n\t * @param workspace\n\t * \t\tThe workspace to export.\n\t *\n\t * @throws IOException\n\t * \t\tWhen exporting failed for any IO related reason.\n\t */\n\tvoid export(@Nonnull Workspace workspace) throws IOException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/io/WorkspaceOutputType.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\n\n/**\n * Output option between single files and directories in {@link WorkspaceExportOptions}.\n *\n * @author Matt Coley\n */\npublic enum WorkspaceOutputType {\n\t/**\n\t * Output to a single file. The type of which is determined by the primary resource's\n\t * {@link WorkspaceFileResource#getFileInfo()} if available. Otherwise, defaults to ZIP/JAR.\n\t * <p>\n\t * Delegates to {@link WorkspaceExportConsumer#write(byte[])}\n\t */\n\tFILE,\n\t/**\n\t * Output to a directory.\n\t * <p>\n\t * Delegates to {@link WorkspaceExportConsumer#writeRelative(String, byte[])}\n\t */\n\tDIRECTORY\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/patch/PatchApplier.java",
    "content": "package software.coley.recaf.services.workspace.patch;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport me.darknet.assembler.compile.JavaClassRepresentation;\nimport me.darknet.assembler.error.Error;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.TextFileInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.assembler.AssemblerPipelineManager;\nimport software.coley.recaf.services.assembler.JvmAssemblerPipeline;\nimport software.coley.recaf.services.workspace.patch.model.JvmAssemblerPatch;\nimport software.coley.recaf.services.workspace.patch.model.RemovePath;\nimport software.coley.recaf.services.workspace.patch.model.TextFilePatch;\nimport software.coley.recaf.services.workspace.patch.model.WorkspacePatch;\nimport software.coley.recaf.util.StringDiff;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Consumer;\n\n/**\n * Service to apply {@link WorkspacePatch}s.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class PatchApplier implements Service {\n\tpublic static final String SERVICE_ID = \"resource-patch-applier\";\n\tprivate static final Logger logger = Logging.get(PatchApplier.class);\n\tprivate final AssemblerPipelineManager assemblerPipelineManager;\n\tprivate final ResourcePatchApplierConfig config;\n\n\t@Inject\n\tpublic PatchApplier(@Nonnull AssemblerPipelineManager assemblerPipelineManager,\n\t                    @Nonnull ResourcePatchApplierConfig config) {\n\t\tthis.assemblerPipelineManager = assemblerPipelineManager;\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Applies the given patch to the workspace it's associated with.\n\t *\n\t * @param patch\n\t * \t\tPatch to apply.\n\t * @param feedback\n\t * \t\tOptional feedback for receiving errors.\n\t * \t\tWhen any error is observed the patching process is abandoned.\n\t *\n\t * @return {@code true} When the patch was successful.\n\t * {@code false} when the patch was abandoned.\n\t */\n\tpublic boolean apply(@Nonnull WorkspacePatch patch, @Nullable PatchFeedback feedback) {\n\t\tList<Runnable> tasks = new ArrayList<>();\n\t\tErrorDelegate errorConsumerDelegate = new ErrorDelegate(feedback == null ? null : feedback::onAssemblerErrorsObserved);\n\n\t\tfor (RemovePath removal : patch.removals()) {\n\t\t\tPathNode<?> path = removal.path();\n\t\t\tInfo toRemove = path.getValueOfType(Info.class);\n\t\t\tBundle<?> containingBundle = path.getValueOfType(Bundle.class);\n\t\t\tif (containingBundle == null) {\n\t\t\t\tif (feedback != null) feedback.onIncompletePathObserved(path);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (toRemove == null) {\n\t\t\t\tif (feedback != null) feedback.onIncompletePathObserved(path);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tString entryName = toRemove.getName();\n\t\t\ttasks.add(() -> {\n\t\t\t\tif (containingBundle.remove(entryName) == null)\n\t\t\t\t\tlogger.warn(\"Could not apply removal for path '{}' - not found in the workspace\", entryName);\n\t\t\t});\n\t\t}\n\n\t\tJvmAssemblerPipeline jvmAssemblerPipeline = assemblerPipelineManager.newJvmAssemblerPipeline(patch.workspace());\n\t\tfor (JvmAssemblerPatch jvmAssemblerPatch : patch.jvmAssemblerPatches()) {\n\t\t\t// Skip if any errors have been seen.\n\t\t\tif (errorConsumerDelegate.hasSeenErrors())\n\t\t\t\treturn false;\n\n\t\t\tClassPathNode path = jvmAssemblerPatch.path().withCurrentWorkspaceContent();\n\t\t\tJvmClassInfo jvmClass = path.getValue().asJvmClass();\n\t\t\tJvmClassBundle jvmBundle = path.getValueOfType(JvmClassBundle.class);\n\t\t\tif (jvmBundle == null) {\n\t\t\t\tif (feedback != null) feedback.onIncompletePathObserved(path);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Apply patch\n\t\t\tList<StringDiff.Diff> diffs = jvmAssemblerPatch.assemblerDiffs();\n\t\t\tjvmAssemblerPipeline.disassemble(path).ifOk(disassemble -> {\n\t\t\t\t// Apply diffs to disassembled class.\n\t\t\t\tString patchedAssembly = StringDiff.Diff.apply(disassemble, diffs);\n\n\t\t\t\t// Reassemble the class and update the workspace.\n\t\t\t\t// And parse / assemble step failure\n\t\t\t\tjvmAssemblerPipeline.tokenize(patchedAssembly, \"<patch>\")\n\t\t\t\t\t\t.flatMap(jvmAssemblerPipeline::roughParse)\n\t\t\t\t\t\t.flatMap(jvmAssemblerPipeline::concreteParse)\n\t\t\t\t\t\t.flatMap(concreteAst -> jvmAssemblerPipeline.assemble(concreteAst, path))\n\t\t\t\t\t\t.ifOk(patchResult -> {\n\t\t\t\t\t\t\tJavaClassRepresentation representation = patchResult.representation();\n\t\t\t\t\t\t\tif (representation != null) {\n\t\t\t\t\t\t\t\ttasks.add(() -> {\n\t\t\t\t\t\t\t\t\tJvmClassInfo patchedClass = jvmClass.toJvmClassBuilder()\n\t\t\t\t\t\t\t\t\t\t\t.adaptFrom(representation.classFile())\n\t\t\t\t\t\t\t\t\t\t\t.build();\n\t\t\t\t\t\t\t\t\tjvmBundle.put(patchedClass);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}).ifErr(errorConsumerDelegate::errors);\n\t\t\t}).ifErr(errors -> {\n\t\t\t\t// Disassemble failure\n\t\t\t\tif (feedback != null) feedback.onAssemblerErrorsObserved(errors);\n\t\t\t});\n\t\t}\n\n\t\tfor (TextFilePatch filePatch : patch.textFilePatches()) {\n\t\t\t// Skip if any errors have been seen.\n\t\t\tif (errorConsumerDelegate.hasSeenErrors())\n\t\t\t\treturn false;\n\n\t\t\tFilePathNode path = filePatch.path().withCurrentWorkspaceContent();\n\t\t\tTextFileInfo textFile = path.getValue().asTextFile();\n\t\t\tFileBundle fileBundle = path.getValueOfType(FileBundle.class);\n\t\t\tif (fileBundle == null) {\n\t\t\t\tif (feedback != null) feedback.onIncompletePathObserved(path);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tString patchedText = StringDiff.Diff.apply(textFile.getText(), filePatch.textDiffs());\n\t\t\ttasks.add(() -> {\n\t\t\t\tTextFileInfo patchedTextFile = textFile.toTextBuilder()\n\t\t\t\t\t\t.withRawContent(patchedText.getBytes(textFile.getCharset()))\n\t\t\t\t\t\t.build();\n\t\t\t\tfileBundle.put(patchedTextFile);\n\t\t\t});\n\t\t}\n\n\t\t// If no errors have been seen apply all patches.\n\t\tif (!errorConsumerDelegate.hasSeenErrors()) {\n\t\t\tfor (Runnable task : tasks) {\n\t\t\t\ttry {\n\t\t\t\t\ttask.run();\n\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t// Most likely caused by listeners and not the patch itself.\n\t\t\t\t\t// Log the error and continue.\n\t\t\t\t\tlogger.error(\"Error applying patch task\", t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ResourcePatchApplierConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\tprivate static class ErrorDelegate {\n\t\tprivate final Consumer<List<Error>> errorConsumer;\n\t\tprivate boolean seenErrors;\n\n\t\tprivate ErrorDelegate(@Nullable Consumer<List<Error>> errorConsumer) {this.errorConsumer = errorConsumer;}\n\n\t\tpublic void errors(List<Error> errors) {\n\t\t\tseenErrors = true;\n\t\t\tif (errorConsumer != null) errorConsumer.accept(errors);\n\t\t}\n\n\t\tpublic boolean hasSeenErrors() {\n\t\t\treturn seenErrors;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/patch/PatchFeedback.java",
    "content": "package software.coley.recaf.services.workspace.patch;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.error.Error;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.workspace.patch.model.WorkspacePatch;\n\nimport java.util.List;\n\n/**\n * System for receiving error notifications when attempting to\n * {@link PatchApplier#apply(WorkspacePatch, PatchFeedback) apply patches}.\n *\n * @author Matt Coley\n */\npublic interface PatchFeedback {\n\t/**\n\t * Called when a {@link WorkspacePatch#jvmAssemblerPatches()} could not be applied.\n\t *\n\t * @param errors\n\t * \t\tAssembler errors observed.\n\t */\n\tdefault void onAssemblerErrorsObserved(@Nonnull List<Error> errors) {}\n\n\t/**\n\t * Called when a required path in a {@link WorkspacePatch} did not contain all necessary components.\n\t *\n\t * @param path\n\t * \t\tIncomplete path.\n\t */\n\tdefault void onIncompletePathObserved(@Nonnull PathNode<?> path) {}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/patch/PatchGenerationException.java",
    "content": "package software.coley.recaf.services.workspace.patch;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Exception to outline various patch generation and serialization problems.\n *\n * @author Matt Coley\n */\npublic class PatchGenerationException extends Exception {\n\tpublic PatchGenerationException(@Nonnull Throwable cause, @Nonnull String message) {\n\t\tsuper(message, cause);\n\t}\n\n\tpublic PatchGenerationException(@Nonnull Throwable cause) {\n\t\tsuper(cause);\n\t}\n\n\tpublic PatchGenerationException(@Nonnull String message) {\n\t\tsuper(message);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/patch/PatchProvider.java",
    "content": "package software.coley.recaf.services.workspace.patch;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport me.darknet.assembler.error.Result;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.*;\nimport software.coley.recaf.path.*;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.assembler.AssemblerPipelineManager;\nimport software.coley.recaf.services.assembler.JvmAssemblerPipeline;\nimport software.coley.recaf.services.workspace.patch.model.JvmAssemblerPatch;\nimport software.coley.recaf.services.workspace.patch.model.RemovePath;\nimport software.coley.recaf.services.workspace.patch.model.TextFilePatch;\nimport software.coley.recaf.services.workspace.patch.model.WorkspacePatch;\nimport software.coley.recaf.util.StringDiff;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.*;\n\n/**\n * Service to provide and handle serialization of {@link WorkspacePatch}s.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class PatchProvider implements Service {\n\tpublic static final String SERVICE_ID = \"resource-patch-provider\";\n\tprivate static final Logger logger = Logging.get(PatchProvider.class);\n\tprivate final AssemblerPipelineManager assemblerPipelineManager;\n\tprivate final ResourcePatchProviderConfig config;\n\n\t@Inject\n\tpublic PatchProvider(@Nonnull AssemblerPipelineManager assemblerPipelineManager,\n\t                     @Nonnull ResourcePatchProviderConfig config) {\n\t\tthis.assemblerPipelineManager = assemblerPipelineManager;\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Maps a workspace patch into JSON.\n\t *\n\t * @param patch\n\t * \t\tPatch to serialize.\n\t *\n\t * @return JSON string representation of the patch.\n\t */\n\t@Nonnull\n\tpublic String serializePatch(@Nonnull WorkspacePatch patch) {\n\t\treturn PatchSerialization.serialize(patch);\n\t}\n\n\t/**\n\t * Maps a JSON file into a workspace patch.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to apply the patch to.\n\t * @param patchPath\n\t * \t\tPath to the JSON file outlining patch contents.\n\t *\n\t * @return A workspace patch instance.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the JSON file couldn't be read.\n\t * @throws PatchGenerationException\n\t * \t\tWhen the JSON file couldn't be parsed, or its contents could not be found in the workspace.\n\t */\n\t@Nonnull\n\tpublic WorkspacePatch deserializePatch(@Nonnull Workspace workspace, @Nonnull Path patchPath) throws IOException, PatchGenerationException {\n\t\treturn deserializePatch(workspace, Files.readString(patchPath));\n\t}\n\n\t/**\n\t * Maps a JSON file into a workspace patch.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to apply the patch to.\n\t * @param patchContents\n\t * \t\tJSON outlining patch contents.\n\t *\n\t * @return A workspace patch instance.\n\t *\n\t * @throws PatchGenerationException\n\t * \t\tWhen the JSON file couldn't be parsed, or its contents could not be found in the workspace.\n\t */\n\t@Nonnull\n\tpublic WorkspacePatch deserializePatch(@Nonnull Workspace workspace, @Nonnull String patchContents) throws PatchGenerationException {\n\t\treturn PatchSerialization.deserialize(workspace, patchContents);\n\t}\n\n\t/**\n\t * Creates a patch which models all changes in the given workspace.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to generate a patch for.\n\t *\n\t * @return Patch modeling all changes made in the workspace.\n\t *\n\t * @throws PatchGenerationException\n\t * \t\tWhen the patch couldn't be made for any reason.\n\t */\n\t@Nonnull\n\tpublic WorkspacePatch createPatch(@Nonnull Workspace workspace) throws PatchGenerationException {\n\t\tList<RemovePath> removals = new ArrayList<>();\n\t\tList<JvmAssemblerPatch> jvmAssemblerPatches = new ArrayList<>();\n\t\tList<TextFilePatch> textFilePatches = new ArrayList<>();\n\t\tPatchConsumer<ClassPathNode, JvmClassInfo> classConsumer = (classPath, initial, current) -> {\n\t\t\tDirectoryPathNode parent = Objects.requireNonNull(classPath.getParent());\n\t\t\tClassPathNode initialPath = parent.child(initial);\n\t\t\tClassPathNode currentPath = parent.child(current);\n\t\t\tJvmAssemblerPipeline assembler = assemblerPipelineManager.newJvmAssemblerPipeline(workspace);\n\t\t\tResult<String> initialDisassembleRes = assembler.disassemble(initialPath);\n\t\t\tResult<String> currentDisassembleRes = assembler.disassemble(currentPath);\n\t\t\tif (!initialDisassembleRes.hasValue())\n\t\t\t\tthrow new PatchGenerationException(\"Failed to disassemble initial state of '\" + initial.getName() + \"'\");\n\t\t\tif (initialDisassembleRes.hasErr())\n\t\t\t\tthrow new PatchGenerationException(\"Initial state of '\" + initial.getName() + \"' has assembler errors\");\n\t\t\tif (!currentDisassembleRes.hasValue())\n\t\t\t\tthrow new PatchGenerationException(\"Failed to disassemble current state of '\" + initial.getName() + \"'\");\n\t\t\tif (currentDisassembleRes.hasErr())\n\t\t\t\tthrow new PatchGenerationException(\"Current state of '\" + initial.getName() + \"' has assembler errors\");\n\t\t\tString initialDisassemble = initialDisassembleRes.get();\n\t\t\tString currentDisassemble = currentDisassembleRes.get();\n\t\t\tList<StringDiff.Diff> assemblerDiffs = StringDiff.diff(initialDisassemble, currentDisassemble);\n\t\t\tif (!assemblerDiffs.isEmpty())\n\t\t\t\tjvmAssemblerPatches.add(new JvmAssemblerPatch(initialPath, assemblerDiffs));\n\t\t};\n\t\tPatchConsumer<FilePathNode, FileInfo> fileConsumer = (filePath, initial, current) -> {\n\t\t\tif (initial.isTextFile() && current.isTextFile()) {\n\t\t\t\tString initialText = initial.asTextFile().getText();\n\t\t\t\tString currentText = current.asTextFile().getText();\n\t\t\t\tList<StringDiff.Diff> textDiffs = StringDiff.diff(initialText, currentText);\n\t\t\t\tif (!textDiffs.isEmpty())\n\t\t\t\t\ttextFilePatches.add(new TextFilePatch(filePath, textDiffs));\n\t\t\t} else {\n\t\t\t\t// TODO: Support binary patches of non-text files\n\t\t\t\tlogger.debug(\"Skipping file diff for '{}' as it is not a text file\", initial.getName());\n\t\t\t}\n\t\t};\n\n\t\ttry {\n\t\t\tWorkspaceResource resource = workspace.getPrimaryResource();\n\t\t\tResourcePathNode resourcePath = PathNodes.resourcePath(workspace, resource);\n\t\t\tresource.bundleStream().forEach(b -> {\n\t\t\t\tBundlePathNode bundlePath = resourcePath.child(b);\n\t\t\t\tSet<String> removedKeys = b.getRemovedKeys();\n\t\t\t\tfor (String key : removedKeys) {\n\t\t\t\t\tif (b instanceof ClassBundle<?>) {\n\t\t\t\t\t\tClassInfo stub = new StubClassInfo(key);\n\t\t\t\t\t\tClassPathNode stubPath = bundlePath.child(stub.getPackageName()).child(stub);\n\t\t\t\t\t\tremovals.add(new RemovePath(stubPath));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tFileInfo stub = new StubFileInfo(key);\n\t\t\t\t\t\tFilePathNode stubPath = bundlePath.child(stub.getDirectoryName()).child(stub);\n\t\t\t\t\t\tremovals.add(new RemovePath(stubPath));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\tvisitDirtyItems(workspace, resource, resource.getJvmClassBundle(), classConsumer);\n\t\t\tfor (var entry : resource.getVersionedJvmClassBundles().entrySet()) {\n\t\t\t\tvisitDirtyItems(workspace, resource, entry.getValue(), classConsumer);\n\t\t\t}\n\t\t\tvisitDirtyItems(workspace, resource, resource.getFileBundle(), fileConsumer);\n\t\t} catch (Throwable t) {\n\t\t\tthrow new PatchGenerationException(t);\n\t\t}\n\n\t\treturn new WorkspacePatch(workspace,\n\t\t\t\tCollections.unmodifiableList(removals),\n\t\t\t\tCollections.unmodifiableList(jvmAssemblerPatches),\n\t\t\t\tCollections.unmodifiableList(textFilePatches));\n\t}\n\n\t@SuppressWarnings({\"unchecked\", \"DataFlowIssue\"})\n\tprivate <I extends Info, P extends PathNode<?>> void visitDirtyItems(@Nonnull Workspace workspace,\n\t                                                                     @Nonnull WorkspaceResource resource,\n\t                                                                     @Nonnull Bundle<I> bundle,\n\t                                                                     @Nonnull PatchConsumer<P, I> consumer) throws Throwable {\n\t\tBundlePathNode bundlePath = PathNodes.bundlePath(workspace, resource, bundle);\n\t\tSet<String> dirtyKeys = bundle.getDirtyKeys();\n\t\tfor (String dirtyKey : dirtyKeys) {\n\t\t\tStack<I> history = bundle.getHistory(dirtyKey);\n\t\t\tI current = history.peek();\n\t\t\tI oldest = history.elementAt(0);\n\t\t\tint lastDirSeparator = dirtyKey.lastIndexOf('/');\n\t\t\tString directoryName = lastDirSeparator >= 0 ? dirtyKey.substring(0, lastDirSeparator) : null;\n\t\t\tDirectoryPathNode directoryPath = bundlePath.child(directoryName);\n\t\t\tif (current instanceof ClassInfo currentClass) {\n\t\t\t\tClassPathNode classPath = directoryPath.child(currentClass);\n\t\t\t\tconsumer.accept((P) classPath, oldest, current);\n\t\t\t} else if (current instanceof FileInfo currentFile) {\n\t\t\t\tFilePathNode filePath = directoryPath.child(currentFile);\n\t\t\t\tconsumer.accept((P) filePath, oldest, current);\n\t\t\t}\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ResourcePatchProviderConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\t@FunctionalInterface\n\tprivate interface PatchConsumer<P extends PathNode<?>, I extends Info> {\n\t\tvoid accept(P path, I initial, I current) throws Throwable;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/patch/PatchSerialization.java",
    "content": "package software.coley.recaf.services.workspace.patch;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonWriter;\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.services.workspace.patch.model.JvmAssemblerPatch;\nimport software.coley.recaf.services.workspace.patch.model.RemovePath;\nimport software.coley.recaf.services.workspace.patch.model.TextFilePatch;\nimport software.coley.recaf.services.workspace.patch.model.WorkspacePatch;\nimport software.coley.recaf.util.StringDiff;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.io.StringWriter;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Patch serialization helper for {@link PatchProvider}.\n *\n * @author Matt Coley\n */\npublic class PatchSerialization {\n\tprivate static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();\n\tprivate static final String KEY_REMOVALS = \"removals\";\n\tprivate static final String KEY_CLASS_JVM_ASM_DIFFS = \"class-jvm-asm-diffs\";\n\tprivate static final String KEY_FILE_TEXT_DIFFS = \"file-text-diffs\";\n\tprivate static final String KEY_NAME = \"name\";\n\tprivate static final String KEY_DIFFS = \"diffs\";\n\tprivate static final String KEY_TYPE = \"type\";\n\tprivate static final String KEY_START_A = \"start-a\";\n\tprivate static final String KEY_END_A = \"end-a\";\n\tprivate static final String KEY_TEXT_A = \"text-a\";\n\tprivate static final String KEY_START_B = \"start-b\";\n\tprivate static final String KEY_END_B = \"end-b\";\n\tprivate static final String KEY_TEXT_B = \"text-b\";\n\tprivate static final String TYPE_CLASS = \"class\";\n\tprivate static final String TYPE_FILE = \"file\";\n\n\tprivate PatchSerialization() {}\n\n\t/**\n\t * Maps a workspace patch into JSON.\n\t *\n\t * @param patch\n\t * \t\tPatch to serialize.\n\t *\n\t * @return JSON string representation of the patch.\n\t */\n\t@Nonnull\n\tpublic static String serialize(@Nonnull WorkspacePatch patch) {\n\t\tStringWriter out = new StringWriter();\n\t\ttry {\n\t\t\tJsonWriter jw = GSON.newJsonWriter(out);\n\t\t\tList<RemovePath> removals = patch.removals();\n\t\t\tList<JvmAssemblerPatch> jvmAssemblerPatches = patch.jvmAssemblerPatches();\n\t\t\tList<TextFilePatch> textFilePatches = patch.textFilePatches();\n\n\t\t\tserializeRemovals(jw, removals);\n\t\t\tserializeJvmAsmPatches(jvmAssemblerPatches, jw);\n\t\t\tserializeTextPatches(textFilePatches, jw);\n\t\t} catch (Exception ex) {\n\t\t\tthrow new IllegalStateException(\"Failed to create json writer for patch\", ex);\n\t\t}\n\n\t\treturn out.toString();\n\t}\n\n\tprivate static void serializeRemovals(@Nonnull JsonWriter jw, @Nonnull List<RemovePath> removals) throws IOException {\n\t\tjw.beginObject();\n\t\tif (!removals.isEmpty()) {\n\t\t\tjw.name(KEY_REMOVALS).beginArray();\n\t\t\tfor (RemovePath removal : removals) {\n\t\t\t\tInfo info = removal.path().getValueOfType(Info.class);\n\t\t\t\tif (info == null)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tString name = info.getName();\n\t\t\t\tjw.beginObject();\n\t\t\t\tif (info.isClass()) {\n\t\t\t\t\tjw.name(KEY_TYPE).value(TYPE_CLASS);\n\t\t\t\t\tjw.name(KEY_NAME).value(name);\n\t\t\t\t} else if (info.isFile()) {\n\t\t\t\t\tjw.name(KEY_TYPE).value(TYPE_FILE);\n\t\t\t\t\tjw.name(KEY_NAME).value(name);\n\t\t\t\t}\n\t\t\t\tjw.endObject();\n\t\t\t}\n\t\t\tjw.endArray();\n\t\t}\n\t}\n\n\tprivate static void serializeJvmAsmPatches(@Nonnull List<JvmAssemblerPatch> jvmAssemblerPatches, @Nonnull JsonWriter jw) throws IOException {\n\t\tif (!jvmAssemblerPatches.isEmpty()) {\n\t\t\tjw.name(KEY_CLASS_JVM_ASM_DIFFS).beginArray();\n\t\t\tfor (JvmAssemblerPatch classPatch : jvmAssemblerPatches) {\n\t\t\t\tString className = classPatch.path().getValue().getName();\n\t\t\t\tjw.beginObject();\n\t\t\t\tjw.name(KEY_NAME).value(className);\n\t\t\t\tjw.name(KEY_DIFFS).beginArray();\n\t\t\t\tfor (StringDiff.Diff assemblerDiff : classPatch.assemblerDiffs())\n\t\t\t\t\tserializeStringDiff(jw, assemblerDiff);\n\t\t\t\tjw.endArray().endObject();\n\t\t\t}\n\t\t\tjw.endArray();\n\t\t}\n\t}\n\n\tprivate static void serializeTextPatches(@Nonnull List<TextFilePatch> textFilePatches, @Nonnull JsonWriter jw) throws IOException {\n\t\tif (!textFilePatches.isEmpty()) {\n\t\t\tjw.name(KEY_FILE_TEXT_DIFFS).beginArray();\n\t\t\tfor (TextFilePatch textPatch : textFilePatches) {\n\t\t\t\tString fileName = textPatch.path().getValue().getName();\n\t\t\t\tjw.beginObject();\n\t\t\t\tjw.name(KEY_NAME).value(fileName);\n\t\t\t\tjw.name(KEY_DIFFS).beginArray();\n\t\t\t\tfor (StringDiff.Diff assemblerDiff : textPatch.textDiffs())\n\t\t\t\t\tserializeStringDiff(jw, assemblerDiff);\n\t\t\t\tjw.endArray().endObject();\n\t\t\t}\n\t\t\tjw.endArray();\n\t\t}\n\t\tjw.endObject();\n\t}\n\n\tprivate static void serializeStringDiff(@Nonnull JsonWriter jw, @Nonnull StringDiff.Diff diff) throws IOException {\n\t\tjw.beginObject();\n\t\tjw.name(KEY_TYPE).value(diff.type().name());\n\t\tjw.name(KEY_START_A).value(diff.startA());\n\t\tjw.name(KEY_END_A).value(diff.endA());\n\t\tjw.name(KEY_TEXT_A).value(diff.textA());\n\t\tjw.name(KEY_START_B).value(diff.startB());\n\t\tjw.name(KEY_END_B).value(diff.endB());\n\t\tjw.name(KEY_TEXT_B).value(diff.textB());\n\t\tjw.endObject();\n\t}\n\n\t/**\n\t * Maps a JSON file into a workspace patch.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to apply the patch to.\n\t * @param patchContents\n\t * \t\tJSON outlining patch contents.\n\t *\n\t * @return A workspace patch instance.\n\t *\n\t * @throws PatchGenerationException\n\t * \t\tWhen the JSON file couldn't be parsed, or its contents could not be found in the workspace.\n\t */\n\t@Nonnull\n\tpublic static WorkspacePatch deserialize(@Nonnull Workspace workspace, @Nonnull String patchContents) throws PatchGenerationException {\n\t\tList<RemovePath> removals = Collections.emptyList();\n\t\tList<JvmAssemblerPatch> jvmAssemblerPatches = Collections.emptyList();\n\t\tList<TextFilePatch> textFilePatches = Collections.emptyList();\n\t\tif (patchContents.isBlank() || patchContents.charAt(0) != '{' || patchContents.charAt(patchContents.length() - 1) != '}')\n\t\t\treturn new WorkspacePatch(workspace, removals, jvmAssemblerPatches, textFilePatches);\n\t\ttry {\n\t\t\tJsonReader jr = GSON.newJsonReader(new StringReader(patchContents));\n\t\t\tjr.beginObject();\n\t\t\twhile (jr.hasNext()) {\n\t\t\t\tString name = jr.nextName();\n\t\t\t\tswitch (name) {\n\t\t\t\t\tcase KEY_CLASS_JVM_ASM_DIFFS -> jvmAssemblerPatches = deserializeClassJvmAsmDiffs(workspace, jr);\n\t\t\t\t\tcase KEY_FILE_TEXT_DIFFS -> textFilePatches = deserializeFileTextDiffs(workspace, jr);\n\t\t\t\t\tcase KEY_REMOVALS -> removals = deserializeRemovals(workspace, jr);\n\t\t\t\t}\n\t\t\t}\n\t\t\tjr.endObject();\n\t\t\treturn new WorkspacePatch(workspace, removals, jvmAssemblerPatches, textFilePatches);\n\t\t} catch (Exception ex) {\n\t\t\tthrow new PatchGenerationException(ex, \"Failed to parse patch contents\");\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate static List<RemovePath> deserializeRemovals(@Nonnull Workspace workspace, @Nonnull JsonReader jr) throws IOException, PatchGenerationException {\n\t\tList<RemovePath> removals = new ArrayList<>();\n\t\tjr.beginArray();\n\t\twhile (jr.hasNext()) {\n\t\t\tString name = null;\n\t\t\tString type = null;\n\t\t\tjr.beginObject();\n\t\t\twhile (jr.hasNext()) {\n\t\t\t\tString key = jr.nextName();\n\t\t\t\tif (key.equals(KEY_NAME))\n\t\t\t\t\tname = jr.nextString();\n\t\t\t\telse if (key.equals(KEY_TYPE))\n\t\t\t\t\ttype = jr.nextString();\n\t\t\t}\n\t\t\tjr.endObject();\n\n\t\t\t// Construct the removal\n\t\t\tif (name != null) {\n\t\t\t\t// If the classes/files do not exist in the workspace then our job is already done,\n\t\t\t\t// and we don't need to include these in the final patch model.\n\t\t\t\tif (TYPE_CLASS.equals(type)) {\n\t\t\t\t\tFilePathNode path = workspace.findFile(name);\n\t\t\t\t\tif (path != null) removals.add(new RemovePath(path));\n\t\t\t\t} else if (TYPE_FILE.equals(type)) {\n\t\t\t\t\tClassPathNode path = workspace.findClass(name);\n\t\t\t\t\tif (path != null) removals.add(new RemovePath(path));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tjr.endArray();\n\t\treturn removals;\n\t}\n\n\t@Nonnull\n\tprivate static List<JvmAssemblerPatch> deserializeClassJvmAsmDiffs(@Nonnull Workspace workspace, @Nonnull JsonReader jr) throws IOException, PatchGenerationException {\n\t\tList<JvmAssemblerPatch> patches = new ArrayList<>();\n\t\tjr.beginArray();\n\t\twhile (jr.hasNext()) {\n\t\t\tString name = null;\n\t\t\tList<StringDiff.Diff> diffs = Collections.emptyList();\n\t\t\tjr.beginObject();\n\t\t\twhile (jr.hasNext()) {\n\t\t\t\tString key = jr.nextName();\n\t\t\t\tif (key.equals(KEY_NAME))\n\t\t\t\t\tname = jr.nextString();\n\t\t\t\telse if (key.equals(KEY_DIFFS))\n\t\t\t\t\tdiffs = deserializeStringDiffs(jr);\n\t\t\t}\n\t\t\tjr.endObject();\n\n\t\t\t// Construct the patch\n\t\t\tif (name != null && !diffs.isEmpty()) {\n\t\t\t\tClassPathNode classPath = workspace.findJvmClass(name);\n\t\t\t\tif (classPath == null)\n\t\t\t\t\tthrow new PatchGenerationException(\"'\" + name + \"' cannot be found in the given workspace\");\n\t\t\t\tpatches.add(new JvmAssemblerPatch(classPath, diffs));\n\t\t\t}\n\t\t}\n\t\tjr.endArray();\n\t\treturn patches;\n\t}\n\n\t@Nonnull\n\tprivate static List<TextFilePatch> deserializeFileTextDiffs(@Nonnull Workspace workspace, @Nonnull JsonReader jr) throws IOException, PatchGenerationException {\n\t\tList<TextFilePatch> patches = new ArrayList<>();\n\t\tjr.beginArray();\n\t\twhile (jr.hasNext()) {\n\t\t\tString name = null;\n\t\t\tList<StringDiff.Diff> diffs = Collections.emptyList();\n\t\t\tjr.beginObject();\n\t\t\twhile (jr.hasNext()) {\n\t\t\t\tString key = jr.nextName();\n\t\t\t\tif (key.equals(KEY_NAME))\n\t\t\t\t\tname = jr.nextString();\n\t\t\t\telse if (key.equals(KEY_DIFFS))\n\t\t\t\t\tdiffs = deserializeStringDiffs(jr);\n\t\t\t}\n\t\t\tjr.endObject();\n\n\t\t\t// Construct the patch\n\t\t\tif (name != null && !diffs.isEmpty()) {\n\t\t\t\tFilePathNode filePath = workspace.findFile(name);\n\t\t\t\tif (filePath == null)\n\t\t\t\t\tthrow new PatchGenerationException(\"'\" + name + \"' cannot be found in the given workspace\");\n\t\t\t\tpatches.add(new TextFilePatch(filePath, diffs));\n\t\t\t}\n\t\t}\n\t\tjr.endArray();\n\t\treturn patches;\n\t}\n\n\t@Nonnull\n\tprivate static List<StringDiff.Diff> deserializeStringDiffs(@Nonnull JsonReader jr) throws IOException {\n\t\tList<StringDiff.Diff> diffs = new ArrayList<>();\n\t\tjr.beginArray();\n\t\twhile (jr.hasNext())\n\t\t\tdiffs.add(deserializeStringDiff(jr));\n\t\tjr.endArray();\n\t\treturn diffs;\n\t}\n\n\t@Nonnull\n\tprivate static StringDiff.Diff deserializeStringDiff(@Nonnull JsonReader jr) throws IOException {\n\t\tStringDiff.DiffType type = null;\n\t\tint startA = -1;\n\t\tint startB = -1;\n\t\tint endA = -1;\n\t\tint endB = -1;\n\t\tString textA = null;\n\t\tString textB = null;\n\n\t\tjr.beginObject();\n\t\twhile (jr.hasNext()) {\n\t\t\tString key = jr.nextName();\n\t\t\tswitch (key) {\n\t\t\t\tcase KEY_TYPE -> type = StringDiff.DiffType.valueOf(jr.nextString());\n\t\t\t\tcase KEY_TEXT_A -> textA = jr.nextString();\n\t\t\t\tcase KEY_TEXT_B -> textB = jr.nextString();\n\t\t\t\tcase KEY_START_A -> startA = jr.nextInt();\n\t\t\t\tcase KEY_START_B -> startB = jr.nextInt();\n\t\t\t\tcase KEY_END_A -> endA = jr.nextInt();\n\t\t\t\tcase KEY_END_B -> endB = jr.nextInt();\n\t\t\t}\n\t\t}\n\t\tjr.endObject();\n\n\t\tif (type == null)\n\t\t\tthrow new IOException(\"String diff missing key: \" + KEY_TYPE);\n\t\tif (textA == null)\n\t\t\tthrow new IOException(\"String diff missing key: \" + KEY_TEXT_A);\n\t\tif (textB == null)\n\t\t\tthrow new IOException(\"String diff missing key: \" + KEY_TEXT_B);\n\t\tif (startA == -1)\n\t\t\tthrow new IOException(\"String diff missing key: \" + KEY_START_A);\n\t\tif (startB == -1)\n\t\t\tthrow new IOException(\"String diff missing key: \" + KEY_START_B);\n\t\tif (endA == -1)\n\t\t\tthrow new IOException(\"String diff missing key: \" + KEY_END_A);\n\t\tif (endB == -1)\n\t\t\tthrow new IOException(\"String diff missing key: \" + KEY_END_B);\n\n\t\treturn new StringDiff.Diff(type, startA, startB, endA, endB, textA, textB);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/patch/ResourcePatchApplierConfig.java",
    "content": "package software.coley.recaf.services.workspace.patch;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link PatchApplier}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ResourcePatchApplierConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic ResourcePatchApplierConfig() {\n\t\tsuper(ConfigGroups.SERVICE_IO, PatchApplier.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/patch/ResourcePatchProviderConfig.java",
    "content": "package software.coley.recaf.services.workspace.patch;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link PatchProvider}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ResourcePatchProviderConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic ResourcePatchProviderConfig() {\n\t\tsuper(ConfigGroups.SERVICE_IO, PatchProvider.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/patch/model/JvmAssemblerPatch.java",
    "content": "package software.coley.recaf.services.workspace.patch.model;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.util.StringDiff;\n\nimport java.util.List;\n\n/**\n * Patches for a single text file.\n *\n * @param path\n * \t\tPath to the JVM class file to patch.\n * @param assemblerDiffs\n * \t\tText patches to apply to the class via the assembler.\n *\n * @author Matt Coley\n */\npublic record JvmAssemblerPatch(@Nonnull ClassPathNode path,\n                                @Nonnull List<StringDiff.Diff> assemblerDiffs) {\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tJvmAssemblerPatch that = (JvmAssemblerPatch) o;\n\n\t\tif (path.localCompare(that.path) != 0)\n\t\t\treturn false;\n\t\treturn assemblerDiffs.equals(that.assemblerDiffs);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn assemblerDiffs.hashCode();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/patch/model/RemovePath.java",
    "content": "package software.coley.recaf.services.workspace.patch.model;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.path.PathNode;\n\n/**\n * Outlines the removal of a given path.\n *\n * @param path\n * \t\tPath to the class/file to remove.\n *\n * @author Matt Coley\n */\npublic record RemovePath(@Nonnull PathNode<?> path) {}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/patch/model/TextFilePatch.java",
    "content": "package software.coley.recaf.services.workspace.patch.model;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.util.StringDiff;\n\nimport java.util.List;\n\n/**\n * Patches for a single text file.\n *\n * @param path\n * \t\tPath to the text file to patch.\n * @param textDiffs\n * \t\tText patches to apply to the text file.\n *\n * @author Matt Coley\n */\npublic record TextFilePatch(@Nonnull FilePathNode path, @Nonnull List<StringDiff.Diff> textDiffs) {\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tTextFilePatch that = (TextFilePatch) o;\n\n\t\tif (path.localCompare(that.path) != 0)\n\t\t\treturn false;\n\t\treturn textDiffs.equals(that.textDiffs);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn textDiffs.hashCode();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/patch/model/WorkspacePatch.java",
    "content": "package software.coley.recaf.services.workspace.patch.model;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.List;\n\n/**\n * Wrapper of various patches to apply to a workspace.\n *\n * @param workspace\n * \t\tWorkspace to apply the patches to.\n * @param removals\n * \t\tRemoval patches to remove content by paths.\n * @param jvmAssemblerPatches\n * \t\tText patches to apply to JVM classes via the assembler.\n * @param textFilePatches\n * \t\tText patches to apply to text files.\n *\n * @author Matt Coley\n */\npublic record WorkspacePatch(@Nonnull Workspace workspace,\n                             @Nonnull List<RemovePath> removals,\n                             @Nonnull List<JvmAssemblerPatch> jvmAssemblerPatches,\n                             @Nonnull List<TextFilePatch> textFilePatches) {\n\n\t// TODO: Add more patch type lists\n\t//  - New classes / files\n\t//  - Special patch types for specific transformations like:\n\t//     - \"replace this method with 'return 0'\"\n\t//     - \"change this method's modifiers\"\n\t//     - Can be JVM/Android agnostic\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tWorkspacePatch that = (WorkspacePatch) o;\n\n\t\tif (!jvmAssemblerPatches.equals(that.jvmAssemblerPatches)) return false;\n\t\treturn textFilePatches.equals(that.textFilePatches);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = jvmAssemblerPatches.hashCode();\n\t\tresult = 31 * result + textFilePatches.hashCode();\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/services/workspace/processors/ThrowablePropertyAssigningProcessor.java",
    "content": "package software.coley.recaf.services.workspace.processors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.properties.builtin.ThrowableProperty;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.inheritance.InheritanceVertex;\nimport software.coley.recaf.services.workspace.WorkspaceProcessor;\nimport software.coley.recaf.util.threading.ThreadUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.WorkspaceModificationListener;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.ResourceAndroidClassListener;\nimport software.coley.recaf.workspace.model.resource.ResourceJvmClassListener;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.concurrent.CompletableFuture;\n\n/**\n * Workspace processor that marks {@link ClassInfo} values that inherit from {@link Throwable}\n * as having a {@link ThrowableProperty}. This allows instant look-ups for if a class is throwable,\n * by bypassing repeated calls to {@link InheritanceGraph}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class ThrowablePropertyAssigningProcessor implements WorkspaceProcessor, ResourceJvmClassListener, ResourceAndroidClassListener {\n\tprivate static final String THROWABLE = \"java/lang/Throwable\";\n\tprivate final InheritanceGraphService graphService;\n\tprivate InheritanceGraph inheritanceGraph;\n\n\t@Inject\n\tpublic ThrowablePropertyAssigningProcessor(@Nonnull InheritanceGraphService graphService) {\n\t\tthis.graphService = graphService;\n\t}\n\n\t@Override\n\tpublic void processWorkspace(@Nonnull Workspace workspace) {\n\t\t// Ensure future changes to workspace will process any new classes.\n\t\tThrowablePropertyAssigningProcessor processor = this;\n\t\tworkspace.addWorkspaceModificationListener(new WorkspaceModificationListener() {\n\t\t\t@Override\n\t\t\tpublic void onAddLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library) {\n\t\t\t\tlibrary.addListener(processor);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onRemoveLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library) {\n\t\t\t\tlibrary.removeListener(processor);\n\t\t\t}\n\t\t});\n\t\tfor (WorkspaceResource resource : workspace.getAllResources(false))\n\t\t\tresource.addListener(processor);\n\n\t\t// Provide a graph asynchronously, then process all classes in the workspace.\n\t\tCompletableFuture.supplyAsync(() -> graphService.getOrCreateInheritanceGraph(workspace), ThreadUtil.executor())\n\t\t\t\t.thenAccept(inheritanceGraph -> {\n\t\t\t\t\tthis.inheritanceGraph = inheritanceGraph;\n\t\t\t\t\tworkspace.findClasses(false, c -> true).forEach(path -> handle(path.getValue()));\n\t\t\t\t});\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String name() {\n\t\treturn \"Mark throwable types\";\n\t}\n\n\tprivate void handle(@Nonnull ClassInfo cls) {\n\t\t// Skip if not ready yet.\n\t\t// Any 'missed' cases here should be satisfied by the initial pass when the graph becomes available.\n\t\tif (inheritanceGraph == null)\n\t\t\treturn;\n\n\t\t// Mark the class if it has 'java/lang/Throwable' as a parent.\n\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(cls.getName());\n\t\tif (vertex != null && vertex.hasParent(THROWABLE))\n\t\t\tThrowableProperty.set(cls);\n\t}\n\n\t@Override\n\tpublic void onNewClass(@Nonnull WorkspaceResource resource,\n\t                       @Nonnull AndroidClassBundle bundle,\n\t                       @Nonnull AndroidClassInfo cls) {\n\t\thandle(cls);\n\t}\n\n\t@Override\n\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource,\n\t                          @Nonnull AndroidClassBundle bundle,\n\t                          @Nonnull AndroidClassInfo oldCls,\n\t                          @Nonnull AndroidClassInfo newCls) {\n\t\thandle(newCls);\n\t}\n\n\t@Override\n\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource,\n\t                          @Nonnull AndroidClassBundle bundle,\n\t                          @Nonnull AndroidClassInfo cls) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void onNewClass(@Nonnull WorkspaceResource resource,\n\t                       @Nonnull JvmClassBundle bundle,\n\t                       @Nonnull JvmClassInfo cls) {\n\t\thandle(cls);\n\t}\n\n\t@Override\n\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource,\n\t                          @Nonnull JvmClassBundle bundle,\n\t                          @Nonnull JvmClassInfo oldCls,\n\t                          @Nonnull JvmClassInfo newCls) {\n\t\thandle(newCls);\n\t}\n\n\t@Override\n\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource,\n\t                          @Nonnull JvmClassBundle bundle,\n\t                          @Nonnull JvmClassInfo cls) {\n\t\t// no-op\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/AccessFlag.java",
    "content": "package software.coley.recaf.util;\n\nimport com.google.common.collect.Multimap;\nimport com.google.common.collect.MultimapBuilder;\nimport com.google.common.collect.SetMultimap;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Opcodes;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.StreamSupport;\n\n/**\n * Utility for handling access flags/modifiers.\n *\n * @author Matt Coley\n * @author Andy Li\n */\npublic enum AccessFlag {\n\tACC_PUBLIC(Opcodes.ACC_PUBLIC, \"public\", true, Type.CLASS, Type.INNER_CLASS, Type.METHOD, Type.FIELD),\n\tACC_PRIVATE(Opcodes.ACC_PRIVATE, \"private\", true, Type.INNER_CLASS, Type.METHOD, Type.FIELD),\n\tACC_PROTECTED(Opcodes.ACC_PROTECTED, \"protected\", true, Type.INNER_CLASS, Type.METHOD, Type.FIELD),\n\tACC_STATIC(Opcodes.ACC_STATIC, \"static\", true, Type.INNER_CLASS, Type.METHOD, Type.FIELD),\n\tACC_FINAL(Opcodes.ACC_FINAL, \"final\", true, Type.CLASS, Type.INNER_CLASS, Type.METHOD, Type.FIELD, Type.PARAM),\n\tACC_SYNCHRONIZED(Opcodes.ACC_SYNCHRONIZED, \"synchronized\", true, Type.METHOD),\n\tACC_SUPER(Opcodes.ACC_SUPER, \"super\", false, Type.CLASS),\n\tACC_BRIDGE(Opcodes.ACC_BRIDGE, \"bridge\", false, Type.METHOD),\n\tACC_VOLATILE(Opcodes.ACC_VOLATILE, \"volatile\", true, Type.FIELD),\n\tACC_VARARGS(Opcodes.ACC_VARARGS, \"varargs\", false, Type.METHOD),\n\tACC_TRANSIENT(Opcodes.ACC_TRANSIENT, \"transient\", true, Type.FIELD),\n\tACC_NATIVE(Opcodes.ACC_NATIVE, \"native\", true, Type.METHOD),\n\tACC_INTERFACE(Opcodes.ACC_INTERFACE, \"interface\", true, Type.CLASS, Type.INNER_CLASS),\n\tACC_ABSTRACT(Opcodes.ACC_ABSTRACT, \"abstract\", true, Type.CLASS, Type.INNER_CLASS, Type.METHOD),\n\tACC_STRICT(Opcodes.ACC_STRICT, \"strictfp\", true, Type.METHOD),\n\tACC_SYNTHETIC(Opcodes.ACC_SYNTHETIC, \"synthetic\", false,\n\t\t\tType.CLASS, Type.INNER_CLASS, Type.METHOD, Type.FIELD, Type.PARAM),\n\tACC_ANNOTATION(Opcodes.ACC_ANNOTATION, \"annotation-interface\", false, Type.CLASS, Type.INNER_CLASS),\n\tACC_ENUM(Opcodes.ACC_ENUM, \"enum\", true, Type.CLASS, Type.INNER_CLASS, Type.FIELD),\n\tACC_OPEN(Opcodes.ACC_OPEN, \"open\", false, Type.MODULE),\n\tACC_STATIC_PHASE(Opcodes.ACC_STATIC_PHASE, \"static-phase\", false, Type.MODULE),\n\tACC_TRANSITIVE(Opcodes.ACC_TRANSITIVE, \"transitive\", false, Type.MODULE),\n\tACC_MODULE(Opcodes.ACC_MODULE, \"module\", false, Type.CLASS),\n\tACC_MANDATED(Opcodes.ACC_MANDATED, \"mandated\", false, Type.PARAM);\n\n\tprivate static final Multimap<Integer, AccessFlag> maskToFlagsMap;\n\tprivate static final Map<String, AccessFlag> nameToFlagMap;\n\tprivate static final Multimap<Type, AccessFlag> typeToFlagsMap;\n\n\tstatic {\n\t\tAccessFlag[] flags = values();\n\t\tSetMultimap<Integer, AccessFlag> maskMap = MultimapBuilder.hashKeys()\n\t\t\t\t.enumSetValues(AccessFlag.class).build();\n\t\tMap<String, AccessFlag> nameMap = new LinkedHashMap<>(flags.length);\n\t\tSetMultimap<Type, AccessFlag> typeMap = MultimapBuilder.enumKeys(Type.class)\n\t\t\t\t.enumSetValues(AccessFlag.class).build();\n\t\tfor (AccessFlag flag : flags) {\n\t\t\tmaskMap.put(flag.mask, flag);\n\t\t\tnameMap.put(flag.name, flag);\n\t\t\tflag.types.forEach(type -> typeMap.put(type, flag));\n\t\t}\n\t\tmaskToFlagsMap = maskMap;\n\t\tnameToFlagMap = nameMap;\n\t\ttypeToFlagsMap = typeMap;\n\t\tType.populateOrder();  // lazy load\n\t}\n\n\t/**\n\t * @param mask\n\t * \t\tAccess flags mask.\n\t *\n\t * @return Set of applicable flags. Some flags can have different meanings in different contexts.\n\t */\n\t@Nonnull\n\tpublic static Collection<AccessFlag> getFlags(int mask) {\n\t\treturn maskToFlagsMap.get(mask);\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tFlag type.\n\t *\n\t * @return Set of flags that belong to the type group.\n\t */\n\t@Nonnull\n\tpublic static Collection<AccessFlag> getApplicableFlags(@Nonnull Type type) {\n\t\treturn typeToFlagsMap.get(type);\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tFlag type.\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return Set of flags that belong to the type group in the access flag mask.\n\t */\n\t@Nonnull\n\tpublic static Set<AccessFlag> getApplicableFlags(@Nonnull Type type, int acc) {\n\t\tSet<AccessFlag> flags = EnumSet.noneOf(AccessFlag.class);\n\t\tfor (AccessFlag applicableFlag : getApplicableFlags(type))\n\t\t\tif (applicableFlag.has(acc))\n\t\t\t\tflags.add(applicableFlag);\n\t\treturn flags;\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tFlag type.\n\t * @param flags\n\t * \t\tCollection of flags.\n\t *\n\t * @return Sorted order of flags.\n\t */\n\t@Nonnull\n\tpublic static List<AccessFlag> sort(@Nonnull Type type,\n\t\t\t\t\t\t\t\t\t\t@Nonnull Collection<AccessFlag> flags) {\n\t\tList<AccessFlag> list;\n\t\tif (flags instanceof ArrayList<AccessFlag>) {\n\t\t\tlist = (List<AccessFlag>) flags;\n\t\t} else {\n\t\t\tlist = new ArrayList<>(flags);\n\t\t}\n\t\tlist.sort(type.recommendOrderComparator);\n\t\treturn list;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName of flag.\n\t *\n\t * @return Flag from name.\n\t */\n\t@Nullable\n\tpublic static AccessFlag getFlag(@Nullable String name) {\n\t\treturn nameToFlagMap.get(name);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tText containing one or more flags, separated by spaces.\n\t *\n\t * @return Flags from text.\n\t */\n\t@Nonnull\n\tpublic static List<AccessFlag> getFlags(@Nonnull String text) {\n\t\tString[] parts = text.split(\"\\\\s+\");\n\t\tList<AccessFlag> flags = new ArrayList<>();\n\t\tfor (String part : parts) {\n\t\t\tAccessFlag flag = getFlag(part);\n\t\t\tif (flag != null)\n\t\t\t\tflags.add(flag);\n\t\t}\n\t\treturn flags;\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t * @param flag\n\t * \t\tFlag to remove.\n\t *\n\t * @return Updated flag mask.\n\t */\n\tpublic static int removeFlag(int acc, int flag) {\n\t\treturn acc & ~flag;\n\t}\n\n\t/**\n\t * @param flags\n\t * \t\tArray of access flags.\n\t *\n\t * @return Access flag mask.\n\t */\n\tpublic static int createAccess(AccessFlag... flags) {\n\t\tint acc = 0;\n\t\tfor (AccessFlag flag : flags) acc = flag.set(acc);\n\t\treturn acc;\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t * @param flags\n\t * \t\tArray of flags to check exist in the mask.\n\t *\n\t * @return {@code true} if all specified flags exist in the mask.\n\t */\n\tpublic static boolean hasAll(int acc, AccessFlag... flags) {\n\t\tfor (AccessFlag flag : flags) {\n\t\t\tif (!flag.has(acc)) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t * @param flags\n\t * \t\tCollection of flags to check exist in the mask.\n\t *\n\t * @return {@code true} if all specified flags exist in the mask.\n\t */\n\tpublic static boolean hasAll(int acc, Collection<AccessFlag> flags) {\n\t\tfor (AccessFlag flag : flags) {\n\t\t\tif (!flag.has(acc)) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t * @param flags\n\t * \t\tArray of flags to check exist in the mask.\n\t *\n\t * @return {@code true} if none of the specified flags exist in the mask.\n\t */\n\tpublic static boolean hasNone(int acc, AccessFlag... flags) {\n\t\tfor (AccessFlag flag : flags) {\n\t\t\tif (flag.has(acc)) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t * @param flags\n\t * \t\tCollection of flags to check exist in the mask.\n\t *\n\t * @return {@code true} if none of the specified flags exist in the mask.\n\t */\n\tpublic static boolean hasNone(int acc, @Nonnull Collection<AccessFlag> flags) {\n\t\tfor (AccessFlag flag : flags) {\n\t\t\tif (flag.has(acc)) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t * @param flags\n\t * \t\tArray of flags to check exist in the mask.\n\t *\n\t * @return {@code true} if any of the specified flags exist in the mask.\n\t */\n\tpublic static boolean hasAny(int acc, AccessFlag... flags) {\n\t\tfor (AccessFlag flag : flags) {\n\t\t\tif (flag.has(acc)) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t * @param flags\n\t * \t\tCollection of flags to check exist in the mask.\n\t *\n\t * @return {@code true} if any of the specified flags exist in the mask.\n\t */\n\tpublic static boolean hasAny(int acc, @Nonnull Collection<AccessFlag> flags) {\n\t\tfor (AccessFlag flag : flags) {\n\t\t\tif (flag.has(acc)) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Flag mask value.\n\t */\n\tprivate final int mask;\n\t/**\n\t * Flag identifier.\n\t */\n\tprivate final String name;\n\t/**\n\t * If the flag is treated as a keyword by the Java compiler.\n\t */\n\tprivate final boolean isKeyword;\n\t/**\n\t * Applicable flag type groups.\n\t */\n\tprivate final Set<Type> types;\n\n\tAccessFlag(int mask, String name, boolean isKeyword, Set<Type> types) {\n\t\tthis.mask = mask;\n\t\tthis.name = name;\n\t\tthis.isKeyword = isKeyword;\n\t\tthis.types = Collections.unmodifiableSet(types);\n\t}\n\n\tAccessFlag(int mask, String name, boolean isKeyword, Type type) {\n\t\tthis(mask, name, isKeyword, Collections.singleton(type));\n\t}\n\n\tAccessFlag(int mask, String name, boolean isKeyword, Type firstType, Type... restTypes) {\n\t\tthis(mask, name, isKeyword, EnumSet.of(firstType, restTypes));\n\t}\n\n\t/**\n\t * @return Access flag mask.\n\t */\n\tpublic int getMask() {\n\t\treturn mask;\n\t}\n\n\t/**\n\t * @param access\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} if the flag contains the mask.\n\t */\n\tpublic boolean has(int access) {\n\t\treturn (access & mask) != 0;\n\t}\n\n\t/**\n\t * @param access\n\t * \t\tAccess flag mask.\n\t *\n\t * @return Mask combined with the given access flag mask.\n\t */\n\tpublic int set(int access) {\n\t\treturn access | mask;\n\t}\n\n\t/**\n\t * @param access\n\t * \t\tAccess flag mask.\n\t *\n\t * @return Mask without the current flag.\n\t */\n\tpublic int clear(int access) {\n\t\treturn access & (~mask);\n\t}\n\n\t/**\n\t * @return Applicable targets for the current flag.\n\t */\n\t@Nonnull\n\tpublic Set<Type> getTypes() {\n\t\treturn types;\n\t}\n\n\t/**\n\t * @return Flag identifier.\n\t */\n\t@Nonnull\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn getCodeFriendlyName();\n\t}\n\n\t/**\n\t * @param flags\n\t * \t\tCollection of flags.\n\t *\n\t * @return String representation of flags.\n\t */\n\t@Nonnull\n\tpublic static String toString(@Nonnull Iterable<AccessFlag> flags) {\n\t\t// Don't include ACC_SUPER, is meaningless\n\t\treturn StreamSupport.stream(flags.spliterator(), false)\n\t\t\t\t.filter(Objects::nonNull)\n\t\t\t\t.filter(v -> v != ACC_SUPER)\n\t\t\t\t.map(AccessFlag::toString)\n\t\t\t\t.collect(Collectors.joining(\" \"));\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tFlag type.\n\t * @param flags\n\t * \t\tCollection of flags.\n\t *\n\t * @return String representation of flags in sorted order.\n\t */\n\t@Nonnull\n\tpublic static String sortAndToString(@Nonnull Type type, @Nonnull Collection<AccessFlag> flags) {\n\t\tList<AccessFlag> list = sort(type, flags);\n\t\treturn toString(list);\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tFlag type.\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return String representation of flags in sorted order.\n\t */\n\t@Nonnull\n\tpublic static String sortAndToString(@Nonnull Type type, int acc) {\n\t\treturn sortAndToString(type, getApplicableFlags(type, acc));\n\t}\n\n\t/**\n\t * @return Flag identifier with surrounding comments if the identifier is not a Java keyword.\n\t */\n\t@Nonnull\n\tpublic String getCodeFriendlyName() {\n\t\treturn isKeyword ? this.name : \"/* \" + name + \" */\"; // comment out non-keyword\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the public flag.\n\t */\n\tpublic static boolean isPublic(int acc) {\n\t\treturn ACC_PUBLIC.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the private flag.\n\t */\n\tpublic static boolean isPrivate(int acc) {\n\t\treturn ACC_PRIVATE.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the protected flag.\n\t */\n\tpublic static boolean isProtected(int acc) {\n\t\treturn ACC_PROTECTED.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains neither a private, protected nor public flag.\n\t */\n\tpublic static boolean isPackage(int acc) {\n\t\treturn !isPrivate(acc) && !isProtected(acc) && !isPublic(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the static flag.\n\t */\n\tpublic static boolean isStatic(int acc) {\n\t\treturn ACC_STATIC.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the final flag.\n\t */\n\tpublic static boolean isFinal(int acc) {\n\t\treturn ACC_FINAL.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the synchronized flag.\n\t */\n\tpublic static boolean isSynchronized(int acc) {\n\t\treturn ACC_SYNCHRONIZED.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the super flag.\n\t */\n\tpublic static boolean isSuper(int acc) {\n\t\treturn ACC_SUPER.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the bridge flag.\n\t */\n\tpublic static boolean isBridge(int acc) {\n\t\treturn ACC_BRIDGE.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the volatile flag.\n\t */\n\tpublic static boolean isVolatile(int acc) {\n\t\treturn ACC_VOLATILE.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the varargs flag.\n\t */\n\tpublic static boolean isVarargs(int acc) {\n\t\treturn ACC_VARARGS.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the transient flag.\n\t */\n\tpublic static boolean isTransient(int acc) {\n\t\treturn ACC_TRANSIENT.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the native flag.\n\t */\n\tpublic static boolean isNative(int acc) {\n\t\treturn ACC_NATIVE.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the interface flag.\n\t */\n\tpublic static boolean isInterface(int acc) {\n\t\treturn ACC_INTERFACE.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the abstract flag.\n\t */\n\tpublic static boolean isAbstract(int acc) {\n\t\treturn ACC_ABSTRACT.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the strict <i>(Floating point math)</i> flag.\n\t */\n\tpublic static boolean isStrict(int acc) {\n\t\treturn ACC_STRICT.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the synthetic flag.\n\t */\n\tpublic static boolean isSynthetic(int acc) {\n\t\treturn ACC_SYNTHETIC.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the annotation flag.\n\t */\n\tpublic static boolean isAnnotation(int acc) {\n\t\treturn ACC_ANNOTATION.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the enum flag.\n\t */\n\tpublic static boolean isEnum(int acc) {\n\t\treturn ACC_ENUM.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the module flag.\n\t */\n\tpublic static boolean isModule(int acc) {\n\t\treturn ACC_MODULE.has(acc);\n\t}\n\n\t/**\n\t * @param acc\n\t * \t\tAccess flag mask.\n\t *\n\t * @return {@code true} when the mask contains the mandated flag.\n\t */\n\tpublic static boolean isMandated(int acc) {\n\t\treturn ACC_MANDATED.has(acc);\n\t}\n\n\t/**\n\t * Flag group.\n\t */\n\tpublic enum Type {\n\t\tCLASS(\"public abstract final strictfp interface annotation-interface enum module\"),\n\t\tINNER_CLASS(\"public protected private abstract static final strictfp interface annotation-interface enum module\"),\n\t\tMETHOD(\"public protected private abstract static final synchronized native strictfp\"),\n\t\tFIELD(\"public protected private static final transient volatile\"),\n\t\tMODULE(\"open mandated synthetic mandated\"),\n\t\tPARAM(\"final\");\n\n\t\tprivate final String order;\n\t\tprivate final List<AccessFlag> orderList = new ArrayList<>();\n\t\t/**\n\t\t * Unmodifiable view of `orderList`\n\t\t */\n\t\tpublic final List<AccessFlag> recommendOrder = Collections.unmodifiableList(orderList);\n\t\t/**\n\t\t * Comparator to sort flags by their recommended ordering.\n\t\t */\n\t\tpublic final Comparator<AccessFlag> recommendOrderComparator;\n\n\t\tType(String recommendOrder) {\n\t\t\tthis.order = recommendOrder;\n\t\t\tthis.recommendOrderComparator = Comparator.comparingInt(this::index);\n\t\t}\n\n\t\tprivate int index(AccessFlag flag) {\n\t\t\tif (recommendOrder.isEmpty()) return 0; // not initialized yet\n\t\t\tint idx = recommendOrder.indexOf(flag);\n\t\t\treturn idx == -1 ? Integer.MAX_VALUE : idx;\n\t\t}\n\n\t\tprivate static void populateOrder() {  // lazy load\n\t\t\tfor (Type type : values()) {\n\t\t\t\tList<AccessFlag> orderList = type.orderList;\n\t\t\t\torderList.clear();\n\t\t\t\torderList.addAll(parseModifierOrder(type.order));\n\t\t\t}\n\t\t}\n\n\t\tprivate static List<AccessFlag> parseModifierOrder(String string) {\n\t\t\tif (string == null) return Collections.emptyList();\n\t\t\treturn Arrays.stream(string.split(\" \"))\n\t\t\t\t\t.map(nameToFlagMap::get)\n\t\t\t\t\t.map(Objects::requireNonNull)\n\t\t\t\t\t.collect(Collectors.toList());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/AccessPatcher.java",
    "content": "package software.coley.recaf.util;\n\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.SystemInformation;\nimport software.coley.recaf.analytics.logging.Logging;\n\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodType;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * Utility to patch away access restrictions.\n * <p>\n * <b>You must initialize {@link ReflectUtil} first!</b>\n *\n * @author xDark\n */\nclass AccessPatcher {\n\tprivate static final Logger logger = Logging.get(AccessPatcher.class);\n\tprivate static boolean patched;\n\n\t// Deny all constructions.\n\tprivate AccessPatcher() {\n\t}\n\n\t/**\n\t * Patches JDK access restrictions.\n\t */\n\tstatic void patch() {\n\t\tif (patched) return;\n\t\ttry {\n\t\t\topenPackages();\n\t\t\tpatchReflectionFilters();\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Failed access patching on Java \" + SystemInformation.JAVA_VERSION +\n\t\t\t\t\t\"(\" + SystemInformation.JAVA_VM_VENDOR + \")\", t);\n\t\t} finally {\n\t\t\tpatched = true;\n\t\t}\n\t}\n\n\t/**\n\t * Opens all packages.\n\t */\n\tprivate static void openPackages() {\n\t\ttry {\n\t\t\tClass<?> context = AccessPatcher.class;\n\t\t\tSet<Module> modules = new HashSet<>();\n\t\t\tModule base = context.getModule();\n\t\t\tModuleLayer baseLayer = base.getLayer();\n\t\t\tif (baseLayer != null)\n\t\t\t\tmodules.addAll(baseLayer.modules());\n\t\t\tmodules.addAll(ModuleLayer.boot().modules());\n\t\t\tfor (ClassLoader cl = context.getClassLoader(); cl != null; cl = cl.getParent())\n\t\t\t\tmodules.add(cl.getUnnamedModule());\n\t\t\tMethodHandle export = ReflectUtil.lookup().findVirtual(Module.class, \"implAddOpens\", MethodType.methodType(void.class, String.class));\n\t\t\tfor (Module module : modules) {\n\t\t\t\tfor (String name : module.getPackages()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\texport.invokeExact(module, name);\n\t\t\t\t\t} catch (Exception ex) {\n\t\t\t\t\t\tlogger.error(\"Could not export package {} in module {}\", name, module);\n\t\t\t\t\t\tlogger.error(\"Root cause: \", ex);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (Throwable t) {\n\t\t\tthrow new IllegalStateException(\"Could not export packages\", t);\n\t\t}\n\t}\n\n\t/**\n\t * Patches reflection filters.\n\t */\n\tprivate static void patchReflectionFilters() {\n\t\tClass<?> klass;\n\t\ttry {\n\t\t\tklass = Class.forName(\"jdk.internal.reflect.Reflection\", true, null);\n\t\t} catch (ClassNotFoundException ex) {\n\t\t\tthrow new IllegalStateException(\"Unable to locate 'jdk.internal.reflect.Reflection' class\", ex);\n\t\t}\n\t\ttry {\n\t\t\tMethodHandles.Lookup lookup = ReflectUtil.lookup();\n\t\t\tlookup.findStaticSetter(klass, \"fieldFilterMap\", Map.class).invokeExact((Map<?, ?>) new HashMap<>());\n\t\t\tlookup.findStaticSetter(klass, \"methodFilterMap\", Map.class).invokeExact((Map<?, ?>) new HashMap<>());\n\t\t} catch (Throwable t) {\n\t\t\tthrow new IllegalStateException(\"Unable to patch reflection filters\", t);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/AsmInsnUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.util.BlwOpcodes;\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.FieldInsnNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.InsnNode;\nimport org.objectweb.asm.tree.IntInsnNode;\nimport org.objectweb.asm.tree.InvokeDynamicInsnNode;\nimport org.objectweb.asm.tree.JumpInsnNode;\nimport org.objectweb.asm.tree.LabelNode;\nimport org.objectweb.asm.tree.LdcInsnNode;\nimport org.objectweb.asm.tree.LocalVariableNode;\nimport org.objectweb.asm.tree.LookupSwitchInsnNode;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.MultiANewArrayInsnNode;\nimport org.objectweb.asm.tree.TableSwitchInsnNode;\nimport org.objectweb.asm.tree.TryCatchBlockNode;\nimport org.objectweb.asm.tree.VarInsnNode;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.BitSet;\nimport java.util.Collections;\nimport java.util.Deque;\nimport java.util.HashMap;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport static java.util.Collections.emptyList;\n\n/**\n * ASM instruction utilities.\n *\n * @author Matt Coley\n */\npublic class AsmInsnUtil implements Opcodes {\n\tprivate static final Map<Integer, String> opcodeToName = new HashMap<>();\n\n\tstatic {\n\t\tBlwOpcodes.getOpcodes().forEach((name, op) -> opcodeToName.put(op, name));\n\t}\n\n\t/**\n\t * @param opcode\n\t * \t\tSome opcode.\n\t *\n\t * @return Name of opcode.\n\t */\n\t@Nonnull\n\tpublic static String getInsnName(int opcode) {\n\t\treturn opcodeToName.getOrDefault(opcode, \"unknown\");\n\t}\n\n\t/**\n\t * Convert an instruction opcode to a {@link Handle} tag.\n\t *\n\t * @param opcode\n\t * \t\tSome method or field opcode.\n\t *\n\t * @return Relevant handle tag.\n\t *\n\t * @throws IllegalStateException\n\t * \t\tWhen the opcode does not have a respective handle tag.\n\t */\n\tpublic static int opcodeToTag(int opcode) {\n\t\treturn switch (opcode) {\n\t\t\tcase INVOKEINTERFACE -> H_INVOKEINTERFACE;\n\t\t\tcase INVOKESPECIAL -> H_INVOKESPECIAL;\n\t\t\tcase INVOKEVIRTUAL -> H_INVOKEVIRTUAL;\n\t\t\tcase INVOKESTATIC -> H_INVOKESTATIC;\n\t\t\tcase GETFIELD -> H_GETFIELD;\n\t\t\tcase GETSTATIC -> H_GETSTATIC;\n\t\t\tcase PUTFIELD -> H_PUTFIELD;\n\t\t\tcase PUTSTATIC -> H_PUTSTATIC;\n\t\t\tdefault -> throw new IllegalStateException(\"Unsupported opcode: \" + opcode);\n\t\t};\n\t}\n\n\t/**\n\t * Convert a {@link Handle} tag to an instruction opcode.\n\t *\n\t * @param tag\n\t * \t\tSome method or field handle tag.\n\t *\n\t * @return Relevant instruction opcode.\n\t *\n\t * @throws IllegalStateException\n\t * \t\tWhen the tag does not have a respective handle opcode.\n\t */\n\tpublic static int tagToOpcode(int tag) {\n\t\treturn switch (tag) {\n\t\t\tcase H_INVOKEINTERFACE -> INVOKEINTERFACE;\n\t\t\tcase H_INVOKESPECIAL -> INVOKESPECIAL;\n\t\t\tcase H_INVOKEVIRTUAL -> INVOKEVIRTUAL;\n\t\t\tcase H_INVOKESTATIC -> INVOKESTATIC;\n\t\t\tcase H_GETFIELD -> GETFIELD;\n\t\t\tcase H_GETSTATIC -> GETSTATIC;\n\t\t\tcase H_PUTFIELD -> PUTFIELD;\n\t\t\tcase H_PUTSTATIC -> PUTSTATIC;\n\t\t\tdefault -> throw new IllegalStateException(\"Unsupported tag: \" + tag);\n\t\t};\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tInstruction to get index of.\n\t *\n\t * @return Index of instruction in containing method.\n\t */\n\tpublic static int indexOf(@Nonnull AbstractInsnNode insn) {\n\t\tint i = 0;\n\t\twhile ((insn = insn.getPrevious()) != null)\n\t\t\ti++;\n\t\treturn i;\n\t}\n\n\t/**\n\t * @param varInsn\n\t * \t\tVariable instruction.\n\t *\n\t * @return Type that encompasses the variable being accessed/written to,\n\t */\n\t@Nonnull\n\tpublic static Type getTypeForVarInsn(@Nonnull VarInsnNode varInsn) {\n\t\treturn switch (varInsn.getOpcode()) {\n\t\t\tcase Opcodes.ISTORE, Opcodes.ILOAD -> Type.INT_TYPE;\n\t\t\tcase Opcodes.LSTORE, Opcodes.LLOAD -> Type.LONG_TYPE;\n\t\t\tcase Opcodes.FSTORE, Opcodes.FLOAD -> Type.FLOAT_TYPE;\n\t\t\tcase Opcodes.DSTORE, Opcodes.DLOAD -> Type.DOUBLE_TYPE;\n\t\t\tdefault -> Types.OBJECT_TYPE;\n\t\t};\n\t}\n\n\t/**\n\t * @param op\n\t * \t\tInstruction opcode.\n\t *\n\t * @return {@code true} when it is any variable storing instruction.\n\t */\n\tpublic static boolean isVarStore(int op) {\n\t\treturn op >= ISTORE && op <= ASTORE;\n\t}\n\n\t/**\n\t * @param op\n\t * \t\tInstruction opcode.\n\t *\n\t * @return {@code true} when it is any variable loading instruction.\n\t */\n\tpublic static boolean isVarLoad(int op) {\n\t\treturn op >= ILOAD && op <= ALOAD;\n\t}\n\n\t/**\n\t * @param index\n\t * \t\tVariable index.\n\t * @param variableType\n\t * \t\tVariable type.\n\t *\n\t * @return Load instruction for variable type at the given index.\n\t */\n\t@Nonnull\n\tpublic static VarInsnNode createVarLoad(int index, @Nonnull Type variableType) {\n\t\treturn createVarLoad(index, variableType.getSort());\n\t}\n\n\t/**\n\t * @param index\n\t * \t\tVariable index.\n\t * @param typeSort\n\t * \t\tVariable type sort.\n\t *\n\t * @return Load instruction for variable type at the given index.\n\t */\n\t@Nonnull\n\tpublic static VarInsnNode createVarLoad(int index, int typeSort) {\n\t\treturn switch (typeSort) {\n\t\t\tcase Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT -> new VarInsnNode(ILOAD, index);\n\t\t\tcase Type.FLOAT -> new VarInsnNode(FLOAD, index);\n\t\t\tcase Type.DOUBLE -> new VarInsnNode(DLOAD, index);\n\t\t\tcase Type.LONG -> new VarInsnNode(LLOAD, index);\n\t\t\tdefault -> new VarInsnNode(ALOAD, index);\n\t\t};\n\t}\n\n\t/**\n\t * @param index\n\t * \t\tVariable index.\n\t * @param variableType\n\t * \t\tVariable type.\n\t *\n\t * @return Store instruction for variable type at the given index.\n\t */\n\t@Nonnull\n\tpublic static VarInsnNode createVarStore(int index, @Nonnull Type variableType) {\n\t\treturn switch (variableType.getSort()) {\n\t\t\tcase Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT -> new VarInsnNode(ISTORE, index);\n\t\t\tcase Type.FLOAT -> new VarInsnNode(FSTORE, index);\n\t\t\tcase Type.DOUBLE -> new VarInsnNode(DSTORE, index);\n\t\t\tcase Type.LONG -> new VarInsnNode(LSTORE, index);\n\t\t\tdefault -> new VarInsnNode(ASTORE, index);\n\t\t};\n\t}\n\n\t/**\n\t * @param access\n\t * \t\tMethod access flags.\n\t *\n\t * @return Invoke opcode for method with the given access flags.\n\t */\n\tpublic static int getInvokeForMethod(int access) {\n\t\tif ((access & Opcodes.ACC_STATIC) != 0)\n\t\t\treturn INVOKESTATIC;\n\t\tif ((access & Opcodes.ACC_INTERFACE) != 0)\n\t\t\treturn INVOKEINTERFACE;\n\t\tif ((access & Opcodes.ACC_PRIVATE) != 0)\n\t\t\treturn INVOKESPECIAL;\n\t\treturn INVOKEVIRTUAL;\n\t}\n\n\t/**\n\t * Checks in the given method for local vars that have label references\n\t * that do not exist in the method's instructions list.\n\t *\n\t * @param method\n\t * \t\tMethod to fix local variables of.\n\t */\n\tpublic static void fixMissingVariableLabels(@Nonnull MethodNode method) {\n\t\t// Must not be abstract\n\t\tInsnList instructions = method.instructions;\n\t\tif (instructions == null || instructions.size() == 0)\n\t\t\treturn;\n\n\t\t// Must have variables to fix\n\t\tList<LocalVariableNode> variables = method.localVariables;\n\t\tif (variables == null || variables.isEmpty())\n\t\t\treturn;\n\n\t\t// Find or create first/last labels\n\t\tLabelNode firstLabel = null;\n\t\tLabelNode lastLabel = null;\n\t\tfor (int i = 0; i < instructions.size(); i++)\n\t\t\tif (instructions.get(i) instanceof LabelNode label) {\n\t\t\t\tfirstLabel = label;\n\t\t\t\tbreak;\n\t\t\t}\n\t\tfor (int i = instructions.size() - 1; i >= 0; i--)\n\t\t\tif (instructions.get(i) instanceof LabelNode label) {\n\t\t\t\tlastLabel = label;\n\t\t\t\tbreak;\n\t\t\t}\n\t\tif (firstLabel == null)\n\t\t\tinstructions.insert(firstLabel = new LabelNode());\n\t\tif (lastLabel == null || lastLabel == firstLabel)\n\t\t\tinstructions.add(lastLabel = new LabelNode());\n\n\t\t// Find any variables that have invalid labels and reassign them if needed\n\t\tfor (LocalVariableNode variable : variables) {\n\t\t\tint start = instructions.indexOf(variable.start);\n\t\t\tint end = instructions.indexOf(variable.end);\n\n\t\t\t// Variable start must be a valid label in the method, and occur before the end label\n\t\t\tif (start < 0 || start > end)\n\t\t\t\tvariable.start = firstLabel;\n\n\t\t\t// End label must be a valid label in the method\n\t\t\tif (end < 0)\n\t\t\t\tvariable.end = lastLabel;\n\t\t}\n\t}\n\n\t/**\n\t * @param op\n\t * \t\tInstruction opcode.\n\t *\n\t * @return {@code true} if the instruction pushes a constant value onto the stack.\n\t */\n\tpublic static boolean isConstValue(int op) {\n\t\treturn op >= ACONST_NULL && op <= LDC;\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tInstruction to check.\n\t *\n\t * @return {@code true} if the instruction pushes a constant {@code int} value onto the stack.\n\t */\n\tpublic static boolean isConstIntValue(@Nonnull AbstractInsnNode insn) {\n\t\tint op = insn.getOpcode();\n\t\tif (op == LDC && ((LdcInsnNode) insn).cst instanceof Integer)\n\t\t\treturn true;\n\t\treturn (op >= ICONST_M1 && op <= ICONST_5) || op == SIPUSH || op == BIPUSH;\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tType to push.\n\t *\n\t * @return Instruction to push a default value of the given type onto the stack.\n\t */\n\t@Nonnull\n\tpublic static AbstractInsnNode getDefaultValue(@Nonnull Type type) {\n\t\treturn switch (type.getSort()) {\n\t\t\tcase Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT -> new InsnNode(ICONST_0);\n\t\t\tcase Type.LONG -> new InsnNode(LCONST_0);\n\t\t\tcase Type.FLOAT -> new InsnNode(FCONST_0);\n\t\t\tcase Type.DOUBLE -> new InsnNode(DCONST_0);\n\t\t\tdefault -> new InsnNode(ACONST_NULL);\n\t\t};\n\t}\n\n\t/**\n\t * Create an instruction to hold a given {@code int} value.\n\t *\n\t * @param value\n\t * \t\tValue to hold.\n\t *\n\t * @return Insn with const value.\n\t */\n\t@Nonnull\n\tpublic static AbstractInsnNode intToInsn(int value) {\n\t\tswitch (value) {\n\t\t\tcase -1:\n\t\t\t\treturn new InsnNode(ICONST_M1);\n\t\t\tcase 0:\n\t\t\t\treturn new InsnNode(ICONST_0);\n\t\t\tcase 1:\n\t\t\t\treturn new InsnNode(ICONST_1);\n\t\t\tcase 2:\n\t\t\t\treturn new InsnNode(ICONST_2);\n\t\t\tcase 3:\n\t\t\t\treturn new InsnNode(ICONST_3);\n\t\t\tcase 4:\n\t\t\t\treturn new InsnNode(ICONST_4);\n\t\t\tcase 5:\n\t\t\t\treturn new InsnNode(ICONST_5);\n\t\t\tdefault:\n\t\t\t\tif (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {\n\t\t\t\t\treturn new IntInsnNode(BIPUSH, value);\n\t\t\t\t} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {\n\t\t\t\t\treturn new IntInsnNode(SIPUSH, value);\n\t\t\t\t} else {\n\t\t\t\t\treturn new LdcInsnNode(value);\n\t\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Create an instruction to hold a given {@code float} value.\n\t *\n\t * @param value\n\t * \t\tValue to hold.\n\t *\n\t * @return Insn with const value.\n\t */\n\t@Nonnull\n\tpublic static AbstractInsnNode floatToInsn(float value) {\n\t\tif (value == 0)\n\t\t\treturn new InsnNode(FCONST_0);\n\t\tif (value == 1)\n\t\t\treturn new InsnNode(FCONST_1);\n\t\tif (value == 2)\n\t\t\treturn new InsnNode(FCONST_2);\n\t\treturn new LdcInsnNode(value);\n\t}\n\n\t/**\n\t * Create an instruction to hold a given {@code double} value.\n\t *\n\t * @param value\n\t * \t\tValue to hold.\n\t *\n\t * @return Insn with const value.\n\t */\n\t@Nonnull\n\tpublic static AbstractInsnNode doubleToInsn(double value) {\n\t\tif (value == 0)\n\t\t\treturn new InsnNode(DCONST_0);\n\t\tif (value == 1)\n\t\t\treturn new InsnNode(DCONST_1);\n\t\treturn new LdcInsnNode(value);\n\t}\n\n\t/**\n\t * Create an instruction to hold a given {@code long} value.\n\t *\n\t * @param value\n\t * \t\tValue to hold.\n\t *\n\t * @return Insn with const value.\n\t */\n\t@Nonnull\n\tpublic static AbstractInsnNode longToInsn(long value) {\n\t\tif (value == 0)\n\t\t\treturn new InsnNode(LCONST_0);\n\t\tif (value == 1)\n\t\t\treturn new InsnNode(LCONST_1);\n\t\treturn new LdcInsnNode(value);\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tSome type.\n\t *\n\t * @return Method return instruction opcode for the given type.\n\t */\n\tpublic static int getReturnOpcode(@Nonnull Type type) {\n\t\treturn switch (type.getSort()) {\n\t\t\tcase Type.VOID -> RETURN;\n\t\t\tcase Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT -> IRETURN;\n\t\t\tcase Type.FLOAT -> FRETURN;\n\t\t\tcase Type.LONG -> LRETURN;\n\t\t\tcase Type.DOUBLE -> DRETURN;\n\t\t\tdefault -> ARETURN;\n\t\t};\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tInstruction to check.\n\t *\n\t * @return {@code true} when it is a return operation.\n\t */\n\tpublic static boolean isReturn(@Nullable AbstractInsnNode insn) {\n\t\tif (insn == null)\n\t\t\treturn false;\n\t\treturn isReturn(insn.getOpcode());\n\t}\n\n\t/**\n\t * @param op\n\t * \t\tInstruction opcode.\n\t *\n\t * @return {@code true} when it is a return operation.\n\t */\n\tpublic static boolean isReturn(int op) {\n\t\treturn switch (op) {\n\t\t\tcase IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, RETURN -> true;\n\t\t\tdefault -> false;\n\t\t};\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tInstruction to check.\n\t *\n\t * @return {@code true} when it is a label.\n\t */\n\tpublic static boolean isLabel(@Nullable AbstractInsnNode insn) {\n\t\tif (insn == null)\n\t\t\treturn false;\n\t\treturn insn.getType() == AbstractInsnNode.LABEL;\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tInstruction to check.\n\t *\n\t * @return {@code true} if the instruction modifies the control flow.\n\t */\n\tpublic static boolean isFlowControl(@Nullable AbstractInsnNode insn) {\n\t\tif (insn == null)\n\t\t\treturn false;\n\t\tint type = insn.getType();\n\t\treturn type == AbstractInsnNode.JUMP_INSN ||\n\t\t\t\ttype == AbstractInsnNode.TABLESWITCH_INSN ||\n\t\t\t\ttype == AbstractInsnNode.LOOKUPSWITCH_INSN ||\n\t\t\t\tinsn.getOpcode() == ATHROW || insn.getOpcode() == RET;\n\t}\n\n\t/**\n\t * Any instruction that is matched by this should be safe to use as the last instruction in a method.\n\t * If the last instruction in a method yields {@code false} then there is dangling control flow and\n\t * the code is not verifier compatible.\n\t *\n\t * @param op\n\t * \t\tInstruction opcode.\n\t *\n\t * @return {@code true} when the opcode represents an instruction that\n\t * terminates the method flow, or consistently takes a branch.\n\t */\n\tpublic static boolean isTerminalOrAlwaysTakeFlowControl(int op) {\n\t\treturn switch (op) {\n\t\t\tcase IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, RETURN, ATHROW, TABLESWITCH, LOOKUPSWITCH, GOTO, JSR ->\n\t\t\t\t\ttrue;\n\t\t\tdefault -> false;\n\t\t};\n\t}\n\n\t/**\n\t * @param switchInsn\n\t * \t\tSwitch instruction.\n\t *\n\t * @return {@code true} when all destinations are identical.\n\t */\n\tpublic static boolean isSwitchEffectiveGoto(@Nonnull TableSwitchInsnNode switchInsn) {\n\t\tLabelNode target = switchInsn.dflt;\n\t\tfor (LabelNode label : switchInsn.labels)\n\t\t\tif (label != target)\n\t\t\t\treturn false;\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param switchInsn\n\t * \t\tSwitch instruction.\n\t *\n\t * @return {@code true} when all destinations are identical.\n\t */\n\tpublic static boolean isSwitchEffectiveGoto(@Nonnull LookupSwitchInsnNode switchInsn) {\n\t\tLabelNode target = switchInsn.dflt;\n\t\tfor (LabelNode label : switchInsn.labels)\n\t\t\tif (label != target)\n\t\t\t\treturn false;\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tInstruction to check.\n\t *\n\t * @return {@code true} if the instruction represents metadata such as line numbers, stack frames, or a label/offset.\n\t */\n\tpublic static boolean isMetaData(@Nonnull AbstractInsnNode insn) {\n\t\t// The following instruction types set their opcode as '-1'\n\t\t// - FrameNode\n\t\t// - LabelNode\n\t\t// - LineNumberNode\n\t\treturn insn.getOpcode() == -1;\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tInstruction to begin from.\n\t *\n\t * @return Next non-metadata instruction.\n\t * Can be {@code null} for no next instruction at the end of a method.\n\t */\n\t@Nullable\n\tpublic static AbstractInsnNode getNextInsn(@Nonnull AbstractInsnNode insn) {\n\t\tAbstractInsnNode next = insn.getNext();\n\t\twhile (next != null && isMetaData(next))\n\t\t\tnext = next.getNext();\n\t\treturn next;\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tInstruction to begin from.\n\t *\n\t * @return Previous non-metadata instruction.\n\t * Can be {@code null} for no previous instruction at the start of a method.\n\t */\n\t@Nullable\n\tpublic static AbstractInsnNode getPreviousInsn(@Nonnull AbstractInsnNode insn) {\n\t\tAbstractInsnNode prev = insn.getPrevious();\n\t\twhile (prev != null && isMetaData(prev))\n\t\t\tprev = prev.getPrevious();\n\t\treturn prev;\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tInstruction to begin from.\n\t *\n\t * @return Next non-metadata instruction, following {@link Opcodes#GOTO} if found.\n\t * Can be {@code null} for no next instruction at the end of a method.\n\t */\n\t@Nullable\n\tpublic static AbstractInsnNode getNextFollowGoto(@Nonnull AbstractInsnNode insn) {\n\t\tAbstractInsnNode next = getNextInsn(insn);\n\t\twhile (next != null && next.getOpcode() == GOTO) {\n\t\t\tJumpInsnNode jin = (JumpInsnNode) next;\n\t\t\tnext = getNextInsn(jin.label);\n\t\t}\n\t\treturn next;\n\t}\n\n\t/**\n\t * Primarily used for debugging and passing to {@link BlwUtil#toString(Iterable)}.\n\t *\n\t * @param insn\n\t * \t\tMidpoint instruction.\n\t * @param back\n\t * \t\tSteps backwards to take and include in the output.\n\t * @param forward\n\t * \t\tSteps forward to take and include in the output.\n\t *\n\t * @return List of instructions surrounding the given instruction.\n\t */\n\t@Nonnull\n\tpublic static List<AbstractInsnNode> getSurrounding(@Nonnull AbstractInsnNode insn, int back, int forward) {\n\t\tList<AbstractInsnNode> list = new ArrayList<>(back + forward + 1);\n\t\tAbstractInsnNode t = insn;\n\t\tfor (int i = 0; i < back; i++) {\n\t\t\tt = getPreviousInsn(t);\n\t\t\tif (t == null)\n\t\t\t\tbreak;\n\t\t\tlist.addFirst(t);\n\t\t}\n\t\tt = insn;\n\t\tlist.add(t);\n\t\tfor (int i = 0; i < forward; i++) {\n\t\t\tt = getNextInsn(t);\n\t\t\tif (t == null)\n\t\t\t\tbreak;\n\t\t\tlist.add(t);\n\t\t}\n\t\treturn list;\n\t}\n\n\t/**\n\t * @param method\n\t * \t\tContaining method of the instruction.\n\t * @param insn\n\t * \t\tInstruction to search for.\n\t *\n\t * @return The {@link TryCatchBlockNode} of a try start-end range containing the given instruction.\n\t */\n\t@Nullable\n\tpublic static TryCatchBlockNode getContainingTryBlock(@Nonnull MethodNode method, @Nonnull AbstractInsnNode insn) {\n\t\tfor (TryCatchBlockNode block : method.tryCatchBlocks) {\n\t\t\tAbstractInsnNode i = block.start;\n\t\t\twhile (i != null && i != block.end && i != insn)\n\t\t\t\ti = i.getNext();\n\t\t\tif (i == insn)\n\t\t\t\treturn block;\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * Check if the given block of instructions has a catch block handler target.\n\t * <p>\n\t * When {@code includeFirstInsn=true} this will include match the first instruction of the block if it is\n\t * the label outlined by {@link TryCatchBlockNode#handler}. Otherwise, if {@code false} is passed, then the handler\n\t * is somewhere in the middle of the block.\n\t *\n\t * @param method\n\t * \t\tContaining method to analyze control flow of.\n\t * @param block\n\t * \t\tSome arbitrary list of instructions representing a block of code.\n\t * @param includeFirstInsn\n\t *        {@code true} to count the first instruction of the {@code block} which is assumed to be a\n\t *        {@link LabelNode} that is a candidate for being a value of {@link TryCatchBlockNode#handler}.\n\t *\n\t * @return {@code true} when the block contains a label that is a catch block handler.\n\t */\n\tpublic static boolean hasHandlerFlowIntoBlock(@Nonnull MethodNode method,\n\t                                              @Nonnull List<AbstractInsnNode> block,\n\t                                              boolean includeFirstInsn) {\n\t\tint start = includeFirstInsn ? 0 : 1;\n\t\tfor (TryCatchBlockNode tryCatchBlock : method.tryCatchBlocks)\n\t\t\tif (block.indexOf(tryCatchBlock.handler) >= start)\n\t\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * Check if the given instruction can throw an exception.\n\t *\n\t * @param insn\n\t * \t\tInstruction to check.\n\t *\n\t * @return {@code true} when the instruction can throw an exception.\n\t */\n\tpublic static boolean canThrow(@Nonnull AbstractInsnNode insn) {\n\t\tint op = insn.getOpcode();\n\t\tif (op == ATHROW) // Obvious case\n\t\t\treturn true;\n\t\tif (insn instanceof MethodInsnNode) // Method calls can throw.\n\t\t\treturn true;\n\t\t// NullPointerException\n\t\treturn op == GETFIELD || op == PUTFIELD || op == ARRAYLENGTH ||\n\t\t\t\t// NullPointerException, ArrayIndexOutOfBoundsException\n\t\t\t\t(op >= IALOAD && op <= AASTORE) ||\n\t\t\t\t// IllegalMonitorStateException\n\t\t\t\top == MONITORENTER || op == MONITOREXIT ||\n\t\t\t\t// ArithmeticException\n\t\t\t\top == IDIV || op == IREM || op == LDIV || op == LREM;\n\t}\n\n\t/**\n\t * Check if the given block of instructions is referenced by explicit control flow instructions.\n\t * <p>\n\t * This does not cover the following cases:\n\t * <ul>\n\t *     <li>Linear control flow where the previous instruction continues normally to the next instruction.</li>\n\t * </ul>\n\t * This is checks for explicit control flow references such as:\n\t * <ul>\n\t *     <li>{@link JumpInsnNode}</li>\n\t *     <li>{@link TableSwitchInsnNode}</li>\n\t *     <li>{@link LookupSwitchInsnNode}</li>\n\t * </ul>\n\t *\n\t * @param method\n\t * \t\tContaining method to analyze control flow of.\n\t * @param block\n\t * \t\tSome arbitrary list of instructions representing a block of code.\n\t *\n\t * @return {@code true} when the method has control flow outside the given block that flows into the given block.\n\t * {@code false} when the given block is never explicitly flowed into via control flow instructions.\n\t */\n\tpublic static boolean hasInboundFlowReferences(@Nonnull MethodNode method, @Nonnull List<AbstractInsnNode> block) {\n\t\tSet<LabelNode> labels = Collections.newSetFromMap(new IdentityHashMap<>());\n\t\tfor (AbstractInsnNode insn : block)\n\t\t\tif (insn.getType() == AbstractInsnNode.LABEL)\n\t\t\t\tlabels.add((LabelNode) insn);\n\n\t\t// If the block has no labels, then there cannot be any inbound references.\n\t\tif (labels.isEmpty())\n\t\t\treturn false;\n\n\t\t// No control flow instruction should point to this block *at all*.\n\t\tfor (AbstractInsnNode insn : method.instructions) {\n\t\t\t// Skip instructions in the given block.\n\t\t\tif (block.contains(insn))\n\t\t\t\tcontinue;\n\n\t\t\t// Check for control-flow instructions pointing to a location in the given block.\n\t\t\tif (insn instanceof JumpInsnNode jump && block.contains(jump.label))\n\t\t\t\treturn true;\n\t\t\tif (insn instanceof TableSwitchInsnNode tswitch) {\n\t\t\t\tif (labels.contains(tswitch.dflt))\n\t\t\t\t\treturn true;\n\t\t\t\tfor (LabelNode label : tswitch.labels)\n\t\t\t\t\tif (labels.contains(label))\n\t\t\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (insn instanceof LookupSwitchInsnNode lswitch) {\n\t\t\t\tif (labels.contains(lswitch.dflt))\n\t\t\t\t\treturn true;\n\t\t\t\tfor (LabelNode label : lswitch.labels)\n\t\t\t\t\tif (labels.contains(label))\n\t\t\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Populate control flow maps for a method.\n\t *\n\t * @param method\n\t * \t\tMethod to analyze.\n\t * @param successorMap\n\t * \t\tOutput successor map.\n\t * @param predecessorMap\n\t * \t\tOutput predecessor map.\n\t */\n\t@SuppressWarnings(\"StatementWithEmptyBody\")\n\tpublic static void populateFlowMaps(@Nonnull MethodNode method,\n\t                                    @Nonnull Int2ObjectMap<List<Integer>> successorMap,\n\t                                    @Nonnull Int2ObjectMap<List<Integer>> predecessorMap) {\n\t\tpopulateFlowMaps(method, successorMap, predecessorMap, true);\n\t}\n\n\t/**\n\t * Populate control flow maps for a method.\n\t *\n\t * @param method\n\t * \t\tMethod to analyze.\n\t * @param successorMap\n\t * \t\tOutput successor map.\n\t * @param predecessorMap\n\t * \t\tOutput predecessor map.\n\t * @param followExceptionFlow\n\t * \t\tFlag to indicate whether to include exception flow in the maps.\n\t * \t\tWhen {@code true}, instructions that can throw exceptions will have their respective catch block handlers included as successors.\n\t * \t\tWhen {@code false}, exception flow is ignored and only explicit control flow instructions are considered.\n\t */\n\t@SuppressWarnings(\"StatementWithEmptyBody\")\n\tpublic static void populateFlowMaps(@Nonnull MethodNode method,\n\t                                    @Nonnull Int2ObjectMap<List<Integer>> successorMap,\n\t                                    @Nonnull Int2ObjectMap<List<Integer>> predecessorMap,\n\t                                    boolean followExceptionFlow) {\n\t\tInsnList instructions = method.instructions;\n\t\tint size = instructions.size();\n\t\tfor (int i = 0; i < size; i++) {\n\t\t\tAbstractInsnNode insn = instructions.get(i);\n\t\t\tif (insn == null) // Sanity check, can happen if you don't use ASM the way it wants you to.\n\t\t\t\tthrow new IllegalStateException(\"You broke the method instruction list\");\n\t\t\tList<Integer> ss = new ArrayList<>();\n\t\t\tint op = insn.getOpcode();\n\t\t\tif (op >= IRETURN && op <= RETURN || op == ATHROW) {\n\t\t\t\t// Terminal instructions, no successors.\n\t\t\t} else if (insn instanceof JumpInsnNode jin) {\n\t\t\t\t// Jump instructions have their target and fall-through (excluding goto/jsr) as successors.\n\t\t\t\tint target = instructions.indexOf(jin.label);\n\t\t\t\tss.add(target);\n\t\t\t\tif (op != GOTO && op != JSR)\n\t\t\t\t\tif (i + 1 < size)\n\t\t\t\t\t\tss.add(i + 1);\n\t\t\t} else if (insn instanceof TableSwitchInsnNode tswitch) {\n\t\t\t\t// Switch instructions have all their targets as successors.\n\t\t\t\tss.add(instructions.indexOf(tswitch.dflt));\n\t\t\t\tfor (LabelNode label : tswitch.labels)\n\t\t\t\t\tss.add(instructions.indexOf(label));\n\t\t\t} else if (insn instanceof LookupSwitchInsnNode lswitch) {\n\t\t\t\t// Same as above.\n\t\t\t\tss.add(instructions.indexOf(lswitch.dflt));\n\t\t\t\tfor (LabelNode label : lswitch.labels)\n\t\t\t\t\tss.add(instructions.indexOf(label));\n\t\t\t} else {\n\t\t\t\t// All other instructions just flow to the next instruction.\n\t\t\t\tif (i + 1 < size)\n\t\t\t\t\tss.add(i + 1);\n\t\t\t}\n\n\t\t\t// Add exception successors.\n\t\t\tif (followExceptionFlow && canThrow(insn)) {\n\t\t\t\t// TODO: Used to filter if the contained instructions could actually throw the handled type.\n\t\t\t\t//  IE, a block of 'nop' instructions wouldn't actually throw anything.\n\t\t\t\tfor (TryCatchBlockNode block : method.tryCatchBlocks) {\n\t\t\t\t\tint start = instructions.indexOf(block.start);\n\t\t\t\t\tint end = instructions.indexOf(block.end);\n\t\t\t\t\tif (start <= i && i < end)\n\t\t\t\t\t\tss.add(instructions.indexOf(block.handler));\n\t\t\t\t}\n\t\t\t}\n\t\t\tsuccessorMap.put(i, ss);\n\t\t}\n\n\t\t// Populate predecessor map from successor map.\n\t\tfor (int i = 0; i < size; i++) {\n\t\t\tfor (int s : successorMap.getOrDefault(i, emptyList()))\n\t\t\t\tpredecessorMap.computeIfAbsent(s, _ -> new ArrayList<>()).add(i);\n\t\t}\n\t}\n\n\t/**\n\t * Compute reachable instructions in a method.\n\t *\n\t * @param size\n\t * \t\tSize of {@link InsnList} for a method's instructions.\n\t * @param successors\n\t * \t\tControl flow successor map for the method. See {@link #populateFlowMaps(MethodNode, Int2ObjectMap, Int2ObjectMap)}\n\t *\n\t * @return BitSet of reachable instructions in the method.\n\t */\n\t@Nonnull\n\tpublic static BitSet computeReachable(int size, @Nonnull Int2ObjectMap<List<Integer>> successors) {\n\t\tBitSet reachable = new BitSet(size);\n\t\tif (size == 0)\n\t\t\treturn reachable;\n\n\t\t// Compute reachable instructions. Start at the beginning.\n\t\tDeque<Integer> unprocessed = new ArrayDeque<>();\n\t\treachable.set(0);\n\t\tunprocessed.add(0);\n\t\twhile (!unprocessed.isEmpty()) {\n\t\t\t// Follow successors.\n\t\t\tint current = unprocessed.poll();\n\t\t\tfor (int s : successors.getOrDefault(current, List.of())) {\n\t\t\t\tif (!reachable.get(s)) {\n\t\t\t\t\treachable.set(s);\n\t\t\t\t\tunprocessed.add(s);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn reachable;\n\t}\n\n\t/**\n\t * Computes the size of stack items consumed for the given operation of the instruction.\n\t * <ul>\n\t *     <li>This considers {@code long} and {@code double} types taking two spaces.</li>\n\t *     <li>This considers {@code dup} like instructions to not <i>\"consume\"</i> values.</li>\n\t * </ul>\n\t *\n\t * @param insn\n\t * \t\tInstruction to compute for.\n\t *\n\t * @return Size of stack consumed. Never negative.\n\t *\n\t * @see #getSizeProduced(AbstractInsnNode)\n\t * @see <a href=\"https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-6.html#jvms-6.5\">JVMS 6.5</a>\n\t */\n\tpublic static int getSizeConsumed(@Nonnull AbstractInsnNode insn) {\n\t\t// Just a note about this consumed and the other produced method, ASM has a giant\n\t\t// array in MethodWriter that has the total delta, but in some cases you want to know\n\t\t// both components that combine into the final delta. It also skips entries that are\n\t\t// not constant, so we would still need some edge-case handling anyways.\n\t\tint op = insn.getOpcode();\n\t\tint type = insn.getType();\n\t\tif (type == AbstractInsnNode.MULTIANEWARRAY_INSN) {\n\t\t\treturn ((MultiANewArrayInsnNode) insn).dims;\n\t\t} else if (type == AbstractInsnNode.METHOD_INSN) {\n\t\t\tMethodInsnNode min = (MethodInsnNode) insn;\n\t\t\tType methodType = Type.getMethodType(min.desc);\n\t\t\tint count = op == INVOKESTATIC ? 0 : 1;\n\t\t\tfor (Type argType : methodType.getArgumentTypes()) {\n\t\t\t\tcount += argType.getSize();\n\t\t\t}\n\t\t\treturn count;\n\t\t} else if (type == AbstractInsnNode.INVOKE_DYNAMIC_INSN) {\n\t\t\tType methodType = Type.getMethodType(((InvokeDynamicInsnNode) insn).desc);\n\t\t\tint count = 0;\n\t\t\tfor (Type argType : methodType.getArgumentTypes()) {\n\t\t\t\tcount += argType.getSize();\n\t\t\t}\n\t\t\treturn count;\n\t\t} else if (type == AbstractInsnNode.FIELD_INSN) {\n\t\t\tFieldInsnNode fin = (FieldInsnNode) insn;\n\t\t\tif (op == GETSTATIC)\n\t\t\t\treturn 0;\n\t\t\tif (op == GETFIELD)\n\t\t\t\treturn 1; // owner-value\n\n\t\t\tif (op == PUTSTATIC)\n\t\t\t\treturn Type.getType(fin.desc).getSize(); // value (can be wide)\n\t\t\tif (op == PUTFIELD)\n\t\t\t\treturn 1 + Type.getType(fin.desc).getSize(); // owner, value (can be wide)\n\t\t} else if (type == AbstractInsnNode.FRAME ||\n\t\t\t\ttype == AbstractInsnNode.LABEL ||\n\t\t\t\ttype == AbstractInsnNode.LINE) {\n\t\t\treturn 0;\n\t\t}\n\t\t// noinspection EnhancedSwitchMigration\n\t\tswitch (op) {\n\t\t\t// visitInsn\n\t\t\tcase NOP:\n\t\t\tcase ACONST_NULL:\n\t\t\tcase ICONST_M1:\n\t\t\tcase ICONST_0:\n\t\t\tcase ICONST_1:\n\t\t\tcase ICONST_2:\n\t\t\tcase ICONST_3:\n\t\t\tcase ICONST_4:\n\t\t\tcase ICONST_5:\n\t\t\tcase LCONST_0:\n\t\t\tcase LCONST_1:\n\t\t\tcase FCONST_0:\n\t\t\tcase FCONST_1:\n\t\t\tcase FCONST_2:\n\t\t\tcase DCONST_0:\n\t\t\tcase DCONST_1:\n\t\t\t\treturn 0;\n\t\t\t// visitIntInsn\n\t\t\tcase BIPUSH:\n\t\t\tcase SIPUSH:\n\t\t\t\treturn 0;\n\t\t\t// visitLdcInsn\n\t\t\tcase LDC:\n\t\t\t\treturn 0;\n\t\t\t// visitVarInsn\n\t\t\tcase ILOAD:\n\t\t\tcase LLOAD:\n\t\t\tcase FLOAD:\n\t\t\tcase DLOAD:\n\t\t\tcase ALOAD:\n\t\t\t\treturn 0;\n\t\t\t// visitInsn\n\t\t\tcase IALOAD:\n\t\t\tcase LALOAD:\n\t\t\tcase FALOAD:\n\t\t\tcase DALOAD:\n\t\t\tcase AALOAD:\n\t\t\tcase BALOAD:\n\t\t\tcase CALOAD:\n\t\t\tcase SALOAD:\n\t\t\t\treturn 2; // arrayref, index\n\t\t\t// visitVarInsn\n\t\t\tcase ISTORE:\n\t\t\tcase FSTORE:\n\t\t\tcase ASTORE:\n\t\t\t\treturn 1; // value\n\t\t\tcase DSTORE:\n\t\t\tcase LSTORE:\n\t\t\t\treturn 2; // wide-value\n\t\t\t// visitInsn\n\t\t\tcase IASTORE:\n\t\t\tcase FASTORE:\n\t\t\tcase AASTORE:\n\t\t\tcase BASTORE:\n\t\t\tcase CASTORE:\n\t\t\tcase SASTORE:\n\t\t\t\treturn 3; // arrayref, index, value\n\t\t\tcase DASTORE:\n\t\t\tcase LASTORE:\n\t\t\t\treturn 4; // arrayref, index, wide-value\n\t\t\tcase POP:\n\t\t\t\treturn 1; // value\n\t\t\tcase POP2:\n\t\t\t\treturn 2; // value x2 or wide-value\n\t\t\tcase DUP:\n\t\t\tcase DUP_X1:\n\t\t\tcase DUP_X2:\n\t\t\tcase DUP2:\n\t\t\tcase DUP2_X1:\n\t\t\tcase DUP2_X2:\n\t\t\tcase SWAP:\n\t\t\t\treturn 0; // Does not \"consume\" technically\n\t\t\tcase IADD:\n\t\t\tcase FADD:\n\t\t\tcase ISUB:\n\t\t\tcase FSUB:\n\t\t\tcase IMUL:\n\t\t\tcase FMUL:\n\t\t\tcase IDIV:\n\t\t\tcase FDIV:\n\t\t\tcase IREM:\n\t\t\tcase FREM:\n\t\t\tcase ISHL:\n\t\t\tcase ISHR:\n\t\t\tcase IUSHR:\n\t\t\tcase IAND:\n\t\t\tcase IXOR:\n\t\t\tcase IOR:\n\t\t\t\treturn 2; // value1, value2\n\t\t\tcase LUSHR:\n\t\t\tcase LSHR:\n\t\t\tcase LSHL:\n\t\t\t\treturn 3; // wide-value1, value2\n\t\t\tcase DREM:\n\t\t\tcase DDIV:\n\t\t\tcase DMUL:\n\t\t\tcase DSUB:\n\t\t\tcase DADD:\n\t\t\tcase LREM:\n\t\t\tcase LDIV:\n\t\t\tcase LMUL:\n\t\t\tcase LSUB:\n\t\t\tcase LADD:\n\t\t\tcase LAND:\n\t\t\tcase LOR:\n\t\t\tcase LXOR:\n\t\t\t\treturn 4; // wide-value1, wide-value2\n\t\t\tcase INEG:\n\t\t\tcase FNEG:\n\t\t\t\treturn 1; // value\n\t\t\tcase DNEG:\n\t\t\tcase LNEG:\n\t\t\t\treturn 2; // wide-value\n\t\t\t// visitIincInsn\n\t\t\tcase IINC:\n\t\t\t\treturn 0;\n\t\t\t// visitInsn\n\t\t\tcase I2L:\n\t\t\tcase I2F:\n\t\t\tcase I2D:\n\t\t\tcase F2I:\n\t\t\tcase F2L:\n\t\t\tcase F2D:\n\t\t\tcase I2B:\n\t\t\tcase I2C:\n\t\t\tcase I2S:\n\t\t\t\treturn 1; // value\n\t\t\tcase D2I:\n\t\t\tcase D2L:\n\t\t\tcase D2F:\n\t\t\tcase L2I:\n\t\t\tcase L2F:\n\t\t\tcase L2D:\n\t\t\t\treturn 2; // wide-value\n\t\t\tcase FCMPL:\n\t\t\tcase FCMPG:\n\t\t\t\treturn 2; // value1, value2\n\t\t\tcase LCMP:\n\t\t\tcase DCMPL:\n\t\t\tcase DCMPG:\n\t\t\t\treturn 4; // wide-value1, wide-value2\n\t\t\t// visitJumpInsn\n\t\t\tcase IFEQ:\n\t\t\tcase IFNE:\n\t\t\tcase IFLT:\n\t\t\tcase IFGE:\n\t\t\tcase IFGT:\n\t\t\tcase IFLE:\n\t\t\tcase IFNULL:\n\t\t\tcase IFNONNULL:\n\t\t\t\treturn 1; // value\n\t\t\tcase IF_ICMPEQ:\n\t\t\tcase IF_ICMPNE:\n\t\t\tcase IF_ICMPLT:\n\t\t\tcase IF_ICMPGE:\n\t\t\tcase IF_ICMPGT:\n\t\t\tcase IF_ICMPLE:\n\t\t\tcase IF_ACMPEQ:\n\t\t\tcase IF_ACMPNE:\n\t\t\t\treturn 2; // value1, value2\n\t\t\tcase GOTO:\n\t\t\t\treturn 0;\n\t\t\tcase JSR:\n\t\t\t\treturn 0;\n\t\t\t// visitVarInsn\n\t\t\tcase RET:\n\t\t\t\treturn 0;\n\t\t\t// visiTableSwitchInsn/visitLookupSwitch\n\t\t\tcase TABLESWITCH:\n\t\t\tcase LOOKUPSWITCH:\n\t\t\t\treturn 1; // value\n\t\t\t// visitInsn\n\t\t\tcase IRETURN:\n\t\t\tcase FRETURN:\n\t\t\tcase ARETURN:\n\t\t\t\treturn 1; // value\n\t\t\tcase LRETURN:\n\t\t\tcase DRETURN:\n\t\t\t\treturn 2; // wide-value\n\t\t\tcase RETURN:\n\t\t\t\treturn 0;\n\t\t\t// visitTypeInsn\n\t\t\tcase NEW:\n\t\t\t\treturn 0;\n\t\t\t// visitIntInsn\n\t\t\tcase NEWARRAY:\n\t\t\t\treturn 1; // count\n\t\t\t// visitTypeInsn\n\t\t\tcase ANEWARRAY:\n\t\t\t\treturn 1; // count\n\t\t\t// visitInsn\n\t\t\tcase ARRAYLENGTH:\n\t\t\t\treturn 1; // array\n\t\t\tcase ATHROW:\n\t\t\t\treturn 1; // exception, but it technically should clear the stack\n\t\t\t// visitTypeInsn\n\t\t\tcase CHECKCAST:\n\t\t\t\treturn 0; // instance to verify, not technically consumed but referenced\n\t\t\tcase INSTANCEOF:\n\t\t\t\treturn 1; // value\n\t\t\t// visitInsn\n\t\t\tcase MONITORENTER:\n\t\t\tcase MONITOREXIT:\n\t\t\t\treturn 1; // monitor\n\t\t\tdefault:\n\t\t\t\tthrow new IllegalArgumentException(\"Unhandled instruction: \" + op);\n\t\t}\n\t}\n\n\t/**\n\t * Computes the size of stack items produced for the given operation of the instruction.\n\t * This considers {@code long} and {@code double} types taking two spaces.\n\t *\n\t * @param insn\n\t * \t\tInstruction to compute for.\n\t *\n\t * @return Size of stack produced. Never negative.\n\t *\n\t * @see #getSizeConsumed(AbstractInsnNode)\n\t * @see <a href=\"https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-6.html#jvms-6.5\">JVMS 6.5</a>\n\t */\n\tpublic static int getSizeProduced(AbstractInsnNode insn) {\n\t\tint op = insn.getOpcode();\n\t\tint type = insn.getType();\n\t\tif (type == AbstractInsnNode.METHOD_INSN) {\n\t\t\tType methodType = Type.getMethodType(((MethodInsnNode) insn).desc);\n\t\t\treturn methodType.getReturnType().getSize();\n\t\t} else if (type == AbstractInsnNode.INVOKE_DYNAMIC_INSN) {\n\t\t\tType methodType = Type.getMethodType(((InvokeDynamicInsnNode) insn).desc);\n\t\t\treturn methodType.getReturnType().getSize();\n\t\t} else if (type == AbstractInsnNode.FIELD_INSN) {\n\t\t\tFieldInsnNode fin = (FieldInsnNode) insn;\n\t\t\tType fieldtype = Type.getType(fin.desc);\n\t\t\tif (op == GETSTATIC)\n\t\t\t\treturn fieldtype.getSize(); // field type can be wide\n\t\t\tif (op == GETFIELD)\n\t\t\t\treturn fieldtype.getSize(); // field type can be wide\n\t\t\tif (op == PUTSTATIC || op == PUTFIELD)\n\t\t\t\treturn 0;\n\t\t} else if (type == AbstractInsnNode.LDC_INSN) {\n\t\t\tObject cst = ((LdcInsnNode) insn).cst;\n\t\t\treturn (cst instanceof Double || cst instanceof Long) ? 2 : 1;\n\t\t} else if (type == AbstractInsnNode.FRAME ||\n\t\t\t\ttype == AbstractInsnNode.LABEL ||\n\t\t\t\ttype == AbstractInsnNode.LINE) {\n\t\t\treturn 0;\n\t\t} else if (type == AbstractInsnNode.JUMP_INSN) {\n\t\t\treturn op == JSR ? 1 : 0;\n\t\t}\n\t\t// noinspection EnhancedSwitchMigration\n\t\tswitch (op) {\n\t\t\t// visitInsn\n\t\t\tcase NOP:\n\t\t\t\treturn 0;\n\t\t\tcase ACONST_NULL:\n\t\t\tcase ICONST_M1:\n\t\t\tcase ICONST_0:\n\t\t\tcase ICONST_1:\n\t\t\tcase ICONST_2:\n\t\t\tcase ICONST_3:\n\t\t\tcase ICONST_4:\n\t\t\tcase ICONST_5:\n\t\t\tcase FCONST_0:\n\t\t\tcase FCONST_1:\n\t\t\tcase FCONST_2:\n\t\t\t\treturn 1; // value\n\t\t\tcase LCONST_0:\n\t\t\tcase LCONST_1:\n\t\t\tcase DCONST_0:\n\t\t\tcase DCONST_1:\n\t\t\t\treturn 2; // wide-value\n\t\t\t// visitIntInsn\n\t\t\tcase BIPUSH:\n\t\t\tcase SIPUSH:\n\t\t\t\treturn 1; // value\n\t\t\t// visitVarInsn\n\t\t\tcase ILOAD:\n\t\t\tcase FLOAD:\n\t\t\tcase ALOAD:\n\t\t\t\treturn 1; // value\n\t\t\tcase LLOAD:\n\t\t\tcase DLOAD:\n\t\t\t\treturn 2; // wide-value\n\t\t\t// visitInsn\n\t\t\tcase IALOAD:\n\t\t\tcase FALOAD:\n\t\t\tcase AALOAD:\n\t\t\tcase BALOAD:\n\t\t\tcase CALOAD:\n\t\t\tcase SALOAD:\n\t\t\t\treturn 1; // value\n\t\t\tcase LALOAD:\n\t\t\tcase DALOAD:\n\t\t\t\treturn 2; // wide-value\n\t\t\t// visitVarInsn\n\t\t\tcase ISTORE:\n\t\t\tcase LSTORE:\n\t\t\tcase FSTORE:\n\t\t\tcase DSTORE:\n\t\t\tcase ASTORE:\n\t\t\t\treturn 0;\n\t\t\t// visitInsn\n\t\t\tcase IASTORE:\n\t\t\tcase LASTORE:\n\t\t\tcase FASTORE:\n\t\t\tcase DASTORE:\n\t\t\tcase AASTORE:\n\t\t\tcase BASTORE:\n\t\t\tcase CASTORE:\n\t\t\tcase SASTORE:\n\t\t\t\treturn 0;\n\t\t\tcase POP:\n\t\t\tcase POP2:\n\t\t\t\treturn 0;\n\t\t\tcase DUP:\n\t\t\tcase DUP_X1:\n\t\t\tcase DUP_X2:\n\t\t\t\treturn 1; // stack stuff\n\t\t\tcase DUP2:\n\t\t\tcase DUP2_X1:\n\t\t\tcase DUP2_X2:\n\t\t\t\treturn 2; // stack stuff\n\t\t\tcase SWAP:\n\t\t\t\treturn 0; // technically does not introduce\n\t\t\tcase IADD:\n\t\t\tcase FADD:\n\t\t\tcase ISUB:\n\t\t\tcase FSUB:\n\t\t\tcase IMUL:\n\t\t\tcase FMUL:\n\t\t\tcase IDIV:\n\t\t\tcase FDIV:\n\t\t\tcase IREM:\n\t\t\tcase FREM:\n\t\t\tcase INEG:\n\t\t\tcase FNEG:\n\t\t\tcase ISHL:\n\t\t\tcase ISHR:\n\t\t\tcase IUSHR:\n\t\t\tcase IAND:\n\t\t\tcase IOR:\n\t\t\tcase IXOR:\n\t\t\t\treturn 1; // result\n\t\t\tcase LDIV:\n\t\t\tcase LMUL:\n\t\t\tcase LSUB:\n\t\t\tcase LADD:\n\t\t\tcase LREM:\n\t\t\tcase LNEG:\n\t\t\tcase LSHL:\n\t\t\tcase LSHR:\n\t\t\tcase LUSHR:\n\t\t\tcase LAND:\n\t\t\tcase LOR:\n\t\t\tcase LXOR:\n\t\t\tcase DADD:\n\t\t\tcase DSUB:\n\t\t\tcase DMUL:\n\t\t\tcase DDIV:\n\t\t\tcase DREM:\n\t\t\tcase DNEG:\n\t\t\t\treturn 2; // wide-result\n\t\t\t// visitIincInsn\n\t\t\tcase IINC:\n\t\t\t\treturn 0;\n\t\t\t// visitInsn\n\t\t\tcase I2F:\n\t\t\tcase L2I:\n\t\t\tcase L2F:\n\t\t\tcase F2I:\n\t\t\tcase D2I:\n\t\t\tcase D2F:\n\t\t\tcase I2B:\n\t\t\tcase I2C:\n\t\t\tcase I2S:\n\t\t\t\treturn 1; // result\n\t\t\tcase I2D:\n\t\t\tcase L2D:\n\t\t\tcase F2D:\n\t\t\tcase F2L:\n\t\t\tcase D2L:\n\t\t\tcase I2L:\n\t\t\t\treturn 2; // wide-result\n\t\t\tcase LCMP:\n\t\t\tcase FCMPL:\n\t\t\tcase FCMPG:\n\t\t\tcase DCMPL:\n\t\t\tcase DCMPG:\n\t\t\t\treturn 1; // result\n\t\t\t// visitVarInsn\n\t\t\tcase RET:\n\t\t\t\treturn 0;\n\t\t\t// visiTableSwitchInsn/visitLookupSwitch\n\t\t\tcase TABLESWITCH:\n\t\t\tcase LOOKUPSWITCH:\n\t\t\t\treturn 0;\n\t\t\t// visitInsn\n\t\t\tcase IRETURN:\n\t\t\tcase LRETURN:\n\t\t\tcase FRETURN:\n\t\t\tcase DRETURN:\n\t\t\tcase ARETURN:\n\t\t\tcase RETURN:\n\t\t\t\treturn 0;\n\t\t\t// visitTypeInsn\n\t\t\tcase NEW:\n\t\t\t\treturn 1; // uninitialized value\n\t\t\t// visitIntInsn\n\t\t\tcase NEWARRAY:\n\t\t\t\treturn 1;\n\t\t\t// visitTypeInsn\n\t\t\tcase ANEWARRAY:\n\t\t\t\treturn 1;\n\t\t\t// visitInsn\n\t\t\tcase ARRAYLENGTH:\n\t\t\t\treturn 1;\n\t\t\tcase ATHROW:\n\t\t\t\treturn 0;\n\t\t\t// visitTypeInsn\n\t\t\tcase CHECKCAST:\n\t\t\t\treturn 0; // technically does not introduce\n\t\t\tcase INSTANCEOF:\n\t\t\t\treturn 1; // result\n\t\t\t// visitInsn\n\t\t\tcase MONITORENTER:\n\t\t\t\treturn 0;\n\t\t\tcase MONITOREXIT:\n\t\t\t\treturn 0;\n\t\t\t// visitMultiANewArrayInsn\n\t\t\tcase MULTIANEWARRAY:\n\t\t\t\treturn 1; // array\n\t\t\tdefault:\n\t\t\t\tthrow new IllegalArgumentException(\"Unhandled instruction: \" + op);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/BlwUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport dev.xdark.blw.asm.internal.Util;\nimport dev.xdark.blw.code.ExtensionOpcodes;\nimport dev.xdark.blw.code.Instruction;\nimport dev.xdark.blw.code.Label;\nimport dev.xdark.blw.code.generic.GenericLabel;\nimport dev.xdark.blw.code.instruction.BranchInstruction;\nimport dev.xdark.blw.code.instruction.ConditionalJumpInstruction;\nimport dev.xdark.blw.code.instruction.ImmediateJumpInstruction;\nimport dev.xdark.blw.code.instruction.LookupSwitchInstruction;\nimport dev.xdark.blw.code.instruction.SimpleInstruction;\nimport dev.xdark.blw.code.instruction.TableSwitchInstruction;\nimport dev.xdark.blw.code.instruction.VarInstruction;\nimport dev.xdark.blw.code.instruction.VariableIncrementInstruction;\nimport dev.xdark.blw.simulation.ExecutionEngines;\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.helper.Variables;\nimport me.darknet.assembler.printer.InstructionPrinter;\nimport me.darknet.assembler.printer.PrintContext;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.FieldInsnNode;\nimport org.objectweb.asm.tree.FrameNode;\nimport org.objectweb.asm.tree.IincInsnNode;\nimport org.objectweb.asm.tree.InsnNode;\nimport org.objectweb.asm.tree.IntInsnNode;\nimport org.objectweb.asm.tree.InvokeDynamicInsnNode;\nimport org.objectweb.asm.tree.JumpInsnNode;\nimport org.objectweb.asm.tree.LabelNode;\nimport org.objectweb.asm.tree.LdcInsnNode;\nimport org.objectweb.asm.tree.LineNumberNode;\nimport org.objectweb.asm.tree.LookupSwitchInsnNode;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport org.objectweb.asm.tree.TableSwitchInsnNode;\nimport org.objectweb.asm.tree.TypeInsnNode;\nimport org.objectweb.asm.tree.VarInsnNode;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * Misc blw utilities.\n *\n * @author Matt Coley\n */\npublic class BlwUtil {\n\t/**\n\t * @param insn\n\t * \t\tASM instruction.\n\t *\n\t * @return BLW instruction.\n\t */\n\t@Nonnull\n\tpublic static Instruction convert(@Nonnull AbstractInsnNode insn) {\n\t\treturn switch (insn) {\n\t\t\tcase LdcInsnNode ldc -> Util.wrapLdcInsn(ldc.cst);\n\t\t\tcase MethodInsnNode min -> Util.wrapMethodInsn(min.getOpcode(), min.owner, min.name, min.desc, false);\n\t\t\tcase FieldInsnNode fin -> Util.wrapFieldInsn(fin.getOpcode(), fin.owner, fin.name, fin.desc);\n\t\t\tcase TypeInsnNode tin -> Util.wrapTypeInsn(tin.getOpcode(), tin.desc);\n\t\t\tcase IntInsnNode iin -> Util.wrapIntInsn(iin.getOpcode(), iin.operand);\n\t\t\tcase InsnNode in -> Util.wrapInsn(in.getOpcode());\n\t\t\tcase InvokeDynamicInsnNode indy -> Util.wrapInvokeDynamicInsn(indy.name, indy.desc, indy.bsm, indy.bsmArgs);\n\t\t\tcase VarInsnNode vin -> new VarInstruction(insn.getOpcode(), vin.var);\n\t\t\tcase IincInsnNode iin -> new VariableIncrementInstruction(iin.var, iin.incr);\n\t\t\tcase JumpInsnNode jin -> {\n\t\t\t\tint offset = AsmInsnUtil.indexOf(jin.label);\n\t\t\t\tLabel label = new GenericLabel();\n\t\t\t\tlabel.setIndex(offset);\n\t\t\t\tyield jin.getOpcode() == Opcodes.GOTO ?\n\t\t\t\t\t\tnew ImmediateJumpInstruction(insn.getOpcode(), label) :\n\t\t\t\t\t\tnew ConditionalJumpInstruction(insn.getOpcode(), label);\n\t\t\t}\n\t\t\tcase TableSwitchInsnNode tsin -> {\n\t\t\t\tint min = tsin.min;\n\t\t\t\tLabel dflt = new GenericLabel();\n\t\t\t\tdflt.setIndex(AsmInsnUtil.indexOf(tsin.dflt));\n\t\t\t\tList<Label> targets = tsin.labels.stream().map(l -> {\n\t\t\t\t\tLabel target = new GenericLabel();\n\t\t\t\t\ttarget.setIndex(AsmInsnUtil.indexOf(l));\n\t\t\t\t\treturn target;\n\t\t\t\t}).toList();\n\t\t\t\tyield new TableSwitchInstruction(min, dflt, targets);\n\t\t\t}\n\t\t\tcase LookupSwitchInsnNode lsin -> {\n\t\t\t\tint[] keys = lsin.keys.stream().mapToInt(i -> i).toArray();\n\t\t\t\tLabel dflt = new GenericLabel();\n\t\t\t\tdflt.setIndex(AsmInsnUtil.indexOf(lsin.dflt));\n\t\t\t\tList<Label> targets = lsin.labels.stream().map(l -> {\n\t\t\t\t\tLabel target = new GenericLabel();\n\t\t\t\t\ttarget.setIndex(AsmInsnUtil.indexOf(l));\n\t\t\t\t\treturn target;\n\t\t\t\t}).toList();\n\t\t\t\tyield new LookupSwitchInstruction(keys, dflt, targets);\n\t\t\t}\n\t\t\tcase LabelNode ln -> new LabelInstruction(AsmInsnUtil.indexOf(ln));\n\t\t\tcase FrameNode fr -> new FrameInstruction();\n\t\t\tcase LineNumberNode ln -> new LineInstruction(ln.line);\n\t\t\tdefault -> new SimpleInstruction(insn.getOpcode());\n\t\t};\n\t}\n\n\t/**\n\t * @param instructions\n\t * \t\tCollection of ASM instruction.\n\t *\n\t * @return JASM text representation of BLW converted instance of the instructions.\n\t */\n\t@Nonnull\n\tpublic static String toString(@Nonnull Iterable<AbstractInsnNode> instructions) {\n\t\tStringBuilder sb = new StringBuilder();\n\t\tfor (AbstractInsnNode instruction : instructions)\n\t\t\tif (instruction.getType() != AbstractInsnNode.FRAME)\n\t\t\t\tsb.append(toString(instruction)).append('\\n');\n\t\tif (sb.isEmpty())\n\t\t\treturn \"\";\n\t\treturn sb.substring(0, sb.length() - 1);\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tASM instruction.\n\t *\n\t * @return JASM text representation of BLW converted instance of the instruction.\n\t */\n\t@Nonnull\n\tpublic static String toString(@Nonnull AbstractInsnNode insn) {\n\t\tInstruction converted = convert(insn);\n\t\treturn toString(converted);\n\t}\n\n\t/**\n\t * @param insn\n\t * \t\tBLW instruction.\n\t *\n\t * @return JASM text representation of BLW instruction.\n\t */\n\t@Nonnull\n\tprivate static String toString(@Nonnull Instruction insn) {\n\t\t// Special cases that don't have dedicated printer methods exposed in JASM.\n\t\tif (insn instanceof LineInstruction line)\n\t\t\treturn \"line \" + line.line;\n\t\telse if (insn instanceof FrameInstruction)\n\t\t\treturn \"// frame\";\n\n\t\t// Collect label names from the instruction.\n\t\tMap<Integer, String> labelNames;\n\t\tif (insn instanceof LabelInstruction label) {\n\t\t\tint index = label.getIndex();\n\t\t\tlabelNames = Map.of(index, \"L\" + index);\n\t\t} else if (insn instanceof BranchInstruction branch) {\n\t\t\tlabelNames = branch.targetsStream()\n\t\t\t\t\t.collect(Collectors.toMap(Label::getIndex, l -> \"L\" + l.getIndex(), (a, b) -> a));\n\t\t} else {\n\t\t\tlabelNames = Collections.emptyMap();\n\t\t}\n\n\t\tPrintContext<?> ctx = new PrintContext<>(\"\");\n\t\tctx.setForceWholeNumberRepresentation(true);\n\t\tVariables emptyVariables = new Variables(Collections.emptyNavigableMap(), Collections.emptyList());\n\t\tInstructionPrinter printer = new InstructionPrinter(ctx.code(), null, emptyVariables, labelNames);\n\n\t\tint op = insn.opcode();\n\t\tif (insn instanceof LabelInstruction label) {\n\t\t\tprinter.label(label);\n\t\t} else if (op >= 0 && op <= ExtensionOpcodes.PRIMITIVE_CONVERSION) {\n\t\t\tExecutionEngines.execute(printer, insn);\n\t\t} else {\n\t\t\t// The current search models shouldn't yield anything aside from the above types.\n\t\t\treturn \"<missing text mapper: \" + insn.getClass().getSimpleName() + \":\" + op + \">\";\n\t\t}\n\n\t\t// Cut off first 2 chars of unused indentation then cap off the max length.\n\t\treturn ctx.toString().substring(2).replace('\\n', ' ').trim();\n\t}\n\n\t/**\n\t * Dummy instruction to facilitate printing in {@link #toString(Instruction)}.\n\t */\n\tprivate record FrameInstruction() implements Instruction {\n\t\t@Override\n\t\tpublic int opcode() {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\t/**\n\t * Dummy instruction to facilitate printing in {@link #toString(Instruction)}.\n\t *\n\t * @param line\n\t * \t\tLine number.\n\t */\n\tprivate record LineInstruction(int line) implements Instruction {\n\t\t@Override\n\t\tpublic int opcode() {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\t/**\n\t * Dummy instruction to facilitate label printing in {@link #toString(Instruction)}.\n\t */\n\tprivate static class LabelInstruction implements Label, Instruction {\n\t\tprivate int index;\n\n\t\tpublic LabelInstruction(int index) {\n\t\t\tsetIndex(index);\n\t\t}\n\n\t\t@Override\n\t\tpublic int opcode() {\n\t\t\treturn -2;\n\t\t}\n\n\t\t@Override\n\t\tpublic int getIndex() {\n\t\t\treturn index;\n\t\t}\n\n\t\t@Override\n\t\tpublic void setIndex(int index) {\n\t\t\tthis.index = index;\n\t\t}\n\n\t\t@Override\n\t\tpublic int getLineNumber() {\n\t\t\treturn -1;\n\t\t}\n\n\t\t@Override\n\t\tpublic void setLineNumber(int line) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean equals(Object o) {\n\t\t\tif (this == o) return true;\n\t\t\tif (!(o instanceof LabelInstruction that)) return false;\n\t\t\treturn index == that.index;\n\t\t}\n\n\t\t@Override\n\t\tpublic int hashCode() {\n\t\t\treturn index;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/ByteHeaderUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport java.util.List;\n\n/**\n * Simple utility with a bunch of pre-defined patterns for file headers.\n *\n * @author Matt Coley\n */\npublic class ByteHeaderUtil {\n\tprivate static final int WILD = Integer.MIN_VALUE;\n\t// JVM/Android\n\tpublic static final int[] CLASS = {0xCA, 0xFE, 0xBA, 0xBE};\n\tpublic static final int[] DEX = {0x64, 0x65, 0x78, 0x0A};\n\t// Archives\n\tpublic static final int[] TAR_LZW = {0x1F, 0x9D};\n\tpublic static final int[] TAR_LZH = {0x1F, 0xA0};\n\tpublic static final int[] BZ2 = {0x42, 0x5A, 0x68};\n\tpublic static final int[] LZ = {0x4C, 0x5A, 0x49, 0x50};\n\tpublic static final int[] TAR = {0x75, 0x73, 0x74, 0x61, 0x72};\n\tpublic static final int[] GZIP = {0x1F, 0x8B};\n\tpublic static final int[] ZIP = {0x50, 0x4B, 0x03, 0x04};\n\tpublic static final int[] ZIP_EMPTY = {0x50, 0x4B, 0x05, 0x06};\n\tpublic static final int[] ZIP_SPANNED = {0x50, 0x4B, 0x07, 0x08};\n\tpublic static final int[] RAR = {0x52, 0x61, 0x72, 0x21, 0x1A, 0x07};\n\tpublic static final int[] SEVEN_Z = {0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C};\n\tpublic static final int[] JMOD = {0x4A, 0x4D};\n\tpublic static final int[] MODULES = {0xDA, 0xDA, 0xFE, 0xCA};\n\tpublic static final List<int[]> ARCHIVE_HEADERS = List.of(\n\t\t\tTAR_LZW,\n\t\t\tTAR_LZH,\n\t\t\tBZ2,\n\t\t\tLZ,\n\t\t\tTAR,\n\t\t\tGZIP,\n\t\t\tZIP,\n\t\t\tZIP_EMPTY,\n\t\t\tZIP_SPANNED,\n\t\t\tRAR,\n\t\t\tSEVEN_Z,\n\t\t\tJMOD);\n\t// Programs\n\tpublic static final int[] PE = {0x4D, 0x5A};\n\tpublic static final int[] ELF = {0x7F, 0x45, 0x4C, 0x46};\n\tpublic static final int[] ELF_AR = {0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0x0A};\n\tpublic static final int[] DYLIB_32 = {0xCE, 0xFA, 0xED, 0xFE};\n\tpublic static final int[] DYLIB_64 = {0xCF, 0xFA, 0xED, 0xFE};\n\tpublic static final List<int[]> PROGRAM_HEADERS = List.of(\n\t\t\tPE,\n\t\t\tELF,\n\t\t\tELF_AR,\n\t\t\tDYLIB_32,\n\t\t\tDYLIB_64);\n\t// Images\n\tpublic static final int[] PNG = {0x89, 0x50, 0x4E, 0x47};\n\tpublic static final int[] JPG = {0xFF, 0xD8, 0xFF};\n\tpublic static final int[] GIF = {0x47, 0x49, 0x46, 0x38};\n\tpublic static final int[] BMP = {0x42, 0x4D};\n\tpublic static final int[] ICO = {0x00, 0x00, 0x01, 0x00};\n\tpublic static final List<int[]> IMAGE_HEADERS = List.of(\n\t\t\tPNG,\n\t\t\tJPG,\n\t\t\tGIF,\n\t\t\tBMP,\n\t\t\tICO);\n\t// Audio\n\tpublic static final int[] OGG = {0x4F, 0x67, 0x67, 0x53};\n\tpublic static final int[] WAV = {0x52, 0x49, 0x46, 0x46, WILD, WILD, WILD, WILD, 0x57, 0x41, 0x56, 0x45};\n\tpublic static final int[] MP3_ID3 = {0x49, 0x44, 0x33};\n\tpublic static final int[] MP3_NO_ID1 = {0xFF, 0xFB};\n\tpublic static final int[] MP3_NO_ID2 = {0xFF, 0xF2};\n\tpublic static final int[] MP3_NO_ID3 = {0xFF, 0xF3};\n\tpublic static final int[] M4A = {0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41};\n\tpublic static final int[] M4ADash = {0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x64, 0x61, 0x73};\n\tpublic static final List<int[]> AUDIO_HEADERS = List.of(\n\t\t\tOGG,\n\t\t\tWAV,\n\t\t\tMP3_ID3, MP3_NO_ID1, MP3_NO_ID2, MP3_NO_ID3,\n\t\t\tM4A,\n\t\t\tM4ADash);\n\t// Video\n\t// For MP4/QuickTime see: https://www.ftyps.com/\n\tpublic static final int[] MP4_FTYP = {WILD, WILD, WILD, WILD, 0x66, 0x74, 0x79, 0x70};\n\tpublic static final int[] MKV = {0x1A, 0x45, 0xDF, 0xA3, 0xA3, 0x42, 0x86, 0x81, 0x01, 0x42, 0xF7, 0x81};\n\tpublic static final List<int[]> VIDEO_HEADERS = List.of(\n\t\t\tMP4_FTYP,\n\t\t\tMKV\n\t);\n\t// Android files (Modular chunk system, use unsigned short LE headers)\n\tpublic static final int[] BINARY_XML = {0x03, 0x00};\n\tpublic static final int[] ARSC = {0x02, 0x00};\n\t// Text BOM\n\tpublic static final int[] TEXT_BOM_UTF_32BE = {0x00, 0x00, 0xFE, 0xFF};\n\tpublic static final int[] TEXT_BOM_UTF_32LE = {0xFF, 0xFE, 0x00, 0x00};\n\tpublic static final int[] TEXT_BOM_UTF_16BE = {0xFE, 0xFF, WILD, WILD};\n\tpublic static final int[] TEXT_BOM_UTF_16LE = {0xFF, 0xFE, WILD, WILD};\n\tpublic static final int[] TEXT_BOM_UTF_8 = {0xEF, 0xBB, 0xBF, WILD};\n\t// Misc\n\tpublic static final int[] PDF = {0x25, 0x50, 0x44, 0x46, 0x2D};\n\n\t/**\n\t * The reason why {@code int[]} is used is for simple unsigned byte values.\n\t * This will convert the arrays into the signed {@code byte[]}.\n\t *\n\t * @param array\n\t * \t\tInput array.\n\t *\n\t * @return Byte array.\n\t */\n\t@Nonnull\n\tpublic static byte[] convert(int[] array) {\n\t\tbyte[] copy = new byte[array.length];\n\t\tfor (int i = 0; i < array.length; i++)\n\t\t\tcopy[i] = (byte) array[i];\n\t\treturn copy;\n\t}\n\n\t/**\n\t * @param array\n\t * \t\tFile data to compare against.\n\t * @param patterns\n\t * \t\tThe header patterns to check against.\n\t *\n\t * @return {@code true} when array prefix matches one of the patterns.\n\t */\n\tpublic static boolean matchAny(byte[] array, List<int[]> patterns) {\n\t\treturn matchAny(array, 0, patterns);\n\t}\n\n\t/**\n\t * @param array\n\t * \t\tFile data to compare against.\n\t * @param offset\n\t * \t\tOffset into the data to check at.\n\t * @param patterns\n\t * \t\tThe header patterns to check against.\n\t *\n\t * @return {@code true} when array prefix matches one of the patterns.\n\t */\n\tpublic static boolean matchAny(byte[] array, int offset, List<int[]> patterns) {\n\t\treturn getMatch(array, offset, patterns) != null;\n\t}\n\n\t/**\n\t * @param array\n\t * \t\tFile data to compare against.\n\t * @param patterns\n\t * \t\tThe header patterns to check against.\n\t *\n\t * @return The pattern in the list matched against the array.\n\t * {@code null} if no match was found.\n\t */\n\t@Nullable\n\tpublic static int[] getMatch(byte[] array, List<int[]> patterns) {\n\t\treturn getMatch(array, 0, patterns);\n\t}\n\n\t/**\n\t * @param array\n\t * \t\tFile data to compare against.\n\t * @param offset\n\t * \t\tOffset into the data to check at.\n\t * @param patterns\n\t * \t\tThe header patterns to check against.\n\t *\n\t * @return The pattern in the list matched against the array.\n\t * {@code null} if no match was found.\n\t */\n\t@Nullable\n\tpublic static int[] getMatch(byte[] array, int offset, List<int[]> patterns) {\n\t\tfor (int[] pattern : patterns) {\n\t\t\tif (match(array, offset, pattern)) {\n\t\t\t\treturn pattern;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param array\n\t * \t\tFile data to compare against.\n\t * @param pattern\n\t * \t\tThe header pattern.\n\t *\n\t * @return {@code true} when array prefix matches pattern.\n\t */\n\tpublic static boolean match(byte[] array, int... pattern) {\n\t\treturn match(array, 0, pattern);\n\t}\n\n\t/**\n\t * @param array\n\t * \t\tFile data to compare against.\n\t * @param offset\n\t * \t\tOffset into the data to check at.\n\t * @param pattern\n\t * \t\tThe header pattern.\n\t *\n\t * @return {@code true} when array prefix matches pattern.\n\t */\n\tpublic static boolean match(byte[] array, int offset, int... pattern) {\n\t\treturn matchLength(array, offset, pattern) == pattern.length;\n\t}\n\n\t/**\n\t * @param array\n\t * \t\tFile data to compare against.\n\t * @param offset\n\t * \t\tOffset into the data to check at.\n\t * @param pattern\n\t * \t\tThe header pattern.\n\t *\n\t * @return Length of the pattern matched by the data in the array at the given offset.\n\t * If the result is the same length as the input pattern, it is a full match.\n\t */\n\tpublic static int matchLength(byte[] array, int offset, int... pattern) {\n\t\tint patternLen = pattern.length;\n\t\tif (array == null || array.length < offset + patternLen)\n\t\t\treturn 0;\n\t\tfor (int i = 0; i < patternLen; i++) {\n\t\t\tint patternVal = pattern[i];\n\t\t\tif (patternVal == WILD)\n\t\t\t\tcontinue;\n\t\t\tif (array[offset + i] != (byte) patternVal)\n\t\t\t\treturn Math.max(0, i - 1);\n\t\t}\n\t\treturn patternLen;\n\t}\n\n\t/**\n\t * @param array\n\t * \t\tFile data to compare against.\n\t * @param pattern\n\t * \t\tThe header pattern.\n\t *\n\t * @return {@code true} when array contains the pattern.\n\t */\n\tpublic static boolean matchAtAnyOffset(byte[] array, int... pattern) {\n\t\treturn matchAtAnyOffset(array, array.length, pattern);\n\t}\n\n\t/**\n\t * @param array\n\t * \t\tFile data to compare against.\n\t * @param offsetLimit\n\t * \t\tMaximum offset to scan up to for matches.\n\t * @param pattern\n\t * \t\tThe header pattern.\n\t *\n\t * @return {@code true} when array contains the pattern.\n\t */\n\tpublic static boolean matchAtAnyOffset(byte[] array, int offsetLimit, int... pattern) {\n\t\tint patternLength = pattern.length;\n\t\tint length = array.length;\n\t\tint end = Math.min(length - patternLength, offsetLimit);\n\t\tint offset = 0;\n\t\twhile (offset < end) {\n\t\t\tint matched = matchLength(array, offset, pattern);\n\t\t\tif (matched == patternLength)\n\t\t\t\treturn true;\n\t\t\toffset += Math.max(1, matched);\n\t\t}\n\t\treturn false;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/CancelSignal.java",
    "content": "package software.coley.recaf.util;\n\n/**\n * Special type of {@link Error} that signals that further execution must be stopped.\n *\n * @author xDark\n */\npublic final class CancelSignal extends Error {\n\tprivate static final CancelSignal INSTANCE = new CancelSignal(false);\n\n\t// Internal flag that *might* be useful for debugging\n\t// in the future.\n\t// Do NOT commit changes if this flag\n\t// is set to TRUE.\n\tprivate static final boolean DEBUG = false;\n\n\t/**\n\t * Deny all constructions.\n\t */\n\tprivate CancelSignal(boolean writableStackTrace) {\n\t\tsuper(null, null, false, writableStackTrace);\n\t}\n\n\t/**\n\t * @return Cancellation signal.\n\t */\n\tpublic static CancelSignal get() {\n\t\tif (DEBUG) {\n\t\t\treturn new CancelSignal(true);\n\t\t}\n\t\treturn INSTANCE;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/ClassDefiner.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.collections.Maps;\n\nimport java.util.Map;\n\n/**\n * Classloader implementation for loading a class from raw {@code byte[]}.\n *\n * @author Matt Coley\n */\npublic class ClassDefiner extends ClassLoader {\n\tprivate final Map<String, byte[]> classes;\n\n\t/**\n\t * @param name\n\t * \t\tName of class.\n\t * @param bytecode\n\t * \t\tBytecode of class.\n\t */\n\tpublic ClassDefiner(@Nonnull String name, @Nonnull byte[] bytecode) {\n\t\tthis(Maps.of(name, bytecode));\n\t}\n\n\t/**\n\t * @param classes\n\t * \t\tMap of classes.\n\t */\n\tpublic ClassDefiner(@Nonnull Map<String, byte[]> classes) {\n\t\tsuper(ClassDefiner.class.getClassLoader());\n\t\tthis.classes = classes;\n\t}\n\n\t@Override\n\tpublic final Class<?> findClass(String name) throws ClassNotFoundException {\n\t\tbyte[] bytecode = classes.get(name);\n\t\tif (bytecode != null)\n\t\t\treturn defineClass(name, bytecode, 0, bytecode.length, null);\n\t\treturn super.findClass(name);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/ClassLoaderInternals.java",
    "content": "package software.coley.recaf.util;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.net.URL;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Hacky code to get internal classloader state.\n *\n * @author xDark\n * @author Matt Coley\n */\npublic class ClassLoaderInternals {\n\t/**\n\t * @return {@code jdk.internal.loader.URLClassPath} instance.\n\t *\n\t * @throws ReflectiveOperationException\n\t * \t\tWhen the internals change and the reflective look-ups fail.\n\t */\n\tpublic static Object getUcp() throws ReflectiveOperationException {\n\t\t// Fetch UCP of application's ClassLoader\n\t\t// - ((ClassLoaders.AppClassLoader) ClassLoaders.appClassLoader()).ucp\n\t\tClass<?> clsClassLoaders = Class.forName(\"jdk.internal.loader.ClassLoaders\");\n\t\tObject appClassLoader = ReflectUtil.quietInvoke(clsClassLoaders, null, \"appClassLoader\",\n\t\t\t\tnew Class[0], new Object[0]);\n\t\tClass<?> ucpOwner = appClassLoader.getClass();\n\n\t\t// Field removed in 16, but still exists in parent class \"BuiltinClassLoader\"\n\t\tif (JavaVersion.get() >= 16)\n\t\t\tucpOwner = ucpOwner.getSuperclass();\n\n\t\tField fieldUCP = ReflectUtil.getDeclaredField(ucpOwner, \"ucp\");\n\t\treturn fieldUCP.get(appClassLoader);\n\t}\n\n\t/**\n\t * @param ucp\n\t * \t\tSee {@link #getUcp()}.\n\t *\n\t * @return The contents of the UCP.\n\t *\n\t * @throws ReflectiveOperationException\n\t * \t\tWhen the internals change and the reflective look-ups fail.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static List<URL> getUcpPathList(Object ucp) throws ReflectiveOperationException {\n\t\tif (ucp == null)\n\t\t\treturn Collections.emptyList();\n\t\tClass<?> ucpClass = ucp.getClass();\n\t\tField path = ReflectUtil.getDeclaredField(ucpClass, \"path\");\n\t\treturn (List<URL>) path.get(ucp);\n\t}\n\n\t/**\n\t * @param ucp\n\t * \t\tSee {@link #getUcp()}.\n\t * @param url\n\t * \t\tURL to add to the search path for directories and Jar files of the UCP.\n\t *\n\t * @throws ReflectiveOperationException\n\t * \t\tWhen the internals change and the reflective look-ups fail.\n\t */\n\tpublic static void appendToUcpPath(Object ucp, URL url) throws ReflectiveOperationException {\n\t\tif (ucp == null)\n\t\t\treturn;\n\t\tClass<?> ucpClass = ucp.getClass();\n\t\tMethod addURL = ReflectUtil.getDeclaredMethod(ucpClass, \"addURL\", URL.class);\n\t\taddURL.invoke(ucp, url);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/ClasspathUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.collections.Unchecked;\nimport software.coley.collections.tree.SortedTree;\nimport software.coley.collections.tree.SortedTreeImpl;\nimport software.coley.collections.tree.Tree;\n\nimport java.io.InputStream;\nimport java.lang.module.ModuleDescriptor;\nimport java.lang.module.ModuleFinder;\nimport java.lang.module.ModuleReader;\nimport java.lang.module.ModuleReference;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport static java.lang.Class.forName;\n\n/**\n * Classpath utility.\n *\n * @author Matt Coley\n * @author Andy Li\n * @author xDark\n */\npublic class ClasspathUtil {\n\t/**\n\t * The system classloader, provided by {@link ClassLoader#getSystemClassLoader()}.\n\t */\n\tpublic static final ClassLoader scl = ClassLoader.getSystemClassLoader();\n\t/**\n\t * Cache of all system classes represented as a tree.\n\t */\n\tprivate static volatile Tree<String, String> classTree;\n\t/**\n\t * Cache of all system classes represented as a set.\n\t */\n\tprivate static volatile NavigableSet<String> classSet;\n\t/**\n\t * Cached list of available system packages.\n\t */\n\tprivate static volatile List<String> systemPackages;\n\n\t/**\n\t * Returns the class associated with the specified name, using\n\t * {@link #scl the system class loader}.\n\t * <br> The class will not be initialized if it has not been initialized earlier.\n\t * <br> This is equivalent to {@code Class.forName(className, false, ClassLoader\n\t * .getSystemClassLoader())}\n\t *\n\t * @param className\n\t * \t\tThe fully qualified class name.\n\t *\n\t * @return class object representing the desired class\n\t *\n\t * @throws ClassNotFoundException\n\t * \t\tif the class cannot be located by the system class loader\n\t * @see Class#forName(String, boolean, ClassLoader)\n\t */\n\t@Nonnull\n\tpublic static Class<?> getSystemClass(@Nonnull String className) throws ClassNotFoundException {\n\t\treturn forName(className, false, ClasspathUtil.scl);\n\t}\n\n\t/**\n\t * Check if a class by the given name exists and is accessible by the system classloader.\n\t *\n\t * @param name\n\t * \t\tThe fully qualified class name.\n\t *\n\t * @return {@code true} if the class exists, {@code false} otherwise.\n\t */\n\tpublic static boolean classExists(@Nonnull String name) {\n\t\ttry {\n\t\t\tgetSystemClass(name);\n\t\t\treturn true;\n\t\t} catch (Exception ex) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Check if a resource exists in the current classpath.\n\t *\n\t * @param path\n\t * \t\tPath to resource.\n\t *\n\t * @return {@code true} if resource exists. {@code false} otherwise.\n\t */\n\tpublic static boolean resourceExists(@Nonnull String path) {\n\t\tif (!path.startsWith(\"/\"))\n\t\t\tpath = \"/\" + path;\n\t\treturn ClasspathUtil.class.getResource(path) != null;\n\t}\n\n\t/**\n\t * Fetch a resource as a stream in the current classpath.\n\t *\n\t * @param path\n\t * \t\tPath to resource.\n\t *\n\t * @return Stream of resource.\n\t */\n\t@Nullable\n\tpublic static InputStream resource(String path) {\n\t\tif (!path.startsWith(\"/\"))\n\t\t\tpath = \"/\" + path;\n\t\treturn ClasspathUtil.class.getResourceAsStream(path);\n\t}\n\n\t/**\n\t * @return List of package names belonging to the core JDK.\n\t */\n\t@Nonnull\n\tpublic static List<String> getSystemPackages() {\n\t\tif (systemPackages == null) {\n\t\t\tsynchronized (ClasspathUtil.class) {\n\t\t\t\tif (systemPackages == null) {\n\t\t\t\t\tsystemPackages = ModuleFinder.ofSystem().findAll().stream()\n\t\t\t\t\t\t\t.flatMap(moduleReference -> moduleReference.descriptor().exports().stream())\n\t\t\t\t\t\t\t.map(ModuleDescriptor.Exports::source)\n\t\t\t\t\t\t\t.distinct()\n\t\t\t\t\t\t\t.collect(Collectors.toList());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn systemPackages;\n\t}\n\n\t/**\n\t * @return Set of all system classes.\n\t */\n\t@Nonnull\n\tpublic static NavigableSet<String> getSystemClassSet() {\n\t\tif (classSet == null) {\n\t\t\tsynchronized (ClasspathUtil.class) {\n\t\t\t\tif (classSet == null) {\n\t\t\t\t\tclassSet = new TreeSet<>();\n\t\t\t\t\tModuleFinder.ofSystem().findAll().stream()\n\t\t\t\t\t\t\t.map(Unchecked.function(ModuleReference::open))\n\t\t\t\t\t\t\t.flatMap(Unchecked.function(ModuleReader::list))\n\t\t\t\t\t\t\t.filter(s -> s.endsWith(\".class\") && s.indexOf('-') == -1)\n\t\t\t\t\t\t\t.map(s -> s.substring(0, s.length() - 6))\n\t\t\t\t\t\t\t.forEach(s -> classSet.add(s));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn classSet;\n\t}\n\n\t/**\n\t * @return Tree representation of all system classes.\n\t */\n\t@Nonnull\n\tpublic static Tree<String, String> getSystemClassTree() {\n\t\tif (classTree == null) {\n\t\t\tsynchronized (ClasspathUtil.class) {\n\t\t\t\tif (classTree == null) {\n\t\t\t\t\tclassTree = new SortedTreeImpl<>();\n\t\t\t\t\tModuleFinder.ofSystem().findAll().stream()\n\t\t\t\t\t\t\t.map(Unchecked.function(ModuleReference::open))\n\t\t\t\t\t\t\t.flatMap(Unchecked.function(ModuleReader::list))\n\t\t\t\t\t\t\t.filter(s -> s.endsWith(\".class\") && s.indexOf('-') == -1)\n\t\t\t\t\t\t\t.map(s -> s.substring(0, s.length() - 6))\n\t\t\t\t\t\t\t.forEach(s -> {\n\t\t\t\t\t\t\t\tString[] sections = s.split(\"/\");\n\t\t\t\t\t\t\t\tTree<String, String> path = classTree;\n\t\t\t\t\t\t\t\tfor (String section : sections) {\n\t\t\t\t\t\t\t\t\tSortedTree<String, String> copy = (SortedTree<String, String>) path;\n\t\t\t\t\t\t\t\t\tpath = path.computeIfAbsent(section, x -> new SortedTreeImpl<>(copy, x));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn classTree;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/CollectionUtils.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.List;\n\n/**\n * Collection utilities that don't fit anywhere else.\n * These should ideally get migrated to <a href=\"https://github.com/Col-E/ExtraCollections\">Extra-Collections</a>.\n *\n * @author Matt Coley\n */\npublic class CollectionUtils {\n\t/**\n\t * @param list\n\t * \t\tList to search through.\n\t * @param element\n\t * \t\tElement to search for.\n\t * @param <T>\n\t * \t\tInferred element type.\n\t *\n\t * @return The index of the first occurrence of {@code element} in {@code list}, or -1 if not found.\n\t */\n\tpublic static <T> int identityIndexOf(@Nonnull List<? extends T> list, T element) {\n\t\tfor (int i = list.size(); i != 0; )\n\t\t\tif (element == list.get(--i))\n\t\t\t\treturn i;\n\t\treturn -1;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/DesktopUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.analytics.SystemInformation;\n\nimport java.awt.Dimension;\nimport java.awt.DisplayMode;\nimport java.awt.GraphicsDevice;\nimport java.awt.GraphicsEnvironment;\nimport java.awt.Toolkit;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\n\n/**\n * Platform specific desktop interaction utilities.\n *\n * @author xDark\n * @author Matt Coley\n */\npublic class DesktopUtil {\n\tprivate static final Dimension primaryScreenSize;\n\tprivate static final Dimension largestScreenSize;\n\n\t/**\n\t * @return Screen dimensions of the primary monitor.\n\t */\n\t@Nonnull\n\tpublic static Dimension getPrimaryScreenSize() {\n\t\treturn primaryScreenSize;\n\t}\n\n\t/**\n\t * @return Largest pairing of any monitor's width and height.\n\t */\n\t@Nonnull\n\tpublic static Dimension getLargestScreenSize() {\n\t\treturn largestScreenSize;\n\t}\n\n\t/**\n\t * Attempts to launch a browser to display a {@link URI}.\n\t *\n\t * @param uri\n\t * \t\tURI to display.\n\t *\n\t * @throws IOException\n\t * \t\tIf the browser is not found, or it fails\n\t * \t\tto be launched.\n\t */\n\tpublic static void showDocument(@Nonnull URI uri) throws IOException {\n\t\tfinal Runtime rt = Runtime.getRuntime();\n\t\tswitch (PlatformType.get()) {\n\t\t\tcase MAC -> rt.exec(new String[]{\"open\", uri.toString()});\n\t\t\tcase WINDOWS -> rt.exec(new String[]{\"rundll32\", \"url.dll,FileProtocolHandler\", uri.toString()});\n\t\t\tcase LINUX -> {\n\t\t\t\tString[] browsers = new String[]{\"xdg-open\", \"google-chrome\", \"firefox\", \"opera\", \"konqueror\", \"mozilla\"};\n\t\t\t\tfor (String browser : browsers) {\n\t\t\t\t\ttry (InputStream in = rt.exec(new String[]{\"which\", browser}).getInputStream()) {\n\t\t\t\t\t\tif (in.read() != -1) {\n\t\t\t\t\t\t\trt.exec(new String[]{browser, uri.toString()});\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthrow new IOException(\"No browser found\");\n\t\t\t}\n\t\t\tdefault -> throw new IllegalStateException(\"Unsupported OS: \" + SystemInformation.OS_NAME);\n\t\t}\n\t}\n\n\tstatic {\n\t\ttry {\n\t\t\tToolkit kit = Toolkit.getDefaultToolkit();\n\t\t\tprimaryScreenSize = kit.getScreenSize();\n\t\t\tint width = 1;\n\t\t\tint heigth = 1;\n\t\t\tfor (GraphicsDevice screenDevice : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) {\n\t\t\t\tDisplayMode display = screenDevice.getDisplayMode();\n\t\t\t\tif (width < display.getWidth())\n\t\t\t\t\twidth = display.getWidth();\n\t\t\t\tif (heigth < display.getHeight())\n\t\t\t\t\theigth = display.getHeight();\n\t\t\t}\n\t\t\tlargestScreenSize = new Dimension(width, heigth);\n\t\t} catch (Exception ex) {\n\t\t\tthrow new IllegalStateException(\"Could not get screen size\", ex);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/DevDetection.java",
    "content": "package software.coley.recaf.util;\n\nimport java.io.File;\n\n/**\n * Basic util to detect if Recaf is running in a developer environment.\n *\n * @author Matt Coley\n */\npublic class DevDetection {\n\t/**\n\t * @return {@code true} when a developer environment is detected.\n\t */\n\tpublic static boolean isDevEnv() {\n\t\t// Should only be true when building Recaf from source/build-system.\n\t\tString path = System.getProperty(\"java.class.path\");\n\t\treturn path.contains(\"recaf-core\" + File.separator + \"build\") ||\n\t\t\t\tpath.contains(\"recaf-core\" + File.separator + \"out\") ||\n\t\t\t\tpath.contains(\"recaf-ui\" + File.separator + \"build\") ||\n\t\t\t\tpath.contains(\"recaf-ui\" + File.separator + \"out\");\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/EscapeUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport it.unimi.dsi.fastutil.chars.Char2ObjectMap;\nimport it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;\nimport it.unimi.dsi.fastutil.chars.CharSet;\nimport it.unimi.dsi.fastutil.objects.Object2CharMap;\nimport it.unimi.dsi.fastutil.objects.Object2CharOpenHashMap;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport java.util.BitSet;\nimport java.util.Set;\nimport java.util.TreeSet;\n\n/**\n * Escape code replacement utility.\n *\n * @author xDark\n */\npublic final class EscapeUtil {\n\tprivate static final Char2ObjectMap<String> WHITESPACE_TO_ESCAPE = new Char2ObjectOpenHashMap<>();\n\tprivate static final Object2CharMap<String> ESCAPE_TO_WHITESPACE = new Object2CharOpenHashMap<>(); // TODO: Shouldn't we use this?\n\tprivate static final Set<String> WHITESPACE_STRINGS = new TreeSet<>();\n\tprivate static final BitSet WHITESPACE_LOOKUP = new BitSet(Character.MAX_VALUE + 1);\n\tpublic static final char TERMINATOR = '\\0';\n\tpublic static final String ESCAPED_SPACE = \"\\\\u0020\";\n\tpublic static final String ESCAPED_TAB = \"\\\\u0009\";\n\tpublic static final String ESCAPED_NEWLINE = \"\\\\u000A\";\n\tpublic static final String ESCAPED_RETURN = \"\\\\u000D\";\n\tpublic static final String ESCAPED_DOUBLE_QUOTE = \"\\\\u0022\";\n\tpublic static final String ESCAPED_BACKWARDS_SLASH = \"\\\\\\\\\";\n\n\tprivate EscapeUtil() {}\n\n\t/**\n\t * @param text\n\t * \t\tText to check.\n\t *\n\t * @return {@code true} when text contains any whitespace characters.\n\t */\n\tpublic static boolean containsWhitespace(@Nonnull String text) {\n\t\tint len = text.length();\n\t\tfor (int i = 0; i < len; i++)\n\t\t\tif (WHITESPACE_LOOKUP.get(text.charAt(i)))\n\t\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param c\n\t * \t\tCharacter to check.\n\t *\n\t * @return {@code true} if it represents a whitespace character.\n\t */\n\tpublic static boolean isWhitespaceChar(char c) {\n\t\treturn WHITESPACE_LOOKUP.get(c);\n\t}\n\n\t/**\n\t * @return Set of strings representing various whitespaces.\n\t *\n\t * @see #getWhitespaceChars() Alternative offering the values as {@code char}\n\t */\n\t@Nonnull\n\tpublic static Set<String> getWhitespaceStrings() {\n\t\treturn WHITESPACE_STRINGS;\n\t}\n\n\t/**\n\t * @return Set of chars representing various whitespaces.\n\t */\n\t@Nonnull\n\tpublic static CharSet getWhitespaceChars() {\n\t\treturn WHITESPACE_TO_ESCAPE.keySet();\n\t}\n\n\t/**\n\t * Replaces any common escapable sequence with an escaped sequence.\n\t * <br>\n\t * For example: {@code \\n}, {@code \\r}, {@code \\t}, {@code \\}, and {@code \"}\n\t *\n\t * @param input\n\t * \t\tInput text.\n\t *\n\t * @return String without common escaped characters.\n\t */\n\tpublic static String escapeStandard(@Nullable String input) {\n\t\treturn visit(input, EscapeUtil::computeUnescapeStandard);\n\t}\n\n\t/**\n\t * Replaces any unicode-whitespace or common escapable sequence with an escaped sequence.\n\t * <br>\n\t * Combines both standard sequences and less common unicode whitespaces.\n\t *\n\t * @param input\n\t * \t\tInput text.\n\t *\n\t * @return String without escaped characters.\n\t */\n\tpublic static String escapeStandardAndUnicodeWhitespace(@Nullable String input) {\n\t\treturn visit(escapeStandard(input), EscapeUtil::computeUnescapeUnicode);\n\t}\n\n\t/**\n\t * Replaces any standard/unicode-whitespace escape targets with unicode escapes.\n\t * <br>\n\t * Combines both standard sequences and less common unicode whitespaces.\n\t * <br>\n\t * Even standard escape targets are escaped using unicode escapes.\n\t *\n\t * @param input\n\t * \t\tInput text.\n\t *\n\t * @return String without escaped characters.\n\t */\n\tpublic static String escapeStandardAndUnicodeWhitespaceAlt(@Nullable String input) {\n\t\treturn visit(input, EscapeUtil::computeUnescapeUnicodeJasm);\n\t}\n\n\t/**\n\t * Replaces any escape code with its literal value.\n\t *\n\t * @param input\n\t * \t\tInput text.\n\t *\n\t * @return String with escaped characters.\n\t */\n\tpublic static String unescapeStandardAndUnicodeWhitespace(@Nullable String input) {\n\t\treturn unescapeStandard(unescapeUnicode(input));\n\t}\n\n\t/**\n\t * Replaces escaped unicode with actual unicode.\n\t * <br>\n\t * For example: {@code \\\\u0048} --> {@code H}\n\t *\n\t * @param input\n\t * \t\tInput text.\n\t *\n\t * @return String with escaped characters.\n\t */\n\tpublic static String unescapeUnicode(@Nullable String input) {\n\t\treturn visit(input, EscapeUtil::computeEscapeUnicode);\n\t}\n\n\t/**\n\t * Replaces standard escape codes with literal values.\n\t * <br>\n\t * For example: {@code \\n}, {@code \\r}, {@code \\t}, {@code \\}, and {@code \"}\n\t *\n\t * @param input\n\t * \t\tInput text.\n\t *\n\t * @return String with escaped characters.\n\t */\n\tpublic static String unescapeStandard(@Nullable String input) {\n\t\treturn visit(input, EscapeUtil::computeEscapeStandard);\n\t}\n\n\tprivate static String visit(@Nullable String input, @Nonnull Visitor consumer) {\n\t\tif (input == null)\n\t\t\treturn null;\n\t\tint len = input.length();\n\t\tint cursor = 0;\n\t\tStringBuilder builder = new StringBuilder(len);\n\t\twhile (cursor < len) {\n\t\t\tint consumed = consumer.apply(input, cursor, builder);\n\t\t\tif (consumed == 0) {\n\t\t\t\t// Nothing consumed, not an escaped character\n\t\t\t\tchar c1 = input.charAt(cursor++);\n\t\t\t\tbuilder.append(c1);\n\n\t\t\t\t// Does additional character need to be appended?\n\t\t\t\tif (Character.isHighSurrogate(c1) && cursor < len) {\n\t\t\t\t\tchar c2 = input.charAt(cursor);\n\t\t\t\t\tif (Character.isLowSurrogate(c2)) {\n\t\t\t\t\t\tbuilder.append(c2);\n\t\t\t\t\t\tcursor += 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Shift cursor by amount consumed\n\t\t\t\tfor (int pt = 0; pt < consumed; ++pt) {\n\t\t\t\t\tcursor += Character.charCount(Character.codePointAt(input, cursor));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn builder.toString();\n\t}\n\n\tprivate static int computeUnescapeUnicode(String input, int cursor, StringBuilder builder) {\n\t\t// Bounds check\n\t\tif (cursor >= input.length()) {\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Check if next character finishes an unescaped value, 1 if so, 0 if not.\n\t\tchar current = input.charAt(cursor);\n\t\tString escaped = WHITESPACE_TO_ESCAPE.get(current);\n\t\tif (escaped != null) {\n\t\t\tbuilder.append(escaped);\n\t\t\treturn 1;\n\t\t}\n\n\t\t// No replacement\n\t\treturn 0;\n\t}\n\n\tprivate static int computeUnescapeUnicodeJasm(String input, int cursor, StringBuilder builder) {\n\t\t// Bounds check\n\t\tif (cursor >= input.length())\n\t\t\treturn 0;\n\n\t\t// Check if character is a special char\n\t\tchar current = input.charAt(cursor);\n\t\tString escaped = switch (current) {\n\t\t\tcase ' ' -> ESCAPED_SPACE;\n\t\t\tcase '\\t' -> ESCAPED_TAB;\n\t\t\tcase '\\n' -> ESCAPED_NEWLINE;\n\t\t\tcase '\\r' -> ESCAPED_RETURN;\n\t\t\tcase '\\\"' -> ESCAPED_DOUBLE_QUOTE;\n\t\t\tcase '\\\\' -> ESCAPED_BACKWARDS_SLASH;\n\t\t\tdefault -> WHITESPACE_TO_ESCAPE.get(current);\n\t\t};\n\n\t\t// Check if next character finishes an unescaped value, 1 if so, 0 if not.\n\t\tif (escaped != null) {\n\t\t\tbuilder.append(escaped);\n\t\t\treturn 1;\n\t\t}\n\n\t\t// No replacement\n\t\treturn 0;\n\t}\n\n\tprivate static int computeUnescapeStandard(String input, int cursor, StringBuilder builder) {\n\t\t// Bounds check\n\t\tif (cursor >= input.length()) {\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Check if next character finishes an unescaped value, 1 if so, 0 if not.\n\t\tchar current = input.charAt(cursor);\n\t\tswitch (current) {\n\t\t\tcase '\\n':\n\t\t\t\tbuilder.append(\"\\\\n\");\n\t\t\t\treturn 1;\n\t\t\tcase '\\r':\n\t\t\t\tbuilder.append(\"\\\\r\");\n\t\t\t\treturn 1;\n\t\t\tcase '\\t':\n\t\t\t\tbuilder.append(\"\\\\t\");\n\t\t\t\treturn 1;\n\t\t\tcase '\\\\':\n\t\t\t\tbuilder.append(\"\\\\\\\\\");\n\t\t\t\treturn 1;\n\t\t\tcase '\"':\n\t\t\t\tbuilder.append(\"\\\\\\\"\");\n\t\t\t\treturn 1;\n\t\t\tdefault:\n\t\t\t\treturn 0;\n\t\t}\n\t}\n\n\tprivate static int computeEscapeUnicode(String input, int cursor, StringBuilder builder) {\n\t\t// Bounds check\n\t\tif (cursor + 1 >= input.length()) {\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Check for double backslash in prefix \"\\\\\\\\u\" in \"\\\\\\\\uXXXX\"\n\t\tchar c1 = input.charAt(cursor);\n\t\tchar c2 = input.charAt(cursor + 1);\n\t\tboolean initialEscape = c1 == '\\\\' && c2 == '\\\\';\n\n\t\t// Check prefix \"\\\\u\" in \"\\\\uXXXX\"\n\t\tif (!initialEscape && (c1 != '\\\\' || c2 != 'u')) {\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Compute escape size, initial is 2 for the \"\\\\u\"\n\t\tfinal int initialLen = 2;\n\t\tint len = initialLen;\n\n\t\t// Combined:\n\t\t// - Bounds check\n\t\t// - Case check for \"\\\\uuXXXX\" where 'u' is repeated\n\t\twhile (cursor + len < input.length() && input.charAt(cursor + len) == 'u') {\n\t\t\tlen++;\n\t\t}\n\n\t\t// Combined:\n\t\t// - Bounds check\n\t\t// - Case check for \"\\\\u+XXXX\" format\n\t\tif (cursor + len < input.length() && input.charAt(cursor + len) == '+') {\n\t\t\tlen += 1;\n\t\t}\n\n\t\t// Bounds check, then fetch hex value and store in builder, then return total consumed length\n\t\tif (cursor + len + 4 <= input.length()) {\n\t\t\tString existing = input.substring(cursor, cursor + len + 4);\n\t\t\tif (initialEscape) {\n\t\t\t\t// Keep the '\\\\uXXXX' format.\n\t\t\t\tbuilder.append(existing);\n\t\t\t} else {\n\t\t\t\tString unicode = input.substring(cursor + len, cursor + len + 4);\n\t\t\t\ttry {\n\t\t\t\t\tint value = Integer.parseInt(unicode, 16);\n\t\t\t\t\tbuilder.append(value != TERMINATOR ? (char) value : existing);\n\t\t\t\t} catch (NumberFormatException ignored) {\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn len + 4;\n\t\t}\n\n\t\t// If we didn't see any recognized pattern, just escape \"\\\\\\\\\" into \"\\\\\"\n\t\tif (initialEscape && len == initialLen) {\n\t\t\treturn 1;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tprivate static int computeEscapeStandard(String input, int cursor, StringBuilder builder) {\n\t\t// Bounds check\n\t\tif (cursor + 1 >= input.length()) {\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Check prefix '\\' in \"\\X\"\n\t\tif (input.charAt(cursor) != '\\\\') {\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Check if next character finishes the escape pattern, 2 if so, 0 if not.\n\t\tchar next = input.charAt(cursor + 1);\n\t\tswitch (next) {\n\t\t\tcase 'n':\n\t\t\t\tbuilder.append('\\n');\n\t\t\t\treturn 2;\n\t\t\tcase 'r':\n\t\t\t\tbuilder.append('\\r');\n\t\t\t\treturn 2;\n\t\t\tcase 't':\n\t\t\t\tbuilder.append('\\t');\n\t\t\t\treturn 2;\n\t\t\tcase '\\\\':\n\t\t\t\tbuilder.append('\\\\');\n\t\t\t\treturn 2;\n\t\t\tcase '\"':\n\t\t\t\tbuilder.append('\"');\n\t\t\t\treturn 2;\n\t\t\tdefault:\n\t\t\t\treturn 0;\n\t\t}\n\t}\n\n\tprivate interface Visitor {\n\t\tint apply(String input, int cursor, StringBuilder builder);\n\t}\n\n\tstatic void addWhitespace(char unescape, String escape) {\n\t\t// Mapping between whitespace unicode value and character\n\t\tWHITESPACE_STRINGS.add(String.valueOf(unescape));\n\t\tWHITESPACE_TO_ESCAPE.put(unescape, escape);\n\t\tESCAPE_TO_WHITESPACE.put(escape, unescape);\n\t\tWHITESPACE_LOOKUP.set(unescape);\n\t}\n\n\tstatic {\n\t\t//Unicode whitespaces or visually empty characters (like null terminators and such)\n\t\tfor (char i = 0; i < 0x20; i++) {\n\t\t\taddWhitespace(i, \"\\\\u\" + String.format(\"%04X\", (int) i));\n\t\t}\n\t\tfor (char i = 0x7F; i < 0xA0; i++) {\n\t\t\taddWhitespace(i, \"\\\\u\" + String.format(\"%04X\", (int) i));\n\t\t}\n\t\tfor (char i = 0x6E5; i < 0x6E6; i++) {\n\t\t\taddWhitespace(i, \"\\\\u\" + String.format(\"%04X\", (int) i));\n\t\t}\n\t\tfor (char i = 0x17B4; i < 0x17B5; i++) {\n\t\t\taddWhitespace(i, \"\\\\u\" + String.format(\"%04X\", (int) i));\n\t\t}\n\t\tfor (char i = 0x180B; i < 0x180E; i++) {\n\t\t\taddWhitespace(i, \"\\\\u\" + String.format(\"%04X\", (int) i));\n\t\t}\n\t\tfor (char i = 0x2000; i < 0x200F; i++) {\n\t\t\taddWhitespace(i, \"\\\\u\" + String.format(\"%04X\", (int) i));\n\t\t}\n\t\tfor (char i = 0x2028; i < 0x202F; i++) {\n\t\t\taddWhitespace(i, \"\\\\u\" + String.format(\"%04X\", (int) i));\n\t\t}\n\t\tfor (char i = 0x205F; i < 0x206F; i++) {\n\t\t\taddWhitespace(i, \"\\\\u\" + String.format(\"%04X\", (int) i));\n\t\t}\n\t\tfor (char i = 0x2400; i < 0x243F; i++) {\n\t\t\taddWhitespace(i, \"\\\\u\" + String.format(\"%04X\", (int) i));\n\t\t}\n\t\tfor (char i = 0xE000; i < 0xF8FF; i++) {\n\t\t\taddWhitespace(i, \"\\\\u\" + String.format(\"%04X\", (int) i));\n\t\t}\n\t\tfor (char i = 0xFE00; i < 0xFE0F; i++) {\n\t\t\taddWhitespace(i, \"\\\\u\" + String.format(\"%04X\", (int) i));\n\t\t}\n\t\tfor (char i = 0xFE1A; i < 0xFE20; i++) {\n\t\t\taddWhitespace(i, \"\\\\u\" + String.format(\"%04X\", (int) i));\n\t\t}\n\t\tfor (char i = 0xFFFF; i >= 0xFFF0; i--) {\n\t\t\taddWhitespace(i, \"\\\\u\" + String.format(\"%04X\", (int) i));\n\t\t}\n\t\taddWhitespace((('ㅤ')), \"\\\\u\" + String.format(\"%04X\", (int) 'ㅤ'));\n\t\taddWhitespace((('\\u1680')), \"\\\\u\" + String.format(\"%04X\", (int) '\\u1680'));\n\t\taddWhitespace((('\\u2800')), \"\\\\u\" + String.format(\"%04X\", (int) '\\u2800'));\n\t\taddWhitespace((('\\u3000')), \"\\\\u\" + String.format(\"%04X\", (int) '\\u3000'));\n\t\taddWhitespace((('\\u318F')), \"\\\\u\" + String.format(\"%04X\", (int) '\\u318F'));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/ExcludeFromJacocoGeneratedReport.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * JaCoCo excludes coverage metrics from classes and methods annotated with names including {@code \"Generated\"}.\n * <p>\n * <h1>USE THIS CLASS SPARINGLY</h1>\n * <b>Only annotate things with this if they are POJO's!</b>\n * Some classes like data models and config objects contribute to coverage metrics with things like getter/setters\n * that do not realistically need to be covered.\n *\n * @author Matt Coley\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.METHOD, ElementType.TYPE})\npublic @interface ExcludeFromJacocoGeneratedReport {\n\t/**\n\t * @return Reason why we exclude the annotated element.\n\t */\n\t@Nonnull\n\tString justification();\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/Handles.java",
    "content": "package software.coley.recaf.util;\n\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.Opcodes;\n\n/**\n * Various handle utils.\n *\n * @author Matt Coley\n */\npublic class Handles {\n\tpublic static final Handle META_FACTORY = new Handle(\n\t\t\tOpcodes.H_INVOKESTATIC,\n\t\t\t\"java/lang/invoke/LambdaMetafactory\",\n\t\t\t\"metafactory\",\n\t\t\t\"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;\" +\n\t\t\t\t\t\"Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)\" +\n\t\t\t\t\t\"Ljava/lang/invoke/CallSite;\", false);\n\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/IOUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.ApkFileInfo;\nimport software.coley.recaf.info.ArscFileInfo;\nimport software.coley.recaf.info.BinaryXmlFileInfo;\nimport software.coley.recaf.info.DexFileInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.JModFileInfo;\nimport software.coley.recaf.info.JarFileInfo;\nimport software.coley.recaf.info.WarFileInfo;\nimport software.coley.recaf.info.ZipFileInfo;\n\nimport java.io.BufferedReader;\nimport java.io.ByteArrayOutputStream;\nimport java.io.Closeable;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.Reader;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.FileSystems;\nimport java.nio.file.FileVisitResult;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.SimpleFileVisitor;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.Arrays;\n\n/**\n * IO utilities.\n *\n * @author xDark\n * @see UnsafeIO\n */\npublic final class IOUtil {\n\tpublic static final int BUFFER_SIZE = 16384;\n\n\t/**\n\t * Deny all constructions.\n\t */\n\tprivate IOUtil() {\n\t}\n\n\t/**\n\t * Quietly closes a resource.\n\t *\n\t * @param closeable\n\t *        {@link Closeable} to close.\n\t */\n\tpublic static void closeQuietly(Closeable closeable) {\n\t\tif (closeable != null) {\n\t\t\ttry {\n\t\t\t\tcloseable.close();\n\t\t\t} catch (IOException ignored) {\n\t\t\t\t// no-op\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Quietly closes a resource.\n\t *\n\t * @param closeable\n\t *        {@link Closeable} to close.\n\t */\n\tpublic static void closeQuietly(AutoCloseable closeable) {\n\t\tif (closeable != null) {\n\t\t\ttry {\n\t\t\t\tcloseable.close();\n\t\t\t} catch (Exception ignored) {\n\t\t\t\t// no-op\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Allocates new byte buffer.\n\t *\n\t * @return New byte buffer.\n\t *\n\t * @see IOUtil#BUFFER_SIZE\n\t */\n\t@Nonnull\n\tpublic static byte[] newByteBuffer() {\n\t\treturn new byte[BUFFER_SIZE];\n\t}\n\n\t/**\n\t * Allocates new char buffer.\n\t *\n\t * @return New char buffer.\n\t *\n\t * @see IOUtil#BUFFER_SIZE\n\t */\n\t@Nonnull\n\tpublic static char[] newCharBuffer() {\n\t\treturn new char[BUFFER_SIZE];\n\t}\n\n\t/**\n\t * Returns content of {@link InputStream} as a byte array.\n\t *\n\t * @param in\n\t * \t\tStream to transfer content from.\n\t * @param buf\n\t * \t\tBuffer that is used to hold data.\n\t *\n\t * @return content of {@link InputStream}.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t */\n\t@Nonnull\n\tpublic static byte[] toByteArray(InputStream in, byte[] buf) throws IOException {\n\t\tint length;\n\t\ttry {\n\t\t\tlength = in.available();\n\t\t} catch (IOException ignored) {\n\t\t\t// Cannot get length from stream, fallback to default.\n\t\t\tlength = BUFFER_SIZE;\n\t\t}\n\t\tOptimizedByteArrayOutputStream baos = new OptimizedByteArrayOutputStream(length);\n\t\tcopy(in, baos, buf);\n\t\treturn baos.getBytes();\n\t}\n\n\t/**\n\t * Returns content of {@link InputStream} as a byte array.\n\t *\n\t * @param in\n\t * \t\tStream to transfer content from.\n\t *\n\t * @return Content of {@link InputStream}.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t * @see IOUtil#newByteBuffer()\n\t */\n\t@Nonnull\n\tpublic static byte[] toByteArray(InputStream in) throws IOException {\n\t\treturn toByteArray(in, newByteBuffer());\n\t}\n\n\t/**\n\t * Creates {@link BufferedReader} from {@link InputStreamReader}.\n\t *\n\t * @param reader\n\t * \t\tReader to buffer.\n\t * @param buf\n\t * \t\tBuffer that is used to hold data.\n\t *\n\t * @return Buffered reader.\n\t */\n\t@Nonnull\n\tpublic static BufferedReader toBufferedReader(InputStreamReader reader, char[] buf) {\n\t\tBufferedReader br = new BufferedReader(reader, 1);\n\t\tUnsafeIO.setReaderBuffer(br, buf);\n\t\treturn br;\n\t}\n\n\t/**\n\t * Creates {@link BufferedReader} from {@link InputStreamReader}.\n\t *\n\t * @param reader\n\t * \t\tReader to buffer.\n\t * @param bufferSize\n\t * \t\tSize of the buffer.\n\t *\n\t * @return Buffered reader.\n\t */\n\t@Nonnull\n\tpublic static BufferedReader toBufferedReader(InputStreamReader reader, int bufferSize) {\n\t\treturn new BufferedReader(reader, bufferSize);\n\t}\n\n\t/**\n\t * Creates {@link BufferedReader} from {@link InputStreamReader}.\n\t *\n\t * @param reader\n\t * \t\tReader to buffer.\n\t *\n\t * @return Buffered reader.\n\t *\n\t * @see IOUtil#BUFFER_SIZE\n\t */\n\t@Nonnull\n\tpublic static BufferedReader toBufferedReader(InputStreamReader reader) {\n\t\treturn new BufferedReader(reader, BUFFER_SIZE);\n\t}\n\n\t/**\n\t * Creates {@link BufferedReader} from {@link Reader}.\n\t *\n\t * @param reader\n\t * \t\tReader to buffer.\n\t * @param buf\n\t * \t\tBuffer that is used to hold data.\n\t *\n\t * @return buffered reader.\n\t */\n\t@Nonnull\n\tpublic static BufferedReader toBufferedReader(Reader reader, char[] buf) {\n\t\tif (reader instanceof BufferedReader) {\n\t\t\treturn (BufferedReader) reader;\n\t\t}\n\t\tBufferedReader br = new BufferedReader(reader, 1);\n\t\tUnsafeIO.setReaderBuffer(br, buf);\n\t\treturn br;\n\t}\n\n\t/**\n\t * Creates {@link BufferedReader} from {@link Reader}.\n\t *\n\t * @param reader\n\t * \t\tReader to buffer.\n\t * @param bufferSize\n\t * \t\tSize of the buffer.\n\t *\n\t * @return buffered reader.\n\t */\n\t@Nonnull\n\tpublic static BufferedReader toBufferedReader(Reader reader, int bufferSize) {\n\t\treturn reader instanceof BufferedReader\n\t\t\t\t? (BufferedReader) reader\n\t\t\t\t: new BufferedReader(reader, bufferSize);\n\t}\n\n\t/**\n\t * Creates {@link BufferedReader} from {@link Reader}.\n\t *\n\t * @param reader\n\t * \t\tReader to buffer.\n\t *\n\t * @return Buffered reader.\n\t *\n\t * @see IOUtil#BUFFER_SIZE\n\t */\n\t@Nonnull\n\tpublic static BufferedReader toBufferedReader(Reader reader) {\n\t\treturn reader instanceof BufferedReader\n\t\t\t\t? (BufferedReader) reader\n\t\t\t\t: new BufferedReader(reader, BUFFER_SIZE);\n\t}\n\n\t/**\n\t * Creates {@link BufferedReader} from {@link InputStream}.\n\t *\n\t * @param in\n\t * \t\tStream to buffer.\n\t * @param charset\n\t * \t\tCharset that will be used to read data.\n\t * @param buf\n\t * \t\tBuffer that is used to hold data.\n\t *\n\t * @return Buffered reader.\n\t */\n\t@Nonnull\n\tpublic static BufferedReader toBufferedReader(InputStream in, Charset charset, char[] buf) {\n\t\treturn toBufferedReader(new InputStreamReader(in, charset), buf);\n\t}\n\n\t/**\n\t * Creates {@link BufferedReader} from {@link InputStream}.\n\t *\n\t * @param in\n\t * \t\tStream to buffer.\n\t * @param charset\n\t * \t\tCharset that will be used to read data.\n\t * @param bufferSize\n\t * \t\tSize of the buffer.\n\t *\n\t * @return Buffered reader.\n\t */\n\t@Nonnull\n\tpublic static BufferedReader toBufferedReader(InputStream in, Charset charset, int bufferSize) {\n\t\treturn toBufferedReader(new InputStreamReader(in, charset), bufferSize);\n\t}\n\n\t/**\n\t * Creates {@link BufferedReader} from {@link InputStream}.\n\t *\n\t * @param in\n\t * \t\tStream to buffer.\n\t * @param charset\n\t * \t\tCharset that will be used to read data.\n\t *\n\t * @return buffered reader.\n\t *\n\t * @see IOUtil#BUFFER_SIZE\n\t */\n\t@Nonnull\n\tpublic static BufferedReader toBufferedReader(InputStream in, Charset charset) {\n\t\treturn new BufferedReader(new InputStreamReader(in, charset), BUFFER_SIZE);\n\t}\n\n\t/**\n\t * Creates {@link BufferedReader} from {@link InputStream}.\n\t * Uses {@link StandardCharsets#UTF_8} as default charset.\n\t *\n\t * @param in\n\t * \t\tStream to buffer.\n\t *\n\t * @return buffered reader.\n\t *\n\t * @see IOUtil#BUFFER_SIZE\n\t * @see StandardCharsets#UTF_8\n\t */\n\t@Nonnull\n\tpublic static BufferedReader toBufferedReader(InputStream in) {\n\t\treturn new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8), BUFFER_SIZE);\n\t}\n\n\t/**\n\t * Reads content of {@link InputStream} to a string.\n\t *\n\t * @param in\n\t * \t\tStream to read from.\n\t * @param charset\n\t * \t\tCharset that will be used to read data.\n\t * @param buf\n\t * \t\tBuffer that is used to hold data.\n\t *\n\t * @return Content of a stream as string.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t */\n\t@Nonnull\n\tpublic static String toString(InputStream in, Charset charset, char[] buf) throws IOException {\n\t\tStringWriter writer;\n\t\ttry (BufferedReader reader = toBufferedReader(in, charset, buf)) {\n\t\t\twriter = new StringWriter();\n\t\t\tcopy(reader, writer, buf);\n\t\t}\n\t\treturn writer.toString();\n\t}\n\n\t/**\n\t * Reads content of {@link InputStream} to a string.\n\t * Uses {@link StandardCharsets#UTF_8} as default charset.\n\t *\n\t * @param in\n\t * \t\tStream to read from.\n\t * @param buf\n\t * \t\tBuffer that is used to hold data.\n\t *\n\t * @return Content of a stream as string.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t * @see StandardCharsets#UTF_8\n\t */\n\t@Nonnull\n\tpublic static String toString(InputStream in, char[] buf) throws IOException {\n\t\treturn toString(in, StandardCharsets.UTF_8, buf);\n\t}\n\n\t/**\n\t * Reads content of {@link InputStream} to a string.\n\t *\n\t * @param in\n\t * \t\tStream to read from.\n\t * @param charset\n\t * \t\tCharset that will be used to read data.\n\t *\n\t * @return Content of a stream as string.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t * @see IOUtil#newCharBuffer()\n\t */\n\t@Nonnull\n\tpublic static String toString(InputStream in, Charset charset) throws IOException {\n\t\treturn toString(in, charset, newCharBuffer());\n\t}\n\n\t/**\n\t * Reads content of {@link InputStream} to a string.\n\t * Uses {@link StandardCharsets#UTF_8} as default charset.\n\t *\n\t * @param in\n\t * \t\tStream to read from.\n\t *\n\t * @return Content of a stream as string.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t * @see IOUtil#newCharBuffer()\n\t * @see StandardCharsets#UTF_8\n\t */\n\t@Nonnull\n\tpublic static String toString(InputStream in) throws IOException {\n\t\treturn toString(in, StandardCharsets.UTF_8, newCharBuffer());\n\t}\n\n\t/**\n\t * Get the extension from a path.\n\t *\n\t * @param path\n\t * \t\tPath to get extension from.\n\t *\n\t * @return Name of extension of the path if present, {@code null} otherwise.\n\t */\n\t@Nullable\n\tpublic static String getExtension(String path) {\n\t\tint dotIndex = path.lastIndexOf('.');\n\t\tif (dotIndex < path.length() - 1) {\n\t\t\treturn path.substring(dotIndex + 1);\n\t\t} else {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get the extension from a path.\n\t *\n\t * @param path\n\t *        {@link Path} to get extension from.\n\t *\n\t * @return Name of extension of the path if present, {@code null} otherwise.\n\t */\n\t@Nullable\n\tpublic static String getExtension(Path path) {\n\t\treturn getExtension(path.getFileName().toString());\n\t}\n\n\t/**\n\t * Get the file extension from specific subtypes of {@link FileInfo}.\n\t *\n\t * @param file\n\t * \t\tFile to get extension of.\n\t *\n\t * @return Extension associated with files of the given type, or {@code null} if no specific extension is known.\n\t */\n\t@Nullable\n\tpublic static String getExtensionFromFileInfo(@Nullable FileInfo file) {\n\t\treturn switch (file) {\n\t\t\tcase ApkFileInfo _ -> \"apk\";\n\t\t\tcase ArscFileInfo _ -> \"arsc\";\n\t\t\tcase BinaryXmlFileInfo _ -> \"xml\";\n\t\t\tcase DexFileInfo _ -> \"dex\";\n\t\t\tcase JarFileInfo _ -> \"jar\";\n\t\t\tcase JModFileInfo _ -> \"jmod\";\n\t\t\tcase WarFileInfo _ -> \"war\";\n\t\t\tcase ZipFileInfo _ -> \"zip\";\n\t\t\tcase null, default -> null;\n\t\t};\n\t}\n\n\n\t/**\n\t * Transfers content of {@link InputStream} into {@link OutputStream}.\n\t *\n\t * @param in\n\t * \t\tStream to transfer content from.\n\t * @param out\n\t * \t\tStream to transfer content to.\n\t * @param buf\n\t * \t\tBuffer that is used to hold data.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t */\n\tpublic static void copy(InputStream in, OutputStream out, byte[] buf) throws IOException {\n\t\tint r;\n\t\twhile ((r = in.read(buf)) != -1)\n\t\t\tout.write(buf, 0, r);\n\t}\n\n\t/**\n\t * Transfers content of {@link InputStream} into {@link OutputStream}.\n\t *\n\t * @param in\n\t * \t\tStream to transfer content from.\n\t * @param out\n\t * \t\tStream to transfer content to.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t * @see IOUtil#newByteBuffer()\n\t */\n\tpublic static void copy(InputStream in, OutputStream out) throws IOException {\n\t\tcopy(in, out, newByteBuffer());\n\t}\n\n\t/**\n\t * Transfers content of {@link Reader} into {@link Writer}.\n\t *\n\t * @param reader\n\t * \t\tReader to transfer content from.\n\t * @param writer\n\t * \t\tWriter to transfer content to.\n\t * @param buf\n\t * \t\tBuffer that is used to hold data.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t */\n\tpublic static void copy(Reader reader, Writer writer, char[] buf) throws IOException {\n\t\tint r;\n\t\twhile ((r = reader.read(buf)) != -1) {\n\t\t\twriter.write(buf, 0, r);\n\t\t}\n\t}\n\n\t/**\n\t * Transfers content of {@link Reader} into {@link Writer}.\n\t *\n\t * @param reader\n\t * \t\tReader to transfer content from.\n\t * @param writer\n\t * \t\tWriter to transfer content to.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t * @see IOUtil#newCharBuffer()\n\t */\n\tpublic static void copy(Reader reader, Writer writer) throws IOException {\n\t\tcopy(reader, writer, newCharBuffer());\n\t}\n\n\t/**\n\t * Transfers content of {@link InputStream} into {@link Writer}.\n\t *\n\t * @param in\n\t * \t\tStream to transfer content from.\n\t * @param writer\n\t * \t\tWriter to transfer content to.\n\t * @param charset\n\t * \t\tCharset that will be used to read data.\n\t * @param buf\n\t * \t\tBuffer that is used to hold data.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t */\n\tpublic static void copy(InputStream in, Writer writer, Charset charset, char[] buf) throws IOException {\n\t\tcopy(new InputStreamReader(in, charset), writer, buf);\n\t}\n\n\t/**\n\t * Transfers content of {@link InputStream} into {@link Writer}.\n\t *\n\t * @param in\n\t * \t\tStream to transfer content from.\n\t * @param writer\n\t * \t\tWriter to transfer content to.\n\t * @param charset\n\t * \t\tCharset that will be used to read data.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t * @see IOUtil#newCharBuffer()\n\t */\n\tpublic static void copy(InputStream in, Charset charset, Writer writer) throws IOException {\n\t\tcopy(in, writer, charset, newCharBuffer());\n\t}\n\n\t/**\n\t * Transfers content of {@link InputStream} into {@link Writer}.\n\t * Uses {@link StandardCharsets#UTF_8} as default charset.\n\t *\n\t * @param in\n\t * \t\tStream to transfer content from.\n\t * @param writer\n\t * \t\tWriter to transfer content to.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t * @see IOUtil#newCharBuffer()\n\t */\n\tpublic static void copy(InputStream in, Writer writer) throws IOException {\n\t\tcopy(in, writer, StandardCharsets.UTF_8, newCharBuffer());\n\t}\n\n\t/**\n\t * Transfers content of {@link InputStream} into {@link Writer}.\n\t * Uses {@link StandardCharsets#UTF_8} as default charset.\n\t *\n\t * @param in\n\t * \t\tStream to transfer content from.\n\t * @param writer\n\t * \t\tWriter to transfer content to.\n\t * @param buf\n\t * \t\tBuffer that is used to hold data.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t * @see IOUtil#newCharBuffer()\n\t */\n\tpublic static void copy(InputStream in, Writer writer, char[] buf) throws IOException {\n\t\tcopy(in, writer, StandardCharsets.UTF_8, buf);\n\t}\n\n\t/**\n\t * Transfer contents of {@link URL} into {@link OutputStream}.\n\t *\n\t * @param url\n\t *        {@link URL} to transfer content from.\n\t * @param out\n\t * \t\tstream to transfer content to.\n\t * @param buf\n\t * \t\tbuffer that is used to hold data.\n\t * @param connectionTimeoutMillis\n\t * \t\tNumber of milliseconds until this method will timeout if no connection could\n\t * \t\tbe established to the {@code url}\n\t * @param readTimeoutMillis\n\t * \t\tNumber of milliseconds until this method will timeout if no data could be read from\n\t * \t\tthe {@code url}\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t */\n\tpublic static void copy(URL url, OutputStream out,\n\t                        byte[] buf,\n\t                        int connectionTimeoutMillis,\n\t                        int readTimeoutMillis)\n\t\t\tthrows IOException {\n\t\tURLConnection connection = url.openConnection();\n\t\tconnection.setDoInput(true);\n\t\tconnection.setConnectTimeout(connectionTimeoutMillis);\n\t\tconnection.setReadTimeout(readTimeoutMillis);\n\t\ttry (InputStream in = connection.getInputStream()) {\n\t\t\tcopy(in, out, buf);\n\t\t} finally {\n\t\t\tif (connection instanceof HttpURLConnection) {\n\t\t\t\t((HttpURLConnection) connection).disconnect();\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Transfer contents of {@link URL} into {@link OutputStream}.\n\t *\n\t * @param url\n\t *        {@link URL} to transfer content from.\n\t * @param out\n\t * \t\tStream to transfer content to.\n\t * @param connectionTimeoutMillis\n\t * \t\tNumber of milliseconds until this method will timeout if no connection could\n\t * \t\tbe established to the {@code url}\n\t * @param readTimeoutMillis\n\t * \t\tNumber of milliseconds until this method will timeout if no data could be read from\n\t * \t\tthe {@code url}\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t * @see IOUtil#newByteBuffer()\n\t */\n\tpublic static void copy(URL url, OutputStream out,\n\t                        int connectionTimeoutMillis,\n\t                        int readTimeoutMillis)\n\t\t\tthrows IOException {\n\t\tcopy(url, out, newByteBuffer(), connectionTimeoutMillis, readTimeoutMillis);\n\t}\n\n\t/**\n\t * Transfer contents of {@link URL} into {@link OutputStream} of {@link Path}.\n\t *\n\t * @param url\n\t *        {@link URL} to transfer content from.\n\t * @param path\n\t * \t\tPath to transfer content to.\n\t * @param buf\n\t * \t\tBuffer that is used to hold data.\n\t * @param connectionTimeoutMillis\n\t * \t\tNumber of milliseconds until this method will timeout if no connection could\n\t * \t\tbe established to the {@code url}\n\t * @param readTimeoutMillis\n\t * \t\tNumber of milliseconds until this method will timeout if no data could be read from\n\t * \t\tthe {@code url}\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t */\n\tpublic static void copy(URL url, Path path,\n\t                        byte[] buf,\n\t                        int connectionTimeoutMillis,\n\t                        int readTimeoutMillis)\n\t\t\tthrows IOException {\n\t\ttry (OutputStream os = Files.newOutputStream(path)) {\n\t\t\tcopy(url, os, buf, connectionTimeoutMillis, readTimeoutMillis);\n\t\t}\n\t}\n\n\t/**\n\t * Transfer contents of {@link URL} into {@link OutputStream} of {@link Path}.\n\t *\n\t * @param url\n\t *        {@link URL} to transfer content from.\n\t * @param path\n\t * \t\tPath to transfer content to.\n\t * @param connectionTimeoutMillis\n\t * \t\tNumber of milliseconds until this method will timeout if no connection could\n\t * \t\tbe established to the {@code url}\n\t * @param readTimeoutMillis\n\t * \t\tNumber of milliseconds until this method will timeout if no data could be read from the {@code url}\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t * @see IOUtil#newByteBuffer()\n\t */\n\tpublic static void copy(URL url, Path path,\n\t                        int connectionTimeoutMillis,\n\t                        int readTimeoutMillis)\n\t\t\tthrows IOException {\n\t\tcopy(url, path, newByteBuffer(), connectionTimeoutMillis, readTimeoutMillis);\n\t}\n\n\t/**\n\t * @param path\n\t *        {@link Path} to check against.\n\t *\n\t * @return {@code true} if a {@link Path} belongs to default file system.\n\t * Otherwise {@code false}.\n\t */\n\tpublic static boolean isOnDefaultFileSystem(Path path) {\n\t\treturn path.getFileSystem() == FileSystems.getDefault();\n\t}\n\n\t/**\n\t * Cleans a directory.\n\t *\n\t * @param path\n\t * \t\tDirectory to clean.\n\t *\n\t * @throws IOException\n\t * \t\tWhen any I/O error occurs.\n\t */\n\tpublic static void cleanDirectory(Path path) throws IOException {\n\t\tFiles.walkFileTree(path, new SimpleFileVisitor<>() {\n\t\t\t@Override\n\t\t\tpublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\n\t\t\t\tFiles.delete(file);\n\t\t\t\treturn FileVisitResult.CONTINUE;\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {\n\t\t\t\tFiles.delete(dir);\n\t\t\t\treturn FileVisitResult.CONTINUE;\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Performs deletion quietly.\n\t *\n\t * @param path\n\t *        {@link Path} to delete.\n\t */\n\tpublic static void deleteQuietly(Path path) {\n\t\ttry {\n\t\t\tif (isOnDefaultFileSystem(path)) {\n\t\t\t\tdeleteQuietly(path.toFile());\n\t\t\t} else {\n\t\t\t\tif (Files.isDirectory(path)) {\n\t\t\t\t\tcleanDirectory(path);\n\t\t\t\t}\n\t\t\t\tFiles.deleteIfExists(path);\n\t\t\t}\n\t\t} catch (IOException ignored) {\n\t\t\t// no-op\n\t\t}\n\t}\n\n\t/**\n\t * Performs deletion quietly.\n\t *\n\t * @param file\n\t *        {@link File} to delete.\n\t */\n\tpublic static void deleteQuietly(File file) {\n\t\tif (file.isDirectory()) {\n\t\t\tFile[] list = file.listFiles();\n\t\t\tif (list != null) {\n\t\t\t\tfor (File f : list) {\n\t\t\t\t\tdeleteQuietly(f);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfile.delete();\n\t}\n\n\t/**\n\t * Tests whether the file denoted by a path is a normal file.\n\t *\n\t * @param path\n\t * \t\tPath to check.\n\t *\n\t * @return {@code true} if file is a regular file.\n\t */\n\tpublic static boolean isRegularFile(Path path) {\n\t\t// Actually fallback to java.io package if possible,\n\t\t// because IO is faster than NIO when for file status checking.\n\t\treturn isOnDefaultFileSystem(path) ? path.toFile().isFile() : Files.isRegularFile(path);\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to some file.\n\t *\n\t * @return First 16 bytes of the file.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the file cannot be read from.\n\t */\n\tpublic static byte[] readHeader(Path path) throws IOException {\n\t\treturn readFirstNBytes(path, 16);\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to some file.\n\t * @param n\n\t * \t\tNumber of bytes to read.\n\t *\n\t * @return First n bytes of the file.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the file cannot be read from.\n\t */\n\tpublic static byte[] readFirstNBytes(Path path, int n) throws IOException {\n\t\tbyte[] data = new byte[n];\n\t\ttry (InputStream fis = Files.newInputStream(path)) {\n\t\t\tfis.read(data);\n\t\t}\n\t\treturn data;\n\t}\n\n\t/**\n\t * A {@link ByteArrayOutputStream} which is optimized for single use cases of known lengths.\n\t * When the buffer is filled to capacity, we yield the buffer as the result directly instead\n\t * of providing a copy like how is done in {@link ByteArrayOutputStream#toByteArray()}.\n\t */\n\tprivate static final class OptimizedByteArrayOutputStream extends ByteArrayOutputStream {\n\t\t/**\n\t\t * @param size\n\t\t * \t\tInitial size.\n\t\t */\n\t\tOptimizedByteArrayOutputStream(int size) {\n\t\t\tsuper(size);\n\t\t}\n\n\t\t/**\n\t\t * Non thread-safe alternative to {@link #toByteArray()} that skips array copying when possible.\n\t\t *\n\t\t * @return Content of this stream.\n\t\t */\n\t\tbyte[] getBytes() {\n\t\t\tbyte[] buf = this.buf;\n\t\t\tint count = this.count;\n\n\t\t\t// If the size is a match we do not need to do a copy operation\n\t\t\tif (count == buf.length)\n\t\t\t\treturn buf;\n\n\t\t\t// Cut buffer down to expected size\n\t\t\treturn Arrays.copyOfRange(buf, 0, count);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/InternalPath.java",
    "content": "package software.coley.recaf.util;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URL;\n\n/**\n * Wrapper for a resource to assist in handling of mixed classpath and non-classpath items.\n *\n * @author Matt Coley\n */\npublic class InternalPath {\n\tprivate final String path;\n\tprivate final boolean internal;\n\n\t/**\n\t * Create a resource wrapper.\n\t *\n\t * @param path\n\t * \t\tPath to resource.\n\t * @param internal\n\t *        {@code true} if the resource is in the classpath, {@code false} if it is external.\n\t */\n\tprivate InternalPath(String path, boolean internal) {\n\t\tthis.path = path;\n\t\tthis.internal = internal;\n\t}\n\n\t/**\n\t * Create an internal resource.\n\t *\n\t * @param path\n\t * \t\tPath to resource.\n\t *\n\t * @return Internal resource wrapper.\n\t */\n\tpublic static InternalPath internal(String path) {\n\t\treturn new InternalPath(path, true);\n\t}\n\n\t/**\n\t * Create an external resource.\n\t *\n\t * @param path\n\t * \t\tPath to resource.\n\t *\n\t * @return External resource wrapper.\n\t */\n\tpublic static InternalPath external(String path) {\n\t\treturn new InternalPath(path, false);\n\t}\n\n\t/**\n\t * @return Resource path.\n\t */\n\tpublic String getPath() {\n\t\treturn path;\n\t}\n\n\t/**\n\t * @return {@code true} if the resource is in the classpath, {@code false} if it is external.\n\t */\n\tpublic boolean isInternal() {\n\t\treturn internal;\n\t}\n\n\t/**\n\t * @return Name of resource file.\n\t */\n\tpublic String getFileName() {\n\t\tString name = getPath();\n\t\tint sep = name.lastIndexOf('/');\n\t\tif (sep > 0)\n\t\t\tname = name.substring(sep + 1);\n\t\treturn name;\n\t}\n\n\t/**\n\t * Creates a URL path to resource,\n\t *\n\t * @return URL path to resource.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the path cannot be created.\n\t */\n\tpublic URL getURL() throws IOException {\n\t\tif (internal) {\n\t\t\treturn InternalPath.class.getClassLoader().getResource(getPath());\n\t\t} else {\n\t\t\treturn new File(getPath()).toURI().toURL();\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/JavaDowngraderUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.slf4j.Logger;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.util.visitors.WorkspaceClassWriter;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.RuntimeWorkspaceResource;\nimport xyz.wagyourtail.jvmdg.ClassDowngrader;\nimport xyz.wagyourtail.jvmdg.cli.Flags;\nimport xyz.wagyourtail.jvmdg.util.Pair;\nimport xyz.wagyourtail.jvmdg.version.map.FullyQualifiedMemberNameAndDesc;\nimport xyz.wagyourtail.jvmdg.version.map.MemberNameAndDesc;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.BiConsumer;\n\n/**\n * Utility for wrapping {@link ClassDowngrader}.\n *\n * @author Matt Coley\n */\npublic class JavaDowngraderUtil {\n\tprivate static final Logger logger = Logging.get(JavaDowngraderUtil.class);\n\tprivate static final String[] undesirableStubs = {\n\t\t\t// Array of stubs (owner;name;desc) that we do not want to pass back to callers.\n\t\t\t\"Lxyz/wagyourtail/jvmdg/j18/stub/java_base/J_L_System;getProperty;(Ljava/lang/String;)Ljava/lang/String;\",\n\t\t\t\"Lxyz/wagyourtail/jvmdg/j18/stub/java_base/J_L_System;getProperties;()Ljava/util/Properties;\"\n\t};\n\n\t/**\n\t * Downgrade the provided classes.\n\t *\n\t * @param targetJavaVersion\n\t * \t\tTarget Java version to downgrade to.\n\t * @param classes\n\t * \t\tMap of classes to downgrade.\n\t * @param transformConsumer\n\t * \t\tConsumer to receive downgraded classes.\n\t * \t\tAdditional classes may be provided for cases where the downgrader creates its own backported library code.,\n\t *\n\t * @throws IOException\n\t * \t\tThrown when the downgrader instance cannot be constructed.\n\t */\n\tpublic static void downgrade(int targetJavaVersion,\n\t                             @Nonnull Map<String, byte[]> classes,\n\t                             @Nonnull BiConsumer<String, byte[]> transformConsumer) throws IOException {\n\t\tdowngrade(targetJavaVersion, null, classes, transformConsumer);\n\t}\n\n\t/**\n\t * Downgrade the provided classes.\n\t *\n\t * @param targetJavaVersion\n\t * \t\tTarget Java version to downgrade to.\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph of the workspace containing the given classes.\n\t * @param classes\n\t * \t\tMap of classes to downgrade.\n\t * @param transformConsumer\n\t * \t\tConsumer to receive downgraded classes.\n\t * \t\tAdditional classes may be provided for cases where the downgrader creates its own backported library code.,\n\t *\n\t * @throws IOException\n\t * \t\tThrown when the downgrader instance cannot be constructed.\n\t */\n\tpublic static void downgrade(int targetJavaVersion,\n\t                             @Nullable InheritanceGraph inheritanceGraph,\n\t                             @Nonnull Map<String, byte[]> classes,\n\t                             @Nonnull BiConsumer<String, byte[]> transformConsumer) throws IOException {\n\t\tint targetClassVersion = JavaVersion.VERSION_OFFSET + targetJavaVersion;\n\n\t\tFlags flags = new Flags();\n\t\tflags.classVersion = targetClassVersion;\n\t\tfor (String undesirableStub : undesirableStubs)\n\t\t\tflags.debugSkipStub.add(FullyQualifiedMemberNameAndDesc.of(undesirableStub));\n\n\t\t// Any stub that is beyond our runtime version is not supported...\n\t\t//\n\t\t// This is due to the downgrader loading stubs at runtime when inserting missing API's, but those stubs\n\t\t// are compiled against the stubbed version minus one.\n\t\t//\n\t\t// If there is a stubbed API for Java 25, and we are running on Java 22 then stubs for 24 and 25 stubs will fail.\n\t\tfor (int i = JavaVersion.get() + JavaVersion.VERSION_OFFSET + 1; i < 100; i++)\n\t\t\tflags.debugSkipStubs.add(i);\n\n\t\ttry (ClassDowngrader downgrader = new RecafClassDowngrader(flags, inheritanceGraph)) {\n\t\t\tint maxClassFileVersion = downgrader.maxVersion();\n\t\t\tclasses.forEach((className, classBytes) -> {\n\t\t\t\tint classFileVersion = classBytes[7];\n\n\t\t\t\t// Hack to ensure downgrader will run on bleeding edge versions of Java.\n\t\t\t\t// This will cause brand-new features to not be properly downgraded, but\n\t\t\t\t// not all bleeding edge classes use all the brand-new features. At least\n\t\t\t\t// trying is better than failing outright for most use cases.\n\t\t\t\tif (classFileVersion > maxClassFileVersion) {\n\t\t\t\t\tclassBytes = Arrays.copyOf(classBytes, classBytes.length);\n\t\t\t\t\tclassBytes[7] = (byte) (maxClassFileVersion);\n\t\t\t\t}\n\n\t\t\t\t// Transform if current class's version is greater than the target.\n\t\t\t\tif (classFileVersion > targetClassVersion) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tMap<String, byte[]> downgraded = downgrader.downgrade(new AtomicReference<>(className), classBytes, false,\n\t\t\t\t\t\t\t\tkey -> getBytes(maxClassFileVersion, classes, key));\n\t\t\t\t\t\tfillStubClasses(downgraded);\n\t\t\t\t\t\tif (downgraded != null)\n\t\t\t\t\t\t\tdowngraded.forEach(transformConsumer);\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tlogger.error(\"Failed down sampling '{}' to version {}\", className, targetJavaVersion, t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t@Nullable\n\tprivate static byte[] getBytes(int maxClassFileVersion, @Nonnull Map<String, byte[]> classes, @Nonnull String key) {\n\t\tbyte[] classBytes = classes.get(key);\n\t\tif (classBytes != null) {\n\t\t\t// Same version hack as above.\n\t\t\tint classFileVersion = classBytes[7];\n\t\t\tif (classFileVersion > maxClassFileVersion) {\n\t\t\t\tclassBytes = Arrays.copyOf(classBytes, classBytes.length);\n\t\t\t\tclassBytes[7] = (byte) (maxClassFileVersion);\n\t\t\t}\n\t\t}\n\t\treturn classBytes;\n\t}\n\n\tprivate static void fillStubClasses(@Nullable Map<String, byte[]> downgraded) {\n\t\tif (downgraded == null || downgraded.isEmpty())\n\t\t\treturn;\n\n\t\t// Find all referenced stubs from the downgraded classes.\n\t\tSet<String> referencedStubs = new HashSet<>();\n\t\tfor (byte[] classBytes : downgraded.values()) {\n\t\t\tClassReader reader = new ClassReader(classBytes);\n\t\t\treader.accept(new ClassVisitor(RecafConstants.getAsmVersion()) {\n\t\t\t\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\t\t\t\treturn new MethodVisitor(RecafConstants.getAsmVersion()) {\n\t\t\t\t\t\tpublic void visitFieldInsn(int opcode, String owner, String name, String descriptor) {\n\t\t\t\t\t\t\tif (owner.startsWith(\"xyz/wagyourtail/jvmdg/\") && owner.contains(\"/stub/\"))\n\t\t\t\t\t\t\t\treferencedStubs.add(owner);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tpublic void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {\n\t\t\t\t\t\t\tif (owner.startsWith(\"xyz/wagyourtail/jvmdg/\") && owner.contains(\"/stub/\"))\n\t\t\t\t\t\t\t\treferencedStubs.add(owner);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);\n\t\t}\n\n\t\t// Add all referenced stubs to the downgraded output.\n\t\tJvmClassBundle runtimeBundle = RuntimeWorkspaceResource.getInstance().getJvmClassBundle();\n\t\tfor (String referencedStub : referencedStubs) {\n\t\t\tJvmClassInfo cls = runtimeBundle.get(referencedStub);\n\t\t\tif (cls != null)\n\t\t\t\tdowngraded.put(cls.getName(), cls.getBytecode());\n\t\t}\n\t}\n\n\tprivate static class RecafClassDowngrader extends ClassDowngrader {\n\t\tprivate final InheritanceGraph inheritanceGraph;\n\n\t\tpublic RecafClassDowngrader(@Nonnull Flags flags, @Nullable InheritanceGraph inheritanceGraph) {\n\t\t\tsuper(flags);\n\n\t\t\tthis.inheritanceGraph = inheritanceGraph;\n\n\t\t\t// Compute the max-version. For the override methods below there are some cases where the base\n\t\t\t// implementation does version checks on classes used only for computing basic structure data.\n\t\t\t// We want these to at least have the chance to run vs failing outright, so we will cap the passed\n\t\t\t// version to the max supported by this downgrader.\n\t\t\tmaxVersion();\n\t\t}\n\n\t\t@Override\n\t\tpublic Set<MemberNameAndDesc> getMembers(int version, Type type, Set<String> warnings) throws IOException {\n\t\t\treturn super.getMembers(Math.min(maxVersion, version), type, warnings);\n\t\t}\n\n\t\t@Override\n\t\tpublic List<Pair<Type, Boolean>> getSupertypes(int version, Type type, Set<String> warnings) throws IOException {\n\t\t\treturn super.getSupertypes(Math.min(maxVersion, version), type, warnings);\n\t\t}\n\n\t\t@Override\n\t\tpublic Boolean isInterface(int version, Type type, Set<String> warnings) throws IOException {\n\t\t\treturn super.isInterface(Math.min(maxVersion, version), type, warnings);\n\t\t}\n\n\t\t@Override\n\t\tpublic Type stubClass(int version, Type type, Set<String> warnings) {\n\t\t\treturn super.stubClass(Math.min(maxVersion, version), type, warnings);\n\t\t}\n\n\t\t@Override\n\t\tpublic byte[] classNodeToBytes(@Nonnull ClassNode node) {\n\t\t\tClassWriter cw;\n\n\t\t\t// Use our class writer which can pull workspace type information.\n\t\t\tif (inheritanceGraph != null) {\n\t\t\t\tcw = new WorkspaceClassWriter(inheritanceGraph, ClassWriter.COMPUTE_MAXS);\n\t\t\t} else {\n\t\t\t\tcw = new ClassWriter(ClassWriter.COMPUTE_MAXS) {\n\t\t\t\t\t@Override\n\t\t\t\t\tprotected String getCommonSuperClass(String type1, String type2) {\n\t\t\t\t\t\t// Shouldn't be called for computing maxes, but we just want to be sure no\n\t\t\t\t\t\t// redundant classloading is attempted by the default implementation.\n\t\t\t\t\t\treturn \"java/lang/Object\";\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Fallback to base impl\n\t\t\tnode.accept(cw);\n\t\t\treturn cw.toByteArray();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/JavaVersion.java",
    "content": "package software.coley.recaf.util;\n\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\n\n/**\n * Supported Java version of the current JVM.\n *\n * @author Matt Coley\n */\npublic class JavaVersion {\n\t/**\n\t * The offset from which a version and the version constant value is. For example, Java 8 is 52 <i>(44 + 8)</i>.\n\t */\n\tpublic static final int VERSION_OFFSET = 44;\n\tprivate static final Logger logger = Logging.get(JavaVersion.class);\n\n\t/**\n\t * Get the supported Java version of the current JVM.\n\t *\n\t * @return Version.\n\t */\n\tpublic static int get() {\n\t\treturn Runtime.version().feature();\n\t}\n\n\t/**\n\t * Adapts the class file spec version to the familiar release versions.\n\t * For example, 52 becomes Java 8.\n\t *\n\t * @param version\n\t * \t\tClass file version, such as from {@link JvmClassInfo#getVersion()}.\n\t *\n\t * @return Version.\n\t */\n\tpublic static int adaptFromClassFileVersion(int version) {\n\t\treturn version - VERSION_OFFSET;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/JdkValidation.java",
    "content": "package software.coley.recaf.util;\n\nimport org.slf4j.Logger;\nimport software.coley.recaf.ExitCodes;\nimport software.coley.recaf.ExitDebugLoggingHook;\nimport software.coley.recaf.analytics.logging.Logging;\n\n/**\n * JDK vs JRE validation utils.\n *\n * @author Matt Coley\n */\npublic class JdkValidation {\n\tprivate static final Logger logger = Logging.get(JdkValidation.class);\n\n\t/**\n\t * Applies a few assorted checks to ensure we are running on a JDK and not a JRE.\n\t */\n\tpublic static void validateJdk() {\n\t\ttry {\n\t\t\tClass.forName(\"com.sun.tools.attach.VirtualMachine\");\n\t\t} catch (ClassNotFoundException ex) {\n\t\t\tlogger.error(\"Recaf must be run with a JDK, but was run with a JRE: \" + System.getProperty(\"java.home\"));\n\t\t\tExitDebugLoggingHook.exit(ExitCodes.ERR_NOT_A_JDK);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/Keywords.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Java language keywords.\n *\n * @author Matt Coley\n */\npublic class Keywords {\n\tprivate static final Set<String> keywords;\n\n\t/**\n\t * @return Set of reserved keywords.\n\t */\n\t@Nonnull\n\tpublic static Set<String> getKeywords() {\n\t\treturn keywords;\n\t}\n\n\t// Commented out items are 'keywords' but can be used as names.\n\tstatic {\n\t\t// Misc language constructs\n\t\tSet<String> words = new HashSet<>();\n\t\twords.addAll(Arrays.asList(\n\t\t\t\t\"assert\",\n\t\t\t\t\"break\",\n\t\t\t\t// \"bridge\",\n\t\t\t\t\"case\",\n\t\t\t\t\"catch\",\n\t\t\t\t\"class\",\n\t\t\t\t\"continue\",\n\t\t\t\t\"default\",\n\t\t\t\t\"do\",\n\t\t\t\t\"else\",\n\t\t\t\t\"enum\",\n\t\t\t\t\"extends\",\n\t\t\t\t\"finally\",\n\t\t\t\t\"while\",\n\t\t\t\t\"for\",\n\t\t\t\t\"goto\",\n\t\t\t\t\"if\",\n\t\t\t\t\"implements\",\n\t\t\t\t\"import\",\n\t\t\t\t\"instanceof\",\n\t\t\t\t\"interface\",\n\t\t\t\t// \"mandated\",\n\t\t\t\t// \"module\",\n\t\t\t\t\"new\",\n\t\t\t\t// \"open\",\n\t\t\t\t\"package\",\n\t\t\t\t\"record\",\n\t\t\t\t\"return\",\n\t\t\t\t\"super\",\n\t\t\t\t\"switch\",\n\t\t\t\t\"try\",\n\t\t\t\t\"this\",\n\t\t\t\t\"throw\",\n\t\t\t\t\"throws\"\n\t\t\t\t// \"var\",\n\t\t\t\t// \"varargs\",\n\t\t\t\t// \"yield\"\n\t\t));\n\n\t\t// Modifiers\n\t\twords.addAll(Arrays.asList(\n\t\t\t\t\"abstract\",\n\t\t\t\t\"const\",\n\t\t\t\t\"final\",\n\t\t\t\t\"native\",\n\t\t\t\t\"private\",\n\t\t\t\t\"protected\",\n\t\t\t\t\"public\",\n\t\t\t\t\"static\",\n\t\t\t\t\"strictfp\",\n\t\t\t\t\"synchronized\",\n\t\t\t\t// \"synthetic\",\n\t\t\t\t\"transient\",\n\t\t\t\t// \"transitive\",\n\t\t\t\t\"volatile\"\n\t\t));\n\n\t\t// Primitive types\n\t\twords.addAll(Arrays.asList(\n\t\t\t\t\"boolean\",\n\t\t\t\t\"byte\",\n\t\t\t\t\"char\",\n\t\t\t\t\"short\",\n\t\t\t\t\"int\",\n\t\t\t\t\"long\",\n\t\t\t\t\"float\",\n\t\t\t\t\"double\",\n\t\t\t\t\"void\"\n\t\t));\n\n\t\t// Primitive values\n\t\twords.addAll(Arrays.asList(\n\t\t\t\t\"true\",\n\t\t\t\t\"false\",\n\t\t\t\t\"null\"\n\t\t));\n\n\t\tkeywords = Collections.unmodifiableSet(words);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/MemoizedFunctions.java",
    "content": "package software.coley.recaf.util;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.BiFunction;\nimport java.util.function.Function;\n\n/**\n * Memoized functions.\n *\n * @author Amejonah\n */\npublic class MemoizedFunctions {\n\tpublic static <Key, Value> Function<Key, Value> memoize(Function<Key, Value> function) {\n\t\treturn new MemoizedFunction<>(function);\n\t}\n\n\tpublic static <KeyA, KeyB, Value> BiFunction<KeyA, KeyB, Value> memoize(BiFunction<KeyA, KeyB, Value> function) {\n\t\treturn new BiMemoizedFunction<>(function);\n\t}\n\n\tprivate static class MemoizedFunction<Key, Value> implements Function<Key, Value> {\n\t\tprivate final Map<Key, Value> cache = new HashMap<>();\n\t\tprivate final Function<Key, Value> function;\n\n\t\tprivate MemoizedFunction(Function<Key, Value> function) {\n\t\t\tthis.function = function;\n\t\t}\n\n\t\t@Override\n\t\tpublic Value apply(Key key) {\n\t\t\treturn cache.computeIfAbsent(key, function);\n\t\t}\n\t}\n\n\tprivate static class BiMemoizedFunction<KeyA, KeyB, Value> implements BiFunction<KeyA, KeyB, Value> {\n\t\tprivate final Map<KeyA, Map<KeyB, Value>> cache = new HashMap<>();\n\t\tprivate final BiFunction<KeyA, KeyB, Value> function;\n\n\t\tprivate BiMemoizedFunction(BiFunction<KeyA, KeyB, Value> function) {\n\t\t\tthis.function = function;\n\t\t}\n\n\n\t\t@Override\n\t\tpublic Value apply(KeyA keyA, KeyB keyB) {\n\t\t\treturn cache.computeIfAbsent(keyA, __ -> new HashMap<>()).computeIfAbsent(keyB, k -> function.apply(keyA, keyB));\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/ModulesIOUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.util.io.ByteSourceElement;\nimport software.coley.recaf.util.io.ByteSources;\n\nimport java.io.IOException;\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.invoke.MethodType;\nimport java.lang.reflect.Method;\nimport java.nio.ByteBuffer;\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.stream.Stream;\n\n/**\n * Java module reading.\n *\n * @author xDark\n */\npublic class ModulesIOUtil {\n\tprivate static final MethodHandles.Lookup lookup = ReflectUtil.lookup();\n\n\t/**\n\t * @param modulesFilePath\n\t * \t\tPath to {@code modules} file.\n\t *\n\t * @return Stream of contents within the file.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the modules file cannot be read from.\n\t */\n\t@SuppressWarnings(\"Convert2MethodRef\") // javac doesn't like reference on 'bmap(...)'\n\tpublic static Stream<ByteSourceElement<Entry>> stream(Path modulesFilePath) throws IOException {\n\t\treturn Unchecked.map(path -> {\n\t\t\t// Cannot directly use internals... ugh.\n\t\t\tClass<?> imageReaderClass = Class.forName(\"jdk.internal.jimage.ImageReader\", true, null);\n\t\t\tMethod m = imageReaderClass.getDeclaredMethod(\"open\", Path.class);\n\t\t\tm.setAccessible(true);\n\t\t\tObject reader = m.invoke(null, path);\n\t\t\tm = imageReaderClass.getDeclaredMethod(\"getEntryNames\");\n\t\t\tm.setAccessible(true);\n\t\t\tString[] entries = (String[]) m.invoke(reader);\n\t\t\tClass<?> imageLocationClass = Class.forName(\"jdk.internal.jimage.ImageLocation\", true, null);\n\t\t\tMethodHandle getResourceBuffer = lookup.findVirtual(imageReaderClass, \"getResourceBuffer\", MethodType.methodType(ByteBuffer.class, imageLocationClass))\n\t\t\t\t\t.bindTo(reader);\n\t\t\tMethodHandle findLocation = lookup.findVirtual(imageReaderClass, \"findLocation\", MethodType.methodType(imageLocationClass, String.class))\n\t\t\t\t\t.bindTo(reader);\n\t\t\treturn Arrays.stream(entries)\n\t\t\t\t\t.filter(entryName -> entryName.indexOf('/', 1) > 1)\n\t\t\t\t\t.map(entryName -> {\n\t\t\t\t\t\t// Follows the pattern: /<module-name>/<file-name>\n\t\t\t\t\t\tint firstSlash = entryName.indexOf('/', 1);\n\t\t\t\t\t\tString moduleName = entryName.substring(1, firstSlash);\n\t\t\t\t\t\tString fileName = entryName.substring(entryName.indexOf('/', 1) + 1);\n\n\t\t\t\t\t\t// Get content source\n\t\t\t\t\t\tObject imageLocation = Unchecked.bmap((t, u) -> t.invoke(u), findLocation, entryName);\n\t\t\t\t\t\tByteBuffer buffer = Unchecked.bmap((t, u) -> (ByteBuffer) t.invoke(u), getResourceBuffer, imageLocation);\n\n\t\t\t\t\t\t// Wrap into element\n\t\t\t\t\t\treturn new ByteSourceElement<>(new Entry(moduleName, fileName), ByteSources.forBuffer(buffer));\n\t\t\t\t\t}).onClose(() -> IOUtil.closeQuietly((AutoCloseable) reader));\n\t\t}, modulesFilePath);\n\t}\n\n\tpublic static class Entry {\n\t\tprivate final String moduleName;\n\t\tprivate final String fileName;\n\n\t\tprivate Entry(String moduleName, String fileName) {\n\t\t\tthis.moduleName = moduleName;\n\t\t\tthis.fileName = fileName;\n\t\t}\n\n\t\t/**\n\t\t * @return The original full path from the {@code modules} file,\n\t\t * of which the module name and file path are derived from.\n\t\t */\n\t\tpublic String getOriginalPath() {\n\t\t\treturn '/' + moduleName + '/' + fileName;\n\t\t}\n\n\t\t/**\n\t\t * @return The file's associated module.\n\t\t */\n\t\tpublic String getModuleName() {\n\t\t\treturn moduleName;\n\t\t}\n\n\t\t/**\n\t\t * @return The file's path name.\n\t\t */\n\t\tpublic String getFileName() {\n\t\t\treturn fileName;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/MultiMap.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\nimport java.util.stream.Stream;\n\n/**\n * Simple multi-map.\n *\n * @author xDark\n */\npublic final class MultiMap<K, V, C extends Collection<V>> {\n\tprivate final Map<K, C> backing;\n\tprivate final Function<K, ? extends C> collectionFunction;\n\n\t/**\n\t * @param backing\n\t * \t\tBacking map.\n\t * @param collectionSupplier\n\t * \t\tCollection supplier.\n\t */\n\tprivate MultiMap(@Nonnull Map<K, C> backing, @Nonnull Supplier<? extends C> collectionSupplier) {\n\t\tthis.backing = backing;\n\t\tthis.collectionFunction = __ -> collectionSupplier.get();\n\t}\n\n\t/**\n\t * @return Total amount of items in the map.\n\t */\n\tpublic int size() {\n\t\treturn backing.values()\n\t\t\t\t.stream()\n\t\t\t\t.mapToInt(Collection::size)\n\t\t\t\t.sum();\n\t}\n\n\t/**\n\t * @return {@code true} if the map is empty.\n\t */\n\tpublic boolean isEmpty() {\n\t\treturn backing.values()\n\t\t\t\t.stream()\n\t\t\t\t.noneMatch(Collection::isEmpty);\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tKey to check.\n\t *\n\t * @return {@code true} if map contains the key.\n\t */\n\tpublic boolean containsKey(K key) {\n\t\treturn backing.containsKey(key);\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check.\n\t *\n\t * @return {@code true} if map contains the value.\n\t */\n\tpublic boolean containsValue(V value) {\n\t\treturn backing.values()\n\t\t\t\t.stream()\n\t\t\t\t.anyMatch(c -> c.contains(value));\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tKey to get a collection for.\n\t *\n\t * @return Collection for the key.\n\t */\n\t@Nonnull\n\tpublic C get(K key) {\n\t\treturn backing.computeIfAbsent(key, collectionFunction);\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tKey to get a collection for.\n\t *\n\t * @return A collection of values or an empty list, if none.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\t@Nonnull\n\tpublic Collection<V> getIfPresent(K key) {\n\t\treturn ((Map<K, Collection<V>>) backing).getOrDefault(key, Collections.emptyList());\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tKey to get a collection for.\n\t *\n\t * @return A collection of values or {@code defaultValue}, if none.\n\t */\n\tpublic C getOrDefault(K key, C defaultValue) {\n\t\treturn backing.getOrDefault(key, defaultValue);\n\t}\n\n\t/**\n\t * Puts value in the map.\n\t *\n\t * @param key\n\t * \t\tKey.\n\t * @param value\n\t * \t\tValue.\n\t *\n\t * @return {@code true} if value was added to the collection.\n\t */\n\tpublic boolean put(K key, V value) {\n\t\treturn get(key).add(value);\n\t}\n\n\t/**\n\t * Puts a collection of values in the map.\n\t *\n\t * @param key\n\t * \t\tKey.\n\t * @param values\n\t * \t\tValues.\n\t *\n\t * @return {@code true} if any value was added to the collection.\n\t */\n\tpublic boolean putAll(K key, @Nonnull Collection<? extends V> values) {\n\t\treturn get(key).addAll(values);\n\t}\n\n\t/**\n\t * Removes a key-value pair.\n\t *\n\t * @param key\n\t * \t\tKey.\n\t * @param value\n\t * \t\tValue.\n\t *\n\t * @return {@code true} if key-value pair was removed.\n\t */\n\tpublic boolean remove(K key, V value) {\n\t\tC collection = backing.get(key);\n\t\tif (collection != null && collection.remove(value) && collection.isEmpty()) {\n\t\t\tbacking.remove(key);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Removes a collection of values.\n\t *\n\t * @param key\n\t * \t\tKey to remove.\n\t *\n\t * @return Collection of removed values or an empty list, if none.\n\t */\n\t@Nonnull\n\tpublic Collection<V> remove(K key) {\n\t\tCollection<V> collection = backing.remove(key);\n\t\tif (collection == null) {\n\t\t\treturn Collections.emptyList();\n\t\t}\n\t\treturn collection;\n\t}\n\n\t/**\n\t * Clears the map.\n\t */\n\tpublic void clear() {\n\t\tbacking.clear();\n\t}\n\n\t/**\n\t * @return All keys.\n\t */\n\t@Nonnull\n\tpublic Set<K> keySet() {\n\t\treturn backing.keySet();\n\t}\n\n\t/**\n\t * @return All values.\n\t */\n\t@Nonnull\n\tpublic Stream<V> values() {\n\t\treturn backing.values()\n\t\t\t\t.stream()\n\t\t\t\t.flatMap(Collection::stream);\n\t}\n\n\t/**\n\t * @return Map entry set.\n\t */\n\t@Nonnull\n\tpublic Set<Map.Entry<K, C>> entrySet() {\n\t\treturn backing.entrySet();\n\t}\n\n\t/**\n\t * Creates a multi-map.\n\t *\n\t * @param map\n\t * \t\tBacking map.\n\t * @param collectionSupplier\n\t * \t\tSupplier for collections of values.\n\t * @param <K>\n\t * \t\tKey type.\n\t * @param <V>\n\t * \t\tValue type.\n\t * @param <C>\n\t * \t\tCollection type.\n\t *\n\t * @return New multi-map.\n\t */\n\t@Nonnull\n\tpublic static <K, V, C extends Collection<V>> MultiMap<K, V, C> from(Map<K, C> map, Supplier<? extends C> collectionSupplier) {\n\t\treturn new MultiMap<>(map, collectionSupplier);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/MultiMapBuilder.java",
    "content": "package software.coley.recaf.util;\n\nimport java.util.*;\nimport java.util.function.Supplier;\n\n/**\n * Multi-map builder.\n *\n * @param <K>\n * \t\tKey type.\n * @param <V>\n * \t\tValue type.\n * @param <C>\n * \t\tCollection type.\n *\n * @author xDark\n */\npublic final class MultiMapBuilder<K, V, C extends Collection<V>> {\n\tprivate final Supplier<? extends Map<K, Collection<V>>> mapSupplier;\n\tprivate Supplier<Collection<V>> collectionSupplier;\n\n\tprivate MultiMapBuilder(Supplier<? extends Map<K, Collection<V>>> mapSupplier) {\n\t\tthis.mapSupplier = mapSupplier;\n\t}\n\n\t/**\n\t * Creates new multi-map builder with hash keys.\n\t *\n\t * @param <K>\n\t * \t\tKey type.\n\t * @param <V>\n\t * \t\tValue type.\n\t *\n\t * @return New builder.\n\t */\n\tpublic static <K, V> MultiMapBuilder<K, V, Collection<V>> hashKeys() {\n\t\treturn new MultiMapBuilder<>(HashMap::new);\n\t}\n\n\t/**\n\t * Creates new multi-map builder with tree keys.\n\t *\n\t * @param <K>\n\t * \t\tKey type.\n\t * @param <V>\n\t * \t\tValue type.\n\t *\n\t * @return New builder.\n\t */\n\tpublic static <K extends Comparable<K>, V> MultiMapBuilder<K, V, Collection<V>> treeKeys() {\n\t\treturn new MultiMapBuilder<>(TreeMap::new);\n\t}\n\n\t/**\n\t * Creates new multi-map builder with enum keys.\n\t *\n\t * @param <K>\n\t * \t\tKey type.\n\t * @param <V>\n\t * \t\tValue type.\n\t *\n\t * @return New builder.\n\t */\n\tpublic static <K extends Enum<K>, V> MultiMapBuilder<K, V, Collection<V>> enumKeys(Class<K> type) {\n\t\treturn new MultiMapBuilder<>(() -> new EnumMap<>(type));\n\t}\n\n\t/**\n\t * Creates new multi-map builder with enum keys.\n\t *\n\t * @param <K>\n\t * \t\tKey type.\n\t * @param <V>\n\t * \t\tValue type.\n\t *\n\t * @return New builder.\n\t */\n\t@SafeVarargs\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static <K extends Enum<K>, V> MultiMapBuilder<K, V, Collection<V>> enumKeys(K... typeHint) {\n\t\treturn enumKeys((Class<K>) typeHint.getClass().getComponentType());\n\t}\n\n\t/**\n\t * Creates new multi-map builder with user-specific keys.\n\t *\n\t * @param supplier\n\t * \t\tMap supplier.\n\t * @param <K>\n\t * \t\tKey type.\n\t * @param <V>\n\t * \t\tValue type.\n\t *\n\t * @return New builder.\n\t */\n\tpublic static <K, V> MultiMapBuilder<K, V, Collection<V>> keys(Supplier<? extends Map<K, Collection<V>>> supplier) {\n\t\treturn new MultiMapBuilder<>(supplier);\n\t}\n\n\t/**\n\t * Sets backing collection to the array list.\n\t *\n\t * @return This builder.\n\t */\n\tpublic MultiMapBuilder<K, V, List<V>> arrayValues() {\n\t\tcollectionSupplier = ArrayList::new;\n\t\treturn upgrade();\n\t}\n\n\t/**\n\t * Sets backing collection to the hash set.\n\t *\n\t * @return This builder.\n\t */\n\tpublic MultiMapBuilder<K, V, Set<V>> hashValues() {\n\t\tcollectionSupplier = HashSet::new;\n\t\treturn upgrade();\n\t}\n\n\t/**\n\t * Sets backing collection to the enum set.\n\t *\n\t * @return This builder.\n\t */\n\t@SafeVarargs\n\t@SuppressWarnings({\"unchecked\", \"rawtypes\"})\n\tpublic final MultiMapBuilder<K, V, Set<V>> enumValues(V... typeHint) {\n\t\tClass<Enum> type = (Class<Enum>) typeHint.getClass().getComponentType();\n\t\tif (!type.isEnum()) {\n\t\t\tthrow new IllegalStateException(\"Values are not a enum\");\n\t\t}\n\t\tcollectionSupplier = () -> (Set) EnumSet.noneOf(type);\n\t\treturn upgrade();\n\t}\n\n\t/**\n\t * @param collectionSupplier\n\t * \t\tCollection supplier.\n\t * @param <C1>\n\t * \t\tBacking collection type.\n\t *\n\t * @return This builder.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic <C1 extends Collection<V>> MultiMapBuilder<K, V, C1> values(Supplier<C1> collectionSupplier) {\n\t\tthis.collectionSupplier = (Supplier<Collection<V>>) collectionSupplier;\n\t\treturn upgrade();\n\t}\n\n\t/**\n\t * @return Built multi-map.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic MultiMap<K, V, C> build() {\n\t\treturn MultiMap.from((Map<K, C>) mapSupplier.get(), (Supplier<C>) collectionSupplier);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate <C1 extends Collection<V>> MultiMapBuilder<K, V, C1> upgrade() {\n\t\treturn (MultiMapBuilder<K, V, C1>) this;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/NumberUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\n\n/**\n * General number parsing and other operations.\n *\n * @author Matt Coley\n */\npublic class NumberUtil {\n\t/**\n\t * @param number\n\t * \t\tValue to convert to string.\n\t *\n\t * @return String representation of value.\n\t */\n\t@Nonnull\n\tpublic static String toString(@Nullable Number number) {\n\t\tif (number == null) return \"0\";\n\n\t\tif (number instanceof Integer || number instanceof Byte || number instanceof Short) {\n\t\t\treturn Integer.toString(number.intValue());\n\t\t} else if (number instanceof Double) {\n\t\t\treturn Double.toString(number.doubleValue());\n\t\t} else if (number instanceof Long) {\n\t\t\treturn number.longValue() + \"L\";\n\t\t} else if (number instanceof Float) {\n\t\t\treturn number.floatValue() + \"F\";\n\t\t}\n\n\t\tthrow new IllegalArgumentException(\"Unsupported number type: \" + number.getClass().getName());\n\t}\n\n\t/**\n\t * @param input\n\t * \t\tText input that represents a number.\n\t * \t\t<ul>\n\t * \t\t<li>Numbers ending with {@code F} are parsed as {@link Float}.</li>\n\t * \t\t<li>Numbers ending with {@code D} are parsed as {@link Double}.</li>\n\t * \t\t<li>Numbers ending with {@code L} are parsed as {@link Long}.</li>\n\t * \t\t</ul>\n\t *\n\t * @return Number parsed from text.\n\t * Can be an {@link Integer}, {@link Long}, {@link Float}, or {@link Double}.\n\t *\n\t * @throws NumberFormatException\n\t * \t\tWhen the input cannot be parsed.\n\t */\n\t@SuppressWarnings(\"\")\n\t@Nonnull\n\tpublic static Number parse(@Nonnull String input) {\n\t\tString text = input.trim().toUpperCase();\n\t\tNumber value;\n\t\tif (text.indexOf('.') > 0) {\n\t\t\tvalue = parseDecimal(text);\n\t\t} else {\n\t\t\tif (text.endsWith(\"L\") && text.startsWith(\"0X\")) {\n\t\t\t\tString substring = text.substring(2, text.indexOf(\"L\"));\n\t\t\t\tif (substring.isEmpty()) return 0L;\n\t\t\t\tvalue = Long.parseLong(substring, 16);\n\t\t\t} else if (text.endsWith(\"L\"))\n\t\t\t\tvalue = Long.parseLong(text.substring(0, text.indexOf(\"L\")));\n\t\t\telse if (text.startsWith(\"0X\")) {\n\t\t\t\tString substring = text.substring(2);\n\t\t\t\tif (substring.isEmpty()) return 0;\n\t\t\t\tvalue = Integer.parseInt(substring, 16);\n\t\t\t} else if (text.endsWith(\"F\"))\n\t\t\t\tvalue = Float.parseFloat(text.substring(0, text.indexOf(\"F\")));\n\t\t\telse if (text.endsWith(\"D\") || text.contains(\".\"))\n\t\t\t\tvalue = Double.parseDouble(text.substring(0, text.indexOf(\"D\")));\n\t\t\telse\n\t\t\t\tvalue = Integer.parseInt(text);\n\t\t}\n\t\treturn value;\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tText input that represents a decimal number.\n\t *\n\t * @return Decimal number, either {@link Float} or {@link Double}.\n\t */\n\t@Nonnull\n\tprivate static Number parseDecimal(@Nonnull String text) {\n\t\tif (text.endsWith(\"F\")) {\n\t\t\tint end = text.indexOf(\"F\");\n\t\t\tif (end > 0)\n\t\t\t\ttext = text.substring(0, end);\n\t\t\treturn Float.parseFloat(text.substring(0, end));\n\t\t} else if (text.endsWith(\"D\") || text.contains(\".\")) {\n\t\t\tint end = text.indexOf(\"D\");\n\t\t\tif (end > 0)\n\t\t\t\ttext = text.substring(0, end);\n\t\t\treturn Double.parseDouble(text);\n\t\t} else\n\t\t\treturn Double.parseDouble(text);\n\t}\n\n\t/**\n\t * @param type1\n\t * \t\tNumeric type. Must be a primitive.\n\t * @param type2\n\t * \t\tNumeric type. Must be a primitive.\n\t *\n\t * @return Widest primitive.\n\t */\n\t@Nonnull\n\tpublic static Type getWidestType(@Nonnull Type type1, @Nonnull Type type2) {\n\t\tif (type1.getSort() > type2.getSort())\n\t\t\treturn type1;\n\t\treturn type2;\n\t}\n\n\t/**\n\t * Compare two numeric values, regardless of their type.\n\t *\n\t * @param right\n\t * \t\tFirst value.\n\t * @param left\n\t * \t\tSecond value.\n\t *\n\t * @return Comparison of {@code X.compare(left, right)}\n\t */\n\tpublic static int cmp(@Nonnull Number left, @Nonnull Number right) {\n\t\t// Check for widest types first, go down the type list to narrower types until reaching int.\n\t\tif (right instanceof Double || left instanceof Double) {\n\t\t\treturn Double.compare(left.doubleValue(), right.doubleValue());\n\t\t} else if (right instanceof Float || left instanceof Float) {\n\t\t\treturn Float.compare(left.floatValue(), right.floatValue());\n\t\t} else if (right instanceof Long || left instanceof Long) {\n\t\t\treturn Long.compare(left.longValue(), right.longValue());\n\t\t} else {\n\t\t\treturn Integer.compare(left.intValue(), right.intValue());\n\t\t}\n\t}\n\n\t/**\n\t * @param first\n\t * \t\tFirst value.\n\t * @param second\n\t * \t\tSecond value.\n\t *\n\t * @return Difference value.\n\t */\n\t@Nonnull\n\tpublic static Number sub(@Nonnull Number first, @Nonnull Number second) {\n\t\t// Check for widest types first, go down the type list to narrower types until reaching int.\n\t\tif (second instanceof Double || first instanceof Double) {\n\t\t\treturn first.doubleValue() - second.doubleValue();\n\t\t} else if (second instanceof Float || first instanceof Float) {\n\t\t\treturn first.floatValue() - second.floatValue();\n\t\t} else if (second instanceof Long || first instanceof Long) {\n\t\t\treturn first.longValue() - second.longValue();\n\t\t} else {\n\t\t\treturn first.intValue() - second.intValue();\n\t\t}\n\t}\n\n\t/**\n\t * @param first\n\t * \t\tFirst value.\n\t * @param second\n\t * \t\tSecond value.\n\t *\n\t * @return Sum value.\n\t */\n\t@Nonnull\n\tpublic static Number add(@Nonnull Number first, @Nonnull Number second) {\n\t\t// Check for widest types first, go down the type list to narrower types until reaching int.\n\t\tif (second instanceof Double || first instanceof Double) {\n\t\t\treturn first.doubleValue() + second.doubleValue();\n\t\t} else if (second instanceof Float || first instanceof Float) {\n\t\t\treturn first.floatValue() + second.floatValue();\n\t\t} else if (second instanceof Long || first instanceof Long) {\n\t\t\treturn first.longValue() + second.longValue();\n\t\t} else {\n\t\t\treturn first.intValue() + second.intValue();\n\t\t}\n\t}\n\n\t/**\n\t * @param first\n\t * \t\tFirst value.\n\t * @param second\n\t * \t\tSecond value.\n\t *\n\t * @return Product value.\n\t */\n\t@Nonnull\n\tpublic static Number mul(@Nonnull Number first, @Nonnull Number second) {\n\t\t// Check for widest types first, go down the type list to narrower types until reaching int.\n\t\tif (second instanceof Double || first instanceof Double) {\n\t\t\treturn first.doubleValue() * second.doubleValue();\n\t\t} else if (second instanceof Float || first instanceof Float) {\n\t\t\treturn first.floatValue() * second.floatValue();\n\t\t} else if (second instanceof Long || first instanceof Long) {\n\t\t\treturn first.longValue() * second.longValue();\n\t\t} else {\n\t\t\treturn first.intValue() * second.intValue();\n\t\t}\n\t}\n\n\t/**\n\t * @param first\n\t * \t\tFirst value.\n\t * @param second\n\t * \t\tSecond value.\n\t *\n\t * @return Divided value.\n\t */\n\t@Nonnull\n\tpublic static Number div(@Nonnull Number first, @Nonnull Number second) {\n\t\t// Check for widest types first, go down the type list to narrower types until reaching int.\n\t\tif (second instanceof Double || first instanceof Double) {\n\t\t\treturn first.doubleValue() / second.doubleValue();\n\t\t} else if (second instanceof Float || first instanceof Float) {\n\t\t\treturn first.floatValue() / second.floatValue();\n\t\t} else if (second instanceof Long || first instanceof Long) {\n\t\t\treturn first.longValue() / second.longValue();\n\t\t} else {\n\t\t\treturn first.intValue() / second.intValue();\n\t\t}\n\t}\n\n\t/**\n\t * @param first\n\t * \t\tFirst value.\n\t * @param second\n\t * \t\tSecond value.\n\t *\n\t * @return Remainder value.\n\t */\n\t@Nonnull\n\tpublic static Number rem(@Nonnull Number first, @Nonnull Number second) {\n\t\t// Check for widest types first, go down the type list to narrower types until reaching int.\n\t\tif (second instanceof Double || first instanceof Double) {\n\t\t\treturn first.doubleValue() % second.doubleValue();\n\t\t} else if (second instanceof Float || first instanceof Float) {\n\t\t\treturn first.floatValue() % second.floatValue();\n\t\t} else if (second instanceof Long || first instanceof Long) {\n\t\t\treturn first.longValue() % second.longValue();\n\t\t} else {\n\t\t\treturn first.intValue() % second.intValue();\n\t\t}\n\t}\n\n\t/**\n\t * @param first\n\t * \t\tFirst value.\n\t * @param second\n\t * \t\tSecond value.\n\t *\n\t * @return Value where matching bits remain.\n\t */\n\t@Nonnull\n\tpublic static Number and(@Nonnull Number first, @Nonnull Number second) {\n\t\t// Check for widest types first, go down the type list to narrower types until reaching int.\n\t\tif (second instanceof Long || first instanceof Long) {\n\t\t\treturn first.longValue() & second.longValue();\n\t\t} else {\n\t\t\treturn first.intValue() & second.intValue();\n\t\t}\n\t}\n\n\t/**\n\t * @param first\n\t * \t\tFirst value.\n\t * @param second\n\t * \t\tSecond value.\n\t *\n\t * @return Value where all active bits remain.\n\t */\n\t@Nonnull\n\tpublic static Number or(@Nonnull Number first, @Nonnull Number second) {\n\t\t// Check for widest types first, go down the type list to narrower types until reaching int.\n\t\tif (second instanceof Long || first instanceof Long) {\n\t\t\treturn first.longValue() | second.longValue();\n\t\t} else {\n\t\t\treturn first.intValue() | second.intValue();\n\t\t}\n\t}\n\n\t/**\n\t * @param first\n\t * \t\tFirst value.\n\t * @param second\n\t * \t\tSecond value.\n\t *\n\t * @return Value where non-matching bits remain.\n\t */\n\t@Nonnull\n\tpublic static Number xor(@Nonnull Number first, @Nonnull Number second) {\n\t\t// Check for widest types first, go down the type list to narrower types until reaching int.\n\t\tif (second instanceof Long || first instanceof Long) {\n\t\t\treturn first.longValue() ^ second.longValue();\n\t\t} else {\n\t\t\treturn first.intValue() ^ second.intValue();\n\t\t}\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tNumeric value.\n\t *\n\t * @return Negated value.\n\t */\n\t@Nonnull\n\tpublic static Number neg(@Nonnull Number value) {\n\t\t// Check for widest types first, go down the type list to narrower types until reaching int.\n\t\tif (value instanceof Double) {\n\t\t\treturn -value.doubleValue();\n\t\t} else if (value instanceof Float) {\n\t\t\treturn -value.floatValue();\n\t\t} else if (value instanceof Long) {\n\t\t\treturn -value.longValue();\n\t\t} else {\n\t\t\treturn -value.intValue();\n\t\t}\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tNumeric value.\n\t * @param shift\n\t * \t\tValue to shift by.\n\t *\n\t * @return Shifted value.\n\t */\n\t@Nonnull\n\tpublic static Number shiftLeft(@Nonnull Number value, @Nonnull Number shift) {\n\t\t// Check for widest types first, go down the type list to narrower types until reaching int.\n\t\tif (value instanceof Long) {\n\t\t\treturn value.longValue() << shift.longValue();\n\t\t} else {\n\t\t\treturn value.intValue() << shift.intValue();\n\t\t}\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tNumeric value.\n\t * @param shift\n\t * \t\tValue to shift by.\n\t *\n\t * @return Shifted value.\n\t */\n\t@Nonnull\n\tpublic static Number shiftRight(@Nonnull Number value, @Nonnull Number shift) {\n\t\t// Check for widest types first, go down the type list to narrower types until reaching int.\n\t\tif (value instanceof Long) {\n\t\t\treturn value.longValue() >> shift.longValue();\n\t\t} else {\n\t\t\treturn value.intValue() >> shift.intValue();\n\t\t}\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tNumeric value.\n\t * @param shift\n\t * \t\tValue to shift by.\n\t *\n\t * @return Shifted value.\n\t */\n\t@Nonnull\n\tpublic static Number shiftRightU(@Nonnull Number value, @Nonnull Number shift) {\n\t\t// Check for widest types first, go down the type list to narrower types until reaching int.\n\t\tif (value instanceof Long) {\n\t\t\treturn value.longValue() >>> shift.longValue();\n\t\t} else {\n\t\t\treturn value.intValue() >>> shift.intValue();\n\t\t}\n\t}\n\n\t/**\n\t * Fast {@link Math#pow(double, double)} for {@code int}.\n\t *\n\t * @param base\n\t * \t\tBase value.\n\t * @param exp\n\t * \t\tExponent power.\n\t *\n\t * @return {@code base^exp}.\n\t */\n\tpublic static int intPow(int base, int exp) {\n\t\tif (exp < 0) throw new IllegalArgumentException(\"Exponent must be positive\");\n\t\tint result = 1;\n\t\twhile (true) {\n\t\t\tif ((exp & 1) != 0) result *= base;\n\t\t\tif ((exp >>= 1) == 0) break;\n\t\t\tbase *= base;\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tBase value.\n\t * @param min\n\t * \t\tClamp min.\n\t * @param max\n\t * \t\tClamp max.\n\t *\n\t * @return Value, or min if value below min, or max if value above max.\n\t */\n\tpublic static int intClamp(int value, int min, int max) {\n\t\tif (value > max)\n\t\t\treturn max;\n\t\tif (value < min)\n\t\t\treturn min;\n\t\treturn value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tBase value.\n\t * @param min\n\t * \t\tClamp min.\n\t * @param max\n\t * \t\tClamp max.\n\t *\n\t * @return Value, or min if value below min, or max if value above max.\n\t */\n\tpublic static double doubleClamp(double value, double min, double max) {\n\t\tif (value > max)\n\t\t\treturn max;\n\t\tif (value < min)\n\t\t\treturn min;\n\t\treturn value;\n\t}\n\n\t/**\n\t * @param i\n\t * \t\tSome value.\n\t *\n\t * @return {@code i != 0}\n\t */\n\tpublic static boolean isNonZero(int i) {\n\t\treturn i != 0;\n\t}\n\n\t/**\n\t * @param i\n\t * \t\tSome value.\n\t *\n\t * @return {@code i == 0}\n\t */\n\tpublic static boolean isZero(int i) {\n\t\treturn i == 0;\n\t}\n\n\t/**\n\t * @param a\n\t * \t\tSome value.\n\t * @param b\n\t * \t\tAnother value.\n\t *\n\t * @return {@code true} when they have the same sign.\n\t */\n\tpublic static boolean haveSameSign(int a, int b) {\n\t\treturn a * b > 0;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/PlatformType.java",
    "content": "package software.coley.recaf.util;\n\nimport software.coley.recaf.analytics.SystemInformation;\n\n/**\n * Operating system enumeration.\n *\n * @author Matt Coley\n */\npublic enum PlatformType {\n\tWINDOWS,\n\tMAC,\n\tLINUX;\n\n\t/**\n\t * @return {@code true} when the current platform is windows.\n\t */\n\tpublic static boolean isWindows() {\n\t\treturn get() == WINDOWS;\n\t}\n\n\t/**\n\t * @return {@code true} when the current platform is mac.\n\t */\n\tpublic static boolean isMac() {\n\t\treturn get() == MAC;\n\t}\n\n\t/**\n\t * @return {@code true} when the current platform is linux.\n\t */\n\tpublic static boolean isLinux() {\n\t\treturn get() == LINUX;\n\t}\n\n\t/**\n\t * @return Operating system type.\n\t */\n\tpublic static PlatformType get() {\n\t\tString osName = SystemInformation.OS_NAME.toLowerCase();\n\t\tif (osName.contains(\"win\"))\n\t\t\treturn WINDOWS;\n\t\tif (osName.contains(\"mac\") || osName.contains(\"osx\"))\n\t\t\treturn MAC;\n\t\treturn LINUX;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/ReflectUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport sun.reflect.ReflectionFactory;\n\nimport java.lang.invoke.MethodHandles;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Reflection utilities.\n *\n * @author Matt Coley\n * @author xDark\n */\npublic final class ReflectUtil {\n\tprivate static final Map<Class<?>, ThrowableGetter<?>> GETTERS = new HashMap<>();\n\tprivate static final Map<Class<?>, ThrowableSetter<?>> SETTERS = new HashMap<>();\n\tprivate static final ThrowableGetter<?> DEFAULT_GETTER = ReflectUtil::get;\n\tprivate static final ThrowableSetter<?> DEFAULT_SETTER = ReflectUtil::set;\n\tprivate static MethodHandles.Lookup trustedLookup;\n\n\t/**\n\t * Deny all constructions.\n\t */\n\tprivate ReflectUtil() {\n\t}\n\n\t/**\n\t * Initialize reflection access patching.\n\t */\n\tpublic static void patch() {\n\t\t// Intentionally empty. The patching happens in the static initializer, which the caller will trigger\n\t\t// by calling this method.\n\t}\n\n\t/**\n\t * @return Trusted lookup with all-access permissions.\n\t */\n\t@Nonnull\n\tpublic static MethodHandles.Lookup lookup() {\n\t\tif (trustedLookup != null)\n\t\t\treturn trustedLookup;\n\t\ttry {\n\t\t\tConstructor<MethodHandles.Lookup> lookupCtor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);\n\t\t\tConstructor<?> ctor = ReflectionFactory.getReflectionFactory()\n\t\t\t\t\t.newConstructorForSerialization(MethodHandles.Lookup.class, lookupCtor);\n\t\t\tMethodHandles.Lookup lookup = (MethodHandles.Lookup) ctor.newInstance(MethodHandles.class);\n\t\t\ttrustedLookup = (MethodHandles.Lookup) lookup\n\t\t\t\t\t.findStaticGetter(MethodHandles.Lookup.class, \"IMPL_LOOKUP\", MethodHandles.Lookup.class)\n\t\t\t\t\t.invokeExact();\n\t\t\treturn trustedLookup;\n\t\t} catch (Throwable t) {\n\t\t\tthrow new IllegalStateException(t);\n\t\t}\n\t}\n\n\t/**\n\t * @param declaringClass\n\t * \t\tClass that declares the field.\n\t * @param name\n\t * \t\tName of the field.\n\t *\n\t * @return {@link Field} object for the specified field in the class.\n\t *\n\t * @throws NoSuchFieldException\n\t * \t\tWhen a field with the specified name is not found.\n\t */\n\t@Nonnull\n\tpublic static Field getDeclaredField(Class<?> declaringClass, String name) throws NoSuchFieldException {\n\t\tField field = declaringClass.getDeclaredField(name);\n\t\tfield.setAccessible(true);\n\t\treturn field;\n\t}\n\n\t/**\n\t * @param declaringClass\n\t * \t\tClass that declares the method.\n\t * @param name\n\t * \t\tName of the method.\n\t * @param args\n\t * \t\tArgument types of the method.\n\t *\n\t * @return {@link Method} object for the specified field in the class.\n\t *\n\t * @throws NoSuchMethodException\n\t * \t\tWhen a method with the specified name and argument specification is not found.\n\t */\n\t@Nonnull\n\tpublic static Method getDeclaredMethod(Class<?> declaringClass, String name, Class<?>... args)\n\t\t\tthrows NoSuchMethodException {\n\t\tMethod method = declaringClass.getDeclaredMethod(name, args);\n\t\tmethod.setAccessible(true);\n\t\treturn method;\n\t}\n\n\t/**\n\t * @param instance\n\t * \t\tField owner instance.\n\t * @param field\n\t * \t\tField to set value of.\n\t * @param value\n\t * \t\tValue to set.\n\t * @param <T>\n\t * \t\tAssumed field type.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static <T> void quietSet(Object instance, Field field, T value) {\n\t\ttry {\n\t\t\tThrowableSetter<T> setter = (ThrowableSetter<T>) SETTERS.getOrDefault(field.getType(), DEFAULT_SETTER);\n\t\t\tsetter.set(field, instance, value);\n\t\t} catch (IllegalAccessException ex) {\n\t\t\tthrow new IllegalStateException(\"Setter failure: \" + instance.getClass() + \".\" + field.getName(), ex);\n\t\t}\n\t}\n\n\t/**\n\t * @param instance\n\t * \t\tField owner instance.\n\t * @param field\n\t * \t\tField to get value from.\n\t * @param <T>\n\t * \t\tAssumed field type.\n\t *\n\t * @return Value.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\t@Nullable\n\tpublic static <T> T quietGet(Object instance, Field field) {\n\t\ttry {\n\t\t\tThrowableGetter<T> getter = (ThrowableGetter<T>) GETTERS.getOrDefault(field.getType(), DEFAULT_GETTER);\n\t\t\treturn getter.get(field, instance);\n\t\t} catch (IllegalAccessException ex) {\n\t\t\tthrow new IllegalStateException(\"Getter failure: \" + instance.getClass() + \".\" + field.getName(), ex);\n\t\t}\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tClass to construct.\n\t * @param argTypes\n\t * \t\tArgument types.\n\t * @param args\n\t * \t\tArgument values.\n\t * @param <T>\n\t * \t\tAssumed class type.\n\t *\n\t * @return New instance of class.\n\t */\n\t@Nonnull\n\tpublic static <T> T quietNew(Class<T> type, Class<?>[] argTypes, Object[] args) {\n\t\ttry {\n\t\t\tConstructor<T> constructor = type.getDeclaredConstructor(argTypes);\n\t\t\tconstructor.setAccessible(true);\n\t\t\treturn constructor.newInstance(args);\n\t\t} catch (ReflectiveOperationException ex) {\n\t\t\tthrow new IllegalStateException(\"Constructor failure: \" + type.getName(), ex);\n\t\t}\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tClass the method is defined in.\n\t * @param instance\n\t * \t\tInstance to invoke in, or {@code null} for static.\n\t * @param name\n\t * \t\tMethod name.\n\t * @param argTypes\n\t * \t\tArgument types.\n\t * @param args\n\t * \t\tArgument values.\n\t * @param <T>\n\t * \t\tAssumed class type.\n\t *\n\t * @return Return value of invoke.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\t@Nullable\n\tpublic static <T> T quietInvoke(Class<?> type, Object instance, String name, Class<?>[] argTypes, Object[] args) {\n\t\ttry {\n\t\t\tMethod method = type.getDeclaredMethod(name, argTypes);\n\t\t\tmethod.setAccessible(true);\n\t\t\treturn (T) method.invoke(instance, args);\n\t\t} catch (ReflectiveOperationException ex) {\n\t\t\tthrow new IllegalStateException(\"Invoke failure: \" + type.getName(), ex);\n\t\t}\n\t}\n\n\t/**\n\t * Copy field values in 'from' into 'to'.\n\t *\n\t * @param from\n\t * \t\tInstance with values to copy.\n\t * @param to\n\t * \t\tDestination to copy values into.\n\t */\n\tpublic static void copyTo(Object from, Object to) {\n\t\tif (from == null || to == null)\n\t\t\treturn;\n\t\t// Types must match\n\t\tClass<?> type = to.getClass();\n\t\tif (!type.equals(from.getClass()))\n\t\t\treturn;\n\t\t// Copy field values in 'from' into 'to'\n\t\tfor (Field field : type.getDeclaredFields()) {\n\t\t\tif ((field.getModifiers() & Modifier.STATIC) > 0)\n\t\t\t\tcontinue;\n\t\t\tfield.setAccessible(true);\n\t\t\tObject value = quietGet(from, field);\n\t\t\tquietSet(to, field, value);\n\t\t}\n\t}\n\n\t/**\n\t * Propagates throwable.\n\t *\n\t * @param t\n\t * \t\tThrowable to propagate.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static <X extends Throwable> void propagate(Throwable t) throws X {\n\t\tthrow (X) t;\n\t}\n\n\t/**\n\t * @param field\n\t * \t\tField to get from.\n\t * @param instance\n\t * \t\tInstance of the class.\n\t *\n\t * @return Field value in the instance.\n\t *\n\t * @throws IllegalAccessException\n\t * \t\tWhen the field could not be accessed.\n\t */\n\t@Nullable\n\tprivate static Object get(Field field, Object instance) throws IllegalAccessException {\n\t\tfield.setAccessible(true);\n\t\treturn field.get(instance);\n\t}\n\n\t/**\n\t * @param field\n\t * \t\tField to set.\n\t * @param instance\n\t * \t\tInstance of the class.\n\t * @param value\n\t * \t\tValue to set.\n\t *\n\t * @throws IllegalAccessException\n\t * \t\tWhen the field could not be accessed.\n\t */\n\tprivate static void set(Field field, Object instance, Object value) throws IllegalAccessException {\n\t\tfield.setAccessible(true);\n\t\tfield.set(instance, value);\n\t}\n\n\tstatic {\n\t\t// Getters\n\t\tGETTERS.put(boolean.class, (ReflectUtil.ThrowableGetter<Boolean>) Field::getBoolean);\n\t\tGETTERS.put(byte.class, (ReflectUtil.ThrowableGetter<Byte>) Field::getByte);\n\t\tGETTERS.put(char.class, (ReflectUtil.ThrowableGetter<Character>) Field::getChar);\n\t\tGETTERS.put(short.class, (ReflectUtil.ThrowableGetter<Short>) Field::getShort);\n\t\tGETTERS.put(int.class, (ReflectUtil.ThrowableGetter<Integer>) Field::getInt);\n\t\tGETTERS.put(long.class, (ReflectUtil.ThrowableGetter<Long>) Field::getLong);\n\t\tGETTERS.put(float.class, (ReflectUtil.ThrowableGetter<Float>) Field::getFloat);\n\t\tGETTERS.put(double.class, (ReflectUtil.ThrowableGetter<Double>) Field::getDouble);\n\n\t\t// Setters\n\t\tSETTERS.put(boolean.class, (ReflectUtil.ThrowableSetter<Boolean>) Field::setBoolean);\n\t\tSETTERS.put(byte.class, (ReflectUtil.ThrowableSetter<Byte>) Field::setByte);\n\t\tSETTERS.put(char.class, (ReflectUtil.ThrowableSetter<Character>) Field::setChar);\n\t\tSETTERS.put(short.class, (ReflectUtil.ThrowableSetter<Short>) Field::setShort);\n\t\tSETTERS.put(int.class, (ReflectUtil.ThrowableSetter<Integer>) Field::setInt);\n\t\tSETTERS.put(long.class, (ReflectUtil.ThrowableSetter<Long>) Field::setLong);\n\t\tSETTERS.put(float.class, (ReflectUtil.ThrowableSetter<Float>) Field::setFloat);\n\t\tSETTERS.put(double.class, (ReflectUtil.ThrowableSetter<Double>) Field::setDouble);\n\n\t\t// Bypass access restrictions\n\t\tAccessPatcher.patch();\n\t}\n\n\t/**\n\t * Functional interface for field set operations.\n\t *\n\t * @param <T>\n\t * \t\tReturn type.\n\t */\n\tinterface ThrowableSetter<T> {\n\t\tvoid set(Field field, Object instance, T value) throws IllegalAccessException;\n\t}\n\n\t/**\n\t * Functional interface for field get operations.\n\t *\n\t * @param <T>\n\t * \t\tParameter type.\n\t */\n\tinterface ThrowableGetter<T> {\n\t\tT get(Field field, Object instance) throws IllegalAccessException;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/RegexUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.slf4j.Logger;\nimport regexodus.Matcher;\nimport regexodus.Pattern;\nimport software.coley.recaf.analytics.logging.Logging;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Some common regular expression functions and cache for compiled patterns.\n *\n * @author Matt Coley\n */\npublic class RegexUtil {\n\tprivate static final Logger logger = Logging.get(RegexUtil.class);\n\tprivate static final Map<String, RegexValidation> FAILED_PATTERNS = new HashMap<>();\n\tprivate static final Map<String, ThreadLocalPattern> PATTERNS = new HashMap<>();\n\tprivate static final ThreadLocalPattern INVALID_PATTERN = new ThreadLocalPattern(new Pattern(\"^$\"));\n\n\t/**\n\t * @param pattern\n\t * \t\tPattern to check.\n\t *\n\t * @return {@code true} when valid. {@code false} otherwise.\n\t */\n\t@Nonnull\n\tpublic static RegexValidation validate(@Nonnull String pattern) {\n\t\t// Check prior failed patterns\n\t\tRegexValidation result = FAILED_PATTERNS.get(pattern);\n\t\tif (result == null) {\n\t\t\ttry {\n\t\t\t\tnew Pattern(pattern);\n\t\t\t\tresult = RegexValidation.VALID;\n\t\t\t} catch (Throwable t) {\n\t\t\t\t// Record new invalid pattern.\n\t\t\t\tresult = new RegexValidation(false, t.getMessage());\n\t\t\t\tFAILED_PATTERNS.put(pattern, result);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * @param pattern\n\t * \t\tPattern to match.\n\t * @param text\n\t * \t\tSome text containing a match for the given pattern.\n\t *\n\t * @return Text matcher.\n\t */\n\tpublic static Matcher getMatcher(@Nonnull String pattern, @Nonnull String text) {\n\t\treturn getPattern(pattern).matcher(text);\n\t}\n\n\t/**\n\t * Checks if the entire input matches a pattern.\n\t *\n\t * @param pattern\n\t * \t\tPattern to match.\n\t * @param input\n\t * \t\tText to match against.\n\t *\n\t * @return {@code true} when the input is a full match.\n\t */\n\tpublic static boolean matches(@Nonnull String pattern, @Nonnull String input) {\n\t\treturn getMatcher(pattern, input).matches();\n\t}\n\n\t/**\n\t * Checks if any portion of the input matches a pattern.\n\t *\n\t * @param pattern\n\t * \t\tPattern to match.\n\t * @param input\n\t * \t\tText to match against.\n\t *\n\t * @return {@code true} when any part of the input matches.\n\t */\n\tpublic static boolean matchesAny(@Nonnull String pattern, @Nonnull String input) {\n\t\treturn getMatcher(pattern, input).find();\n\t}\n\n\t/**\n\t * Creates new {@link Pattern} or gets it from cache.\n\t *\n\t * @param regex\n\t * \t\tRegular expression text.\n\t *\n\t * @return Compiled {@link Pattern} of the regular expression.\n\t */\n\t@Nonnull\n\tpublic synchronized static Pattern pattern(@Nonnull String regex) {\n\t\treturn getPattern(regex).pattern;\n\t}\n\n\t/**\n\t * Creates new {@link ThreadLocalPattern} or gets it from cache.\n\t *\n\t * @param regex\n\t * \t\tRegular expression text.\n\t *\n\t * @return Compiled {@link ThreadLocalPattern} of the regular expression.\n\t */\n\t@Nonnull\n\tprivate synchronized static ThreadLocalPattern getPattern(@Nonnull String regex) {\n\t\treturn PATTERNS.computeIfAbsent(regex, RegexUtil::generate);\n\t}\n\n\t/**\n\t * @param regex\n\t * \t\tRegular expression to compile.\n\t *\n\t * @return Compiled pattern, or {@link #INVALID_PATTERN} if the pattern is invalid.\n\t */\n\t@Nonnull\n\tprivate static ThreadLocalPattern generate(@Nonnull String regex) {\n\t\tif (FAILED_PATTERNS.containsKey(regex))\n\t\t\treturn INVALID_PATTERN;\n\t\ttry {\n\t\t\treturn new ThreadLocalPattern(new Pattern(regex));\n\t\t} catch (Throwable t) {\n\t\t\tFAILED_PATTERNS.put(regex, new RegexValidation(false, t.getMessage()));\n\t\t\tlogger.error(\"Invalid regex pattern\", t);\n\t\t\treturn INVALID_PATTERN;\n\t\t}\n\t}\n\n\tpublic record RegexValidation(boolean valid, @Nullable String message) {\n\n\t\tpublic static final RegexValidation VALID = new RegexValidation(true, null);\n\t}\n\n\tprivate static final class ThreadLocalPattern {\n\t\tfinal ThreadLocal<Matcher> matcher = new ThreadLocal<>();\n\t\tfinal Pattern pattern;\n\n\t\tprivate ThreadLocalPattern(@Nonnull Pattern pattern) {\n\t\t\tthis.pattern = pattern;\n\t\t}\n\n\t\t@Nonnull\n\t\tMatcher matcher(@Nonnull String input) {\n\t\t\tThreadLocal<Matcher> tlc = this.matcher;\n\t\t\tMatcher matcher = tlc.get();\n\t\t\tif (matcher == null) {\n\t\t\t\tmatcher = pattern.matcher(input);\n\t\t\t\ttlc.set(matcher);\n\t\t\t} else {\n\t\t\t\tmatcher.flush();\n\t\t\t\tmatcher.setTarget(input);\n\t\t\t}\n\t\t\treturn matcher;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/ResourceUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport java.io.InputStream;\n\n/**\n * Utility for classpath resources.\n *\n * @author Matt Coley\n */\npublic class ResourceUtil {\n\t/**\n\t * Check if a resource exists in the current classpath.\n\t *\n\t * @param path\n\t * \t\tPath to resource.\n\t *\n\t * @return {@code true} if resource exists. {@code false} otherwise.\n\t */\n\tpublic static boolean resourceExists(String path) {\n\t\tif (!path.startsWith(\"/\"))\n\t\t\tpath = \"/\" + path;\n\t\treturn ResourceUtil.class.getResource(path) != null;\n\t}\n\n\t/**\n\t * Fetch a resource as a stream in the current classpath.\n\t *\n\t * @param path\n\t * \t\tPath to resource.\n\t *\n\t * @return Stream of resource.\n\t */\n\tpublic static InputStream resource(String path) {\n\t\tif (!path.startsWith(\"/\"))\n\t\t\tpath = \"/\" + path;\n\t\treturn ResourceUtil.class.getResourceAsStream(path);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/SelfReferenceUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.CodeSource;\nimport java.util.ArrayList;\nimport java.util.Enumeration;\nimport java.util.List;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\n/**\n * Utility for self-referencing the Recaf application as a file.\n *\n * @author Matt Coley\n */\npublic class SelfReferenceUtil {\n\tprivate static final Logger logger = Logging.get(SelfReferenceUtil.class);\n\tprivate static SelfReferenceUtil instance = null;\n\tprivate final File file;\n\tprivate final boolean isJar;\n\n\tprivate SelfReferenceUtil(File file) {\n\t\tthis.file = file;\n\t\tthis.isJar = file.getName().toLowerCase().endsWith(\".jar\");\n\t}\n\n\t/**\n\t * @return File reference to self.\n\t */\n\tpublic File getFile() {\n\t\treturn file;\n\t}\n\n\t/**\n\t * @return File path to self.\n\t */\n\tpublic String getPath() {\n\t\treturn file.getAbsolutePath();\n\t}\n\n\t/**\n\t * @return Is the current executable context a jar file.\n\t */\n\tpublic boolean isJar() {\n\t\treturn isJar;\n\t}\n\n\t/**\n\t * Used in the UI module for syntax highlighting based on language patterns.\n\t *\n\t * @return List of language files recognized.\n\t */\n\tpublic List<InternalPath> getLanguages() {\n\t\treturn getFiles(\"languages/\", \".json\");\n\t}\n\n\t/**\n\t * Used in the UI module for translating the UI.\n\t *\n\t * @return List of translation files recognized.\n\t */\n\tpublic List<InternalPath> getTranslations() {\n\t\treturn getFiles(\"translations/\", \".lang\");\n\t}\n\n\t/**\n\t * @param prefix\n\t * \t\tFile prefix to match.\n\t * @param suffix\n\t * \t\tFile suffix to match <i>(such as a file extension)</i>.\n\t *\n\t * @return List of matching files.\n\t */\n\tprivate List<InternalPath> getFiles(String prefix, String suffix) {\n\t\tList<InternalPath> list = new ArrayList<>();\n\t\tif (isJar()) {\n\t\t\t// Read self as jar\n\t\t\ttry (ZipFile file = new ZipFile(getFile())) {\n\t\t\t\tEnumeration<? extends ZipEntry> entries = file.entries();\n\t\t\t\twhile (entries.hasMoreElements()) {\n\t\t\t\t\tZipEntry entry = entries.nextElement();\n\t\t\t\t\t// skip directories\n\t\t\t\t\tif (entry.isDirectory()) continue;\n\t\t\t\t\tString name = entry.getName();\n\t\t\t\t\tif (prefix != null && !name.startsWith(prefix))\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tif (suffix != null && !name.endsWith(suffix))\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tlist.add(InternalPath.internal(name));\n\t\t\t\t}\n\t\t\t} catch (Exception ex) {\n\t\t\t\tlogger.error(\"Failed internal file (archive) lookup: {}\", getFile(), ex);\n\t\t\t}\n\t\t} else {\n\t\t\ttry {\n\t\t\t\t// You may be asking yourself \"wtf is this?\"\n\t\t\t\t// Well, if this is run via gradle the current path is: 'recaf-ui/build/classes/java/main'\n\t\t\t\t//  - Needs to become: 'recaf-ui/build/resources/main'\n\t\t\t\t//  - So cd '../../resources/main'\n\t\t\t\t// But if you want to use IntelliJ's optimized run (waaaaay faster startup than Gradle) this changes.\n\t\t\t\t// However, for both you can use the system classloader's internal state to check the loaded directories.\n\t\t\t\t// Using this we scan for '/resources/' since this is a common path element in each case.\n\t\t\t\t// Plus, this supports finding files from all modules, not just the 'recaf-ui' module.\n\t\t\t\tList<URL> resourceDirUrls = ClassLoaderInternals.getUcpPathList(ClassLoaderInternals.getUcp()).stream()\n\t\t\t\t\t\t.filter(url -> url.toString().contains(\"/resources/\"))\n\t\t\t\t\t\t.toList();\n\t\t\t\tfor (URL url : resourceDirUrls) {\n\t\t\t\t\tPath dir = Paths.get(url.toURI());\n\t\t\t\t\tFiles.walk(dir).forEach(p -> {\n\t\t\t\t\t\tFile file = dir.relativize(p).toFile();\n\t\t\t\t\t\tString path = file.getPath().replace('\\\\', '/');\n\t\t\t\t\t\tif (prefix != null && !path.startsWith(prefix))\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\tif (suffix != null && !path.endsWith(suffix))\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\tlist.add(InternalPath.internal(path));\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (IOException ex) {\n\t\t\t\tlogger.error(\"Failed internal file (directory) lookup: {}\", getFile(), ex);\n\t\t\t} catch (ReflectiveOperationException ex) {\n\t\t\t\tlogger.error(\"Failed to lookup resources path from UCP\", ex);\n\t\t\t} catch (URISyntaxException ex) {\n\t\t\t\tthrow new IllegalStateException(\"Malformed URI path from UCP\", ex);\n\t\t\t}\n\t\t}\n\t\treturn list;\n\t}\n\n\t/**\n\t * Initializes the self-reference util based on the {@link CodeSource} of the given class.\n\t *\n\t * @param context\n\t * \t\tClass to initialize from.\n\t *\n\t * @see #getInstance()\n\t */\n\tpublic static void initializeFromContext(Class<?> context) {\n\t\tif (instance != null) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tCodeSource codeSource = context.getProtectionDomain().getCodeSource();\n\t\t\tFile selfFile = new File(codeSource.getLocation().toURI().getPath());\n\t\t\tinstance = new SelfReferenceUtil(selfFile);\n\t\t} catch (URISyntaxException e) {\n\t\t\tlogger.error(\"Failed to resolve self reference\", e);\n\t\t}\n\t}\n\n\tpublic static SelfReferenceUtil getInstance() {\n\t\treturn instance;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/ShortcutUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.text.ParseException;\nimport java.util.Arrays;\n\n/**\n * Represents a Windows shortcut (typically visible to Java only as a '.lnk' file).\n * <br>\n * Retrieved 2011-09-23 from:\n * <a href=\"http://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java/672775#672775\">\n * Windows shortcut (.lnk) parser in Java?</a> - Originally called LnkParser\n *\n * @author crysxd - Removing Apache VFS dependency\n * @author Headerified - Refactoring and comments\n * @author Stefan Cordes - Adaptation\n * @author Matt Coley - Minor additions\n */\npublic class ShortcutUtil {\n\tprivate boolean isDirectory;\n\tprivate boolean isLocal;\n\tprivate String realFile;\n\n\t/**\n\t * @param path\n\t * \t\tPath to follow.\n\t * @param followDepth\n\t * \t\tMaximum depth to follow symbolic links.\n\t *\n\t * @return Target of shortcut/symbolic link.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the shortcut could not be parsed.\n\t */\n\t@Nonnull\n\tpublic static Path follow(Path path, int followDepth) throws IOException {\n\t\ttry {\n\t\t\t// First check if it's a windows 'lnk' shortcut\n\t\t\tif (isPotentialValidLink(path))\n\t\t\t\tpath = path.getFileSystem().getPath(new ShortcutUtil(path).getRealFilename());\n\n\t\t\t// Then traverse any symbolic links up to the given depth\n\t\t\tint symLevel = 0;\n\t\t\twhile (true) {\n\t\t\t\tboolean visited = false;\n\n\t\t\t\t// Follow symbolic links\n\t\t\t\twhile (Files.isSymbolicLink(path) && symLevel < followDepth) {\n\t\t\t\t\tpath = Files.readSymbolicLink(path);\n\t\t\t\t\tsymLevel++;\n\t\t\t\t\tvisited = true;\n\t\t\t\t}\n\n\t\t\t\t// Follow symbolic links via real path resolution (as a fallback, since nio can be inconsistent)\n\t\t\t\t// This is primarily observed to occur with directory links vs file links.\n\t\t\t\twhile (!path.equals(path.toRealPath())) {\n\t\t\t\t\tpath = path.toRealPath();\n\t\t\t\t\tsymLevel++;\n\t\t\t\t\tvisited = true;\n\t\t\t\t}\n\n\t\t\t\tif (!visited)\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\treturn path;\n\t\t} catch (ParseException ex) {\n\t\t\tthrow new IOException(\"Failed to parse shortcut/sym-link for path: \" + path, ex);\n\t\t}\n\t}\n\n\t/**\n\t * Provides a quick test to see if this could be a valid link.\n\t *\n\t * @param path\n\t * \t\tThe potential link\n\t *\n\t * @return {@code true} if the path may be a link, {@code false} otherwise\n\t */\n\tpublic static boolean isPotentialValidLink(final Path path) {\n\t\tif (!Files.exists(path))\n\t\t\treturn false;\n\t\tString extension = IOUtil.getExtension(path);\n\t\tif (!\"lnk\".equalsIgnoreCase(extension))\n\t\t\treturn false;\n\t\tfinal int minimumLength = 0x64;\n\t\ttry (InputStream fis = Files.newInputStream(path)) {\n\t\t\tif (fis.available() < minimumLength)\n\t\t\t\treturn false;\n\t\t\tint bufSize = 64;\n\t\t\tbyte[] buf = new byte[bufSize];\n\t\t\tint offset = 0;\n\t\t\twhile (bufSize != 0) {\n\t\t\t\tint read = fis.read(buf, offset, bufSize);\n\t\t\t\tif (read == -1)\n\t\t\t\t\tbreak;\n\t\t\t\tbufSize -= read;\n\t\t\t\toffset += read;\n\t\t\t}\n\t\t\treturn fis.available() >= minimumLength &&\n\t\t\t\t\tisMagicPresent(IOUtil.toByteArray(fis, Arrays.copyOf(buf, offset)));\n\t\t} catch (Exception ex) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath reference to shortcut.\n\t *\n\t * @throws IOException\n\t * \t\tIf the path cannot be read.\n\t * @throws ParseException\n\t * \t\tIf the link cannot be read.\n\t */\n\tpublic ShortcutUtil(final Path path) throws IOException, ParseException {\n\t\ttry (InputStream in = Files.newInputStream(path)) {\n\t\t\tparseLink(IOUtil.toByteArray(in));\n\t\t}\n\t}\n\n\t/**\n\t * @return the name of the filesystem object pointed to by this shortcut\n\t */\n\t@Nonnull\n\tpublic String getRealFilename() {\n\t\treturn realFile;\n\t}\n\n\t/**\n\t * Tests if the shortcut points to a local resource.\n\t *\n\t * @return true if the 'local' bit is set in this shortcut, false otherwise\n\t */\n\tpublic boolean isLocal() {\n\t\treturn isLocal;\n\t}\n\n\t/**\n\t * Tests if the shortcut points to a directory.\n\t *\n\t * @return true if the 'directory' bit is set in this shortcut, false otherwise\n\t */\n\tpublic boolean isDirectory() {\n\t\treturn isDirectory;\n\t}\n\n\tprivate static boolean isMagicPresent(final byte[] link) {\n\t\tfinal int magic = 0x0000004C;\n\t\tfinal int magicOffset = 0x00;\n\t\treturn link.length >= 32 && bytesToDword(link, magicOffset) == magic;\n\t}\n\n\t/**\n\t * Gobbles up link data by parsing it and storing info in member fields\n\t *\n\t * @param link\n\t * \t\tall the bytes from the .lnk file\n\t */\n\tprivate void parseLink(final byte[] link) throws ParseException {\n\t\ttry {\n\t\t\tif (!isMagicPresent(link))\n\t\t\t\tthrow new ParseException(\"Invalid shortcut; magic is missing\", 0);\n\n\t\t\t// get the flags byte\n\t\t\tfinal byte flags = link[0x14];\n\n\t\t\t// get the file attributes byte\n\t\t\tfinal int fileAttsOffset = 0x18;\n\t\t\tfinal byte fileAtts = link[fileAttsOffset];\n\t\t\tfinal byte isDirMask = (byte) 0x10;\n\t\t\tisDirectory = (fileAtts & isDirMask) > 0;\n\n\t\t\t// if the shell settings are present, skip them\n\t\t\tfinal int shellOffset = 0x4c;\n\t\t\tfinal byte hasShellMask = (byte) 0x01;\n\t\t\tint shellLen = 0;\n\t\t\tif ((flags & hasShellMask) > 0) {\n\t\t\t\t// the plus 2 accounts for the length marker itself\n\t\t\t\tshellLen = bytesToWord(link, shellOffset) + 2;\n\t\t\t}\n\n\t\t\t// get to the file settings\n\t\t\tfinal int fileStart = 0x4c + shellLen;\n\n\t\t\tfinal int fileLocationInfoFlagOffsetOffset = 0x08;\n\t\t\tfinal int fileLocationInfoFlag =\n\t\t\t\t\tlink[fileStart + fileLocationInfoFlagOffsetOffset];\n\t\t\tisLocal = (fileLocationInfoFlag & 2) == 0;\n\t\t\t// get the local volume and local system values\n\t\t\t//final int localVolumeTable_offset_offset = 0x0C;\n\t\t\tfinal int basenameOffsetOffset = 0x10;\n\t\t\tfinal int networkVolumeTableOffsetOffset = 0x14;\n\t\t\tfinal int finalnameOffsetOffset = 0x18;\n\t\t\tfinal int finalnameOffset = link[fileStart + finalnameOffsetOffset] + fileStart;\n\t\t\tfinal String finalname = getNullDelimitedString(link, finalnameOffset);\n\t\t\tif (isLocal) {\n\t\t\t\tfinal int basenameOffset = link[fileStart + basenameOffsetOffset] + fileStart;\n\t\t\t\tfinal String basename = getNullDelimitedString(link, basenameOffset);\n\t\t\t\trealFile = basename + finalname;\n\t\t\t} else {\n\t\t\t\tfinal int networkVolumeTableOffset =\n\t\t\t\t\t\tlink[fileStart + networkVolumeTableOffsetOffset] + fileStart;\n\t\t\t\tfinal int shareNameOffsetOffset = 0x08;\n\t\t\t\tfinal int shareNameOffset =\n\t\t\t\t\t\tlink[networkVolumeTableOffset + shareNameOffsetOffset] + networkVolumeTableOffset;\n\t\t\t\tfinal String shareName = getNullDelimitedString(link, shareNameOffset);\n\t\t\t\trealFile = shareName + \"\\\\\" + finalname;\n\t\t\t}\n\t\t} catch (final ArrayIndexOutOfBoundsException e) {\n\t\t\tthrow new ParseException(\"Could not be parsed, probably not a valid WindowsShortcut\", 0);\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate static String getNullDelimitedString(final byte[] bytes, final int off) {\n\t\t// count bytes until the null character (0)\n\t\tint len = 0;\n\t\twhile (bytes[off + len] != 0)\n\t\t\tlen++;\n\t\treturn new String(bytes, off, len);\n\t}\n\n\t/*\n\t * Convert two bytes into a short note, this is little endian because it's\n\t * for an Intel only OS.\n\t */\n\tprivate static int bytesToWord(final byte[] bytes, final int off) {\n\t\treturn ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);\n\t}\n\n\tprivate static int bytesToDword(final byte[] bytes, final int off) {\n\t\treturn (bytesToWord(bytes, off + 2) << 16) | bytesToWord(bytes, off);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/Streams.java",
    "content": "package software.coley.recaf.util;\n\nimport software.coley.recaf.util.threading.CountDown;\n\nimport java.util.ArrayDeque;\nimport java.util.Collections;\nimport java.util.Deque;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.Set;\nimport java.util.Spliterator;\nimport java.util.Spliterators;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.stream.Stream;\nimport java.util.stream.StreamSupport;\n\n/**\n * Stream utilities.\n *\n * @author xDark\n */\npublic final class Streams {\n\tprivate Streams() {\n\t}\n\n\t/**\n\t * Combines the given streams.\n\t *\n\t * @param streams\n\t * \t\tStreams to combine.\n\t * @param <T>\n\t * \t\tStream element type.\n\t *\n\t * @return Combined stream.\n\t */\n\tpublic static <T> Stream<? extends T> of(Stream<? extends T>... streams) {\n\t\tStream<? extends T> merged = null;\n\t\tfor (Stream<? extends T> stream : streams) {\n\t\t\tif (merged == null) merged = stream;\n\t\t\telse merged = Stream.concat(merged, stream);\n\t\t}\n\t\treturn merged == null ? Stream.empty() : merged;\n\t}\n\n\t/**\n\t * Makes stream interruptable.\n\t *\n\t * @param stream\n\t * \t\tStream to make interruptable.\n\t * @param <T>\n\t * \t\tStream element type.\n\t *\n\t * @return Interruptable stream.\n\t */\n\tpublic static <T> Stream<T> interruptable(Stream<? extends T> stream) {\n\t\tSpliterator<? extends T> spliterator = stream.spliterator();\n\t\treturn StreamSupport.stream(new Spliterators.AbstractSpliterator<>(spliterator.estimateSize(), spliterator.characteristics()) {\n\t\t\t@Override\n\t\t\tpublic boolean tryAdvance(Consumer<? super T> action) {\n\t\t\t\tif (Thread.interrupted()) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn spliterator.tryAdvance(action);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void forEachRemaining(Consumer<? super T> action) {\n\t\t\t\tif (Thread.interrupted()) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tspliterator.forEachRemaining(action);\n\t\t\t}\n\t\t}, stream.isParallel());\n\t}\n\n\t/**\n\t * Accepts all elements in the stream in the given executor.\n\t *\n\t * @param stream\n\t * \t\tStream to accept.\n\t * @param consumer\n\t * \t\tElement consumer.\n\t * @param executor\n\t * \t\tExecutor to use.\n\t * @param <T>\n\t * \t\tElement type.\n\t */\n\tpublic static <T> void forEachOn(Stream<T> stream, Consumer<? super T> consumer, Executor executor) {\n\t\tAtomicReference<Throwable> throwable = new AtomicReference<>();\n\t\tCountDown countDown = new CountDown();\n\t\tstream.forEach(x -> {\n\t\t\tThrowable thrown = throwable.get();\n\t\t\tif (thrown != null) {\n\t\t\t\tReflectUtil.propagate(thrown);\n\t\t\t}\n\t\t\tcountDown.register();\n\t\t\texecutor.execute(() -> {\n\t\t\t\ttry {\n\t\t\t\t\tif (throwable.get() == null) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconsumer.accept(x);\n\t\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\t\tthrowable.compareAndSet(null, t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} finally {\n\t\t\t\t\tcountDown.release();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\ttry {\n\t\t\tcountDown.await();\n\t\t} catch (InterruptedException ignored) {\n\t\t}\n\t\tThrowable thrown = throwable.get();\n\t\tif (thrown != null) {\n\t\t\tReflectUtil.propagate(thrown);\n\t\t}\n\t}\n\n\t/**\n\t * Recursively traversed {@code seed}.\n\t *\n\t * @param seed\n\t * \t\tInitial seed.\n\t * @param fn\n\t * \t\tTransforming function.\n\t * @param <T>\n\t * \t\tElement type.\n\t *\n\t * @return Stream containing all traversed elements.\n\t */\n\tpublic static <T> Stream<T> recurse(T seed, Function<? super T, Stream<? extends T>> fn) {\n\t\treturn Stream.concat(\n\t\t\t\tStream.of(seed),\n\t\t\t\tStream.of(seed)\n\t\t\t\t\t\t.flatMap(fn)\n\t\t\t\t\t\t.flatMap(x -> recurse(x, fn)));\n\t}\n\n\t/**\n\t * Recursively traversed {@code seed}.\n\t *\n\t * @param seed\n\t * \t\tInitial stream.\n\t * @param fn\n\t * \t\tTransforming function.\n\t * @param <T>\n\t * \t\tElement type.\n\t *\n\t * @return Stream containing all traversed elements.\n\t */\n\tpublic static <T> Stream<T> recurse(Stream<? extends T> seed, Function<? super T, Stream<? extends T>> fn) {\n\t\treturn seed.flatMap(x -> recurse(x, fn));\n\t}\n\n\t/**\n\t * @param flatMap\n\t * \t\tTransforming function.\n\t * @param <T>\n\t * \t\tElement type.\n\t *\n\t * @return Stream containing all traversed elements.\n\t */\n\tpublic static <T> Stream<T> recurseWithoutCycles(T seed, Function<T, Set<T>> flatMap) {\n\t\tDeque<Iterator<T>> vertices = new ArrayDeque<>();\n\t\tSet<T> visited = new HashSet<>();\n\t\tvertices.push(Collections.singletonList(seed).iterator());\n\t\treturn StreamSupport.stream(new Spliterators.AbstractSpliterator<>(Long.MAX_VALUE, Spliterator.IMMUTABLE | Spliterator.NONNULL) {\n\t\t\t@Override\n\t\t\tpublic boolean tryAdvance(Consumer<? super T> action) {\n\t\t\t\twhile (true) {\n\t\t\t\t\tIterator<T> iterator = vertices.peek();\n\t\t\t\t\tif (iterator == null) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (!iterator.hasNext()) {\n\t\t\t\t\t\tvertices.poll();\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tT vertex = iterator.next();\n\t\t\t\t\tif (visited.add(vertex)) {\n\t\t\t\t\t\taction.accept(vertex);\n\t\t\t\t\t\tvertices.push(flatMap.apply(vertex).iterator());\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}, false);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/StringDecodingResult.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\nimport java.nio.charset.Charset;\n\n/**\n * Model of attempted string decoding.\n *\n * @param raw\n * \t\tBytes that were attempted to be decoded.\n * @param charset\n * \t\tDetermined encoding of the text bytes. {@code null} if the bytes did not get interpreted as text.\n * @param text\n * \t\tDetermined string representation of the text bytes. {@code null} if the bytes did not get interpreted as text.\n *\n * @author Matt Coley\n * @see StringUtil#decodeString(byte[]) Utility method to guess the encoding of some arbitrary input.\n */\npublic record StringDecodingResult(@Nonnull byte[] raw, @Nullable Charset charset, @Nullable String text) {\n\t/**\n\t * @return {@code true} when the {@link #raw() raw bytes} could be decoded into a known {@link #charset() charset}.\n\t */\n\tpublic boolean couldDecode() {\n\t\treturn charset != null;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/StringDiff.java",
    "content": "package software.coley.recaf.util;\n\nimport com.github.difflib.DiffUtils;\nimport com.github.difflib.patch.AbstractDelta;\nimport com.github.difflib.patch.DeltaType;\nimport com.github.difflib.patch.Patch;\nimport jakarta.annotation.Nonnull;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Comparator;\nimport java.util.List;\n\n/**\n * Wrapping around JGit diff logic.\n *\n * @author Matt Coley\n */\npublic class StringDiff {\n\t/**\n\t * @param a\n\t * \t\tInput.\n\t * @param b\n\t * \t\tModified input.\n\t *\n\t * @return Diffs from {@code a} --> {@code b}.\n\t */\n\t@Nonnull\n\tpublic static List<Diff> diff(@Nonnull String a, @Nonnull String b) {\n\t\tint[] newlineOffsetsA = computeNewlineOffsets(a);\n\t\tint[] newlineOffsetsB = computeNewlineOffsets(b);\n\t\tList<String> aa = Arrays.asList(StringUtil.splitNewline(a));\n\t\tList<String> bb = Arrays.asList(StringUtil.splitNewline(b));\n\t\tPatch<String> stringPatch = DiffUtils.diff(aa, bb);\n\t\tList<Diff> diffs = new ArrayList<>();\n\t\tfor (AbstractDelta<String> delta : stringPatch.getDeltas()) {\n\t\t\tif (delta.getType() == DeltaType.EQUAL)\n\t\t\t\tcontinue;\n\t\t\tint newlineStartA = delta.getSource().getPosition();\n\t\t\tint newlineStartB = delta.getTarget().getPosition();\n\t\t\tint offsetStartA = newlineOffsetsA[newlineStartA];\n\t\t\tint offsetStartB = newlineOffsetsB[newlineStartB];\n\t\t\tint offsetEndA = newlineOffsetsA[Math.max(newlineStartA + 1, delta.getSource().last() + 1)];\n\t\t\tint offsetEndB = newlineOffsetsB[Math.max(newlineStartB + 1, delta.getTarget().last() + 1)];\n\t\t\tString contentA = a.substring(offsetStartA, offsetEndA);\n\t\t\tString contentB = b.substring(offsetStartB, offsetEndB);\n\n\t\t\t// Shrink difference by trimming out the common prefix and suffix.\n\t\t\tString commonPrefix = StringUtil.getCommonPrefix(contentA, contentB);\n\t\t\tif (!commonPrefix.isEmpty()) {\n\t\t\t\tint common = commonPrefix.length();\n\t\t\t\toffsetStartA += common;\n\t\t\t\toffsetStartB += common;\n\t\t\t\tcontentA = contentA.substring(common);\n\t\t\t\tcontentB = contentB.substring(common);\n\t\t\t}\n\t\t\tString commonSuffix = StringUtil.getCommonSuffix(contentA, contentB);\n\t\t\tif (!commonSuffix.isEmpty()) {\n\t\t\t\tint common = commonSuffix.length();\n\t\t\t\toffsetEndA -= common;\n\t\t\t\toffsetEndB -= common;\n\t\t\t\tcontentA = contentA.substring(0, contentA.length() - common);\n\t\t\t\tcontentB = contentB.substring(0, contentB.length() - common);\n\t\t\t}\n\n\t\t\t// Compute the diff type. It will have changed from what JGit reports if our\n\t\t\t// shrinking has shrunk either 'A' or 'B' to an empty string.\n\t\t\tDiffType type = DiffType.from(delta.getType());\n\t\t\tif (contentA.isEmpty() && !contentB.isEmpty())\n\t\t\t\ttype = DiffType.INSERT;\n\t\t\telse if (!contentA.isEmpty() && contentB.isEmpty())\n\t\t\t\ttype = DiffType.REMOVE;\n\n\t\t\t// Add our mapped diff.\n\t\t\tdiffs.add(new Diff(type, offsetStartA, offsetStartB, offsetEndA, offsetEndB, contentA, contentB));\n\t\t}\n\t\treturn diffs;\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tText to scan.\n\t *\n\t * @return Offsets of newline separators, with included cases for the start and end of\n\t * the string even if {@code \"\\n\"} does not prefix or suffix the string.\n\t */\n\t@Nonnull\n\tprivate static int[] computeNewlineOffsets(@Nonnull String text) {\n\t\t// Find existing newline offsets.\n\t\tint[] offsets = StringUtil.indicesOf(text, '\\n');\n\n\t\tint textLength = text.length();\n\t\tif (offsets.length == 0) {\n\t\t\t// Base cases for strings without newlines.\n\t\t\treturn text.isEmpty() ? new int[]{0} : new int[]{0, textLength};\n\t\t} else {\n\t\t\t// Ensure we start with a newline offset.\n\t\t\tif (offsets[0] != 0) {\n\t\t\t\tint[] newOffsets = new int[offsets.length + 1];\n\t\t\t\tnewOffsets[0] = 0;\n\t\t\t\tSystem.arraycopy(offsets, 0, newOffsets, 1, offsets.length);\n\t\t\t\toffsets = newOffsets;\n\t\t\t}\n\n\t\t\t// Ensure we end with a newline offset.\n\t\t\tif (offsets[offsets.length - 1] != textLength - 1) {\n\t\t\t\tint[] newOffsets = new int[offsets.length + 1];\n\t\t\t\tnewOffsets[offsets.length] = textLength;\n\t\t\t\tSystem.arraycopy(offsets, 0, newOffsets, 0, offsets.length);\n\t\t\t\toffsets = newOffsets;\n\t\t\t}\n\t\t}\n\n\t\treturn offsets;\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tDiff type.\n\t * @param startA\n\t * \t\tStart position of original text.\n\t * @param startB\n\t * \t\tStart position of modified text.\n\t * @param endA\n\t * \t\tEnd position of original text.\n\t * @param endB\n\t * \t\tEnd position of modified text.\n\t * @param textA\n\t * \t\tOriginal text in range.\n\t * @param textB\n\t * \t\tModified text in range.\n\t *\n\t * @author Matt Coley\n\t */\n\tpublic record Diff(@Nonnull DiffType type, int startA, int startB, int endA, int endB,\n\t                   @Nonnull String textA, @Nonnull String textB) {\n\t\t/**\n\t\t * Compare diffs by start positions.\n\t\t */\n\t\tpublic static Comparator<Diff> COMPARE_BY_START_OFFSET = (da, db) -> {\n\t\t\tint cmp = Integer.compare(da.startA, db.startA);\n\t\t\tif (cmp == 0)\n\t\t\t\tcmp = -Integer.compare(da.endA, db.endA);\n\t\t\tif (cmp == 0)\n\t\t\t\tcmp = da.textA.compareTo(db.textB);\n\t\t\treturn cmp;\n\t\t};\n\n\t\t/**\n\t\t * @return {@code true} when {@link #type()} is {@link DiffType#CHANGE}.\n\t\t */\n\t\tpublic boolean isReplace() {\n\t\t\treturn type == DiffType.CHANGE;\n\t\t}\n\n\t\t/**\n\t\t * @return {@code true} when {@link #type()} is {@link DiffType#REMOVE}.\n\t\t */\n\t\tpublic boolean isRemoval() {\n\t\t\treturn type == DiffType.REMOVE;\n\t\t}\n\n\t\t/**\n\t\t * @return {@code true} when {@link #type()} is {@link DiffType#INSERT}.\n\t\t */\n\t\tpublic boolean isInsert() {\n\t\t\treturn type == DiffType.INSERT;\n\t\t}\n\n\t\t/**\n\t\t * @return {@code true} when {@link #type()} is {@link DiffType#EMPTY}.\n\t\t */\n\t\tpublic boolean isEmpty() {\n\t\t\treturn type == DiffType.EMPTY;\n\t\t}\n\n\t\t/**\n\t\t * @param pos\n\t\t * \t\tPosition to check.\n\t\t *\n\t\t * @return {@code true} when {@code pos} is in the range {@code [startA, endA)}.\n\t\t */\n\t\tpublic boolean inRangeA(int pos) {\n\t\t\treturn pos >= startA && pos < endA;\n\t\t}\n\n\t\t/**\n\t\t * @param pos\n\t\t * \t\tPosition to check.\n\t\t *\n\t\t * @return {@code true} when {@code pos} is in the range {@code [startB, endB)}.\n\t\t */\n\t\tpublic boolean inRangeB(int pos) {\n\t\t\treturn pos >= startB && pos < endB;\n\t\t}\n\n\t\t/**\n\t\t * @param inputA\n\t\t * \t\tFull text for input \"A\".\n\t\t *\n\t\t * @return Patched text to conform to input \"B\".\n\t\t */\n\t\t@Nonnull\n\t\tpublic String apply(@Nonnull String inputA) {\n\t\t\tString prefix = inputA.substring(0, startA);\n\t\t\tString suffix = inputA.substring(endA);\n\t\t\treturn prefix + textB + suffix;\n\t\t}\n\n\t\t/**\n\t\t * @param inputA\n\t\t * \t\tFull text for input \"A\".\n\t\t * @param diffs\n\t\t * \t\tSeries of diffs to apply.\n\t\t *\n\t\t * @return Patched text to conform to input \"B\".\n\t\t */\n\t\t@Nonnull\n\t\tpublic static String apply(@Nonnull String inputA, @Nonnull Collection<Diff> diffs) {\n\t\t\tList<Diff> sortedDiffs = diffs.stream().sorted(COMPARE_BY_START_OFFSET).toList();\n\t\t\tfor (int i = sortedDiffs.size() - 1; i >= 0; i--)\n\t\t\t\tinputA = sortedDiffs.get(i).apply(inputA);\n\t\t\treturn inputA;\n\t\t}\n\t}\n\n\t/**\n\t * Type of diff.\n\t *\n\t * @author Matt Coley\n\t */\n\tpublic enum DiffType {\n\t\tCHANGE,\n\t\tINSERT,\n\t\tREMOVE,\n\t\tEMPTY;\n\n\t\t/**\n\t\t * @param type\n\t\t * \t\tDelta type.\n\t\t *\n\t\t * @return Our diff type.\n\t\t */\n\t\t@Nonnull\n\t\tpublic static DiffType from(@Nonnull DeltaType type) {\n\t\t\treturn switch (type) {\n\t\t\t\tcase INSERT -> INSERT;\n\t\t\t\tcase DELETE -> REMOVE;\n\t\t\t\tcase CHANGE -> CHANGE;\n\t\t\t\tcase EQUAL -> EMPTY;\n\t\t\t};\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/StringUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport it.unimi.dsi.fastutil.chars.Char2IntArrayMap;\nimport it.unimi.dsi.fastutil.chars.Char2IntMap;\nimport it.unimi.dsi.fastutil.ints.IntArrayList;\nimport it.unimi.dsi.fastutil.ints.IntList;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport regexodus.Matcher;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.nio.charset.Charset;\nimport java.nio.charset.CharsetDecoder;\nimport java.nio.charset.CoderResult;\nimport java.nio.charset.CodingErrorAction;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.text.DecimalFormat;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Random;\n\n/**\n * Various utilities for {@link String} manipulation.\n *\n * @author Matt Coley\n */\npublic class StringUtil {\n\tpublic static final String[] EMPTY_STRING_ARRAY = new String[0];\n\tprivate static final int[] EMPTY_INT_ARRAY = new int[0];\n\tprivate static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat(\"#\");\n\n\tstatic {\n\t\tDECIMAL_FORMAT.setMaximumFractionDigits(10);\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to print.\n\t *\n\t * @return Whole number toString form of the given number.\n\t */\n\t@Nonnull\n\tpublic static String toString(double value) {\n\t\treturn DECIMAL_FORMAT.format(value);\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to print.\n\t *\n\t * @return Whole number toString form of the given number.\n\t */\n\t@Nonnull\n\tpublic static String toString(float value) {\n\t\treturn DECIMAL_FORMAT.format(value);\n\t}\n\n\t/**\n\t * @param input\n\t * \t\tSome text to search.\n\t * @param c\n\t * \t\tChar to get index of in the input string.\n\t *\n\t * @return Array of indices in the input string where the char exists.\n\t */\n\tpublic static int[] indicesOf(@Nonnull String input, char c) {\n\t\tIntList list = new IntArrayList(5);\n\t\tint i = -1;\n\t\tdo {\n\t\t\ti = input.indexOf(c, i);\n\t\t\tif (i >= 0) {\n\t\t\t\tlist.add(i);\n\t\t\t\ti++;\n\t\t\t}\n\t\t} while (i >= 0);\n\t\treturn list.toArray(EMPTY_INT_ARRAY);\n\t}\n\n\t/**\n\t * @param input\n\t * \t\tSome text to split.\n\t * @param includeEmpty\n\t *        {@code true} to include empty strings.\n\t * @param split\n\t * \t\tSplit character.\n\t *\n\t * @return List of strings between the split characters.\n\t */\n\t@Nonnull\n\tpublic static List<String> fastSplit(@Nonnull String input, boolean includeEmpty, char split) {\n\t\tStringBuilder sb = new StringBuilder();\n\t\tArrayList<String> words = new ArrayList<>(input.length() / 5);\n\t\tchar[] strArray = input.toCharArray();\n\t\tfor (char c : strArray) {\n\t\t\tif (c == split) {\n\t\t\t\tif ((includeEmpty || !sb.isEmpty())) words.add(sb.toString());\n\t\t\t\tsb.setLength(0);\n\t\t\t} else {\n\t\t\t\tsb.append(c);\n\t\t\t}\n\t\t}\n\t\tif ((includeEmpty || !sb.isEmpty())) words.add(sb.toString());\n\t\treturn words;\n\t}\n\n\t/**\n\t * @param input\n\t * \t\tSome text to split by all non-identifier <i>({@link Character#isJavaIdentifierPart(char)})</i> characters.\n\t *\n\t * @return List of strings between the split characters.\n\t */\n\t@Nonnull\n\tpublic static List<String> fastSplitNonIdentifier(@Nonnull String input) {\n\t\tStringBuilder sb = new StringBuilder();\n\t\tArrayList<String> words = new ArrayList<>(input.length() / 5);\n\t\tchar[] strArray = input.toCharArray();\n\t\tfor (char c : strArray) {\n\t\t\tif (!Character.isJavaIdentifierPart(c)) {\n\t\t\t\tif (!sb.isEmpty()) words.add(sb.toString());\n\t\t\t\tsb.setLength(0);\n\t\t\t} else {\n\t\t\t\tsb.append(c);\n\t\t\t}\n\t\t}\n\t\tif (!sb.isEmpty()) words.add(sb.toString());\n\t\treturn words;\n\t}\n\n\t/**\n\t * @param input\n\t * \t\tSome text containing newlines.\n\t *\n\t * @return Input split by newline.\n\t */\n\t@Nonnull\n\tpublic static String[] splitNewline(@Nonnull String input) {\n\t\treturn input.split(\"\\n\\r|\\r\\n|\\n\");\n\t}\n\n\t/**\n\t * @param input\n\t * \t\tSome text containing newlines.\n\t *\n\t * @return Input split by newline.\n\t * Empty lines <i>(Containing only the newline split)</i> are omitted.\n\t */\n\t@Nonnull\n\tpublic static String[] splitNewlineSkipEmpty(@Nonnull String input) {\n\t\tString[] split = input.split(\"[\\r\\n]+\");\n\t\t// If the first line of the file is a newline split will still have\n\t\t// one blank entry at the start.\n\t\tif (split.length > 1 && split[0].isEmpty())\n\t\t\treturn Arrays.copyOfRange(split, 1, split.length);\n\t\treturn split;\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tInput text.\n\t * @param cutoff\n\t * \t\tIndex to match up to.\n\t *\n\t * @return Input text, up until the cut-off length.\n\t */\n\t@Nonnull\n\tpublic static String cutOff(@Nonnull String text, int cutoff) {\n\t\tif (text.length() > cutoff)\n\t\t\treturn text.substring(0, cutoff);\n\t\treturn text;\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tInput text.\n\t * @param cutoff\n\t * \t\tCharacter to match up to.\n\t *\n\t * @return Input text, up until the first cutoff sequence.\n\t */\n\t@Nonnull\n\tpublic static String cutOffAtFirst(@Nonnull String text, char cutoff) {\n\t\tint i = text.indexOf(cutoff);\n\t\tif (i < 0) return text;\n\t\treturn text.substring(0, i);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tInput text.\n\t * @param cutoff\n\t * \t\tSequence to match up to.\n\t *\n\t * @return Input text, up until the first cutoff sequence.\n\t */\n\t@Nonnull\n\tpublic static String cutOffAtFirst(@Nonnull String text, @Nonnull String cutoff) {\n\t\tint i = text.indexOf(cutoff);\n\t\tif (i < 0) return text;\n\t\treturn text.substring(0, i);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tInput text.\n\t * @param cutoff\n\t * \t\tCharacter to match up to.\n\t *\n\t * @return Input text, up until the last cutoff sequence.\n\t */\n\t@Nonnull\n\tpublic static String cutOffAtLast(@Nonnull String text, char cutoff) {\n\t\tint i = text.lastIndexOf(cutoff);\n\t\tif (i < 0) return text;\n\t\treturn text.substring(0, i);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tInput text.\n\t * @param cutoff\n\t * \t\tSequence to match up to.\n\t *\n\t * @return Input text, up until the last cutoff sequence.\n\t */\n\t@Nonnull\n\tpublic static String cutOffAtLast(@Nonnull String text, @Nonnull String cutoff) {\n\t\tint i = text.lastIndexOf(cutoff);\n\t\tif (i < 0) return text;\n\t\treturn text.substring(0, i);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tInput text.\n\t * @param cutoff\n\t * \t\tCharacter to match up to.\n\t * @param n\n\t * \t\tTimes to match the character.\n\t *\n\t * @return Input text, up until the last cutoff sequence.\n\t */\n\t@Nonnull\n\tpublic static String cutOffAtNth(@Nonnull String text, char cutoff, int n) {\n\t\tint i = -1;\n\t\twhile (n-- > 0) {\n\t\t\tif ((i = text.indexOf(cutoff, i + 1)) < 0)\n\t\t\t\treturn text;\n\t\t}\n\t\tif (i < 0) return text;\n\t\treturn text.substring(0, i);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tInput text.\n\t * @param cutoff\n\t * \t\tCharacter to match up to.\n\t * @param n\n\t * \t\tTimes to match the character.\n\t *\n\t * @return Input text, up until the last cutoff sequence.\n\t */\n\t@Nonnull\n\tpublic static String cutOffAtNth(@Nonnull String text, String cutoff, int n) {\n\t\tint i = -1;\n\t\twhile (n-- > 0) {\n\t\t\tif ((i = text.indexOf(cutoff, i + 1)) < 0)\n\t\t\t\treturn text;\n\t\t}\n\t\tif (i < 0) return text;\n\t\treturn text.substring(0, i);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tInput text.\n\t * @param after\n\t * \t\tSequence to find and use as a cut-off point.\n\t *\n\t * @return Input text, after the occurrence of the given pattern.\n\t */\n\t@Nonnull\n\tpublic static String getAfter(@Nonnull String text, @Nonnull String after) {\n\t\tint i = text.lastIndexOf(after);\n\t\tif (i < 0) return text;\n\t\treturn text.substring(i + after.length());\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tInput text.\n\t * @param insertIndex\n\t * \t\tPosition to insert into.\n\t * @param insertion\n\t * \t\tText to insert.\n\t *\n\t * @return Modified text.\n\t */\n\t@Nonnull\n\tpublic static String insert(@Nonnull String text, int insertIndex, @Nullable String insertion) {\n\t\tif (insertion == null || insertion.isEmpty() || insertIndex < 0 || insertIndex > text.length()) return text;\n\t\tString pre = text.substring(0, insertIndex);\n\t\tString post = text.substring(insertIndex);\n\t\treturn pre + insertion + post;\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tInput text.\n\t * @param removeIndex\n\t * \t\tPosition to remove at.\n\t * @param removeLength\n\t * \t\tLength of removal.\n\t *\n\t * @return Modified text.\n\t */\n\t@Nonnull\n\tpublic static String remove(@Nonnull String text, int removeIndex, int removeLength) {\n\t\tif (removeIndex < 0 || removeIndex >= text.length()) return text;\n\t\tString pre = text.substring(0, removeIndex);\n\t\tString post = text.substring(Math.min(removeIndex + removeLength, text.length()));\n\t\treturn pre + post;\n\t}\n\n\t/**\n\t * @param defaultText\n\t * \t\tText to return.\n\t * @param fallback\n\t * \t\tFallback to use if the default text is blank.\n\t *\n\t * @return Text or fallback if text is empty.\n\t */\n\t@Nonnull\n\tpublic static String withEmptyFallback(@Nullable String defaultText, @Nonnull String fallback) {\n\t\tif (defaultText == null || defaultText.trim().isBlank())\n\t\t\treturn fallback;\n\t\treturn defaultText;\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tText to check.\n\t *\n\t * @return {@code true} when the given text represents a decimal number.\n\t */\n\tpublic static boolean isDecimal(@Nullable String text) {\n\t\tif (text == null || text.isEmpty())\n\t\t\treturn false;\n\t\ttry {\n\t\t\tDouble.parseDouble(text);\n\t\t\treturn true;\n\t\t} catch (NumberFormatException ex) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Replace the last match of some text in the given string.\n\t *\n\t * @param string\n\t * \t\tText containing items to replace.\n\t * @param toReplace\n\t * \t\tPattern to match.\n\t * @param replacement\n\t * \t\tText to replace pattern with.\n\t *\n\t * @return Modified string.\n\t */\n\t@Nonnull\n\tpublic static String replaceLast(@Nonnull String string, @Nonnull String toReplace, @Nonnull String replacement) {\n\t\tint i = string.lastIndexOf(toReplace);\n\t\tif (i > -1)\n\t\t\treturn string.substring(0, i) + replacement + string.substring(i + toReplace.length());\n\t\treturn string;\n\t}\n\n\t/**\n\t * @param string\n\t * \t\tText containing prefix to replace.\n\t * @param oldPrefix\n\t * \t\tOld prefix.\n\t * @param newPrefix\n\t * \t\tNew prefix to replace with.\n\t *\n\t * @return Modified string.\n\t */\n\t@Nonnull\n\tpublic static String replacePrefix(@Nonnull String string, @Nullable String oldPrefix, @Nonnull String newPrefix) {\n\t\tif (isNullOrEmpty(oldPrefix))\n\t\t\treturn newPrefix + string;\n\t\tif (string.startsWith(oldPrefix))\n\t\t\treturn newPrefix + string.substring(oldPrefix.length());\n\t\treturn string;\n\t}\n\n\t/**\n\t * @param string\n\t * \t\tText with range to replace.\n\t * @param start\n\t * \t\tRange start.\n\t * @param end\n\t * \t\tRange end.\n\t * @param replacement\n\t * \t\tReplacement text to replace the contents of the given range.\n\t *\n\t * @return Modified string.\n\t */\n\t@Nonnull\n\tpublic static String replaceRange(@Nonnull String string, int start, int end, String replacement) {\n\t\tString temp = string.substring(0, start);\n\t\ttemp += replacement;\n\t\ttemp += string.substring(end);\n\t\treturn temp;\n\t}\n\n\t/**\n\t * @param pattern\n\t * \t\tPattern to look for.\n\t * @param text\n\t * \t\tText to check.\n\t *\n\t * @return Number of times the given pattern appears in the text.\n\t */\n\tpublic static int count(@Nonnull String pattern, @Nullable String text) {\n\t\tif (text == null || text.isEmpty())\n\t\t\treturn 0;\n\t\tint count = 0;\n\t\tint patternIndex = text.indexOf(pattern);\n\t\twhile (patternIndex >= 0) {\n\t\t\tpatternIndex = text.indexOf(pattern, patternIndex + 1);\n\t\t\tcount++;\n\t\t}\n\t\treturn count;\n\t}\n\n\t/**\n\t * @param pattern\n\t * \t\tRegex pattern to look for.\n\t * @param text\n\t * \t\tText to check.\n\t *\n\t * @return Number of times the given pattern appears in the text.\n\t */\n\tpublic static int countRegex(@Nonnull String pattern, @Nullable String text) {\n\t\tif (text == null || text.isEmpty())\n\t\t\treturn 0;\n\t\tint count = 0;\n\t\tMatcher matcher = RegexUtil.pattern(pattern).matcher(text);\n\t\twhile (matcher.find())\n\t\t\tcount++;\n\t\treturn count;\n\t}\n\n\t/**\n\t * @param pattern\n\t * \t\tPattern to look for.\n\t * @param text\n\t * \t\tText to check.\n\t *\n\t * @return Number of times the given pattern appears in the text.\n\t */\n\tpublic static int count(char pattern, @Nullable String text) {\n\t\tif (text == null || text.isEmpty())\n\t\t\treturn 0;\n\t\tint count = 0;\n\t\tint length = text.length();\n\t\tfor (int i = 0; i < length; i++) {\n\t\t\tif (text.charAt(i) == pattern) count++;\n\t\t}\n\t\treturn count;\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tSome path.\n\t *\n\t * @return String of path.\n\t */\n\t@Nonnull\n\tpublic static String pathToString(@Nonnull Path path) {\n\t\treturn path.toString();\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tSome path.\n\t *\n\t * @return String of file name at path.\n\t */\n\t@Nonnull\n\tpublic static String pathToNameString(@Nonnull Path path) {\n\t\treturn path.getFileName().toString();\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tSome path.\n\t *\n\t * @return String of absolute path.\n\t */\n\t@Nonnull\n\tpublic static String pathToAbsoluteString(@Nonnull Path path) {\n\t\tString absolutePath = path.toAbsolutePath().toString();\n\t\tif (absolutePath.indexOf('\\\\') > 0) // Translate windows-specific path to platform independent path name\n\t\t\tabsolutePath = absolutePath.replace('\\\\', '/');\n\t\treturn absolutePath;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tPath name to shorten.\n\t *\n\t * @return Shortened name.\n\t */\n\t@Nonnull\n\tpublic static String shortenPath(@Nonnull String name) {\n\t\tint separatorIndex = name.lastIndexOf('/');\n\t\tif (separatorIndex > 0)\n\t\t\tname = name.substring(separatorIndex + 1);\n\t\treturn name;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tPath name.\n\t *\n\t * @return Path name without file extension.\n\t */\n\t@Nonnull\n\tpublic static String removeExtension(@Nonnull String name) {\n\t\tint dotIndex = name.lastIndexOf('.');\n\t\tif (dotIndex >= 0)\n\t\t\treturn name.substring(0, dotIndex);\n\t\treturn name;\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tInput text.\n\t * @param max\n\t * \t\tMax length of text.\n\t *\n\t * @return Text cut off to max length.\n\t */\n\t@Nonnull\n\tpublic static String limit(@Nonnull String text, int max) {\n\t\treturn limit(text, null, max);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tInput text.\n\t * @param cutoffPattern\n\t * \t\tText to append to text that gets limited.\n\t * @param max\n\t * \t\tMax length of text.\n\t *\n\t * @return Text cut off to max length.\n\t */\n\t@Nonnull\n\tpublic static String limit(@Nonnull String text, @Nullable String cutoffPattern, int max) {\n\t\tif (max <= 0) return \"\";\n\t\tif (text.length() > max) {\n\t\t\tString s = text.substring(0, max);\n\t\t\tif (cutoffPattern != null)\n\t\t\t\ts += cutoffPattern;\n\t\t\treturn s;\n\t\t}\n\t\treturn text;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tText to lowercase the first letter of.\n\t *\n\t * @return Text with first letter lower-cased.\n\t */\n\t@Nonnull\n\tpublic static String lowercaseFirstChar(@Nonnull String name) {\n\t\tint len = name.length();\n\t\tif (len == 1)\n\t\t\treturn name.toLowerCase();\n\t\telse if (len > 1)\n\t\t\treturn name.substring(0, 1).toLowerCase() + name.substring(1);\n\t\telse\n\t\t\treturn name;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tText to uppercase the first letter of.\n\t *\n\t * @return Text with first letter upper-cased.\n\t */\n\t@Nonnull\n\tpublic static String uppercaseFirstChar(@Nonnull String name) {\n\t\tint len = name.length();\n\t\tif (len == 1)\n\t\t\treturn name.toUpperCase();\n\t\telse if (len > 1)\n\t\t\treturn name.substring(0, 1).toUpperCase() + name.substring(1);\n\t\telse\n\t\t\treturn name;\n\t}\n\n\t/**\n\t * @param a\n\t * \t\tSome string.\n\t * @param b\n\t * \t\tAnother.\n\t *\n\t * @return The common prefix between the two strings.\n\t */\n\t@Nonnull\n\tpublic static String getCommonPrefix(@Nonnull String a, @Nonnull String b) {\n\t\tint len = Math.min(a.length(), b.length());\n\t\tfor (int i = 0; i < len; i++) {\n\t\t\tif (a.charAt(i) != b.charAt(i)) {\n\t\t\t\treturn a.substring(0, i);\n\t\t\t}\n\t\t}\n\t\treturn a.substring(0, len);\n\t}\n\n\t/**\n\t * @param a\n\t * \t\tSome string.\n\t * @param b\n\t * \t\tAnother.\n\t *\n\t * @return The common suffix between the two strings.\n\t */\n\t@Nonnull\n\tpublic static String getCommonSuffix(@Nonnull String a, @Nonnull String b) {\n\t\tint alen = a.length();\n\t\tint blen = b.length();\n\t\tint commonLength = Math.min(alen, blen);\n\n\t\t// Base case where one string is empty.\n\t\tif (commonLength == 0)\n\t\t\treturn \"\";\n\n\t\t// Find fist non-equal char and get the suffix string.\n\t\tfor (int i = 1; i < commonLength + 1; i++) {\n\t\t\tif (a.charAt(alen - i) != b.charAt(blen - i)) {\n\t\t\t\treturn a.substring(alen - i + 1);\n\t\t\t}\n\t\t}\n\n\t\t// For the shared common length both strings are equal.\n\t\t// Yield the shorter string.\n\t\treturn alen < blen ? a : b;\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tText to compute length of.\n\t *\n\t * @return Length of text, considering tabs.\n\t */\n\tpublic static int getTabAdjustedLength(@Nonnull String text) {\n\t\treturn getTabAdjustedLength(text, 4);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tText to compute length of.\n\t * @param tabWidth\n\t * \t\tTab width.\n\t *\n\t * @return Length of text, considering tabs.\n\t */\n\tpublic static int getTabAdjustedLength(@Nonnull String text, int tabWidth) {\n\t\tint tabIndex = text.indexOf('\\t');\n\t\twhile (tabIndex >= 0) {\n\t\t\tif (tabIndex == 0) {\n\t\t\t\ttext = \" \".repeat(tabWidth) + text.substring(1);\n\t\t\t} else {\n\t\t\t\t// Assuming tab width is four: Having two spaces then a tab still yields a length of 4 visually\n\t\t\t\t// Thus, we must consider such alignment in our length adjustments.\n\t\t\t\tString pre = text.substring(0, tabIndex);\n\t\t\t\tString post = text.substring(tabIndex + 1);\n\t\t\t\tint alignedLengthExtra = tabWidth - (tabIndex % tabWidth);\n\t\t\t\ttext = pre + \" \".repeat(alignedLengthExtra) + post;\n\t\t\t}\n\t\t\ttabIndex = text.indexOf('\\t');\n\t\t}\n\t\treturn text.length();\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tText to scan prefix of.\n\t *\n\t * @return Number of blank spaces until some non-whitespace char is found.\n\t */\n\tpublic static int getWhitespacePrefixLength(@Nonnull String text) {\n\t\treturn getWhitespacePrefixLength(text, 4);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tText to scan prefix of.\n\t * @param tabWidth\n\t * \t\tWidth of spaces to translate tab characters to.\n\t *\n\t * @return Number of blank spaces until some non-whitespace char is found.\n\t */\n\tpublic static int getWhitespacePrefixLength(@Nonnull String text, int tabWidth) {\n\t\tchar[] chars = text.toCharArray();\n\t\tint offset = 0;\n\t\tfor (char c : chars) {\n\t\t\tif (c == ' ' || c == '\\t') offset++;\n\t\t\telse break;\n\t\t}\n\t\treturn getTabAdjustedLength(text.substring(0, offset), tabWidth);\n\t}\n\n\t/**\n\t * @param len\n\t * \t\tTarget string length.\n\t * @param pattern\n\t * \t\tPattern to fill with.\n\t * @param string\n\t * \t\tInitial string.\n\t *\n\t * @return String with pattern filling up to the desired length on the left.\n\t */\n\t@Nonnull\n\tpublic static String fillLeft(int len, @Nonnull String pattern, @Nullable String string) {\n\t\tStringBuilder sb = new StringBuilder(string == null ? \"\" : string);\n\t\twhile (sb.length() < len)\n\t\t\tsb.insert(0, pattern);\n\t\treturn sb.toString();\n\t}\n\n\t/**\n\t * @param len\n\t * \t\tTarget string length.\n\t * @param pattern\n\t * \t\tPattern to fill with.\n\t * @param string\n\t * \t\tInitial string.\n\t *\n\t * @return String with pattern filling up to the desired length on the right.\n\t */\n\t@Nonnull\n\tpublic static String fillRight(int len, @Nonnull String pattern, @Nullable String string) {\n\t\tStringBuilder sb = new StringBuilder(string == null ? \"\" : string);\n\t\twhile (sb.length() < len)\n\t\t\tsb.append(pattern);\n\t\treturn sb.toString();\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tText to word-wrap.\n\t * @param length\n\t * \t\tMax line length.\n\t *\n\t * @return String with max length enforced.\n\t */\n\t@Nonnull\n\tpublic static String wordWrap(@Nonnull String text, int length) {\n\t\tStringBuilder sb = new StringBuilder();\n\t\tStringBuilder line = new StringBuilder();\n\t\tStringBuilder word = new StringBuilder();\n\n\t\t// Add a trailing '\\n' so we cleanup wrapping logic for the last line in the text.\n\t\tchar[] chars = (text + '\\n').toCharArray();\n\t\tfor (char c : chars) {\n\t\t\tif (Character.isWhitespace(c)) {\n\t\t\t\t// Skip this, we only operate on newlines.\n\t\t\t\tif (c == '\\r') continue;\n\n\t\t\t\t// Append word\n\t\t\t\tif (!word.isEmpty()) {\n\t\t\t\t\tString wordStr = word.toString();\n\t\t\t\t\tword.setLength(0);\n\t\t\t\t\tint newLineLength = line.length() + wordStr.length();\n\t\t\t\t\tif (newLineLength >= length) {\n\t\t\t\t\t\tsb.append(line.toString().trim()).append('\\n');\n\t\t\t\t\t\tline.setLength(0);\n\t\t\t\t\t}\n\t\t\t\t\tline.append(wordStr);\n\t\t\t\t}\n\n\t\t\t\t// Edge case handling for newlines\n\t\t\t\tif (c == '\\n') {\n\t\t\t\t\tif (line.isEmpty()) {\n\t\t\t\t\t\tsb.append('\\n');\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsb.append(line.toString().trim()).append('\\n');\n\t\t\t\t\t\tline.setLength(0);\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Append whitespace\n\t\t\t\tif (line.length() >= length) {\n\t\t\t\t\tsb.append(line.toString().trim()).append('\\n');\n\t\t\t\t\tline.setLength(0);\n\t\t\t\t} else {\n\t\t\t\t\tline.append(c);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tword.append(c);\n\t\t\t}\n\t\t}\n\t\treturn sb.toString().trim();\n\t}\n\n\t/**\n\t * @param args\n\t * \t\tInput arguments.\n\t *\n\t * @return {@code true} when any of the arguments is {@code null} or an empty string.\n\t */\n\tpublic static boolean isAnyNullOrEmpty(String... args) {\n\t\tfor (String arg : args)\n\t\t\tif (StringUtil.isNullOrEmpty(arg))\n\t\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tText to check.\n\t *\n\t * @return {@code true} when the text is either {@code null} or an empty string.\n\t */\n\tpublic static boolean isNullOrEmpty(String text) {\n\t\treturn text == null || text.isEmpty();\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tInput text.\n\t *\n\t * @return Entropy of the characters in the text.\n\t */\n\tpublic static double getEntropy(@Nullable String text) {\n\t\tif (text == null || text.isEmpty())\n\t\t\treturn 0;\n\t\tChar2IntMap charCountMap = new Char2IntArrayMap(64);\n\t\tfor (char c : text.toCharArray())\n\t\t\tcharCountMap.mergeInt(c, 1, Integer::sum);\n\t\tint length = text.length();\n\t\tfinal double[] entropy = {0.0};\n\t\tcharCountMap.values().forEach(value -> {\n\t\t\tdouble freq = ((double) value) / length;\n\t\t\tentropy[0] -= freq * (Math.log(freq) / Math.log(2));\n\t\t});\n\t\treturn entropy[0];\n\t}\n\n\t/**\n\t * @param t\n\t * \t\tThrowable error.\n\t *\n\t * @return Stacktrace as string.\n\t */\n\t@Nonnull\n\tpublic static String traceToString(@Nonnull Throwable t) {\n\t\tStringWriter trace = new StringWriter();\n\t\tt.printStackTrace(new PrintWriter(trace));\n\t\treturn trace.toString();\n\t}\n\n\t/**\n\t * Wrapper for {@link Integer#toHexString(int)} that pads zeros when needed.\n\t *\n\t * @param value\n\t * \t\tValue to print.\n\t *\n\t * @return Hex printed value.\n\t */\n\t@Nonnull\n\tpublic static String toHexString(int value) {\n\t\treturn toHexString(value, -1, 1);\n\t}\n\n\t/**\n\t * Wrapper for {@link Integer#toHexString(int)} that pads zeros when needed.\n\t *\n\t * @param value\n\t * \t\tValue to print.\n\t * @param mask\n\t * \t\tMask to apply to the value.\n\t *\n\t * @return Hex printed value.\n\t */\n\t@Nonnull\n\tpublic static String toHexString(int value, int mask) {\n\t\treturn toHexString(value, mask, 1);\n\t}\n\n\t/**\n\t * Wrapper for {@link Integer#toHexString(int)} that pads zeros when needed.\n\t *\n\t * @param value\n\t * \t\tValue to print.\n\t * @param mask\n\t * \t\tMask to apply to the value.\n\t * @param minLength\n\t * \t\tMinimum length of hex output. Left-padded with zeros to match.\n\t *\n\t * @return Hex printed value.\n\t */\n\t@Nonnull\n\tpublic static String toHexString(int value, int mask, int minLength) {\n\t\treturn StringUtil.fillLeft(minLength, \"0\", Integer.toHexString(value & mask));\n\t}\n\n\t/**\n\t * Creates a string incrementing in numerical value.\n\t * Example: a, b, c, ... z, aa, ab ...\n\t *\n\t * @param alphabet\n\t * \t\tThe alphabet to pull from.\n\t * @param index\n\t * \t\tName index.\n\t *\n\t * @return Generated String\n\t */\n\t@Nonnull\n\tpublic static String generateIncrementingName(@Nonnull String alphabet, int index) {\n\t\tchar[] charz = alphabet.toCharArray();\n\t\tint alphabetLength = charz.length;\n\t\tint length = 8;\n\t\tfinal char[] array = new char[length];\n\t\tint offset = length - 1;\n\t\twhile (index > charz.length - 1) {\n\t\t\tint k = Math.abs(-(index % alphabetLength));\n\t\t\tarray[offset--] = charz[k];\n\t\t\tindex /= alphabetLength;\n\t\t\tindex -= 1;\n\t\t}\n\t\tarray[offset] = charz[index];\n\t\treturn new String(array, offset, length - offset);\n\t}\n\n\t/**\n\t * Creates pseudo-random names of the desired length.\n\t * Repeated calls with the same input yield the same value.\n\t *\n\t * @param alphabet\n\t * \t\tThe alphabet to pull from.\n\t * @param length\n\t * \t\tDesired length of generated string.\n\t * @param seed\n\t * \t\tInput seed.\n\t *\n\t * @return Generated name.\n\t */\n\t@Nonnull\n\tpublic static String generateName(@Nonnull String alphabet, int length, int seed) {\n\t\tRandom r = new Random(seed);\n\t\tStringBuilder sb = new StringBuilder();\n\t\twhile (sb.length() < length)\n\t\t\tsb.append(alphabet.charAt(r.nextInt(alphabet.length())));\n\t\treturn sb.toString();\n\t}\n\n\t/**\n\t * @param data\n\t * \t\tSome data to decode.\n\t *\n\t * @return String decoding result. Check {@link StringDecodingResult#couldDecode()} to determine if successful.\n\t */\n\t@Nonnull\n\tpublic static StringDecodingResult decodeString(@Nonnull byte[] data) {\n\t\t// It would be wrong to say we know this data is supposed to be text if its empty.\n\t\t// It's up to the caller to use context clues like file extensions to figure this case out.\n\t\tif (data.length == 0)\n\t\t\treturn failedDecoding(data);\n\n\t\t// Check for byte-order-mark\n\t\tStringDecodingResult result = null;\n\t\tif (data.length >= 4) {\n\t\t\tif (ByteHeaderUtil.match(data, ByteHeaderUtil.TEXT_BOM_UTF_32BE)) {\n\t\t\t\tresult = decodeString(data, StandardCharsets.UTF_32BE);\n\t\t\t} else if (ByteHeaderUtil.match(data, ByteHeaderUtil.TEXT_BOM_UTF_32LE)) {\n\t\t\t\tresult = decodeString(data, StandardCharsets.UTF_32LE);\n\t\t\t} else if (ByteHeaderUtil.match(data, ByteHeaderUtil.TEXT_BOM_UTF_16BE)) {\n\t\t\t\tresult = decodeString(data, StandardCharsets.UTF_16BE);\n\t\t\t} else if (ByteHeaderUtil.match(data, ByteHeaderUtil.TEXT_BOM_UTF_16LE)) {\n\t\t\t\tresult = decodeString(data, StandardCharsets.UTF_16BE);\n\t\t\t} else if (ByteHeaderUtil.match(data, ByteHeaderUtil.TEXT_BOM_UTF_8)) {\n\t\t\t\tresult = decodeString(data, StandardCharsets.UTF_8);\n\t\t\t}\n\n\t\t\t// If that BOM specifies a charset that works then we're good to go.\n\t\t\tif (result != null && result.couldDecode())\n\t\t\t\treturn result;\n\t\t}\n\n\t\t// From 'https://stackoverflow.com/questions/3584069/is-it-possible-to-detect-text-file-encoding-of-two-possible'\n\t\t//\n\t\t// It's not possible with 100% accuracy because, for example, the bytes C3 B1 are an equally valid\n\t\t// representation of \"Ăą\" in ISO-8859-2 as they are of \"ñ\" in UTF-8. In fact, because ISO-8859-2 assigns\n\t\t// a character to all 256 possible bytes, every UTF-8 string is also a valid ISO-8859-2 string\n\t\t// (representing different characters if non-ASCII).\n\t\t//\n\t\t// However, the converse is not true. UTF-8 has strict rules about what sequences are valid.\n\t\t// More than 99% of possible 8-octet sequences are not valid UTF-8. And your CSV files are probably much\n\t\t// longer than that. Because of this, you can get good accuracy if you:\n\t\t//\n\t\t//  1. Perform a UTF-8 validity check. If it passes, assume the data is UTF-8.\n\t\t//  2. Otherwise, assume it's ISO-8859 (Latin).\n\t\tresult = decodeUtf8(data);\n\t\tif (result.couldDecode())\n\t\t\treturn result;\n\t\treturn decodeLatin(data);\n\t}\n\n\t/**\n\t * @param data\n\t * \t\tSome data to decode.\n\t *\n\t * @return String decoding result. Check {@link StringDecodingResult#couldDecode()} to determine if successful.\n\t */\n\t@Nonnull\n\tpublic static StringDecodingResult decodeUtf8(@Nonnull byte[] data) {\n\t\treturn decodeString(data, StandardCharsets.UTF_8);\n\t}\n\n\t/**\n\t * @param data\n\t * \t\tSome data to decode.\n\t *\n\t * @return String decoding result. Check {@link StringDecodingResult#couldDecode()} to determine if successful.\n\t */\n\t@Nonnull\n\t@SuppressWarnings(\"DataFlowIssue\")\n\tpublic static StringDecodingResult decodeLatin(@Nonnull byte[] data) {\n\t\tStringDecodingResult result = decodeString(data, StandardCharsets.ISO_8859_1);\n\n\t\t// ISO_8859_1 should be a mapping of one byte to one char, and if it is not we throw it out as being invalid.\n\t\t//  - Chars < 32 will be discarded since they are special control chars and not indented for display, triggering this.\n\t\tString text = result.text();\n\t\tif (result.couldDecode() && data.length != text.length())\n\t\t\treturn failedDecoding(data);\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * @param data\n\t * \t\tSome data to decode.\n\t * @param encoding\n\t * \t\tEncoding to decode with.\n\t *\n\t * @return String decoding result. Check {@link StringDecodingResult#couldDecode()} to determine if successful.\n\t */\n\t@Nonnull\n\tpublic static StringDecodingResult decodeString(@Nonnull byte[] data, @Nonnull Charset encoding) {\n\t\tint bytesToChars = 1;\n\t\tif (encoding == StandardCharsets.UTF_8)\n\t\t\tbytesToChars = 2;\n\t\telse if (encoding == StandardCharsets.UTF_16 || encoding == StandardCharsets.UTF_16BE || encoding == StandardCharsets.UTF_16LE)\n\t\t\tbytesToChars = 2;\n\t\telse if (encoding == StandardCharsets.UTF_32 || encoding == StandardCharsets.UTF_32BE || encoding == StandardCharsets.UTF_32LE)\n\t\t\tbytesToChars = 4;\n\t\treturn decodingResult(data, encoding, bytesToChars);\n\t}\n\n\t/**\n\t * @param data\n\t * \t\tSome data to decode.\n\t * @param encoding\n\t * \t\tEncoding to decode with.\n\t * @param bytesToChars\n\t * \t\tExpected number of bytes to make up a single {@code char} in the decoded output.\n\t * \t\tStrictly used to reduce calls to {@link StringBuilder#ensureCapacity(int)}.\n\t *\n\t * @return String decoding result. Check {@link StringDecodingResult#couldDecode()} to determine if successful.\n\t */\n\t@Nonnull\n\tprivate static StringDecodingResult decodingResult(@Nonnull byte[] data, @Nonnull Charset encoding, int bytesToChars) {\n\t\tCharsetDecoder decoder = encoding.newDecoder()\n\t\t\t\t.onMalformedInput(CodingErrorAction.REPORT)\n\t\t\t\t.onUnmappableCharacter(CodingErrorAction.REPORT);\n\t\tByteBuffer buffer = ByteBuffer.wrap(data);\n\t\tint totalChars = 0;\n\t\tint totalTextChars = 0;\n\t\tint length = data.length;\n\t\tint bufferSize = Math.min(length, 4096);\n\t\tchar[] charArray = new char[bufferSize];\n\t\tCharBuffer charBuf = CharBuffer.wrap(charArray);\n\t\tStringBuilder output = new StringBuilder(data.length / bytesToChars);\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\t// Exit when no remaining chars to decode\n\t\t\t\tif (!buffer.hasRemaining())\n\t\t\t\t\tbreak;\n\n\t\t\t\t// Decode next chunk into buffer\n\t\t\t\tCoderResult result = decoder.decode(buffer, charBuf, true);\n\t\t\t\tif (result.isMalformed() || result.isError() || result.isUnmappable())\n\t\t\t\t\treturn failedDecoding(data);\n\t\t\t\tif (result.isUnderflow())\n\t\t\t\t\tdecoder.flush(charBuf);\n\n\t\t\t\t// Check each character\n\t\t\t\tint textChars = 0;\n\t\t\t\tint arrayEnd = charBuf.position();\n\t\t\t\tfor (int i = 0; i < arrayEnd; i++) {\n\t\t\t\t\tchar c = charArray[i];\n\t\t\t\t\tint type = Character.getType(c);\n\t\t\t\t\tboolean isTextChar = switch (type) {\n\t\t\t\t\t\tcase Character.CONTROL -> (c == '\\n' || c == '\\r');\n\t\t\t\t\t\tcase Character.FORMAT -> EscapeUtil.isWhitespaceChar(c);\n\t\t\t\t\t\tcase Character.PRIVATE_USE, Character.SURROGATE, Character.UNASSIGNED -> false;\n\t\t\t\t\t\tdefault -> true;\n\t\t\t\t\t};\n\t\t\t\t\tif (isTextChar) textChars++;\n\t\t\t\t}\n\t\t\t\ttotalTextChars += textChars;\n\t\t\t\toutput.append(charArray, 0, arrayEnd);\n\t\t\t\ttotalChars += arrayEnd;\n\n\t\t\t\t// If ay any point we see more than half of the temporary buffer full of non-text characters\n\t\t\t\t// we're going to assume the rest of the content is also going to be garbage.\n\t\t\t\tif (((double) textChars / arrayEnd) <= 0.5)\n\t\t\t\t\treturn failedDecoding(data);\n\n\t\t\t\t// If we overflowed in our result, we still have more to decode with this\n\t\t\t\t// current input buffer, so we should clear the output buffer and continue as-is.\n\t\t\t\tif (result.isOverflow()) {\n\t\t\t\t\tcharBuf.flip();\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Ensure buffer position increments to next place, but does not exceed the wrapped array's length.\n\t\t\t\tbuffer.position(Math.min(length, totalChars));\n\n\t\t\t\t// Reset the char-buffer contents.\n\t\t\t\tcharBuf.flip();\n\n\t\t\t\t// If we underflowed in our result we are most likely done.\n\t\t\t\tif (result.isUnderflow())\n\t\t\t\t\tbreak;\n\t\t\t} catch (Exception ex) {\n\t\t\t\treturn failedDecoding(data);\n\t\t\t}\n\t\t}\n\n\t\t// This isn't great but works well enough for now.1\n\t\t// Basically, if most of the content is text we'll call it text even if there is some that isn't totally valid.\n\t\tif (((double) totalTextChars / totalChars) > 0.9)\n\t\t\treturn new StringDecodingResult(data, encoding, output.toString());\n\t\treturn failedDecoding(data);\n\t}\n\n\t@Nonnull\n\tprivate static StringDecodingResult failedDecoding(@Nonnull byte[] data) {\n\t\treturn new StringDecodingResult(data, null, null);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/TestEnvironment.java",
    "content": "package software.coley.recaf.util;\n\n/**\n * Util for testing.\n * <br>\n * Some behaviors may be omitted when under a test environment.\n *\n * @author Matt Coley\n */\npublic class TestEnvironment {\n\tprivate static final String KEY = \"TEST-ENV\";\n\tprivate static boolean isTest;\n\tprivate static boolean checked;\n\n\t/**\n\t * Apply key.\n\t */\n\tpublic static void initTestEnv() {\n\t\tSystem.setProperty(KEY, \"\");\n\t}\n\n\t/**\n\t * @return {@code true} when we are in a test environment.\n\t */\n\tpublic static boolean isTestEnv() {\n\t\t// Only check once\n\t\tif (!checked) {\n\t\t\tisTest = System.getProperty(KEY) != null;\n\t\t\tchecked = true;\n\t\t}\n\t\treturn isTest;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/Types.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.util.CheckClassAdapter;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * A wrapper around {@link org.objectweb.asm.Type}.\n *\n * @author Matt Coley\n */\npublic class Types {\n\tpublic static final Type OBJECT_TYPE = Type.getObjectType(\"java/lang/Object\");\n\tpublic static final Type CLASS_TYPE = Type.getObjectType(\"java/lang/Class\");\n\tpublic static final Type STRING_TYPE = Type.getObjectType(\"java/lang/String\");\n\tpublic static final Type ARRAY_1D_BOOLEAN = Type.getObjectType(\"[Z\");\n\tpublic static final Type ARRAY_1D_CHAR = Type.getObjectType(\"[C\");\n\tpublic static final Type ARRAY_1D_BYTE = Type.getObjectType(\"[B\");\n\tpublic static final Type ARRAY_1D_SHORT = Type.getObjectType(\"[S\");\n\tpublic static final Type ARRAY_1D_INT = Type.getObjectType(\"[I\");\n\tpublic static final Type ARRAY_1D_FLOAT = Type.getObjectType(\"[F\");\n\tpublic static final Type ARRAY_1D_DOUBLE = Type.getObjectType(\"[D\");\n\tpublic static final Type ARRAY_1D_LONG = Type.getObjectType(\"[J\");\n\tpublic static final Type ARRAY_1D_OBJECT = Type.getObjectType(\"[Ljava/lang/Object;\");\n\tpublic static final Type ARRAY_1D_STRING = Type.getObjectType(\"[Ljava/lang/String;\");\n\tpublic static final Type[] PRIMITIVES = new Type[]{\n\t\t\tType.VOID_TYPE,\n\t\t\tType.BOOLEAN_TYPE,\n\t\t\tType.BYTE_TYPE,\n\t\t\tType.CHAR_TYPE,\n\t\t\tType.SHORT_TYPE,\n\t\t\tType.INT_TYPE,\n\t\t\tType.FLOAT_TYPE,\n\t\t\tType.DOUBLE_TYPE,\n\t\t\tType.LONG_TYPE\n\t};\n\tpublic static final Collection<String> PRIMITIVE_BOXES = Arrays.asList(\n\t\t\t\"Ljava/lang/Boolean;\",\n\t\t\t\"Ljava/lang/Byte;\",\n\t\t\t\"Ljava/lang/Character;\",\n\t\t\t\"Ljava/lang/Short;\",\n\t\t\t\"Ljava/lang/Integer;\",\n\t\t\t\"Ljava/lang/Float;\",\n\t\t\t\"Ljava/lang/Double;\",\n\t\t\t\"Ljava/lang/Long;\"\n\t);\n\n\t/**\n\t * @param type\n\t * \t\tSome type to check.\n\t *\n\t * @return {@code true} if it matches a primitive type.\n\t */\n\tpublic static boolean isPrimitive(@Nullable Type type) {\n\t\treturn type != null && type.getSort() <= Type.DOUBLE;\n\t}\n\n\t/**\n\t * @param desc\n\t * \t\tSome internal type descriptor.\n\t *\n\t * @return {@code true} if it matches a reserved primitive type.\n\t */\n\tpublic static boolean isPrimitive(@Nullable String desc) {\n\t\tif (desc == null || desc.length() != 1)\n\t\t\treturn false;\n\t\tchar c = desc.charAt(0);\n\t\treturn switch (c) {\n\t\t\tcase 'V', 'Z', 'C', 'B', 'S', 'I', 'F', 'J', 'D' -> true;\n\t\t\tdefault -> false;\n\t\t};\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tMust be a primitive class name. See {@link #isPrimitiveClassName(String)}.\n\t *\n\t * @return Internal name of primitive.\n\t *\n\t * @throws IllegalArgumentException\n\t * \t\tWhen the descriptor was not a primitive.\n\t */\n\t@Nonnull\n\tpublic static String classToPrimitive(@Nonnull String name) {\n\t\tfor (Type prim : PRIMITIVES) {\n\t\t\tString className = prim.getClassName();\n\t\t\tif (className.equals(name))\n\t\t\t\treturn prim.getInternalName();\n\t\t}\n\t\tthrow new IllegalArgumentException(\"Descriptor was not a primitive class name!\");\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tSome class name.\n\t *\n\t * @return {@code true} if it matches the class name of a primitive type.\n\t */\n\tpublic static boolean isPrimitiveClassName(@Nullable String name) {\n\t\tif (name == null)\n\t\t\treturn false;\n\t\tfor (Type prim : PRIMITIVES)\n\t\t\tif (prim.getClassName().equals(name))\n\t\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param desc\n\t * \t\tClass descriptor.\n\t *\n\t * @return {@code true} if it is one of the children of {@link Number}.\n\t */\n\tpublic static boolean isBoxedPrimitive(@Nullable String desc) {\n\t\treturn PRIMITIVE_BOXES.contains(desc);\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tSome type to check.\n\t *\n\t * @return {@code true} if type is a void type.\n\t */\n\tpublic static boolean isVoid(@Nullable Type type) {\n\t\treturn type != null && type.getSort() == Type.VOID;\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tBase type.\n\t * @param dimensions\n\t * \t\tArray dimensions.\n\t *\n\t * @return Array type of dimension size.\n\t */\n\t@Nonnull\n\tpublic static Type array(@Nonnull Type type, int dimensions) {\n\t\treturn Type.getType(\"[\".repeat(dimensions) + type.getDescriptor());\n\t}\n\n\t/**\n\t * @param arrayType\n\t * \t\tSome array type.\n\t *\n\t * @return The type when removing one dimension from the array.\n\t */\n\t@Nonnull\n\tpublic static Type undimension(@Nonnull Type arrayType) {\n\t\tif (arrayType.getSort() != Type.ARRAY)\n\t\t\tthrow new IllegalStateException(\"Not an array: \" + arrayType);\n\t\treturn Type.getType(arrayType.getDescriptor().substring(1));\n\t}\n\n\t/**\n\t * @param methodType\n\t * \t\tParsed method descriptor type.\n\t *\n\t * @return Number of variable slots occupied by the parameters.\n\t */\n\tpublic static int countParameterSlots(@Nonnull Type methodType) {\n\t\tint size = 0;\n\t\tType[] methodArgs = methodType.getArgumentTypes();\n\t\tfor (Type arg : methodArgs)\n\t\t\tsize += arg.getSize();\n\t\treturn size;\n\t}\n\n\t/**\n\t * ASM likes to throw {@link IllegalArgumentException} in cases where it can't parse type descriptors.\n\t * This lets us check beforehand if its valid.\n\t *\n\t * @param desc\n\t * \t\tDescriptor to check.\n\t *\n\t * @return {@code true} when its parsable.\n\t */\n\t@SuppressWarnings(\"all\")\n\tpublic static boolean isValidDesc(@Nullable String desc) {\n\t\tif (desc == null)\n\t\t\treturn false;\n\t\tif (desc.length() == 0)\n\t\t\treturn false;\n\t\tchar first = desc.charAt(0);\n\t\tif (first == '(') {\n\t\t\ttry {\n\t\t\t\tType methodType = Type.getMethodType(desc);\n\t\t\t\tmethodType.getArgumentTypes();\n\t\t\t\tmethodType.getReturnType();\n\t\t\t\treturn true;\n\t\t\t} catch (Throwable t) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else if (first == 'L' || first == '[') {\n\t\t\ttry {\n\t\t\t\tType type = Type.getType(desc);\n\t\t\t\tif (type.getSort() == Type.OBJECT && !desc.endsWith(\";\"))\n\t\t\t\t\treturn false;\n\t\t\t\telse if (type.getSort() == Type.ARRAY && type.getElementType() == null)\n\t\t\t\t\treturn false;\n\t\t\t\treturn true;\n\t\t\t} catch (Throwable t) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tType to check.\n\t *\n\t * @return {@code true} if it is a wide type.\n\t */\n\tpublic static boolean isWide(@Nullable Type type) {\n\t\tif (type == null) return false;\n\t\treturn Type.DOUBLE_TYPE.equals(type) || Type.LONG_TYPE.equals(type);\n\t}\n\n\t/**\n\t * @param opcode\n\t * \t\tSome instruction opcode.\n\t *\n\t * @return The implied variable type, or {@code null} if the passed opcode does not imply a type.\n\t */\n\t@Nullable\n\tpublic static Type fromVarOpcode(int opcode) {\n\t\treturn switch (opcode) {\n\t\t\tcase Opcodes.IINC, Opcodes.ILOAD, Opcodes.ISTORE -> Type.INT_TYPE;\n\t\t\tcase Opcodes.ALOAD, Opcodes.ASTORE -> Types.OBJECT_TYPE;\n\t\t\tcase Opcodes.FLOAD, Opcodes.FSTORE -> Type.FLOAT_TYPE;\n\t\t\tcase Opcodes.DLOAD, Opcodes.DSTORE -> Type.DOUBLE_TYPE;\n\t\t\tcase Opcodes.LLOAD, Opcodes.LSTORE -> Type.LONG_TYPE;\n\t\t\tdefault -> null;\n\t\t};\n\t}\n\n\t/**\n\t * @param opcode\n\t * \t\tSome array opcode.\n\t *\n\t * @return The implied variable type, or {@code null} if the passed opcode does not imply a type.\n\t */\n\t@Nullable\n\tpublic static Type fromArrayOpcode(int opcode) {\n\t\treturn switch (opcode) {\n\t\t\tcase Opcodes.ARRAYLENGTH, Opcodes.BALOAD, Opcodes.CALOAD, Opcodes.SALOAD, Opcodes.IALOAD,\n\t\t\t     Opcodes.BASTORE, Opcodes.CASTORE, Opcodes.SASTORE, Opcodes.IASTORE -> Type.INT_TYPE;\n\t\t\tcase Opcodes.AALOAD, Opcodes.AASTORE -> Types.OBJECT_TYPE;\n\t\t\tcase Opcodes.FALOAD, Opcodes.FASTORE -> Type.FLOAT_TYPE;\n\t\t\tcase Opcodes.DALOAD, Opcodes.DASTORE -> Type.DOUBLE_TYPE;\n\t\t\tcase Opcodes.LALOAD, Opcodes.LASTORE -> Type.LONG_TYPE;\n\t\t\tdefault -> null;\n\t\t};\n\t}\n\n\t/**\n\t * @param sort\n\t * \t\tType sort.\n\t *\n\t * @return Primitive type for the given sort, or {@link #OBJECT_TYPE} if not a primitive.\n\t */\n\t@Nonnull\n\tpublic static Type fromSort(int sort) {\n\t\treturn switch (sort) {\n\t\t\tcase Type.VOID -> Type.VOID_TYPE;\n\t\t\tcase Type.BOOLEAN -> Type.BOOLEAN_TYPE;\n\t\t\tcase Type.CHAR -> Type.CHAR_TYPE;\n\t\t\tcase Type.BYTE -> Type.BYTE_TYPE;\n\t\t\tcase Type.SHORT -> Type.SHORT_TYPE;\n\t\t\tcase Type.INT -> Type.INT_TYPE;\n\t\t\tcase Type.FLOAT -> Type.FLOAT_TYPE;\n\t\t\tcase Type.LONG -> Type.LONG_TYPE;\n\t\t\tcase Type.DOUBLE -> Type.DOUBLE_TYPE;\n\t\t\tdefault -> OBJECT_TYPE;\n\t\t};\n\t}\n\n\t/**\n\t * @param sort\n\t * \t\tSome type sort.\n\t *\n\t * @return Normalized sort. This is in the context of runtime expectations.\n\t * Any type smaller than {@code int} is treated as an {@code int}.\n\t * Array types are essentially drop-in replaceable with object types in most cases.\n\t */\n\tpublic static int getNormalizedSort(int sort) {\n\t\tif (sort == Type.ARRAY)\n\t\t\tsort = Type.OBJECT;\n\t\telse if (sort > 0 && sort < Type.INT)\n\t\t\tsort = Type.INT;\n\t\treturn sort;\n\t}\n\n\t/**\n\t * @param sort\n\t *        {@link Type#getSort()}.\n\t *\n\t * @return Name of sort.\n\t */\n\t@Nonnull\n\tpublic static String getSortName(int sort) {\n\t\treturn switch (sort) {\n\t\t\tcase Type.VOID -> \"void\";\n\t\t\tcase Type.BOOLEAN -> \"boolean\";\n\t\t\tcase Type.CHAR -> \"char\";\n\t\t\tcase Type.BYTE -> \"byte\";\n\t\t\tcase Type.SHORT -> \"short\";\n\t\t\tcase Type.INT -> \"int\";\n\t\t\tcase Type.FLOAT -> \"float\";\n\t\t\tcase Type.LONG -> \"long\";\n\t\t\tcase Type.DOUBLE -> \"double\";\n\t\t\tcase Type.ARRAY -> \"array\";\n\t\t\tcase Type.OBJECT -> \"object\";\n\t\t\tcase Type.METHOD -> \"method\";\n\t\t\tcase -1 -> \"<undefined>\";\n\t\t\tdefault -> \"<unknown>\";\n\t\t};\n\t}\n\n\t/**\n\t * @param descriptor\n\t * \t\tInput descriptor.\n\t *\n\t * @return Pretty-printed type.\n\t */\n\t@Nonnull\n\tpublic static String pretty(@Nonnull String descriptor) {\n\t\ttry {\n\t\t\tType type;\n\t\t\tif (descriptor.charAt(0) == '(') {\n\t\t\t\ttype = Type.getMethodType(descriptor);\n\t\t\t} else {\n\t\t\t\ttype = Type.getType(descriptor);\n\t\t\t}\n\t\t\treturn pretty(type);\n\n\t\t} catch (Throwable t) {\n\t\t\t// Invalid descriptor, return as-is.\n\t\t\treturn descriptor;\n\t\t}\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tInput type.\n\t *\n\t * @return Pretty-printed type.\n\t */\n\t@Nonnull\n\tpublic static String pretty(@Nonnull Type type) {\n\t\tint sort = type.getSort();\n\t\tString suffix = null;\n\t\tString name;\n\t\tif (sort == Type.ARRAY) {\n\t\t\tsuffix = \"[]\".repeat(type.getDimensions());\n\t\t\ttype = type.getElementType();\n\t\t\tsort = type.getSort();\n\t\t} else if (sort == Type.METHOD) {\n\t\t\tList<String> args = Arrays.stream(type.getArgumentTypes())\n\t\t\t\t\t.map(Types::pretty)\n\t\t\t\t\t.toList();\n\t\t\treturn \"(\" + String.join(\", \", args) + \") \" + pretty(type.getReturnType());\n\t\t}\n\t\tif (sort <= Type.DOUBLE) {\n\t\t\tname = getSortName(sort);\n\t\t} else {\n\t\t\tname = type.getInternalName();\n\t\t}\n\t\tString pretty = StringUtil.shortenPath(name);\n\t\tif (suffix != null) {\n\t\t\tpretty += suffix;\n\t\t}\n\t\treturn pretty;\n\t}\n\n\t/**\n\t * @param signature\n\t * \t\tClass declaration signature.\n\t *\n\t * @return {@code true} for a valid signatures, or {@code null}. Will be {@code false} otherwise.\n\t */\n\tpublic static boolean isValidClassSignature(@Nullable String signature) {\n\t\treturn isValidSignature(signature, SignatureContext.CLASS);\n\t}\n\n\t/**\n\t * @param signature\n\t * \t\tField or variable signature.\n\t *\n\t * @return {@code true} for a valid signatures, or {@code null}. Will be {@code false} otherwise.\n\t */\n\tpublic static boolean isValidFieldSignature(@Nullable String signature) {\n\t\treturn isValidSignature(signature, SignatureContext.FIELD);\n\t}\n\n\n\t/**\n\t * @param signature\n\t * \t\tMethod signature.\n\t *\n\t * @return {@code true} for a valid signatures, or {@code null}. Will be {@code false} otherwise.\n\t */\n\tpublic static boolean isValidMethodSignature(@Nullable String signature) {\n\t\treturn isValidSignature(signature, SignatureContext.METHOD);\n\t}\n\n\t/**\n\t * @param signature\n\t * \t\tSignature contents.\n\t * @param context\n\t * \t\tSignature usage context.\n\t *\n\t * @return {@code true} for a valid signatures, or {@code null}. Will be {@code false} otherwise.\n\t */\n\tpublic static boolean isValidSignature(@Nullable String signature, @Nonnull SignatureContext context) {\n\t\tif (signature == null)\n\t\t\treturn true;\n\t\tif (signature.isEmpty())\n\t\t\treturn false;\n\t\ttry {\n\t\t\tswitch (context) {\n\t\t\t\tcase CLASS -> CheckClassAdapter.checkClassSignature(signature);\n\t\t\t\tcase FIELD -> CheckClassAdapter.checkFieldSignature(signature);\n\t\t\t\tcase METHOD -> CheckClassAdapter.checkMethodSignature(signature);\n\t\t\t}\n\t\t\treturn true;\n\t\t} catch (Throwable t) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Types of signature use-cases.\n\t *\n\t * @see #isValidSignature(String, SignatureContext)\n\t */\n\tpublic enum SignatureContext {\n\t\t/**\n\t\t * Class declarations.\n\t\t */\n\t\tCLASS,\n\t\t/**\n\t\t * Fields or variable declarations.\n\t\t */\n\t\tFIELD,\n\t\t/**\n\t\t * Method declarations.\n\t\t */\n\t\tMETHOD\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/UnsafeIO.java",
    "content": "package software.coley.recaf.util;\n\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\n\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles.Lookup;\n\n/**\n * Unsafe IO utilities.\n *\n * @author xDark\n */\npublic final class UnsafeIO {\n\tprivate static final Logger logger = Logging.get(UnsafeIO.class);\n\tprivate static final MethodHandle BR_SET_BUFFER;\n\tprivate static final MethodHandle BW_SET_BUFFER;\n\tprivate static final MethodHandle BW_SET_NCHARS;\n\n\t/**\n\t * Deny all constructions.\n\t */\n\tprivate UnsafeIO() {\n\t}\n\n\t/**\n\t * Updates underlying buffer of a given {@link BufferedReader}.\n\t *\n\t * @param reader\n\t * \t\tReader to update.\n\t * @param buffer\n\t * \t\tBuffer to set.\n\t */\n\tpublic static void setReaderBuffer(BufferedReader reader, char[] buffer) {\n\t\ttry {\n\t\t\tBR_SET_BUFFER.invokeExact(reader, buffer);\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Failed to set buffer for reader\", t);\n\t\t\tthrow new AssertionError(t);\n\t\t}\n\t}\n\n\t/**\n\t * Updates underlying buffer of a given {@link BufferedWriter}.\n\t *\n\t * @param writer\n\t * \t\tWriter to update.\n\t * @param buffer\n\t * \t\tBuffer to set.\n\t */\n\tpublic static void setWriterBuffer(BufferedWriter writer, char[] buffer) {\n\t\ttry {\n\t\t\tBW_SET_BUFFER.invokeExact(writer, buffer);\n\t\t\tBW_SET_NCHARS.invokeExact(writer, buffer.length);\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Failed to set buffer for writer\", t);\n\t\t\tthrow new AssertionError(t);\n\t\t}\n\t}\n\n\tstatic {\n\t\ttry {\n\t\t\tLookup lookup = ReflectUtil.lookup();\n\t\t\tBR_SET_BUFFER = lookup.findSetter(BufferedReader.class, \"cb\", char[].class);\n\t\t\tBW_SET_BUFFER = lookup.findSetter(BufferedWriter.class, \"cb\", char[].class);\n\t\t\tBW_SET_NCHARS = lookup.findSetter(BufferedWriter.class, \"nChars\", Integer.TYPE);\n\t\t} catch (NoSuchFieldException | IllegalAccessException ex) {\n\t\t\tthrow new ExceptionInInitializerError(ex);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/UnsafeUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport sun.misc.Unsafe;\n\nimport java.lang.reflect.Field;\n\n/**\n * Util to access {@link Unsafe}.\n *\n * @author xDark\n */\npublic class UnsafeUtil {\n\tprivate static final Unsafe UNSAFE;\n\n\t/**\n\t * @return The unsafe instance.\n\t */\n\t@Nonnull\n\tpublic static Unsafe get() {\n\t\treturn UNSAFE;\n\t}\n\n\tstatic {\n\t\ttry {\n\t\t\tUnsafe unsafe = null;\n\t\t\tfor (Field field : Unsafe.class.getDeclaredFields()) {\n\t\t\t\tif (Unsafe.class == field.getType()) {\n\t\t\t\t\tfield.setAccessible(true);\n\t\t\t\t\tunsafe = (Unsafe) field.get(null);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (unsafe == null)\n\t\t\t\tthrow new IllegalStateException(\"Unable to locate unsafe instance\");\n\t\t\tUNSAFE = unsafe;\n\t\t} catch (IllegalAccessException ex) {\n\t\t\tthrow new ExceptionInInitializerError(ex);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/ZipCreationUtils.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.slf4j.Logger;\nimport software.coley.collections.func.UncheckedConsumer;\nimport software.coley.recaf.analytics.logging.Logging;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.nio.file.attribute.FileTime;\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\nimport java.util.zip.CRC32;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipOutputStream;\n\n/**\n * Utility for creating simple ZIP files.\n *\n * @author Matt Coley\n */\npublic class ZipCreationUtils {\n\tprivate static final byte[] EMPTY = new byte[0];\n\tprivate static final Logger logger = Logging.get(ZipCreationUtils.class);\n\n\t/**\n\t * @param name\n\t * \t\tEntry name.\n\t * @param content\n\t * \t\tEntry value.\n\t *\n\t * @return ZIP bytes, containing the single entry.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the content cannot be written.\n\t */\n\tpublic static byte[] createSingleEntryZip(String name, byte[] content) throws IOException {\n\t\treturn createZip(zos -> {\n\t\t\tzos.putNextEntry(new ZipEntry(name));\n\t\t\tzos.write(content);\n\t\t\tzos.closeEntry();\n\t\t});\n\t}\n\n\t/**\n\t * @param entryMap\n\t * \t\tMap of entry name --> contents.\n\t *\n\t * @return ZIP bytes, containing given entries.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the content cannot be written.\n\t */\n\tpublic static byte[] createZip(Map<String, byte[]> entryMap) throws IOException {\n\t\treturn createZip(zos -> {\n\t\t\tfor (Map.Entry<String, byte[]> entry : entryMap.entrySet()) {\n\t\t\t\tzos.putNextEntry(new ZipEntry(entry.getKey()));\n\t\t\t\tzos.write(entry.getValue());\n\t\t\t\tzos.closeEntry();\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * @param consumer\n\t * \t\tAction to do on the ZIP stream.\n\t *\n\t * @return ZIP bytes.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the action fails.\n\t */\n\tpublic static byte[] createZip(UncheckedConsumer<ZipOutputStream> consumer) throws IOException {\n\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n\t\ttry (ZipOutputStream zos = new ZipOutputStream(baos)) {\n\t\t\tconsumer.accept(zos);\n\t\t}\n\t\treturn baos.toByteArray();\n\t}\n\n\t/**\n\t * Resetting the tracked names allows you to write duplicate entries to a ZIP file.\n\t *\n\t * @param zos\n\t * \t\tZIP stream to reset name tracking of.\n\t */\n\tprivate static void resetNames(ZipOutputStream zos) {\n\t\ttry {\n\t\t\tField field = ReflectUtil.getDeclaredField(ZipOutputStream.class, \"names\");\n\t\t\tCollection<?> names = (Collection<?>) field.get(zos);\n\t\t\tnames.clear();\n\t\t} catch (Exception ex) {\n\t\t\tlogger.error(\"Failed to reset ZIP name tracking: {}\", zos, ex);\n\t\t}\n\t}\n\n\n\t/**\n\t * @return New ZIP builder.\n\t */\n\tpublic static ZipBuilder builder() {\n\t\treturn new ZipBuilder();\n\t}\n\n\t/**\n\t * Copied from {@code java.util.zip.ZipUtils}.\n\t *\n\t * @param time\n\t * \t\tTime from extra field at some offset.\n\t *\n\t * @return NIO time representation.\n\t */\n\tpublic static FileTime winTimeToFileTime(long time) {\n\t\treturn FileTime.from(time / 10 - 11644473600000000L /* windows epoch */, TimeUnit.MICROSECONDS);\n\t}\n\n\t/**\n\t * Copied from {@code java.util.zip.ZipUtils}.\n\t *\n\t * @param utime\n\t * \t\tTime from extra field at some offset.\n\t *\n\t * @return NIO time representation.\n\t */\n\tpublic static FileTime unixTimeToFileTime(long utime) {\n\t\treturn FileTime.from(utime, TimeUnit.SECONDS);\n\t}\n\n\t/**\n\t * Helper to create zip files.\n\t */\n\tpublic static class ZipBuilder {\n\t\tprivate static final int MAX_DIR_DEPTH = 64;\n\t\tprivate final List<Entry> entries = new ArrayList<>();\n\t\tprivate boolean createDirectories;\n\n\t\t/**\n\t\t * Enables creation of directory entries.\n\t\t *\n\t\t * @return Builder.\n\t\t */\n\t\t@Nonnull\n\t\tpublic ZipBuilder createDirectories() {\n\t\t\tcreateDirectories = true;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * @param name\n\t\t * \t\tEntry name.\n\t\t * @param content\n\t\t * \t\tEntry contents.\n\t\t *\n\t\t * @return Builder.\n\t\t */\n\t\t@Nonnull\n\t\tpublic ZipBuilder add(@Nonnull String name, @Nonnull byte[] content) {\n\t\t\treturn add(name, content, true, null, -1, -1, -1);\n\t\t}\n\n\t\t/**\n\t\t * @param name\n\t\t * \t\tEntry name.\n\t\t * @param content\n\t\t * \t\tEntry contents.\n\t\t * @param compression\n\t\t * \t\tCompression flag.\n\t\t * @param comment\n\t\t * \t\tOptional comment.\n\t\t * @param createTime\n\t\t * \t\tCreation time.\n\t\t * @param modifyTime\n\t\t * \t\tModification time.\n\t\t * @param accessTime\n\t\t * \t\tAccess time.\n\t\t *\n\t\t * @return Builder.\n\t\t */\n\t\t@Nonnull\n\t\tpublic ZipBuilder add(@Nonnull String name, @Nonnull byte[] content, boolean compression,\n\t\t\t\t\t\t\t  @Nullable String comment, long createTime, long modifyTime, long accessTime) {\n\t\t\treturn add(new Entry(name, content, compression, comment, null, createTime, modifyTime, accessTime));\n\t\t}\n\n\t\t/**\n\t\t * @param entry\n\t\t * \t\tEntry to add.\n\t\t *\n\t\t * @return Builder.\n\t\t */\n\t\t@Nonnull\n\t\tprivate ZipBuilder add(@Nonnull Entry entry) {\n\t\t\tentries.add(entry);\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * @return Generated ZIP.\n\t\t *\n\t\t * @throws IOException\n\t\t * \t\tWhen the content cannot be written.\n\t\t */\n\t\t@Nonnull\n\t\tpublic byte[] bytes() throws IOException {\n\t\t\treturn createZip(zos -> {\n\t\t\t\tSet<String> dirsVisited = new HashSet<>();\n\t\t\t\tCRC32 crc = new CRC32();\n\t\t\t\tfor (Entry entry : entries) {\n\t\t\t\t\tString key = entry.name;\n\t\t\t\t\tbyte[] content = entry.content;\n\n\t\t\t\t\t// Write directories for upcoming entries if necessary\n\t\t\t\t\t// - Ugly, but does the job.\n\t\t\t\t\tif (createDirectories && key.contains(\"/\")) {\n\t\t\t\t\t\t// Record directories\n\t\t\t\t\t\tString parent = key;\n\t\t\t\t\t\tList<String> toAdd = new ArrayList<>();\n\t\t\t\t\t\tdo {\n\t\t\t\t\t\t\t// Abort if the max-dir depth is reached.\n\t\t\t\t\t\t\tif (toAdd.size() > MAX_DIR_DEPTH) {\n\t\t\t\t\t\t\t\ttoAdd.clear();\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tparent = parent.substring(0, parent.lastIndexOf('/'));\n\t\t\t\t\t\t\tif (dirsVisited.add(parent)) {\n\t\t\t\t\t\t\t\ttoAdd.add(0, parent + '/');\n\t\t\t\t\t\t\t} else break;\n\t\t\t\t\t\t} while (parent.contains(\"/\"));\n\t\t\t\t\t\t// Put directories in order of depth\n\t\t\t\t\t\tfor (String dir : toAdd) {\n\t\t\t\t\t\t\t// Update CRC\n\t\t\t\t\t\t\tcrc.reset();\n\t\t\t\t\t\t\tcrc.update(EMPTY);\n\n\t\t\t\t\t\t\t// Add the entry\n\t\t\t\t\t\t\t// We use STORED for directories so that the DEFLATE header doesn't clutter the\n\t\t\t\t\t\t\t// LocalFileHeader data store. Using STORE keeps it empty.\n\t\t\t\t\t\t\tZipEntry dirEntry = new ZipEntry(dir);\n\t\t\t\t\t\t\tdirEntry.setSize(0);\n\t\t\t\t\t\t\tdirEntry.setCompressedSize(0);\n\t\t\t\t\t\t\tdirEntry.setMethod(ZipEntry.STORED);\n\t\t\t\t\t\t\tdirEntry.setCrc(crc.getValue());\n\t\t\t\t\t\t\tzos.putNextEntry(dirEntry);\n\t\t\t\t\t\t\tzos.closeEntry();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Update CRC\n\t\t\t\t\tcrc.reset();\n\t\t\t\t\tcrc.update(content);\n\n\t\t\t\t\t// Write ZIP entry\n\t\t\t\t\t//  - Always use STORED for empty files to save space.\n\t\t\t\t\tboolean doStore = entry.content.length == 0 || !entry.compression;\n\t\t\t\t\tint level = doStore ? ZipEntry.STORED : ZipEntry.DEFLATED;\n\t\t\t\t\tZipEntry zipEntry = new ZipEntry(key);\n\t\t\t\t\tzipEntry.setMethod(level);\n\t\t\t\t\tzipEntry.setCrc(crc.getValue());\n\t\t\t\t\tif (doStore) {\n\t\t\t\t\t\tzipEntry.setSize(content.length);\n\t\t\t\t\t\tzipEntry.setCompressedSize(content.length);\n\t\t\t\t\t}\n\t\t\t\t\tif (entry.comment != null) zipEntry.setComment(entry.comment);\n\t\t\t\t\tif (entry.extra != null) zipEntry.setExtra(entry.extra);\n\t\t\t\t\tif (entry.creationTime >= 0L) zipEntry.setCreationTime(FileTime.fromMillis(entry.creationTime));\n\t\t\t\t\tif (entry.modifyTime >= 0L) zipEntry.setLastModifiedTime(FileTime.fromMillis(entry.modifyTime));\n\t\t\t\t\tif (entry.accessTime >= 0L) zipEntry.setLastAccessTime(FileTime.fromMillis(entry.accessTime));\n\n\t\t\t\t\tzos.putNextEntry(zipEntry);\n\t\t\t\t\tzos.write(content);\n\t\t\t\t\tzos.closeEntry();\n\n\t\t\t\t\t// Reset to allow name hacks\n\t\t\t\t\tresetNames(zos);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tpublic static class Entry {\n\t\t\tprivate final String name;\n\t\t\tprivate final byte[] content;\n\t\t\tprivate final boolean compression;\n\t\t\tprivate final String comment;\n\t\t\tprivate final byte[] extra;\n\t\t\tprivate final long creationTime;\n\t\t\tprivate final long modifyTime;\n\t\t\tprivate final long accessTime;\n\n\t\t\tprivate Entry(@Nonnull String name,\n\t\t\t\t\t\t  @Nonnull byte[] content,\n\t\t\t\t\t\t  boolean compression,\n\t\t\t\t\t\t  @Nullable String comment,\n\t\t\t\t\t\t  @Nullable byte[] extra,\n\t\t\t\t\t\t  long creationTime,\n\t\t\t\t\t\t  long modifyTime,\n\t\t\t\t\t\t  long accessTime\n\t\t\t) {\n\t\t\t\tthis.name = name;\n\t\t\t\tthis.content = content;\n\t\t\t\tthis.compression = compression;\n\t\t\t\tthis.comment = comment;\n\t\t\t\tthis.extra = extra;\n\t\t\t\tthis.creationTime = creationTime;\n\t\t\t\tthis.modifyTime = modifyTime;\n\t\t\t\tthis.accessTime = accessTime;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/Branching.java",
    "content": "package software.coley.recaf.util.analysis;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Branching behavior for jumps.\n *\n * @author Matt Coley\n */\npublic enum Branching {\n\tUNKNOWN, TAKEN, NOT_TAKEN;\n\n\t/**\n\t * @return Inverted branching behavior.\n\t */\n\t@Nonnull\n\tpublic Branching invert() {\n\t\treturn switch (this) {\n\t\t\tcase NOT_TAKEN -> TAKEN;\n\t\t\tcase TAKEN -> NOT_TAKEN;\n\t\t\tdefault -> UNKNOWN;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/Nullness.java",
    "content": "package software.coley.recaf.util.analysis;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Nullability state.\n *\n * @author Matt Coley\n */\npublic enum Nullness {\n\tUNKNOWN, NULL, NOT_NULL;\n\n\t/**\n\t * @param other\n\t * \t\tOther value to merge with.\n\t *\n\t * @return Common nullability state.\n\t */\n\t@Nonnull\n\tpublic Nullness mergeWith(@Nonnull Nullness other) {\n\t\tif (this != other) return UNKNOWN;\n\t\treturn this;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/ReAnalyzer.java",
    "content": "package software.coley.recaf.util.analysis;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.JumpInsnNode;\nimport org.objectweb.asm.tree.LabelNode;\nimport org.objectweb.asm.tree.LookupSwitchInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.TableSwitchInsnNode;\nimport org.objectweb.asm.tree.analysis.Analyzer;\nimport org.objectweb.asm.tree.analysis.AnalyzerException;\nimport org.objectweb.asm.tree.analysis.Frame;\nimport org.objectweb.asm.tree.analysis.Interpreter;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\nimport java.util.function.Consumer;\n\n/**\n * Analyzer that takes in an interpreter for {@link ReValue enhanced value types}.\n *\n * @author Matt Coley\n */\npublic class ReAnalyzer extends Analyzer<ReValue> {\n\tprivate final ReInterpreter interpreter;\n\tprivate MethodNode targetMethod;\n\tprivate AbstractInsnNode currentInsn;\n\n\t/**\n\t * @param interpreter\n\t * \t\tEnhanced interpreter.\n\t */\n\tpublic ReAnalyzer(@Nonnull ReInterpreter interpreter) {\n\t\tsuper(interpreter);\n\t\tthis.interpreter = interpreter;\n\t}\n\n\t/**\n\t * @return Interpreter backing this analyzer.\n\t */\n\t@Nonnull\n\tpublic ReInterpreter getInterpreter() {\n\t\treturn interpreter;\n\t}\n\n\t@Override\n\tprotected ReFrame newFrame(int numLocals, int numStack) {\n\t\treturn new ReFrame(this, numLocals, numStack);\n\t}\n\n\t@Override\n\tprotected ReFrame newFrame(@Nonnull Frame<? extends ReValue> frame) {\n\t\treturn new ReFrame(this, frame);\n\t}\n\n\t@Override\n\tprotected void init(@Nullable String owner, @Nonnull MethodNode method) throws AnalyzerException {\n\t\ttargetMethod = method;\n\t\tfillJumpTargetTemplateFrames();\n\t}\n\n\t@Override\n\tpublic Frame<ReValue>[] analyze(@Nonnull String owner, @Nonnull MethodNode method) throws AnalyzerException {\n\t\t// Process the method's frames.\n\t\tFrame<ReValue>[] frames = super.analyze(owner, method);\n\n\t\t// Remove template and frames never branched to.\n\t\tint frameCount = frames.length;\n\t\tfor (int i = 0; i < frameCount; i++) {\n\t\t\tReFrame frame = (ReFrame) frames[i];\n\t\t\tif (frame != null && (frame.isTemplate() || frame.isNeverBranchedTo()))\n\t\t\t\tframes[i] = null;\n\t\t}\n\n\t\treturn frames;\n\t}\n\n\t/**\n\t * Fills in all jump/switch targets with template frames.\n\t * These templates exist so that {@code Analyzer#merge(int, Frame, Subroutine)}\n\t * will call the template frame's {@link Frame#merge(Frame, Interpreter)}\n\t * instead of creating a new frame.\n\t * <p>\n\t * When we control the frame's merge function we can hint to the analyzer\n\t * that we should not process following instructions if merge yields {@code false}.\n\t * With this, we can track when the frames of jump destinations will never be visited\n\t * and then remove those frames once analysis completes. A missing frame implies the\n\t * code is unreachable, which is our desired outcome for jump targets that we know\n\t * for sure will never be visited <i>(like {@code iconst_1, ifeq neverVisited})</i>.\n\t */\n\tprotected void fillJumpTargetTemplateFrames() {\n\t\tInsnList instructions = targetMethod.instructions;\n\t\tFrame<ReValue>[] frames = getFrames();\n\t\tConsumer<AbstractInsnNode> frameFill = insn -> {\n\t\t\tif (insn == null) return;\n\t\t\tint index = instructions.indexOf(insn);\n\t\t\tif (index > 0 && index < frames.length) {\n\t\t\t\tReFrame frame = newFrame(targetMethod.maxLocals, targetMethod.maxStack);\n\t\t\t\tframe.markTemplate();\n\t\t\t\tframes[index] = frame;\n\t\t\t}\n\t\t};\n\t\tfor (AbstractInsnNode instruction : instructions) {\n\t\t\tif (instruction instanceof JumpInsnNode jin) {\n\t\t\t\tframeFill.accept(jin.label);\n\t\t\t\tint opcode = jin.getOpcode();\n\t\t\t\tif (opcode != GOTO && opcode != JSR)\n\t\t\t\t\tframeFill.accept(jin.getNext());\n\t\t\t} else if (instruction instanceof TableSwitchInsnNode tswitch) {\n\t\t\t\tframeFill.accept(tswitch.dflt);\n\t\t\t\ttswitch.labels.forEach(frameFill);\n\t\t\t} else if (instruction instanceof LookupSwitchInsnNode lswitch) {\n\t\t\t\tframeFill.accept(lswitch.dflt);\n\t\t\t\tlswitch.labels.forEach(frameFill);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Called by {@link ReFrame#execute(AbstractInsnNode, Interpreter)}.\n\t *\n\t * @param insn\n\t * \t\tCurrent instruction being executed.\n\t */\n\tprotected void notifyCurrentInsn(@Nonnull AbstractInsnNode insn) {\n\t\tcurrentInsn = insn;\n\t}\n\n\t/**\n\t * Called by {@link ReFrame#initJumpTarget(int, LabelNode)}.\n\t *\n\t * @param target\n\t * \t\tThe target of the jump, or {@code null} for contol flow fall-through.\n\t * @param branching\n\t * \t\tThe branching behavior of the jump.\n\t * \t\tCan be {@link Branching#TAKEN} or {@link Branching#NOT_TAKEN} for cases where we know for sure,\n\t * \t\tor {@link Branching#UNKNOWN} for cases where we do not know for sure.\n\t */\n\tprotected void notifyJumpVisited(@Nullable LabelNode target, @Nonnull Branching branching) {\n\t\tif (target == null) {\n\t\t\t// Get target frame index for fall-through.\n\t\t\tAbstractInsnNode next = currentInsn.getNext();\n\t\t\tif (next == null)\n\t\t\t\treturn;\n\t\t\tint targetIndex = targetMethod.instructions.indexOf(next);\n\t\t\tif (targetIndex < 0)\n\t\t\t\treturn;\n\n\t\t\t// Record inverse branching behavior for fall-through target.\n\t\t\tReFrame targetFrame = (ReFrame) getFrames()[targetIndex];\n\t\t\ttargetFrame.addBranchingBehavior(branching.invert());\n\t\t} else {\n\t\t\tint targetIndex = targetMethod.instructions.indexOf(target);\n\t\t\tif (targetIndex < 0)\n\t\t\t\treturn;\n\n\t\t\t// Record branching behavior for jump target.\n\t\t\tReFrame targetFrame = (ReFrame) getFrames()[targetIndex];\n\t\t\ttargetFrame.addBranchingBehavior(branching);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/ReFrame.java",
    "content": "package software.coley.recaf.util.analysis;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.LabelNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.analysis.AnalyzerException;\nimport org.objectweb.asm.tree.analysis.Frame;\nimport org.objectweb.asm.tree.analysis.Interpreter;\nimport software.coley.recaf.util.analysis.value.ArrayValue;\nimport software.coley.recaf.util.analysis.value.IntValue;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\nimport java.util.Arrays;\nimport java.util.function.BiPredicate;\nimport java.util.function.Predicate;\n\n/**\n * Analysis frame for some enhanced value tracking.\n *\n * @author Matt Coley\n */\npublic class ReFrame extends Frame<ReValue> {\n\tprivate final ReAnalyzer analyzer;\n\tprivate final ReValue[] localsSnapshot;\n\tprivate final ReValue[] stackSnapshot;\n\tprivate int stackSnapshotSize;\n\tprivate int localsSnapshotSize;\n\tprivate Branching branchState;\n\tprivate boolean template;\n\n\n\t/**\n\t * New frame with the given number of expected locals/stack-slots.\n\t *\n\t * @param analyzer\n\t * \t\tParent analyzer for additional control flow hinting. {@code null} to skip hinting process.\n\t * @param numLocals\n\t * \t\tAvailable local variable slots.\n\t * @param maxStack\n\t * \t\tAvailable stack slots.\n\t */\n\tpublic ReFrame(@Nullable ReAnalyzer analyzer, int numLocals, int maxStack) {\n\t\tsuper(numLocals, maxStack);\n\t\tthis.analyzer = analyzer;\n\t\tlocalsSnapshot = new ReValue[numLocals];\n\t\tstackSnapshot = new ReValue[maxStack];\n\t}\n\n\t/**\n\t * New frame copying the state of the given frame.\n\t *\n\t * @param analyzer\n\t * \t\tParent analyzer for additional control flow hinting. {@code null} to skip hinting process.\n\t * @param frame\n\t * \t\tFrame to copy stack/locals of.\n\t */\n\tpublic ReFrame(@Nullable ReAnalyzer analyzer, @Nonnull Frame<? extends ReValue> frame) {\n\t\tsuper(frame);\n\t\tthis.analyzer = analyzer;\n\t\tlocalsSnapshot = new ReValue[frame.getLocals()];\n\t\tstackSnapshot = new ReValue[frame.getMaxStackSize()];\n\t}\n\n\t@Override\n\tpublic void execute(@Nonnull AbstractInsnNode insn, @Nonnull Interpreter<ReValue> interpreter) throws AnalyzerException {\n\t\t// Update control flow hinting.\n\t\tif (analyzer != null) {\n\t\t\t// Notify parent where we are at in the method.\n\t\t\tanalyzer.notifyCurrentInsn(insn);\n\n\t\t\t// Update branch state to indicate we've naturally flowed into this instruction.\n\t\t\taddBranchingBehavior(Branching.TAKEN);\n\n\t\t\t// Track the local/stack state before execution.\n\t\t\tupdateLocalAndStackSnapshots();\n\t\t}\n\n\t\t// Handle specific instruction edge cases, or delegate to default execution handling.\n\t\tswitch (insn.getOpcode()) {\n\t\t\tcase Opcodes.IASTORE:\n\t\t\tcase Opcodes.LASTORE:\n\t\t\tcase Opcodes.FASTORE:\n\t\t\tcase Opcodes.DASTORE:\n\t\t\tcase Opcodes.AASTORE:\n\t\t\tcase Opcodes.BASTORE:\n\t\t\tcase Opcodes.CASTORE:\n\t\t\tcase Opcodes.SASTORE:\n\t\t\t\t// Support updating array contents in this frame when values are stored into it.\n\t\t\t\tReValue value = pop();\n\t\t\t\tReValue index = pop();\n\t\t\t\tReValue array = pop();\n\t\t\t\tReValue updatedArray = interpreter.ternaryOperation(insn, array, index, value);\n\t\t\t\tif (updatedArray != array)\n\t\t\t\t\treplaceValue(array, updatedArray);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tsuper.execute(insn, interpreter);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void initJumpTarget(int opcode, @Nullable LabelNode target) {\n\t\t// No parent? Not able to share flow control hints.\n\t\tif (analyzer == null)\n\t\t\treturn;\n\n\t\t// Notify the parent analyzer of which jumps are taken.\n\t\tswitch (opcode) {\n\t\t\tcase Opcodes.IFEQ -> notifyUnary(target, top -> top.isEqualTo(0));\n\t\t\tcase Opcodes.IFNE -> notifyUnary(target, top -> top.isNotEqualTo(0));\n\t\t\tcase Opcodes.IFLT -> notifyUnary(target, top -> top.isLessThan(0));\n\t\t\tcase Opcodes.IFGE -> notifyUnary(target, top -> top.isGreaterThanOrEqual(0));\n\t\t\tcase Opcodes.IFGT -> notifyUnary(target, top -> top.isGreaterThan(0));\n\t\t\tcase Opcodes.IFLE -> notifyUnary(target, top -> top.isLessThanOrEqual(0));\n\t\t\tcase Opcodes.IFNULL -> notifyUnaryNullness(target, top ->\n\t\t\t\t\ttop.nullness() == Nullness.UNKNOWN ?\n\t\t\t\t\t\t\tBranching.UNKNOWN :\n\t\t\t\t\t\t\ttop.isNull() ?\n\t\t\t\t\t\t\t\t\tBranching.TAKEN : Branching.NOT_TAKEN);\n\t\t\tcase Opcodes.IFNONNULL -> notifyUnaryNullness(target, top ->\n\t\t\t\t\ttop.nullness() == Nullness.UNKNOWN ?\n\t\t\t\t\t\t\tBranching.UNKNOWN :\n\t\t\t\t\t\t\ttop.isNotNull() ?\n\t\t\t\t\t\t\t\t\tBranching.TAKEN : Branching.NOT_TAKEN);\n\t\t\tcase Opcodes.IF_ICMPEQ -> notifyBinary(target, IntValue::isEqualTo);\n\t\t\tcase Opcodes.IF_ICMPNE -> notifyBinary(target, IntValue::isNotEqualTo);\n\t\t\tcase Opcodes.IF_ICMPLT -> notifyBinary(target, IntValue::isLessThan);\n\t\t\tcase Opcodes.IF_ICMPGE -> notifyBinary(target, IntValue::isGreaterThanOrEqual);\n\t\t\tcase Opcodes.IF_ICMPGT -> notifyBinary(target, IntValue::isGreaterThan);\n\t\t\tcase Opcodes.IF_ICMPLE -> notifyBinary(target, IntValue::isLessThanOrEqual);\n\t\t\tcase Opcodes.IF_ACMPEQ -> notifyBinaryRefs(target, (l, r) ->\n\t\t\t\t\tl.nullness() == Nullness.UNKNOWN || r.nullness() == Nullness.UNKNOWN ?\n\t\t\t\t\t\t\tBranching.UNKNOWN :\n\t\t\t\t\t\t\tl == r ?\n\t\t\t\t\t\t\t\t\tBranching.TAKEN :\n\t\t\t\t\t\t\t\t\tBranching.UNKNOWN);\n\t\t\tcase Opcodes.IF_ACMPNE -> notifyBinaryRefs(target, (l, r) ->\n\t\t\t\t\tl.nullness() == Nullness.UNKNOWN || r.nullness() == Nullness.UNKNOWN ?\n\t\t\t\t\t\t\tBranching.UNKNOWN :\n\t\t\t\t\t\t\tl == r ?\n\t\t\t\t\t\t\t\t\tBranching.NOT_TAKEN :\n\t\t\t\t\t\t\t\t\tBranching.UNKNOWN);\n\t\t\tdefault -> {}\n\t\t}\n\t}\n\n\tprivate void notifyUnary(@Nullable LabelNode target, @Nonnull Predicate<IntValue> predicate) {\n\t\tReValue top = stackSnapshot[stackSnapshotSize - 1];\n\t\tif (top.hasKnownValue() && top instanceof IntValue intTop) {\n\t\t\tBranching branching = predicate.test(intTop) ? Branching.TAKEN : Branching.NOT_TAKEN;\n\t\t\tanalyzer.notifyJumpVisited(target, branching);\n\t\t} else {\n\t\t\tanalyzer.notifyJumpVisited(target, Branching.UNKNOWN);\n\t\t}\n\t}\n\n\tprivate void notifyUnaryNullness(@Nullable LabelNode target, @Nonnull UnBranchingComputer<ObjectValue> predicate) {\n\t\tReValue top = stackSnapshot[stackSnapshotSize - 1];\n\t\tif (top instanceof ObjectValue objTop) {\n\t\t\tBranching branching = predicate.compute(objTop);\n\t\t\tanalyzer.notifyJumpVisited(target, branching);\n\t\t} else {\n\t\t\tanalyzer.notifyJumpVisited(target, Branching.UNKNOWN);\n\t\t}\n\t}\n\n\tprivate void notifyBinary(@Nullable LabelNode target, @Nonnull BiPredicate<IntValue, IntValue> predicate) {\n\t\tReValue topL = stackSnapshot[stackSnapshotSize - 2];\n\t\tReValue topR = stackSnapshot[stackSnapshotSize - 1];\n\t\tif (topL.hasKnownValue() && topL instanceof IntValue intTopLeft &&\n\t\t\t\ttopR.hasKnownValue() && topR instanceof IntValue intTopRight) {\n\t\t\tBranching branching = predicate.test(intTopLeft, intTopRight) ? Branching.TAKEN : Branching.NOT_TAKEN;\n\t\t\tanalyzer.notifyJumpVisited(target, branching);\n\t\t} else {\n\t\t\tanalyzer.notifyJumpVisited(target, Branching.UNKNOWN);\n\t\t}\n\t}\n\n\tprivate void notifyBinaryRefs(@Nullable LabelNode target, @Nonnull BiBranchingComputer<ObjectValue> predicate) {\n\t\tReValue topL = stackSnapshot[stackSnapshotSize - 2];\n\t\tReValue topR = stackSnapshot[stackSnapshotSize - 1];\n\t\tif (topL.hasKnownValue() && topL instanceof ObjectValue objTopLeft &&\n\t\t\t\ttopR.hasKnownValue() && topR instanceof ObjectValue objTopRight) {\n\t\t\tBranching branching = predicate.compute(objTopLeft, objTopRight);\n\t\t\tanalyzer.notifyJumpVisited(target, branching);\n\t\t} else {\n\t\t\tanalyzer.notifyJumpVisited(target, Branching.UNKNOWN);\n\t\t}\n\t}\n\n\t@Override\n\tpublic boolean merge(@Nonnull Frame<? extends ReValue> frame, @Nonnull Interpreter<ReValue> interpreter) throws AnalyzerException {\n\t\tif (analyzer != null && template) {\n\t\t\t// Template frames must take a full copy of the originating frame.\n\t\t\tinit(frame);\n\t\t\ttemplate = false;\n\n\t\t\t// Quick return since we don't need to call super.merge()\n\t\t\t// since all values are the same as the originating frame.\n\t\t\treturn !isNeverBranchedTo();\n\t\t}\n\n\t\t// We will merge this frame's values, but return false when the observed branching state is 'never taken'.\n\t\t// The branching state is updated when we handle execution of a branching instruction targeting this frame.\n\t\t// If we can asser that the branching behavior for this frame is 'never taken' then returning false here\n\t\t// will prevent the parent analyzer from continuing execution from this point.\n\t\treturn super.merge(frame, interpreter);\n\t}\n\n\tprivate void updateLocalAndStackSnapshots() {\n\t\tint stackSnapshotSize = getStackSize();\n\t\tint localsSnapshotSize = getLocals();\n\n\t\tthis.stackSnapshotSize = stackSnapshotSize;\n\t\tthis.localsSnapshotSize = localsSnapshotSize;\n\n\t\tArrays.fill(stackSnapshot, stackSnapshotSize, stackSnapshot.length, null);\n\t\tfor (int i = 0; i < stackSnapshotSize; i++)\n\t\t\tstackSnapshot[i] = getStack(i);\n\t\tfor (int i = 0; i < localsSnapshotSize; i++)\n\t\t\tlocalsSnapshot[i] = getLocal(i);\n\t}\n\n\t/**\n\t * Replace all references to the given value with the replacement.\n\t *\n\t * @param existing\n\t * \t\tSome existing value that exists in this frame.\n\t * @param replacement\n\t * \t\tInstance to replace the existing value with.\n\t */\n\tpublic void replaceValue(@Nonnull ReValue existing, @Nonnull ReValue replacement) {\n\t\tfor (int i = 0; i < getStackSize(); i++) {\n\t\t\tReValue stack = getStack(i);\n\t\t\tif (stack == existing) {\n\t\t\t\tsetStack(i, replacement);\n\t\t\t} else if (stack instanceof ArrayValue stackArray) {\n\t\t\t\tArrayValue updatedStackArray = stackArray.updatedCopyIfContained(existing, replacement);\n\t\t\t\tsetStack(i, updatedStackArray);\n\t\t\t}\n\t\t}\n\t\tfor (int i = 0; i < getLocals(); i++) {\n\t\t\tReValue local = getLocal(i);\n\t\t\tif (local == existing) {\n\t\t\t\tsetLocal(i, replacement);\n\t\t\t} else if (local instanceof ArrayValue stackArray) {\n\t\t\t\tArrayValue updatedStackArray = stackArray.updatedCopyIfContained(existing, replacement);\n\t\t\t\tsetLocal(i, updatedStackArray);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @return {@code true} when this frame represents code that is never reached.\n\t * {@code false} when this code is visited by any means.\n\t */\n\tpublic boolean isNeverBranchedTo() {\n\t\treturn branchState == Branching.NOT_TAKEN;\n\t}\n\n\t/**\n\t * Record an instance of branching behavior. Merges the branching behavior state\n\t * to a value encompassing all observed cases.\n\t * <ul>\n\t *     <li>Consistent {@link Branching} values yields the consistent value as the merged state.</li>\n\t *     <li>Inconsistent {@link Branching} values yields {@link Branching#UNKNOWN}</li>\n\t * </ul>\n\t *\n\t * @param branching\n\t * \t\tBranching behavior observed in this frame.\n\t */\n\tprotected void addBranchingBehavior(@Nonnull Branching branching) {\n\t\tif (branchState == null)\n\t\t\tbranchState = branching;\n\t\telse if (branching == Branching.UNKNOWN)\n\t\t\tbranchState = Branching.UNKNOWN;\n\t\telse if (branchState == Branching.TAKEN && branching == Branching.NOT_TAKEN)\n\t\t\tbranchState = Branching.UNKNOWN;\n\t\telse if (branchState == Branching.NOT_TAKEN && branching == Branching.TAKEN)\n\t\t\tbranchState = Branching.UNKNOWN;\n\t}\n\n\t/**\n\t * @return {@code true} when this frame is a placeholder for later control flow visitation.\n\t *\n\t * @see ReAnalyzer#init(String, MethodNode)\n\t * @see #merge(Frame, Interpreter)\n\t */\n\tpublic boolean isTemplate() {\n\t\treturn template;\n\t}\n\n\t/**\n\t * Marks this frame as being a template for later control flow visitation.\n\t *\n\t * @see ReAnalyzer#init(String, MethodNode)\n\t * @see #merge(Frame, Interpreter)\n\t */\n\tprotected void markTemplate() {\n\t\ttemplate = true;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\tif (template) return \"<template>\";\n\t\treturn super.toString();\n\t}\n\n\tprivate interface UnBranchingComputer<T extends ReValue> {\n\t\t@Nonnull\n\t\tBranching compute(@Nonnull T value);\n\t}\n\n\tprivate interface BiBranchingComputer<T extends ReValue> {\n\t\t@Nonnull\n\t\tBranching compute(@Nonnull T valueLeft, @Nonnull T valueRight);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/ReInterpreter.java",
    "content": "package software.coley.recaf.util.analysis;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ConstantDynamic;\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.FieldInsnNode;\nimport org.objectweb.asm.tree.IincInsnNode;\nimport org.objectweb.asm.tree.IntInsnNode;\nimport org.objectweb.asm.tree.InvokeDynamicInsnNode;\nimport org.objectweb.asm.tree.LdcInsnNode;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport org.objectweb.asm.tree.MultiANewArrayInsnNode;\nimport org.objectweb.asm.tree.TypeInsnNode;\nimport org.objectweb.asm.tree.analysis.AnalyzerException;\nimport org.objectweb.asm.tree.analysis.Interpreter;\nimport org.objectweb.asm.tree.analysis.SimpleVerifier;\nimport org.slf4j.Logger;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceVertex;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.analysis.eval.InstancedObjectValue;\nimport software.coley.recaf.util.analysis.lookup.GetFieldLookup;\nimport software.coley.recaf.util.analysis.lookup.GetStaticLookup;\nimport software.coley.recaf.util.analysis.lookup.InvokeStaticLookup;\nimport software.coley.recaf.util.analysis.lookup.InvokeVirtualLookup;\nimport software.coley.recaf.util.analysis.value.ArrayValue;\nimport software.coley.recaf.util.analysis.value.DoubleValue;\nimport software.coley.recaf.util.analysis.value.FloatValue;\nimport software.coley.recaf.util.analysis.value.IllegalValueException;\nimport software.coley.recaf.util.analysis.value.IntValue;\nimport software.coley.recaf.util.analysis.value.LongValue;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.UninitializedValue;\n\nimport java.util.List;\nimport java.util.OptionalInt;\n\n/**\n * Interpreter implementation for enhanced value tracking.\n *\n * @author Matt Coley\n * @see ReValue Base enhanced value type.\n */\npublic class ReInterpreter extends Interpreter<ReValue> implements Opcodes {\n\tprivate static final Logger logger = Logging.get(ReInterpreter.class);\n\tprivate final InheritanceGraph inheritanceGraph;\n\tprivate GetStaticLookup getStaticLookup;\n\tprivate GetFieldLookup getFieldLookup;\n\tprivate InvokeStaticLookup invokeStaticLookup;\n\tprivate InvokeVirtualLookup invokeVirtualLookup;\n\n\tpublic ReInterpreter(@Nonnull InheritanceGraph inheritanceGraph) {\n\t\tsuper(RecafConstants.getAsmVersion());\n\t\tthis.inheritanceGraph = inheritanceGraph;\n\t}\n\n\t@Nullable\n\tpublic GetStaticLookup getGetStaticLookup() {\n\t\treturn getStaticLookup;\n\t}\n\n\t@Nullable\n\tpublic GetFieldLookup getGetFieldLookup() {\n\t\treturn getFieldLookup;\n\t}\n\n\t@Nullable\n\tpublic InvokeStaticLookup getInvokeStaticLookup() {\n\t\treturn invokeStaticLookup;\n\t}\n\n\t@Nullable\n\tpublic InvokeVirtualLookup getInvokeVirtualLookup() {\n\t\treturn invokeVirtualLookup;\n\t}\n\n\tpublic void setGetStaticLookup(@Nullable GetStaticLookup getStaticLookup) {\n\t\tthis.getStaticLookup = getStaticLookup;\n\t}\n\n\tpublic void setGetFieldLookup(@Nullable GetFieldLookup getFieldLookup) {\n\t\tthis.getFieldLookup = getFieldLookup;\n\t}\n\n\tpublic void setInvokeStaticLookup(@Nullable InvokeStaticLookup invokeStaticLookup) {\n\t\tthis.invokeStaticLookup = invokeStaticLookup;\n\t}\n\n\tpublic void setInvokeVirtualLookup(@Nullable InvokeVirtualLookup invokeVirtualLookup) {\n\t\tthis.invokeVirtualLookup = invokeVirtualLookup;\n\t}\n\n\t@Nonnull\n\t@SuppressWarnings(\"DataFlowIssue\") // Won't happen because we use arrays\n\tprivate ReValue newArrayValue(@Nonnull Type type, int dimensions) {\n\t\tif (dimensions == 0)\n\t\t\treturn newValue(type);\n\t\tString descriptor = \"[\".repeat(Math.max(0, dimensions)) + type.getDescriptor();\n\t\treturn newValue(Type.getType(descriptor), Nullness.NOT_NULL);\n\t}\n\n\t@Nullable\n\tpublic ReValue newValue(@Nullable Type type, @Nonnull Nullness nullness) {\n\t\ttry {\n\t\t\treturn ReValue.ofType(type, nullness);\n\t\t} catch (IllegalValueException ex) {\n\t\t\tthrow new IllegalStateException(ex);\n\t\t}\n\t}\n\n\t@Override\n\tpublic ReValue newValue(@Nullable Type type) {\n\t\treturn newValue(type, Nullness.UNKNOWN);\n\t}\n\n\t@Override\n\tpublic ReValue newOperation(@Nonnull AbstractInsnNode insn) throws AnalyzerException {\n\t\tswitch (insn.getOpcode()) {\n\t\t\tcase ACONST_NULL:\n\t\t\t\treturn ObjectValue.VAL_OBJECT_NULL;\n\t\t\tcase ICONST_M1:\n\t\t\t\treturn IntValue.VAL_M1;\n\t\t\tcase ICONST_0:\n\t\t\t\treturn IntValue.VAL_0;\n\t\t\tcase ICONST_1:\n\t\t\t\treturn IntValue.VAL_1;\n\t\t\tcase ICONST_2:\n\t\t\t\treturn IntValue.VAL_2;\n\t\t\tcase ICONST_3:\n\t\t\t\treturn IntValue.VAL_3;\n\t\t\tcase ICONST_4:\n\t\t\t\treturn IntValue.VAL_4;\n\t\t\tcase ICONST_5:\n\t\t\t\treturn IntValue.VAL_5;\n\t\t\tcase LCONST_0:\n\t\t\t\treturn LongValue.VAL_0;\n\t\t\tcase LCONST_1:\n\t\t\t\treturn LongValue.VAL_1;\n\t\t\tcase FCONST_0:\n\t\t\t\treturn FloatValue.VAL_0;\n\t\t\tcase FCONST_1:\n\t\t\t\treturn FloatValue.VAL_1;\n\t\t\tcase FCONST_2:\n\t\t\t\treturn FloatValue.VAL_2;\n\t\t\tcase DCONST_0:\n\t\t\t\treturn DoubleValue.VAL_0;\n\t\t\tcase DCONST_1:\n\t\t\t\treturn DoubleValue.VAL_1;\n\t\t\tcase BIPUSH:\n\t\t\tcase SIPUSH:\n\t\t\t\tIntInsnNode intInsn = (IntInsnNode) insn;\n\t\t\t\treturn IntValue.of(intInsn.operand);\n\t\t\tcase LDC:\n\t\t\t\tObject value = ((LdcInsnNode) insn).cst;\n\t\t\t\tswitch (value) {\n\t\t\t\t\tcase Integer i -> {\n\t\t\t\t\t\treturn IntValue.of(i);\n\t\t\t\t\t}\n\t\t\t\t\tcase Float f -> {\n\t\t\t\t\t\treturn FloatValue.of(f);\n\t\t\t\t\t}\n\t\t\t\t\tcase Long l -> {\n\t\t\t\t\t\treturn LongValue.of(l);\n\t\t\t\t\t}\n\t\t\t\t\tcase Double d -> {\n\t\t\t\t\t\treturn DoubleValue.of(d);\n\t\t\t\t\t}\n\t\t\t\t\tcase String s -> {\n\t\t\t\t\t\treturn ObjectValue.string(s);\n\t\t\t\t\t}\n\t\t\t\t\tcase Type type -> {\n\t\t\t\t\t\tint sort = type.getSort();\n\t\t\t\t\t\tif (sort == Type.OBJECT || sort == Type.ARRAY) {\n\t\t\t\t\t\t\treturn ObjectValue.VAL_CLASS;\n\t\t\t\t\t\t} else if (sort == Type.METHOD) {\n\t\t\t\t\t\t\treturn ObjectValue.VAL_METHOD_TYPE;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthrow new AnalyzerException(insn, \"Illegal LDC value \" + value);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcase Handle handle -> {\n\t\t\t\t\t\treturn ObjectValue.VAL_METHOD_HANDLE;\n\t\t\t\t\t}\n\t\t\t\t\tcase ConstantDynamic constantDynamic -> {\n\t\t\t\t\t\tType dynamicType = Type.getType(constantDynamic.getDescriptor());\n\t\t\t\t\t\treturn newValue(dynamicType, Nullness.NOT_NULL);\n\t\t\t\t\t}\n\t\t\t\t\tcase null, default -> throw new AnalyzerException(insn, \"Illegal LDC value \" + value);\n\t\t\t\t}\n\t\t\tcase JSR:\n\t\t\t\treturn ObjectValue.VAL_JSR;\n\t\t\tcase GETSTATIC:\n\t\t\t\tFieldInsnNode field = (FieldInsnNode) insn;\n\t\t\t\tif (getStaticLookup != null)\n\t\t\t\t\treturn getStaticLookup.get(field);\n\t\t\t\tType fieldType = Type.getType(field.desc);\n\t\t\t\treturn newValue(fieldType);\n\t\t\tcase NEW:\n\t\t\t\tType objectType = Type.getObjectType(((TypeInsnNode) insn).desc);\n\t\t\t\treturn newValue(objectType, Nullness.NOT_NULL);\n\t\t\tdefault:\n\t\t\t\tthrow new AssertionError();\n\t\t}\n\t}\n\n\t@Override\n\tpublic ReValue copyOperation(@Nonnull AbstractInsnNode insn, @Nonnull ReValue value) {\n\t\t// Just keep the same value reference\n\t\treturn value;\n\t}\n\n\t@Override\n\tpublic ReValue unaryOperation(@Nonnull AbstractInsnNode insn, @Nonnull ReValue value) throws AnalyzerException {\n\t\tswitch (insn.getOpcode()) {\n\t\t\tcase IINC: {\n\t\t\t\tint incr = ((IincInsnNode) insn).incr;\n\t\t\t\tif (value instanceof IntValue iv) return iv.add(incr);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\t}\n\t\t\tcase INEG:\n\t\t\t\tif (value instanceof IntValue iv) return iv.negate();\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase L2I:\n\t\t\t\tif (value instanceof LongValue lv) return lv.castInt();\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase F2I:\n\t\t\t\tif (value instanceof FloatValue fv) return fv.castInt();\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase D2I:\n\t\t\t\tif (value instanceof DoubleValue dv) return dv.castInt();\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase I2B:\n\t\t\t\tif (value instanceof IntValue iv) return iv.castByte();\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase I2C:\n\t\t\t\tif (value instanceof IntValue iv) return iv.castChar();\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase I2S:\n\t\t\t\tif (value instanceof IntValue iv) return iv.castShort();\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase FNEG:\n\t\t\t\tif (value instanceof FloatValue fv) return fv.negate();\n\t\t\t\treturn FloatValue.UNKNOWN;\n\t\t\tcase I2F:\n\t\t\t\tif (value instanceof IntValue iv) return iv.castFloat();\n\t\t\t\treturn FloatValue.UNKNOWN;\n\t\t\tcase L2F:\n\t\t\t\tif (value instanceof LongValue lv) return lv.castFloat();\n\t\t\t\treturn FloatValue.UNKNOWN;\n\t\t\tcase D2F:\n\t\t\t\tif (value instanceof DoubleValue dv) return dv.castFloat();\n\t\t\t\treturn FloatValue.UNKNOWN;\n\t\t\tcase LNEG:\n\t\t\t\tif (value instanceof LongValue lv) return lv.negate();\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase I2L:\n\t\t\t\tif (value instanceof IntValue iv) return iv.castLong();\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase F2L:\n\t\t\t\tif (value instanceof FloatValue fv) return fv.castLong();\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase D2L:\n\t\t\t\tif (value instanceof DoubleValue dv) return dv.castLong();\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase DNEG:\n\t\t\t\tif (value instanceof DoubleValue dv) return dv.negate();\n\t\t\t\treturn DoubleValue.UNKNOWN;\n\t\t\tcase I2D:\n\t\t\t\tif (value instanceof IntValue iv) return iv.castDouble();\n\t\t\t\treturn DoubleValue.UNKNOWN;\n\t\t\tcase L2D:\n\t\t\t\tif (value instanceof LongValue lv) return lv.castDouble();\n\t\t\t\treturn DoubleValue.UNKNOWN;\n\t\t\tcase F2D:\n\t\t\t\tif (value instanceof FloatValue fv) return fv.castDouble();\n\t\t\t\treturn DoubleValue.UNKNOWN;\n\t\t\tcase IFEQ:\n\t\t\tcase IFNE:\n\t\t\tcase IFLT:\n\t\t\tcase IFGE:\n\t\t\tcase IFGT:\n\t\t\tcase IFLE:\n\t\t\tcase TABLESWITCH:\n\t\t\tcase LOOKUPSWITCH:\n\t\t\tcase IRETURN:\n\t\t\tcase LRETURN:\n\t\t\tcase FRETURN:\n\t\t\tcase DRETURN:\n\t\t\tcase ARETURN:\n\t\t\tcase PUTSTATIC:\n\t\t\t\treturn null;\n\t\t\tcase MONITORENTER:\n\t\t\tcase MONITOREXIT:\n\t\t\tcase IFNULL:\n\t\t\tcase IFNONNULL:\n\t\t\t\treturn null;\n\t\t\tcase ATHROW:\n\t\t\t\treturn null;\n\t\t\tcase GETFIELD: {\n\t\t\t\tFieldInsnNode field = (FieldInsnNode) insn;\n\t\t\t\tif (getFieldLookup != null && value.hasKnownValue())\n\t\t\t\t\treturn getFieldLookup.get(field, value);\n\t\t\t\tType fieldType = Type.getType(field.desc);\n\t\t\t\treturn newValue(fieldType);\n\t\t\t}\n\t\t\tcase NEWARRAY:\n\t\t\t\tint arrayKind = ((IntInsnNode) insn).operand;\n\t\t\t\tif (value instanceof IntValue length) {\n\t\t\t\t\tOptionalInt lengthValue = length.value();\n\t\t\t\t\tif (lengthValue.isPresent()) {\n\t\t\t\t\t\tType type = switch (arrayKind) {\n\t\t\t\t\t\t\tcase T_BOOLEAN -> Types.ARRAY_1D_BOOLEAN;\n\t\t\t\t\t\t\tcase T_CHAR -> Types.ARRAY_1D_CHAR;\n\t\t\t\t\t\t\tcase T_BYTE -> Types.ARRAY_1D_BYTE;\n\t\t\t\t\t\t\tcase T_SHORT -> Types.ARRAY_1D_SHORT;\n\t\t\t\t\t\t\tcase T_INT -> Types.ARRAY_1D_INT;\n\t\t\t\t\t\t\tcase T_FLOAT -> Types.ARRAY_1D_FLOAT;\n\t\t\t\t\t\t\tcase T_DOUBLE -> Types.ARRAY_1D_DOUBLE;\n\t\t\t\t\t\t\tcase T_LONG -> Types.ARRAY_1D_LONG;\n\t\t\t\t\t\t\tdefault -> throw new AnalyzerException(insn, \"Invalid array type\");\n\t\t\t\t\t\t};\n\t\t\t\t\t\treturn ArrayValue.of(type, Nullness.NOT_NULL, lengthValue.getAsInt());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn switch (arrayKind) {\n\t\t\t\t\tcase T_BOOLEAN -> ArrayValue.VAL_BOOLEANS;\n\t\t\t\t\tcase T_CHAR -> ArrayValue.VAL_CHARS;\n\t\t\t\t\tcase T_BYTE -> ArrayValue.VAL_BYTES;\n\t\t\t\t\tcase T_SHORT -> ArrayValue.VAL_SHORTS;\n\t\t\t\t\tcase T_INT -> ArrayValue.VAL_INTS;\n\t\t\t\t\tcase T_FLOAT -> ArrayValue.VAL_FLOATS;\n\t\t\t\t\tcase T_DOUBLE -> ArrayValue.VAL_DOUBLES;\n\t\t\t\t\tcase T_LONG -> ArrayValue.VAL_LONGS;\n\t\t\t\t\tdefault -> throw new AnalyzerException(insn, \"Invalid array type\");\n\t\t\t\t};\n\t\t\tcase ANEWARRAY: {\n\t\t\t\tType arrayType = Type.getType(\"[\" + Type.getObjectType(((TypeInsnNode) insn).desc));\n\t\t\t\tif (value instanceof IntValue iv && iv.hasKnownValue())\n\t\t\t\t\treturn ArrayValue.of(arrayType, Nullness.NOT_NULL, iv.value().getAsInt());\n\t\t\t\treturn newValue(arrayType, Nullness.NOT_NULL);\n\t\t\t}\n\t\t\tcase ARRAYLENGTH:\n\t\t\t\tif (value instanceof ArrayValue array) {\n\t\t\t\t\tOptionalInt firstDimensionLength = array.getFirstDimensionLength();\n\t\t\t\t\tif (firstDimensionLength.isPresent())\n\t\t\t\t\t\treturn IntValue.of(firstDimensionLength.getAsInt());\n\t\t\t\t}\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase CHECKCAST:\n\t\t\t\tType targetType = Type.getObjectType(((TypeInsnNode) insn).desc);\n\t\t\t\tif (value instanceof InstancedObjectValue<?> instancedValue && isAssignableFrom(targetType, instancedValue.type()))\n\t\t\t\t\treturn value;\n\t\t\t\treturn newValue(targetType);\n\t\t\tcase INSTANCEOF:\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tdefault:\n\t\t\t\tthrow new AnalyzerException(insn, \"Unknown unary op: \" + insn.getOpcode());\n\t\t}\n\t}\n\n\t@Override\n\tpublic ReValue binaryOperation(@Nonnull AbstractInsnNode insn, @Nonnull ReValue value1, @Nonnull ReValue value2) {\n\t\tswitch (insn.getOpcode()) {\n\t\t\tcase IALOAD:\n\t\t\tcase BALOAD:\n\t\t\tcase CALOAD:\n\t\t\tcase SALOAD:\n\t\t\t\tif (value1 instanceof ArrayValue array && value2 instanceof IntValue index && index.value().isPresent())\n\t\t\t\t\treturn array.getValue(index.value().getAsInt());\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase FALOAD:\n\t\t\t\tif (value1 instanceof ArrayValue array && value2 instanceof IntValue index && index.value().isPresent())\n\t\t\t\t\treturn array.getValue(index.value().getAsInt());\n\t\t\t\treturn FloatValue.UNKNOWN;\n\t\t\tcase LALOAD:\n\t\t\t\tif (value1 instanceof ArrayValue array && value2 instanceof IntValue index && index.value().isPresent())\n\t\t\t\t\treturn array.getValue(index.value().getAsInt());\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase DALOAD:\n\t\t\t\tif (value1 instanceof ArrayValue array && value2 instanceof IntValue index && index.value().isPresent())\n\t\t\t\t\treturn array.getValue(index.value().getAsInt());\n\t\t\t\treturn DoubleValue.UNKNOWN;\n\t\t\tcase AALOAD:\n\t\t\t\tif (value1 instanceof ArrayValue array && value2 instanceof IntValue index && index.value().isPresent())\n\t\t\t\t\treturn array.getValue(index.value().getAsInt());\n\t\t\t\treturn ObjectValue.VAL_OBJECT_MAYBE_NULL;\n\t\t\tcase IADD:\n\t\t\t\tif (value1 instanceof IntValue i1 && value2 instanceof IntValue i2) return i1.add(i2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase ISUB:\n\t\t\t\tif (value1 instanceof IntValue i1 && value2 instanceof IntValue i2) return i1.sub(i2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase IMUL:\n\t\t\t\tif (value1 instanceof IntValue i1 && value2 instanceof IntValue i2) return i1.mul(i2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase IDIV:\n\t\t\t\tif (value1 instanceof IntValue i1 && value2 instanceof IntValue i2) return i1.div(i2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase IREM:\n\t\t\t\tif (value1 instanceof IntValue i1 && value2 instanceof IntValue i2) return i1.rem(i2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase ISHL:\n\t\t\t\tif (value1 instanceof IntValue i1 && value2 instanceof IntValue i2) return i1.shl(i2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase ISHR:\n\t\t\t\tif (value1 instanceof IntValue i1 && value2 instanceof IntValue i2) return i1.shr(i2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase IUSHR:\n\t\t\t\tif (value1 instanceof IntValue i1 && value2 instanceof IntValue i2) return i1.ushr(i2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase IAND:\n\t\t\t\tif (value1 instanceof IntValue i1 && value2 instanceof IntValue i2) return i1.and(i2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase IOR:\n\t\t\t\tif (value1 instanceof IntValue i1 && value2 instanceof IntValue i2) return i1.or(i2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase IXOR:\n\t\t\t\tif (value1 instanceof IntValue i1 && value2 instanceof IntValue i2) return i1.xor(i2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase FADD:\n\t\t\t\tif (value1 instanceof FloatValue f1 && value2 instanceof FloatValue f2) return f1.add(f2);\n\t\t\t\treturn FloatValue.UNKNOWN;\n\t\t\tcase FSUB:\n\t\t\t\tif (value1 instanceof FloatValue f1 && value2 instanceof FloatValue f2) return f1.sub(f2);\n\t\t\t\treturn FloatValue.UNKNOWN;\n\t\t\tcase FMUL:\n\t\t\t\tif (value1 instanceof FloatValue f1 && value2 instanceof FloatValue f2) return f1.mul(f2);\n\t\t\t\treturn FloatValue.UNKNOWN;\n\t\t\tcase FDIV:\n\t\t\t\tif (value1 instanceof FloatValue f1 && value2 instanceof FloatValue f2) return f1.div(f2);\n\t\t\t\treturn FloatValue.UNKNOWN;\n\t\t\tcase FREM:\n\t\t\t\tif (value1 instanceof FloatValue f1 && value2 instanceof FloatValue f2) return f1.rem(f2);\n\t\t\t\treturn FloatValue.UNKNOWN;\n\t\t\tcase LADD:\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof LongValue l2) return l1.add(l2);\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase LSUB:\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof LongValue l2) return l1.sub(l2);\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase LMUL:\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof LongValue l2) return l1.mul(l2);\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase LDIV:\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof LongValue l2) return l1.div(l2);\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase LREM:\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof LongValue l2) return l1.rem(l2);\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase LSHL:\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof IntValue l2) return l1.shl(l2);\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof LongValue l2) return l1.shl(l2);\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase LSHR:\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof IntValue l2) return l1.shr(l2);\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof LongValue l2) return l1.shr(l2);\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase LUSHR:\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof IntValue l2) return l1.ushr(l2);\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof LongValue l2) return l1.ushr(l2);\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase LAND:\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof LongValue l2) return l1.and(l2);\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase LOR:\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof LongValue l2) return l1.or(l2);\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase LXOR:\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof LongValue l2) return l1.xor(l2);\n\t\t\t\treturn LongValue.UNKNOWN;\n\t\t\tcase DADD:\n\t\t\t\tif (value1 instanceof DoubleValue d1 && value2 instanceof DoubleValue d2) return d1.add(d2);\n\t\t\t\treturn DoubleValue.UNKNOWN;\n\t\t\tcase DSUB:\n\t\t\t\tif (value1 instanceof DoubleValue d1 && value2 instanceof DoubleValue d2) return d1.sub(d2);\n\t\t\t\treturn DoubleValue.UNKNOWN;\n\t\t\tcase DMUL:\n\t\t\t\tif (value1 instanceof DoubleValue d1 && value2 instanceof DoubleValue d2) return d1.mul(d2);\n\t\t\t\treturn DoubleValue.UNKNOWN;\n\t\t\tcase DDIV:\n\t\t\t\tif (value1 instanceof DoubleValue d1 && value2 instanceof DoubleValue d2) return d1.div(d2);\n\t\t\t\treturn DoubleValue.UNKNOWN;\n\t\t\tcase DREM:\n\t\t\t\tif (value1 instanceof DoubleValue d1 && value2 instanceof DoubleValue d2) return d1.rem(d2);\n\t\t\t\treturn DoubleValue.UNKNOWN;\n\t\t\tcase LCMP:\n\t\t\t\tif (value1 instanceof LongValue l1 && value2 instanceof LongValue l2) return l1.cmp(l2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase FCMPL:\n\t\t\t\tif (value1 instanceof FloatValue f1 && value2 instanceof FloatValue f2) return f1.cmpl(f2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase FCMPG:\n\t\t\t\tif (value1 instanceof FloatValue f1 && value2 instanceof FloatValue f2) return f1.cmpg(f2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase DCMPL:\n\t\t\t\tif (value1 instanceof DoubleValue d1 && value2 instanceof DoubleValue d2) return d1.cmpl(d2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase DCMPG:\n\t\t\t\tif (value1 instanceof DoubleValue d1 && value2 instanceof DoubleValue d2) return d1.cmpg(d2);\n\t\t\t\treturn IntValue.UNKNOWN;\n\t\t\tcase IF_ICMPEQ:\n\t\t\tcase IF_ICMPNE:\n\t\t\tcase IF_ICMPLT:\n\t\t\tcase IF_ICMPGE:\n\t\t\tcase IF_ICMPGT:\n\t\t\tcase IF_ICMPLE:\n\t\t\tcase IF_ACMPEQ:\n\t\t\tcase IF_ACMPNE:\n\t\t\tcase PUTFIELD:\n\t\t\t\t// Just popping values, not pushing anything back onto the stack\n\t\t\t\treturn null;\n\t\t\tdefault:\n\t\t\t\tthrow new AssertionError();\n\t\t}\n\t}\n\n\t@Override\n\tpublic ReValue ternaryOperation(@Nonnull AbstractInsnNode insn, @Nonnull ReValue value1, @Nonnull ReValue value2, @Nonnull ReValue value3) {\n\t\t// This method covers the following instructions:\n\t\t//  IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE\n\t\t// It will always be an array-store operation.\n\t\t// NOTE: Load operations are handled in 'binaryOperation'\n\t\tif (value1 instanceof ArrayValue array && value2 instanceof IntValue index && index.value().isPresent())\n\t\t\treturn array.setValue(index.value().getAsInt(), value3);\n\t\treturn value1;\n\t}\n\n\t@Override\n\tpublic ReValue naryOperation(@Nonnull AbstractInsnNode insn, @Nonnull List<? extends ReValue> values) {\n\t\tint opcode = insn.getOpcode();\n\t\tif (opcode == MULTIANEWARRAY) {\n\t\t\tType type = Type.getType(((MultiANewArrayInsnNode) insn).desc);\n\n\t\t\t// Extract dimensions from passed values.\n\t\t\t// Unknown values will be negative, which we will sanity check for.\n\t\t\tint[] dimensions = values.stream()\n\t\t\t\t\t.mapToInt(v -> v instanceof IntValue intValue && intValue.value().isPresent() ?\n\t\t\t\t\t\t\tintValue.value().getAsInt() : -1)\n\t\t\t\t\t.toArray();\n\t\t\tif (dimensions.length == 0) // Sanity check, should never occur.\n\t\t\t\treturn newValue(type, Nullness.NOT_NULL);\n\t\t\tfor (int dimension : dimensions)\n\t\t\t\tif (dimension < 0) // Validate dimensions are valid\n\t\t\t\t\treturn newValue(type, Nullness.NOT_NULL);\n\n\t\t\t// Make array of known dimensions.\n\t\t\treturn ArrayValue.multiANewArray(type, dimensions);\n\t\t} else if (opcode == INVOKEDYNAMIC) {\n\t\t\tType returnType = Type.getReturnType(((InvokeDynamicInsnNode) insn).desc);\n\t\t\treturn newValue(returnType);\n\t\t} else {\n\t\t\tMethodInsnNode method = (MethodInsnNode) insn;\n\t\t\tType returnType = Type.getReturnType(method.desc);\n\t\t\tif (returnType.getSort() != Type.VOID) {\n\t\t\t\t// We only support \"lookups\" as values are immutable.\n\t\t\t\t// Something like System.arraycopy(...) isn't supported here.\n\t\t\t\tif (opcode == INVOKESTATIC && invokeStaticLookup != null && values.stream().allMatch(ReValue::hasKnownValue)) {\n\t\t\t\t\treturn invokeStaticLookup.get(method, values);\n\t\t\t\t} else if ((opcode == INVOKEVIRTUAL || opcode == INVOKEINTERFACE || opcode == INVOKESPECIAL)\n\t\t\t\t\t\t&& invokeVirtualLookup != null && values.stream().allMatch(ReValue::hasKnownValue)) {\n\t\t\t\t\treturn invokeVirtualLookup.get(method, values.getFirst(), values.subList(1, values.size()));\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn newValue(returnType);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void returnOperation(@Nonnull AbstractInsnNode insn, @Nonnull ReValue value, @Nonnull ReValue expected) {\n\t\t// no-op\n\t}\n\n\t/**\n\t * {@inheritDoc}\n\t * <p>\n\t * Implementation adapted from {@link SimpleVerifier}.\n\t *\n\t * @param value1\n\t * \t\tA value.\n\t * @param value2\n\t * \t\tAnother value.\n\t *\n\t * @return The merged value.\n\t */\n\t@Override\n\tpublic ReValue merge(@Nonnull ReValue value1, @Nonnull ReValue value2) {\n\t\tType type1 = value1.type();\n\t\tType type2 = value2.type();\n\n\t\t// Null types correspond to UNINITIALIZED_VALUE.\n\t\tif (type1 == null || type2 == null)\n\t\t\treturn UninitializedValue.UNINITIALIZED_VALUE;\n\n\t\t// If the types are the same, we should merge their tracked value state.\n\t\tif (type1.equals(type2)) {\n\t\t\ttry {\n\t\t\t\treturn value1.mergeWith(value2);\n\t\t\t} catch (IllegalValueException t) {\n\t\t\t\tlogger.error(\"Failed ReValue merge of of same type\", t);\n\t\t\t\tthrow new IllegalStateException(\"Failed ReValue merge of of same type\", t);\n\t\t\t}\n\t\t}\n\n\t\t// The merge of a primitive type with a different type is the type of uninitialized values.\n\t\tif (type1.getSort() != Type.OBJECT && type1.getSort() != Type.ARRAY)\n\t\t\treturn UninitializedValue.UNINITIALIZED_VALUE;\n\t\tif (type2.getSort() != Type.OBJECT && type2.getSort() != Type.ARRAY)\n\t\t\treturn UninitializedValue.UNINITIALIZED_VALUE;\n\n\t\t// Special case for the type of the \"null\" literal.\n\t\tif (value1 instanceof ObjectValue ov1 && ov1.isNull())\n\t\t\treturn value2;\n\t\tif (value2 instanceof ObjectValue ov2 && ov2.isNull())\n\t\t\treturn value1;\n\n\t\t// Convert type1 to its element type and array dimension. Arrays of primitive values are seen as\n\t\t// Object arrays with one dimension less. Hence, the element type is always of Type.OBJECT sort.\n\t\tint dim1 = 0;\n\t\tif (type1.getSort() == Type.ARRAY) {\n\t\t\tdim1 = type1.getDimensions();\n\t\t\ttype1 = type1.getElementType();\n\t\t\tif (type1.getSort() != Type.OBJECT) {\n\t\t\t\tdim1 = dim1 - 1;\n\t\t\t\ttype1 = Types.OBJECT_TYPE;\n\t\t\t}\n\t\t}\n\n\t\t// Do the same for type2.\n\t\tint dim2 = 0;\n\t\tif (type2.getSort() == Type.ARRAY) {\n\t\t\tdim2 = type2.getDimensions();\n\t\t\ttype2 = type2.getElementType();\n\t\t\tif (type2.getSort() != Type.OBJECT) {\n\t\t\t\tdim2 = dim2 - 1;\n\t\t\t\ttype2 = Types.OBJECT_TYPE;\n\t\t\t}\n\t\t}\n\n\t\t// The merge of array types of different dimensions is an Object array type.\n\t\tif (dim1 != dim2)\n\t\t\treturn newArrayValue(Types.OBJECT_TYPE, Math.min(dim1, dim2));\n\n\t\t// If the dimensions are the same, and there is no array aspect of these values\n\t\t// then we want to merge the values in such a way that tracks state if possible.\n\t\tif (dim1 == 0) {\n\t\t\ttry {\n\t\t\t\tif (isAssignableFrom(type1, type2))\n\t\t\t\t\treturn value1.mergeWith(value2);\n\t\t\t\tif (isAssignableFrom(type2, type1))\n\t\t\t\t\treturn value2.mergeWith(value1);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Failed ReValue merge of {} and {}\",\n\t\t\t\t\t\tvalue1.getClass().getSimpleName(), value2.getClass().getSimpleName(), t);\n\t\t\t\tthrow new IllegalStateException(\"Failed ReValue merge of of same type\", t);\n\t\t\t}\n\t\t}\n\n\t\t// Type1 and type2 have a Type.OBJECT sort by construction (see above),\n\t\t// as expected by isAssignableFrom.\n\t\tif (isAssignableFrom(type1, type2))\n\t\t\treturn newArrayValue(type1, dim1);\n\t\tif (isAssignableFrom(type2, type1))\n\t\t\treturn newArrayValue(type2, dim1);\n\n\t\tif (!isInterface(type1)) {\n\t\t\twhile (!Types.OBJECT_TYPE.equals(type1)) {\n\t\t\t\ttype1 = getSuperClass(type1);\n\t\t\t\tif (isAssignableFrom(type1, type2))\n\t\t\t\t\treturn newArrayValue(type1, dim1);\n\t\t\t}\n\t\t}\n\n\t\treturn newArrayValue(Types.OBJECT_TYPE, dim1);\n\t}\n\n\t@Nonnull\n\tprivate Type getSuperClass(@Nonnull Type type) {\n\t\tString name = type.getInternalName();\n\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(name);\n\t\tif (vertex == null)\n\t\t\treturn Types.OBJECT_TYPE;\n\t\tString superName = vertex.getValue().getSuperName();\n\t\treturn superName == null ? Types.OBJECT_TYPE : Type.getObjectType(superName);\n\t}\n\n\tprivate boolean isInterface(@Nonnull Type type) {\n\t\tString name = type.getInternalName();\n\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(name);\n\t\tif (vertex == null)\n\t\t\treturn false;\n\t\treturn vertex.getValue().hasInterfaceModifier();\n\t}\n\n\tprivate boolean isAssignableFrom(@Nonnull Type type1, @Nonnull Type type2) {\n\t\tString name1 = type1.getInternalName();\n\t\tString name2 = type2.getInternalName();\n\t\treturn inheritanceGraph.isAssignableFrom(name1, name2);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/eval/EvaluationException.java",
    "content": "package software.coley.recaf.util.analysis.eval;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Exception thrown when {@link Evaluator} cannot evaluate a method.\n *\n * @author Matt Coley\n */\npublic class EvaluationException extends Exception {\n\t/**\n\t * @param message\n\t * \t\tDetail message.\n\t */\n\tpublic EvaluationException(@Nonnull String message) {\n\t\tsuper(message);\n\t}\n\n\t/**\n\t * @param cause\n\t * \t\tUnderlying cause/error.\n\t * @param message\n\t * \t\tDetail message.\n\t */\n\tpublic EvaluationException(@Nonnull Throwable cause, @Nonnull String message) {\n\t\tsuper(message, cause);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/eval/EvaluationFailureResult.java",
    "content": "package software.coley.recaf.util.analysis.eval;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\n\n/**\n * Result of the method or block not being able to be evaluated.\n *\n * @param reason\n * \t\tReason why evaluation failed.\n * @param cause\n * \t\tOptional cause of failure.\n *\n * @author Matt Coley\n */\npublic record EvaluationFailureResult(@Nonnull String reason,\n                                      @Nullable EvaluationException cause) implements EvaluationResult {}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/eval/EvaluationResult.java",
    "content": "package software.coley.recaf.util.analysis.eval;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Result of evaluating a method or block of instructions.\n *\n * @author Matt Coley\n */\npublic sealed interface EvaluationResult permits EvaluationFailureResult, EvaluationThrowsResult, EvaluationYieldResult {\n\t/**\n\t * Result of the method or block not being able to be evaluated.\n\t *\n\t * @param reason\n\t * \t\tReason why evaluation failed.\n\t *\n\t * @return Evaluation failure result.\n\t */\n\tstatic EvaluationFailureResult cannotEvaluate(@Nonnull String reason) {\n\t\treturn new EvaluationFailureResult(reason, new EvaluationException(reason));\n\t}\n\n\t/**\n\t * Result of the method or block not being able to be evaluated.\n\t *\n\t * @param reason\n\t * \t\tReason why evaluation failed.\n\t * @param cause\n\t * \t\tOptional cause of failure.\n\t *\n\t * @return Evaluation failure result.\n\t */\n\tstatic EvaluationFailureResult cannotEvaluate(@Nonnull String reason, @Nonnull Throwable cause) {\n\t\treturn new EvaluationFailureResult(reason, new EvaluationException(cause, reason));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/eval/EvaluationThrowsResult.java",
    "content": "package software.coley.recaf.util.analysis.eval;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\n/**\n * Result of an exception being thrown from the method or instruction block.\n *\n * @param exception\n * \t\tThrown exception value.\n *\n * @author Matt Coley\n */\npublic record EvaluationThrowsResult(@Nonnull ReValue exception) implements EvaluationResult {}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/eval/EvaluationYieldResult.java",
    "content": "package software.coley.recaf.util.analysis.eval;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\n/**\n * Result of a normal return from the method or instruction block.\n *\n * @param value\n * \t\tReturn value of the method or instruction block.\n * \t\tIf the block has no defined {@code return} instruction then the stack top value at the end of execution.\n *\n * @author Matt Coley\n */\npublic record EvaluationYieldResult(@Nonnull ReValue value) implements EvaluationResult {}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/eval/Evaluator.java",
    "content": "package software.coley.recaf.util.analysis.eval;\n\nimport it.unimi.dsi.fastutil.objects.Object2BooleanMap;\nimport it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ConstantDynamic;\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.FieldInsnNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.JumpInsnNode;\nimport org.objectweb.asm.tree.LdcInsnNode;\nimport org.objectweb.asm.tree.LookupSwitchInsnNode;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.TableSwitchInsnNode;\nimport org.objectweb.asm.tree.TypeInsnNode;\nimport org.objectweb.asm.tree.VarInsnNode;\nimport org.objectweb.asm.tree.analysis.AnalyzerException;\nimport org.objectweb.asm.tree.analysis.Interpreter;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.util.BlwUtil;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.ReFrame;\nimport software.coley.recaf.util.analysis.ReInterpreter;\nimport software.coley.recaf.util.analysis.lookup.InvokeStaticLookup;\nimport software.coley.recaf.util.analysis.lookup.InvokeVirtualLookup;\nimport software.coley.recaf.util.analysis.value.ArrayValue;\nimport software.coley.recaf.util.analysis.value.IntValue;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.impl.ArrayValueImpl;\nimport software.coley.recaf.util.visitors.MemberFilteringVisitor;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.RuntimeWorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.OptionalInt;\nimport java.util.function.BiPredicate;\nimport java.util.function.Predicate;\n\n/**\n * Simple method evaluator.\n *\n * @author Matt Coley\n */\npublic class Evaluator {\n\tprivate static final Object2BooleanMap<String> evaluationSupportCache = new Object2BooleanOpenHashMap<>();\n\tprivate static final InstanceFactory instanceFactory = new InstanceFactory();\n\tprivate final Workspace workspace;\n\tprivate final ReInterpreter interpreter;\n\tprivate final FieldCacheManager fieldCacheManager;\n\tprivate final boolean evaluateInternals;\n\tprivate final int maxSteps;\n\tprivate int stepAllocation;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull classes from.\n\t * @param interpreter\n\t * \t\tInterpreter to evaluate instructions with.\n\t * @param fieldCacheManager\n\t * \t\tSupport class for tracking instance data.\n\t * @param maxSteps\n\t * \t\tMaximum number of steps to allow when evaluating a method.\n\t * @param evaluateInternals\n\t * \t\tFlag to allow evaluation of methods defined by classes of internal resources\n\t * \t\t<i>(Mainly the {@link RuntimeWorkspaceResource} to facilitate emulating core JDK methods)</i>\n\t */\n\tpublic Evaluator(@Nonnull Workspace workspace, @Nonnull ReInterpreter interpreter,\n\t                 @Nonnull FieldCacheManager fieldCacheManager, int maxSteps, boolean evaluateInternals) {\n\t\tthis.workspace = workspace;\n\t\tthis.interpreter = interpreter;\n\t\tthis.fieldCacheManager = fieldCacheManager;\n\t\tthis.maxSteps = maxSteps;\n\t\tthis.stepAllocation = maxSteps;\n\t\tthis.evaluateInternals = evaluateInternals;\n\t}\n\n\t/**\n\t * @param className\n\t * \t\tName of class defining the target method.\n\t * @param methodName\n\t * \t\tName of the target method.\n\t * @param methodDescriptor\n\t * \t\tDescriptor of the target method.\n\t *\n\t * @return {@code true} when all instructions in the method can be evaluated.\n\t */\n\tpublic boolean canEvaluate(@Nonnull String className,\n\t                           @Nonnull String methodName,\n\t                           @Nonnull String methodDescriptor) {\n\t\tString key = className + '.' + methodName + methodDescriptor;\n\t\tsynchronized (evaluationSupportCache) {\n\t\t\treturn evaluationSupportCache.computeIfAbsent(key, k -> {\n\t\t\t\t// Find class in workspace.\n\t\t\t\tClassPathNode classPath = workspace.findClass(evaluateInternals, className);\n\t\t\t\tif (classPath == null)\n\t\t\t\t\treturn false;\n\n\t\t\t\t// Ensure method exists in class.\n\t\t\t\tJvmClassInfo jvmClass = classPath.getValue().asJvmClass();\n\t\t\t\tMethodMember method = jvmClass.getDeclaredMethod(methodName, methodDescriptor);\n\t\t\t\tif (method == null)\n\t\t\t\t\treturn false;\n\n\t\t\t\t// Extract method-node model and delegate to evaluate check.\n\t\t\t\tClassNode node = new ClassNode();\n\t\t\t\tjvmClass.getClassReader().accept(new MemberFilteringVisitor(node, method), ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);\n\t\t\t\treturn node.methods.size() == 1 && canEvaluate(node.methods.getFirst());\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * @param method\n\t * \t\tMethod to check for evaluation support.\n\t *\n\t * @return {@code true} when all instructions in the method can be evaluated.\n\t */\n\tpublic boolean canEvaluate(@Nonnull MethodNode method) {\n\t\t// Cannot be abstract / have no instructions.\n\t\tif (method.instructions == null || method.instructions.size() == 0)\n\t\t\treturn false;\n\n\t\t// Must not have any unsupported instructions\n\t\tExecutingFrame frame = new ExecutingFrame(method);\n\t\tfor (AbstractInsnNode instruction : method.instructions)\n\t\t\tif (!frame.canEvaluateInsn(instruction, interpreter))\n\t\t\t\treturn false;\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param instructionBlock\n\t * \t\tBlock of instructions to evaluate.\n\t * \t\tThis may be an incomplete expression with no {@code return} instruction.\n\t * \t\tIn such cases, the resulting stack top value will be returned.\n\t * @param originFrame\n\t * \t\tThe origin frame to initiate evaluation state from.\n\t * @param methodAccess\n\t * \t\tThe access flags of the method defining the given instruction block.\n\t *\n\t * @return {@code true} when all instructions in the given list can be evaluated.\n\t */\n\tpublic boolean canEvaluateBlock(@Nonnull InsnList instructionBlock,\n\t                                @Nonnull ReFrame originFrame,\n\t                                int methodAccess) {\n\t\t// Must not have any unsupported instructions\n\t\tExecutingFrame frame = new ExecutingFrame(0xFF, 0xFF, methodAccess);\n\t\tfor (AbstractInsnNode instruction : instructionBlock)\n\t\t\tif (!frame.canEvaluateInsn(instruction, interpreter))\n\t\t\t\treturn false;\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param className\n\t * \t\tName of class defining the target method.\n\t * @param methodName\n\t * \t\tName of the target method.\n\t * @param methodDescriptor\n\t * \t\tDescriptor of the target method.\n\t * @param classInstance\n\t * \t\tInstance of {@code this} for instance methods.\n\t * \t\tCan be {@code null} for {@code static} methods.\n\t * @param parameters\n\t * \t\tParameters to pass to the target method.\n\t *\n\t * @return Result of evaluating the target method with the given parameters.\n\t */\n\t@Nonnull\n\tpublic EvaluationResult evaluate(@Nonnull String className,\n\t                                 @Nonnull String methodName,\n\t                                 @Nonnull String methodDescriptor,\n\t                                 @Nullable ReValue classInstance,\n\t                                 @Nonnull List<ReValue> parameters) {\n\t\tType methodType = Type.getMethodType(methodDescriptor);\n\t\tif (methodType.getReturnType() == Type.VOID_TYPE)\n\t\t\treturn EvaluationResult.cannotEvaluate(\"Method must yield a value\");\n\n\t\tClassPathNode classPath = workspace.findClass(evaluateInternals, className);\n\t\tif (classPath == null)\n\t\t\treturn EvaluationResult.cannotEvaluate(\"Class not found in workspace: \" + className);\n\n\t\tJvmClassInfo classInfo = classPath.getValue().asJvmClass();\n\t\tif (classInfo.getDeclaredMethod(methodName, methodDescriptor) == null)\n\t\t\treturn EvaluationResult.cannotEvaluate(\"Method not found in class: \" + className + \".\" + methodName + methodDescriptor);\n\n\t\tClassNode classNode = new ClassNode();\n\t\tClassReader reader = classInfo.getClassReader();\n\t\treader.accept(classNode, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);\n\n\t\tfor (MethodNode methodNode : classNode.methods)\n\t\t\tif (methodName.equals(methodNode.name) && methodDescriptor.equals(methodNode.desc))\n\t\t\t\treturn evaluate(classNode, methodNode, classInstance, parameters);\n\n\t\treturn EvaluationResult.cannotEvaluate(\"Method exists in class model, but not in tree node representation\");\n\t}\n\n\t/**\n\t * @param classNode\n\t * \t\tClass defining the target method.\n\t * @param methodNode\n\t * \t\tTarget method.\n\t * @param classInstance\n\t * \t\tInstance of {@code this} for instance methods.\n\t * \t\tCan be {@code null} for {@code static} methods.\n\t * @param parameters\n\t * \t\tParameters to pass to the target method.\n\t *\n\t * @return Result of evaluating the target method with the given parameters.\n\t */\n\t@Nonnull\n\tpublic EvaluationResult evaluate(@Nonnull ClassNode classNode,\n\t                                 @Nonnull MethodNode methodNode,\n\t                                 @Nullable ReValue classInstance,\n\t                                 @Nonnull List<ReValue> parameters) {\n\t\t// Must support evaluation\n\t\tif (!canEvaluate(methodNode))\n\t\t\treturn EvaluationResult.cannotEvaluate(\"Target method does not support evaluation: \" + classNode.name + \".\" + methodNode.name + methodNode.desc);\n\n\t\t// Sanity check parameters\n\t\tType methodType = Type.getMethodType(methodNode.desc);\n\t\tif (parameters.size() != methodType.getArgumentCount())\n\t\t\treturn EvaluationResult.cannotEvaluate(\"Mismatched parameter count, method expects \"\n\t\t\t\t\t+ methodType.getArgumentCount() + \" but was given \" + parameters.size() + \" parameters\");\n\t\tType[] argumentTypes = methodType.getArgumentTypes();\n\t\tfor (int i = 0; i < argumentTypes.length; i++) {\n\t\t\tType parameterType = parameters.get(i).type();\n\t\t\tif (parameterType == null)\n\t\t\t\treturn EvaluationResult.cannotEvaluate(\"Unknown passed parameter type at index \" + i);\n\t\t\tint expectedSort = argumentTypes[i].getSort();\n\t\t\tint actualSort = parameterType.getSort();\n\t\t\tif (expectedSort != actualSort && !(expectedSort <= Type.INT && actualSort < Type.INT))\n\t\t\t\treturn EvaluationResult.cannotEvaluate(\"Mismatched parameter type at index \" + i);\n\t\t}\n\n\t\t// Create initial frame\n\t\tExecutingFrame frame = new ExecutingFrame(methodNode);\n\t\tfor (int i = 0; i < methodNode.maxLocals; i++)\n\t\t\tframe.setLocal(i, i < parameters.size() ? parameters.get(i) : interpreter.newEmptyValue(i));\n\t\tif (!AccessFlag.isStatic(methodNode.access))\n\t\t\tframe.setLocal(0, classInstance);\n\n\t\t// Handle execution\n\t\tInsnList instructions = methodNode.instructions;\n\t\tAbstractInsnNode pc = instructions.getFirst();\n\t\twhile (stepAllocation > 0) {\n\t\t\ttry {\n\t\t\t\tpc = frame.evaluate(pc, interpreter);\n\t\t\t\tReValue retVal = frame.returnValue;\n\t\t\t\tif (retVal != null) {\n\t\t\t\t\tif (retVal instanceof InstancedObjectValue<?> instanced && instanced.getRealInstance() != null)\n\t\t\t\t\t\tretVal = instanced.unmap();\n\t\t\t\t\treturn new EvaluationYieldResult(retVal);\n\t\t\t\t}\n\t\t\t} catch (AnalyzerException e) {\n\t\t\t\treturn EvaluationResult.cannotEvaluate(\"Failed executing instruction: \" + BlwUtil.toString(pc), e);\n\t\t\t} catch (NoNextException e) {\n\t\t\t\treturn EvaluationResult.cannotEvaluate(\"Execution falls through end\", e);\n\t\t\t}\n\t\t\tstepAllocation--;\n\t\t}\n\t\treturn EvaluationResult.cannotEvaluate(\"Method did not yield an value in \" + maxSteps + \" steps\");\n\t}\n\n\t/**\n\t * @param instructionBlock\n\t * \t\tBlock of instructions to evaluate.\n\t * \t\tThis may be an incomplete expression with no {@code return} instruction.\n\t * \t\tIn such cases, the resulting stack top value will be returned.\n\t * @param originFrame\n\t * \t\tThe origin frame to initiate evaluation state from.\n\t * @param methodAccess\n\t * \t\tThe access flags of the method defining the given instruction block.\n\t *\n\t * @return Result of evaluating the given block of instructions.\n\t */\n\t@Nonnull\n\tpublic EvaluationResult evaluateBlock(@Nonnull InsnList instructionBlock,\n\t                                      @Nonnull ReFrame originFrame,\n\t                                      int methodAccess) {\n\t\t// Must support evaluation\n\t\tif (!canEvaluateBlock(instructionBlock, originFrame, methodAccess))\n\t\t\treturn EvaluationResult.cannotEvaluate(\"Target block does not support evaluation\");\n\n\t\t// Create initial frame\n\t\tExecutingFrame frame = new ExecutingFrame(originFrame.getLocals(), originFrame.getMaxStackSize(), methodAccess);\n\t\tfor (int i = 0; i < originFrame.getLocals(); i++)\n\t\t\tframe.setLocal(i, originFrame.getLocal(i));\n\n\t\t// Handle execution\n\t\tAbstractInsnNode pc = instructionBlock.getFirst();\n\t\twhile (stepAllocation > 0) {\n\t\t\ttry {\n\t\t\t\tpc = frame.evaluate(pc, interpreter);\n\n\t\t\t\t// Check if return instruction assigned a value.\n\t\t\t\tif (frame.returnValue != null)\n\t\t\t\t\treturn new EvaluationYieldResult(frame.returnValue);\n\t\t\t} catch (AnalyzerException e) {\n\t\t\t\treturn EvaluationResult.cannotEvaluate(\"Failed executing instruction: \" + BlwUtil.toString(pc), e);\n\t\t\t} catch (NoNextException e) {\n\t\t\t\t// If there is no next instruction from the given block, then control flow has exited the block.\n\t\t\t\t// The intended use case for this is to be given incomplete segments of code and see what's on the\n\t\t\t\t// top at the end, so we will yield that here.\n\t\t\t\treturn new EvaluationYieldResult(frame.getStack(frame.getStackSize() - 1));\n\t\t\t}\n\t\t\tstepAllocation--;\n\t\t}\n\t\treturn EvaluationResult.cannotEvaluate(\"Block did not yield an value in \" + maxSteps + \" steps\");\n\t}\n\n\t/** Frame extension to support control flow processing of this evaluator. */\n\tprivate class ExecutingFrame extends ReFrame implements Opcodes {\n\t\tprivate AbstractInsnNode next;\n\t\tprivate ReValue returnValue;\n\t\tprivate final boolean isStatic;\n\n\t\tpublic ExecutingFrame(@Nonnull MethodNode method) {\n\t\t\tthis(method.maxLocals, method.maxStack, method.access);\n\t\t}\n\n\t\tpublic ExecutingFrame(int maxLocals, int maxStack, int access) {\n\t\t\tsuper(null, maxLocals, maxStack);\n\n\t\t\tisStatic = AccessFlag.isStatic(access);\n\t\t}\n\n\t\t/**\n\t\t * Determines if the given instruction can be evaluated by {@link #evaluate(AbstractInsnNode, ReInterpreter)}.\n\t\t *\n\t\t * @param insn\n\t\t * \t\tInstruction to evaluate.\n\t\t * @param interpreter\n\t\t * \t\tInterpreter to evaluate with.\n\t\t *\n\t\t * @return {@code true} when the given instruction can be evaluated via {@link #evaluate(AbstractInsnNode, ReInterpreter)}.\n\t\t */\n\t\tpublic boolean canEvaluateInsn(@Nonnull AbstractInsnNode insn, @Nonnull ReInterpreter interpreter) {\n\t\t\treturn switch (insn.getOpcode()) {\n\t\t\t\tcase JSR, RET, // Legacy instructions\n\t\t\t\t     INVOKEDYNAMIC // Dynamic linking not supported\n\t\t\t\t\t\t-> false;\n\t\t\t\tcase ALOAD -> {\n\t\t\t\t\t// Local variable 'this' is not supported until we make some form of instance tracking\n\t\t\t\t\tint local = ((VarInsnNode) insn).var;\n\t\t\t\t\tyield isStatic || local != 0;\n\t\t\t\t}\n\t\t\t\tcase LDC -> {\n\t\t\t\t\t// Dynamic linking + method handles not supported\n\t\t\t\t\tObject cst = ((LdcInsnNode) insn).cst;\n\t\t\t\t\tyield !(cst instanceof ConstantDynamic || cst instanceof Handle);\n\t\t\t\t}\n\t\t\t\tcase ATHROW -> {\n\t\t\t\t\t// TODO: Need to finish control-flow handling for this, then this would yield true.\n\t\t\t\t\tyield false;\n\t\t\t\t}\n\t\t\t\tcase NEW -> insn instanceof TypeInsnNode tin && instanceFactory.isSupportedType(tin.desc);\n\t\t\t\tcase INVOKESPECIAL, INVOKEINTERFACE, INVOKEVIRTUAL -> {\n\t\t\t\t\tif (insn instanceof MethodInsnNode min) {\n\t\t\t\t\t\t// Check if the method can be instanced.\n\t\t\t\t\t\tif (instanceFactory.getMethodHandler(min) != null || instanceFactory.getMapper(min) != null)\n\t\t\t\t\t\t\tyield true;\n\n\t\t\t\t\t\t// Check if the method is declared in the workspace, meaning we can evaluate it.\n\t\t\t\t\t\tClassPathNode targetClassPath = workspace.findClass(evaluateInternals, min.owner);\n\t\t\t\t\t\tif (targetClassPath != null)\n\t\t\t\t\t\t\tyield true;\n\n\t\t\t\t\t\t// Check if we have a value lookup for the method.\n\t\t\t\t\t\tInvokeVirtualLookup lookup = interpreter.getInvokeVirtualLookup();\n\t\t\t\t\t\tyield lookup != null && lookup.hasLookup(min);\n\t\t\t\t\t}\n\t\t\t\t\tyield false;\n\t\t\t\t}\n\t\t\t\tcase INVOKESTATIC -> {\n\t\t\t\t\tif (insn instanceof MethodInsnNode min) {\n\t\t\t\t\t\t// Check if the method can be instanced.\n\t\t\t\t\t\tif (instanceFactory.getMapper(min) != null || instanceFactory.getMethodHandler(min) != null)\n\t\t\t\t\t\t\tyield true;\n\n\t\t\t\t\t\t// Check if the method is declared in the workspace, meaning we can evaluate it.\n\t\t\t\t\t\tClassPathNode targetClassPath = workspace.findClass(evaluateInternals, min.owner);\n\t\t\t\t\t\tif (targetClassPath != null)\n\t\t\t\t\t\t\tyield true;\n\n\t\t\t\t\t\t// Check if we have a value lookup for the method.\n\t\t\t\t\t\tInvokeStaticLookup lookup = interpreter.getInvokeStaticLookup();\n\t\t\t\t\t\tyield lookup != null && lookup.hasLookup(min);\n\t\t\t\t\t}\n\t\t\t\t\tyield false;\n\t\t\t\t}\n\t\t\t\tdefault -> true;\n\t\t\t};\n\t\t}\n\n\t\t/**\n\t\t * Wrapper for {@link #execute(AbstractInsnNode, Interpreter)}.\n\t\t *\n\t\t * @param insn\n\t\t * \t\tInstruction to evaluate.\n\t\t * @param interpreter\n\t\t * \t\tInterpreter to evaluate with.\n\t\t *\n\t\t * @return Next instruction to evaluate <i>(following control flow rules)</i>.\n\t\t *\n\t\t * @throws AnalyzerException\n\t\t * \t\tWhen the instruction cannot be evaluated.\n\t\t * @throws NoNextException\n\t\t * \t\tWhen there is no next instruction to execute.\n\t\t */\n\t\t@Nonnull\n\t\tpublic AbstractInsnNode evaluate(@Nonnull AbstractInsnNode insn, @Nonnull ReInterpreter interpreter) throws AnalyzerException, NoNextException {\n\t\t\tAbstractInsnNode next = switch (insn.getOpcode()) {\n\t\t\t\tcase GOTO -> ((JumpInsnNode) insn).label;\n\t\t\t\tcase IFEQ -> conditional(insn, i -> i.isEqualTo(0));\n\t\t\t\tcase IFNE -> conditional(insn, i -> i.isNotEqualTo(0));\n\t\t\t\tcase IFLT -> conditional(insn, i -> i.isLessThan(0));\n\t\t\t\tcase IFGE -> conditional(insn, i -> i.isGreaterThanOrEqual(0));\n\t\t\t\tcase IFGT -> conditional(insn, i -> i.isGreaterThan(0));\n\t\t\t\tcase IFLE -> conditional(insn, i -> i.isLessThanOrEqual(0));\n\t\t\t\tcase IFNULL -> {\n\t\t\t\t\tReValue value = pop();\n\t\t\t\t\tif (value instanceof ObjectValue ov && ov.isNull())\n\t\t\t\t\t\tyield ((JumpInsnNode) insn).label;\n\t\t\t\t\tyield insn.getNext();\n\t\t\t\t}\n\t\t\t\tcase IFNONNULL -> {\n\t\t\t\t\tReValue value = pop();\n\t\t\t\t\tif (value instanceof ObjectValue ov && ov.isNotNull())\n\t\t\t\t\t\tyield ((JumpInsnNode) insn).label;\n\t\t\t\t\tyield insn.getNext();\n\t\t\t\t}\n\t\t\t\tcase IF_ICMPEQ -> conditional(insn, IntValue::isEqualTo);\n\t\t\t\tcase IF_ICMPNE -> conditional(insn, IntValue::isNotEqualTo);\n\t\t\t\tcase IF_ICMPLT -> conditional(insn, IntValue::isLessThan);\n\t\t\t\tcase IF_ICMPGE -> conditional(insn, IntValue::isGreaterThanOrEqual);\n\t\t\t\tcase IF_ICMPGT -> conditional(insn, IntValue::isGreaterThan);\n\t\t\t\tcase IF_ICMPLE -> conditional(insn, IntValue::isLessThanOrEqual);\n\t\t\t\tcase IF_ACMPEQ -> {\n\t\t\t\t\tReValue value2 = pop();\n\t\t\t\t\tReValue value1 = pop();\n\t\t\t\t\tif (value1 == value2)\n\t\t\t\t\t\tyield ((JumpInsnNode) insn).label;\n\t\t\t\t\tyield insn.getNext();\n\t\t\t\t}\n\t\t\t\tcase IF_ACMPNE -> {\n\t\t\t\t\tReValue value2 = pop();\n\t\t\t\t\tReValue value1 = pop();\n\t\t\t\t\tif (value1 != value2)\n\t\t\t\t\t\tyield ((JumpInsnNode) insn).label;\n\t\t\t\t\tyield insn.getNext();\n\t\t\t\t}\n\t\t\t\tcase TABLESWITCH -> {\n\t\t\t\t\tReValue value = pop();\n\t\t\t\t\tif (insn instanceof TableSwitchInsnNode table && value instanceof IntValue iv && iv.hasKnownValue()) {\n\t\t\t\t\t\tint arg = iv.value().getAsInt();\n\t\t\t\t\t\tint keyIndex = (arg > table.max || arg < table.min) ? -1 : (arg - table.min);\n\t\t\t\t\t\tyield keyIndex == -1 ? table.dflt : table.labels.get(keyIndex);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow new AnalyzerException(insn, \"Invalid table-switch state\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcase LOOKUPSWITCH -> {\n\t\t\t\t\tReValue value = pop();\n\t\t\t\t\tif (insn instanceof LookupSwitchInsnNode table && value instanceof IntValue iv && iv.hasKnownValue()) {\n\t\t\t\t\t\tint arg = iv.value().getAsInt();\n\t\t\t\t\t\tint keyIndex = -1;\n\t\t\t\t\t\tfor (int j = 0; j < table.keys.size(); j++) {\n\t\t\t\t\t\t\tint key = table.keys.get(j);\n\t\t\t\t\t\t\tif (arg == key) {\n\t\t\t\t\t\t\t\tkeyIndex = j;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield keyIndex == -1 ? table.dflt : table.labels.get(keyIndex);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow new AnalyzerException(insn, \"Invalid lookup-switch state\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcase IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> {\n\t\t\t\t\treturnValue = peek();\n\t\t\t\t\tyield insn;\n\t\t\t\t}\n\t\t\t\tcase ATHROW -> {\n\t\t\t\t\t// TODO: Find handler and set instruction pointer 'insn' there with exception being only stack element\n\t\t\t\t\t//  - Need to also support this for other exception throwing behavior for other instructions\n\t\t\t\t\t//    - Math operations that fail (div by zero)\n\t\t\t\t\t//    - Null pointer exceptions from field/method ops\n\t\t\t\t\t//    - Array ops (index out of bounds, null pointer)\n\t\t\t\t\tthrow new UnsupportedOperationException();\n\t\t\t\t}\n\t\t\t\tcase NEW -> {\n\t\t\t\t\tif (insn instanceof TypeInsnNode tin) {\n\t\t\t\t\t\tpush(new InstancedObjectValue<>(Type.getObjectType(tin.desc)));\n\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t}\n\t\t\t\t\tthrow new AnalyzerException(insn, \"Invalid new state\");\n\t\t\t\t}\n\t\t\t\tcase GETFIELD -> {\n\t\t\t\t\tif (insn instanceof FieldInsnNode fieldInsn) {\n\t\t\t\t\t\t// Try to get the field value from the instance cache.\n\t\t\t\t\t\tReValue receiver = peek();\n\t\t\t\t\t\tReValue value = fieldCacheManager.getInstanceFieldCache(receiver).getField(fieldInsn.name, fieldInsn.desc);\n\t\t\t\t\t\tif (value != null) {\n\t\t\t\t\t\t\tpop(); // Pop receiver\n\t\t\t\t\t\t\tpush(value); // Push field value\n\t\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Fall back to normal execution, which can handle some remaining cases.\n\t\t\t\t\t\texecute(insn, interpreter);\n\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t}\n\t\t\t\t\tthrow new AnalyzerException(insn, \"Invalid getfield state\");\n\t\t\t\t}\n\t\t\t\tcase GETSTATIC -> {\n\t\t\t\t\tif (insn instanceof FieldInsnNode fieldInsn) {\n\t\t\t\t\t\t// Try to get the field value from the static cache.\n\t\t\t\t\t\tReValue value = fieldCacheManager.getStaticFieldCache(fieldInsn.owner).getField(fieldInsn.name, fieldInsn.desc);\n\t\t\t\t\t\tif (value != null) {\n\t\t\t\t\t\t\tpush(value);\n\t\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Fall back to normal execution, which can handle some remaining cases.\n\t\t\t\t\t\texecute(insn, interpreter);\n\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t}\n\t\t\t\t\tthrow new AnalyzerException(insn, \"Invalid getstatic state\");\n\t\t\t\t}\n\t\t\t\tcase PUTFIELD -> {\n\t\t\t\t\t// Assign the top value to the instance field in the cache.\n\t\t\t\t\tif (insn instanceof FieldInsnNode fin) {\n\t\t\t\t\t\tReValue value = pop();\n\t\t\t\t\t\tReValue receiver = pop();\n\t\t\t\t\t\tfieldCacheManager.getInstanceFieldCache(receiver).setField(fin.name, fin.desc, value);\n\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t}\n\t\t\t\t\tthrow new AnalyzerException(insn, \"Invalid putfield state\");\n\t\t\t\t}\n\t\t\t\tcase PUTSTATIC -> {\n\t\t\t\t\t// Assign the top value to the static field in the cache.\n\t\t\t\t\tif (insn instanceof FieldInsnNode fin) {\n\t\t\t\t\t\tReValue value = pop();\n\t\t\t\t\t\tfieldCacheManager.getStaticFieldCache(fin.owner).setField(fin.name, fin.desc, value);\n\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t}\n\t\t\t\t\tthrow new AnalyzerException(insn, \"Invalid putstatic state\");\n\t\t\t\t}\n\t\t\t\tcase INVOKESPECIAL -> {\n\t\t\t\t\tif (insn instanceof MethodInsnNode min) {\n\t\t\t\t\t\tString methodDescriptor = min.desc;\n\n\t\t\t\t\t\t// Handle instance initialization for supported types.\n\t\t\t\t\t\tInstanceMapper mapper = instanceFactory.getMapper(min);\n\t\t\t\t\t\tif (mapper != null) {\n\t\t\t\t\t\t\t// Collect parameters.\n\t\t\t\t\t\t\tList<ReValue> valueList = new ArrayList<>();\n\t\t\t\t\t\t\tfor (int i = Type.getArgumentCount(methodDescriptor); i > 0; --i)\n\t\t\t\t\t\t\t\tvalueList.addFirst(pop());\n\n\t\t\t\t\t\t\t// Get the receiver and populate the instance if it's a supported type.\n\t\t\t\t\t\t\tReValue receiver = pop();\n\t\t\t\t\t\t\tif (receiver instanceof InstancedObjectValue<?> instancedReceiver) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tObject instance = mapper.map(instancedReceiver, valueList);\n\t\t\t\t\t\t\t\t\tinstancedReceiver.setRealInstance(Unchecked.cast(instance));\n\t\t\t\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\t\t\t\t// If the mapper fails, we can still fall back to normal execution, which may be able to handle some cases.\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Fall back to normal execution, which can handle some remaining cases, including value lookups.\n\t\t\t\t\t\t\texecute(insn, interpreter);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t}\n\t\t\t\t\tthrow new AnalyzerException(insn, \"Invalid invokespecial state\");\n\t\t\t\t}\n\t\t\t\tcase INVOKEVIRTUAL, INVOKEINTERFACE -> {\n\t\t\t\t\tif (insn instanceof MethodInsnNode min) {\n\t\t\t\t\t\t// Collect parameters.\n\t\t\t\t\t\tList<ReValue> valueList = new ArrayList<>();\n\t\t\t\t\t\tfor (int i = Type.getArgumentCount(min.desc); i > 0; --i)\n\t\t\t\t\t\t\tvalueList.addFirst(pop());\n\n\t\t\t\t\t\t// Get the receiver and check if we can handle the invoke with instance support or a value lookup.\n\t\t\t\t\t\tReValue receiver = pop();\n\t\t\t\t\t\tboolean isVoid = Type.getReturnType(min.desc) == Type.VOID_TYPE;\n\t\t\t\t\t\tif (receiver instanceof InstancedObjectValue<?> instancedReceiver && instancedReceiver.getRealInstance() != null) {\n\t\t\t\t\t\t\tMethodInvokeHandler<?> handler = instanceFactory.getMethodHandler(min);\n\t\t\t\t\t\t\tif (handler != null) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tReValue result = handler.invoke(instancedReceiver, Unchecked.cast(instancedReceiver.getRealInstance()), valueList);\n\t\t\t\t\t\t\t\t\tif (isVoid)\n\t\t\t\t\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t\t\t\t\tif (result != null) {\n\t\t\t\t\t\t\t\t\t\tpush(result);\n\t\t\t\t\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\t\t\t\t// TODO: Need to handle exception throwing control flow\n\t\t\t\t\t\t\t\t\t//  - Yield appropriate exception block handler instead of normal next instruction\n\t\t\t\t\t\t\t\t\t//  - Need to have some way to determine if the exception should be thrown (bad usage of method)\n\t\t\t\t\t\t\t\t\t//    vs a problem with our handler logic itself.\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Check if the method is defined in the workspace and can be evaluated.\n\t\t\t\t\t\tif (canEvaluate(min.owner, min.name, min.desc)) {\n\t\t\t\t\t\t\tEvaluationResult result = Evaluator.this.evaluate(min.owner, min.name, min.desc, receiver, valueList);\n\t\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\t\tcase EvaluationYieldResult yielded -> {\n\t\t\t\t\t\t\t\t\tpush(yielded.value());\n\t\t\t\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase EvaluationThrowsResult thrown -> {\n\t\t\t\t\t\t\t\t\t// TODO: Need to handle exception throwing control flow\n\t\t\t\t\t\t\t\t\t//  - Yield appropriate exception block handler instead of normal next instruction\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase EvaluationFailureResult failure -> {\n\t\t\t\t\t\t\t\t\t// No-op, fallthrough will attempt to handle this.\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Fall back to normal execution, which can handle some remaining cases, including value lookups.\n\t\t\t\t\t\t// - Need to unmap values here since the underlying lookup system doesn't know how to handle our wrapped values.\n\t\t\t\t\t\tvalueList.addFirst(receiver);\n\t\t\t\t\t\tList<ReValue> unmappedValueList = unmapValues(valueList);\n\t\t\t\t\t\tif (isVoid) {\n\t\t\t\t\t\t\tinterpreter.naryOperation(insn, valueList);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tpush(interpreter.naryOperation(insn, valueList));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t}\n\t\t\t\t\tthrow new AnalyzerException(insn, \"Invalid invokevirtual/interface state\");\n\t\t\t\t}\n\t\t\t\tcase INVOKESTATIC -> {\n\t\t\t\t\tif (insn instanceof MethodInsnNode min) {\n\t\t\t\t\t\t// Collect parameters.\n\t\t\t\t\t\tList<ReValue> valueList = new ArrayList<>();\n\t\t\t\t\t\tfor (int i = Type.getArgumentCount(min.desc); i > 0; --i)\n\t\t\t\t\t\t\tvalueList.addFirst(pop());\n\n\t\t\t\t\t\t// Check if we have a mapper for this method (assuming it is a static factory for a supported type)\n\t\t\t\t\t\tType returnType = Type.getReturnType(min.desc);\n\t\t\t\t\t\tboolean isVoid = returnType == Type.VOID_TYPE;\n\t\t\t\t\t\tif (!isVoid) {\n\t\t\t\t\t\t\tInstanceMapper mapper = instanceFactory.getMapper(min);\n\t\t\t\t\t\t\tif (mapper != null) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tInstancedObjectValue<?> returnValue = new InstancedObjectValue<>(returnType);\n\t\t\t\t\t\t\t\t\tObject value = mapper.map(returnValue, valueList);\n\t\t\t\t\t\t\t\t\treturnValue.setRealInstance(Unchecked.cast(value));\n\t\t\t\t\t\t\t\t\tpush(returnValue);\n\t\t\t\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\t\t\t\t// TODO: Need to handle exception throwing control flow\n\t\t\t\t\t\t\t\t\t//  - Yield appropriate exception block handler instead of normal next instruction\n\t\t\t\t\t\t\t\t\t//  - Need to have some way to determine if the exception should be thrown (bad usage of method)\n\t\t\t\t\t\t\t\t\t//    vs a problem with our handler logic itself.\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Check if the method is defined in the workspace and can be evaluated.\n\t\t\t\t\t\tif (canEvaluate(min.owner, min.name, min.desc)) {\n\t\t\t\t\t\t\tEvaluationResult result = Evaluator.this.evaluate(min.owner, min.name, min.desc, null, valueList);\n\t\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\t\tcase EvaluationYieldResult yielded -> {\n\t\t\t\t\t\t\t\t\tpush(yielded.value());\n\t\t\t\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase EvaluationThrowsResult thrown -> {\n\t\t\t\t\t\t\t\t\t// TODO: Need to handle exception throwing control flow\n\t\t\t\t\t\t\t\t\t//  - Yield appropriate exception block handler instead of normal next instruction\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase EvaluationFailureResult failure -> {\n\t\t\t\t\t\t\t\t\t// No-op, fallthrough will attempt to handle this.\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Fall back to normal execution, which can handle some remaining cases, including value lookups.\n\t\t\t\t\t\t// - Need to unmap values here since the underlying lookup system doesn't know how to handle our wrapped values.\n\t\t\t\t\t\tList<ReValue> unmappedValueList = unmapValues(valueList);\n\t\t\t\t\t\tif (isVoid) {\n\t\t\t\t\t\t\tinterpreter.naryOperation(insn, unmappedValueList);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tpush(interpreter.naryOperation(insn, unmappedValueList));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield insn.getNext();\n\t\t\t\t\t}\n\t\t\t\t\tthrow new AnalyzerException(insn, \"Invalid invokestatic state\");\n\n\t\t\t\t}\n\t\t\t\tcase JSR, RET -> {\n\t\t\t\t\tthrow new UnsupportedOperationException();\n\t\t\t\t}\n\t\t\t\tcase AASTORE -> {\n\t\t\t\t\texecute(insn, interpreter);\n\t\t\t\t\tyield insn.getNext();\n\t\t\t\t}\n\t\t\t\tdefault -> {\n\t\t\t\t\tif (insn.getOpcode() != -1) // Skip labels\n\t\t\t\t\t\texecute(insn, interpreter);\n\t\t\t\t\tyield insn.getNext();\n\t\t\t\t}\n\t\t\t};\n\t\t\tif (next == null)\n\t\t\t\tthrow NoNextException.INSTANCE;\n\t\t\treturn next;\n\t\t}\n\n\t\t@Nonnull\n\t\tprivate AbstractInsnNode conditional(@Nonnull AbstractInsnNode insn, @Nonnull Predicate<IntValue> cmp) {\n\t\t\tReValue value = pop();\n\t\t\tif (value instanceof IntValue iv && cmp.test(iv))\n\t\t\t\treturn ((JumpInsnNode) insn).label;\n\t\t\treturn insn.getNext();\n\t\t}\n\n\t\t@Nonnull\n\t\tprivate AbstractInsnNode conditional(@Nonnull AbstractInsnNode insn, @Nonnull BiPredicate<IntValue, IntValue> cmp) {\n\t\t\tReValue value2 = pop();\n\t\t\tReValue value1 = pop();\n\t\t\tif (value1 instanceof IntValue i1 && value2 instanceof IntValue i2 && cmp.test(i1, i2))\n\t\t\t\treturn ((JumpInsnNode) insn).label;\n\t\t\treturn insn.getNext();\n\t\t}\n\n\t\t@Nonnull\n\t\tpublic ReValue peek() {\n\t\t\treturn peek(0);\n\t\t}\n\n\t\t@Nonnull\n\t\tpublic ReValue peek(int offset) {\n\t\t\treturn getStack(getStackSize() - 1 - offset);\n\t\t}\n\n\t\t@Nonnull\n\t\tprivate static List<ReValue> unmapValues(@Nonnull List<ReValue> values) {\n\t\t\treturn values.stream()\n\t\t\t\t\t.map(v -> {\n\t\t\t\t\t\tif (v instanceof InstancedObjectValue<?> instanced) return instanced.unmap();\n\t\t\t\t\t\tif (v instanceof ArrayValue array) {\n\t\t\t\t\t\t\tOptionalInt dimension = array.getFirstDimensionLength();\n\t\t\t\t\t\t\tif (dimension.isPresent()) {\n\t\t\t\t\t\t\t\tint length = dimension.getAsInt();\n\t\t\t\t\t\t\t\tList<ReValue> arrayValues = new ArrayList<>(length);\n\t\t\t\t\t\t\t\tfor (int i = 0; i < length; i++) {\n\t\t\t\t\t\t\t\t\tReValue element = array.getValue(i);\n\t\t\t\t\t\t\t\t\tarrayValues.add(element);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tList<ReValue> unmappedArrayValues = unmapValues(arrayValues);\n\t\t\t\t\t\t\t\treturn new ArrayValueImpl(array.type(), Nullness.NOT_NULL, length, unmappedArrayValues::get);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn array;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn v;\n\t\t\t\t\t})\n\t\t\t\t\t.toList();\n\t\t}\n\t}\n\n\t/** Dummy exception to signal out-of-bounds flow in the evaluate methods. */\n\tprivate static class NoNextException extends Exception {\n\t\tprivate static final NoNextException INSTANCE = new NoNextException();\n\n\t\tprivate NoNextException() {}\n\n\t\t@Override\n\t\tpublic synchronized Throwable fillInStackTrace() {\n\t\t\t// Don't care.\n\t\t\treturn this;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/eval/FieldCache.java",
    "content": "package software.coley.recaf.util.analysis.eval;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Support for tracking field values.\n *\n * @author Matt Coley\n */\npublic class FieldCache {\n\t/** Maps field names/types to their values. */\n\tprivate final Map<String, ReValue> fields = new HashMap<>();\n\n\t/**\n\t * Set a field value.\n\t *\n\t * @param name\n\t * \t\tField name.\n\t * @param desc\n\t * \t\tField descriptor.\n\t * @param value\n\t * \t\tValue to set.\n\t */\n\tpublic void setField(@Nonnull String name, @Nonnull String desc, @Nonnull ReValue value) {\n\t\tfields.put(getKey(name, desc), value);\n\t}\n\n\t/**\n\t * Get a field value.\n\t *\n\t * @param name\n\t * \t\tField name.\n\t * @param desc\n\t * \t\tField descriptor.\n\t *\n\t * @return Value of the field, or {@code null} if no value is known.\n\t * An actual {@code null} value is represented by {@link ObjectValue#VAL_OBJECT_NULL}.\n\t */\n\t@Nullable\n\tpublic ReValue getField(@Nonnull String name, @Nonnull String desc) {\n\t\treturn fields.get(getKey(name, desc));\n\t}\n\n\t@Nonnull\n\tprivate static String getKey(@Nonnull String name, @Nonnull String desc) {\n\t\treturn name + \".\" + desc;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/eval/FieldCacheManager.java",
    "content": "package software.coley.recaf.util.analysis.eval;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.IdentityHashMap;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Support for tracking instance and static field values during evaluation.\n *\n * @author Matt Coley\n */\npublic class FieldCacheManager {\n\t/** Maps instances to their field states. */\n\tprotected final Map<ReValue, FieldCache> instanceFields = Collections.synchronizedMap(new IdentityHashMap<>());\n\t/** Maps class names to their static field states. */\n\tprotected final Map<String, FieldCache> staticFields = new ConcurrentHashMap<>();\n\n\t/**\n\t * Clear all instance and static field caches.\n\t */\n\tpublic void reset() {\n\t\tinstanceFields.clear();\n\t\tstaticFields.clear();\n\t}\n\n\t/**\n\t * Get the static field cache for a given class.\n\t *\n\t * @param className\n\t * \t\tInternal class name.\n\t *\n\t * @return Field cache of static fields for the given class.\n\t */\n\t@Nonnull\n\tpublic FieldCache getStaticFieldCache(@Nonnull String className) {\n\t\treturn staticFields.computeIfAbsent(className, c -> new FieldCache());\n\t}\n\n\t/**\n\t * Get the instance field cache for a given instance.\n\t *\n\t * @param instance\n\t * \t\tValue instance.\n\t *\n\t * @return Field cache of static fields for the given class.\n\t */\n\t@Nonnull\n\tpublic FieldCache getInstanceFieldCache(@Nonnull ReValue instance) {\n\t\treturn instanceFields.computeIfAbsent(instance, c -> new FieldCache());\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/eval/InstanceFactory.java",
    "content": "package software.coley.recaf.util.analysis.eval;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport software.coley.recaf.util.analysis.gen.InstanceMapperGenerator;\nimport software.coley.recaf.util.analysis.gen.InstanceMethodInvokeHandlerGenerator;\nimport software.coley.recaf.util.analysis.gen.InstanceStaticMapperGenerator;\nimport software.coley.recaf.util.analysis.lookup.BasicLookupUtils;\nimport software.coley.recaf.util.analysis.value.ArrayValue;\nimport software.coley.recaf.util.analysis.value.DoubleValue;\nimport software.coley.recaf.util.analysis.value.FloatValue;\nimport software.coley.recaf.util.analysis.value.IntValue;\nimport software.coley.recaf.util.analysis.value.LongValue;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.StringValue;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\n\n/**\n * Factory for creating real instances of supported types and handling method calls on them.\n *\n * @author Matt Coley\n */\npublic class InstanceFactory extends BasicLookupUtils {\n\tprivate final Map<String, InstanceMapper> mappers = new HashMap<>();\n\tprivate final Map<String, MethodInvokeHandler<?>> methodHandlers = new HashMap<>();\n\tprivate final Set<String> supportedTypes = new HashSet<>();\n\n\t/**\n\t * Register supported types and method handlers.\n\t */\n\tpublic InstanceFactory() {\n\t\tregisterCtorMappers();\n\t\tregisterStaticMappers();\n\t\tregisterMethodHandlers();\n\t}\n\n\t/**\n\t * @see InstanceMethodInvokeHandlerGenerator\n\t */\n\t@SuppressWarnings(\"all\")\n\tprivate void registerMethodHandlers() {\n\t\t// java.lang.String\n\t\tregisterMethodHandler(\"java/lang/String\", \"equals\", \"(Ljava/lang/Object;)Z\", (ReValue host, String receiver, List<ReValue> args) -> z(receiver.equals(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"length\", \"()I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.length()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"toString\", \"()Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.toString()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"hashCode\", \"()I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.hashCode()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"getChars\", \"(II[CI)V\", (ReValue host, String receiver, List<ReValue> args) -> {\n\t\t\treceiver.getChars(i((IntValue) args.get(0)), i((IntValue) args.get(1)), arrc((ArrayValue) args.get(2)), i((IntValue) args.get(3)));\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/String\", \"compareTo\", \"(Ljava/lang/String;)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.compareTo(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"indexOf\", \"(Ljava/lang/String;II)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.indexOf(str((StringValue) args.get(0)), i((IntValue) args.get(1)), i((IntValue) args.get(2)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"indexOf\", \"(Ljava/lang/String;)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.indexOf(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"indexOf\", \"(I)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.indexOf(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"indexOf\", \"(II)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.indexOf(i((IntValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"indexOf\", \"(III)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.indexOf(i((IntValue) args.get(0)), i((IntValue) args.get(1)), i((IntValue) args.get(2)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"indexOf\", \"(Ljava/lang/String;I)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.indexOf(str((StringValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"charAt\", \"(I)C\", (ReValue host, String receiver, List<ReValue> args) -> c(receiver.charAt(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"codePointAt\", \"(I)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.codePointAt(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"codePointBefore\", \"(I)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.codePointBefore(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"codePointCount\", \"(II)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.codePointCount(i((IntValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"offsetByCodePoints\", \"(II)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.offsetByCodePoints(i((IntValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"getBytes\", \"()[B\", (ReValue host, String receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.getBytes()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"getBytes\", \"(Ljava/lang/String;)[B\", (ReValue host, String receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.getBytes(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"getBytes\", \"(II[BI)V\", (ReValue host, String receiver, List<ReValue> args) -> {\n\t\t\treceiver.getBytes(i((IntValue) args.get(0)), i((IntValue) args.get(1)), arrb((ArrayValue) args.get(2)), i((IntValue) args.get(3)));\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/String\", \"contentEquals\", \"(Ljava/lang/CharSequence;)Z\", (ReValue host, String receiver, List<ReValue> args) -> z(receiver.contentEquals(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"regionMatches\", \"(ZILjava/lang/String;II)Z\", (ReValue host, String receiver, List<ReValue> args) -> z(receiver.regionMatches(z((IntValue) args.get(0)), i((IntValue) args.get(1)), str((StringValue) args.get(2)), i((IntValue) args.get(3)), i((IntValue) args.get(4)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"regionMatches\", \"(ILjava/lang/String;II)Z\", (ReValue host, String receiver, List<ReValue> args) -> z(receiver.regionMatches(i((IntValue) args.get(0)), str((StringValue) args.get(1)), i((IntValue) args.get(2)), i((IntValue) args.get(3)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"startsWith\", \"(Ljava/lang/String;)Z\", (ReValue host, String receiver, List<ReValue> args) -> z(receiver.startsWith(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"startsWith\", \"(Ljava/lang/String;I)Z\", (ReValue host, String receiver, List<ReValue> args) -> z(receiver.startsWith(str((StringValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"lastIndexOf\", \"(Ljava/lang/String;)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.lastIndexOf(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"lastIndexOf\", \"(II)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.lastIndexOf(i((IntValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"lastIndexOf\", \"(Ljava/lang/String;I)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.lastIndexOf(str((StringValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"lastIndexOf\", \"(I)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.lastIndexOf(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"substring\", \"(I)Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.substring(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"substring\", \"(II)Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.substring(i((IntValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"isEmpty\", \"()Z\", (ReValue host, String receiver, List<ReValue> args) -> z(receiver.isEmpty()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"replace\", \"(CC)Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.replace(c((IntValue) args.get(0)), c((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"replace\", \"(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.replace(str((StringValue) args.get(0)), str((StringValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"matches\", \"(Ljava/lang/String;)Z\", (ReValue host, String receiver, List<ReValue> args) -> z(receiver.matches(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"replaceFirst\", \"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.replaceFirst(str((StringValue) args.get(0)), str((StringValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"replaceAll\", \"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.replaceAll(str((StringValue) args.get(0)), str((StringValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"split\", \"(Ljava/lang/String;)[Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.split(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"split\", \"(Ljava/lang/String;I)[Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.split(str((StringValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"splitWithDelimiters\", \"(Ljava/lang/String;I)[Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.splitWithDelimiters(str((StringValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"toLowerCase\", \"()Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.toLowerCase()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"toUpperCase\", \"()Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.toUpperCase()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"trim\", \"()Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.trim()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"strip\", \"()Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.strip()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"stripLeading\", \"()Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.stripLeading()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"stripTrailing\", \"()Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.stripTrailing()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"repeat\", \"(I)Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.repeat(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"isBlank\", \"()Z\", (ReValue host, String receiver, List<ReValue> args) -> z(receiver.isBlank()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"toCharArray\", \"()[C\", (ReValue host, String receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.toCharArray()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"equalsIgnoreCase\", \"(Ljava/lang/String;)Z\", (ReValue host, String receiver, List<ReValue> args) -> z(receiver.equalsIgnoreCase(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"compareToIgnoreCase\", \"(Ljava/lang/String;)I\", (ReValue host, String receiver, List<ReValue> args) -> i(receiver.compareToIgnoreCase(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"endsWith\", \"(Ljava/lang/String;)Z\", (ReValue host, String receiver, List<ReValue> args) -> z(receiver.endsWith(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"subSequence\", \"(II)Ljava/lang/CharSequence;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.subSequence(i((IntValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"concat\", \"(Ljava/lang/String;)Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.concat(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"contains\", \"(Ljava/lang/CharSequence;)Z\", (ReValue host, String receiver, List<ReValue> args) -> z(receiver.contains(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"indent\", \"(I)Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.indent(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"stripIndent\", \"()Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.stripIndent()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"translateEscapes\", \"()Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.translateEscapes()));\n\t\tregisterMethodHandler(\"java/lang/String\", \"formatted\", \"([Ljava/lang/Object;)Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.formatted(arrobj((ArrayValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/String\", \"intern\", \"()Ljava/lang/String;\", (ReValue host, String receiver, List<ReValue> args) -> str(receiver.intern()));\n\n\t\t// java.lang.StringBuilder\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"toString\", \"()Ljava/lang/String;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> str(receiver.toString()));\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"append\", \"(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.append(str((StringValue) args.get(0)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"append\", \"(Ljava/lang/CharSequence;II)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.append(str((StringValue) args.get(0)), i((IntValue) args.get(1)), i((IntValue) args.get(2)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"append\", \"([C)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.append(arrc((ArrayValue) args.get(0)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"append\", \"([CII)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.append(arrc((ArrayValue) args.get(0)), i((IntValue) args.get(1)), i((IntValue) args.get(2)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"append\", \"(Ljava/lang/Object;)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.append(objl((ObjectValue) args.get(0)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"append\", \"(Ljava/lang/String;)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.append(str((StringValue) args.get(0)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"append\", \"(J)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.append(j((LongValue) args.get(0)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"append\", \"(F)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.append(f((FloatValue) args.get(0)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"append\", \"(D)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.append(d((DoubleValue) args.get(0)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"append\", \"(Z)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.append(z((IntValue) args.get(0)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"append\", \"(C)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.append(c((IntValue) args.get(0)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"append\", \"(I)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.append(i((IntValue) args.get(0)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"reverse\", \"()Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.reverse();\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"compareTo\", \"(Ljava/lang/StringBuilder;)I\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> i(receiver.compareTo(BasicLookupUtils.<StringBuilder>obj((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"indexOf\", \"(Ljava/lang/String;I)I\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> i(receiver.indexOf(str((StringValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"indexOf\", \"(Ljava/lang/String;)I\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> i(receiver.indexOf(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"insert\", \"(ILjava/lang/CharSequence;)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.insert(i((IntValue) args.get(0)), str((StringValue) args.get(1)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"insert\", \"(ILjava/lang/String;)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.insert(i((IntValue) args.get(0)), str((StringValue) args.get(1)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"insert\", \"(I[C)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.insert(i((IntValue) args.get(0)), arrc((ArrayValue) args.get(1)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"insert\", \"(II)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.insert(i((IntValue) args.get(0)), i((IntValue) args.get(1)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"insert\", \"(ID)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.insert(i((IntValue) args.get(0)), d((DoubleValue) args.get(1)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"insert\", \"(IF)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.insert(i((IntValue) args.get(0)), f((FloatValue) args.get(1)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"insert\", \"(IJ)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.insert(i((IntValue) args.get(0)), j((LongValue) args.get(1)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"insert\", \"(IC)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.insert(i((IntValue) args.get(0)), c((IntValue) args.get(1)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"insert\", \"(IZ)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.insert(i((IntValue) args.get(0)), z((IntValue) args.get(1)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"insert\", \"(ILjava/lang/CharSequence;II)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.insert(i((IntValue) args.get(0)), str((StringValue) args.get(1)), i((IntValue) args.get(2)), i((IntValue) args.get(3)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"insert\", \"(ILjava/lang/Object;)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.insert(i((IntValue) args.get(0)), objl((ObjectValue) args.get(1)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"insert\", \"(I[CII)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.insert(i((IntValue) args.get(0)), arrc((ArrayValue) args.get(1)), i((IntValue) args.get(2)), i((IntValue) args.get(3)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"lastIndexOf\", \"(Ljava/lang/String;I)I\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> i(receiver.lastIndexOf(str((StringValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"lastIndexOf\", \"(Ljava/lang/String;)I\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> i(receiver.lastIndexOf(str((StringValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"replace\", \"(IILjava/lang/String;)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.replace(i((IntValue) args.get(0)), i((IntValue) args.get(1)), str((StringValue) args.get(2)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"repeat\", \"(Ljava/lang/CharSequence;I)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.repeat(str((StringValue) args.get(0)), i((IntValue) args.get(1)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"repeat\", \"(II)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.repeat(i((IntValue) args.get(0)), i((IntValue) args.get(1)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"delete\", \"(II)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.delete(i((IntValue) args.get(0)), i((IntValue) args.get(1)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"appendCodePoint\", \"(I)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.appendCodePoint(i((IntValue) args.get(0)));\n\t\t\treturn host;\n\t\t});\n\t\tregisterMethodHandler(\"java/lang/StringBuilder\", \"deleteCharAt\", \"(I)Ljava/lang/StringBuilder;\", (ReValue host, StringBuilder receiver, List<ReValue> args) -> {\n\t\t\treceiver.deleteCharAt(i((IntValue) args.get(0)));\n\t\t\treturn host;\n\t\t});\n\n\t\t// java.lang.Boolean\n\t\tregisterMethodHandler(\"java/lang/Boolean\", \"equals\", \"(Ljava/lang/Object;)Z\", (ReValue host, Boolean receiver, List<ReValue> args) -> z(receiver.equals(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Boolean\", \"toString\", \"()Ljava/lang/String;\", (ReValue host, Boolean receiver, List<ReValue> args) -> str(receiver.toString()));\n\t\tregisterMethodHandler(\"java/lang/Boolean\", \"hashCode\", \"()I\", (ReValue host, Boolean receiver, List<ReValue> args) -> i(receiver.hashCode()));\n\t\tregisterMethodHandler(\"java/lang/Boolean\", \"compareTo\", \"(Ljava/lang/Boolean;)I\", (ReValue host, Boolean receiver, List<ReValue> args) -> i(receiver.compareTo(BasicLookupUtils.<Boolean>obj((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Boolean\", \"booleanValue\", \"()Z\", (ReValue host, Boolean receiver, List<ReValue> args) -> z(receiver.booleanValue()));\n\n\t\t// java.lang.Byte\n\t\tregisterMethodHandler(\"java/lang/Byte\", \"equals\", \"(Ljava/lang/Object;)Z\", (ReValue host, Byte receiver, List<ReValue> args) -> z(receiver.equals(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Byte\", \"toString\", \"()Ljava/lang/String;\", (ReValue host, Byte receiver, List<ReValue> args) -> str(receiver.toString()));\n\t\tregisterMethodHandler(\"java/lang/Byte\", \"hashCode\", \"()I\", (ReValue host, Byte receiver, List<ReValue> args) -> i(receiver.hashCode()));\n\t\tregisterMethodHandler(\"java/lang/Byte\", \"compareTo\", \"(Ljava/lang/Byte;)I\", (ReValue host, Byte receiver, List<ReValue> args) -> i(receiver.compareTo(BasicLookupUtils.<Byte>obj((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Byte\", \"byteValue\", \"()B\", (ReValue host, Byte receiver, List<ReValue> args) -> b(receiver.byteValue()));\n\t\tregisterMethodHandler(\"java/lang/Byte\", \"shortValue\", \"()S\", (ReValue host, Byte receiver, List<ReValue> args) -> s(receiver.shortValue()));\n\t\tregisterMethodHandler(\"java/lang/Byte\", \"intValue\", \"()I\", (ReValue host, Byte receiver, List<ReValue> args) -> i(receiver.intValue()));\n\t\tregisterMethodHandler(\"java/lang/Byte\", \"longValue\", \"()J\", (ReValue host, Byte receiver, List<ReValue> args) -> j(receiver.longValue()));\n\t\tregisterMethodHandler(\"java/lang/Byte\", \"floatValue\", \"()F\", (ReValue host, Byte receiver, List<ReValue> args) -> f(receiver.floatValue()));\n\t\tregisterMethodHandler(\"java/lang/Byte\", \"doubleValue\", \"()D\", (ReValue host, Byte receiver, List<ReValue> args) -> d(receiver.doubleValue()));\n\n\t\t// java.lang.Character\n\t\tregisterMethodHandler(\"java/lang/Character\", \"equals\", \"(Ljava/lang/Object;)Z\", (ReValue host, Character receiver, List<ReValue> args) -> z(receiver.equals(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Character\", \"toString\", \"()Ljava/lang/String;\", (ReValue host, Character receiver, List<ReValue> args) -> str(receiver.toString()));\n\t\tregisterMethodHandler(\"java/lang/Character\", \"hashCode\", \"()I\", (ReValue host, Character receiver, List<ReValue> args) -> i(receiver.hashCode()));\n\t\tregisterMethodHandler(\"java/lang/Character\", \"compareTo\", \"(Ljava/lang/Character;)I\", (ReValue host, Character receiver, List<ReValue> args) -> i(receiver.compareTo(BasicLookupUtils.<Character>obj((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Character\", \"charValue\", \"()C\", (ReValue host, Character receiver, List<ReValue> args) -> c(receiver.charValue()));\n\n\t\t// java.lang.Short\n\t\tregisterMethodHandler(\"java/lang/Short\", \"equals\", \"(Ljava/lang/Object;)Z\", (ReValue host, Short receiver, List<ReValue> args) -> z(receiver.equals(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Short\", \"toString\", \"()Ljava/lang/String;\", (ReValue host, Short receiver, List<ReValue> args) -> str(receiver.toString()));\n\t\tregisterMethodHandler(\"java/lang/Short\", \"hashCode\", \"()I\", (ReValue host, Short receiver, List<ReValue> args) -> i(receiver.hashCode()));\n\t\tregisterMethodHandler(\"java/lang/Short\", \"compareTo\", \"(Ljava/lang/Short;)I\", (ReValue host, Short receiver, List<ReValue> args) -> i(receiver.compareTo(BasicLookupUtils.<Short>obj((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Short\", \"byteValue\", \"()B\", (ReValue host, Short receiver, List<ReValue> args) -> b(receiver.byteValue()));\n\t\tregisterMethodHandler(\"java/lang/Short\", \"shortValue\", \"()S\", (ReValue host, Short receiver, List<ReValue> args) -> s(receiver.shortValue()));\n\t\tregisterMethodHandler(\"java/lang/Short\", \"intValue\", \"()I\", (ReValue host, Short receiver, List<ReValue> args) -> i(receiver.intValue()));\n\t\tregisterMethodHandler(\"java/lang/Short\", \"longValue\", \"()J\", (ReValue host, Short receiver, List<ReValue> args) -> j(receiver.longValue()));\n\t\tregisterMethodHandler(\"java/lang/Short\", \"floatValue\", \"()F\", (ReValue host, Short receiver, List<ReValue> args) -> f(receiver.floatValue()));\n\t\tregisterMethodHandler(\"java/lang/Short\", \"doubleValue\", \"()D\", (ReValue host, Short receiver, List<ReValue> args) -> d(receiver.doubleValue()));\n\n\t\t// java.lang.Integer\n\t\tregisterMethodHandler(\"java/lang/Integer\", \"equals\", \"(Ljava/lang/Object;)Z\", (ReValue host, Integer receiver, List<ReValue> args) -> z(receiver.equals(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Integer\", \"toString\", \"()Ljava/lang/String;\", (ReValue host, Integer receiver, List<ReValue> args) -> str(receiver.toString()));\n\t\tregisterMethodHandler(\"java/lang/Integer\", \"hashCode\", \"()I\", (ReValue host, Integer receiver, List<ReValue> args) -> i(receiver.hashCode()));\n\t\tregisterMethodHandler(\"java/lang/Integer\", \"compareTo\", \"(Ljava/lang/Integer;)I\", (ReValue host, Integer receiver, List<ReValue> args) -> i(receiver.compareTo(BasicLookupUtils.<Integer>obj((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Integer\", \"byteValue\", \"()B\", (ReValue host, Integer receiver, List<ReValue> args) -> b(receiver.byteValue()));\n\t\tregisterMethodHandler(\"java/lang/Integer\", \"shortValue\", \"()S\", (ReValue host, Integer receiver, List<ReValue> args) -> s(receiver.shortValue()));\n\t\tregisterMethodHandler(\"java/lang/Integer\", \"intValue\", \"()I\", (ReValue host, Integer receiver, List<ReValue> args) -> i(receiver.intValue()));\n\t\tregisterMethodHandler(\"java/lang/Integer\", \"longValue\", \"()J\", (ReValue host, Integer receiver, List<ReValue> args) -> j(receiver.longValue()));\n\t\tregisterMethodHandler(\"java/lang/Integer\", \"floatValue\", \"()F\", (ReValue host, Integer receiver, List<ReValue> args) -> f(receiver.floatValue()));\n\t\tregisterMethodHandler(\"java/lang/Integer\", \"doubleValue\", \"()D\", (ReValue host, Integer receiver, List<ReValue> args) -> d(receiver.doubleValue()));\n\n\t\t// java.lang.Long\n\t\tregisterMethodHandler(\"java/lang/Long\", \"equals\", \"(Ljava/lang/Object;)Z\", (ReValue host, Long receiver, List<ReValue> args) -> z(receiver.equals(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Long\", \"toString\", \"()Ljava/lang/String;\", (ReValue host, Long receiver, List<ReValue> args) -> str(receiver.toString()));\n\t\tregisterMethodHandler(\"java/lang/Long\", \"hashCode\", \"()I\", (ReValue host, Long receiver, List<ReValue> args) -> i(receiver.hashCode()));\n\t\tregisterMethodHandler(\"java/lang/Long\", \"compareTo\", \"(Ljava/lang/Long;)I\", (ReValue host, Long receiver, List<ReValue> args) -> i(receiver.compareTo(BasicLookupUtils.<Long>obj((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Long\", \"byteValue\", \"()B\", (ReValue host, Long receiver, List<ReValue> args) -> b(receiver.byteValue()));\n\t\tregisterMethodHandler(\"java/lang/Long\", \"shortValue\", \"()S\", (ReValue host, Long receiver, List<ReValue> args) -> s(receiver.shortValue()));\n\t\tregisterMethodHandler(\"java/lang/Long\", \"intValue\", \"()I\", (ReValue host, Long receiver, List<ReValue> args) -> i(receiver.intValue()));\n\t\tregisterMethodHandler(\"java/lang/Long\", \"longValue\", \"()J\", (ReValue host, Long receiver, List<ReValue> args) -> j(receiver.longValue()));\n\t\tregisterMethodHandler(\"java/lang/Long\", \"floatValue\", \"()F\", (ReValue host, Long receiver, List<ReValue> args) -> f(receiver.floatValue()));\n\t\tregisterMethodHandler(\"java/lang/Long\", \"doubleValue\", \"()D\", (ReValue host, Long receiver, List<ReValue> args) -> d(receiver.doubleValue()));\n\n\t\t// java.lang.Float\n\t\tregisterMethodHandler(\"java/lang/Float\", \"equals\", \"(Ljava/lang/Object;)Z\", (ReValue host, Float receiver, List<ReValue> args) -> z(receiver.equals(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Float\", \"toString\", \"()Ljava/lang/String;\", (ReValue host, Float receiver, List<ReValue> args) -> str(receiver.toString()));\n\t\tregisterMethodHandler(\"java/lang/Float\", \"hashCode\", \"()I\", (ReValue host, Float receiver, List<ReValue> args) -> i(receiver.hashCode()));\n\t\tregisterMethodHandler(\"java/lang/Float\", \"isInfinite\", \"()Z\", (ReValue host, Float receiver, List<ReValue> args) -> z(receiver.isInfinite()));\n\t\tregisterMethodHandler(\"java/lang/Float\", \"compareTo\", \"(Ljava/lang/Float;)I\", (ReValue host, Float receiver, List<ReValue> args) -> i(receiver.compareTo(BasicLookupUtils.<Float>obj((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Float\", \"byteValue\", \"()B\", (ReValue host, Float receiver, List<ReValue> args) -> b(receiver.byteValue()));\n\t\tregisterMethodHandler(\"java/lang/Float\", \"shortValue\", \"()S\", (ReValue host, Float receiver, List<ReValue> args) -> s(receiver.shortValue()));\n\t\tregisterMethodHandler(\"java/lang/Float\", \"intValue\", \"()I\", (ReValue host, Float receiver, List<ReValue> args) -> i(receiver.intValue()));\n\t\tregisterMethodHandler(\"java/lang/Float\", \"longValue\", \"()J\", (ReValue host, Float receiver, List<ReValue> args) -> j(receiver.longValue()));\n\t\tregisterMethodHandler(\"java/lang/Float\", \"floatValue\", \"()F\", (ReValue host, Float receiver, List<ReValue> args) -> f(receiver.floatValue()));\n\t\tregisterMethodHandler(\"java/lang/Float\", \"doubleValue\", \"()D\", (ReValue host, Float receiver, List<ReValue> args) -> d(receiver.doubleValue()));\n\t\tregisterMethodHandler(\"java/lang/Float\", \"isNaN\", \"()Z\", (ReValue host, Float receiver, List<ReValue> args) -> z(receiver.isNaN()));\n\n\t\t// java.lang.Double\n\t\tregisterMethodHandler(\"java/lang/Double\", \"equals\", \"(Ljava/lang/Object;)Z\", (ReValue host, Double receiver, List<ReValue> args) -> z(receiver.equals(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Double\", \"toString\", \"()Ljava/lang/String;\", (ReValue host, Double receiver, List<ReValue> args) -> str(receiver.toString()));\n\t\tregisterMethodHandler(\"java/lang/Double\", \"hashCode\", \"()I\", (ReValue host, Double receiver, List<ReValue> args) -> i(receiver.hashCode()));\n\t\tregisterMethodHandler(\"java/lang/Double\", \"isInfinite\", \"()Z\", (ReValue host, Double receiver, List<ReValue> args) -> z(receiver.isInfinite()));\n\t\tregisterMethodHandler(\"java/lang/Double\", \"compareTo\", \"(Ljava/lang/Double;)I\", (ReValue host, Double receiver, List<ReValue> args) -> i(receiver.compareTo(BasicLookupUtils.<Double>obj((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/Double\", \"byteValue\", \"()B\", (ReValue host, Double receiver, List<ReValue> args) -> b(receiver.byteValue()));\n\t\tregisterMethodHandler(\"java/lang/Double\", \"shortValue\", \"()S\", (ReValue host, Double receiver, List<ReValue> args) -> s(receiver.shortValue()));\n\t\tregisterMethodHandler(\"java/lang/Double\", \"intValue\", \"()I\", (ReValue host, Double receiver, List<ReValue> args) -> i(receiver.intValue()));\n\t\tregisterMethodHandler(\"java/lang/Double\", \"longValue\", \"()J\", (ReValue host, Double receiver, List<ReValue> args) -> j(receiver.longValue()));\n\t\tregisterMethodHandler(\"java/lang/Double\", \"floatValue\", \"()F\", (ReValue host, Double receiver, List<ReValue> args) -> f(receiver.floatValue()));\n\t\tregisterMethodHandler(\"java/lang/Double\", \"doubleValue\", \"()D\", (ReValue host, Double receiver, List<ReValue> args) -> d(receiver.doubleValue()));\n\t\tregisterMethodHandler(\"java/lang/Double\", \"isNaN\", \"()Z\", (ReValue host, Double receiver, List<ReValue> args) -> z(receiver.isNaN()));\n\n\t\t// java.util.Random\n\t\tregisterMethodHandler(\"java/util/Random\", \"nextDouble\", \"()D\", (ReValue host, Random receiver, List<ReValue> args) -> d(receiver.nextDouble()));\n\t\tregisterMethodHandler(\"java/util/Random\", \"nextInt\", \"()I\", (ReValue host, Random receiver, List<ReValue> args) -> i(receiver.nextInt()));\n\t\tregisterMethodHandler(\"java/util/Random\", \"nextInt\", \"(I)I\", (ReValue host, Random receiver, List<ReValue> args) -> i(receiver.nextInt(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/Random\", \"nextBytes\", \"([B)V\", (ReValue host, Random receiver, List<ReValue> args) -> {\n\t\t\treceiver.nextBytes(arrb((ArrayValue) args.get(0)));\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/util/Random\", \"setSeed\", \"(J)V\", (ReValue host, Random receiver, List<ReValue> args) -> {\n\t\t\treceiver.setSeed(j((LongValue) args.get(0)));\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/util/Random\", \"nextLong\", \"()J\", (ReValue host, Random receiver, List<ReValue> args) -> j(receiver.nextLong()));\n\t\tregisterMethodHandler(\"java/util/Random\", \"nextBoolean\", \"()Z\", (ReValue host, Random receiver, List<ReValue> args) -> z(receiver.nextBoolean()));\n\t\tregisterMethodHandler(\"java/util/Random\", \"nextFloat\", \"()F\", (ReValue host, Random receiver, List<ReValue> args) -> f(receiver.nextFloat()));\n\t\tregisterMethodHandler(\"java/util/Random\", \"nextGaussian\", \"()D\", (ReValue host, Random receiver, List<ReValue> args) -> d(receiver.nextGaussian()));\n\n\t\t// java.util.List\n\t\tregisterMethodHandler(\"java/util/List\", \"remove\", \"(I)Ljava/lang/Object;\", (ReValue host, List receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.remove(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/List\", \"remove\", \"(Ljava/lang/Object;)Z\", (ReValue host, List receiver, List<ReValue> args) -> z(receiver.remove(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/List\", \"size\", \"()I\", (ReValue host, List receiver, List<ReValue> args) -> i(receiver.size()));\n\t\tregisterMethodHandler(\"java/util/List\", \"get\", \"(I)Ljava/lang/Object;\", (ReValue host, List receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.get(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/List\", \"equals\", \"(Ljava/lang/Object;)Z\", (ReValue host, List receiver, List<ReValue> args) -> z(receiver.equals(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/List\", \"hashCode\", \"()I\", (ReValue host, List receiver, List<ReValue> args) -> i(receiver.hashCode()));\n\t\tregisterMethodHandler(\"java/util/List\", \"indexOf\", \"(Ljava/lang/Object;)I\", (ReValue host, List receiver, List<ReValue> args) -> i(receiver.indexOf(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/List\", \"clear\", \"()V\", (ReValue host, List receiver, List<ReValue> args) -> {\n\t\t\treceiver.clear();\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/util/List\", \"lastIndexOf\", \"(Ljava/lang/Object;)I\", (ReValue host, List receiver, List<ReValue> args) -> i(receiver.lastIndexOf(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/List\", \"isEmpty\", \"()Z\", (ReValue host, List receiver, List<ReValue> args) -> z(receiver.isEmpty()));\n\t\tregisterMethodHandler(\"java/util/List\", \"add\", \"(Ljava/lang/Object;)Z\", (ReValue host, List receiver, List<ReValue> args) -> z(receiver.add(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/List\", \"add\", \"(ILjava/lang/Object;)V\", (ReValue host, List receiver, List<ReValue> args) -> {\n\t\t\treceiver.add(i((IntValue) args.get(0)), objl((ObjectValue) args.get(1)));\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/util/List\", \"subList\", \"(II)Ljava/util/List;\", (ReValue host, List receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.subList(i((IntValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/util/List\", \"toArray\", \"()[Ljava/lang/Object;\", (ReValue host, List receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.toArray()));\n\t\tregisterMethodHandler(\"java/util/List\", \"toArray\", \"([Ljava/lang/Object;)[Ljava/lang/Object;\", (ReValue host, List receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.toArray(arrobj((ArrayValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/List\", \"contains\", \"(Ljava/lang/Object;)Z\", (ReValue host, List receiver, List<ReValue> args) -> z(receiver.contains(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/List\", \"set\", \"(ILjava/lang/Object;)Ljava/lang/Object;\", (ReValue host, List receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.set(i((IntValue) args.get(0)), objl((ObjectValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/util/List\", \"getFirst\", \"()Ljava/lang/Object;\", (ReValue host, List receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.getFirst()));\n\t\tregisterMethodHandler(\"java/util/List\", \"getLast\", \"()Ljava/lang/Object;\", (ReValue host, List receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.getLast()));\n\t\tregisterMethodHandler(\"java/util/List\", \"addFirst\", \"(Ljava/lang/Object;)V\", (ReValue host, List receiver, List<ReValue> args) -> {\n\t\t\treceiver.addFirst(objl((ObjectValue) args.get(0)));\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/util/List\", \"addLast\", \"(Ljava/lang/Object;)V\", (ReValue host, List receiver, List<ReValue> args) -> {\n\t\t\treceiver.addLast(objl((ObjectValue) args.get(0)));\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/util/List\", \"removeFirst\", \"()Ljava/lang/Object;\", (ReValue host, List receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.removeFirst()));\n\t\tregisterMethodHandler(\"java/util/List\", \"removeLast\", \"()Ljava/lang/Object;\", (ReValue host, List receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.removeLast()));\n\t\tregisterMethodHandler(\"java/util/List\", \"reversed\", \"()Ljava/util/List;\", (ReValue host, List receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.reversed()));\n\n\t\t// java.lang.CharSequence\n\t\tregisterMethodHandler(\"java/lang/CharSequence\", \"length\", \"()I\", (ReValue host, CharSequence receiver, List<ReValue> args) -> i(receiver.length()));\n\t\tregisterMethodHandler(\"java/lang/CharSequence\", \"toString\", \"()Ljava/lang/String;\", (ReValue host, CharSequence receiver, List<ReValue> args) -> str(receiver.toString()));\n\t\tregisterMethodHandler(\"java/lang/CharSequence\", \"charAt\", \"(I)C\", (ReValue host, CharSequence receiver, List<ReValue> args) -> c(receiver.charAt(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/lang/CharSequence\", \"isEmpty\", \"()Z\", (ReValue host, CharSequence receiver, List<ReValue> args) -> z(receiver.isEmpty()));\n\t\tregisterMethodHandler(\"java/lang/CharSequence\", \"subSequence\", \"(II)Ljava/lang/CharSequence;\", (ReValue host, CharSequence receiver, List<ReValue> args) -> str(receiver.subSequence(i((IntValue) args.get(0)), i((IntValue) args.get(1)))));\n\n\t\t// java.util.ArrayList\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"remove\", \"(Ljava/lang/Object;)Z\", (ReValue host, ArrayList receiver, List<ReValue> args) -> z(receiver.remove(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"remove\", \"(I)Ljava/lang/Object;\", (ReValue host, ArrayList receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.remove(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"size\", \"()I\", (ReValue host, ArrayList receiver, List<ReValue> args) -> i(receiver.size()));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"get\", \"(I)Ljava/lang/Object;\", (ReValue host, ArrayList receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.get(i((IntValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"equals\", \"(Ljava/lang/Object;)Z\", (ReValue host, ArrayList receiver, List<ReValue> args) -> z(receiver.equals(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"hashCode\", \"()I\", (ReValue host, ArrayList receiver, List<ReValue> args) -> i(receiver.hashCode()));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"clone\", \"()Ljava/lang/Object;\", (ReValue host, ArrayList receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.clone()));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"indexOf\", \"(Ljava/lang/Object;)I\", (ReValue host, ArrayList receiver, List<ReValue> args) -> i(receiver.indexOf(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"clear\", \"()V\", (ReValue host, ArrayList receiver, List<ReValue> args) -> {\n\t\t\treceiver.clear();\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"lastIndexOf\", \"(Ljava/lang/Object;)I\", (ReValue host, ArrayList receiver, List<ReValue> args) -> i(receiver.lastIndexOf(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"isEmpty\", \"()Z\", (ReValue host, ArrayList receiver, List<ReValue> args) -> z(receiver.isEmpty()));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"add\", \"(Ljava/lang/Object;)Z\", (ReValue host, ArrayList receiver, List<ReValue> args) -> z(receiver.add(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"add\", \"(ILjava/lang/Object;)V\", (ReValue host, ArrayList receiver, List<ReValue> args) -> {\n\t\t\treceiver.add(i((IntValue) args.get(0)), objl((ObjectValue) args.get(1)));\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"subList\", \"(II)Ljava/util/List;\", (ReValue host, ArrayList receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.subList(i((IntValue) args.get(0)), i((IntValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"toArray\", \"()[Ljava/lang/Object;\", (ReValue host, ArrayList receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.toArray()));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"toArray\", \"([Ljava/lang/Object;)[Ljava/lang/Object;\", (ReValue host, ArrayList receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.toArray(arrobj((ArrayValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"contains\", \"(Ljava/lang/Object;)Z\", (ReValue host, ArrayList receiver, List<ReValue> args) -> z(receiver.contains(objl((ObjectValue) args.get(0)))));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"set\", \"(ILjava/lang/Object;)Ljava/lang/Object;\", (ReValue host, ArrayList receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.set(i((IntValue) args.get(0)), objl((ObjectValue) args.get(1)))));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"ensureCapacity\", \"(I)V\", (ReValue host, ArrayList receiver, List<ReValue> args) -> {\n\t\t\treceiver.ensureCapacity(i((IntValue) args.get(0)));\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"trimToSize\", \"()V\", (ReValue host, ArrayList receiver, List<ReValue> args) -> {\n\t\t\treceiver.trimToSize();\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"getFirst\", \"()Ljava/lang/Object;\", (ReValue host, ArrayList receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.getFirst()));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"getLast\", \"()Ljava/lang/Object;\", (ReValue host, ArrayList receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.getLast()));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"addFirst\", \"(Ljava/lang/Object;)V\", (ReValue host, ArrayList receiver, List<ReValue> args) -> {\n\t\t\treceiver.addFirst(objl((ObjectValue) args.get(0)));\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"addLast\", \"(Ljava/lang/Object;)V\", (ReValue host, ArrayList receiver, List<ReValue> args) -> {\n\t\t\treceiver.addLast(objl((ObjectValue) args.get(0)));\n\t\t\treturn null;\n\t\t});\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"removeFirst\", \"()Ljava/lang/Object;\", (ReValue host, ArrayList receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.removeFirst()));\n\t\tregisterMethodHandler(\"java/util/ArrayList\", \"removeLast\", \"()Ljava/lang/Object;\", (ReValue host, ArrayList receiver, List<ReValue> args) -> new InstancedObjectValue<>(receiver.removeLast()));\n\t}\n\n\t/**\n\t * @see InstanceMapperGenerator\n\t */\n\t@SuppressWarnings(\"all\")\n\tprivate void registerCtorMappers() {\n\t\t// java.lang.String\n\t\tregisterMapper(String.class, \"([BLjava/lang/String;)V\", (host, parameters) -> new String(arrb((ArrayValue) parameters.get(0)), str((StringValue) parameters.get(1))));\n\t\tregisterMapper(String.class, \"([BII)V\", (host, parameters) -> new String(arrb((ArrayValue) parameters.get(0)), i((IntValue) parameters.get(1)), i((IntValue) parameters.get(2))));\n\t\tregisterMapper(String.class, \"([B)V\", (host, parameters) -> new String(arrb((ArrayValue) parameters.get(0))));\n\t\tregisterMapper(String.class, \"([BB)V\", (host, parameters) -> new String(arrb((ArrayValue) parameters.get(0)), b((IntValue) parameters.get(1))));\n\t\tregisterMapper(String.class, \"([CII)V\", (host, parameters) -> new String(arrc((ArrayValue) parameters.get(0)), i((IntValue) parameters.get(1)), i((IntValue) parameters.get(2))));\n\t\tregisterMapper(String.class, \"([C)V\", (host, parameters) -> new String(arrc((ArrayValue) parameters.get(0))));\n\t\tregisterMapper(String.class, \"(Ljava/lang/String;)V\", (host, parameters) -> new String(str((StringValue) parameters.get(0))));\n\t\tregisterMapper(String.class, \"()V\", (host, parameters) -> new String());\n\t\tregisterMapper(String.class, \"([BIILjava/lang/String;)V\", (host, parameters) -> new String(arrb((ArrayValue) parameters.get(0)), i((IntValue) parameters.get(1)), i((IntValue) parameters.get(2)), str((StringValue) parameters.get(3))));\n\t\tregisterMapper(String.class, \"([BI)V\", (host, parameters) -> new String(arrb((ArrayValue) parameters.get(0)), i((IntValue) parameters.get(1))));\n\t\tregisterMapper(String.class, \"([BIII)V\", (host, parameters) -> new String(arrb((ArrayValue) parameters.get(0)), i((IntValue) parameters.get(1)), i((IntValue) parameters.get(2)), i((IntValue) parameters.get(3))));\n\t\tregisterMapper(String.class, \"([III)V\", (host, parameters) -> new String(arri((ArrayValue) parameters.get(0)), i((IntValue) parameters.get(1)), i((IntValue) parameters.get(2))));\n\n\t\t// java.lang.StringBuilder\n\t\tregisterMapper(StringBuilder.class, \"(Ljava/lang/CharSequence;)V\", (host, parameters) -> new StringBuilder(str((StringValue) parameters.get(0))));\n\t\tregisterMapper(StringBuilder.class, \"(Ljava/lang/String;)V\", (host, parameters) -> new StringBuilder(str((StringValue) parameters.get(0))));\n\t\tregisterMapper(StringBuilder.class, \"(I)V\", (host, parameters) -> new StringBuilder(i((IntValue) parameters.get(0))));\n\t\tregisterMapper(StringBuilder.class, \"()V\", (host, parameters) -> new StringBuilder());\n\n\t\t// java.lang.Boolean\n\t\tregisterMapper(Boolean.class, \"(Z)V\", (host, parameters) -> new Boolean(z((IntValue) parameters.get(0))));\n\t\tregisterMapper(Boolean.class, \"(Ljava/lang/String;)V\", (host, parameters) -> new Boolean(str((StringValue) parameters.get(0))));\n\n\t\t// java.lang.Byte\n\t\tregisterMapper(Byte.class, \"(B)V\", (host, parameters) -> new Byte(b((IntValue) parameters.get(0))));\n\t\tregisterMapper(Byte.class, \"(Ljava/lang/String;)V\", (host, parameters) -> new Byte(str((StringValue) parameters.get(0))));\n\n\t\t// java.lang.Character\n\t\tregisterMapper(Character.class, \"(C)V\", (host, parameters) -> new Character(c((IntValue) parameters.get(0))));\n\n\t\t// java.lang.Short\n\t\tregisterMapper(Short.class, \"(S)V\", (host, parameters) -> new Short(s((IntValue) parameters.get(0))));\n\t\tregisterMapper(Short.class, \"(Ljava/lang/String;)V\", (host, parameters) -> new Short(str((StringValue) parameters.get(0))));\n\n\t\t// java.lang.Integer\n\t\tregisterMapper(Integer.class, \"(I)V\", (host, parameters) -> new Integer(i((IntValue) parameters.get(0))));\n\t\tregisterMapper(Integer.class, \"(Ljava/lang/String;)V\", (host, parameters) -> new Integer(str((StringValue) parameters.get(0))));\n\n\t\t// java.lang.Long\n\t\tregisterMapper(Long.class, \"(Ljava/lang/String;)V\", (host, parameters) -> new Long(str((StringValue) parameters.get(0))));\n\t\tregisterMapper(Long.class, \"(J)V\", (host, parameters) -> new Long(j((LongValue) parameters.get(0))));\n\n\t\t// java.lang.Float\n\t\tregisterMapper(Float.class, \"(Ljava/lang/String;)V\", (host, parameters) -> new Float(str((StringValue) parameters.get(0))));\n\t\tregisterMapper(Float.class, \"(D)V\", (host, parameters) -> new Float(d((DoubleValue) parameters.get(0))));\n\t\tregisterMapper(Float.class, \"(F)V\", (host, parameters) -> new Float(f((FloatValue) parameters.get(0))));\n\n\t\t// java.lang.Double\n\t\tregisterMapper(Double.class, \"(D)V\", (host, parameters) -> new Double(d((DoubleValue) parameters.get(0))));\n\t\tregisterMapper(Double.class, \"(Ljava/lang/String;)V\", (host, parameters) -> new Double(str((StringValue) parameters.get(0))));\n\n\t\t// java.util.Random\n\t\tregisterMapper(Random.class, \"(J)V\", (host, parameters) -> new Random(j((LongValue) parameters.get(0))));\n\t\tregisterMapper(Random.class, \"()V\", (host, parameters) -> new Random(0));\n\n\t\t// java.util.ArrayList\n\t\tregisterMapper(ArrayList.class, \"()V\", (host, parameters) -> new ArrayList());\n\t\tregisterMapper(ArrayList.class, \"(I)V\", (host, parameters) -> new ArrayList(i((IntValue) parameters.get(0))));\n\t}\n\n\t/**\n\t * @see InstanceStaticMapperGenerator\n\t */\n\tprivate void registerStaticMappers() {\n\t\t// TODO: Create mappers for static constructors of supported types\n\t}\n\n\t/**\n\t * @param min\n\t * \t\tMethod instruction to find a handler for.\n\t *\n\t * @return Handler for the method instruction, if supported.\n\t */\n\t@Nullable\n\tpublic MethodInvokeHandler<?> getMethodHandler(@Nonnull MethodInsnNode min) {\n\t\treturn methodHandlers.get(min.owner + '.' + min.name + min.desc);\n\t}\n\n\t/**\n\t * @param min\n\t * \t\tMethod instruction to find a mapper for.\n\t *\n\t * @return Mapper for the method instruction, if supported.\n\t */\n\t@Nullable\n\tpublic InstanceMapper getMapper(@Nonnull MethodInsnNode min) {\n\t\treturn mappers.get(min.owner + '.' + min.desc);\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tType to check for support.\n\t *\n\t * @return {@code true} if the type is supported, {@code false} otherwise.\n\t */\n\tpublic boolean isSupportedType(@Nonnull String type) {\n\t\treturn supportedTypes.contains(type);\n\t}\n\n\tprivate void registerMethodHandler(@Nonnull String owner, @Nonnull String name, @Nonnull String desc, @Nonnull MethodInvokeHandler<?> handler) {\n\t\tmethodHandlers.put(owner + '.' + name + desc, handler);\n\t}\n\n\tprivate void registerMapper(@Nonnull Class<?> type, @Nonnull String desc, @Nonnull InstanceMapper mapper) {\n\t\tString internalName = type.getName().replace('.', '/');\n\t\tsupportedTypes.add(internalName);\n\t\tmappers.put(internalName + '.' + desc, mapper);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/eval/InstanceMapper.java",
    "content": "package software.coley.recaf.util.analysis.eval;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.analysis.gen.InstanceMapperGenerator;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\nimport java.util.List;\n\n/**\n * Outline for creating objects from {@link ReValue} items taken off the stack.\n * Represents a call to a constructor or static factory method that creates an instance of an object.\n *\n * @author Matt Coley\n * @see InstancedObjectValue#setRealInstance(Object)\n * @see InstanceMapperGenerator\n */\npublic interface InstanceMapper {\n\t/**\n\t * @param host\n\t * \t\tObject value to be populated with a real instance.\n\t * \t\tFor constructors, this will be the object being constructed.\n\t * \t\tFor static factories, this will be a dummy object of the correct type.\n\t * @param parameters\n\t * \t\tParameters passed to the constructor or static factory method.\n\t * \t\tThese are the values on the stack at the time of the call.\n\t *\n\t * @return Real constructed instance to back the given object value.\n\t *\n\t * @throws Throwable\n\t * \t\tWhen any error occurs during the mapping process.\n\t */\n\t@Nonnull\n\tObject map(@Nonnull InstancedObjectValue<?> host, @Nonnull List<ReValue> parameters) throws Throwable;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/eval/InstancedObjectValue.java",
    "content": "package software.coley.recaf.util.analysis.eval;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.value.IllegalValueException;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.impl.ArrayValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedBooleanValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedByteValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedCharacterValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedDoubleValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedFloatValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedIntegerValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedLongValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedShortValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.ObjectValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.StringValueImpl;\n\nimport java.lang.reflect.Array;\n\n/**\n * Object value that has a real instance backing it.\n * This is used to call real methods on the instance for specific supported APIs.\n *\n * @param <T>\n * \t\tType of the real instance.\n *\n * @author Matt Coley\n */\npublic class InstancedObjectValue<T> extends ObjectValueImpl {\n\tprivate T realInstance;\n\n\t/**\n\t * New instanced value without the real instance specified immediately.\n\t *\n\t * @param type\n\t * \t\tObject type.\n\t */\n\tpublic InstancedObjectValue(@Nonnull Type type) {\n\t\tsuper(type, Nullness.NOT_NULL);\n\t}\n\n\t/**\n\t * New instanced value with the real instance specified immediately.\n\t *\n\t * @param value\n\t * \t\tReal object instance.\n\t */\n\tpublic InstancedObjectValue(@Nonnull T value) {\n\t\tsuper(Type.getType(value.getClass()), Nullness.NOT_NULL);\n\t\tsetRealInstance(value);\n\t}\n\n\t/**\n\t * Assign the real instance backing this value.\n\t *\n\t * @param realInstance\n\t * \t\tReal object instance.\n\t *\n\t * @see InstanceMapper\n\t * @see InstanceFactory\n\t */\n\tpublic void setRealInstance(@Nonnull T realInstance) {\n\t\tthis.realInstance = realInstance;\n\t}\n\n\t/**\n\t * @return Real object instance backing this value, or {@code null} if no instance is assigned.\n\t */\n\t@Nullable\n\tpublic T getRealInstance() {\n\t\treturn realInstance;\n\t}\n\n\t/**\n\t * Attempt to unmap the backing instance into a mapped value.\n\t * If the instance is not supported for mapping, then this will return {@code this}.\n\t *\n\t * @return Mapped {@link ReValue} representation of the backing {@link #getRealInstance() real instance}.\n\t */\n\t@Nonnull\n\tpublic ReValue unmap() {\n\t\tReValue unmapped = unmap(type(), realInstance);\n\t\tif (unmapped != null)\n\t\t\treturn unmapped;\n\t\treturn this;\n\t}\n\n\t@Nullable\n\tprivate static ReValue unmap(@Nonnull Type type, @Nullable Object instance) {\n\t\ttry {\n\t\t\tif (instance == null)\n\t\t\t\tthrow new IllegalValueException(\"No real instance available for unmapping.\");\n\t\t\tif (Types.isPrimitive(type))\n\t\t\t\treturn ReValue.ofConstant(instance);\n\t\t\tif (instance instanceof CharSequence cs)\n\t\t\t\treturn new StringValueImpl(cs.toString());\n\t\t\tString descriptor = type.getDescriptor();\n\t\t\tif (Types.isBoxedPrimitive(descriptor)) switch (descriptor) {\n\t\t\t\tcase \"Ljava/lang/Boolean;\" -> {\n\t\t\t\t\treturn new BoxedBooleanValueImpl((Boolean) instance);\n\t\t\t\t}\n\t\t\t\tcase \"Ljava/lang/Byte;\" -> {\n\t\t\t\t\treturn new BoxedByteValueImpl((Byte) instance);\n\t\t\t\t}\n\t\t\t\tcase \"Ljava/lang/Short;\" -> {\n\t\t\t\t\treturn new BoxedShortValueImpl((Short) instance);\n\t\t\t\t}\n\t\t\t\tcase \"Ljava/lang/Character;\" -> {\n\t\t\t\t\treturn new BoxedCharacterValueImpl((Character) instance);\n\t\t\t\t}\n\t\t\t\tcase \"Ljava/lang/Integer;\" -> {\n\t\t\t\t\treturn new BoxedIntegerValueImpl((Integer) instance);\n\t\t\t\t}\n\t\t\t\tcase \"Ljava/lang/Long;\" -> {\n\t\t\t\t\treturn new BoxedLongValueImpl((Long) instance);\n\t\t\t\t}\n\t\t\t\tcase \"Ljava/lang/Float;\" -> {\n\t\t\t\t\treturn new BoxedFloatValueImpl((Float) instance);\n\t\t\t\t}\n\t\t\t\tcase \"Ljava/lang/Double;\" -> {\n\t\t\t\t\treturn new BoxedDoubleValueImpl((Double) instance);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (type.getSort() == Type.ARRAY) {\n\t\t\t\tType componentType = Types.undimension(type);\n\t\t\t\treturn new ArrayValueImpl(type, Nullness.NOT_NULL, Array.getLength(instance), i -> unmap(componentType, Array.get(instance, i)));\n\t\t\t}\n\t\t} catch (IllegalValueException _) {}\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic boolean hasKnownValue() {\n\t\treturn realInstance != null;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn super.toString() + \" : \" + realInstance;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/eval/MethodInvokeHandler.java",
    "content": "package software.coley.recaf.util.analysis.eval;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.analysis.gen.InstanceMethodInvokeHandlerGenerator;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\nimport java.util.List;\n\n/**\n * Handler for invoking methods on real instances of objects.\n *\n * @param <T>\n * \t\tType of the real instance.\n *\n * @author Matt Coley\n * @see InstanceMethodInvokeHandlerGenerator\n */\npublic interface MethodInvokeHandler<T> {\n\t/**\n\t * @param receiverValue\n\t * \t\tValue of the receiver on the stack.\n\t * \t\tThis is used to {@link FieldCacheManager#getInstanceFieldCache(ReValue) track field values}\n\t * \t\tand other state on the instance.\n\t * @param receiver\n\t * \t\tReal instance of the object the method is being called on.\n\t * @param args\n\t * \t\tValues of the arguments on the stack passed to the method.\n\t *\n\t * @return Value returned by the method, or {@code null} if the method is {@code void}.\n\t *\n\t * @throws Throwable\n\t * \t\tWhen any error occurs during method invocation.\n\t */\n\t@Nullable\n\tReValue invoke(@Nonnull ReValue receiverValue, @Nonnull T receiver, @Nonnull List<ReValue> args) throws Throwable;\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/gen/GenUtils.java",
    "content": "package software.coley.recaf.util.analysis.gen;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.Types;\n\n/**\n * Common utilities for code generation of mappers and method handlers.\n *\n * @author Matt Coley\n */\npublic class GenUtils {\n\tprotected static boolean isSupportedType(@Nonnull Class<?> cls) {\n\t\twhile (cls.isArray())\n\t\t\tcls = cls.getComponentType();\n\t\tif (cls == void.class) return false;\n\t\treturn cls.isPrimitive() || cls == String.class || cls == CharSequence.class\n\t\t\t\t|| cls == Object.class\n\t\t\t\t|| Types.isBoxedPrimitive(Type.getDescriptor(cls));\n\t}\n\n\t@Nonnull\n\tprotected static String toValue(@Nonnull Class<?> cls) {\n\t\tif (cls.isArray()) {\n\t\t\treturn \"ArrayValue\";\n\t\t} else if (cls.isPrimitive()) {\n\t\t\tif (cls == short.class || cls == char.class || cls == byte.class || cls == boolean.class)\n\t\t\t\treturn \"IntValue\";\n\t\t\treturn StringUtil.uppercaseFirstChar(cls.getSimpleName()) + \"Value\";\n\t\t} else if (cls == String.class || cls == CharSequence.class) {\n\t\t\treturn \"StringValue\";\n\t\t} else {\n\t\t\treturn \"ObjectValue\";\n\t\t}\n\t}\n\n\t@Nonnull\n\tprotected static String toMapper(@Nonnull Class<?> cls) {\n\t\treturn toMapper(cls, true);\n\t}\n\n\tprotected static String toMapper(@Nonnull Class<?> cls, boolean objectLiteral) {\n\t\tif (cls == String.class || cls == CharSequence.class) return \"str\";\n\t\tif (cls == boolean.class) return \"z\";\n\t\tif (cls == byte.class) return \"b\";\n\t\tif (cls == char.class) return \"c\";\n\t\tif (cls == short.class) return \"s\";\n\t\tif (cls == int.class) return \"i\";\n\t\tif (cls == long.class) return \"j\";\n\t\tif (cls == float.class) return \"f\";\n\t\tif (cls == double.class) return \"d\";\n\t\tif (cls.isArray()) return \"arr\" + toMapper(cls.componentType(), false);\n\t\tif (cls != Object.class) return \"BasicLookupUtils.<\" + cls.getSimpleName() + \">obj\";\n\t\treturn objectLiteral ? \"objl\" : \"obj\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/gen/InstanceMapperGenerator.java",
    "content": "package software.coley.recaf.util.analysis.gen;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.analysis.eval.InstanceFactory;\nimport software.coley.recaf.util.analysis.eval.InstanceMapper;\n\nimport java.lang.reflect.Constructor;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Random;\n\n/**\n * Generator for items in {@link InstanceFactory} to generate {@link InstanceMapper} implementations from constructors.\n *\n * @author Matt Coley\n */\npublic class InstanceMapperGenerator extends GenUtils {\n\tstatic List<Class<?>> emitTargets = List.of(\n\t\t\tString.class,\n\t\t\tStringBuilder.class,\n\t\t\tBoolean.class,\n\t\t\tByte.class,\n\t\t\tCharacter.class,\n\t\t\tShort.class,\n\t\t\tInteger.class,\n\t\t\tLong.class,\n\t\t\tFloat.class,\n\t\t\tDouble.class,\n\t\t\tRandom.class,\n\t\t\tList.class,\n\t\t\t// Support classes\n\t\t\tCharSequence.class,\n\t\t\tArrayList.class\n\t);\n\n\t/**\n\t * Generator for method definitions.\n\t */\n\tpublic static void main(String[] args) {\n\t\tfor (Class<?> type : emitTargets) {\n\t\t\tSystem.out.println(\" // \" + type.getName());\n\t\t\tfor (Constructor<?> constructor : type.getDeclaredConstructors()) {\n\t\t\t\t// Skip if we don't support a parameter type\n\t\t\t\tboolean supportedParameters = true;\n\t\t\t\tClass<?>[] parameterTypes = constructor.getParameterTypes();\n\t\t\t\tfor (Class<?> parameterType : parameterTypes) {\n\t\t\t\t\tif (!isSupportedType(parameterType)) {\n\t\t\t\t\t\tsupportedParameters = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!supportedParameters)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Build final output\n\t\t\t\tString template = buildImplementation(type, constructor);\n\t\t\t\tSystem.out.println(\" registerMapper(\" + type.getSimpleName() + \".class, \\\"\" + Type.getConstructorDescriptor(constructor) + \"\\\", \" + template + \");\");\n\t\t\t}\n\t\t\tSystem.out.println();\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate static String buildImplementation(@Nonnull Class<?> type, @Nonnull Constructor<?> method) {\n\t\treturn \"(host, parameters) -> new \" + type.getSimpleName() + \"(\" + buildParameterList(method.getParameterTypes()) + \")\";\n\t}\n\n\t@Nonnull\n\tprivate static String buildParameterList(@Nonnull Class<?>[] parameterTypes) {\n\t\t//  new String(   arrc((ArrayValue) parameters.get(0))    );\n\t\tList<String> entries = new ArrayList<>(parameterTypes.length);\n\t\tfor (int i = 0; i < parameterTypes.length; i++) {\n\t\t\tString parameter = \"parameters.get(\" + i + \")\";\n\t\t\tClass<?> parameterType = parameterTypes[i];\n\t\t\tString entry = toMapper(parameterType) + \"((\" + toValue(parameterType) + \")\" + parameter + \")\";\n\t\t\tentries.add(entry);\n\t\t}\n\t\treturn String.join(\", \", entries);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/gen/InstanceMethodInvokeHandlerGenerator.java",
    "content": "package software.coley.recaf.util.analysis.gen;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.analysis.eval.InstanceFactory;\nimport software.coley.recaf.util.analysis.eval.MethodInvokeHandler;\n\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\n\n/**\n * Generator for items in {@link InstanceFactory} to generate {@link MethodInvokeHandler} implementations.\n *\n * @author Matt Coley\n */\npublic class InstanceMethodInvokeHandlerGenerator extends GenUtils {\n\t/**\n\t * Generator for method definitions.\n\t */\n\tpublic static void main(String[] args) {\n\t\tfor (Class<?> type : InstanceMapperGenerator.emitTargets) {\n\t\t\tSystem.out.println(\" // \" + type.getName());\n\t\t\tfor (Method method : type.getDeclaredMethods()) {\n\t\t\t\t// Skip inaccessible methods\n\t\t\t\tint modifiers = method.getModifiers();\n\t\t\t\tif (!Modifier.isPublic(modifiers))\n\t\t\t\t\tcontinue;\n\t\t\t\tif (Modifier.isStatic(modifiers))\n\t\t\t\t\tcontinue;\n\t\t\t\tif (AccessFlag.isBridge(modifiers) || AccessFlag.isSynthetic(modifiers))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Skip static initializer and constructors\n\t\t\t\tString methodName = method.getName();\n\t\t\t\tif (method.getName().charAt(0) == '<')\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Skip if we don't support the return type\n\t\t\t\tif (!isSupportedTypeOrEmitTarget(method.getReturnType()))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Skip if we don't support a parameter type\n\t\t\t\tboolean supportedParameters = true;\n\t\t\t\tClass<?>[] parameterTypes = method.getParameterTypes();\n\t\t\t\tfor (Class<?> parameterType : parameterTypes) {\n\t\t\t\t\tif (!isSupportedTypeOrEmitTarget(parameterType)) {\n\t\t\t\t\t\tsupportedParameters = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!supportedParameters)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Build final output\n\t\t\t\tString methodDescriptor = Type.getMethodDescriptor(method);\n\t\t\t\tString template = buildImplementation(type, method);\n\t\t\t\tSystem.out.println(\" registerMethodHandler(\\\"\" + type.getName().replace('.', '/') +\n\t\t\t\t\t\t\"\\\", \\\"\" + methodName + \"\\\", \\\"\" + methodDescriptor + \"\\\", \" + template + \"  );\");\n\t\t\t}\n\t\t\tSystem.out.println();\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate static String buildImplementation(@Nonnull Class<?> type, @Nonnull Method method) {\n\t\t//  ReValue host, T receiver, List<ReValue> args -> new InstancedObjectValue<>(receiver.method( %MAPPER%(args.get(0)), ... ))\n\t\t//                   for primitive methods       -> %MAPPER%(receiver.method( %MAPPER%(args.get(0)), ... ))\n\t\t//                        for void methods       -> { receiver.method( %MAPPER%(args.get(0)), ... ); return null; }\n\t\t//                            for builders       -> { receiver.method( %MAPPER%(args.get(0)), ... ); return host; }\n\t\tStringBuilder sb = new StringBuilder(\"(ReValue host, \" + type.getSimpleName() + \" receiver, List<ReValue> args) -> \");\n\t\tClass<?> returnType = method.getReturnType();\n\t\tboolean isBuilder = (type == StringBuffer.class || type == StringBuilder.class) && returnType == type;\n\t\tboolean isPrimitive = returnType.isPrimitive() || returnType == String.class || returnType == CharSequence.class;\n\t\tif (returnType == void.class || isBuilder) {\n\t\t\tsb.append(\"{ receiver.\").append(method.getName()).append(\"(\");\n\t\t} else if (isPrimitive) {\n\t\t\tsb.append(toMapper(returnType)).append(\"(receiver.\").append(method.getName()).append(\"(\");\n\t\t} else {\n\t\t\tsb.append(\"new InstancedObjectValue<>(receiver.\").append(method.getName()).append(\"(\");\n\t\t}\n\n\t\tClass<?>[] parameterTypes = method.getParameterTypes();\n\t\tfor (int i = 0; i < parameterTypes.length; i++) {\n\t\t\tClass<?> parameterType = parameterTypes[i];\n\t\t\tString entry = toMapper(parameterType) + \"((\" + toValue(parameterType) + \")\" + \"args.get(\" + i + \"))\";\n\t\t\tsb.append(entry);\n\t\t\tif (i < parameterTypes.length - 1)\n\t\t\t\tsb.append(\", \");\n\t\t}\n\t\tsb.append(\")\");\n\t\tif (returnType == void.class) {\n\t\t\tsb.append(\"; return null; }\");\n\t\t} else if (isBuilder) {\n\t\t\tsb.append(\"; return host; }\");\n\t\t} else {\n\t\t\tsb.append(\")\");\n\t\t}\n\n\t\tString string = sb.toString();\n\t\tif (StringUtil.count('(', string) != StringUtil.count(')', string))\n\t\t\tthrow new IllegalStateException(\"Mismatched parentheses in generated code: \" + string);\n\t\treturn string;\n\t}\n\n\tprivate static boolean isSupportedTypeOrEmitTarget(@Nonnull Class<?> cls) {\n\t\treturn isSupportedType(cls) || cls == void.class || InstanceMapperGenerator.emitTargets.contains(cls);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/gen/InstanceStaticMapperGenerator.java",
    "content": "package software.coley.recaf.util.analysis.gen;\n\nimport software.coley.recaf.util.analysis.eval.InstanceFactory;\nimport software.coley.recaf.util.analysis.eval.InstanceMapper;\n\n/**\n * Generator for items in {@link InstanceFactory} to generate {@link InstanceMapper} implementations static constructors for supported types.\n *\n * @author Matt Coley\n */\npublic class InstanceStaticMapperGenerator extends GenUtils {\n\t/**\n\t * Generator for method definitions.\n\t */\n\tpublic static void main(String[] args) {\n\t\t// TODO: When we add support for types that can be constructed via static factory methods,\n\t\t//       add support for generating mappers for those as well.\n\t\t//        - Example: List.of(...)\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/gen/LookupGenerator.java",
    "content": "package software.coley.recaf.util.analysis.gen;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.analysis.lookup.BasicInvokeStaticLookup;\nimport software.coley.recaf.util.analysis.lookup.BasicInvokeVirtualLookup;\n\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.util.Arrays;\nimport java.util.Objects;\nimport java.util.function.Function;\n\n/**\n * Generator for items in {@link BasicInvokeStaticLookup} and {@link BasicInvokeVirtualLookup}.\n *\n * @author Matt Coley\n */\npublic class LookupGenerator extends GenUtils {\n\tstatic boolean emitStatic = false;\n\tstatic Class<?>[] emitTargets = new Class[]{\n\t\t\tSystem.class,\n\t\t\tArrays.class,\n\t\t\tString.class,\n\t\t\tCharSequence.class,\n\t\t\tMath.class,\n\t\t\tBoolean.class,\n\t\t\tByte.class,\n\t\t\tCharacter.class,\n\t\t\tShort.class,\n\t\t\tInteger.class,\n\t\t\tLong.class,\n\t\t\tFloat.class,\n\t\t\tDouble.class,\n\t\t\tNumber.class,\n\t\t\tObjects.class\n\t};\n\n\t/**\n\t * Generator for method definitions.\n\t */\n\tpublic static void main(String[] args) {\n\t\tfor (Class<?> type : emitTargets) {\n\t\t\tSystem.out.println(\"// \" + type.getName());\n\t\t\tfor (Method method : type.getDeclaredMethods()) {\n\t\t\t\t// Skip inaccessible methods\n\t\t\t\tint modifiers = method.getModifiers();\n\t\t\t\tif (!Modifier.isPublic(modifiers))\n\t\t\t\t\tcontinue;\n\t\t\t\tif (emitStatic && !Modifier.isStatic(modifiers))\n\t\t\t\t\tcontinue;\n\t\t\t\tif (!emitStatic && Modifier.isStatic(modifiers))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Skip static initializer and constructors\n\t\t\t\tString methodName = method.getName();\n\t\t\t\tif (method.getName().charAt(0) == '<')\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Skip if we don't support the return type\n\t\t\t\tif (!isSupportedType(method.getReturnType()))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Skip if we don't support a parameter type\n\t\t\t\tboolean supportedParameters = true;\n\t\t\t\tClass<?>[] parameterTypes = method.getParameterTypes();\n\t\t\t\tfor (Class<?> parameterType : parameterTypes) {\n\t\t\t\t\tif (!isSupportedType(parameterType)) {\n\t\t\t\t\t\tsupportedParameters = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!supportedParameters)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Skip if we don't support the instance type on non-static methods\n\t\t\t\tif (!emitStatic && !isSupportedType(type))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Build final output\n\t\t\t\tString template = buildTemplate(type, method);\n\t\t\t\tSystem.out.println(template);\n\t\t\t}\n\t\t\tSystem.out.println(\"\\n\\n\");\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate static String buildTemplate(@Nonnull Class<?> type, @Nonnull Method method) {\n\t\tString typeName = Type.getInternalName(type);\n\t\tString methodName = method.getName();\n\t\tString wrapFunc = toMapper(method.getReturnType());\n\t\tString methodContext = emitStatic ? type.getSimpleName() : toMapper(type) + \"(ctx)\";\n\t\tClass<?>[] parameterTypes = method.getParameterTypes();\n\t\tint parameterCount = parameterTypes.length;\n\t\tFunction<Integer, String> valueizer = i -> {\n\t\t\tif (!emitStatic) {\n\t\t\t\ti--;\n\n\t\t\t\t// 0th parameter becomes instance type of the method owner\n\t\t\t\tif (i < 0)\n\t\t\t\t\treturn toValue(type);\n\t\t\t}\n\t\t\treturn toValue(parameterTypes[i]);\n\t\t};\n\t\tFunction<Integer, String> namer = i -> {\n\t\t\tif (!emitStatic) {\n\t\t\t\ti--;\n\n\t\t\t\t// 0th parameter becomes reference to the method owner\n\t\t\t\tif (i < 0)\n\t\t\t\t\treturn \"ctx\";\n\t\t\t}\n\t\t\treturn String.valueOf((char) ('a' + i));\n\t\t};\n\t\tFunction<Integer, String> parameterizer = i -> {\n\t\t\t// Does not need to be adjusted for virtual vs static, already done in usage\n\t\t\tClass<?> parameterType = parameterTypes[i];\n\t\t\tString mapped = toMapper(parameterType);\n\n\t\t\treturn mapped;\n\t\t};\n\n\t\t// METHODS.put(\"java/lang/Math.max(II)I\",\n\t\tString methodDescriptor = Type.getMethodDescriptor(method);\n\t\tStringBuilder sb = new StringBuilder(\"METHODS.put(\\\"\");\n\t\tsb.append(typeName).append(\".\").append(methodName).append(methodDescriptor).append(\"\\\", \");\n\n\n\t\t// (Func_1<IntValue>) (a)\n\t\t// (Func_2<IntValue, IntValue>) (a, b)\n\t\t// (Func_3<IntValue, IntValue, IntValue>) (a, b, c)\n\t\tswitch (parameterCount + (emitStatic ? 0 : 1)) {\n\t\t\tcase 0 -> {\n\t\t\t\tsb.append(\"(Func_0) () -> \");\n\t\t\t}\n\t\t\tcase 1 -> {\n\t\t\t\tString typeA = valueizer.apply(0);\n\t\t\t\tsb.append(\"(Func_1<\")\n\t\t\t\t\t\t.append(typeA).append(\">) (\")\n\t\t\t\t\t\t.append(namer.apply(0)).append(\") -> \");\n\t\t\t}\n\t\t\tcase 2 -> {\n\t\t\t\tString typeA = valueizer.apply(0);\n\t\t\t\tString typeB = valueizer.apply(1);\n\t\t\t\tsb.append(\"(Func_2<\")\n\t\t\t\t\t\t.append(typeA).append(\", \")\n\t\t\t\t\t\t.append(typeB).append(\">) (\")\n\t\t\t\t\t\t.append(namer.apply(0)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(1)).append(\") -> \");\n\n\t\t\t}\n\t\t\tcase 3 -> {\n\t\t\t\tString typeA = valueizer.apply(0);\n\t\t\t\tString typeB = valueizer.apply(1);\n\t\t\t\tString typeC = valueizer.apply(2);\n\t\t\t\tsb.append(\"(Func_3<\")\n\t\t\t\t\t\t.append(typeA).append(\", \")\n\t\t\t\t\t\t.append(typeB).append(\", \")\n\t\t\t\t\t\t.append(typeC).append(\">) (\")\n\t\t\t\t\t\t.append(namer.apply(0)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(1)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(2)).append(\") -> \");\n\t\t\t}\n\t\t\tcase 4 -> {\n\t\t\t\tString typeA = valueizer.apply(0);\n\t\t\t\tString typeB = valueizer.apply(1);\n\t\t\t\tString typeC = valueizer.apply(2);\n\t\t\t\tString typeD = valueizer.apply(3);\n\t\t\t\tsb.append(\"(Func_4<\")\n\t\t\t\t\t\t.append(typeA).append(\", \")\n\t\t\t\t\t\t.append(typeB).append(\", \")\n\t\t\t\t\t\t.append(typeC).append(\", \")\n\t\t\t\t\t\t.append(typeD).append(\">) (\")\n\t\t\t\t\t\t.append(namer.apply(0)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(1)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(2)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(3)).append(\") -> \");\n\t\t\t}\n\t\t\tcase 5 -> {\n\t\t\t\tString typeA = valueizer.apply(0);\n\t\t\t\tString typeB = valueizer.apply(1);\n\t\t\t\tString typeC = valueizer.apply(2);\n\t\t\t\tString typeD = valueizer.apply(3);\n\t\t\t\tString typeE = valueizer.apply(4);\n\t\t\t\tsb.append(\"(Func_5<\")\n\t\t\t\t\t\t.append(typeA).append(\", \")\n\t\t\t\t\t\t.append(typeB).append(\", \")\n\t\t\t\t\t\t.append(typeC).append(\", \")\n\t\t\t\t\t\t.append(typeD).append(\", \")\n\t\t\t\t\t\t.append(typeE).append(\">) (\")\n\t\t\t\t\t\t.append(namer.apply(0)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(1)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(2)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(3)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(4)).append(\") -> \");\n\t\t\t}\n\t\t\tcase 6 -> {\n\t\t\t\tString typeA = valueizer.apply(0);\n\t\t\t\tString typeB = valueizer.apply(1);\n\t\t\t\tString typeC = valueizer.apply(2);\n\t\t\t\tString typeD = valueizer.apply(3);\n\t\t\t\tString typeE = valueizer.apply(4);\n\t\t\t\tString typeF = valueizer.apply(5);\n\t\t\t\tsb.append(\"(Func_6<\")\n\t\t\t\t\t\t.append(typeA).append(\", \")\n\t\t\t\t\t\t.append(typeB).append(\", \")\n\t\t\t\t\t\t.append(typeC).append(\", \")\n\t\t\t\t\t\t.append(typeD).append(\", \")\n\t\t\t\t\t\t.append(typeE).append(\", \")\n\t\t\t\t\t\t.append(typeF).append(\">) (\")\n\t\t\t\t\t\t.append(namer.apply(0)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(1)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(2)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(3)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(4)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(5)).append(\") -> \");\n\t\t\t}\n\t\t\tcase 7 -> {\n\t\t\t\tString typeA = valueizer.apply(0);\n\t\t\t\tString typeB = valueizer.apply(1);\n\t\t\t\tString typeC = valueizer.apply(2);\n\t\t\t\tString typeD = valueizer.apply(3);\n\t\t\t\tString typeE = valueizer.apply(4);\n\t\t\t\tString typeF = valueizer.apply(5);\n\t\t\t\tString typeG = valueizer.apply(6);\n\t\t\t\tsb.append(\"(Func_7<\")\n\t\t\t\t\t\t.append(typeA).append(\", \")\n\t\t\t\t\t\t.append(typeB).append(\", \")\n\t\t\t\t\t\t.append(typeC).append(\", \")\n\t\t\t\t\t\t.append(typeD).append(\", \")\n\t\t\t\t\t\t.append(typeE).append(\", \")\n\t\t\t\t\t\t.append(typeF).append(\", \")\n\t\t\t\t\t\t.append(typeG).append(\">) (\")\n\t\t\t\t\t\t.append(namer.apply(0)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(1)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(2)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(3)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(4)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(5)).append(\", \")\n\t\t\t\t\t\t.append(namer.apply(6)).append(\") -> \");\n\t\t\t}\n\t\t}\n\n\t\t// str(ctx).indexOf(i(a))\n\t\t// Math.max(i(a), i(b))\n\t\tswitch (parameterCount) {\n\t\t\tcase 0 -> {\n\t\t\t\tsb.append(wrapFunc).append(\"(\")\n\t\t\t\t\t\t.append(methodContext).append('.').append(methodName).append(\"()));\");\n\t\t\t}\n\t\t\tcase 1 -> {\n\t\t\t\tsb.append(wrapFunc).append(\"(\")\n\t\t\t\t\t\t.append(methodContext).append('.').append(methodName).append(\"(\")\n\t\t\t\t\t\t.append(parameterizer.apply(0)).append(\"(a))));\");\n\t\t\t}\n\t\t\tcase 2 -> {\n\t\t\t\tsb.append(wrapFunc).append(\"(\")\n\t\t\t\t\t\t.append(methodContext).append('.').append(methodName).append(\"(\")\n\t\t\t\t\t\t.append(parameterizer.apply(0)).append(\"(a), \")\n\t\t\t\t\t\t.append(parameterizer.apply(1)).append(\"(b))));\");\n\n\t\t\t}\n\t\t\tcase 3 -> {\n\t\t\t\tsb.append(wrapFunc).append(\"(\")\n\t\t\t\t\t\t.append(methodContext).append('.').append(methodName).append(\"(\")\n\t\t\t\t\t\t.append(parameterizer.apply(0)).append(\"(a), \")\n\t\t\t\t\t\t.append(parameterizer.apply(1)).append(\"(b), \")\n\t\t\t\t\t\t.append(parameterizer.apply(2)).append(\"(c))));\");\n\t\t\t}\n\t\t\tcase 4 -> {\n\t\t\t\tsb.append(wrapFunc).append(\"(\")\n\t\t\t\t\t\t.append(methodContext).append('.').append(methodName).append(\"(\")\n\t\t\t\t\t\t.append(parameterizer.apply(0)).append(\"(a), \")\n\t\t\t\t\t\t.append(parameterizer.apply(1)).append(\"(b), \")\n\t\t\t\t\t\t.append(parameterizer.apply(2)).append(\"(c), \")\n\t\t\t\t\t\t.append(parameterizer.apply(3)).append(\"(d))));\");\n\t\t\t}\n\t\t\tcase 5 -> {\n\t\t\t\tsb.append(wrapFunc).append(\"(\")\n\t\t\t\t\t\t.append(methodContext).append('.').append(methodName).append(\"(\")\n\t\t\t\t\t\t.append(parameterizer.apply(0)).append(\"(a), \")\n\t\t\t\t\t\t.append(parameterizer.apply(1)).append(\"(b), \")\n\t\t\t\t\t\t.append(parameterizer.apply(2)).append(\"(c), \")\n\t\t\t\t\t\t.append(parameterizer.apply(3)).append(\"(d), \")\n\t\t\t\t\t\t.append(parameterizer.apply(4)).append(\"(e))));\");\n\t\t\t}\n\t\t\tcase 6 -> {\n\t\t\t\tsb.append(wrapFunc).append(\"(\")\n\t\t\t\t\t\t.append(methodContext).append('.').append(methodName).append(\"(\")\n\t\t\t\t\t\t.append(parameterizer.apply(0)).append(\"(a), \")\n\t\t\t\t\t\t.append(parameterizer.apply(1)).append(\"(b), \")\n\t\t\t\t\t\t.append(parameterizer.apply(2)).append(\"(c), \")\n\t\t\t\t\t\t.append(parameterizer.apply(3)).append(\"(d), \")\n\t\t\t\t\t\t.append(parameterizer.apply(4)).append(\"(e), \")\n\t\t\t\t\t\t.append(parameterizer.apply(5)).append(\"(f))));\");\n\t\t\t}\n\t\t\tcase 7 -> {\n\t\t\t\tsb.append(wrapFunc).append(\"(\")\n\t\t\t\t\t\t.append(methodContext).append('.').append(methodName).append(\"(\")\n\t\t\t\t\t\t.append(parameterizer.apply(0)).append(\"(a), \")\n\t\t\t\t\t\t.append(parameterizer.apply(1)).append(\"(b), \")\n\t\t\t\t\t\t.append(parameterizer.apply(2)).append(\"(c), \")\n\t\t\t\t\t\t.append(parameterizer.apply(3)).append(\"(d), \")\n\t\t\t\t\t\t.append(parameterizer.apply(4)).append(\"(e), \")\n\t\t\t\t\t\t.append(parameterizer.apply(5)).append(\"(f), \")\n\t\t\t\t\t\t.append(parameterizer.apply(6)).append(\"(g))));\");\n\t\t\t}\n\t\t}\n\t\treturn sb.toString();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/lookup/BasicGetStaticLookup.java",
    "content": "package software.coley.recaf.util.analysis.lookup;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.FieldInsnNode;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.value.IllegalValueException;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\nimport java.io.File;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * Basic implementation of {@link GetStaticLookup} for common static fields.\n *\n * @author Matt Coley\n */\npublic class BasicGetStaticLookup implements GetStaticLookup {\n\tprivate static final Map<String, ReValue> CONST_FIELDS = new HashMap<>();\n\tprivate static final Logger logger = Logging.get(BasicGetStaticLookup.class);\n\n\t@Nonnull\n\t@Override\n\tpublic ReValue get(@Nonnull FieldInsnNode field) {\n\t\tString key = getKey(field);\n\t\tReValue value = CONST_FIELDS.get(key);\n\t\tif (value == null) {\n\t\t\ttry {\n\t\t\t\tvalue = ReValue.ofType(Type.getType(field.desc), Nullness.UNKNOWN);\n\t\t\t} catch (IllegalValueException ex) {\n\t\t\t\tlogger.error(\"Failed default value computation for: \" + key, ex);\n\t\t\t}\n\t\t}\n\t\treturn Objects.requireNonNull(value);\n\t}\n\n\t@Override\n\tpublic boolean hasLookup(@Nonnull FieldInsnNode field) {\n\t\treturn CONST_FIELDS.containsKey(getKey(field));\n\t}\n\n\t@Nonnull\n\tprivate static String getKey(@Nonnull FieldInsnNode field) {\n\t\treturn field.owner + \".\" + field.name;\n\t}\n\n\tstatic {\n\t\ttry {\n\t\t\tCONST_FIELDS.put(\"java/lang/Byte.BYTES\", ReValue.ofConstant(Byte.BYTES));\n\t\t\tCONST_FIELDS.put(\"java/lang/Byte.SIZE\", ReValue.ofConstant(Byte.SIZE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Byte.MIN_VALUE\", ReValue.ofConstant(Byte.MIN_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Byte.MAX_VALUE\", ReValue.ofConstant(Byte.MAX_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Short.BYTES\", ReValue.ofConstant(Short.BYTES));\n\t\t\tCONST_FIELDS.put(\"java/lang/Short.SIZE\", ReValue.ofConstant(Short.SIZE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Short.MIN_VALUE\", ReValue.ofConstant(Short.MIN_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Short.MAX_VALUE\", ReValue.ofConstant(Short.MAX_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Integer.BYTES\", ReValue.ofConstant(Integer.BYTES));\n\t\t\tCONST_FIELDS.put(\"java/lang/Integer.SIZE\", ReValue.ofConstant(Integer.SIZE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Integer.MIN_VALUE\", ReValue.ofConstant(Integer.MIN_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Integer.MAX_VALUE\", ReValue.ofConstant(Integer.MAX_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Long.BYTES\", ReValue.ofConstant(Long.BYTES));\n\t\t\tCONST_FIELDS.put(\"java/lang/Long.SIZE\", ReValue.ofConstant(Long.SIZE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Long.MIN_VALUE\", ReValue.ofConstant(Long.MIN_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Long.MAX_VALUE\", ReValue.ofConstant(Long.MAX_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Float.BYTES\", ReValue.ofConstant(Float.BYTES));\n\t\t\tCONST_FIELDS.put(\"java/lang/Float.SIZE\", ReValue.ofConstant(Float.SIZE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Float.MIN_VALUE\", ReValue.ofConstant(Float.MIN_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Float.MAX_VALUE\", ReValue.ofConstant(Float.MAX_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Float.MIN_EXPONENT\", ReValue.ofConstant(Float.MIN_EXPONENT));\n\t\t\tCONST_FIELDS.put(\"java/lang/Float.MAX_EXPONENT\", ReValue.ofConstant(Float.MAX_EXPONENT));\n\t\t\tCONST_FIELDS.put(\"java/lang/Float.MIN_NORMAL\", ReValue.ofConstant(Float.MIN_NORMAL));\n\t\t\tCONST_FIELDS.put(\"java/lang/Float.NaN\", ReValue.ofConstant(Float.NaN));\n\t\t\tCONST_FIELDS.put(\"java/lang/Float.NEGATIVE_INFINITY\", ReValue.ofConstant(Float.NEGATIVE_INFINITY));\n\t\t\tCONST_FIELDS.put(\"java/lang/Float.POSITIVE_INFINITY\", ReValue.ofConstant(Float.POSITIVE_INFINITY));\n\t\t\tCONST_FIELDS.put(\"java/lang/Double.BYTES\", ReValue.ofConstant(Double.BYTES));\n\t\t\tCONST_FIELDS.put(\"java/lang/Double.SIZE\", ReValue.ofConstant(Double.SIZE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Double.MIN_VALUE\", ReValue.ofConstant(Double.MIN_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Double.MAX_VALUE\", ReValue.ofConstant(Double.MAX_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Double.MIN_EXPONENT\", ReValue.ofConstant(Double.MIN_EXPONENT));\n\t\t\tCONST_FIELDS.put(\"java/lang/Double.MAX_EXPONENT\", ReValue.ofConstant(Double.MAX_EXPONENT));\n\t\t\tCONST_FIELDS.put(\"java/lang/Double.MIN_NORMAL\", ReValue.ofConstant(Double.MIN_NORMAL));\n\t\t\tCONST_FIELDS.put(\"java/lang/Double.NaN\", ReValue.ofConstant(Double.NaN));\n\t\t\tCONST_FIELDS.put(\"java/lang/Double.NEGATIVE_INFINITY\", ReValue.ofConstant(Double.NEGATIVE_INFINITY));\n\t\t\tCONST_FIELDS.put(\"java/lang/Double.POSITIVE_INFINITY\", ReValue.ofConstant(Double.POSITIVE_INFINITY));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.BYTES\", ReValue.ofConstant(Character.BYTES));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.SIZE\", ReValue.ofConstant(Character.SIZE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MIN_RADIX\", ReValue.ofConstant(Character.MIN_RADIX));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MAX_RADIX\", ReValue.ofConstant(Character.MAX_RADIX));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MIN_VALUE\", ReValue.ofConstant(Character.MIN_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MAX_VALUE\", ReValue.ofConstant(Character.MAX_VALUE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.UNASSIGNED\", ReValue.ofConstant((byte) 0));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.UPPERCASE_LETTER\", ReValue.ofConstant((byte) 1));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.LOWERCASE_LETTER\", ReValue.ofConstant((byte) 2));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.TITLECASE_LETTER\", ReValue.ofConstant((byte) 3));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MODIFIER_LETTER\", ReValue.ofConstant((byte) 4));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.OTHER_LETTER\", ReValue.ofConstant((byte) 5));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.NON_SPACING_MARK\", ReValue.ofConstant((byte) 6));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.ENCLOSING_MARK\", ReValue.ofConstant((byte) 7));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.COMBINING_SPACING_MARK\", ReValue.ofConstant((byte) 8));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DECIMAL_DIGIT_NUMBER\", ReValue.ofConstant((byte) 9));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.LETTER_NUMBER\", ReValue.ofConstant((byte) 10));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.OTHER_NUMBER\", ReValue.ofConstant((byte) 11));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.SPACE_SEPARATOR\", ReValue.ofConstant((byte) 12));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.LINE_SEPARATOR\", ReValue.ofConstant((byte) 13));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.PARAGRAPH_SEPARATOR\", ReValue.ofConstant((byte) 14));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.CONTROL\", ReValue.ofConstant((byte) 15));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.FORMAT\", ReValue.ofConstant((byte) 16));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.PRIVATE_USE\", ReValue.ofConstant((byte) 18));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.SURROGATE\", ReValue.ofConstant((byte) 19));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DASH_PUNCTUATION\", ReValue.ofConstant((byte) 20));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.START_PUNCTUATION\", ReValue.ofConstant((byte) 21));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.END_PUNCTUATION\", ReValue.ofConstant((byte) 22));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.CONNECTOR_PUNCTUATION\", ReValue.ofConstant((byte) 23));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.OTHER_PUNCTUATION\", ReValue.ofConstant((byte) 24));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MATH_SYMBOL\", ReValue.ofConstant((byte) 25));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.CURRENCY_SYMBOL\", ReValue.ofConstant((byte) 26));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MODIFIER_SYMBOL\", ReValue.ofConstant((byte) 27));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.OTHER_SYMBOL\", ReValue.ofConstant((byte) 28));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.INITIAL_QUOTE_PUNCTUATION\", ReValue.ofConstant((byte) 29));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.FINAL_QUOTE_PUNCTUATION\", ReValue.ofConstant((byte) 30));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_UNDEFINED\", ReValue.ofConstant((byte) -1));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_LEFT_TO_RIGHT\", ReValue.ofConstant((byte) 0));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_RIGHT_TO_LEFT\", ReValue.ofConstant((byte) 1));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC\", ReValue.ofConstant((byte) 2));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_EUROPEAN_NUMBER\", ReValue.ofConstant((byte) 3));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR\", ReValue.ofConstant((byte) 4));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR\", ReValue.ofConstant((byte) 5));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_ARABIC_NUMBER\", ReValue.ofConstant((byte) 6));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR\", ReValue.ofConstant((byte) 7));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_NONSPACING_MARK\", ReValue.ofConstant((byte) 8));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_BOUNDARY_NEUTRAL\", ReValue.ofConstant((byte) 9));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR\", ReValue.ofConstant((byte) 10));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_SEGMENT_SEPARATOR\", ReValue.ofConstant((byte) 11));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_WHITESPACE\", ReValue.ofConstant((byte) 12));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_OTHER_NEUTRALS\", ReValue.ofConstant((byte) 13));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING\", ReValue.ofConstant((byte) 14));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE\", ReValue.ofConstant((byte) 15));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING\", ReValue.ofConstant((byte) 16));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE\", ReValue.ofConstant((byte) 17));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT\", ReValue.ofConstant((byte) 18));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_LEFT_TO_RIGHT_ISOLATE\", ReValue.ofConstant((byte) 19));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_RIGHT_TO_LEFT_ISOLATE\", ReValue.ofConstant((byte) 20));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_FIRST_STRONG_ISOLATE\", ReValue.ofConstant((byte) 21));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.DIRECTIONALITY_POP_DIRECTIONAL_ISOLATE\", ReValue.ofConstant((byte) 22));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MIN_HIGH_SURROGATE\", ReValue.ofConstant(Character.MIN_HIGH_SURROGATE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MAX_HIGH_SURROGATE\", ReValue.ofConstant(Character.MAX_HIGH_SURROGATE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MIN_LOW_SURROGATE\", ReValue.ofConstant(Character.MIN_LOW_SURROGATE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MAX_LOW_SURROGATE\", ReValue.ofConstant(Character.MAX_LOW_SURROGATE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MIN_SURROGATE\", ReValue.ofConstant(Character.MIN_SURROGATE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MAX_SURROGATE\", ReValue.ofConstant(Character.MAX_SURROGATE));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MIN_SUPPLEMENTARY_CODE_POINT\", ReValue.ofConstant(Character.MIN_SUPPLEMENTARY_CODE_POINT));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MIN_CODE_POINT\", ReValue.ofConstant(Character.MIN_CODE_POINT));\n\t\t\tCONST_FIELDS.put(\"java/lang/Character.MAX_CODE_POINT\", ReValue.ofConstant(Character.MAX_CODE_POINT));\n\t\t\tCONST_FIELDS.put(\"java/lang/Math.E\", ReValue.ofConstant(Math.E));\n\t\t\tCONST_FIELDS.put(\"java/lang/Math.PI\", ReValue.ofConstant(Math.PI));\n\t\t\tCONST_FIELDS.put(\"java/lang/StrictMath.E\", ReValue.ofConstant(Math.E));\n\t\t\tCONST_FIELDS.put(\"java/lang/StrictMath.PI\", ReValue.ofConstant(Math.PI));\n\t\t\tCONST_FIELDS.put(\"java/io/File.separator\", ReValue.ofConstant(File.separator));\n\t\t\tCONST_FIELDS.put(\"java/io/File.separatorChar\", ReValue.ofConstant(File.separatorChar));\n\t\t\tCONST_FIELDS.put(\"java/io/File.pathSeparator\", ReValue.ofConstant(File.pathSeparator));\n\t\t\tCONST_FIELDS.put(\"java/io/File.pathSeparatorChar\", ReValue.ofConstant(File.pathSeparatorChar));\n\t\t} catch (IllegalValueException ex) {\n\t\t\tlogger.error(\"Failed creating value registry\", ex);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/lookup/BasicInvokeStaticLookup.java",
    "content": "package software.coley.recaf.util.analysis.lookup;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.gen.LookupGenerator;\nimport software.coley.recaf.util.analysis.value.ArrayValue;\nimport software.coley.recaf.util.analysis.value.DoubleValue;\nimport software.coley.recaf.util.analysis.value.FloatValue;\nimport software.coley.recaf.util.analysis.value.IllegalValueException;\nimport software.coley.recaf.util.analysis.value.IntValue;\nimport software.coley.recaf.util.analysis.value.LongValue;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.StringValue;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * Basic implementation of {@link InvokeStaticLookup} for common static fields.\n * <br>\n * Mostly auto-generated, see {@link LookupGenerator#main(String[])}\n *\n * @author Matt Coley\n */\npublic class BasicInvokeStaticLookup extends BasicLookupUtils implements InvokeStaticLookup {\n\tprivate static final Map<String, Func> METHODS = new HashMap<>();\n\tprivate static final DebuggingLogger logger = Logging.get(BasicInvokeStaticLookup.class);\n\n\t@Nonnull\n\t@Override\n\tpublic ReValue get(@Nonnull MethodInsnNode method, @Nonnull List<? extends ReValue> values) {\n\t\tString key = getKey(method);\n\t\tFunc func = METHODS.get(key);\n\t\tReValue value = null;\n\t\tif (func != null)\n\t\t\ttry {\n\t\t\t\tvalue = func.apply(values);\n\t\t\t} catch (Throwable t) {\n\t\t\t\t// Some methods may inherently throw, like 'Math.floorDiv(0, 0)' so these error\n\t\t\t\t// log calls are only active while debugging.\n\t\t\t\tlogger.debugging(l -> l.error(\"Computation threw an exception for: \" + key, t));\n\t\t\t}\n\t\tif (value == null) {\n\t\t\ttry {\n\t\t\t\tvalue = ReValue.ofType(Type.getReturnType(method.desc), Nullness.UNKNOWN);\n\t\t\t} catch (IllegalValueException ex) {\n\t\t\t\tlogger.error(\"Failed default value computation for: \" + key, ex);\n\t\t\t}\n\t\t}\n\t\treturn Objects.requireNonNull(value);\n\t}\n\n\t@Override\n\tpublic boolean hasLookup(@Nonnull MethodInsnNode method) {\n\t\treturn METHODS.containsKey(getKey(method));\n\t}\n\n\t@Nonnull\n\tprivate static String getKey(@Nonnull MethodInsnNode method) {\n\t\treturn method.owner + \".\" + method.name + method.desc;\n\t}\n\n\tstatic {\n\t\t// Utilities & common types\n\t\tmath();\n\t\tarrays();\n\t\tsystem();\n\t\tstrings();\n\t\tobjects();\n\n\t\t// Primitives\n\t\tbooleans();\n\t\tbytes();\n\t\tchars();\n\t\tshorts();\n\t\tints();\n\t\tlongs();\n\t\tfloats();\n\t\tdoubles();\n\t}\n\n\tprivate static void system() {\n\t\tMETHODS.put(\"java/lang/System.lineSeparator()Ljava/lang/String;\", (Func_0) () -> str(System.lineSeparator()));\n\t\t//METHODS.put(\"java/lang/System.getProperty(Ljava/lang/String;)Ljava/lang/String;\", (Func_1<StringValue>) (a) -> str(System.getProperty(str(a))));\n\t\t//METHODS.put(\"java/lang/System.getProperty(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;\", (Func_2<StringValue, StringValue>) (a, b) -> str(System.getProperty(str(a), str(b))));\n\t\t//METHODS.put(\"java/lang/System.getenv(Ljava/lang/String;)Ljava/lang/String;\", (Func_1<StringValue>) (a) -> str(System.getenv(str(a))));\n\t}\n\n\tprivate static void strings() {\n\t\tMETHODS.put(\"java/lang/String.valueOf(J)Ljava/lang/String;\", (Func_1<LongValue>) (a) -> str(String.valueOf(j(a))));\n\t\tMETHODS.put(\"java/lang/String.valueOf([C)Ljava/lang/String;\", (Func_1<ArrayValue>) (a) -> str(String.valueOf(arrc(a))));\n\t\tMETHODS.put(\"java/lang/String.valueOf(Ljava/lang/Object;)Ljava/lang/String;\", (Func_1<ObjectValue>) (a) -> str(String.valueOf(obj(a))));\n\t\tMETHODS.put(\"java/lang/String.valueOf([CII)Ljava/lang/String;\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> str(String.valueOf(arrc(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/lang/String.valueOf(F)Ljava/lang/String;\", (Func_1<FloatValue>) (a) -> str(String.valueOf(f(a))));\n\t\tMETHODS.put(\"java/lang/String.valueOf(D)Ljava/lang/String;\", (Func_1<DoubleValue>) (a) -> str(String.valueOf(d(a))));\n\t\tMETHODS.put(\"java/lang/String.valueOf(C)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(String.valueOf(c(a))));\n\t\tMETHODS.put(\"java/lang/String.valueOf(Z)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(String.valueOf(z(a))));\n\t\tMETHODS.put(\"java/lang/String.valueOf(I)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(String.valueOf(i(a))));\n\t\tMETHODS.put(\"java/lang/String.join(Ljava/lang/CharSequence;[Ljava/lang/CharSequence;)Ljava/lang/String;\", (Func_2<StringValue, ArrayValue>) (a, b) -> str(String.join(str(a), arrstr(b))));\n\t\tMETHODS.put(\"java/lang/String.format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;\", (Func_2<StringValue, ArrayValue>) (a, b) -> str(String.format(str(a), arrobj(b))));\n\t\tMETHODS.put(\"java/lang/String.copyValueOf([C)Ljava/lang/String;\", (Func_1<ArrayValue>) (a) -> str(String.copyValueOf(arrc(a))));\n\t\tMETHODS.put(\"java/lang/String.copyValueOf([CII)Ljava/lang/String;\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> str(String.copyValueOf(arrc(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/lang/CharSequence.compare(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)I\", (Func_2<StringValue, StringValue>) (a, b) -> i(CharSequence.compare(str(a), str(b))));\n\t}\n\n\tprivate static void objects() {\n\t\tMETHODS.put(\"java/util/Objects.equals(Ljava/lang/Object;Ljava/lang/Object;)Z\", (Func_2<ObjectValue, ObjectValue>) (a, b) -> z(Objects.equals(obj(a), obj(b))));\n\t\tMETHODS.put(\"java/util/Objects.toString(Ljava/lang/Object;)Ljava/lang/String;\", (Func_1<ObjectValue>) (a) -> str(Objects.toString(obj(a))));\n\t\tMETHODS.put(\"java/util/Objects.toString(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/String;\", (Func_2<ObjectValue, StringValue>) (a, b) -> str(Objects.toString(obj(a), str(b))));\n\t\tMETHODS.put(\"java/util/Objects.checkIndex(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Objects.checkIndex(i(a), i(b))));\n\t\tMETHODS.put(\"java/util/Objects.checkIndex(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Objects.checkIndex(j(a), j(b))));\n\t\tMETHODS.put(\"java/util/Objects.hashCode(Ljava/lang/Object;)I\", (Func_1<ObjectValue>) (a) -> i(Objects.hashCode(obj(a))));\n\t\tMETHODS.put(\"java/util/Objects.hash([Ljava/lang/Object;)I\", (Func_1<ArrayValue>) (a) -> i(Objects.hash(arrobj(a))));\n\t\tMETHODS.put(\"java/util/Objects.requireNonNull(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;\", (Func_2<ObjectValue, StringValue>) (a, b) -> obj(Objects.requireNonNull(obj(a), str(b))));\n\t\tMETHODS.put(\"java/util/Objects.requireNonNull(Ljava/lang/Object;)Ljava/lang/Object;\", (Func_1<ObjectValue>) (a) -> obj(Objects.requireNonNull(obj(a))));\n\t\tMETHODS.put(\"java/util/Objects.checkFromToIndex(JJJ)J\", (Func_3<LongValue, LongValue, LongValue>) (a, b, c) -> j(Objects.checkFromToIndex(j(a), j(b), j(c))));\n\t\tMETHODS.put(\"java/util/Objects.checkFromToIndex(III)I\", (Func_3<IntValue, IntValue, IntValue>) (a, b, c) -> i(Objects.checkFromToIndex(i(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/util/Objects.checkFromIndexSize(III)I\", (Func_3<IntValue, IntValue, IntValue>) (a, b, c) -> i(Objects.checkFromIndexSize(i(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/util/Objects.checkFromIndexSize(JJJ)J\", (Func_3<LongValue, LongValue, LongValue>) (a, b, c) -> j(Objects.checkFromIndexSize(j(a), j(b), j(c))));\n\t\tMETHODS.put(\"java/util/Objects.deepEquals(Ljava/lang/Object;Ljava/lang/Object;)Z\", (Func_2<ObjectValue, ObjectValue>) (a, b) -> z(Objects.deepEquals(obj(a), obj(b))));\n\t\tMETHODS.put(\"java/util/Objects.toIdentityString(Ljava/lang/Object;)Ljava/lang/String;\", (Func_1<ObjectValue>) (a) -> str(Objects.toIdentityString(obj(a))));\n\t\tMETHODS.put(\"java/util/Objects.isNull(Ljava/lang/Object;)Z\", (Func_1<ObjectValue>) (a) -> z(Objects.isNull(obj(a))));\n\t\tMETHODS.put(\"java/util/Objects.nonNull(Ljava/lang/Object;)Z\", (Func_1<ObjectValue>) (a) -> z(Objects.nonNull(obj(a))));\n\t\tMETHODS.put(\"java/util/Objects.requireNonNullElse(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;\", (Func_2<ObjectValue, ObjectValue>) (a, b) -> obj(Objects.requireNonNullElse(obj(a), obj(b))));\n\t}\n\n\tprivate static void arrays() {\n\t\tMETHODS.put(\"java/util/Arrays.equals([D[D)Z\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> z(Arrays.equals(arrd(a), arrd(b))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([ZII[ZII)Z\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> z(Arrays.equals(arrz(a), i(b), i(c), arrz(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([Z[Z)Z\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> z(Arrays.equals(arrz(a), arrz(b))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([BII[BII)Z\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> z(Arrays.equals(arrb(a), i(b), i(c), arrb(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([B[B)Z\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> z(Arrays.equals(arrb(a), arrb(b))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([Ljava/lang/Object;II[Ljava/lang/Object;II)Z\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> z(Arrays.equals(arrobj(a), i(b), i(c), arrobj(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([Ljava/lang/Object;[Ljava/lang/Object;)Z\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> z(Arrays.equals(arrobj(a), arrobj(b))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([FII[FII)Z\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> z(Arrays.equals(arrf(a), i(b), i(c), arrf(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([F[F)Z\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> z(Arrays.equals(arrf(a), arrf(b))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([DII[DII)Z\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> z(Arrays.equals(arrd(a), i(b), i(c), arrd(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([III[III)Z\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> z(Arrays.equals(arri(a), i(b), i(c), arri(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([CII[CII)Z\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> z(Arrays.equals(arrc(a), i(b), i(c), arrc(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([JII[JII)Z\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> z(Arrays.equals(arrj(a), i(b), i(c), arrj(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([J[J)Z\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> z(Arrays.equals(arrj(a), arrj(b))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([S[S)Z\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> z(Arrays.equals(arrs(a), arrs(b))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([SII[SII)Z\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> z(Arrays.equals(arrs(a), i(b), i(c), arrs(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([C[C)Z\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> z(Arrays.equals(arrc(a), arrc(b))));\n\t\tMETHODS.put(\"java/util/Arrays.equals([I[I)Z\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> z(Arrays.equals(arri(a), arri(b))));\n\t\tMETHODS.put(\"java/util/Arrays.toString([C)Ljava/lang/String;\", (Func_1<ArrayValue>) (a) -> str(Arrays.toString(arrc(a))));\n\t\tMETHODS.put(\"java/util/Arrays.toString([F)Ljava/lang/String;\", (Func_1<ArrayValue>) (a) -> str(Arrays.toString(arrf(a))));\n\t\tMETHODS.put(\"java/util/Arrays.toString([D)Ljava/lang/String;\", (Func_1<ArrayValue>) (a) -> str(Arrays.toString(arrd(a))));\n\t\tMETHODS.put(\"java/util/Arrays.toString([Ljava/lang/Object;)Ljava/lang/String;\", (Func_1<ArrayValue>) (a) -> str(Arrays.toString(arrobj(a))));\n\t\tMETHODS.put(\"java/util/Arrays.toString([B)Ljava/lang/String;\", (Func_1<ArrayValue>) (a) -> str(Arrays.toString(arrb(a))));\n\t\tMETHODS.put(\"java/util/Arrays.toString([Z)Ljava/lang/String;\", (Func_1<ArrayValue>) (a) -> str(Arrays.toString(arrz(a))));\n\t\tMETHODS.put(\"java/util/Arrays.toString([J)Ljava/lang/String;\", (Func_1<ArrayValue>) (a) -> str(Arrays.toString(arrj(a))));\n\t\tMETHODS.put(\"java/util/Arrays.toString([I)Ljava/lang/String;\", (Func_1<ArrayValue>) (a) -> str(Arrays.toString(arri(a))));\n\t\tMETHODS.put(\"java/util/Arrays.toString([S)Ljava/lang/String;\", (Func_1<ArrayValue>) (a) -> str(Arrays.toString(arrs(a))));\n\t\tMETHODS.put(\"java/util/Arrays.hashCode([F)I\", (Func_1<ArrayValue>) (a) -> i(Arrays.hashCode(arrf(a))));\n\t\tMETHODS.put(\"java/util/Arrays.hashCode([B)I\", (Func_1<ArrayValue>) (a) -> i(Arrays.hashCode(arrb(a))));\n\t\tMETHODS.put(\"java/util/Arrays.hashCode([Z)I\", (Func_1<ArrayValue>) (a) -> i(Arrays.hashCode(arrz(a))));\n\t\tMETHODS.put(\"java/util/Arrays.hashCode([D)I\", (Func_1<ArrayValue>) (a) -> i(Arrays.hashCode(arrd(a))));\n\t\tMETHODS.put(\"java/util/Arrays.hashCode([Ljava/lang/Object;)I\", (Func_1<ArrayValue>) (a) -> i(Arrays.hashCode(arrobj(a))));\n\t\tMETHODS.put(\"java/util/Arrays.hashCode([J)I\", (Func_1<ArrayValue>) (a) -> i(Arrays.hashCode(arrj(a))));\n\t\tMETHODS.put(\"java/util/Arrays.hashCode([I)I\", (Func_1<ArrayValue>) (a) -> i(Arrays.hashCode(arri(a))));\n\t\tMETHODS.put(\"java/util/Arrays.hashCode([S)I\", (Func_1<ArrayValue>) (a) -> i(Arrays.hashCode(arrs(a))));\n\t\tMETHODS.put(\"java/util/Arrays.hashCode([C)I\", (Func_1<ArrayValue>) (a) -> i(Arrays.hashCode(arrc(a))));\n\t\tMETHODS.put(\"java/util/Arrays.compareUnsigned([B[B)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.compareUnsigned(arrb(a), arrb(b))));\n\t\tMETHODS.put(\"java/util/Arrays.compareUnsigned([S[S)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.compareUnsigned(arrs(a), arrs(b))));\n\t\tMETHODS.put(\"java/util/Arrays.compareUnsigned([BII[BII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.compareUnsigned(arrb(a), i(b), i(c), arrb(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.compareUnsigned([JII[JII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.compareUnsigned(arrj(a), i(b), i(c), arrj(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.compareUnsigned([SII[SII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.compareUnsigned(arrs(a), i(b), i(c), arrs(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.compareUnsigned([J[J)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.compareUnsigned(arrj(a), arrj(b))));\n\t\tMETHODS.put(\"java/util/Arrays.compareUnsigned([III[III)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.compareUnsigned(arri(a), i(b), i(c), arri(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.compareUnsigned([I[I)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.compareUnsigned(arri(a), arri(b))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOf([DI)[D\", (Func_2<ArrayValue, IntValue>) (a, b) -> arrd(Arrays.copyOf(arrd(a), i(b))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOf([II)[I\", (Func_2<ArrayValue, IntValue>) (a, b) -> arri(Arrays.copyOf(arri(a), i(b))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOf([BI)[B\", (Func_2<ArrayValue, IntValue>) (a, b) -> arrb(Arrays.copyOf(arrb(a), i(b))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOf([Ljava/lang/Object;I)[Ljava/lang/Object;\", (Func_2<ArrayValue, IntValue>) (a, b) -> arrobj(Arrays.copyOf(arrobj(a), i(b))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOf([ZI)[Z\", (Func_2<ArrayValue, IntValue>) (a, b) -> arrz(Arrays.copyOf(arrz(a), i(b))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOf([FI)[F\", (Func_2<ArrayValue, IntValue>) (a, b) -> arrf(Arrays.copyOf(arrf(a), i(b))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOf([CI)[C\", (Func_2<ArrayValue, IntValue>) (a, b) -> arrc(Arrays.copyOf(arrc(a), i(b))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOf([JI)[J\", (Func_2<ArrayValue, IntValue>) (a, b) -> arrj(Arrays.copyOf(arrj(a), i(b))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOf([SI)[S\", (Func_2<ArrayValue, IntValue>) (a, b) -> arrs(Arrays.copyOf(arrs(a), i(b))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOfRange([ZII)[Z\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> arrz(Arrays.copyOfRange(arrz(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOfRange([CII)[C\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> arrc(Arrays.copyOfRange(arrc(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOfRange([JII)[J\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> arrj(Arrays.copyOfRange(arrj(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOfRange([FII)[F\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> arrf(Arrays.copyOfRange(arrf(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOfRange([DII)[D\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> arrd(Arrays.copyOfRange(arrd(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOfRange([Ljava/lang/Object;II)[Ljava/lang/Object;\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> arrobj(Arrays.copyOfRange(arrobj(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOfRange([BII)[B\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> arrb(Arrays.copyOfRange(arrb(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOfRange([SII)[S\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> arrs(Arrays.copyOfRange(arrs(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/util/Arrays.copyOfRange([III)[I\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> arri(Arrays.copyOfRange(arri(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([CII[CII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.compare(arrc(a), i(b), i(c), arrc(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([I[I)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.compare(arri(a), arri(b))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([C[C)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.compare(arrc(a), arrc(b))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([III[III)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.compare(arri(a), i(b), i(c), arri(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([J[J)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.compare(arrj(a), arrj(b))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([JII[JII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.compare(arrj(a), i(b), i(c), arrj(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([D[D)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.compare(arrd(a), arrd(b))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([FII[FII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.compare(arrf(a), i(b), i(c), arrf(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([S[S)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.compare(arrs(a), arrs(b))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([SII[SII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.compare(arrs(a), i(b), i(c), arrs(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([Z[Z)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.compare(arrz(a), arrz(b))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([ZII[ZII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.compare(arrz(a), i(b), i(c), arrz(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([B[B)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.compare(arrb(a), arrb(b))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([BII[BII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.compare(arrb(a), i(b), i(c), arrb(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([DII[DII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.compare(arrd(a), i(b), i(c), arrd(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.compare([F[F)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.compare(arrf(a), arrf(b))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([J[J)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.mismatch(arrj(a), arrj(b))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([F[F)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.mismatch(arrf(a), arrf(b))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([JII[JII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.mismatch(arrj(a), i(b), i(c), arrj(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([Ljava/lang/Object;[Ljava/lang/Object;)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.mismatch(arrobj(a), arrobj(b))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([Ljava/lang/Object;II[Ljava/lang/Object;II)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.mismatch(arrobj(a), i(b), i(c), arrobj(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([DII[DII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.mismatch(arrd(a), i(b), i(c), arrd(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([D[D)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.mismatch(arrd(a), arrd(b))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([FII[FII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.mismatch(arrf(a), i(b), i(c), arrf(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([BII[BII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.mismatch(arrb(a), i(b), i(c), arrb(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([C[C)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.mismatch(arrc(a), arrc(b))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([CII[CII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.mismatch(arrc(a), i(b), i(c), arrc(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([I[I)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.mismatch(arri(a), arri(b))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([SII[SII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.mismatch(arrs(a), i(b), i(c), arrs(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([S[S)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.mismatch(arrs(a), arrs(b))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([III[III)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.mismatch(arri(a), i(b), i(c), arri(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([Z[Z)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.mismatch(arrz(a), arrz(b))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([ZII[ZII)I\", (Func_6<ArrayValue, IntValue, IntValue, ArrayValue, IntValue, IntValue>) (a, b, c, d, e, f) -> i(Arrays.mismatch(arrz(a), i(b), i(c), arrz(d), i(e), i(f))));\n\t\tMETHODS.put(\"java/util/Arrays.mismatch([B[B)I\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> i(Arrays.mismatch(arrb(a), arrb(b))));\n\t\tMETHODS.put(\"java/util/Arrays.deepEquals([Ljava/lang/Object;[Ljava/lang/Object;)Z\", (Func_2<ArrayValue, ArrayValue>) (a, b) -> z(Arrays.deepEquals(arrobj(a), arrobj(b))));\n\t\tMETHODS.put(\"java/util/Arrays.deepHashCode([Ljava/lang/Object;)I\", (Func_1<ArrayValue>) (a) -> i(Arrays.deepHashCode(arrobj(a))));\n\t\tMETHODS.put(\"java/util/Arrays.deepToString([Ljava/lang/Object;)Ljava/lang/String;\", (Func_1<ArrayValue>) (a) -> str(Arrays.deepToString(arrobj(a))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([FIIF)I\", (Func_4<ArrayValue, IntValue, IntValue, FloatValue>) (a, b, c, d) -> i(Arrays.binarySearch(arrf(a), i(b), i(c), f(d))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([FF)I\", (Func_2<ArrayValue, FloatValue>) (a, b) -> i(Arrays.binarySearch(arrf(a), f(b))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([SS)I\", (Func_2<ArrayValue, IntValue>) (a, b) -> i(Arrays.binarySearch(arrs(a), s(b))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([SIIS)I\", (Func_4<ArrayValue, IntValue, IntValue, IntValue>) (a, b, c, d) -> i(Arrays.binarySearch(arrs(a), i(b), i(c), s(d))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([Ljava/lang/Object;Ljava/lang/Object;)I\", (Func_2<ArrayValue, ObjectValue>) (a, b) -> i(Arrays.binarySearch(arrobj(a), obj(b))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([Ljava/lang/Object;IILjava/lang/Object;)I\", (Func_4<ArrayValue, IntValue, IntValue, ObjectValue>) (a, b, c, d) -> i(Arrays.binarySearch(arrobj(a), i(b), i(c), obj(d))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([DIID)I\", (Func_4<ArrayValue, IntValue, IntValue, DoubleValue>) (a, b, c, d) -> i(Arrays.binarySearch(arrd(a), i(b), i(c), d(d))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([DD)I\", (Func_2<ArrayValue, DoubleValue>) (a, b) -> i(Arrays.binarySearch(arrd(a), d(b))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([BIIB)I\", (Func_4<ArrayValue, IntValue, IntValue, IntValue>) (a, b, c, d) -> i(Arrays.binarySearch(arrb(a), i(b), i(c), b(d))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([BB)I\", (Func_2<ArrayValue, IntValue>) (a, b) -> i(Arrays.binarySearch(arrb(a), b(b))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([CIIC)I\", (Func_4<ArrayValue, IntValue, IntValue, IntValue>) (a, b, c, d) -> i(Arrays.binarySearch(arrc(a), i(b), i(c), c(d))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([CC)I\", (Func_2<ArrayValue, IntValue>) (a, b) -> i(Arrays.binarySearch(arrc(a), c(b))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([IIII)I\", (Func_4<ArrayValue, IntValue, IntValue, IntValue>) (a, b, c, d) -> i(Arrays.binarySearch(arri(a), i(b), i(c), i(d))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([JJ)I\", (Func_2<ArrayValue, LongValue>) (a, b) -> i(Arrays.binarySearch(arrj(a), j(b))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([JIIJ)I\", (Func_4<ArrayValue, IntValue, IntValue, LongValue>) (a, b, c, d) -> i(Arrays.binarySearch(arrj(a), i(b), i(c), j(d))));\n\t\tMETHODS.put(\"java/util/Arrays.binarySearch([II)I\", (Func_2<ArrayValue, IntValue>) (a, b) -> i(Arrays.binarySearch(arri(a), i(b))));\n\t}\n\n\tprivate static void booleans() {\n\t\tMETHODS.put(\"java/lang/Boolean.toString(Z)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(Boolean.toString(z(a))));\n\t\tMETHODS.put(\"java/lang/Boolean.hashCode(Z)I\", (Func_1<IntValue>) (a) -> i(Boolean.hashCode(z(a))));\n\t\tMETHODS.put(\"java/lang/Boolean.getBoolean(Ljava/lang/String;)Z\", (Func_1<StringValue>) (a) -> z(Boolean.getBoolean(str(a))));\n\t\tMETHODS.put(\"java/lang/Boolean.compare(ZZ)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Boolean.compare(z(a), z(b))));\n\t\tMETHODS.put(\"java/lang/Boolean.valueOf(Ljava/lang/String;)Ljava/lang/Boolean;\", (Func_1<StringValue>) (a) -> obj(Boolean.valueOf(str(a))));\n\t\tMETHODS.put(\"java/lang/Boolean.valueOf(Z)Ljava/lang/Boolean;\", (Func_1<IntValue>) (a) -> obj(Boolean.valueOf(z(a))));\n\t\tMETHODS.put(\"java/lang/Boolean.parseBoolean(Ljava/lang/String;)Z\", (Func_1<StringValue>) (a) -> z(Boolean.parseBoolean(str(a))));\n\t\tMETHODS.put(\"java/lang/Boolean.logicalAnd(ZZ)Z\", (Func_2<IntValue, IntValue>) (a, b) -> z(Boolean.logicalAnd(z(a), z(b))));\n\t\tMETHODS.put(\"java/lang/Boolean.logicalOr(ZZ)Z\", (Func_2<IntValue, IntValue>) (a, b) -> z(Boolean.logicalOr(z(a), z(b))));\n\t\tMETHODS.put(\"java/lang/Boolean.logicalXor(ZZ)Z\", (Func_2<IntValue, IntValue>) (a, b) -> z(Boolean.logicalXor(z(a), z(b))));\n\t}\n\n\tprivate static void bytes() {\n\t\tMETHODS.put(\"java/lang/Byte.toString(B)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(Byte.toString(b(a))));\n\t\tMETHODS.put(\"java/lang/Byte.hashCode(B)I\", (Func_1<IntValue>) (a) -> i(Byte.hashCode(b(a))));\n\t\tMETHODS.put(\"java/lang/Byte.compareUnsigned(BB)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Byte.compareUnsigned(b(a), b(b))));\n\t\tMETHODS.put(\"java/lang/Byte.compare(BB)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Byte.compare(b(a), b(b))));\n\t\tMETHODS.put(\"java/lang/Byte.valueOf(Ljava/lang/String;)Ljava/lang/Byte;\", (Func_1<StringValue>) (a) -> obj(Byte.valueOf(str(a))));\n\t\tMETHODS.put(\"java/lang/Byte.valueOf(Ljava/lang/String;I)Ljava/lang/Byte;\", (Func_2<StringValue, IntValue>) (a, b) -> obj(Byte.valueOf(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Byte.valueOf(B)Ljava/lang/Byte;\", (Func_1<IntValue>) (a) -> obj(Byte.valueOf(b(a))));\n\t\tMETHODS.put(\"java/lang/Byte.decode(Ljava/lang/String;)Ljava/lang/Byte;\", (Func_1<StringValue>) (a) -> obj(Byte.decode(str(a))));\n\t\tMETHODS.put(\"java/lang/Byte.toUnsignedLong(B)J\", (Func_1<IntValue>) (a) -> j(Byte.toUnsignedLong(b(a))));\n\t\tMETHODS.put(\"java/lang/Byte.toUnsignedInt(B)I\", (Func_1<IntValue>) (a) -> i(Byte.toUnsignedInt(b(a))));\n\t\tMETHODS.put(\"java/lang/Byte.parseByte(Ljava/lang/String;)B\", (Func_1<StringValue>) (a) -> b(Byte.parseByte(str(a))));\n\t\tMETHODS.put(\"java/lang/Byte.parseByte(Ljava/lang/String;I)B\", (Func_2<StringValue, IntValue>) (a, b) -> b(Byte.parseByte(str(a), i(b))));\n\t}\n\n\t@SuppressWarnings(\"deprecation\")\n\tprivate static void chars() {\n\t\tMETHODS.put(\"java/lang/Character.getName(I)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(Character.getName(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isJavaIdentifierStart(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isJavaIdentifierStart(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isJavaIdentifierStart(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isJavaIdentifierStart(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isJavaIdentifierPart(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isJavaIdentifierPart(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isJavaIdentifierPart(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isJavaIdentifierPart(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.toString(I)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(Character.toString(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.toString(C)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(Character.toString(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.hashCode(C)I\", (Func_1<IntValue>) (a) -> i(Character.hashCode(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.reverseBytes(C)C\", (Func_1<IntValue>) (a) -> c(Character.reverseBytes(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isDigit(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isDigit(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isDigit(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isDigit(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isLowerCase(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isLowerCase(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isLowerCase(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isLowerCase(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isUpperCase(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isUpperCase(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isUpperCase(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isUpperCase(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isWhitespace(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isWhitespace(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isWhitespace(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isWhitespace(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.compare(CC)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Character.compare(c(a), c(b))));\n\t\tMETHODS.put(\"java/lang/Character.valueOf(C)Ljava/lang/Character;\", (Func_1<IntValue>) (a) -> obj(Character.valueOf(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.toChars(I)[C\", (Func_1<IntValue>) (a) -> arrc(Character.toChars(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.toChars(I[CI)I\", (Func_3<IntValue, ArrayValue, IntValue>) (a, b, c) -> i(Character.toChars(i(a), arrc(b), i(c))));\n\t\tMETHODS.put(\"java/lang/Character.isHighSurrogate(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isHighSurrogate(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isLowSurrogate(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isLowSurrogate(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isSurrogate(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isSurrogate(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isSupplementaryCodePoint(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isSupplementaryCodePoint(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.highSurrogate(I)C\", (Func_1<IntValue>) (a) -> c(Character.highSurrogate(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.lowSurrogate(I)C\", (Func_1<IntValue>) (a) -> c(Character.lowSurrogate(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.toCodePoint(CC)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Character.toCodePoint(c(a), c(b))));\n\t\tMETHODS.put(\"java/lang/Character.codePointAt(Ljava/lang/CharSequence;I)I\", (Func_2<StringValue, IntValue>) (a, b) -> i(Character.codePointAt(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Character.codePointAt([CII)I\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> i(Character.codePointAt(arrc(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/lang/Character.codePointAt([CI)I\", (Func_2<ArrayValue, IntValue>) (a, b) -> i(Character.codePointAt(arrc(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Character.codePointBefore(Ljava/lang/CharSequence;I)I\", (Func_2<StringValue, IntValue>) (a, b) -> i(Character.codePointBefore(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Character.codePointBefore([CI)I\", (Func_2<ArrayValue, IntValue>) (a, b) -> i(Character.codePointBefore(arrc(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Character.codePointBefore([CII)I\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> i(Character.codePointBefore(arrc(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/lang/Character.codePointCount(Ljava/lang/CharSequence;II)I\", (Func_3<StringValue, IntValue, IntValue>) (a, b, c) -> i(Character.codePointCount(str(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/lang/Character.codePointCount([CII)I\", (Func_3<ArrayValue, IntValue, IntValue>) (a, b, c) -> i(Character.codePointCount(arrc(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/lang/Character.offsetByCodePoints([CIIII)I\", (Func_5<ArrayValue, IntValue, IntValue, IntValue, IntValue>) (a, b, c, d, e) -> i(Character.offsetByCodePoints(arrc(a), i(b), i(c), i(d), i(e))));\n\t\tMETHODS.put(\"java/lang/Character.offsetByCodePoints(Ljava/lang/CharSequence;II)I\", (Func_3<StringValue, IntValue, IntValue>) (a, b, c) -> i(Character.offsetByCodePoints(str(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/lang/Character.toLowerCase(C)C\", (Func_1<IntValue>) (a) -> c(Character.toLowerCase(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.toLowerCase(I)I\", (Func_1<IntValue>) (a) -> i(Character.toLowerCase(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.toUpperCase(I)I\", (Func_1<IntValue>) (a) -> i(Character.toUpperCase(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.toUpperCase(C)C\", (Func_1<IntValue>) (a) -> c(Character.toUpperCase(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isBmpCodePoint(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isBmpCodePoint(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.getType(C)I\", (Func_1<IntValue>) (a) -> i(Character.getType(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.getType(I)I\", (Func_1<IntValue>) (a) -> i(Character.getType(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isLetter(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isLetter(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isLetter(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isLetter(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isLetterOrDigit(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isLetterOrDigit(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isLetterOrDigit(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isLetterOrDigit(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isValidCodePoint(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isValidCodePoint(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isTitleCase(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isTitleCase(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isTitleCase(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isTitleCase(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isDefined(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isDefined(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isDefined(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isDefined(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isIdeographic(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isIdeographic(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isUnicodeIdentifierStart(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isUnicodeIdentifierStart(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isUnicodeIdentifierStart(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isUnicodeIdentifierStart(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isUnicodeIdentifierPart(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isUnicodeIdentifierPart(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isUnicodeIdentifierPart(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isUnicodeIdentifierPart(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isIdentifierIgnorable(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isIdentifierIgnorable(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isIdentifierIgnorable(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isIdentifierIgnorable(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isEmoji(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isEmoji(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isEmojiPresentation(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isEmojiPresentation(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isEmojiModifier(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isEmojiModifier(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isEmojiModifierBase(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isEmojiModifierBase(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isEmojiComponent(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isEmojiComponent(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isExtendedPictographic(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isExtendedPictographic(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.toTitleCase(C)C\", (Func_1<IntValue>) (a) -> c(Character.toTitleCase(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.toTitleCase(I)I\", (Func_1<IntValue>) (a) -> i(Character.toTitleCase(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.digit(CI)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Character.digit(c(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Character.digit(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Character.digit(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Character.getNumericValue(I)I\", (Func_1<IntValue>) (a) -> i(Character.getNumericValue(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.getNumericValue(C)I\", (Func_1<IntValue>) (a) -> i(Character.getNumericValue(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isSpaceChar(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isSpaceChar(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isSpaceChar(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isSpaceChar(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isISOControl(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isISOControl(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isISOControl(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isISOControl(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.getDirectionality(C)B\", (Func_1<IntValue>) (a) -> b(Character.getDirectionality(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.getDirectionality(I)B\", (Func_1<IntValue>) (a) -> b(Character.getDirectionality(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isMirrored(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isMirrored(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isMirrored(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isMirrored(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isSurrogatePair(CC)Z\", (Func_2<IntValue, IntValue>) (a, b) -> z(Character.isSurrogatePair(c(a), c(b))));\n\t\tMETHODS.put(\"java/lang/Character.charCount(I)I\", (Func_1<IntValue>) (a) -> i(Character.charCount(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isJavaLetter(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isJavaLetter(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isJavaLetterOrDigit(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isJavaLetterOrDigit(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.isAlphabetic(I)Z\", (Func_1<IntValue>) (a) -> z(Character.isAlphabetic(i(a))));\n\t\tMETHODS.put(\"java/lang/Character.isSpace(C)Z\", (Func_1<IntValue>) (a) -> z(Character.isSpace(c(a))));\n\t\tMETHODS.put(\"java/lang/Character.forDigit(II)C\", (Func_2<IntValue, IntValue>) (a, b) -> c(Character.forDigit(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Character.codePointOf(Ljava/lang/String;)I\", (Func_1<StringValue>) (a) -> i(Character.codePointOf(str(a))));\n\t}\n\n\tprivate static void shorts() {\n\t\tMETHODS.put(\"java/lang/Short.toString(S)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(Short.toString(s(a))));\n\t\tMETHODS.put(\"java/lang/Short.hashCode(S)I\", (Func_1<IntValue>) (a) -> i(Short.hashCode(s(a))));\n\t\tMETHODS.put(\"java/lang/Short.compareUnsigned(SS)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Short.compareUnsigned(s(a), s(b))));\n\t\tMETHODS.put(\"java/lang/Short.reverseBytes(S)S\", (Func_1<IntValue>) (a) -> s(Short.reverseBytes(s(a))));\n\t\tMETHODS.put(\"java/lang/Short.compare(SS)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Short.compare(s(a), s(b))));\n\t\tMETHODS.put(\"java/lang/Short.valueOf(Ljava/lang/String;I)Ljava/lang/Short;\", (Func_2<StringValue, IntValue>) (a, b) -> obj(Short.valueOf(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Short.valueOf(Ljava/lang/String;)Ljava/lang/Short;\", (Func_1<StringValue>) (a) -> obj(Short.valueOf(str(a))));\n\t\tMETHODS.put(\"java/lang/Short.valueOf(S)Ljava/lang/Short;\", (Func_1<IntValue>) (a) -> obj(Short.valueOf(s(a))));\n\t\tMETHODS.put(\"java/lang/Short.decode(Ljava/lang/String;)Ljava/lang/Short;\", (Func_1<StringValue>) (a) -> obj(Short.decode(str(a))));\n\t\tMETHODS.put(\"java/lang/Short.toUnsignedLong(S)J\", (Func_1<IntValue>) (a) -> j(Short.toUnsignedLong(s(a))));\n\t\tMETHODS.put(\"java/lang/Short.toUnsignedInt(S)I\", (Func_1<IntValue>) (a) -> i(Short.toUnsignedInt(s(a))));\n\t\tMETHODS.put(\"java/lang/Short.parseShort(Ljava/lang/String;)S\", (Func_1<StringValue>) (a) -> s(Short.parseShort(str(a))));\n\t\tMETHODS.put(\"java/lang/Short.parseShort(Ljava/lang/String;I)S\", (Func_2<StringValue, IntValue>) (a, b) -> s(Short.parseShort(str(a), i(b))));\n\t}\n\n\tprivate static void ints() {\n\t\tMETHODS.put(\"java/lang/Integer.numberOfLeadingZeros(I)I\", (Func_1<IntValue>) (a) -> i(Integer.numberOfLeadingZeros(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.numberOfTrailingZeros(I)I\", (Func_1<IntValue>) (a) -> i(Integer.numberOfTrailingZeros(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.bitCount(I)I\", (Func_1<IntValue>) (a) -> i(Integer.bitCount(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.toString(I)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(Integer.toString(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.toString(II)Ljava/lang/String;\", (Func_2<IntValue, IntValue>) (a, b) -> str(Integer.toString(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.hashCode(I)I\", (Func_1<IntValue>) (a) -> i(Integer.hashCode(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.min(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Integer.min(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.max(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Integer.max(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.signum(I)I\", (Func_1<IntValue>) (a) -> i(Integer.signum(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.expand(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Integer.expand(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.compareUnsigned(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Integer.compareUnsigned(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.divideUnsigned(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Integer.divideUnsigned(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.remainderUnsigned(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Integer.remainderUnsigned(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.reverse(I)I\", (Func_1<IntValue>) (a) -> i(Integer.reverse(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.reverseBytes(I)I\", (Func_1<IntValue>) (a) -> i(Integer.reverseBytes(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.compress(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Integer.compress(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.compare(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Integer.compare(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.valueOf(Ljava/lang/String;)Ljava/lang/Integer;\", (Func_1<StringValue>) (a) -> obj(Integer.valueOf(str(a))));\n\t\tMETHODS.put(\"java/lang/Integer.valueOf(Ljava/lang/String;I)Ljava/lang/Integer;\", (Func_2<StringValue, IntValue>) (a, b) -> obj(Integer.valueOf(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.valueOf(I)Ljava/lang/Integer;\", (Func_1<IntValue>) (a) -> obj(Integer.valueOf(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.toHexString(I)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(Integer.toHexString(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.decode(Ljava/lang/String;)Ljava/lang/Integer;\", (Func_1<StringValue>) (a) -> obj(Integer.decode(str(a))));\n\t\tMETHODS.put(\"java/lang/Integer.parseInt(Ljava/lang/String;)I\", (Func_1<StringValue>) (a) -> i(Integer.parseInt(str(a))));\n\t\tMETHODS.put(\"java/lang/Integer.parseInt(Ljava/lang/CharSequence;III)I\", (Func_4<StringValue, IntValue, IntValue, IntValue>) (a, b, c, d) -> i(Integer.parseInt(str(a), i(b), i(c), i(d))));\n\t\tMETHODS.put(\"java/lang/Integer.parseInt(Ljava/lang/String;I)I\", (Func_2<StringValue, IntValue>) (a, b) -> i(Integer.parseInt(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.toUnsignedLong(I)J\", (Func_1<IntValue>) (a) -> j(Integer.toUnsignedLong(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.sum(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Integer.sum(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.toUnsignedString(II)Ljava/lang/String;\", (Func_2<IntValue, IntValue>) (a, b) -> str(Integer.toUnsignedString(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.toUnsignedString(I)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(Integer.toUnsignedString(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.parseUnsignedInt(Ljava/lang/String;)I\", (Func_1<StringValue>) (a) -> i(Integer.parseUnsignedInt(str(a))));\n\t\tMETHODS.put(\"java/lang/Integer.parseUnsignedInt(Ljava/lang/String;I)I\", (Func_2<StringValue, IntValue>) (a, b) -> i(Integer.parseUnsignedInt(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.parseUnsignedInt(Ljava/lang/CharSequence;III)I\", (Func_4<StringValue, IntValue, IntValue, IntValue>) (a, b, c, d) -> i(Integer.parseUnsignedInt(str(a), i(b), i(c), i(d))));\n\t\tMETHODS.put(\"java/lang/Integer.getInteger(Ljava/lang/String;Ljava/lang/Integer;)Ljava/lang/Integer;\", (Func_2<StringValue, ObjectValue>) (a, b) -> obj(Integer.getInteger(str(a), BasicLookupUtils.<Integer>obj(b))));\n\t\tMETHODS.put(\"java/lang/Integer.getInteger(Ljava/lang/String;I)Ljava/lang/Integer;\", (Func_2<StringValue, IntValue>) (a, b) -> obj(Integer.getInteger(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.getInteger(Ljava/lang/String;)Ljava/lang/Integer;\", (Func_1<StringValue>) (a) -> obj(Integer.getInteger(str(a))));\n\t\tMETHODS.put(\"java/lang/Integer.toOctalString(I)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(Integer.toOctalString(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.toBinaryString(I)Ljava/lang/String;\", (Func_1<IntValue>) (a) -> str(Integer.toBinaryString(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.highestOneBit(I)I\", (Func_1<IntValue>) (a) -> i(Integer.highestOneBit(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.lowestOneBit(I)I\", (Func_1<IntValue>) (a) -> i(Integer.lowestOneBit(i(a))));\n\t\tMETHODS.put(\"java/lang/Integer.rotateLeft(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Integer.rotateLeft(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Integer.rotateRight(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Integer.rotateRight(i(a), i(b))));\n\t}\n\n\tprivate static void longs() {\n\t\tMETHODS.put(\"java/lang/Long.numberOfLeadingZeros(J)I\", (Func_1<LongValue>) (a) -> i(Long.numberOfLeadingZeros(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.numberOfTrailingZeros(J)I\", (Func_1<LongValue>) (a) -> i(Long.numberOfTrailingZeros(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.bitCount(J)I\", (Func_1<LongValue>) (a) -> i(Long.bitCount(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.toString(J)Ljava/lang/String;\", (Func_1<LongValue>) (a) -> str(Long.toString(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.toString(JI)Ljava/lang/String;\", (Func_2<LongValue, IntValue>) (a, b) -> str(Long.toString(j(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Long.hashCode(J)I\", (Func_1<LongValue>) (a) -> i(Long.hashCode(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.min(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Long.min(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Long.max(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Long.max(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Long.signum(J)I\", (Func_1<LongValue>) (a) -> i(Long.signum(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.expand(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Long.expand(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Long.compareUnsigned(JJ)I\", (Func_2<LongValue, LongValue>) (a, b) -> i(Long.compareUnsigned(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Long.divideUnsigned(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Long.divideUnsigned(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Long.remainderUnsigned(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Long.remainderUnsigned(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Long.reverse(J)J\", (Func_1<LongValue>) (a) -> j(Long.reverse(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.reverseBytes(J)J\", (Func_1<LongValue>) (a) -> j(Long.reverseBytes(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.compress(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Long.compress(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Long.getLong(Ljava/lang/String;Ljava/lang/Long;)Ljava/lang/Long;\", (Func_2<StringValue, ObjectValue>) (a, b) -> obj(Long.getLong(str(a), BasicLookupUtils.<Long>obj(b))));\n\t\tMETHODS.put(\"java/lang/Long.getLong(Ljava/lang/String;)Ljava/lang/Long;\", (Func_1<StringValue>) (a) -> obj(Long.getLong(str(a))));\n\t\tMETHODS.put(\"java/lang/Long.getLong(Ljava/lang/String;J)Ljava/lang/Long;\", (Func_2<StringValue, LongValue>) (a, b) -> obj(Long.getLong(str(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Long.compare(JJ)I\", (Func_2<LongValue, LongValue>) (a, b) -> i(Long.compare(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Long.valueOf(Ljava/lang/String;)Ljava/lang/Long;\", (Func_1<StringValue>) (a) -> obj(Long.valueOf(str(a))));\n\t\tMETHODS.put(\"java/lang/Long.valueOf(J)Ljava/lang/Long;\", (Func_1<LongValue>) (a) -> obj(Long.valueOf(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.valueOf(Ljava/lang/String;I)Ljava/lang/Long;\", (Func_2<StringValue, IntValue>) (a, b) -> obj(Long.valueOf(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Long.toHexString(J)Ljava/lang/String;\", (Func_1<LongValue>) (a) -> str(Long.toHexString(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.decode(Ljava/lang/String;)Ljava/lang/Long;\", (Func_1<StringValue>) (a) -> obj(Long.decode(str(a))));\n\t\tMETHODS.put(\"java/lang/Long.sum(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Long.sum(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Long.toUnsignedString(J)Ljava/lang/String;\", (Func_1<LongValue>) (a) -> str(Long.toUnsignedString(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.toUnsignedString(JI)Ljava/lang/String;\", (Func_2<LongValue, IntValue>) (a, b) -> str(Long.toUnsignedString(j(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Long.toOctalString(J)Ljava/lang/String;\", (Func_1<LongValue>) (a) -> str(Long.toOctalString(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.toBinaryString(J)Ljava/lang/String;\", (Func_1<LongValue>) (a) -> str(Long.toBinaryString(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.highestOneBit(J)J\", (Func_1<LongValue>) (a) -> j(Long.highestOneBit(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.lowestOneBit(J)J\", (Func_1<LongValue>) (a) -> j(Long.lowestOneBit(j(a))));\n\t\tMETHODS.put(\"java/lang/Long.rotateLeft(JI)J\", (Func_2<LongValue, IntValue>) (a, b) -> j(Long.rotateLeft(j(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Long.rotateRight(JI)J\", (Func_2<LongValue, IntValue>) (a, b) -> j(Long.rotateRight(j(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Long.parseLong(Ljava/lang/CharSequence;III)J\", (Func_4<StringValue, IntValue, IntValue, IntValue>) (a, b, c, d) -> j(Long.parseLong(str(a), i(b), i(c), i(d))));\n\t\tMETHODS.put(\"java/lang/Long.parseLong(Ljava/lang/String;I)J\", (Func_2<StringValue, IntValue>) (a, b) -> j(Long.parseLong(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Long.parseLong(Ljava/lang/String;)J\", (Func_1<StringValue>) (a) -> j(Long.parseLong(str(a))));\n\t\tMETHODS.put(\"java/lang/Long.parseUnsignedLong(Ljava/lang/CharSequence;III)J\", (Func_4<StringValue, IntValue, IntValue, IntValue>) (a, b, c, d) -> j(Long.parseUnsignedLong(str(a), i(b), i(c), i(d))));\n\t\tMETHODS.put(\"java/lang/Long.parseUnsignedLong(Ljava/lang/String;I)J\", (Func_2<StringValue, IntValue>) (a, b) -> j(Long.parseUnsignedLong(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Long.parseUnsignedLong(Ljava/lang/String;)J\", (Func_1<StringValue>) (a) -> j(Long.parseUnsignedLong(str(a))));\n\t}\n\n\tprivate static void floats() {\n\t\tMETHODS.put(\"java/lang/Float.toString(F)Ljava/lang/String;\", (Func_1<FloatValue>) (a) -> str(Float.toString(f(a))));\n\t\tMETHODS.put(\"java/lang/Float.hashCode(F)I\", (Func_1<FloatValue>) (a) -> i(Float.hashCode(f(a))));\n\t\tMETHODS.put(\"java/lang/Float.min(FF)F\", (Func_2<FloatValue, FloatValue>) (a, b) -> f(Float.min(f(a), f(b))));\n\t\tMETHODS.put(\"java/lang/Float.max(FF)F\", (Func_2<FloatValue, FloatValue>) (a, b) -> f(Float.max(f(a), f(b))));\n\t\tMETHODS.put(\"java/lang/Float.isInfinite(F)Z\", (Func_1<FloatValue>) (a) -> z(Float.isInfinite(f(a))));\n\t\tMETHODS.put(\"java/lang/Float.isFinite(F)Z\", (Func_1<FloatValue>) (a) -> z(Float.isFinite(f(a))));\n\t\tMETHODS.put(\"java/lang/Float.floatToRawIntBits(F)I\", (Func_1<FloatValue>) (a) -> i(Float.floatToRawIntBits(f(a))));\n\t\tMETHODS.put(\"java/lang/Float.floatToIntBits(F)I\", (Func_1<FloatValue>) (a) -> i(Float.floatToIntBits(f(a))));\n\t\tMETHODS.put(\"java/lang/Float.intBitsToFloat(I)F\", (Func_1<IntValue>) (a) -> f(Float.intBitsToFloat(i(a))));\n\t\tMETHODS.put(\"java/lang/Float.float16ToFloat(S)F\", (Func_1<IntValue>) (a) -> f(Float.float16ToFloat(s(a))));\n\t\tMETHODS.put(\"java/lang/Float.floatToFloat16(F)S\", (Func_1<FloatValue>) (a) -> s(Float.floatToFloat16(f(a))));\n\t\tMETHODS.put(\"java/lang/Float.compare(FF)I\", (Func_2<FloatValue, FloatValue>) (a, b) -> i(Float.compare(f(a), f(b))));\n\t\tMETHODS.put(\"java/lang/Float.toHexString(F)Ljava/lang/String;\", (Func_1<FloatValue>) (a) -> str(Float.toHexString(f(a))));\n\t\tMETHODS.put(\"java/lang/Float.isNaN(F)Z\", (Func_1<FloatValue>) (a) -> z(Float.isNaN(f(a))));\n\t\tMETHODS.put(\"java/lang/Float.sum(FF)F\", (Func_2<FloatValue, FloatValue>) (a, b) -> f(Float.sum(f(a), f(b))));\n\t\tMETHODS.put(\"java/lang/Float.parseFloat(Ljava/lang/String;)F\", (Func_1<StringValue>) (a) -> f(Float.parseFloat(str(a))));\n\t}\n\n\tprivate static void doubles() {\n\t\tMETHODS.put(\"java/lang/Double.toString(D)Ljava/lang/String;\", (Func_1<DoubleValue>) (a) -> str(Double.toString(d(a))));\n\t\tMETHODS.put(\"java/lang/Double.hashCode(D)I\", (Func_1<DoubleValue>) (a) -> i(Double.hashCode(d(a))));\n\t\tMETHODS.put(\"java/lang/Double.min(DD)D\", (Func_2<DoubleValue, DoubleValue>) (a, b) -> d(Double.min(d(a), d(b))));\n\t\tMETHODS.put(\"java/lang/Double.max(DD)D\", (Func_2<DoubleValue, DoubleValue>) (a, b) -> d(Double.max(d(a), d(b))));\n\t\tMETHODS.put(\"java/lang/Double.isInfinite(D)Z\", (Func_1<DoubleValue>) (a) -> z(Double.isInfinite(d(a))));\n\t\tMETHODS.put(\"java/lang/Double.isFinite(D)Z\", (Func_1<DoubleValue>) (a) -> z(Double.isFinite(d(a))));\n\t\tMETHODS.put(\"java/lang/Double.doubleToRawLongBits(D)J\", (Func_1<DoubleValue>) (a) -> j(Double.doubleToRawLongBits(d(a))));\n\t\tMETHODS.put(\"java/lang/Double.doubleToLongBits(D)J\", (Func_1<DoubleValue>) (a) -> j(Double.doubleToLongBits(d(a))));\n\t\tMETHODS.put(\"java/lang/Double.longBitsToDouble(J)D\", (Func_1<LongValue>) (a) -> d(Double.longBitsToDouble(j(a))));\n\t\tMETHODS.put(\"java/lang/Double.compare(DD)I\", (Func_2<DoubleValue, DoubleValue>) (a, b) -> i(Double.compare(d(a), d(b))));\n\t\tMETHODS.put(\"java/lang/Double.toHexString(D)Ljava/lang/String;\", (Func_1<DoubleValue>) (a) -> str(Double.toHexString(d(a))));\n\t\tMETHODS.put(\"java/lang/Double.isNaN(D)Z\", (Func_1<DoubleValue>) (a) -> z(Double.isNaN(d(a))));\n\t\tMETHODS.put(\"java/lang/Double.sum(DD)D\", (Func_2<DoubleValue, DoubleValue>) (a, b) -> d(Double.sum(d(a), d(b))));\n\t\tMETHODS.put(\"java/lang/Double.parseDouble(Ljava/lang/String;)D\", (Func_1<StringValue>) (a) -> d(Double.parseDouble(str(a))));\n\t}\n\n\tprivate static void math() {\n\t\tMETHODS.put(\"java/lang/Math.abs(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.abs(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.abs(F)F\", (Func_1<FloatValue>) (a) -> f(Math.abs(f(a))));\n\t\tMETHODS.put(\"java/lang/Math.abs(J)J\", (Func_1<LongValue>) (a) -> j(Math.abs(j(a))));\n\t\tMETHODS.put(\"java/lang/Math.abs(I)I\", (Func_1<IntValue>) (a) -> i(Math.abs(i(a))));\n\t\tMETHODS.put(\"java/lang/Math.sin(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.sin(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.cos(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.cos(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.tan(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.tan(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.atan2(DD)D\", (Func_2<DoubleValue, DoubleValue>) (a, b) -> d(Math.atan2(d(a), d(b))));\n\t\tMETHODS.put(\"java/lang/Math.sqrt(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.sqrt(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.log(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.log(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.log10(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.log10(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.pow(DD)D\", (Func_2<DoubleValue, DoubleValue>) (a, b) -> d(Math.pow(d(a), d(b))));\n\t\tMETHODS.put(\"java/lang/Math.exp(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.exp(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.min(DD)D\", (Func_2<DoubleValue, DoubleValue>) (a, b) -> d(Math.min(d(a), d(b))));\n\t\tMETHODS.put(\"java/lang/Math.min(FF)F\", (Func_2<FloatValue, FloatValue>) (a, b) -> f(Math.min(f(a), f(b))));\n\t\tMETHODS.put(\"java/lang/Math.min(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.min(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.min(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Math.min(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.max(DD)D\", (Func_2<DoubleValue, DoubleValue>) (a, b) -> d(Math.max(d(a), d(b))));\n\t\tMETHODS.put(\"java/lang/Math.max(FF)F\", (Func_2<FloatValue, FloatValue>) (a, b) -> f(Math.max(f(a), f(b))));\n\t\tMETHODS.put(\"java/lang/Math.max(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.max(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.max(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Math.max(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.floor(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.floor(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.ceil(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.ceil(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.rint(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.rint(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.round(F)I\", (Func_1<FloatValue>) (a) -> i(Math.round(f(a))));\n\t\tMETHODS.put(\"java/lang/Math.round(D)J\", (Func_1<DoubleValue>) (a) -> j(Math.round(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.addExact(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.addExact(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.addExact(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Math.addExact(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.decrementExact(I)I\", (Func_1<IntValue>) (a) -> i(Math.decrementExact(i(a))));\n\t\tMETHODS.put(\"java/lang/Math.decrementExact(J)J\", (Func_1<LongValue>) (a) -> j(Math.decrementExact(j(a))));\n\t\tMETHODS.put(\"java/lang/Math.incrementExact(J)J\", (Func_1<LongValue>) (a) -> j(Math.incrementExact(j(a))));\n\t\tMETHODS.put(\"java/lang/Math.incrementExact(I)I\", (Func_1<IntValue>) (a) -> i(Math.incrementExact(i(a))));\n\t\tMETHODS.put(\"java/lang/Math.multiplyExact(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.multiplyExact(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.multiplyExact(JI)J\", (Func_2<LongValue, IntValue>) (a, b) -> j(Math.multiplyExact(j(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.multiplyExact(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Math.multiplyExact(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.multiplyHigh(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.multiplyHigh(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.unsignedMultiplyHigh(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.unsignedMultiplyHigh(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.negateExact(I)I\", (Func_1<IntValue>) (a) -> i(Math.negateExact(i(a))));\n\t\tMETHODS.put(\"java/lang/Math.negateExact(J)J\", (Func_1<LongValue>) (a) -> j(Math.negateExact(j(a))));\n\t\tMETHODS.put(\"java/lang/Math.subtractExact(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.subtractExact(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.subtractExact(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Math.subtractExact(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.fma(DDD)D\", (Func_3<DoubleValue, DoubleValue, DoubleValue>) (a, b, c) -> d(Math.fma(d(a), d(b), d(c))));\n\t\tMETHODS.put(\"java/lang/Math.fma(FFF)F\", (Func_3<FloatValue, FloatValue, FloatValue>) (a, b, c) -> f(Math.fma(f(a), f(b), f(c))));\n\t\tMETHODS.put(\"java/lang/Math.copySign(DD)D\", (Func_2<DoubleValue, DoubleValue>) (a, b) -> d(Math.copySign(d(a), d(b))));\n\t\tMETHODS.put(\"java/lang/Math.copySign(FF)F\", (Func_2<FloatValue, FloatValue>) (a, b) -> f(Math.copySign(f(a), f(b))));\n\t\tMETHODS.put(\"java/lang/Math.signum(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.signum(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.signum(F)F\", (Func_1<FloatValue>) (a) -> f(Math.signum(f(a))));\n\t\tMETHODS.put(\"java/lang/Math.clamp(FFF)F\", (Func_3<FloatValue, FloatValue, FloatValue>) (a, b, c) -> f(Math.clamp(f(a), f(b), f(c))));\n\t\tMETHODS.put(\"java/lang/Math.clamp(JJJ)J\", (Func_3<LongValue, LongValue, LongValue>) (a, b, c) -> j(Math.clamp(j(a), j(b), j(c))));\n\t\tMETHODS.put(\"java/lang/Math.clamp(DDD)D\", (Func_3<DoubleValue, DoubleValue, DoubleValue>) (a, b, c) -> d(Math.clamp(d(a), d(b), d(c))));\n\t\tMETHODS.put(\"java/lang/Math.clamp(JII)I\", (Func_3<LongValue, IntValue, IntValue>) (a, b, c) -> i(Math.clamp(j(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/lang/Math.scalb(FI)F\", (Func_2<FloatValue, IntValue>) (a, b) -> f(Math.scalb(f(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.scalb(DI)D\", (Func_2<DoubleValue, IntValue>) (a, b) -> d(Math.scalb(d(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.getExponent(D)I\", (Func_1<DoubleValue>) (a) -> i(Math.getExponent(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.getExponent(F)I\", (Func_1<FloatValue>) (a) -> i(Math.getExponent(f(a))));\n\t\tMETHODS.put(\"java/lang/Math.floorMod(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Math.floorMod(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.floorMod(JI)I\", (Func_2<LongValue, IntValue>) (a, b) -> i(Math.floorMod(j(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.floorMod(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.floorMod(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.asin(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.asin(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.acos(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.acos(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.atan(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.atan(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.cbrt(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.cbrt(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.IEEEremainder(DD)D\", (Func_2<DoubleValue, DoubleValue>) (a, b) -> d(Math.IEEEremainder(d(a), d(b))));\n\t\tMETHODS.put(\"java/lang/Math.floorDiv(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.floorDiv(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.floorDiv(JI)J\", (Func_2<LongValue, IntValue>) (a, b) -> j(Math.floorDiv(j(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.floorDiv(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Math.floorDiv(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.ceilDiv(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.ceilDiv(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.ceilDiv(JI)J\", (Func_2<LongValue, IntValue>) (a, b) -> j(Math.ceilDiv(j(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.ceilDiv(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Math.ceilDiv(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.ceilMod(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.ceilMod(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.ceilMod(JI)I\", (Func_2<LongValue, IntValue>) (a, b) -> i(Math.ceilMod(j(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.ceilMod(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Math.ceilMod(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.sinh(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.sinh(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.cosh(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.cosh(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.tanh(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.tanh(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.hypot(DD)D\", (Func_2<DoubleValue, DoubleValue>) (a, b) -> d(Math.hypot(d(a), d(b))));\n\t\tMETHODS.put(\"java/lang/Math.expm1(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.expm1(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.log1p(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.log1p(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.toRadians(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.toRadians(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.toDegrees(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.toDegrees(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.random()D\", (Func_0) () -> d(Math.random()));\n\t\tMETHODS.put(\"java/lang/Math.divideExact(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Math.divideExact(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.divideExact(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.divideExact(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.floorDivExact(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.floorDivExact(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.floorDivExact(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Math.floorDivExact(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.ceilDivExact(JJ)J\", (Func_2<LongValue, LongValue>) (a, b) -> j(Math.ceilDivExact(j(a), j(b))));\n\t\tMETHODS.put(\"java/lang/Math.ceilDivExact(II)I\", (Func_2<IntValue, IntValue>) (a, b) -> i(Math.ceilDivExact(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.toIntExact(J)I\", (Func_1<LongValue>) (a) -> i(Math.toIntExact(j(a))));\n\t\tMETHODS.put(\"java/lang/Math.multiplyFull(II)J\", (Func_2<IntValue, IntValue>) (a, b) -> j(Math.multiplyFull(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/Math.absExact(J)J\", (Func_1<LongValue>) (a) -> j(Math.absExact(j(a))));\n\t\tMETHODS.put(\"java/lang/Math.absExact(I)I\", (Func_1<IntValue>) (a) -> i(Math.absExact(i(a))));\n\t\tMETHODS.put(\"java/lang/Math.ulp(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.ulp(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.ulp(F)F\", (Func_1<FloatValue>) (a) -> f(Math.ulp(f(a))));\n\t\tMETHODS.put(\"java/lang/Math.nextAfter(FD)F\", (Func_2<FloatValue, DoubleValue>) (a, b) -> f(Math.nextAfter(f(a), d(b))));\n\t\tMETHODS.put(\"java/lang/Math.nextAfter(DD)D\", (Func_2<DoubleValue, DoubleValue>) (a, b) -> d(Math.nextAfter(d(a), d(b))));\n\t\tMETHODS.put(\"java/lang/Math.nextUp(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.nextUp(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.nextUp(F)F\", (Func_1<FloatValue>) (a) -> f(Math.nextUp(f(a))));\n\t\tMETHODS.put(\"java/lang/Math.nextDown(D)D\", (Func_1<DoubleValue>) (a) -> d(Math.nextDown(d(a))));\n\t\tMETHODS.put(\"java/lang/Math.nextDown(F)F\", (Func_1<FloatValue>) (a) -> f(Math.nextDown(f(a))));\n\n\t\t// Duplicate for StrictMath\n\t\tnew ArrayList<>(METHODS.entrySet()).iterator().forEachRemaining((e) -> {\n\t\t\tString key = e.getKey();\n\t\t\tif (key.startsWith(\"java/lang/Math.\")) {\n\t\t\t\tMETHODS.put(key.replace(\"/Math.\", \"/StrictMath.\"), e.getValue());\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/lookup/BasicInvokeVirtualLookup.java",
    "content": "package software.coley.recaf.util.analysis.lookup;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.gen.LookupGenerator;\nimport software.coley.recaf.util.analysis.value.ArrayValue;\nimport software.coley.recaf.util.analysis.value.IllegalValueException;\nimport software.coley.recaf.util.analysis.value.IntValue;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.StringValue;\n\nimport java.io.UnsupportedEncodingException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * Basic implementation of {@link InvokeVirtualLookup} for common static fields.\n * <br>\n * Mostly auto-generated, see {@link LookupGenerator#main(String[])}\n *\n * @author Matt Coley\n */\npublic class BasicInvokeVirtualLookup extends BasicLookupUtils implements InvokeVirtualLookup {\n\tprivate static final Map<String, Func> METHODS = new HashMap<>();\n\tprivate static final DebuggingLogger logger = Logging.get(BasicInvokeVirtualLookup.class);\n\n\t@Nonnull\n\t@Override\n\tpublic ReValue get(@Nonnull MethodInsnNode method, @Nonnull ReValue context, @Nonnull List<? extends ReValue> values) {\n\t\tString key = getKey(method);\n\t\tFunc func = METHODS.get(key);\n\t\tReValue value = null;\n\t\tif (func != null)\n\t\t\ttry {\n\t\t\t\tList<ReValue> params = new ArrayList<>(values.size() + 1);\n\t\t\t\tparams.add(context);\n\t\t\t\tparams.addAll(values);\n\t\t\t\tvalue = func.apply(params);\n\t\t\t} catch (Throwable t) {\n\t\t\t\t// Some methods may inherently throw, like 'Math.floorDiv(0, 0)' so these error\n\t\t\t\t// log calls are only active while debugging.\n\t\t\t\tlogger.debugging(l -> l.error(\"Computation threw an exception for: \" + key, t));\n\t\t\t}\n\t\tif (value == null) {\n\t\t\ttry {\n\t\t\t\tvalue = ReValue.ofType(Type.getReturnType(method.desc), Nullness.UNKNOWN);\n\t\t\t} catch (IllegalValueException ex) {\n\t\t\t\tlogger.error(\"Failed default value computation for: \" + key, ex);\n\t\t\t}\n\t\t}\n\t\treturn Objects.requireNonNull(value);\n\t}\n\n\t@Override\n\tpublic boolean hasLookup(@Nonnull MethodInsnNode method) {\n\t\treturn METHODS.containsKey(getKey(method));\n\t}\n\n\t@Nonnull\n\tprivate static String getKey(@Nonnull MethodInsnNode method) {\n\t\treturn method.owner + \".\" + method.name + method.desc;\n\t}\n\n\tstatic {\n\t\tstrings();\n\n\t\t// primitives\n\t\tbooleans();\n\t\tbytes();\n\t\tchars();\n\t\tshorts();\n\t\tints();\n\t\tlongs();\n\t\tfloats();\n\t\tdoubles();\n\t}\n\n\tprivate static void booleans() {\n\t\tMETHODS.put(\"java/lang/Boolean.equals(Ljava/lang/Object;)Z\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> z(BasicLookupUtils.<Boolean>obj(ctx).equals(obj(a))));\n\t\tMETHODS.put(\"java/lang/Boolean.toString()Ljava/lang/String;\", (Func_1<ObjectValue>) (ctx) -> str(BasicLookupUtils.<Boolean>obj(ctx).toString()));\n\t\tMETHODS.put(\"java/lang/Boolean.hashCode()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Boolean>obj(ctx).hashCode()));\n\t\tMETHODS.put(\"java/lang/Boolean.compareTo(Ljava/lang/Boolean;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Boolean>obj(ctx).compareTo(BasicLookupUtils.<Boolean>obj(a))));\n\t\tMETHODS.put(\"java/lang/Boolean.compareTo(Ljava/lang/Object;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Boolean>obj(ctx).compareTo(obj(a))));\n\t\tMETHODS.put(\"java/lang/Boolean.booleanValue()Z\", (Func_1<ObjectValue>) (ctx) -> z(BasicLookupUtils.<Boolean>obj(ctx).booleanValue()));\n\t}\n\n\tprivate static void bytes() {\n\t\tMETHODS.put(\"java/lang/Byte.equals(Ljava/lang/Object;)Z\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> z(BasicLookupUtils.<Byte>obj(ctx).equals(obj(a))));\n\t\tMETHODS.put(\"java/lang/Byte.toString()Ljava/lang/String;\", (Func_1<ObjectValue>) (ctx) -> str(BasicLookupUtils.<Byte>obj(ctx).toString()));\n\t\tMETHODS.put(\"java/lang/Byte.hashCode()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Byte>obj(ctx).hashCode()));\n\t\tMETHODS.put(\"java/lang/Byte.compareTo(Ljava/lang/Byte;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Byte>obj(ctx).compareTo(BasicLookupUtils.<Byte>obj(a))));\n\t\tMETHODS.put(\"java/lang/Byte.compareTo(Ljava/lang/Object;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Byte>obj(ctx).compareTo(obj(a))));\n\t\tMETHODS.put(\"java/lang/Byte.byteValue()B\", (Func_1<ObjectValue>) (ctx) -> b(BasicLookupUtils.<Byte>obj(ctx).byteValue()));\n\t\tMETHODS.put(\"java/lang/Byte.shortValue()S\", (Func_1<ObjectValue>) (ctx) -> s(BasicLookupUtils.<Byte>obj(ctx).shortValue()));\n\t\tMETHODS.put(\"java/lang/Byte.intValue()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Byte>obj(ctx).intValue()));\n\t\tMETHODS.put(\"java/lang/Byte.longValue()J\", (Func_1<ObjectValue>) (ctx) -> j(BasicLookupUtils.<Byte>obj(ctx).longValue()));\n\t\tMETHODS.put(\"java/lang/Byte.floatValue()F\", (Func_1<ObjectValue>) (ctx) -> f(BasicLookupUtils.<Byte>obj(ctx).floatValue()));\n\t\tMETHODS.put(\"java/lang/Byte.doubleValue()D\", (Func_1<ObjectValue>) (ctx) -> d(BasicLookupUtils.<Byte>obj(ctx).doubleValue()));\n\t}\n\n\tprivate static void chars() {\n\t\tMETHODS.put(\"java/lang/Character.equals(Ljava/lang/Object;)Z\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> z(BasicLookupUtils.<Character>obj(ctx).equals(obj(a))));\n\t\tMETHODS.put(\"java/lang/Character.toString()Ljava/lang/String;\", (Func_1<ObjectValue>) (ctx) -> str(BasicLookupUtils.<Character>obj(ctx).toString()));\n\t\tMETHODS.put(\"java/lang/Character.hashCode()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Character>obj(ctx).hashCode()));\n\t\tMETHODS.put(\"java/lang/Character.compareTo(Ljava/lang/Character;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Character>obj(ctx).compareTo(BasicLookupUtils.<Character>obj(a))));\n\t\tMETHODS.put(\"java/lang/Character.compareTo(Ljava/lang/Object;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Character>obj(ctx).compareTo(obj(a))));\n\t\tMETHODS.put(\"java/lang/Character.charValue()C\", (Func_1<ObjectValue>) (ctx) -> c(BasicLookupUtils.<Character>obj(ctx).charValue()));\n\t}\n\n\tprivate static void shorts() {\n\t\tMETHODS.put(\"java/lang/Short.equals(Ljava/lang/Object;)Z\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> z(BasicLookupUtils.<Short>obj(ctx).equals(obj(a))));\n\t\tMETHODS.put(\"java/lang/Short.toString()Ljava/lang/String;\", (Func_1<ObjectValue>) (ctx) -> str(BasicLookupUtils.<Short>obj(ctx).toString()));\n\t\tMETHODS.put(\"java/lang/Short.hashCode()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Short>obj(ctx).hashCode()));\n\t\tMETHODS.put(\"java/lang/Short.compareTo(Ljava/lang/Short;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Short>obj(ctx).compareTo(BasicLookupUtils.<Short>obj(a))));\n\t\tMETHODS.put(\"java/lang/Short.compareTo(Ljava/lang/Object;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Short>obj(ctx).compareTo(obj(a))));\n\t\tMETHODS.put(\"java/lang/Short.byteValue()B\", (Func_1<ObjectValue>) (ctx) -> b(BasicLookupUtils.<Short>obj(ctx).byteValue()));\n\t\tMETHODS.put(\"java/lang/Short.shortValue()S\", (Func_1<ObjectValue>) (ctx) -> s(BasicLookupUtils.<Short>obj(ctx).shortValue()));\n\t\tMETHODS.put(\"java/lang/Short.intValue()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Short>obj(ctx).intValue()));\n\t\tMETHODS.put(\"java/lang/Short.longValue()J\", (Func_1<ObjectValue>) (ctx) -> j(BasicLookupUtils.<Short>obj(ctx).longValue()));\n\t\tMETHODS.put(\"java/lang/Short.floatValue()F\", (Func_1<ObjectValue>) (ctx) -> f(BasicLookupUtils.<Short>obj(ctx).floatValue()));\n\t\tMETHODS.put(\"java/lang/Short.doubleValue()D\", (Func_1<ObjectValue>) (ctx) -> d(BasicLookupUtils.<Short>obj(ctx).doubleValue()));\n\t}\n\n\tprivate static void ints() {\n\t\tMETHODS.put(\"java/lang/Integer.equals(Ljava/lang/Object;)Z\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> z(BasicLookupUtils.<Integer>obj(ctx).equals(obj(a))));\n\t\tMETHODS.put(\"java/lang/Integer.toString()Ljava/lang/String;\", (Func_1<ObjectValue>) (ctx) -> str(BasicLookupUtils.<Integer>obj(ctx).toString()));\n\t\tMETHODS.put(\"java/lang/Integer.hashCode()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Integer>obj(ctx).hashCode()));\n\t\tMETHODS.put(\"java/lang/Integer.compareTo(Ljava/lang/Object;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Integer>obj(ctx).compareTo(BasicLookupUtils.<Integer>obj(a))));\n\t\tMETHODS.put(\"java/lang/Integer.compareTo(Ljava/lang/Integer;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Integer>obj(ctx).compareTo(BasicLookupUtils.<Integer>obj(a))));\n\t\tMETHODS.put(\"java/lang/Integer.byteValue()B\", (Func_1<ObjectValue>) (ctx) -> b(BasicLookupUtils.<Integer>obj(ctx).byteValue()));\n\t\tMETHODS.put(\"java/lang/Integer.shortValue()S\", (Func_1<ObjectValue>) (ctx) -> s(BasicLookupUtils.<Integer>obj(ctx).shortValue()));\n\t\tMETHODS.put(\"java/lang/Integer.intValue()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Integer>obj(ctx).intValue()));\n\t\tMETHODS.put(\"java/lang/Integer.longValue()J\", (Func_1<ObjectValue>) (ctx) -> j(BasicLookupUtils.<Integer>obj(ctx).longValue()));\n\t\tMETHODS.put(\"java/lang/Integer.floatValue()F\", (Func_1<ObjectValue>) (ctx) -> f(BasicLookupUtils.<Integer>obj(ctx).floatValue()));\n\t\tMETHODS.put(\"java/lang/Integer.doubleValue()D\", (Func_1<ObjectValue>) (ctx) -> d(BasicLookupUtils.<Integer>obj(ctx).doubleValue()));\n\t}\n\n\tprivate static void longs() {\n\t\tMETHODS.put(\"java/lang/Long.equals(Ljava/lang/Object;)Z\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> z(BasicLookupUtils.<Long>obj(ctx).equals(obj(a))));\n\t\tMETHODS.put(\"java/lang/Long.toString()Ljava/lang/String;\", (Func_1<ObjectValue>) (ctx) -> str(BasicLookupUtils.<Long>obj(ctx).toString()));\n\t\tMETHODS.put(\"java/lang/Long.hashCode()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Long>obj(ctx).hashCode()));\n\t\tMETHODS.put(\"java/lang/Long.compareTo(Ljava/lang/Object;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Long>obj(ctx).compareTo(obj(a))));\n\t\tMETHODS.put(\"java/lang/Long.compareTo(Ljava/lang/Long;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Long>obj(ctx).compareTo(BasicLookupUtils.<Long>obj(a))));\n\t\tMETHODS.put(\"java/lang/Long.byteValue()B\", (Func_1<ObjectValue>) (ctx) -> b(BasicLookupUtils.<Long>obj(ctx).byteValue()));\n\t\tMETHODS.put(\"java/lang/Long.shortValue()S\", (Func_1<ObjectValue>) (ctx) -> s(BasicLookupUtils.<Long>obj(ctx).shortValue()));\n\t\tMETHODS.put(\"java/lang/Long.intValue()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Long>obj(ctx).intValue()));\n\t\tMETHODS.put(\"java/lang/Long.longValue()J\", (Func_1<ObjectValue>) (ctx) -> j(BasicLookupUtils.<Long>obj(ctx).longValue()));\n\t\tMETHODS.put(\"java/lang/Long.floatValue()F\", (Func_1<ObjectValue>) (ctx) -> f(BasicLookupUtils.<Long>obj(ctx).floatValue()));\n\t\tMETHODS.put(\"java/lang/Long.doubleValue()D\", (Func_1<ObjectValue>) (ctx) -> d(BasicLookupUtils.<Long>obj(ctx).doubleValue()));\n\t}\n\n\tprivate static void floats() {\n\t\tMETHODS.put(\"java/lang/Float.equals(Ljava/lang/Object;)Z\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> z(BasicLookupUtils.<Float>obj(ctx).equals(obj(a))));\n\t\tMETHODS.put(\"java/lang/Float.toString()Ljava/lang/String;\", (Func_1<ObjectValue>) (ctx) -> str(BasicLookupUtils.<Float>obj(ctx).toString()));\n\t\tMETHODS.put(\"java/lang/Float.hashCode()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Float>obj(ctx).hashCode()));\n\t\tMETHODS.put(\"java/lang/Float.isInfinite()Z\", (Func_1<ObjectValue>) (ctx) -> z(BasicLookupUtils.<Float>obj(ctx).isInfinite()));\n\t\tMETHODS.put(\"java/lang/Float.compareTo(Ljava/lang/Object;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Float>obj(ctx).compareTo(obj(a))));\n\t\tMETHODS.put(\"java/lang/Float.compareTo(Ljava/lang/Float;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Float>obj(ctx).compareTo(BasicLookupUtils.<Float>obj(a))));\n\t\tMETHODS.put(\"java/lang/Float.byteValue()B\", (Func_1<ObjectValue>) (ctx) -> b(BasicLookupUtils.<Float>obj(ctx).byteValue()));\n\t\tMETHODS.put(\"java/lang/Float.shortValue()S\", (Func_1<ObjectValue>) (ctx) -> s(BasicLookupUtils.<Float>obj(ctx).shortValue()));\n\t\tMETHODS.put(\"java/lang/Float.intValue()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Float>obj(ctx).intValue()));\n\t\tMETHODS.put(\"java/lang/Float.longValue()J\", (Func_1<ObjectValue>) (ctx) -> j(BasicLookupUtils.<Float>obj(ctx).longValue()));\n\t\tMETHODS.put(\"java/lang/Float.floatValue()F\", (Func_1<ObjectValue>) (ctx) -> f(BasicLookupUtils.<Float>obj(ctx).floatValue()));\n\t\tMETHODS.put(\"java/lang/Float.doubleValue()D\", (Func_1<ObjectValue>) (ctx) -> d(BasicLookupUtils.<Float>obj(ctx).doubleValue()));\n\t\tMETHODS.put(\"java/lang/Float.isNaN()Z\", (Func_1<ObjectValue>) (ctx) -> z(BasicLookupUtils.<Float>obj(ctx).isNaN()));\n\t}\n\n\tprivate static void doubles() {\n\t\tMETHODS.put(\"java/lang/Double.equals(Ljava/lang/Object;)Z\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> z(BasicLookupUtils.<Double>obj(ctx).equals(obj(a))));\n\t\tMETHODS.put(\"java/lang/Double.toString()Ljava/lang/String;\", (Func_1<ObjectValue>) (ctx) -> str(BasicLookupUtils.<Double>obj(ctx).toString()));\n\t\tMETHODS.put(\"java/lang/Double.hashCode()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Double>obj(ctx).hashCode()));\n\t\tMETHODS.put(\"java/lang/Double.isInfinite()Z\", (Func_1<ObjectValue>) (ctx) -> z(BasicLookupUtils.<Double>obj(ctx).isInfinite()));\n\t\tMETHODS.put(\"java/lang/Double.compareTo(Ljava/lang/Double;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Double>obj(ctx).compareTo(BasicLookupUtils.<Double>obj(a))));\n\t\tMETHODS.put(\"java/lang/Double.compareTo(Ljava/lang/Object;)I\", (Func_2<ObjectValue, ObjectValue>) (ctx, a) -> i(BasicLookupUtils.<Double>obj(ctx).compareTo(BasicLookupUtils.<Double>obj(a))));\n\t\tMETHODS.put(\"java/lang/Double.byteValue()B\", (Func_1<ObjectValue>) (ctx) -> b(BasicLookupUtils.<Double>obj(ctx).byteValue()));\n\t\tMETHODS.put(\"java/lang/Double.shortValue()S\", (Func_1<ObjectValue>) (ctx) -> s(BasicLookupUtils.<Double>obj(ctx).shortValue()));\n\t\tMETHODS.put(\"java/lang/Double.intValue()I\", (Func_1<ObjectValue>) (ctx) -> i(BasicLookupUtils.<Double>obj(ctx).intValue()));\n\t\tMETHODS.put(\"java/lang/Double.longValue()J\", (Func_1<ObjectValue>) (ctx) -> j(BasicLookupUtils.<Double>obj(ctx).longValue()));\n\t\tMETHODS.put(\"java/lang/Double.floatValue()F\", (Func_1<ObjectValue>) (ctx) -> f(BasicLookupUtils.<Double>obj(ctx).floatValue()));\n\t\tMETHODS.put(\"java/lang/Double.doubleValue()D\", (Func_1<ObjectValue>) (ctx) -> d(BasicLookupUtils.<Double>obj(ctx).doubleValue()));\n\t\tMETHODS.put(\"java/lang/Double.isNaN()Z\", (Func_1<ObjectValue>) (ctx) -> z(BasicLookupUtils.<Double>obj(ctx).isNaN()));\n\t}\n\n\tprivate static void strings() {\n\t\tMETHODS.put(\"java/lang/String.equals(Ljava/lang/Object;)Z\", (Func_2<StringValue, ObjectValue>) (ctx, a) -> z(str(ctx).equals(obj(a))));\n\t\tMETHODS.put(\"java/lang/String.length()I\", (Func_1<StringValue>) (ctx) -> i(str(ctx).length()));\n\t\tMETHODS.put(\"java/lang/String.toString()Ljava/lang/String;\", (Func_1<StringValue>) ctx -> ctx);\n\t\tMETHODS.put(\"java/lang/String.hashCode()I\", (Func_1<StringValue>) (ctx) -> i(str(ctx).hashCode()));\n\t\tMETHODS.put(\"java/lang/String.compareTo(Ljava/lang/Object;)I\", (Func_2<StringValue, ObjectValue>) (ctx, a) -> {\n\t\t\tif (a instanceof StringValue as)\n\t\t\t\treturn i(str(ctx).compareTo(str(as)));\n\t\t\tthrow new IllegalArgumentException();\n\t\t});\n\t\tMETHODS.put(\"java/lang/String.compareTo(Ljava/lang/String;)I\", (Func_2<StringValue, StringValue>) (ctx, a) -> i(str(ctx).compareTo(str(a))));\n\t\tMETHODS.put(\"java/lang/String.indexOf(Ljava/lang/String;II)I\", (Func_4<StringValue, StringValue, IntValue, IntValue>) (ctx, a, b, c) -> i(str(ctx).indexOf(str(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/lang/String.indexOf(Ljava/lang/String;)I\", (Func_2<StringValue, StringValue>) (ctx, a) -> i(str(ctx).indexOf(str(a))));\n\t\tMETHODS.put(\"java/lang/String.indexOf(I)I\", (Func_2<StringValue, IntValue>) (ctx, a) -> i(str(ctx).indexOf(i(a))));\n\t\tMETHODS.put(\"java/lang/String.indexOf(II)I\", (Func_3<StringValue, IntValue, IntValue>) (ctx, a, b) -> i(str(ctx).indexOf(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/String.indexOf(III)I\", (Func_4<StringValue, IntValue, IntValue, IntValue>) (ctx, a, b, c) -> i(str(ctx).indexOf(i(a), i(b), i(c))));\n\t\tMETHODS.put(\"java/lang/String.indexOf(Ljava/lang/String;I)I\", (Func_3<StringValue, StringValue, IntValue>) (ctx, a, b) -> i(str(ctx).indexOf(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/String.charAt(I)C\", (Func_2<StringValue, IntValue>) (ctx, a) -> c(str(ctx).charAt(i(a))));\n\t\tMETHODS.put(\"java/lang/String.codePointAt(I)I\", (Func_2<StringValue, IntValue>) (ctx, a) -> i(str(ctx).codePointAt(i(a))));\n\t\tMETHODS.put(\"java/lang/String.codePointBefore(I)I\", (Func_2<StringValue, IntValue>) (ctx, a) -> i(str(ctx).codePointBefore(i(a))));\n\t\tMETHODS.put(\"java/lang/String.codePointCount(II)I\", (Func_3<StringValue, IntValue, IntValue>) (ctx, a, b) -> i(str(ctx).codePointCount(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/String.offsetByCodePoints(II)I\", (Func_3<StringValue, IntValue, IntValue>) (ctx, a, b) -> i(str(ctx).offsetByCodePoints(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/String.getBytes()[B\", (Func_1<StringValue>) (ctx) -> arrb(str(ctx).getBytes()));\n\t\tMETHODS.put(\"java/lang/String.getBytes(Ljava/lang/String;)[B\", (Func_2<StringValue, StringValue>) (ctx, a) -> {\n\t\t\ttry {\n\t\t\t\treturn arrb(str(ctx).getBytes(str(a)));\n\t\t\t} catch (UnsupportedEncodingException e) {\n\t\t\t\treturn ArrayValue.VAL_BYTES;\n\t\t\t}\n\t\t});\n\t\tMETHODS.put(\"java/lang/String.contentEquals(Ljava/lang/CharSequence;)Z\", (Func_2<StringValue, StringValue>) (ctx, a) -> z(str(ctx).contentEquals(str(a))));\n\t\tMETHODS.put(\"java/lang/String.regionMatches(ZILjava/lang/String;II)Z\", (Func_6<StringValue, IntValue, IntValue, StringValue, IntValue, IntValue>) (ctx, a, b, c, d, e) -> z(str(ctx).regionMatches(z(a), i(b), str(c), i(d), i(e))));\n\t\tMETHODS.put(\"java/lang/String.regionMatches(ILjava/lang/String;II)Z\", (Func_5<StringValue, IntValue, StringValue, IntValue, IntValue>) (ctx, a, b, c, d) -> z(str(ctx).regionMatches(i(a), str(b), i(c), i(d))));\n\t\tMETHODS.put(\"java/lang/String.startsWith(Ljava/lang/String;)Z\", (Func_2<StringValue, StringValue>) (ctx, a) -> z(str(ctx).startsWith(str(a))));\n\t\tMETHODS.put(\"java/lang/String.startsWith(Ljava/lang/String;I)Z\", (Func_3<StringValue, StringValue, IntValue>) (ctx, a, b) -> z(str(ctx).startsWith(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/String.lastIndexOf(Ljava/lang/String;)I\", (Func_2<StringValue, StringValue>) (ctx, a) -> i(str(ctx).lastIndexOf(str(a))));\n\t\tMETHODS.put(\"java/lang/String.lastIndexOf(II)I\", (Func_3<StringValue, IntValue, IntValue>) (ctx, a, b) -> i(str(ctx).lastIndexOf(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/String.lastIndexOf(Ljava/lang/String;I)I\", (Func_3<StringValue, StringValue, IntValue>) (ctx, a, b) -> i(str(ctx).lastIndexOf(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/String.lastIndexOf(I)I\", (Func_2<StringValue, IntValue>) (ctx, a) -> i(str(ctx).lastIndexOf(i(a))));\n\t\tMETHODS.put(\"java/lang/String.substring(I)Ljava/lang/String;\", (Func_2<StringValue, IntValue>) (ctx, a) -> str(str(ctx).substring(i(a))));\n\t\tMETHODS.put(\"java/lang/String.substring(II)Ljava/lang/String;\", (Func_3<StringValue, IntValue, IntValue>) (ctx, a, b) -> str(str(ctx).substring(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/String.isEmpty()Z\", (Func_1<StringValue>) (ctx) -> z(str(ctx).isEmpty()));\n\t\tMETHODS.put(\"java/lang/String.replace(CC)Ljava/lang/String;\", (Func_3<StringValue, IntValue, IntValue>) (ctx, a, b) -> str(str(ctx).replace(c(a), c(b))));\n\t\tMETHODS.put(\"java/lang/String.replace(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;\", (Func_3<StringValue, StringValue, StringValue>) (ctx, a, b) -> str(str(ctx).replace(str(a), str(b))));\n\t\tMETHODS.put(\"java/lang/String.matches(Ljava/lang/String;)Z\", (Func_2<StringValue, StringValue>) (ctx, a) -> z(str(ctx).matches(str(a))));\n\t\tMETHODS.put(\"java/lang/String.replaceFirst(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;\", (Func_3<StringValue, StringValue, StringValue>) (ctx, a, b) -> str(str(ctx).replaceFirst(str(a), str(b))));\n\t\tMETHODS.put(\"java/lang/String.replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;\", (Func_3<StringValue, StringValue, StringValue>) (ctx, a, b) -> str(str(ctx).replaceAll(str(a), str(b))));\n\t\tMETHODS.put(\"java/lang/String.split(Ljava/lang/String;)[Ljava/lang/String;\", (Func_2<StringValue, StringValue>) (ctx, a) -> arrstr(str(ctx).split(str(a))));\n\t\tMETHODS.put(\"java/lang/String.split(Ljava/lang/String;I)[Ljava/lang/String;\", (Func_3<StringValue, StringValue, IntValue>) (ctx, a, b) -> arrstr(str(ctx).split(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/String.splitWithDelimiters(Ljava/lang/String;I)[Ljava/lang/String;\", (Func_3<StringValue, StringValue, IntValue>) (ctx, a, b) -> arrstr(str(ctx).splitWithDelimiters(str(a), i(b))));\n\t\tMETHODS.put(\"java/lang/String.toLowerCase()Ljava/lang/String;\", (Func_1<StringValue>) (ctx) -> str(str(ctx).toLowerCase()));\n\t\tMETHODS.put(\"java/lang/String.toUpperCase()Ljava/lang/String;\", (Func_1<StringValue>) (ctx) -> str(str(ctx).toUpperCase()));\n\t\tMETHODS.put(\"java/lang/String.trim()Ljava/lang/String;\", (Func_1<StringValue>) (ctx) -> str(str(ctx).trim()));\n\t\tMETHODS.put(\"java/lang/String.strip()Ljava/lang/String;\", (Func_1<StringValue>) (ctx) -> str(str(ctx).strip()));\n\t\tMETHODS.put(\"java/lang/String.stripLeading()Ljava/lang/String;\", (Func_1<StringValue>) (ctx) -> str(str(ctx).stripLeading()));\n\t\tMETHODS.put(\"java/lang/String.stripTrailing()Ljava/lang/String;\", (Func_1<StringValue>) (ctx) -> str(str(ctx).stripTrailing()));\n\t\tMETHODS.put(\"java/lang/String.repeat(I)Ljava/lang/String;\", (Func_2<StringValue, IntValue>) (ctx, a) -> str(str(ctx).repeat(i(a))));\n\t\tMETHODS.put(\"java/lang/String.isBlank()Z\", (Func_1<StringValue>) (ctx) -> z(str(ctx).isBlank()));\n\t\tMETHODS.put(\"java/lang/String.toCharArray()[C\", (Func_1<StringValue>) (ctx) -> arrc(str(ctx).toCharArray()));\n\t\tMETHODS.put(\"java/lang/String.equalsIgnoreCase(Ljava/lang/String;)Z\", (Func_2<StringValue, StringValue>) (ctx, a) -> z(str(ctx).equalsIgnoreCase(str(a))));\n\t\tMETHODS.put(\"java/lang/String.compareToIgnoreCase(Ljava/lang/String;)I\", (Func_2<StringValue, StringValue>) (ctx, a) -> i(str(ctx).compareToIgnoreCase(str(a))));\n\t\tMETHODS.put(\"java/lang/String.endsWith(Ljava/lang/String;)Z\", (Func_2<StringValue, StringValue>) (ctx, a) -> z(str(ctx).endsWith(str(a))));\n\t\tMETHODS.put(\"java/lang/String.subSequence(II)Ljava/lang/CharSequence;\", (Func_3<StringValue, IntValue, IntValue>) (ctx, a, b) -> str(str(ctx).subSequence(i(a), i(b))));\n\t\tMETHODS.put(\"java/lang/String.concat(Ljava/lang/String;)Ljava/lang/String;\", (Func_2<StringValue, StringValue>) (ctx, a) -> str(str(ctx).concat(str(a))));\n\t\tMETHODS.put(\"java/lang/String.contains(Ljava/lang/CharSequence;)Z\", (Func_2<StringValue, StringValue>) (ctx, a) -> z(str(ctx).contains(str(a))));\n\t\tMETHODS.put(\"java/lang/String.indent(I)Ljava/lang/String;\", (Func_2<StringValue, IntValue>) (ctx, a) -> str(str(ctx).indent(i(a))));\n\t\tMETHODS.put(\"java/lang/String.stripIndent()Ljava/lang/String;\", (Func_1<StringValue>) (ctx) -> str(str(ctx).stripIndent()));\n\t\tMETHODS.put(\"java/lang/String.translateEscapes()Ljava/lang/String;\", (Func_1<StringValue>) (ctx) -> str(str(ctx).translateEscapes()));\n\t\tMETHODS.put(\"java/lang/String.formatted([Ljava/lang/Object;)Ljava/lang/String;\", (Func_2<StringValue, ArrayValue>) (ctx, a) -> str(str(ctx).formatted(arrobj(a))));\n\t\tMETHODS.put(\"java/lang/String.intern()Ljava/lang/String;\", (Func_1<StringValue>) (ctx) -> str(str(ctx).intern()));\n\t\tMETHODS.put(\"java/lang/CharSequence.length()I\", (Func_1<StringValue>) (ctx) -> i(str(ctx).length()));\n\t\tMETHODS.put(\"java/lang/CharSequence.toString()Ljava/lang/String;\", (Func_1<StringValue>) (ctx) -> str(str(ctx)));\n\t\tMETHODS.put(\"java/lang/CharSequence.charAt(I)C\", (Func_2<StringValue, IntValue>) (ctx, a) -> c(str(ctx).charAt(i(a))));\n\t\tMETHODS.put(\"java/lang/CharSequence.isEmpty()Z\", (Func_1<StringValue>) (ctx) -> z(str(ctx).isEmpty()));\n\t\tMETHODS.put(\"java/lang/CharSequence.subSequence(II)Ljava/lang/CharSequence;\", (Func_3<StringValue, IntValue, IntValue>) (ctx, a, b) -> str(str(ctx).subSequence(i(a), i(b))));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/lookup/BasicLookupUtils.java",
    "content": "package software.coley.recaf.util.analysis.lookup;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.value.ArrayValue;\nimport software.coley.recaf.util.analysis.value.DoubleValue;\nimport software.coley.recaf.util.analysis.value.FloatValue;\nimport software.coley.recaf.util.analysis.value.IntValue;\nimport software.coley.recaf.util.analysis.value.LongValue;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.StringValue;\nimport software.coley.recaf.util.analysis.value.impl.ArrayValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedByteValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedCharacterValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedDoubleValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedFloatValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedIntegerValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedLongValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.BoxedShortValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.ObjectValueBoxImpl;\n\nimport java.util.List;\nimport java.util.OptionalInt;\n\n/**\n * Common utilities for lookup implementations to convert between JVM values and interpreter values.\n *\n * @author Matt Coley\n */\npublic class BasicLookupUtils {\n\t// TODO: These short-named methods are useful for code generation and should stay as-is,\n\t//       But we should also expose the conversion logic in a more explicit way for plugin developers\n\t//       to use when writing transformers or their own lookup implementations.\n\n\t@SuppressWarnings(\"all\")\n\tprotected static byte b(@Nonnull IntValue value) {return (byte) value.value().getAsInt();}\n\n\t@SuppressWarnings(\"all\")\n\tprotected static boolean z(@Nonnull IntValue value) {return value.isNotEqualTo(0);}\n\n\t@SuppressWarnings(\"all\")\n\tprotected static short s(@Nonnull IntValue value) {return (short) value.value().getAsInt();}\n\n\t@SuppressWarnings(\"all\")\n\tprotected static char c(@Nonnull IntValue value) {return (char) value.value().getAsInt();}\n\n\t@SuppressWarnings(\"all\")\n\tprotected static int i(@Nonnull IntValue value) {return value.value().getAsInt();}\n\n\t@SuppressWarnings(\"all\")\n\tprotected static long j(@Nonnull LongValue value) {return value.value().getAsLong();}\n\n\t@SuppressWarnings(\"all\")\n\tprotected static float f(@Nonnull FloatValue value) {return (float) value.value().getAsDouble();}\n\n\t@SuppressWarnings(\"all\")\n\tprotected static double d(@Nonnull DoubleValue value) {return value.value().getAsDouble();}\n\n\t@SuppressWarnings(\"all\")\n\tprotected static String str(@Nonnull StringValue value) {return value.getText().get();}\n\n\tprotected static Object objl(@Nonnull ObjectValue value) {\n\t\t// Yield object type literally instead of auto-casting at call-site with T.\n\t\treturn obj(value);\n\t}\n\n\t@SuppressWarnings(\"all\")\n\tprotected static <T> T obj(@Nonnull ObjectValue value) {\n\t\t// Map null to null\n\t\tif (value.isNull())\n\t\t\treturn null;\n\n\t\t// Unwrap strings\n\t\tif (value instanceof StringValue string)\n\t\t\treturn (T) str(string);\n\n\t\t// Unwrap boxed values\n\t\tif (value instanceof ObjectValueBoxImpl<?> box)\n\t\t\treturn (T) box.unbox();\n\n\t\tthrow new IllegalArgumentException(\"Unsupported object unwrap: \" + value);\n\t}\n\n\t@Nonnull\n\tprotected static IntValue z(boolean value) {return IntValue.of(value ? 1 : 0);}\n\n\t@Nonnull\n\tprotected static IntValue b(byte value) {return IntValue.of(value);}\n\n\t@Nonnull\n\tprotected static IntValue c(char value) {return IntValue.of(value);}\n\n\t@Nonnull\n\tprotected static IntValue s(short value) {return IntValue.of(value);}\n\n\t@Nonnull\n\tprotected static IntValue i(int value) {return IntValue.of(value);}\n\n\t@Nonnull\n\tprotected static LongValue j(long value) {return LongValue.of(value);}\n\n\t@Nonnull\n\tprotected static FloatValue f(float value) {return FloatValue.of(value);}\n\n\t@Nonnull\n\tprotected static DoubleValue d(double value) {return DoubleValue.of(value);}\n\n\t@Nonnull\n\tprotected static StringValue str(@Nullable String value) {return ObjectValue.string(value);}\n\n\t@Nonnull\n\tprotected static StringValue str(@Nullable CharSequence value) {return str(value == null ? null : value.toString());}\n\n\t@Nonnull\n\tprotected static ObjectValue obj(@Nullable Object value) {\n\t\t// This isn't exactly perfect, as we lose type info with this conversion.\n\t\tif (value == null)\n\t\t\treturn ObjectValue.VAL_OBJECT_NULL;\n\n\t\t// String-like\n\t\tif (value instanceof CharSequence string)\n\t\t\treturn str(string);\n\n\t\t// Primitive boxes\n\t\tif (value instanceof Integer i)\n\t\t\treturn new BoxedIntegerValueImpl(i);\n\t\tif (value instanceof Long l)\n\t\t\treturn new BoxedLongValueImpl(l);\n\t\tif (value instanceof Byte b)\n\t\t\treturn new BoxedByteValueImpl(b);\n\t\tif (value instanceof Float f)\n\t\t\treturn new BoxedFloatValueImpl(f);\n\t\tif (value instanceof Double d)\n\t\t\treturn new BoxedDoubleValueImpl(d);\n\t\tif (value instanceof Character c)\n\t\t\treturn new BoxedCharacterValueImpl(c);\n\t\tif (value instanceof Short s)\n\t\t\treturn new BoxedShortValueImpl(s);\n\n\t\tthrow new IllegalArgumentException(\"Unsupported Object wrap: \" + value);\n\t}\n\n\tprotected static boolean[] arrz(@Nonnull ArrayValue value) {\n\t\tif (value.isNull())\n\t\t\treturn null;\n\t\tOptionalInt dimLength = value.getFirstDimensionLength();\n\t\tif (dimLength.isEmpty() || !value.hasKnownValue())\n\t\t\tthrow new IllegalArgumentException();\n\t\tint length = dimLength.getAsInt();\n\t\tboolean[] booleans = new boolean[length];\n\t\tfor (int i = 0; i < length; i++) {\n\t\t\tReValue iv = value.getValue(i);\n\t\t\tif (iv instanceof IntValue iiv && iiv.hasKnownValue())\n\t\t\t\tbooleans[i] = z(iiv);\n\t\t}\n\t\treturn booleans;\n\t}\n\n\tprotected static byte[] arrb(@Nonnull ArrayValue value) {\n\t\tif (value.isNull())\n\t\t\treturn null;\n\t\tOptionalInt dimLength = value.getFirstDimensionLength();\n\t\tif (dimLength.isEmpty() || !value.hasKnownValue())\n\t\t\tthrow new IllegalArgumentException();\n\t\tint length = dimLength.getAsInt();\n\t\tbyte[] bytes = new byte[length];\n\t\tfor (int i = 0; i < length; i++) {\n\t\t\tReValue iv = value.getValue(i);\n\t\t\tif (iv instanceof IntValue iiv && iiv.hasKnownValue())\n\t\t\t\tbytes[i] = b(iiv);\n\t\t}\n\t\treturn bytes;\n\t}\n\n\tprotected static short[] arrs(@Nonnull ArrayValue value) {\n\t\tif (value.isNull())\n\t\t\treturn null;\n\t\tOptionalInt dimLength = value.getFirstDimensionLength();\n\t\tif (dimLength.isEmpty() || !value.hasKnownValue())\n\t\t\tthrow new IllegalArgumentException();\n\t\tint length = dimLength.getAsInt();\n\t\tshort[] shorts = new short[length];\n\t\tfor (int i = 0; i < length; i++) {\n\t\t\tReValue iv = value.getValue(i);\n\t\t\tif (iv instanceof IntValue iiv && iiv.hasKnownValue())\n\t\t\t\tshorts[i] = s(iiv);\n\t\t}\n\t\treturn shorts;\n\t}\n\n\tprotected static char[] arrc(@Nonnull ArrayValue value) {\n\t\tif (value.isNull())\n\t\t\treturn null;\n\t\tOptionalInt dimLength = value.getFirstDimensionLength();\n\t\tif (dimLength.isEmpty() || !value.hasKnownValue())\n\t\t\tthrow new IllegalArgumentException();\n\t\tint length = dimLength.getAsInt();\n\t\tchar[] chars = new char[length];\n\t\tfor (int i = 0; i < length; i++) {\n\t\t\tReValue iv = value.getValue(i);\n\t\t\tif (iv instanceof IntValue iiv && iiv.hasKnownValue())\n\t\t\t\tchars[i] = c(iiv);\n\t\t}\n\t\treturn chars;\n\t}\n\n\tprotected static int[] arri(@Nonnull ArrayValue value) {\n\t\tif (value.isNull())\n\t\t\treturn null;\n\t\tOptionalInt dimLength = value.getFirstDimensionLength();\n\t\tif (dimLength.isEmpty() || !value.hasKnownValue())\n\t\t\tthrow new IllegalArgumentException();\n\t\tint length = dimLength.getAsInt();\n\t\tint[] ints = new int[length];\n\t\tfor (int i = 0; i < length; i++) {\n\t\t\tReValue iv = value.getValue(i);\n\t\t\tif (iv instanceof IntValue iiv && iiv.hasKnownValue())\n\t\t\t\tints[i] = i(iiv);\n\t\t}\n\t\treturn ints;\n\t}\n\n\tprotected static float[] arrf(@Nonnull ArrayValue value) {\n\t\tif (value.isNull())\n\t\t\treturn null;\n\t\tOptionalInt dimLength = value.getFirstDimensionLength();\n\t\tif (dimLength.isEmpty() || !value.hasKnownValue())\n\t\t\tthrow new IllegalArgumentException();\n\t\tint length = dimLength.getAsInt();\n\t\tfloat[] floats = new float[length];\n\t\tfor (int i = 0; i < length; i++) {\n\t\t\tReValue iv = value.getValue(i);\n\t\t\tif (iv instanceof FloatValue fv && fv.hasKnownValue())\n\t\t\t\tfloats[i] = f(fv);\n\t\t}\n\t\treturn floats;\n\t}\n\n\tprotected static double[] arrd(@Nonnull ArrayValue value) {\n\t\tif (value.isNull())\n\t\t\treturn null;\n\t\tOptionalInt dimLength = value.getFirstDimensionLength();\n\t\tif (dimLength.isEmpty() || !value.hasKnownValue())\n\t\t\tthrow new IllegalArgumentException();\n\t\tint length = dimLength.getAsInt();\n\t\tdouble[] doubles = new double[length];\n\t\tfor (int i = 0; i < length; i++) {\n\t\t\tReValue iv = value.getValue(i);\n\t\t\tif (iv instanceof DoubleValue dv && dv.hasKnownValue())\n\t\t\t\tdoubles[i] = d(dv);\n\t\t}\n\t\treturn doubles;\n\t}\n\n\tprotected static long[] arrj(@Nonnull ArrayValue value) {\n\t\tif (value.isNull())\n\t\t\treturn null;\n\t\tOptionalInt dimLength = value.getFirstDimensionLength();\n\t\tif (dimLength.isEmpty() || !value.hasKnownValue())\n\t\t\tthrow new IllegalArgumentException();\n\t\tint length = dimLength.getAsInt();\n\t\tlong[] longs = new long[length];\n\t\tfor (int i = 0; i < length; i++) {\n\t\t\tReValue iv = value.getValue(i);\n\t\t\tif (iv instanceof LongValue lv && lv.hasKnownValue())\n\t\t\t\tlongs[i] = j(lv);\n\t\t}\n\t\treturn longs;\n\t}\n\n\tprotected static String[] arrstr(@Nonnull ArrayValue value) {\n\t\tif (value.isNull())\n\t\t\treturn null;\n\t\tOptionalInt dimLength = value.getFirstDimensionLength();\n\t\tif (dimLength.isEmpty() || !value.hasKnownValue())\n\t\t\tthrow new IllegalArgumentException();\n\t\tint length = dimLength.getAsInt();\n\t\tString[] strings = new String[length];\n\t\tfor (int i = 0; i < length; i++) {\n\t\t\tReValue iv = value.getValue(i);\n\t\t\tif (iv instanceof StringValue sv && sv.hasKnownValue())\n\t\t\t\tstrings[i] = str(sv);\n\t\t}\n\t\treturn strings;\n\t}\n\n\tprotected static Object[] arrobj(@Nonnull ArrayValue value) {\n\t\tif (value.isNull())\n\t\t\treturn null;\n\t\tOptionalInt dimLength = value.getFirstDimensionLength();\n\t\tif (dimLength.isEmpty() || !value.hasKnownValue())\n\t\t\tthrow new IllegalArgumentException();\n\t\tint length = dimLength.getAsInt();\n\t\tObject[] objects = new Object[length];\n\t\tfor (int i = 0; i < length; i++) {\n\t\t\tReValue iv = value.getValue(i);\n\t\t\tif (iv instanceof ObjectValue sv && sv.hasKnownValue())\n\t\t\t\tobjects[i] = obj(sv);\n\t\t}\n\t\treturn objects;\n\t}\n\n\t@Nonnull\n\tprotected static ArrayValue arrz(@Nullable boolean[] value) {\n\t\tif (value == null)\n\t\t\treturn ArrayValue.VAL_BOOLEANS_NULL;\n\t\treturn new ArrayValueImpl(Types.ARRAY_1D_BOOLEAN, Nullness.NOT_NULL, value.length, index -> IntValue.of(value[index] ? 1 : 0));\n\t}\n\n\t@Nonnull\n\tprotected static ArrayValue arrb(@Nullable byte[] value) {\n\t\tif (value == null)\n\t\t\treturn ArrayValue.VAL_BYTES_NULL;\n\t\treturn new ArrayValueImpl(Types.ARRAY_1D_BYTE, Nullness.NOT_NULL, value.length, index -> IntValue.of(value[index]));\n\t}\n\n\t@Nonnull\n\tprotected static ArrayValue arrs(@Nullable short[] value) {\n\t\tif (value == null)\n\t\t\treturn ArrayValue.VAL_SHORTS_NULL;\n\t\treturn new ArrayValueImpl(Types.ARRAY_1D_SHORT, Nullness.NOT_NULL, value.length, index -> IntValue.of(value[index]));\n\t}\n\n\t@Nonnull\n\tprotected static ArrayValue arrc(@Nullable char[] value) {\n\t\tif (value == null)\n\t\t\treturn ArrayValue.VAL_CHARS_NULL;\n\t\treturn new ArrayValueImpl(Types.ARRAY_1D_CHAR, Nullness.NOT_NULL, value.length, index -> IntValue.of(value[index]));\n\t}\n\n\t@Nonnull\n\tprotected static ArrayValue arri(@Nullable int[] value) {\n\t\tif (value == null)\n\t\t\treturn ArrayValue.VAL_INTS_NULL;\n\t\treturn new ArrayValueImpl(Types.ARRAY_1D_INT, Nullness.NOT_NULL, value.length, index -> IntValue.of(value[index]));\n\t}\n\n\t@Nonnull\n\tprotected static ArrayValue arrj(@Nullable long[] value) {\n\t\tif (value == null)\n\t\t\treturn ArrayValue.VAL_LONGS_NULL;\n\t\treturn new ArrayValueImpl(Types.ARRAY_1D_LONG, Nullness.NOT_NULL, value.length, index -> LongValue.of(value[index]));\n\t}\n\n\t@Nonnull\n\tprotected static ArrayValue arrf(@Nullable float[] value) {\n\t\tif (value == null)\n\t\t\treturn ArrayValue.VAL_FLOATS_NULL;\n\t\treturn new ArrayValueImpl(Types.ARRAY_1D_FLOAT, Nullness.NOT_NULL, value.length, index -> FloatValue.of(value[index]));\n\t}\n\n\t@Nonnull\n\tprotected static ArrayValue arrd(@Nullable double[] value) {\n\t\tif (value == null)\n\t\t\treturn ArrayValue.VAL_DOUBLES_NULL;\n\t\treturn new ArrayValueImpl(Types.ARRAY_1D_DOUBLE, Nullness.NOT_NULL, value.length, index -> DoubleValue.of(value[index]));\n\t}\n\n\t@Nonnull\n\tprotected static ArrayValue arrstr(@Nullable CharSequence[] value) {\n\t\tif (value == null)\n\t\t\treturn ArrayValue.VAL_STRINGS_NULL;\n\t\treturn new ArrayValueImpl(Types.ARRAY_1D_STRING, Nullness.NOT_NULL, value.length, index -> ObjectValue.string(String.valueOf(value[index])));\n\t}\n\n\t@Nonnull\n\tprotected static ArrayValue arrstr(@Nullable String[] value) {\n\t\tif (value == null)\n\t\t\treturn ArrayValue.VAL_STRINGS_NULL;\n\t\treturn new ArrayValueImpl(Types.ARRAY_1D_STRING, Nullness.NOT_NULL, value.length, index -> ObjectValue.string(value[index]));\n\t}\n\n\t@Nonnull\n\tprotected static ArrayValue arrobj(@Nullable Object[] value) {\n\t\tif (value == null)\n\t\t\treturn ArrayValue.VAL_OBJECTS_NULL;\n\t\treturn new ArrayValueImpl(Types.ARRAY_1D_OBJECT, Nullness.NOT_NULL, value.length, index -> obj(value[index]));\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprotected interface Func_7<A extends ReValue, B extends ReValue, C extends ReValue, D extends ReValue, E extends ReValue, F extends ReValue, G extends ReValue> extends Func {\n\t\t@Nullable\n\t\t@Override\n\t\tdefault ReValue apply(@Nonnull List<? extends ReValue> values) {\n\t\t\treturn apply((A) values.get(0), (B) values.get(1), (C) values.get(2), (D) values.get(3), (E) values.get(4), (F) values.get(5), (G) values.get(6));\n\t\t}\n\n\t\t@Nullable\n\t\tReValue apply(A a, B b, C c, D d, E e, F f, G g);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprotected interface Func_6<A extends ReValue, B extends ReValue, C extends ReValue, D extends ReValue, E extends ReValue, F extends ReValue> extends Func {\n\t\t@Nullable\n\t\t@Override\n\t\tdefault ReValue apply(@Nonnull List<? extends ReValue> values) {\n\t\t\treturn apply((A) values.get(0), (B) values.get(1), (C) values.get(2), (D) values.get(3), (E) values.get(4), (F) values.get(5));\n\t\t}\n\n\t\t@Nullable\n\t\tReValue apply(A a, B b, C c, D d, E e, F f);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprotected interface Func_5<A extends ReValue, B extends ReValue, C extends ReValue, D extends ReValue, E extends ReValue> extends Func {\n\t\t@Nullable\n\t\t@Override\n\t\tdefault ReValue apply(@Nonnull List<? extends ReValue> values) {\n\t\t\treturn apply((A) values.get(0), (B) values.get(1), (C) values.get(2), (D) values.get(3), (E) values.get(4));\n\t\t}\n\n\t\t@Nullable\n\t\tReValue apply(A a, B b, C c, D d, E e);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprotected interface Func_4<A extends ReValue, B extends ReValue, C extends ReValue, D extends ReValue> extends Func {\n\t\t@Nullable\n\t\t@Override\n\t\tdefault ReValue apply(@Nonnull List<? extends ReValue> values) {\n\t\t\treturn apply((A) values.get(0), (B) values.get(1), (C) values.get(2), (D) values.get(3));\n\t\t}\n\n\t\t@Nullable\n\t\tReValue apply(A a, B b, C c, D d);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprotected interface Func_3<A extends ReValue, B extends ReValue, C extends ReValue> extends Func {\n\t\t@Nullable\n\t\t@Override\n\t\tdefault ReValue apply(@Nonnull List<? extends ReValue> values) {\n\t\t\treturn apply((A) values.get(0), (B) values.get(1), (C) values.get(2));\n\t\t}\n\n\t\t@Nullable\n\t\tReValue apply(A a, B b, C c);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprotected interface Func_2<A extends ReValue, B extends ReValue> extends Func {\n\t\t@Nullable\n\t\t@Override\n\t\tdefault ReValue apply(@Nonnull List<? extends ReValue> values) {\n\t\t\treturn apply((A) values.get(0), (B) values.get(1));\n\t\t}\n\n\t\t@Nullable\n\t\tReValue apply(A a, B b);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprotected interface Func_1<A extends ReValue> extends Func {\n\t\t@Nullable\n\t\t@Override\n\t\tdefault ReValue apply(@Nonnull List<? extends ReValue> values) {\n\t\t\treturn apply((A) values.getFirst());\n\t\t}\n\n\t\t@Nullable\n\t\tReValue apply(A a);\n\t}\n\n\tprotected interface Func_0 extends Func {\n\t\t@Nullable\n\t\t@Override\n\t\tdefault ReValue apply(@Nonnull List<? extends ReValue> values) {\n\t\t\treturn apply();\n\t\t}\n\n\t\t@Nullable\n\t\tReValue apply();\n\t}\n\n\tprotected interface Func {\n\t\t@Nullable\n\t\tReValue apply(@Nonnull List<? extends ReValue> values);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/lookup/GetFieldLookup.java",
    "content": "package software.coley.recaf.util.analysis.lookup;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.tree.FieldInsnNode;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\n/**\n * Lookup for context-bound field values.\n *\n * @author Matt Coley\n */\npublic interface GetFieldLookup {\n\t/**\n\t * @param field\n\t * \t\tField reference.\n\t * @param context\n\t * \t\tClass context the field resides within.\n\t * \t\tHas a {@link ReValue#hasKnownValue() known value}.\n\t *\n\t * @return Value representing the field.\n\t */\n\t@Nonnull\n\tReValue get(@Nonnull FieldInsnNode field, @Nonnull ReValue context);\n\n\t/**\n\t * @param field\n\t * \t\tField reference.\n\t *\n\t * @return {@code true} when this lookup can provide a value via {@link #get(FieldInsnNode, ReValue)}.\n\t */\n\tboolean hasLookup(@Nonnull FieldInsnNode field);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/lookup/GetStaticLookup.java",
    "content": "package software.coley.recaf.util.analysis.lookup;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.tree.FieldInsnNode;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\n/**\n * Lookup for static field values.\n *\n * @author Matt Coley\n */\npublic interface GetStaticLookup {\n\t/**\n\t * @param field\n\t * \t\tField reference.\n\t *\n\t * @return Value representing the field.\n\t */\n\t@Nonnull\n\tReValue get(@Nonnull FieldInsnNode field);\n\n\t/**\n\t * @param field\n\t * \t\tField reference.\n\t *\n\t * @return {@code true} when this lookup can provide a value via {@link #get(FieldInsnNode)}.\n\t */\n\tboolean hasLookup(@Nonnull FieldInsnNode field);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/lookup/InvokeStaticLookup.java",
    "content": "package software.coley.recaf.util.analysis.lookup;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\nimport java.util.List;\n\n/**\n * Lookup for static method return values.\n *\n * @author Matt Coley\n */\npublic interface InvokeStaticLookup {\n\t/**\n\t * @param method\n\t * \t\tMethod reference.\n\t * @param values\n\t * \t\tArgument values to the method.\n\t * \t\tAll items {@link ReValue#hasKnownValue() have known values}.\n\t *\n\t * @return Value representing the return value of the method.\n\t */\n\t@Nonnull\n\tReValue get(@Nonnull MethodInsnNode method, @Nonnull List<? extends ReValue> values);\n\n\t/**\n\t * @param method\n\t * \t\tMethod reference.\n\t *\n\t * @return {@code true} when this lookup can provide a value via {@link #get(MethodInsnNode, List)}.\n\t */\n\tboolean hasLookup(@Nonnull MethodInsnNode method);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/lookup/InvokeVirtualLookup.java",
    "content": "package software.coley.recaf.util.analysis.lookup;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\nimport java.util.List;\n\n/**\n * Lookup for context-bound method return values.\n *\n * @author Matt Coley\n */\npublic interface InvokeVirtualLookup {\n\t/**\n\t * @param method\n\t * \t\tMethod reference.\n\t * @param context\n\t * \t\tClass context the method resides within.\n\t * \t\tHas a {@link ReValue#hasKnownValue() known value}.\n\t * @param values\n\t * \t\tArgument values to the method.\n\t * \t\tAll items {@link ReValue#hasKnownValue() have known values}.\n\t *\n\t * @return Value representing the return value of the method.\n\t */\n\t@Nonnull\n\tReValue get(@Nonnull MethodInsnNode method, @Nonnull ReValue context, @Nonnull List<? extends ReValue> values);\n\n\t/**\n\t * @param method\n\t * \t\tMethod reference.\n\t *\n\t * @return {@code true} when this lookup can provide a value via {@link #get(MethodInsnNode, ReValue, List)}.\n\t */\n\tboolean hasLookup(@Nonnull MethodInsnNode method);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/ArrayValue.java",
    "content": "package software.coley.recaf.util.analysis.value;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.value.impl.ArrayValueImpl;\n\nimport java.util.Arrays;\nimport java.util.OptionalInt;\n\n/**\n * Value capable of recording partial details of array content.\n *\n * @author Matt Coley\n */\npublic interface ArrayValue extends ObjectValue {\n\tArrayValue VAL_BOOLEANS = new ArrayValueImpl(Type.getType(\"[Z\"), Nullness.NOT_NULL);\n\tArrayValue VAL_BOOLEANS_NULL = new ArrayValueImpl(Type.getType(\"[Z\"), Nullness.NULL);\n\tArrayValue VAL_CHARS = new ArrayValueImpl(Type.getType(\"[C\"), Nullness.NOT_NULL);\n\tArrayValue VAL_CHARS_NULL = new ArrayValueImpl(Type.getType(\"[C\"), Nullness.NULL);\n\tArrayValue VAL_BYTES = new ArrayValueImpl(Type.getType(\"[B\"), Nullness.NOT_NULL);\n\tArrayValue VAL_BYTES_NULL = new ArrayValueImpl(Type.getType(\"[B\"), Nullness.NULL);\n\tArrayValue VAL_SHORTS = new ArrayValueImpl(Type.getType(\"[S\"), Nullness.NOT_NULL);\n\tArrayValue VAL_SHORTS_NULL = new ArrayValueImpl(Type.getType(\"[S\"), Nullness.NULL);\n\tArrayValue VAL_INTS = new ArrayValueImpl(Type.getType(\"[I\"), Nullness.NOT_NULL);\n\tArrayValue VAL_INTS_NULL = new ArrayValueImpl(Type.getType(\"[I\"), Nullness.NULL);\n\tArrayValue VAL_FLOATS = new ArrayValueImpl(Type.getType(\"[F\"), Nullness.NOT_NULL);\n\tArrayValue VAL_FLOATS_NULL = new ArrayValueImpl(Type.getType(\"[F\"), Nullness.NULL);\n\tArrayValue VAL_DOUBLES = new ArrayValueImpl(Type.getType(\"[D\"), Nullness.NOT_NULL);\n\tArrayValue VAL_DOUBLES_NULL = new ArrayValueImpl(Type.getType(\"[D\"), Nullness.NULL);\n\tArrayValue VAL_LONGS = new ArrayValueImpl(Type.getType(\"[J\"), Nullness.NOT_NULL);\n\tArrayValue VAL_LONGS_NULL = new ArrayValueImpl(Type.getType(\"[J\"), Nullness.NULL);\n\tArrayValue VAL_OBJECTS = new ArrayValueImpl(Type.getType(\"[Ljava/lang/Object;\"), Nullness.NOT_NULL);\n\tArrayValue VAL_OBJECTS_NULL = new ArrayValueImpl(Type.getType(\"[Ljava/lang/Object;\"), Nullness.NULL);\n\tArrayValue VAL_STRINGS = new ArrayValueImpl(Type.getType(\"[Ljava/lang/String;\"), Nullness.NOT_NULL);\n\tArrayValue VAL_STRINGS_NULL = new ArrayValueImpl(Type.getType(\"[Ljava/lang/String;\"), Nullness.NULL);\n\n\t/**\n\t * @param type\n\t * \t\tArray type.\n\t * @param nullness\n\t * \t\tArray null state.\n\t *\n\t * @return Array value of the given type.\n\t */\n\t@Nonnull\n\tstatic ArrayValue of(@Nonnull Type type, @Nonnull Nullness nullness) {\n\t\tString descriptor = type.getDescriptor();\n\t\treturn switch (descriptor) {\n\t\t\tcase \"[Z\" -> nullness == Nullness.NULL ? VAL_BOOLEANS_NULL : VAL_BOOLEANS;\n\t\t\tcase \"[C\" -> nullness == Nullness.NULL ? VAL_CHARS_NULL : VAL_CHARS;\n\t\t\tcase \"[B\" -> nullness == Nullness.NULL ? VAL_BYTES_NULL : VAL_BYTES;\n\t\t\tcase \"[S\" -> nullness == Nullness.NULL ? VAL_SHORTS_NULL : VAL_SHORTS;\n\t\t\tcase \"[I\" -> nullness == Nullness.NULL ? VAL_INTS_NULL : VAL_INTS;\n\t\t\tcase \"[F\" -> nullness == Nullness.NULL ? VAL_FLOATS_NULL : VAL_FLOATS;\n\t\t\tcase \"[D\" -> nullness == Nullness.NULL ? VAL_DOUBLES_NULL : VAL_DOUBLES;\n\t\t\tcase \"[J\" -> nullness == Nullness.NULL ? VAL_LONGS_NULL : VAL_LONGS;\n\t\t\tcase \"[Ljava/lang/String;\" -> nullness == Nullness.NULL ? VAL_STRINGS_NULL : VAL_STRINGS;\n\t\t\tcase \"[Ljava/lang/Object;\" -> nullness == Nullness.NULL ? VAL_OBJECTS_NULL : VAL_OBJECTS;\n\t\t\tdefault -> new ArrayValueImpl(type, nullness);\n\t\t};\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tArray type.\n\t * @param nullness\n\t * \t\tArray null state.\n\t * @param length\n\t * \t\tArray length.\n\t *\n\t * @return Array value of the given type/length.\n\t */\n\t@Nonnull\n\tstatic ArrayValue of(@Nonnull Type type, @Nonnull Nullness nullness, int length) {\n\t\treturn new ArrayValueImpl(type, nullness, length);\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tArray type.\n\t * @param dimensions\n\t * \t\tDimensions of the array to create.\n\t *\n\t * @return Array value created from a {@link Opcodes#MULTIANEWARRAY} instruction.\n\t */\n\t@Nonnull\n\tstatic ReValue multiANewArray(@Nonnull Type type, @Nonnull int[] dimensions) {\n\t\tint length = dimensions[0];\n\t\tif (dimensions.length == 1)\n\t\t\treturn of(type, Nullness.NOT_NULL, length);\n\t\treturn new ArrayValueImpl(type, Nullness.NOT_NULL, length,\n\t\t\t\ti -> multiANewArray(Types.undimension(type), Arrays.copyOfRange(dimensions, 1, dimensions.length))\n\t\t);\n\t}\n\n\t/**\n\t * @param index\n\t * \t\tIndex to assign value at.\n\t * @param value\n\t * \t\tValue to assign.\n\t *\n\t * @return New array with the given value assigned at the given index.\n\t */\n\t@Nonnull\n\tArrayValue setValue(int index, @Nonnull ReValue value);\n\n\t/**\n\t * @param originalValue\n\t * \t\tSome value.\n\t * @param updatedValue\n\t * \t\tSome updated version of the value.\n\t *\n\t * @return New array with the given original value replaced with the updated value.\n\t */\n\t@Nonnull\n\tArrayValue updatedCopyIfContained(@Nonnull ReValue originalValue, @Nonnull ReValue updatedValue);\n\n\t@Override\n\tdefault boolean hasKnownValue() {\n\t\treturn false;\n\t}\n\n\t@Nonnull\n\t@Override\n\tType type();\n\n\t/**\n\t * The element type is the base of any array. Consider the following:\n\t * <ul>\n\t *     <li>{@code int[]}</li>\n\t *     <li>{@code int[][]}</li>\n\t *     <li>{@code int[][][]}</li>\n\t * </ul>\n\t * The element type of each is {@code int}.\n\t *\n\t * @return Element type of the array.\n\t */\n\t@Nonnull\n\tdefault Type elementType() {\n\t\treturn type().getElementType();\n\t}\n\n\t/**\n\t * Consider the following:\n\t * <ul>\n\t *     <li>1: {@code int[]}</li>\n\t *     <li>2: {@code int[][]}</li>\n\t *     <li>3: {@code int[][][]}</li>\n\t * </ul>\n\t *\n\t * @return Dimensions of the array.\n\t */\n\tdefault int dimensions() {\n\t\treturn type().getDimensions();\n\t}\n\n\t/**\n\t * Consider the following:\n\t * <table>\n\t *     <tr><th>Length</th><th>Array definition</th></tr>\n\t *     <tr><td>{@code 7}</td><td>{@code int[7]}</td></tr>\n\t *     <tr><td>{@code 7}</td><td>{@code int[7][9]}</td></tr>\n\t *     <tr><td>Unknown</td><td>{@code int[][9]}</td></tr>\n\t *     <tr><td>Unknown</td><td>{@code int[]}</td></tr>\n\t * </table>\n\t *\n\t * @return Length of the first dimension.\n\t */\n\t@Nonnull\n\tOptionalInt getFirstDimensionLength();\n\n\t/**\n\t * @param index\n\t * \t\tIndex within {@link #getFirstDimensionLength()}.\n\t *\n\t * @return Value, if known, at the given index. Otherwise, a {@link ReValue} of the array's element type..\n\t */\n\t@Nullable\n\tReValue getValue(int index);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/DoubleValue.java",
    "content": "package software.coley.recaf.util.analysis.value;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.value.impl.DoubleValueImpl;\n\nimport java.util.OptionalDouble;\n\n/**\n * Value capable of recording exact floating double precision content.\n *\n * @author Matt Coley\n */\npublic non-sealed interface DoubleValue extends ReValue {\n\tDoubleValue UNKNOWN = new DoubleValueImpl();\n\tDoubleValue VAL_MAX = new DoubleValueImpl(Double.MAX_VALUE);\n\tDoubleValue VAL_MIN = new DoubleValueImpl(Double.MIN_VALUE);\n\tDoubleValue VAL_M1 = new DoubleValueImpl(-1);\n\tDoubleValue VAL_0 = new DoubleValueImpl(0);\n\tDoubleValue VAL_1 = new DoubleValueImpl(1);\n\n\t/**\n\t * @param value\n\t * \t\tDouble value to hold.\n\t *\n\t * @return Double value holding the exact content.\n\t */\n\t@Nonnull\n\tstatic DoubleValue of(double value) {\n\t\tif (value == 0) return VAL_0;\n\t\telse if (value == 1) return VAL_1;\n\t\telse if (value == -1) return VAL_M1;\n\t\telse if (value == Double.MAX_VALUE) return VAL_MAX;\n\t\telse if (value == Double.MIN_VALUE) return VAL_MIN;\n\t\treturn new DoubleValueImpl(value);\n\t}\n\n\t/**\n\t * @return Double content of value. Empty if {@link #hasKnownValue() not known}.\n\t */\n\t@Nonnull\n\tOptionalDouble value();\n\n\t@Override\n\tdefault boolean hasKnownValue() {\n\t\treturn value().isPresent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tdefault Type type() {\n\t\treturn Type.DOUBLE_TYPE;\n\t}\n\n\t@Override\n\tdefault int getSize() {\n\t\treturn 2;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is equal to the given value.\n\t */\n\tdefault boolean isEqualTo(double value) {\n\t\treturn value().isPresent() && value().getAsDouble() == value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is less than the given value.\n\t */\n\tdefault boolean isLessThan(double value) {\n\t\treturn value().isPresent() && value().getAsDouble() < value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is less than or equal to the given value.\n\t */\n\tdefault boolean isLessThanOrEqual(double value) {\n\t\treturn value().isPresent() && value().getAsDouble() <= value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is greater than the given value.\n\t */\n\tdefault boolean isGreaterThan(double value) {\n\t\treturn value().isPresent() && value().getAsDouble() > value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is greater than or equal to the given value.\n\t */\n\tdefault boolean isGreaterThanOrEqual(double value) {\n\t\treturn value().isPresent() && value().getAsDouble() >= value;\n\t}\n\n\t@Nonnull\n\tdefault DoubleValue add(@Nonnull DoubleValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsDouble() + otherValue.getAsDouble());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault DoubleValue sub(@Nonnull DoubleValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsDouble() - otherValue.getAsDouble());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault DoubleValue mul(@Nonnull DoubleValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsDouble() * otherValue.getAsDouble());\n\t\tif (isEqualTo(0) || other.isEqualTo(0)) return VAL_0;\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault DoubleValue div(@Nonnull DoubleValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) {\n\t\t\tdouble otherLiteral = otherValue.getAsDouble();\n\t\t\tif (otherLiteral == 0) return UNKNOWN; // We'll just pretend this works\n\t\t\treturn of((value.getAsDouble() / otherLiteral));\n\t\t}\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue cmpg(@Nonnull DoubleValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) {\n\t\t\tdouble f1 = value.getAsDouble();\n\t\t\tdouble f2 = otherValue.getAsDouble();\n\t\t\tif (Double.isNaN(f1) || Double.isNaN(f2)) return IntValue.VAL_1;\n\t\t\treturn IntValue.of(Double.compare(f1, f2));\n\t\t}\n\t\treturn IntValue.UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue cmpl(@Nonnull DoubleValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) {\n\t\t\tdouble f1 = value.getAsDouble();\n\t\t\tdouble f2 = otherValue.getAsDouble();\n\t\t\tif (Double.isNaN(f1) || Double.isNaN(f2)) return IntValue.VAL_M1;\n\t\t\treturn IntValue.of(Double.compare(f1, f2));\n\t\t}\n\t\treturn IntValue.UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault DoubleValue rem(@Nonnull DoubleValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent())\n\t\t\treturn of((float) (value.getAsDouble() % otherValue.getAsDouble()));\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault DoubleValue negate() {\n\t\tOptionalDouble value = value();\n\t\tif (value.isPresent()) return of(-value.getAsDouble());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue castInt() {\n\t\tOptionalDouble value = value();\n\t\tif (value.isPresent()) return IntValue.of((int) value.getAsDouble());\n\t\treturn IntValue.UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault FloatValue castFloat() {\n\t\tOptionalDouble value = value();\n\t\tif (value.isPresent()) return FloatValue.of((float) value.getAsDouble());\n\t\treturn FloatValue.UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue castLong() {\n\t\tOptionalDouble value = value();\n\t\tif (value.isPresent()) return LongValue.of((long) value.getAsDouble());\n\t\treturn LongValue.UNKNOWN;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/FloatValue.java",
    "content": "package software.coley.recaf.util.analysis.value;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.value.impl.FloatValueImpl;\n\nimport java.util.OptionalDouble;\n\n/**\n * Value capable of recording exact floating point content.\n *\n * @author Matt Coley\n */\npublic non-sealed interface FloatValue extends ReValue {\n\tFloatValue UNKNOWN = new FloatValueImpl();\n\tFloatValue VAL_MAX = new FloatValueImpl(Float.MAX_VALUE);\n\tFloatValue VAL_MIN = new FloatValueImpl(Float.MIN_VALUE);\n\tFloatValue VAL_M1 = new FloatValueImpl(-1);\n\tFloatValue VAL_0 = new FloatValueImpl(0);\n\tFloatValue VAL_1 = new FloatValueImpl(1);\n\tFloatValue VAL_2 = new FloatValueImpl(2);\n\n\t/**\n\t * @param value\n\t * \t\tFloat value to hold.\n\t *\n\t * @return Float value holding the exact content.\n\t */\n\t@Nonnull\n\tstatic FloatValue of(float value) {\n\t\tif (value == 0) return VAL_0;\n\t\telse if (value == 1) return VAL_1;\n\t\telse if (value == -1) return VAL_M1;\n\t\telse if (value == 2) return VAL_2;\n\t\telse if (value == Float.MAX_VALUE) return VAL_MAX;\n\t\telse if (value == Float.MIN_VALUE) return VAL_MIN;\n\t\treturn new FloatValueImpl(value);\n\t}\n\n\t/**\n\t * @return Float content of value. Empty if {@link #hasKnownValue() not known}.\n\t *\n\t * @implNote Java does not have an {@code OptionalFloat}.\n\t */\n\t@Nonnull\n\tOptionalDouble value();\n\n\t@Override\n\tdefault boolean hasKnownValue() {\n\t\treturn value().isPresent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tdefault Type type() {\n\t\treturn Type.FLOAT_TYPE;\n\t}\n\n\t@Override\n\tdefault int getSize() {\n\t\treturn 1;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is equal to the given value.\n\t */\n\tdefault boolean isEqualTo(float value) {\n\t\treturn value().isPresent() && value().getAsDouble() == value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is less than the given value.\n\t */\n\tdefault boolean isLessThan(float value) {\n\t\treturn value().isPresent() && value().getAsDouble() < value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is less than or equal to the given value.\n\t */\n\tdefault boolean isLessThanOrEqual(float value) {\n\t\treturn value().isPresent() && value().getAsDouble() <= value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is greater than the given value.\n\t */\n\tdefault boolean isGreaterThan(float value) {\n\t\treturn value().isPresent() && value().getAsDouble() > value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is greater than or equal to the given value.\n\t */\n\tdefault boolean isGreaterThanOrEqual(float value) {\n\t\treturn value().isPresent() && value().getAsDouble() >= value;\n\t}\n\n\t@Nonnull\n\tdefault FloatValue add(@Nonnull FloatValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent())\n\t\t\treturn of((float) (value.getAsDouble() + otherValue.getAsDouble()));\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault FloatValue sub(@Nonnull FloatValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent())\n\t\t\treturn of((float) (value.getAsDouble() - otherValue.getAsDouble()));\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault FloatValue mul(@Nonnull FloatValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent())\n\t\t\treturn of((float) (value.getAsDouble() * otherValue.getAsDouble()));\n\t\tif (isEqualTo(0) || other.isEqualTo(0)) return VAL_0;\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault FloatValue div(@Nonnull FloatValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) {\n\t\t\tdouble otherLiteral = otherValue.getAsDouble();\n\t\t\tif (otherLiteral == 0) return UNKNOWN; // We'll just pretend this works\n\t\t\treturn of((float) (value.getAsDouble() / otherLiteral));\n\t\t}\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue cmpg(@Nonnull FloatValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) {\n\t\t\tfloat f1 = (float) value.getAsDouble();\n\t\t\tfloat f2 = (float) otherValue.getAsDouble();\n\t\t\tif (Float.isNaN(f1) || Float.isNaN(f2)) return IntValue.VAL_1;\n\t\t\treturn IntValue.of(Float.compare(f1, f2));\n\t\t}\n\t\treturn IntValue.UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue cmpl(@Nonnull FloatValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) {\n\t\t\tfloat f1 = (float) value.getAsDouble();\n\t\t\tfloat f2 = (float) otherValue.getAsDouble();\n\t\t\tif (Float.isNaN(f1) || Float.isNaN(f2)) return IntValue.VAL_M1;\n\t\t\treturn IntValue.of(Float.compare(f1, f2));\n\t\t}\n\t\treturn IntValue.UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault FloatValue rem(@Nonnull FloatValue other) {\n\t\tOptionalDouble value = value();\n\t\tOptionalDouble otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent())\n\t\t\treturn of((float) (value.getAsDouble() % otherValue.getAsDouble()));\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault FloatValue negate() {\n\t\tOptionalDouble value = value();\n\t\tif (value.isPresent()) return of((float) -value.getAsDouble());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue castInt() {\n\t\tOptionalDouble value = value();\n\t\tif (value.isPresent()) return IntValue.of((int) value.getAsDouble());\n\t\treturn IntValue.UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault DoubleValue castDouble() {\n\t\tOptionalDouble value = value();\n\t\tif (value.isPresent()) return DoubleValue.of(value.getAsDouble());\n\t\treturn DoubleValue.UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue castLong() {\n\t\tOptionalDouble value = value();\n\t\tif (value.isPresent()) return LongValue.of((long) value.getAsDouble());\n\t\treturn LongValue.UNKNOWN;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/IllegalValueException.java",
    "content": "package software.coley.recaf.util.analysis.value;\n\n/**\n * Exception thrown when creating a {@link ReValue} from some input fails.\n *\n * @author Matt Coley\n */\npublic class IllegalValueException extends Exception {\n\t/**\n\t * @param message\n\t * \t\tReason why the value could not be created.\n\t */\n\tpublic IllegalValueException(String message) {\n\t\tsuper(message);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/IntValue.java",
    "content": "package software.coley.recaf.util.analysis.value;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.value.impl.IntValueImpl;\n\nimport java.util.OptionalInt;\n\n/**\n * Value capable of recording exact integer content.\n *\n * @author Matt Coley\n */\npublic non-sealed interface IntValue extends ReValue {\n\tIntValue UNKNOWN = new IntValueImpl();\n\tIntValue VAL_MAX = new IntValueImpl(Integer.MAX_VALUE);\n\tIntValue VAL_MIN = new IntValueImpl(Integer.MIN_VALUE);\n\tIntValue VAL_M1 = new IntValueImpl(-1);\n\tIntValue VAL_0 = new IntValueImpl(0);\n\tIntValue VAL_1 = new IntValueImpl(1);\n\tIntValue VAL_2 = new IntValueImpl(2);\n\tIntValue VAL_3 = new IntValueImpl(3);\n\tIntValue VAL_4 = new IntValueImpl(4);\n\tIntValue VAL_5 = new IntValueImpl(5);\n\n\t/**\n\t * @param value\n\t * \t\tInteger value to hold.\n\t *\n\t * @return Integer value holding the exact content.\n\t */\n\t@Nonnull\n\tstatic IntValue of(int value) {\n\t\treturn switch (value) {\n\t\t\tcase Integer.MAX_VALUE -> VAL_MAX;\n\t\t\tcase Integer.MIN_VALUE -> VAL_MIN;\n\t\t\tcase -1 -> VAL_M1;\n\t\t\tcase 0 -> VAL_0;\n\t\t\tcase 1 -> VAL_1;\n\t\t\tcase 2 -> VAL_2;\n\t\t\tcase 3 -> VAL_3;\n\t\t\tcase 4 -> VAL_4;\n\t\t\tcase 5 -> VAL_5;\n\t\t\tdefault -> new IntValueImpl(value);\n\t\t};\n\t}\n\n\t/**\n\t * @return Integer content of value. Empty if {@link #hasKnownValue() not known}.\n\t */\n\t@Nonnull\n\tOptionalInt value();\n\n\t@Override\n\tdefault boolean hasKnownValue() {\n\t\treturn value().isPresent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tdefault Type type() {\n\t\treturn Type.INT_TYPE;\n\t}\n\n\t@Override\n\tdefault int getSize() {\n\t\treturn 1;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is equal to the given value.\n\t */\n\tdefault boolean isEqualTo(int value) {\n\t\treturn value().isPresent() && value().getAsInt() == value;\n\t}\n\n\t/**\n\t * @param otherValue\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is equal to the given value.\n\t */\n\tdefault boolean isEqualTo(@Nonnull IntValue otherValue) {\n\t\treturn value().isPresent() && otherValue.value().isPresent()\n\t\t\t\t&& value().getAsInt() == otherValue.value().getAsInt();\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is not equal to the given value.\n\t */\n\tdefault boolean isNotEqualTo(int value) {\n\t\treturn value().isPresent() && value().getAsInt() != value;\n\t}\n\n\t/**\n\t * @param otherValue\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is not equal to the given value.\n\t */\n\tdefault boolean isNotEqualTo(@Nonnull IntValue otherValue) {\n\t\treturn value().isPresent() && otherValue.value().isPresent()\n\t\t\t\t&& value().getAsInt() != otherValue.value().getAsInt();\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is less than the given value.\n\t */\n\tdefault boolean isLessThan(int value) {\n\t\treturn value().isPresent() && value().getAsInt() < value;\n\t}\n\n\t/**\n\t * @param otherValue\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is less than the given value.\n\t */\n\tdefault boolean isLessThan(@Nonnull IntValue otherValue) {\n\t\treturn value().isPresent() && otherValue.value().isPresent()\n\t\t\t\t&& value().getAsInt() < otherValue.value().getAsInt();\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is less than or equal to the given value.\n\t */\n\tdefault boolean isLessThanOrEqual(int value) {\n\t\treturn value().isPresent() && value().getAsInt() <= value;\n\t}\n\n\t/**\n\t * @param otherValue\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is less than or equal to the given value.\n\t */\n\tdefault boolean isLessThanOrEqual(@Nonnull IntValue otherValue) {\n\t\treturn value().isPresent() && otherValue.value().isPresent()\n\t\t\t\t&& value().getAsInt() <= otherValue.value().getAsInt();\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is greater than the given value.\n\t */\n\tdefault boolean isGreaterThan(int value) {\n\t\treturn value().isPresent() && value().getAsInt() > value;\n\t}\n\n\t/**\n\t * @param otherValue\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is greater than the given value.\n\t */\n\tdefault boolean isGreaterThan(@Nonnull IntValue otherValue) {\n\t\treturn value().isPresent() && otherValue.value().isPresent()\n\t\t\t\t&& value().getAsInt() > otherValue.value().getAsInt();\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is greater than or equal to the given value.\n\t */\n\tdefault boolean isGreaterThanOrEqual(int value) {\n\t\treturn value().isPresent() && value().getAsInt() >= value;\n\t}\n\n\t/**\n\t * @param otherValue\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is greater than or equal to the given value.\n\t */\n\tdefault boolean isGreaterThanOrEqual(@Nonnull IntValue otherValue) {\n\t\treturn value().isPresent() && otherValue.value().isPresent()\n\t\t\t\t&& value().getAsInt() >= otherValue.value().getAsInt();\n\t}\n\n\t@Nonnull\n\tdefault IntValue add(int incr) {\n\t\tOptionalInt value = value();\n\t\tif (value.isPresent()) return of(value.getAsInt() + incr);\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue add(@Nonnull IntValue other) {\n\t\tOptionalInt value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsInt() + otherValue.getAsInt());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue sub(@Nonnull IntValue other) {\n\t\tOptionalInt value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsInt() - otherValue.getAsInt());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue mul(@Nonnull IntValue other) {\n\t\tOptionalInt value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsInt() * otherValue.getAsInt());\n\t\tif (isEqualTo(0) || other.isEqualTo(0)) return VAL_0;\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue div(@Nonnull IntValue other) {\n\t\tOptionalInt value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) {\n\t\t\tint otherLiteral = otherValue.getAsInt();\n\t\t\tif (otherLiteral == 0) return UNKNOWN; // We'll just pretend this works\n\t\t\treturn of(value.getAsInt() / otherLiteral);\n\t\t}\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue and(@Nonnull IntValue other) {\n\t\tOptionalInt value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsInt() & otherValue.getAsInt());\n\t\tif (isEqualTo(0) || other.isEqualTo(0)) return VAL_0;\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue or(@Nonnull IntValue other) {\n\t\tOptionalInt value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsInt() | otherValue.getAsInt());\n\t\tif (isEqualTo(-1) || other.isEqualTo(-1)) return VAL_M1;\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue xor(@Nonnull IntValue other) {\n\t\tOptionalInt value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsInt() ^ otherValue.getAsInt());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue rem(@Nonnull IntValue other) {\n\t\tOptionalInt value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) {\n\t\t\tint otherLiteral = otherValue.getAsInt();\n\t\t\tif (otherLiteral == 0) return UNKNOWN; // We'll just pretend this works\n\t\t\treturn of(value.getAsInt() % otherLiteral);\n\t\t}\n\t\tif (other.isEqualTo(1)) return VAL_0;\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue shl(@Nonnull IntValue other) {\n\t\tOptionalInt value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsInt() << otherValue.getAsInt());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue shr(@Nonnull IntValue other) {\n\t\tOptionalInt value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsInt() >> otherValue.getAsInt());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue ushr(@Nonnull IntValue other) {\n\t\tOptionalInt value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsInt() >>> otherValue.getAsInt());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue negate() {\n\t\tOptionalInt value = value();\n\t\tif (value.isPresent()) return of(-value.getAsInt());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue castByte() {\n\t\tOptionalInt value = value();\n\t\tif (value.isPresent()) return of((byte) value.getAsInt());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue castChar() {\n\t\tOptionalInt value = value();\n\t\tif (value.isPresent()) return of((char) value.getAsInt());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue castShort() {\n\t\tOptionalInt value = value();\n\t\tif (value.isPresent()) return of((short) value.getAsInt());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault FloatValue castFloat() {\n\t\tOptionalInt value = value();\n\t\tif (value.isPresent()) return FloatValue.of(value.getAsInt());\n\t\treturn FloatValue.UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault DoubleValue castDouble() {\n\t\tOptionalInt value = value();\n\t\tif (value.isPresent()) return DoubleValue.of(value.getAsInt());\n\t\treturn DoubleValue.UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue castLong() {\n\t\tOptionalInt value = value();\n\t\tif (value.isPresent()) return LongValue.of(value.getAsInt());\n\t\treturn LongValue.UNKNOWN;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/LongValue.java",
    "content": "package software.coley.recaf.util.analysis.value;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.value.impl.LongValueImpl;\n\nimport java.util.OptionalInt;\nimport java.util.OptionalLong;\n\n/**\n * Value capable of recording exact long content.\n *\n * @author Matt Coley\n */\npublic non-sealed interface LongValue extends ReValue {\n\tLongValue UNKNOWN = new LongValueImpl();\n\tLongValue VAL_MAX = new LongValueImpl(Long.MAX_VALUE);\n\tLongValue VAL_MIN = new LongValueImpl(Long.MIN_VALUE);\n\tLongValue VAL_M1 = new LongValueImpl(-1);\n\tLongValue VAL_0 = new LongValueImpl(0);\n\tLongValue VAL_1 = new LongValueImpl(1);\n\n\t/**\n\t * @param value\n\t * \t\tLong value to hold.\n\t *\n\t * @return Long value holding the exact content.\n\t */\n\t@Nonnull\n\tstatic LongValue of(long value) {\n\t\tif (value == 0) return VAL_0;\n\t\telse if (value == 1) return VAL_1;\n\t\telse if (value == -1) return VAL_M1;\n\t\telse if (value == Long.MAX_VALUE) return VAL_MAX;\n\t\telse if (value == Long.MIN_VALUE) return VAL_MIN;\n\t\treturn new LongValueImpl(value);\n\t}\n\n\t/**\n\t * @return Long content of value. Empty if {@link #hasKnownValue() not known}.\n\t */\n\t@Nonnull\n\tOptionalLong value();\n\n\t@Override\n\tdefault boolean hasKnownValue() {\n\t\treturn value().isPresent();\n\t}\n\n\t@Nonnull\n\t@Override\n\tdefault Type type() {\n\t\treturn Type.LONG_TYPE;\n\t}\n\n\t@Override\n\tdefault int getSize() {\n\t\treturn 2;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is equal to the given value.\n\t */\n\tdefault boolean isEqualTo(long value) {\n\t\treturn value().isPresent() && value().getAsLong() == value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is less than the given value.\n\t */\n\tdefault boolean isLessThan(long value) {\n\t\treturn value().isPresent() && value().getAsLong() < value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is less than or equal to the given value.\n\t */\n\tdefault boolean isLessThanOrEqual(long value) {\n\t\treturn value().isPresent() && value().getAsLong() <= value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is greater than the given value.\n\t */\n\tdefault boolean isGreaterThan(long value) {\n\t\treturn value().isPresent() && value().getAsLong() > value;\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is greater than or equal to the given value.\n\t */\n\tdefault boolean isGreaterThanOrEqual(long value) {\n\t\treturn value().isPresent() && value().getAsLong() >= value;\n\t}\n\n\t@Nonnull\n\tdefault LongValue add(@Nonnull LongValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalLong otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsLong() + otherValue.getAsLong());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue sub(@Nonnull LongValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalLong otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsLong() - otherValue.getAsLong());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue mul(@Nonnull LongValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalLong otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsLong() * otherValue.getAsLong());\n\t\tif (isEqualTo(0) || other.isEqualTo(0)) return VAL_0;\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue div(@Nonnull LongValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalLong otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) {\n\t\t\tlong otherLiteral = otherValue.getAsLong();\n\t\t\tif (otherLiteral == 0) return UNKNOWN; // We'll just pretend this works\n\t\t\treturn of(value.getAsLong() / otherLiteral);\n\t\t}\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue and(@Nonnull LongValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalLong otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsLong() & otherValue.getAsLong());\n\t\tif (isEqualTo(0) || other.isEqualTo(0)) return VAL_0;\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue or(@Nonnull LongValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalLong otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsLong() | otherValue.getAsLong());\n\t\tif (isEqualTo(-1) || other.isEqualTo(-1)) return VAL_M1;\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue xor(@Nonnull LongValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalLong otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsLong() ^ otherValue.getAsLong());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue cmp(@Nonnull LongValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalLong otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent())\n\t\t\treturn IntValue.of(Long.compare(value.getAsLong(), otherValue.getAsLong()));\n\t\treturn IntValue.UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue rem(@Nonnull LongValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalLong otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) {\n\t\t\tlong otherLiteral = otherValue.getAsLong();\n\t\t\tif (otherLiteral == 0) return UNKNOWN; // We'll just pretend this works\n\t\t\treturn of(value.getAsLong() % otherLiteral);\n\t\t}\n\t\tif (other.isEqualTo(1)) return VAL_0;\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue shl(@Nonnull LongValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalLong otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsLong() << otherValue.getAsLong());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue shl(@Nonnull IntValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsLong() << otherValue.getAsInt());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue shr(@Nonnull LongValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalLong otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsLong() >> otherValue.getAsLong());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue shr(@Nonnull IntValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsLong() >> otherValue.getAsInt());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue ushr(@Nonnull LongValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalLong otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsLong() >>> otherValue.getAsLong());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue ushr(@Nonnull IntValue other) {\n\t\tOptionalLong value = value();\n\t\tOptionalInt otherValue = other.value();\n\t\tif (value.isPresent() && otherValue.isPresent()) return of(value.getAsLong() >>> otherValue.getAsInt());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue negate() {\n\t\tOptionalLong value = value();\n\t\tif (value.isPresent()) return of(-value.getAsLong());\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault LongValue add(long incr) {\n\t\tOptionalLong value = value();\n\t\tif (value.isPresent()) return of(value.getAsLong() + incr);\n\t\treturn UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault IntValue castInt() {\n\t\tOptionalLong value = value();\n\t\tif (value.isPresent()) return IntValue.of((int) value.getAsLong());\n\t\treturn IntValue.UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault FloatValue castFloat() {\n\t\tOptionalLong value = value();\n\t\tif (value.isPresent()) return FloatValue.of(value.getAsLong());\n\t\treturn FloatValue.UNKNOWN;\n\t}\n\n\t@Nonnull\n\tdefault DoubleValue castDouble() {\n\t\tOptionalLong value = value();\n\t\tif (value.isPresent()) return DoubleValue.of(value.getAsLong());\n\t\treturn DoubleValue.UNKNOWN;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/ObjectValue.java",
    "content": "package software.coley.recaf.util.analysis.value;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.value.impl.ObjectValueImpl;\nimport software.coley.recaf.util.analysis.value.impl.StringValueImpl;\n\n/**\n * Value capable of recording exact content of certain object types.\n * <table>\n *     <tr><th>Content</th><th>Value usage</th></tr>\n *     <tr><td>{@code null}</td><td>{@link #VAL_OBJECT_NULL}</td></tr>\n *     <tr><td>{@code Any.class}</td><td>{@link #VAL_CLASS}</td></tr>\n *     <tr><td>{@code Method::reference}</td><td>{@link #VAL_METHOD_HANDLE}</td></tr>\n *     <tr><td>{@code \"strings\"}</td><td>{@link #string(String)} or {@link #string(Nullness)}</td></tr>\n * </table>\n *\n * @author Matt Coley\n */\npublic non-sealed interface ObjectValue extends ReValue {\n\tObjectValue VAL_OBJECT = new ObjectValueImpl(Types.OBJECT_TYPE, Nullness.NOT_NULL);\n\tObjectValue VAL_OBJECT_NULL = new ObjectValueImpl(Types.OBJECT_TYPE, Nullness.NULL);\n\tObjectValue VAL_OBJECT_MAYBE_NULL = new ObjectValueImpl(Types.OBJECT_TYPE, Nullness.UNKNOWN);\n\tObjectValue VAL_CLASS = new ObjectValueImpl(Types.CLASS_TYPE, Nullness.NOT_NULL);\n\tObjectValue VAL_CLASS_NULL = new ObjectValueImpl(Types.CLASS_TYPE, Nullness.NULL);\n\tObjectValue VAL_CLASS_MAYBE_NULL = new ObjectValueImpl(Types.CLASS_TYPE, Nullness.NULL);\n\tObjectValue VAL_METHOD_TYPE = new ObjectValueImpl(Type.getObjectType(\"java/lang/invoke/MethodType\"), Nullness.NOT_NULL);\n\tObjectValue VAL_METHOD_HANDLE = new ObjectValueImpl(Type.getObjectType(\"java/lang/invoke/MethodType\"), Nullness.NOT_NULL);\n\tObjectValue VAL_JSR = new ObjectValueImpl(Type.VOID_TYPE, Nullness.NOT_NULL);\n\n\t/**\n\t * @param text\n\t * \t\tExact string content.\n\t *\n\t * @return String value holding the exact content.\n\t */\n\t@Nonnull\n\tstatic StringValue string(@Nullable String text) {\n\t\tif (text == null) return StringValue.VAL_STRING_NULL;\n\t\tif (text.isEmpty()) return StringValue.VAL_STRING_EMPTY;\n\t\tif (text.equals(\" \")) return StringValue.VAL_STRING_SPACE;\n\t\treturn new StringValueImpl(text);\n\t}\n\n\t/**\n\t * @param nullness\n\t * \t\tNull state of the {@link Class}.\n\t *\n\t * @return Object value for a class literal of the given nullness.\n\t */\n\t@Nonnull\n\tstatic ObjectValue clazz(@Nonnull Nullness nullness) {\n\t\treturn switch (nullness) {\n\t\t\tcase NULL -> VAL_CLASS_NULL;\n\t\t\tcase NOT_NULL -> VAL_CLASS;\n\t\t\tcase UNKNOWN -> VAL_CLASS_MAYBE_NULL;\n\t\t};\n\t}\n\n\t/**\n\t * @param nullness\n\t * \t\tNull state of the string.\n\t *\n\t * @return String value of the given nullness.\n\t */\n\t@Nonnull\n\tstatic StringValue string(@Nonnull Nullness nullness) {\n\t\treturn switch (nullness) {\n\t\t\tcase NULL -> StringValue.VAL_STRING_NULL;\n\t\t\tcase NOT_NULL -> StringValue.VAL_STRING;\n\t\t\tcase UNKNOWN -> StringValue.VAL_STRING_MAYBE_NULL;\n\t\t};\n\t}\n\n\t/**\n\t * @param nullness\n\t * \t\tNull state of the string.\n\t *\n\t * @return Object value of the given nullness with a type of {@link Object}.\n\t */\n\t@Nonnull\n\tstatic ObjectValue object(@Nonnull Nullness nullness) {\n\t\treturn switch (nullness) {\n\t\t\tcase NULL -> VAL_OBJECT_NULL;\n\t\t\tcase NOT_NULL -> VAL_OBJECT;\n\t\t\tcase UNKNOWN -> VAL_OBJECT_MAYBE_NULL;\n\t\t};\n\t}\n\n\t@Nonnull\n\tstatic ObjectValue object(@Nonnull Type type, @Nonnull Nullness nullness) {\n\t\tif (Types.OBJECT_TYPE.equals(type))\n\t\t\treturn object(nullness);\n\t\tif (Types.STRING_TYPE.equals(type))\n\t\t\treturn string(nullness);\n\t\tif (Types.CLASS_TYPE.equals(type))\n\t\t\treturn clazz(nullness);\n\t\treturn new ObjectValueImpl(type, nullness);\n\t}\n\n\t@Nonnull\n\t@Override\n\tType type();\n\n\t/**\n\t * @return Null state of this value.\n\t */\n\t@Nonnull\n\tNullness nullness();\n\n\t/**\n\t * @return {@code true} when this value is known to be {@code null}.\n\t */\n\tdefault boolean isNull() {\n\t\treturn nullness() == Nullness.NULL;\n\t}\n\n\t/**\n\t * @return {@code true} when this value is known to be <b>not</b> {@code null}.\n\t */\n\tdefault boolean isNotNull() {\n\t\treturn nullness() == Nullness.NOT_NULL;\n\t}\n\n\t@Override\n\tdefault int getSize() {\n\t\treturn 1;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/ReValue.java",
    "content": "package software.coley.recaf.util.analysis.value;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ConstantDynamic;\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.LdcInsnNode;\nimport org.objectweb.asm.tree.analysis.Value;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.ReInterpreter;\n\n/**\n * Base type of value capable of recording exact content\n * when all control flow paths converge on a single use case.\n *\n * @author Matt Coley\n */\npublic sealed interface ReValue extends Value permits IntValue, FloatValue, DoubleValue, LongValue, ObjectValue, UninitializedValue {\n\t/**\n\t * @param value\n\t * \t\tASM constant value in a LDC instruction.\n\t *\n\t * @return A {@link ReValue} wrapper of the given input.\n\t *\n\t * @throws IllegalValueException\n\t * \t\tWhen the value could not be mapped to a {@link ReValue}.\n\t * @see LdcInsnNode#cst Possible values\n\t */\n\t@Nonnull\n\tstatic ReValue ofConstant(@Nullable Object value) throws IllegalValueException {\n\t\treturn switch (value) {\n\t\t\tcase Character c -> IntValue.of(c);\n\t\t\tcase Byte b -> IntValue.of(b);\n\t\t\tcase Short s -> IntValue.of(s);\n\t\t\tcase Integer i -> IntValue.of(i);\n\t\t\tcase Float f -> FloatValue.of(f);\n\t\t\tcase Long l -> LongValue.of(l);\n\t\t\tcase Double d -> DoubleValue.of(d);\n\t\t\tcase String s -> ObjectValue.string(s);\n\t\t\tcase Handle handle -> ObjectValue.VAL_METHOD_HANDLE;\n\t\t\tcase Type type -> {\n\t\t\t\tint sort = type.getSort();\n\t\t\t\tif (sort == Type.OBJECT || sort == Type.ARRAY)\n\t\t\t\t\tyield ObjectValue.VAL_CLASS;\n\t\t\t\tif (sort == Type.METHOD)\n\t\t\t\t\tyield ObjectValue.VAL_METHOD_TYPE;\n\t\t\t\tthrow new IllegalValueException(\"Illegal LDC value \" + value);\n\t\t\t}\n\t\t\tcase ConstantDynamic constantDynamic -> {\n\t\t\t\tType dynamicType = Type.getType(constantDynamic.getDescriptor());\n\t\t\t\tReValue dynamicValue = ofType(dynamicType, Nullness.NOT_NULL);\n\t\t\t\tif (dynamicValue == null)\n\t\t\t\t\tthrow new IllegalValueException(\"Illegal LDC dynamic value descriptor \" + dynamicType);\n\t\t\t\tyield dynamicValue;\n\t\t\t}\n\t\t\tcase null, default -> throw new IllegalValueException(\"Illegal LDC value \" + value);\n\t\t};\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tType of value to create a new generic value for.\n\t * @param nullness\n\t * \t\tNullability state of the value.\n\t *\n\t * @return Value of the given type.\n\t *\n\t * @throws IllegalValueException\n\t * \t\tWhen the type could not be mapped to a {@link ReValue}.\n\t */\n\t@Nullable\n\tstatic ReValue ofType(@Nullable Type type, @Nonnull Nullness nullness) throws IllegalValueException {\n\t\tif (type == null)\n\t\t\treturn UninitializedValue.UNINITIALIZED_VALUE;\n\t\treturn switch (type.getSort()) {\n\t\t\tcase Type.VOID -> null;\n\t\t\tcase Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT -> IntValue.UNKNOWN;\n\t\t\tcase Type.FLOAT -> FloatValue.UNKNOWN;\n\t\t\tcase Type.LONG -> LongValue.UNKNOWN;\n\t\t\tcase Type.DOUBLE -> DoubleValue.UNKNOWN;\n\t\t\tcase Type.ARRAY -> ArrayValue.of(type, nullness);\n\t\t\tcase Type.OBJECT -> ObjectValue.object(type, nullness);\n\t\t\tdefault -> throw new IllegalValueException(\"Invalid type for new value: \" + type);\n\t\t};\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tType of value to create a new generic value for.\n\t *\n\t * @return Value of the given type with the default value <i>({@code 0} for primitives, {@code null} for objects/arrays)</i>.\n\t *\n\t * @throws IllegalValueException\n\t * \t\tWhen the type could not be mapped to a {@link ReValue}.\n\t */\n\t@Nonnull\n\tstatic ReValue ofTypeDefaultValue(@Nonnull Type type) throws IllegalValueException {\n\t\treturn switch (type.getSort()) {\n\t\t\tcase Type.VOID -> throw new IllegalValueException(\"Cannot create default value for 'void' type\");\n\t\t\tcase Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT -> IntValue.VAL_0;\n\t\t\tcase Type.FLOAT -> FloatValue.VAL_0;\n\t\t\tcase Type.LONG -> LongValue.VAL_0;\n\t\t\tcase Type.DOUBLE -> DoubleValue.VAL_0;\n\t\t\tcase Type.ARRAY -> ArrayValue.of(type, Nullness.NULL);\n\t\t\tcase Type.OBJECT -> ObjectValue.object(type, Nullness.NULL);\n\t\t\tdefault -> throw new IllegalValueException(\"Invalid type for new default value: \" + type);\n\t\t};\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tSome value that may be a primitive.\n\t * @param v\n\t * \t\tPrimitive value to compare against.\n\t *\n\t * @return {@code true} when the value is a primitive,\n\t * and the primitive value is equal to the given literal value.\n\t */\n\tstatic boolean isPrimitiveEqualTo(@Nonnull ReValue value, int v) {\n\t\treturn switch (value) {\n\t\t\tcase DoubleValue x -> x.isEqualTo(v);\n\t\t\tcase FloatValue x -> x.isEqualTo(v);\n\t\t\tcase IntValue x -> x.isEqualTo(v);\n\t\t\tcase LongValue x -> x.isEqualTo(v);\n\t\t\tcase ObjectValue _, UninitializedValue _ -> false;\n\t\t};\n\t}\n\n\t/**\n\t * @return {@code true} when the exact content is known.\n\t * {@code null} does not count if this is an {@link ObjectValue}\n\t * and for that you should use {@link ObjectValue#isNull()}.\n\t */\n\tboolean hasKnownValue();\n\n\t/**\n\t * @return Type of value content.\n\t */\n\t@Nullable\n\tType type();\n\n\t/**\n\t * Called from {@link ReInterpreter#merge(ReValue, ReValue)} only when our value's type is a looser type than the given other type.\n\t * For instance, if we are a {@code ArrayList} we could see {@code List} as the {@code other} value, but never the other way around.\n\t *\n\t * @param other\n\t * \t\tOther value to merge with.\n\t * \t\tIt should be assumed that this other value's type is the same as, or a child type of our {@link #type()}.\n\t *\n\t * @return Merged value.\n\t *\n\t * @throws IllegalValueException\n\t * \t\tWhen the given value cannot be merged with this one.\n\t */\n\t@Nonnull\n\tReValue mergeWith(@Nonnull ReValue other) throws IllegalValueException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/StringValue.java",
    "content": "package software.coley.recaf.util.analysis.value;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.value.impl.StringValueImpl;\n\nimport java.util.Optional;\n\n/**\n * Value capable of recording exact content of strings.\n *\n * @author Matt Coley\n */\npublic interface StringValue extends ObjectValue {\n\tStringValue VAL_STRING = new StringValueImpl(Nullness.NOT_NULL);\n\tStringValue VAL_STRING_MAYBE_NULL = new StringValueImpl(Nullness.UNKNOWN);\n\tStringValue VAL_STRING_NULL = new StringValueImpl(Nullness.NULL);\n\tStringValue VAL_STRING_EMPTY = new StringValueImpl(\"\");\n\tStringValue VAL_STRING_SPACE = new StringValueImpl(\" \");\n\n\t/**\n\t * @return String content of value. Empty if {@link #hasKnownValue() not known}.\n\t */\n\t@Nonnull\n\tOptional<String> getText();\n\n\t/**\n\t * @param otherValue\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is equal to the given value.\n\t */\n\tdefault boolean isEqualTo(@Nullable String otherValue) {\n\t\tif (getText().isPresent())\n\t\t\treturn false;\n\t\tif (otherValue == null && getText().isEmpty())\n\t\t\treturn true;\n\t\treturn getText().get().equals(otherValue);\n\t}\n\n\t/**\n\t * @param otherValue\n\t * \t\tValue to check against.\n\t *\n\t * @return {@code true} when the known value is not equal to the given value.\n\t */\n\tdefault boolean isNotEqualTo(@Nullable String otherValue) {\n\t\tif (getText().isPresent())\n\t\t\treturn false;\n\t\tif (otherValue == null && getText().isEmpty())\n\t\t\treturn false;\n\t\treturn !getText().get().equals(otherValue);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/UninitializedValue.java",
    "content": "package software.coley.recaf.util.analysis.value;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.value.impl.UninitializedValueImpl;\n\n/**\n * Value representing a slot that has not been initialized or used yet.\n *\n * @author Matt Coley\n */\npublic non-sealed interface UninitializedValue extends ReValue {\n\tUninitializedValue UNINITIALIZED_VALUE = UninitializedValueImpl.UNINITIALIZED_VALUE;\n\n\t@Override\n\tdefault boolean hasKnownValue() {\n\t\treturn false;\n\t}\n\n\t@Nullable\n\t@Override\n\tdefault Type type() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tdefault int getSize() {\n\t\treturn 1;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/ArrayValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.collections.func.UncheckedFunction;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.value.ArrayValue;\nimport software.coley.recaf.util.analysis.value.IllegalValueException;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.UninitializedValue;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.OptionalInt;\nimport java.util.function.IntFunction;\n\n/**\n * Array value holder implementation.\n *\n * @author Matt Coley\n */\n@SuppressWarnings(\"OptionalUsedAsFieldOrParameterType\")\npublic class ArrayValueImpl implements ArrayValue {\n\tprivate final Type type;\n\tprivate final Nullness nullness;\n\tprivate final OptionalInt length;\n\tprivate final List<ReValue> contents;\n\n\t/**\n\t * New array value where we don't know the exact length.\n\t *\n\t * @param type\n\t * \t\tArray type.\n\t * @param nullness\n\t * \t\tArray reference nullability.\n\t */\n\tpublic ArrayValueImpl(@Nonnull Type type, @Nonnull Nullness nullness) {\n\t\tif (type.getSort() != Type.ARRAY) throw new IllegalStateException(\"Non-array type passed to array-value\");\n\t\tthis.type = type;\n\t\tthis.nullness = nullness;\n\t\tthis.length = OptionalInt.empty();\n\t\tthis.contents = null;\n\t}\n\n\t/**\n\t * New array value where we know the exact length.\n\t * <p>\n\t * Array contents are filled with default {@link ReValue} instances for the element type.\n\t *\n\t * @param type\n\t * \t\tArray type.\n\t * @param nullness\n\t * \t\tArray reference nullability.\n\t * @param length\n\t * \t\tLength of array.\n\t */\n\tpublic ArrayValueImpl(@Nonnull Type type, @Nonnull Nullness nullness, int length) {\n\t\tthis(type, nullness, length, new ConstProvider(getSubTypedValue(type, ReValue::ofTypeDefaultValue)));\n\t}\n\n\t/**\n\t * New array where we know the exact length and want to pre-populate values.\n\t * <p>\n\t * Array contents are filled with what is provided by the index-value function.\n\t *\n\t * @param type\n\t * \t\tArray type.\n\t * @param nullness\n\t * \t\tArray reference nullability.\n\t * @param length\n\t * \t\tLength of array.\n\t * @param indexValueFunction\n\t * \t\tArray index to value function.\n\t */\n\tpublic ArrayValueImpl(@Nonnull Type type, @Nonnull Nullness nullness, int length, @Nonnull IntFunction<ReValue> indexValueFunction) {\n\t\tif (type.getSort() != Type.ARRAY)\n\t\t\tthrow new IllegalStateException(\"Non-array type passed to array-value\");\n\t\tthis.type = type;\n\t\tthis.nullness = nullness;\n\t\tif (length >= 0) {\n\t\t\tthis.length = OptionalInt.of(length);\n\t\t\tthis.contents = new ArrayList<>(length);\n\t\t\tfor (int i = 0; i < length; i++)\n\t\t\t\tcontents.add(indexValueFunction.apply(i));\n\t\t} else {\n\t\t\t// Array length is negative. So we have two possibilities:\n\t\t\t// - We have a bug in our stack evaluation\n\t\t\t// - We are looking at obfuscated code intentionally trying to throw exceptions\n\t\t\tthis.length = OptionalInt.empty();\n\t\t\tthis.contents = null;\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\t@SuppressWarnings(\"OptionalGetWithoutIsPresent\")\n\tpublic ArrayValue setValue(int index, @Nonnull ReValue value) {\n\t\tif (hasKnownValue()) {\n\t\t\tArrayValueImpl copy = new ArrayValueImpl(type, nullness, length.getAsInt());\n\t\t\tfor (int i = 0; i < contents.size(); i++) {\n\t\t\t\tReValue valueAtIndex = i == index ? value : contents.get(i);\n\t\t\t\tcopy.contents.set(i, valueAtIndex);\n\t\t\t}\n\t\t\treturn copy;\n\t\t}\n\n\t\t// Values not known, so no need to create a copy.\n\t\treturn this;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ArrayValue updatedCopyIfContained(@Nonnull ReValue originalValue, @Nonnull ReValue updatedValue) {\n\t\tif (hasKnownValue()) {\n\t\t\tfor (int i = 0; i < contents.size(); i++) {\n\t\t\t\tReValue content = contents.get(i);\n\n\t\t\t\t// Case 1: The value is a direct entry in this array.\n\t\t\t\tif (content == originalValue) {\n\t\t\t\t\treturn setValue(i, updatedValue);\n\t\t\t\t}\n\n\t\t\t\t// Case 2: This array is multidimensional and the value is in a nested sub array.\n\t\t\t\telse if (content instanceof ArrayValue subArray) {\n\t\t\t\t\tArrayValue updatedSubArray = subArray.updatedCopyIfContained(originalValue, updatedValue);\n\t\t\t\t\tif (subArray != updatedSubArray)\n\t\t\t\t\t\treturn setValue(i, updatedSubArray);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Not contained, no changes needed.\n\t\treturn this;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Type type() {\n\t\treturn type;\n\t}\n\n\t@Override\n\tpublic boolean hasKnownValue() {\n\t\treturn nullness == Nullness.NOT_NULL\n\t\t\t\t&& length.isPresent()\n\t\t\t\t&& contents != null && contents.stream().allMatch(v -> v.hasKnownValue() || v instanceof ObjectValue ro && ro.isNull());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ReValue mergeWith(@Nonnull ReValue other) throws IllegalValueException {\n\t\tif (other == UninitializedValue.UNINITIALIZED_VALUE)\n\t\t\treturn other;\n\t\telse if (other instanceof ArrayValue otherArray) {\n\t\t\tif (getFirstDimensionLength().isPresent() && otherArray.getFirstDimensionLength().isPresent()\n\t\t\t\t\t&& nullness() == otherArray.nullness()\n\t\t\t\t\t&& dimensions() == otherArray.dimensions()) {\n\t\t\t\tint length = getFirstDimensionLength().getAsInt();\n\t\t\t\tArrayValueImpl merged = new ArrayValueImpl(type, nullness, length, i -> {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tReValue value = Objects.requireNonNull(otherArray.getValue(i));\n\t\t\t\t\t\treturn contents.get(i).mergeWith(value);\n\t\t\t\t\t} catch (IllegalValueException ex) {\n\t\t\t\t\t\tthrow new IllegalStateException(\"Failed merging array contents\", ex);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn ArrayValue.of(type, nullness.mergeWith(otherArray.nullness()));\n\t\t} else if (other instanceof ObjectValue otherObject) {\n\t\t\treturn ObjectValue.VAL_OBJECT;\n\t\t}\n\t\tthrow new IllegalValueException(\"Cannot merge with: \" + other);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Nullness nullness() {\n\t\treturn nullness;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic OptionalInt getFirstDimensionLength() {\n\t\treturn length;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic ReValue getValue(int index) {\n\t\tif (index < 0 || index >= length.orElse(0))\n\t\t\treturn getSubTypedValue(type, t -> ReValue.ofType(t, Nullness.UNKNOWN));\n\t\treturn contents.get(index);\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (!(o instanceof ArrayValueImpl that)) return false;\n\n\t\tif (!type.equals(that.type)) return false;\n\t\tif (nullness != that.nullness) return false;\n\t\tif (!length.equals(that.length)) return false;\n\t\treturn Objects.equals(contents, that.contents);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = type.hashCode();\n\t\tresult = 31 * result + nullness.hashCode();\n\t\tresult = 31 * result + length.hashCode();\n\t\tresult = 31 * result + (contents != null ? contents.hashCode() : 0);\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn type().getInternalName();\n\t}\n\n\n\t@Nonnull\n\tprivate static ReValue getSubTypedValue(@Nonnull Type arrayType, @Nonnull UncheckedFunction<Type, ReValue> function) {\n\t\ttry {\n\t\t\tif (arrayType.getSort() != Type.ARRAY)\n\t\t\t\tthrow new IllegalStateException(\"ArrayValue had non-array type\");\n\t\t\treturn Objects.requireNonNull(function.apply(Types.undimension(arrayType)));\n\t\t} catch (Exception ex) {\n\t\t\tthrow new IllegalStateException(\"Failed providing fallback value of array\", ex);\n\t\t}\n\t}\n\n\tprivate record ConstProvider(@Nonnull ReValue defaultValue) implements IntFunction<ReValue> {\n\t\t@Override\n\t\tpublic ReValue apply(int value) {\n\t\t\treturn defaultValue;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/BoxedBooleanValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.Nullness;\n\n/**\n * Boxed {@link Boolean} value holder implementation.\n *\n * @author Matt Coley\n */\npublic class BoxedBooleanValueImpl extends ObjectValueBoxImpl<Boolean> {\n\tprivate static final Type TYPE = Type.getType(Boolean.class);\n\n\tpublic BoxedBooleanValueImpl(@Nonnull Nullness nullness) {\n\t\tsuper(TYPE, nullness);\n\t}\n\n\tpublic BoxedBooleanValueImpl(@Nullable Boolean value) {\n\t\tsuper(TYPE, value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Boolean> wrap(@Nullable Boolean value) {\n\t\treturn new BoxedBooleanValueImpl(value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Boolean> wrapUnknown(@Nonnull Nullness nullness) {\n\t\treturn new BoxedBooleanValueImpl(nullness);\n\t}\n\n\t@Override\n\tpublic int getSize() {\n\t\t// Deconflict between object-v-primitive value interfaces\n\t\treturn 1;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/BoxedByteValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.Nullness;\n\n/**\n * Boxed {@link Byte} value holder implementation.\n *\n * @author Matt Coley\n */\npublic class BoxedByteValueImpl extends ObjectValueBoxImpl<Byte> {\n\tprivate static final Type TYPE = Type.getType(Byte.class);\n\n\tpublic BoxedByteValueImpl(@Nonnull Nullness nullness) {\n\t\tsuper(TYPE, nullness);\n\t}\n\n\tpublic BoxedByteValueImpl(@Nullable Byte value) {\n\t\tsuper(TYPE, value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Byte> wrap(@Nullable Byte value) {\n\t\treturn new BoxedByteValueImpl(value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Byte> wrapUnknown(@Nonnull Nullness nullness) {\n\t\treturn new BoxedByteValueImpl(nullness);\n\t}\n\n\t@Override\n\tpublic int getSize() {\n\t\t// Deconflict between object-v-primitive value interfaces\n\t\treturn 1;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/BoxedCharacterValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.Nullness;\n\n/**\n * Boxed {@link Character} value holder implementation.\n *\n * @author Matt Coley\n */\npublic class BoxedCharacterValueImpl extends ObjectValueBoxImpl<Character> {\n\tprivate static final Type TYPE = Type.getType(Character.class);\n\n\tpublic BoxedCharacterValueImpl(@Nonnull Nullness nullness) {\n\t\tsuper(TYPE, nullness);\n\t}\n\n\tpublic BoxedCharacterValueImpl(@Nullable Character value) {\n\t\tsuper(TYPE, value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Character> wrap(@Nullable Character value) {\n\t\treturn new BoxedCharacterValueImpl(value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Character> wrapUnknown(@Nonnull Nullness nullness) {\n\t\treturn new BoxedCharacterValueImpl(nullness);\n\t}\n\n\t@Override\n\tpublic int getSize() {\n\t\t// Deconflict between object-v-primitive value interfaces\n\t\treturn 1;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/BoxedDoubleValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.Nullness;\n\n/**\n * Boxed {@link Double} value holder implementation.\n *\n * @author Matt Coley\n */\npublic class BoxedDoubleValueImpl extends ObjectValueBoxImpl<Double> {\n\tprivate static final Type TYPE = Type.getType(Double.class);\n\n\tpublic BoxedDoubleValueImpl(@Nonnull Nullness nullness) {\n\t\tsuper(TYPE, nullness);\n\t}\n\n\tpublic BoxedDoubleValueImpl(@Nullable Double value) {\n\t\tsuper(TYPE, value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Double> wrap(@Nullable Double value) {\n\t\treturn new BoxedDoubleValueImpl(value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Double> wrapUnknown(@Nonnull Nullness nullness) {\n\t\treturn new BoxedDoubleValueImpl(nullness);\n\t}\n\n\t@Override\n\tpublic int getSize() {\n\t\t// Deconflict between object-v-primitive value interfaces\n\t\treturn 1;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/BoxedFloatValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.Nullness;\n\n/**\n * Boxed {@link Float} value holder implementation.\n *\n * @author Matt Coley\n */\npublic class BoxedFloatValueImpl extends ObjectValueBoxImpl<Float> {\n\tprivate static final Type TYPE = Type.getType(Float.class);\n\n\tpublic BoxedFloatValueImpl(@Nonnull Nullness nullness) {\n\t\tsuper(TYPE, nullness);\n\t}\n\n\tpublic BoxedFloatValueImpl(@Nullable Float value) {\n\t\tsuper(TYPE, value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Float> wrap(@Nullable Float value) {\n\t\treturn new BoxedFloatValueImpl(value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Float> wrapUnknown(@Nonnull Nullness nullness) {\n\t\treturn new BoxedFloatValueImpl(nullness);\n\t}\n\n\t@Override\n\tpublic int getSize() {\n\t\t// Deconflict between object-v-primitive value interfaces\n\t\treturn 1;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/BoxedIntegerValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.Nullness;\n\n/**\n * Boxed {@link Integer} value holder implementation.\n *\n * @author Matt Coley\n */\npublic class BoxedIntegerValueImpl extends ObjectValueBoxImpl<Integer> {\n\tprivate static final Type TYPE = Type.getType(Integer.class);\n\n\tpublic BoxedIntegerValueImpl(@Nonnull Nullness nullness) {\n\t\tsuper(TYPE, nullness);\n\t}\n\n\tpublic BoxedIntegerValueImpl(@Nullable Integer value) {\n\t\tsuper(TYPE, value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Integer> wrap(@Nullable Integer value) {\n\t\treturn new BoxedIntegerValueImpl(value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Integer> wrapUnknown(@Nonnull Nullness nullness) {\n\t\treturn new BoxedIntegerValueImpl(nullness);\n\t}\n\n\t@Override\n\tpublic int getSize() {\n\t\t// Deconflict between object-v-primitive value interfaces\n\t\treturn 1;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/BoxedLongValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.Nullness;\n\n/**\n * Boxed {@link Long} value holder implementation.\n *\n * @author Matt Coley\n */\npublic class BoxedLongValueImpl extends ObjectValueBoxImpl<Long> {\n\tprivate static final Type TYPE = Type.getType(Long.class);\n\n\tpublic BoxedLongValueImpl(@Nonnull Nullness nullness) {\n\t\tsuper(TYPE, nullness);\n\t}\n\n\tpublic BoxedLongValueImpl(@Nullable Long value) {\n\t\tsuper(TYPE, value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Long> wrap(@Nullable Long value) {\n\t\treturn new BoxedLongValueImpl(value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Long> wrapUnknown(@Nonnull Nullness nullness) {\n\t\treturn new BoxedLongValueImpl(nullness);\n\t}\n\n\t@Override\n\tpublic int getSize() {\n\t\t// Deconflict between object-v-primitive value interfaces\n\t\treturn 1;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/BoxedShortValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.Nullness;\n\n/**\n * Boxed {@link Short} value holder implementation.\n *\n * @author Matt Coley\n */\npublic class BoxedShortValueImpl extends ObjectValueBoxImpl<Short> {\n\tprivate static final Type TYPE = Type.getType(Short.class);\n\n\tpublic BoxedShortValueImpl(@Nonnull Nullness nullness) {\n\t\tsuper(TYPE, nullness);\n\t}\n\n\tpublic BoxedShortValueImpl(@Nullable Short value) {\n\t\tsuper(TYPE, value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Short> wrap(@Nullable Short value) {\n\t\treturn new BoxedShortValueImpl(value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<Short> wrapUnknown(@Nonnull Nullness nullness) {\n\t\treturn new BoxedShortValueImpl(nullness);\n\t}\n\n\t@Override\n\tpublic int getSize() {\n\t\t// Deconflict between object-v-primitive value interfaces\n\t\treturn 1;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/DoubleValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.analysis.value.DoubleValue;\nimport software.coley.recaf.util.analysis.value.IllegalValueException;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.UninitializedValue;\n\nimport java.util.OptionalDouble;\n\n/**\n * Double value holder implementation.\n *\n * @author Matt Coley\n */\n@SuppressWarnings(\"OptionalUsedAsFieldOrParameterType\")\npublic class DoubleValueImpl implements DoubleValue {\n\tprivate final OptionalDouble value;\n\n\tpublic DoubleValueImpl(double value) {\n\t\tthis.value = OptionalDouble.of(value);\n\t}\n\n\tpublic DoubleValueImpl() {\n\t\tthis.value = OptionalDouble.empty();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic OptionalDouble value() {\n\t\treturn value;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tDoubleValueImpl other = (DoubleValueImpl) o;\n\n\t\treturn value.equals(other.value);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn value.hashCode();\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn type().getInternalName() + \":\" + (value.isPresent() ? value.getAsDouble() : \"?\");\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ReValue mergeWith(@Nonnull ReValue other) throws IllegalValueException {\n\t\tif (other == UninitializedValue.UNINITIALIZED_VALUE)\n\t\t\treturn other;\n\t\telse if (other instanceof DoubleValue otherDouble) {\n\t\t\tif (value().isPresent() && otherDouble.value().isPresent()) {\n\t\t\t\tdouble d = value().getAsDouble();\n\t\t\t\tdouble otherD = otherDouble.value().getAsDouble();\n\t\t\t\tif (d == otherD)\n\t\t\t\t\treturn DoubleValue.of(d);\n\t\t\t}\n\t\t\treturn DoubleValue.UNKNOWN;\n\t\t}\n\t\tthrow new IllegalValueException(\"Cannot merge with: \" + other);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/FloatValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.analysis.value.FloatValue;\nimport software.coley.recaf.util.analysis.value.IllegalValueException;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.UninitializedValue;\n\nimport java.util.OptionalDouble;\n\n/**\n * Float value holder implementation.\n *\n * @author Matt Coley\n */\n@SuppressWarnings(\"OptionalUsedAsFieldOrParameterType\")\npublic class FloatValueImpl implements FloatValue {\n\tprivate final OptionalDouble value;\n\n\tpublic FloatValueImpl(float value) {\n\t\tthis.value = OptionalDouble.of(value);\n\t}\n\n\tpublic FloatValueImpl(double value) {\n\t\tthis.value = OptionalDouble.of(value);\n\t}\n\n\tpublic FloatValueImpl() {\n\t\tthis.value = OptionalDouble.empty();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic OptionalDouble value() {\n\t\treturn value;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tFloatValueImpl other = (FloatValueImpl) o;\n\n\t\treturn value.equals(other.value);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn value.hashCode();\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn type().getInternalName() + \":\" + (value.isPresent() ? value.getAsDouble() : \"?\");\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ReValue mergeWith(@Nonnull ReValue other) throws IllegalValueException {\n\t\tif (other == UninitializedValue.UNINITIALIZED_VALUE)\n\t\t\treturn other;\n\t\telse if (other instanceof FloatValue otherFloat) {\n\t\t\tif (value().isPresent() && otherFloat.value().isPresent()) {\n\t\t\t\tdouble f = value().getAsDouble();\n\t\t\t\tdouble otherF = otherFloat.value().getAsDouble();\n\t\t\t\tif (f == otherF)\n\t\t\t\t\treturn FloatValue.of((float) f);\n\t\t\t}\n\t\t\treturn FloatValue.UNKNOWN;\n\t\t}\n\t\tthrow new IllegalValueException(\"Cannot merge with: \" + other);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/IntValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.analysis.value.IllegalValueException;\nimport software.coley.recaf.util.analysis.value.IntValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.UninitializedValue;\n\nimport java.util.OptionalInt;\n\n/**\n * Integer value holder implementation.\n *\n * @author Matt Coley\n */\n@SuppressWarnings(\"OptionalUsedAsFieldOrParameterType\")\npublic class IntValueImpl implements IntValue {\n\tprivate final OptionalInt value;\n\n\tpublic IntValueImpl() {\n\t\tthis.value = OptionalInt.empty();\n\t}\n\n\tpublic IntValueImpl(int value) {\n\t\tthis.value = OptionalInt.of(value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic OptionalInt value() {\n\t\treturn value;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tIntValueImpl other = (IntValueImpl) o;\n\n\t\treturn value.equals(other.value);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn value.hashCode();\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn type().getInternalName() + \":\" + (value.isPresent() ? value.getAsInt() : \"?\");\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ReValue mergeWith(@Nonnull ReValue other) throws IllegalValueException {\n\t\tif (other == UninitializedValue.UNINITIALIZED_VALUE)\n\t\t\treturn other;\n\t\telse if (other instanceof IntValue otherInt) {\n\t\t\tif (value().isPresent() && otherInt.value().isPresent()) {\n\t\t\t\tint i = value().getAsInt();\n\t\t\t\tint otherI = otherInt.value().getAsInt();\n\t\t\t\tif (i == otherI)\n\t\t\t\t\treturn IntValue.of(i);\n\t\t\t}\n\t\t\treturn IntValue.UNKNOWN;\n\t\t}\n\t\tthrow new IllegalValueException(\"Cannot merge with: \" + other);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/LongValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.analysis.value.IllegalValueException;\nimport software.coley.recaf.util.analysis.value.LongValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.UninitializedValue;\n\nimport java.util.OptionalLong;\n\n/**\n * Long value holder implementation.\n *\n * @author Matt Coley\n */\n@SuppressWarnings(\"OptionalUsedAsFieldOrParameterType\")\npublic class LongValueImpl implements LongValue {\n\tprivate final OptionalLong value;\n\n\tpublic LongValueImpl(long value) {\n\t\tthis.value = OptionalLong.of(value);\n\t}\n\n\tpublic LongValueImpl() {\n\t\tthis.value = OptionalLong.empty();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic OptionalLong value() {\n\t\treturn value;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tLongValueImpl other = (LongValueImpl) o;\n\n\t\treturn value.equals(other.value);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn value.hashCode();\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn type().getInternalName() + \":\" + (value.isPresent() ? value.getAsLong() : \"?\");\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ReValue mergeWith(@Nonnull ReValue other) throws IllegalValueException {\n\t\tif (other == UninitializedValue.UNINITIALIZED_VALUE)\n\t\t\treturn other;\n\t\telse if (other instanceof LongValue otherLong) {\n\t\t\tif (value().isPresent() && otherLong.value().isPresent()) {\n\t\t\t\tlong i = value().getAsLong();\n\t\t\t\tlong otherI = otherLong.value().getAsLong();\n\t\t\t\tif (i == otherI)\n\t\t\t\t\treturn LongValue.of(i);\n\t\t\t}\n\t\t\treturn LongValue.UNKNOWN;\n\t\t}\n\t\tthrow new IllegalValueException(\"Cannot merge with: \" + other);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/ObjectValueBoxImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.value.IllegalValueException;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.UninitializedValue;\n\nimport java.util.Objects;\nimport java.util.Optional;\n\n/**\n * Boxed object value holder implementation.\n *\n * @author Matt Coley\n */\n@SuppressWarnings(\"OptionalUsedAsFieldOrParameterType\")\npublic abstract class ObjectValueBoxImpl<T> extends ObjectValueImpl {\n\tprivate final Optional<T> value;\n\n\tpublic ObjectValueBoxImpl(@Nonnull Type type, @Nonnull Nullness nullness) {\n\t\tsuper(type, nullness);\n\t\tvalue = Optional.empty();\n\t}\n\n\tpublic ObjectValueBoxImpl(@Nonnull Type type, @Nullable T value) {\n\t\tsuper(type, value == null ? Nullness.NULL : Nullness.NOT_NULL);\n\t\tthis.value = Optional.ofNullable(value);\n\t}\n\n\t@Nonnull\n\tprotected abstract ObjectValueBoxImpl<T> wrap(@Nullable T value);\n\n\t@Nonnull\n\tprotected abstract ObjectValueBoxImpl<T> wrapUnknown(@Nonnull Nullness nullness);\n\n\t@Override\n\tpublic boolean hasKnownValue() {\n\t\treturn nullness() == Nullness.NOT_NULL && value.isPresent();\n\t}\n\n\t@Nullable\n\tpublic T unbox() {\n\t\treturn value().orElseThrow();\n\t}\n\n\t@Nonnull\n\tpublic Optional<T> value() {\n\t\treturn value;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tObjectValueBoxImpl<?> other = (ObjectValueBoxImpl<?>) o;\n\n\t\treturn value().equals(other.value());\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint value = type().hashCode();\n\t\tvalue = 31 * value + value().hashCode();\n\t\treturn value;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn type().getInternalName() + \":\" + (hasKnownValue() ? unbox() : \"<?>\");\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ReValue mergeWith(@Nonnull ReValue other) throws IllegalValueException {\n\t\tif (other == UninitializedValue.UNINITIALIZED_VALUE)\n\t\t\treturn other;\n\t\telse if (other instanceof ObjectValueBoxImpl<?> otherBoxed) {\n\t\t\tif (type().equals(otherBoxed.type()) && value().isPresent() && otherBoxed.value().isPresent()) {\n\t\t\t\tT v = value().get();\n\t\t\t\tT otherV = (T) otherBoxed.value().get();\n\t\t\t\tif (Objects.equals(v, otherV))\n\t\t\t\t\treturn wrap(v);\n\t\t\t}\n\t\t\treturn wrapUnknown(nullness().mergeWith(otherBoxed.nullness()));\n\t\t} else if (other instanceof ObjectValue otherObject) {\n\t\t\treturn ObjectValue.object(type(), nullness().mergeWith(otherObject.nullness()));\n\t\t}\n\t\tthrow new IllegalValueException(\"Cannot merge with: \" + other);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/ObjectValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.value.IllegalValueException;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.UninitializedValue;\n\n/**\n * Object value holder implementation.\n *\n * @author Matt Coley\n */\npublic class ObjectValueImpl implements ObjectValue {\n\tprivate final Type type;\n\tprivate final Nullness nullness;\n\n\tpublic ObjectValueImpl(@Nonnull Type type, @Nonnull Nullness nullness) {\n\t\tthis.type = type;\n\t\tthis.nullness = nullness;\n\t}\n\n\t@Override\n\tpublic boolean hasKnownValue() {\n\t\treturn nullness == Nullness.NULL;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Type type() {\n\t\treturn type;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ReValue mergeWith(@Nonnull ReValue other) throws IllegalValueException {\n\t\tif (other == UninitializedValue.UNINITIALIZED_VALUE)\n\t\t\treturn other;\n\t\telse if (other instanceof ObjectValue otherObject)\n\t\t\treturn ObjectValue.object(type, nullness().mergeWith(otherObject.nullness()));\n\t\tthrow new IllegalValueException(\"Cannot merge with: \" + other);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Nullness nullness() {\n\t\treturn nullness;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tObjectValueImpl other = (ObjectValueImpl) o;\n\n\t\treturn type.equals(other.type) && nullness.equals(other.nullness);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = type.hashCode();\n\t\tresult = 31 * result + nullness.hashCode();\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn type.getInternalName();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/StringValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.analysis.Nullness;\nimport software.coley.recaf.util.analysis.value.StringValue;\n\nimport java.util.Optional;\n\n/**\n * String value holder implementation.\n *\n * @author Matt Coley\n */\npublic class StringValueImpl extends ObjectValueBoxImpl<String> implements StringValue {\n\tpublic StringValueImpl(@Nonnull Nullness nullness) {\n\t\tsuper(Types.STRING_TYPE, nullness);\n\t}\n\n\tpublic StringValueImpl(@Nullable String value) {\n\t\tsuper(Types.STRING_TYPE, value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<String> wrap(@Nullable String value) {\n\t\treturn new StringValueImpl(value);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObjectValueBoxImpl<String> wrapUnknown(@Nonnull Nullness nullness) {\n\t\treturn new StringValueImpl(nullness);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Optional<String> getText() {\n\t\treturn value();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/analysis/value/impl/UninitializedValueImpl.java",
    "content": "package software.coley.recaf.util.analysis.value.impl;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.UninitializedValue;\n\n/**\n * Uninitialized value implementation.\n *\n * @author Matt Coley\n */\npublic class UninitializedValueImpl implements UninitializedValue {\n\tpublic static final UninitializedValue UNINITIALIZED_VALUE = new UninitializedValueImpl();\n\n\tprivate UninitializedValueImpl() {}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \".\";\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ReValue mergeWith(@Nonnull ReValue other) {\n\t\treturn this;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/android/AndroidRes.java",
    "content": "package software.coley.recaf.util.android;\n\nimport com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceFile;\nimport com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceValue;\nimport com.google.devrel.gmscore.tools.apk.arsc.Chunk;\nimport com.google.devrel.gmscore.tools.apk.arsc.PackageChunk;\nimport com.google.devrel.gmscore.tools.apk.arsc.ResourceTableChunk;\nimport com.google.devrel.gmscore.tools.apk.arsc.TypeChunk;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonParser;\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\nimport it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;\nimport it.unimi.dsi.fastutil.objects.Object2IntMap;\nimport it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;\nimport it.unimi.dsi.fastutil.objects.Object2LongMap;\nimport it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.android.xml.AndroidResourceProvider;\nimport software.coley.recaf.workspace.model.resource.AndroidApiResource;\n\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.TreeMap;\nimport java.util.TreeSet;\nimport java.util.stream.Collectors;\n\n/**\n * Android resource information.\n * <ul>\n *     <li>{@link #getAndroidBase()} - Common Android resource information</li>\n *     <li>{@link #fromArsc(BinaryResourceFile)} - Android resource information from an ARSC file</li>\n * </ul>\n *\n * @author Matt Coley\n */\npublic class AndroidRes implements AndroidResourceProvider {\n\tprivate static final AndroidRes ANDROID_BASE;\n\tprivate final Int2ObjectMap<String> resIdToName;\n\tprivate final Object2IntMap<String> resNameToId;\n\tprivate final Map<String, String> attrToFormat;\n\tprivate final Map<String, Object2LongMap<String>> attrToEnum;\n\tprivate final Map<String, Object2LongMap<String>> attrToFlags;\n\tprivate final Map<String, BinaryResourceValue> attrToSimpleResource;\n\tprivate final Map<String, Map<Integer, BinaryResourceValue>> attrToComplexResource;\n\tprivate final Map<String, Set<String>> formatToAttrs;\n\n\tprivate AndroidRes(@Nonnull Int2ObjectMap<String> resIdToName,\n\t                   @Nonnull Object2IntMap<String> resNameToId,\n\t                   @Nonnull Map<String, String> attrToFormat,\n\t                   @Nonnull Map<String, Object2LongMap<String>> attrToEnum,\n\t                   @Nonnull Map<String, Object2LongMap<String>> attrToFlags,\n\t                   @Nonnull Map<String, BinaryResourceValue> attrToSimpleResource,\n\t                   @Nonnull Map<String, Map<Integer, BinaryResourceValue>> attrToComplexResource,\n\t                   @Nonnull Map<String, Set<String>> formatToAttrs) {\n\t\tthis.resIdToName = resIdToName;\n\t\tthis.resNameToId = resNameToId;\n\t\tthis.attrToFormat = attrToFormat;\n\t\tthis.attrToEnum = attrToEnum;\n\t\tthis.attrToFlags = attrToFlags;\n\t\tthis.attrToSimpleResource = attrToSimpleResource;\n\t\tthis.attrToComplexResource = attrToComplexResource;\n\t\tthis.formatToAttrs = formatToAttrs;\n\t}\n\n\t/**\n\t * @param arsc\n\t * \t\tARSC resource file to parse resource information from.\n\t *\n\t * @return Resource information model unpacked from the chunked data.\n\t */\n\t@Nonnull\n\tpublic static AndroidRes fromArsc(@Nonnull BinaryResourceFile arsc) {\n\t\tList<Chunk> chunks = arsc.getChunks();\n\n\t\t// Maps to collect data into.\n\t\tInt2ObjectMap<String> resIdToName = new Int2ObjectOpenHashMap<>();\n\t\tObject2IntMap<String> resNameToId = new Object2IntOpenHashMap<>();\n\t\tMap<String, String> attrToFormat = new TreeMap<>();\n\t\tMap<String, Object2LongMap<String>> attrToEnum = new TreeMap<>();\n\t\tMap<String, Object2LongMap<String>> attrToFlags = new TreeMap<>();\n\t\tMap<String, BinaryResourceValue> attrToSimpleResource = new TreeMap<>();\n\t\tMap<String, Map<Integer, BinaryResourceValue>> attrToComplexResource = new TreeMap<>();\n\t\tMap<String, Set<String>> formatToAttrs = new TreeMap<>();\n\n\t\t// Parse the chunks in the ARSC, extracting all entries\n\t\tfor (Chunk chunk : chunks) {\n\t\t\tif (chunk instanceof ResourceTableChunk) {\n\t\t\t\tResourceTableChunk resourceTableChunk = (ResourceTableChunk) chunk;\n\t\t\t\tfor (PackageChunk packageChunk : resourceTableChunk.getPackages()) {\n\t\t\t\t\tint packageId = packageChunk.getId();\n\t\t\t\t\tfor (TypeChunk typeChunk : packageChunk.getTypeChunks()) {\n\t\t\t\t\t\tint typeId = typeChunk.getId();\n\t\t\t\t\t\tString typePrefix = typeChunk.getTypeName();\n\t\t\t\t\t\ttypeChunk.getEntries().forEach((entryId, entry) -> {\n\t\t\t\t\t\t\tint entryResId = packageId << 24 | typeId << 16 | entryId;\n\t\t\t\t\t\t\tString key = entry.key();\n\t\t\t\t\t\t\tString mapKey = typePrefix + \"/\" + key;\n\t\t\t\t\t\t\tresIdToName.put(entryResId, mapKey);\n\t\t\t\t\t\t\tresNameToId.put(mapKey, entryResId);\n\t\t\t\t\t\t\tif (entry.isComplex()) {\n\t\t\t\t\t\t\t\tMap<Integer, BinaryResourceValue> values = entry.values();\n\t\t\t\t\t\t\t\tattrToComplexResource.put(mapKey, values);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tBinaryResourceValue value = entry.value();\n\t\t\t\t\t\t\t\tattrToSimpleResource.put(mapKey, value);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn new AndroidRes(resIdToName, resNameToId, attrToFormat, attrToEnum, attrToFlags,\n\t\t\t\tattrToSimpleResource, attrToComplexResource, formatToAttrs);\n\t}\n\n\t/**\n\t * @return Instance of core Android resources.\n\t */\n\t@Nonnull\n\tpublic static AndroidRes getAndroidBase() {\n\t\treturn ANDROID_BASE;\n\t}\n\n\t/**\n\t * @param attrName\n\t * \t\tLocal attribute name, without the {@code attr/} prefix.\n\t *\n\t * @return Resource ID.\n\t */\n\tpublic int getAttrResId(@Nonnull String attrName) {\n\t\treturn getResId(\"attr/\" + attrName);\n\t}\n\n\t/**\n\t * @param resName\n\t * \t\tResource name.\n\t *\n\t * @return Resource ID.\n\t */\n\tpublic int getResId(@Nonnull String resName) {\n\t\treturn resNameToId.getOrDefault(resName, -1);\n\t}\n\n\t@Override\n\tpublic boolean hasResName(int resId) {\n\t\treturn resIdToName.get(resId) != null;\n\t}\n\n\t@Override\n\t@Nullable\n\tpublic String getResName(int resId) {\n\t\treturn resIdToName.get(resId);\n\t}\n\n\t/**\n\t * @param resId\n\t * \t\tResource ID.\n\t *\n\t * @return {@code true} when the resource is a known enum.\n\t */\n\tpublic boolean isResEnum(int resId) {\n\t\tString resName = getResName(resId);\n\t\tif (resName == null)\n\t\t\treturn false;\n\t\treturn hasResEnum(resName);\n\t}\n\n\t@Override\n\tpublic boolean hasResEnum(@Nonnull String resName) {\n\t\treturn attrToEnum.containsKey(resName);\n\t}\n\n\t@Override\n\t@Nullable\n\tpublic String getResEnumName(@Nonnull String resName, long value) {\n\t\tObject2LongMap<String> values = attrToEnum.get(resName);\n\t\tif (values == null)\n\t\t\treturn null;\n\t\treturn values.object2LongEntrySet().stream()\n\t\t\t\t.filter(e -> e.getLongValue() == value)\n\t\t\t\t.findFirst()\n\t\t\t\t.map(Map.Entry::getKey)\n\t\t\t\t.orElse(null);\n\t}\n\n\t/**\n\t * @param resName\n\t * \t\tResource name.\n\t *\n\t * @return {@code true} when the resource is a known value.\n\t */\n\tpublic boolean isSimpleResValue(@Nonnull String resName) {\n\t\treturn attrToSimpleResource.containsKey(resName);\n\t}\n\n\t/**\n\t * @param resName\n\t * \t\tResource name.\n\t *\n\t * @return Resource value for the associated value if known, otherwise {@code null}.\n\t */\n\t@Nullable\n\tpublic BinaryResourceValue getSimpleResValue(@Nonnull String resName) {\n\t\treturn attrToSimpleResource.get(resName);\n\t}\n\n\t/**\n\t * @param resName\n\t * \t\tResource name.\n\t *\n\t * @return {@code true} when the resource is a known value.\n\t */\n\tpublic boolean isComplexResValue(@Nonnull String resName) {\n\t\treturn attrToComplexResource.containsKey(resName);\n\t}\n\n\t/**\n\t * @param resName\n\t * \t\tResource name.\n\t *\n\t * @return Resource value for the associated value if known, otherwise {@code null}.\n\t */\n\t@Nullable\n\tpublic Map<Integer, BinaryResourceValue> getComplexResValue(@Nonnull String resName) {\n\t\treturn attrToComplexResource.get(resName);\n\t}\n\n\t/**\n\t * @param attrName\n\t * \t\tAttribute name.\n\t *\n\t * @return {@code true} when the attribute has an associated format.\n\t */\n\tpublic boolean attributeHasFormat(@Nonnull String attrName) {\n\t\treturn attrToFormat.containsKey(attrName);\n\t}\n\n\t/**\n\t * @param attrName\n\t * \t\tAttribute name.\n\t *\n\t * @return Format identifier for the associated value if known, otherwise {@code null}.\n\t */\n\t@Nullable\n\tpublic String getAttributeFormat(@Nonnull String attrName) {\n\t\treturn attrToFormat.get(attrName);\n\t}\n\n\t/**\n\t * @param resId\n\t * \t\tResource ID.\n\t *\n\t * @return {@code true} when the resource is a known flag.\n\t */\n\tpublic boolean isResFlag(int resId) {\n\t\tString resName = getResName(resId);\n\t\tif (resName == null)\n\t\t\treturn false;\n\t\treturn hasResEnum(resName);\n\t}\n\n\t@Override\n\tpublic boolean hasResFlag(@Nonnull String resName) {\n\t\treturn attrToFlags.containsKey(resName);\n\t}\n\n\t@Override\n\t@Nonnull\n\tpublic String getResFlagNames(@Nonnull String resName, long mask) {\n\t\tObject2LongMap<String> values = attrToFlags.get(resName);\n\t\tif (values == null)\n\t\t\treturn \"\";\n\t\treturn values.object2LongEntrySet().stream()\n\t\t\t\t.filter(e -> (mask & e.getLongValue()) != 0L)\n\t\t\t\t.sorted(Comparator.comparingLong(Object2LongMap.Entry::getLongValue))\n\t\t\t\t.map(Map.Entry::getKey)\n\t\t\t\t.collect(Collectors.joining(\"|\"));\n\t}\n\n\tprivate static final Gson GSON = new GsonBuilder().create();\n\n\tstatic {\n\t\ttry {\n\t\t\t// Parse res-map entries (hex=name)\n\t\t\tString resMapText = new String(AndroidApiResource.class.getResourceAsStream(\"/android/res-map.txt\").readAllBytes());\n\t\t\tString[] resMapLines = resMapText.split(\"[\\n\\r]+\");\n\t\t\tInt2ObjectMap<String> resIdToName = new Int2ObjectOpenHashMap<>(resMapLines.length);\n\t\t\tObject2IntMap<String> resNameToId = new Object2IntOpenHashMap<>(resMapLines.length);\n\t\t\tfor (String line : resMapLines) {\n\t\t\t\tString[] kv = line.split(\"=\");\n\t\t\t\tint key = Integer.parseInt(kv[0], 16);\n\t\t\t\tString name = kv[1];\n\t\t\t\tresIdToName.put(key, name);\n\t\t\t\tresNameToId.put(name, key);\n\t\t\t}\n\n\t\t\t// Parse the attribute manifest\n\t\t\tMap<String, Set<String>> formatToAttrs = new TreeMap<>();\n\t\t\tMap<String, String> attrToFormat = new TreeMap<>();\n\t\t\tMap<String, Object2LongMap<String>> attrToEnum = new TreeMap<>();\n\t\t\tMap<String, Object2LongMap<String>> attrToFlags = new TreeMap<>();\n\n\t\t\tJsonObject tree = (JsonObject) JsonParser.parseReader(new InputStreamReader(AndroidRes.class.getResourceAsStream(\"/android/attrs.json\")));\n\t\t\tvisit(formatToAttrs, attrToFormat, attrToEnum, attrToFlags, tree);\n\t\t\tANDROID_BASE = new AndroidRes(resIdToName, resNameToId,\n\t\t\t\t\tattrToFormat, attrToEnum, attrToFlags, Collections.emptyMap(), Collections.emptyMap(), formatToAttrs);\n\t\t} catch (IOException ex) {\n\t\t\tthrow new IllegalStateException(ex);\n\t\t}\n\t}\n\n\tprivate static void visit(@Nonnull Map<String, Set<String>> formatToAttrs,\n\t                          @Nonnull Map<String, String> attrToFormat,\n\t                          @Nonnull Map<String, Object2LongMap<String>> attrToEnum,\n\t                          @Nonnull Map<String, Object2LongMap<String>> attrToFlags,\n\t                          @Nonnull JsonObject node) {\n\t\tJsonElement nameNode = node.get(\"name\");\n\t\tif (nameNode != null && node instanceof JsonObject childObject) {\n\t\t\tString name = nameNode.getAsString();\n\n\t\t\t// Handle different kinds of entries\n\t\t\tJsonElement formatNode = childObject.get(\"format\");\n\t\t\tif (formatNode != null) {\n\t\t\t\tString format = formatNode.getAsString();\n\t\t\t\tattrToFormat.put(name, format);\n\t\t\t\tformatToAttrs.computeIfAbsent(format, f -> new TreeSet<>()).add(name);\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\tJsonElement flagNode = childObject.get(\"flag\");\n\t\t\t\tif (flagNode instanceof JsonArray flagArray) {\n\t\t\t\t\tObject2LongMap<String> nameToValue = new Object2LongOpenHashMap<>();\n\t\t\t\t\tfor (JsonElement flagEntry : flagArray) {\n\t\t\t\t\t\tJsonObject flagObject = flagEntry.getAsJsonObject();\n\t\t\t\t\t\tString flagName = flagObject.get(\"name\").getAsString();\n\t\t\t\t\t\tString flagValueText = flagObject.get(\"value\").getAsString();\n\t\t\t\t\t\tif (flagValueText.startsWith(\"0x\"))\n\t\t\t\t\t\t\tflagValueText = flagValueText.substring(2);\n\t\t\t\t\t\tlong flagValue = Long.parseLong(flagValueText, 16);\n\t\t\t\t\t\tnameToValue.put(flagName, flagValue);\n\t\t\t\t\t}\n\t\t\t\t\tattrToFlags.put(name, nameToValue);\n\t\t\t\t\treturn;\n\t\t\t\t} else {\n\t\t\t\t\tJsonElement enumNode = node.get(\"enum\");\n\t\t\t\t\tif (enumNode instanceof JsonArray enumArray) {\n\t\t\t\t\t\tObject2LongMap<String> nameToValue = new Object2LongOpenHashMap<>();\n\t\t\t\t\t\tfor (JsonElement flagEntry : enumArray) {\n\t\t\t\t\t\t\tJsonObject flagObject = flagEntry.getAsJsonObject();\n\t\t\t\t\t\t\tString enumName = flagObject.get(\"name\").getAsString();\n\t\t\t\t\t\t\tString enumValueText = flagObject.get(\"value\").getAsString();\n\t\t\t\t\t\t\tif (enumValueText.startsWith(\"0x\"))\n\t\t\t\t\t\t\t\tenumValueText = enumValueText.substring(2);\n\t\t\t\t\t\t\tlong enumValue = Long.parseLong(enumValueText);\n\t\t\t\t\t\t\tnameToValue.put(enumName, enumValue);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tattrToEnum.put(name, nameToValue);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Visit the children if this node is not a child and isn't one of the types we expect.\n\t\tfor (JsonElement child : node.asMap().values())\n\t\t\tif (child instanceof JsonObject childObject)\n\t\t\t\tvisit(formatToAttrs, attrToFormat, attrToEnum, attrToFlags, childObject);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/android/AndroidXmlUtil.java",
    "content": "package software.coley.recaf.util.android;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.ByteHeaderUtil;\n\n/**\n * Hacky utils for Android xml utils.\n *\n * @author Matt Coley\n */\npublic class AndroidXmlUtil {\n\tprivate static final int[] ANDROID_SCHEMA_URL = new int[]{\n\t\t\t's', 'c', 'h', 'e', 'm', 'a', 's', '.',\n\t\t\t'a', 'n', 'd', 'r', 'o', 'i', 'd', '.',\n\t\t\t'c', 'o', 'm'\n\t};\n\tprivate static final int[] ANDROID_SCHEMA_URL_UTF16 = new int[]{\n\t\t\t's', 0, 'c', 0, 'h', 0, 'e', 0, 'm', 0, 'a', 0, 's', 0, '.', 0,\n\t\t\t'a', 0, 'n', 0, 'd', 0, 'r', 0, 'o', 0, 'i', 0, 'd', 0, '.', 0,\n\t\t\t'c', 0, 'o', 0, 'm', 0\n\t};\n\tprivate static final int[] ANDROID_PERMISSION = new int[]{\n\t\t\t'a', 'n', 'd', 'r', 'o', 'i', 'd', '.',\n\t\t\t'p', 'e', 'r', 'm', 'i', 's', 's', 'i', 'o', 'n'\n\t};\n\tprivate static final int[] ANDROID_PERMISSION_UTF16 = new int[]{\n\t\t\t'a', 0, 'n', 0, 'd', 0, 'r', 0, 'o', 0, 'i', 0, 'd', 0, '.', 0,\n\t\t\t'p', 0, 'e', 0, 'r', 0, 'm', 0, 'i', 0, 's', 0, 's', 0, 'i', 0, 'o', 0, 'n'\n\t};\n\tprivate static final int[] INTENT_FILTER = new int[]{\n\t\t\t'i', 'n', 't', 'e', 'n', 't', '-',\n\t\t\t'f', 'i', 'l', 't', 'e', 'r'\n\t};\n\tprivate static final int[] INTENT_FILTER_UTF16 = new int[]{\n\t\t\t'i', 0, 'n', 0, 't', 0, 'e', 0, 'n', 0, 't', 0, '-', 0,\n\t\t\t'f', 0, 'i', 0, 'l', 0, 't', 0, 'e', 0, 'r'\n\t};\n\n\t/**\n\t * @param data\n\t * \t\tData to check.\n\t *\n\t * @return {@code true} if likely obscured XML chunk model, where the header does not match.\n\t */\n\tpublic static boolean hasXmlIndicators(@Nonnull byte[] data) {\n\t\treturn ByteHeaderUtil.matchAtAnyOffset(data, ANDROID_PERMISSION) ||\n\t\t\t\tByteHeaderUtil.matchAtAnyOffset(data, ANDROID_PERMISSION_UTF16) ||\n\t\t\t\tByteHeaderUtil.matchAtAnyOffset(data, INTENT_FILTER) ||\n\t\t\t\tByteHeaderUtil.matchAtAnyOffset(data, INTENT_FILTER_UTF16) ||\n\t\t\t\tByteHeaderUtil.matchAtAnyOffset(data, ANDROID_SCHEMA_URL) ||\n\t\t\t\tByteHeaderUtil.matchAtAnyOffset(data, ANDROID_SCHEMA_URL_UTF16);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/android/DexIOUtil.java",
    "content": "package software.coley.recaf.util.android;\n\nimport com.android.tools.r8.graph.DexProgramClass;\nimport jakarta.annotation.Nonnull;\nimport software.coley.dextranslator.model.ApplicationData;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.builder.AndroidClassInfoBuilder;\nimport software.coley.recaf.util.io.ByteSource;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicAndroidClassBundle;\n\nimport java.io.IOException;\n\n/**\n * Dex file reading and writing.\n *\n * @author Matt Coley\n */\npublic class DexIOUtil {\n\t/**\n\t * @param source\n\t * \t\tContent source to read from. Must be wrapping a dex file.\n\t *\n\t * @return Bundle of classes from the dex file.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the dex file cannot be read from.\n\t */\n\t@Nonnull\n\tpublic static AndroidClassBundle read(@Nonnull ByteSource source) throws IOException {\n\t\treturn read(source.readAll());\n\t}\n\n\t/**\n\t * @param dex\n\t * \t\tRaw bytes of a dex file.\n\t *\n\t * @return Bundle of classes from the dex file.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the dex file cannot be read from.\n\t */\n\t@Nonnull\n\tpublic static AndroidClassBundle read(@Nonnull byte[] dex) throws IOException {\n\t\t// Read dex file content\n\t\tApplicationData data = ApplicationData.fromDex(dex);\n\n\t\t// Populate bundle\n\t\tBasicAndroidClassBundle classBundle = new BasicAndroidClassBundle();\n\t\tfor (DexProgramClass dexClass : data.getApplication().classes()) {\n\t\t\tAndroidClassInfo classInfo = new AndroidClassInfoBuilder()\n\t\t\t\t\t.adaptFrom(dexClass)\n\t\t\t\t\t.build();\n\t\t\tclassBundle.initialPut(classInfo);\n\t\t}\n\t\treturn classBundle;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/io/ByteArraySource.java",
    "content": "package software.coley.recaf.util.io;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.foreign.MemorySegment;\nimport java.util.Arrays;\n\n/**\n * Immediate byte source.\n *\n * @author xDark\n */\npublic final class ByteArraySource implements ByteSource {\n\tprivate final byte[] bytes;\n\tprivate final int off;\n\tprivate final int len;\n\n\t/**\n\t * @param bytes\n\t * \t\tInput bytes to wrap.\n\t */\n\tpublic ByteArraySource(byte[] bytes) {\n\t\tthis(bytes, 0, bytes.length);\n\t}\n\n\t/**\n\t * @param bytes\n\t * \t\tInput bytes to wrap.\n\t * @param off\n\t * \t\tStart offset.\n\t * @param len\n\t * \t\tLength of content.\n\t */\n\tpublic ByteArraySource(byte[] bytes, int off, int len) {\n\t\tthis.bytes = bytes;\n\t\tthis.off = off;\n\t\tthis.len = len;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] readAll() {\n\t\treturn Arrays.copyOfRange(bytes, off, len);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] peek(int count) {\n\t\tcount = Math.min(count, len);\n\t\treturn Arrays.copyOfRange(bytes, off, count);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic InputStream openStream() {\n\t\treturn new ByteArrayInputStream(bytes, off, len);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic MemorySegment mmap() throws IOException {\n\t\treturn MemorySegment.ofArray(bytes).asSlice(off, len);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/io/ByteBufferSource.java",
    "content": "package software.coley.recaf.util.io;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.foreign.MemorySegment;\nimport java.nio.ByteBuffer;\n\n/**\n * Buffer byte source.\n *\n * @author xDark\n */\npublic final class ByteBufferSource implements ByteSource {\n\tprivate final ByteSource source;\n\n\t/**\n\t * @param buffer\n\t * \t\tBuffer.\n\t */\n\tpublic ByteBufferSource(ByteBuffer buffer) {\n\t\tsource = ByteSources.forMemorySegment(MemorySegment.ofBuffer(buffer));\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] readAll() throws IOException {\n\t\treturn source.readAll();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] peek(int count) throws IOException {\n\t\treturn source.peek(count);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic InputStream openStream() throws IOException {\n\t\treturn source.openStream();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic MemorySegment mmap() throws IOException {\n\t\treturn source.mmap();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/io/ByteSource.java",
    "content": "package software.coley.recaf.util.io;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.foreign.MemorySegment;\n\n/**\n * Lazily provides byte array form a source.\n *\n * @author xDark\n */\npublic interface ByteSource {\n\n\t/**\n\t * @return All bytes.\n\t *\n\t * @throws IOException\n\t * \t\tIf any I/O error occurs.\n\t */\n\t@Nonnull\n\tbyte[] readAll() throws IOException;\n\n\t/**\n\t * Peeks some amount of bytes\n\t * from the source.\n\t *\n\t * @param count\n\t * \t\tAmount of bytes to peek.\n\t *\n\t * @return Peeked byte array which size may be\n\t * smaller than the requested {@code count}.\n\t *\n\t * @throws IOException\n\t * \t\tIf any I/O error occurs.\n\t */\n\t@Nonnull\n\tbyte[] peek(int count) throws IOException;\n\n\t/**\n\t * Streams this source.\n\t *\n\t * @return Stream for this source.\n\t *\n\t * @throws IOException\n\t * \t\tIf any I/O error occurs.\n\t */\n\t@Nonnull\n\tInputStream openStream() throws IOException;\n\n\t/**\n\t * Maps this source into memory.\n\t * @return Memory-mapped view.\n\t *\n\t * @throws IOException\n\t * \t\tIf any I/O error occurs.\n\t */\n\t@Nonnull\n\tMemorySegment mmap() throws IOException;;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/io/ByteSourceConsumer.java",
    "content": "package software.coley.recaf.util.io;\n\nimport java.io.IOException;\n\n/**\n * IO consumer for byte source.\n *\n * @param <E>\n * \t\tAdditional argument type.\n *\n * @author xDark\n */\n@FunctionalInterface\npublic interface ByteSourceConsumer<E> {\n\t/**\n\t * Performs this operation on the given byte source.\n\t *\n\t * @param e\n\t * \t\tAdditional argument.\n\t * @param source\n\t * \t\tByte source.\n\t *\n\t * @throws IOException\n\t * \t\tIf any I/O error occurs.\n\t */\n\tvoid accept(E e, ByteSource source) throws IOException;\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/io/ByteSourceElement.java",
    "content": "package software.coley.recaf.util.io;\n\n/**\n * Container for an element and a byte source.\n *\n * @author xDark\n */\npublic class ByteSourceElement<E> {\n\tprivate final E element;\n\tprivate final ByteSource byteSource;\n\n\t/**\n\t * @param element\n\t * \t\tElement value.\n\t * @param byteSource\n\t * \t\tByte source.\n\t */\n\tpublic ByteSourceElement(E element, ByteSource byteSource) {\n\t\tthis.element = element;\n\t\tthis.byteSource = byteSource;\n\t}\n\n\t/**\n\t * @return Element value.\n\t */\n\tpublic E getElement() {\n\t\treturn element;\n\t}\n\n\t/**\n\t * @return Byte source.\n\t */\n\tpublic ByteSource getByteSource() {\n\t\treturn byteSource;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/io/ByteSources.java",
    "content": "package software.coley.recaf.util.io;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.ReflectUtil;\n\nimport java.io.IOException;\nimport java.lang.foreign.MemorySegment;\nimport java.nio.ByteBuffer;\nimport java.nio.file.Path;\nimport java.util.function.Consumer;\n\n/**\n * Byte source utilities.\n *\n * @author xDark\n */\npublic class ByteSources {\n\t/**\n\t * Deny all constructions.\n\t */\n\tprivate ByteSources() {\n\t}\n\n\t/**\n\t * Wraps {@link ByteSourceConsumer} into a consumer\n\t * that accepts {@link ByteSourceElement}.\n\t *\n\t * @param consumer\n\t * \t\tConsumer to wrap.\n\t * @param <E>\n\t * \t\tElement type.\n\t *\n\t * @return Wrapped consumer.\n\t */\n\t@Nonnull\n\tpublic static <E> Consumer<ByteSourceElement<E>> consume(@Nonnull ByteSourceConsumer<E> consumer) {\n\t\treturn e -> {\n\t\t\ttry {\n\t\t\t\tconsumer.accept(e.getElement(), e.getByteSource());\n\t\t\t} catch (IOException ex) {\n\t\t\t\tReflectUtil.propagate(ex);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Byte source that wraps existing byte array.\n\t *\n\t * @param bytes\n\t * \t\tSource content.\n\t * @param offset\n\t * \t\tContent offset.\n\t * @param length\n\t * \t\tContent length.\n\t *\n\t * @return New byte source.\n\t */\n\t@Nonnull\n\tpublic static ByteSource wrap(@Nonnull byte[] bytes, int offset, int length) {\n\t\treturn new ByteArraySource(bytes, offset, length);\n\t}\n\n\t/**\n\t * Byte source that wraps existing byte array.\n\t *\n\t * @param bytes\n\t * \t\tSource content.\n\t *\n\t * @return New byte source.\n\t */\n\t@Nonnull\n\tpublic static ByteSource wrap(@Nonnull byte[] bytes) {\n\t\treturn new ByteArraySource(bytes, 0, bytes.length);\n\t}\n\n\t/**\n\t * Creates new byte source from byte buffer.\n\t *\n\t * @param buffer\n\t * \t\tBuffer to wrap.\n\t *\n\t * @return New byte source.\n\t */\n\t@Nonnull\n\tpublic static ByteSource forBuffer(@Nonnull ByteBuffer buffer) {\n\t\treturn new ByteBufferSource(buffer);\n\t}\n\n\t/**\n\t * Creates new byte source from path.\n\t *\n\t * @param path\n\t * \t\tPath to read bytes from.\n\t *\n\t * @return New byte source.\n\t */\n\t@Nonnull\n\tpublic static ByteSource forPath(@Nonnull Path path) {\n\t\treturn new PathByteSource(path);\n\t}\n\n\t/**\n\t * Creates new byte source from lljzip byte data.\n\t *\n\t * @param data\n\t * \t\tData to create source for.\n\t *\n\t * @return New byte source.\n\t */\n\t@Nonnull\n\tpublic static ByteSource forMemorySegment(@Nonnull MemorySegment data) {\n\t\treturn new MemorySegmentDataSource(data);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/io/LocalFileHeaderSource.java",
    "content": "package software.coley.recaf.util.io;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.lljzip.format.compression.ZipCompressions;\nimport software.coley.lljzip.format.model.LocalFileHeader;\nimport software.coley.lljzip.util.MemorySegmentUtil;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.foreign.MemorySegment;\nimport java.lang.foreign.ValueLayout;\n\n/**\n * Byte source from {@link LocalFileHeader}.\n *\n * @author xDark\n */\npublic final class LocalFileHeaderSource implements ByteSource {\n\tprivate final LocalFileHeader fileHeader;\n\tprivate final boolean isAndroid;\n\tprivate MemorySegment decompressed;\n\n\tpublic LocalFileHeaderSource(LocalFileHeader fileHeader) {\n\t\tthis(fileHeader, false);\n\t}\n\n\tpublic LocalFileHeaderSource(LocalFileHeader fileHeader, boolean isAndroid) {\n\t\tthis.fileHeader = fileHeader;\n\t\tthis.isAndroid = isAndroid;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] readAll() throws IOException {\n\t\treturn MemorySegmentUtil.toByteArray(decompress());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] peek(int count) throws IOException {\n\t\tMemorySegment data = decompress();\n\t\tlong length = data.byteSize();\n\t\tif (length < count)\n\t\t\tcount = (int) length;\n\t\treturn data.asSlice(0, count).toArray(ValueLayout.JAVA_BYTE);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic InputStream openStream() throws IOException {\n\t\t// Delegate to byte source\n\t\treturn ByteSources.forMemorySegment(decompress()).openStream();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic MemorySegment mmap() throws IOException {\n\t\treturn decompress();\n\t}\n\n\t/**\n\t * @return {@code true} when the data length of the content is 0.\n\t *\n\t * @throws IOException\n\t * \t\tWhen data cannot be decompressed to determine true content length.\n\t */\n\tpublic boolean isEmpty() throws IOException {\n\t\tif (fileHeader.getCompressionMethod() == ZipCompressions.STORED)\n\t\t\treturn fileHeader.getFileData().byteSize() == 0;\n\t\treturn decompress().byteSize() == 0;\n\t}\n\n\tprivate MemorySegment decompress() throws IOException {\n\t\ttry {\n\t\t\tMemorySegment decompressed = this.decompressed;\n\t\t\tif (decompressed == null) {\n\t\t\t\t// From: https://cs.android.com/android/_/android/platform/frameworks/base/+/b3559643b946829933a76ed45750d13edfefad30:tools/aapt/ZipFile.cpp;l=436\n\t\t\t\t//  - If the compression mode given fails, it will get treated as STORED as a fallback\n\t\t\t\tif (isAndroid) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn this.decompressed = ZipCompressions.decompress(fileHeader);\n\t\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\t\treturn this.decompressed = fileHeader.getFileData();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// In other cases, malformed content should throw an exception and be handled by the caller.\n\t\t\t\treturn this.decompressed = ZipCompressions.decompress(fileHeader);\n\t\t\t}\n\t\t\treturn decompressed;\n\t\t} catch (OutOfMemoryError error) {\n\t\t\t// This is cringe, I know. However, in practice it will immediately free the memory\n\t\t\t// used in the failed decompression. In a profile you'll the heap look like a saw-tooth with this.\n\t\t\t// Without, the heap just grows. It may go down a little, but not to the baseline of just calling gc().\n\t\t\tSystem.gc();\n\t\t\tthrow new IOException(\"Insufficient memory to decompress '\" + fileHeader.getFileNameAsString() + \"'\", error);\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/io/MemorySegmentDataSource.java",
    "content": "package software.coley.recaf.util.io;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.lljzip.util.MemorySegmentInputStream;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.foreign.MemorySegment;\nimport java.lang.foreign.ValueLayout;\n\n/**\n * {@link ByteSource} implemented via {@link MemorySegment}.\n *\n * @author xDark\n */\npublic final class MemorySegmentDataSource implements ByteSource, AutoCloseable {\n\tprivate final MemorySegment data;\n\n\t/**\n\t * @param data\n\t * \t\tData to read bytes from.\n\t */\n\tpublic MemorySegmentDataSource(MemorySegment data) {\n\t\tthis.data = data;\n\t}\n\n\t@Override\n\tpublic void close() throws Exception {\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] readAll() throws IOException {\n\t\tMemorySegment data = this.data;\n\t\tif (data.byteSize() > Integer.MAX_VALUE - 8) {\n\t\t\tthrow new IOException(\"Too large content\");\n\t\t}\n\t\treturn data.toArray(ValueLayout.JAVA_BYTE);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] peek(int count) {\n\t\tMemorySegment data = this.data;\n\t\tcount = (int) Math.min(count, data.byteSize());\n\t\treturn data.asSlice(0, count).toArray(ValueLayout.JAVA_BYTE);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic InputStream openStream() {\n\t\treturn new MemorySegmentInputStream(data);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic MemorySegment mmap() {\n\t\treturn data;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/io/PathByteSource.java",
    "content": "package software.coley.recaf.util.io;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.foreign.Arena;\nimport java.lang.foreign.MemorySegment;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Arrays;\n\n/**\n * Path byte source.\n *\n * @author xDark\n */\nfinal class PathByteSource implements ByteSource {\n\tprivate final Path path;\n\n\t/**\n\t * @param path\n\t * \t\tPath to read bytes from.\n\t */\n\tPathByteSource(Path path) {\n\t\tthis.path = path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] readAll() throws IOException {\n\t\treturn Files.readAllBytes(path);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic byte[] peek(int count) throws IOException {\n\t\ttry (InputStream in = openStream()) {\n\t\t\tbyte[] buf = new byte[count];\n\t\t\tint offset = 0;\n\t\t\tint r;\n\t\t\twhile ((r = in.read(buf, offset, count)) > 0) {\n\t\t\t\toffset += r;\n\t\t\t\tcount -= r;\n\t\t\t}\n\t\t\treturn count == 0 ? buf : Arrays.copyOf(buf, offset);\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic InputStream openStream() throws IOException {\n\t\treturn Files.newInputStream(path);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic MemorySegment mmap() throws IOException {\n\t\ttry (FileChannel fc = FileChannel.open(path)) {\n\t\t\treturn fc.map(FileChannel.MapMode.READ_ONLY, 0L, fc.size(), Arena.ofAuto());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/kotlin/KotlinMetadata.java",
    "content": "package software.coley.recaf.util.kotlin;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.jetbrains.kotlin.metadata.MetadataNameResolver;\nimport org.jetbrains.kotlin.metadata.MetadataUtils;\nimport org.jetbrains.kotlin.metadata.ProtoBuf;\nimport org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf;\nimport org.jetbrains.kotlin.metadata.jvm.deserialization.BitEncoding;\nimport org.jetbrains.kotlin.protobuf.ExtensionRegistryLite;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassVisitor;\nimport org.slf4j.Logger;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.util.kotlin.model.KtClass;\nimport software.coley.recaf.util.kotlin.model.KtClassKind;\nimport software.coley.recaf.util.kotlin.model.KtConstructor;\nimport software.coley.recaf.util.kotlin.model.KtFunction;\nimport software.coley.recaf.util.kotlin.model.KtNullability;\nimport software.coley.recaf.util.kotlin.model.KtParameter;\nimport software.coley.recaf.util.kotlin.model.KtProperty;\nimport software.coley.recaf.util.kotlin.model.KtType;\nimport software.coley.recaf.util.kotlin.model.KtVariable;\nimport software.coley.recaf.util.visitors.KotlinMetadataVisitor;\n\nimport java.io.ByteArrayInputStream;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.Supplier;\n\n/**\n * Container for information extracted from Kotlin's {@code @Metadata} annotation.\n *\n * @author Matt Coley\n */\npublic class KotlinMetadata {\n\tprivate static final Logger logger = Logging.get(KotlinMetadata.class);\n\n\t/** Descriptor of metadata annotation */\n\tpublic static final String METADATA_DESC = \"Lkotlin/Metadata;\";\n\tprivate static final ExtensionRegistryLite EXTENSIONS = ExtensionRegistryLite.newInstance();\n\tprivate static int parseFailCount;\n\tprivate final Supplier<KtClass> ktModelSupplier;\n\n\tstatic {\n\t\tMetadataUtils.register(EXTENSIONS);\n\t}\n\n\tprivate KotlinMetadata(@Nonnull MetadataNameResolver resolver,\n\t                       @Nonnull KotlinMetadataVisitor visitor,\n\t                       @Nonnull Supplier<KtClass> ktModelSupplier) {\n\t\tthis.ktModelSupplier = ktModelSupplier;\n\t}\n\n\tprivate KotlinMetadata(@Nonnull ProtoBuf.Class cls,\n\t                       @Nonnull MetadataNameResolver resolver,\n\t                       @Nonnull KotlinMetadataVisitor visitor) {\n\t\tthis(resolver, visitor, () -> {\n\t\t\tKtClassKind kind = KtClassKind.fromKindInt(visitor.getKind());\n\t\t\tint extraFlags = visitor.getExtraInt();\n\t\t\tString fqName = cls.hasFqName() ? resolver.resolve(cls.getFqName()) : visitor.getDefiningClass();\n\t\t\tif (fqName != null)\n\t\t\t\tfqName = fqName.replace('.', '$');\n\t\t\tList<KtType> superTypes = cls.getSupertypeList().stream()\n\t\t\t\t\t.map(t -> mapType(resolver, t))\n\t\t\t\t\t.toList();\n\t\t\tString companionObjectName = cls.hasCompanionObjectName() ? resolver.resolve(cls.getCompanionObjectName()) : null;\n\t\t\tList<KtConstructor> constructors = newList(cls.getConstructorCount());\n\t\t\tfor (var constructor : cls.getConstructorList())\n\t\t\t\tconstructors.add(new KtConstructor(fqName, constructor.getValueParameterList().stream().map(vp -> mapParameter(resolver, vp)).toList()));\n\t\t\tList<KtProperty> properties = newList(cls.getPropertyCount());\n\t\t\tfor (var property : cls.getPropertyList())\n\t\t\t\tproperties.add(mapProperty(resolver, property));\n\t\t\tList<KtFunction> functions = newList(cls.getFunctionCount());\n\t\t\tfor (var function : cls.getFunctionList())\n\t\t\t\tfunctions.add(mapFunction(resolver, function));\n\t\t\treturn new KtClass(kind, extraFlags, fqName, companionObjectName,\n\t\t\t\t\tsuperTypes,\n\t\t\t\t\tconstructors,\n\t\t\t\t\tproperties,\n\t\t\t\t\tfunctions);\n\t\t});\n\t}\n\n\tprivate KotlinMetadata(@Nonnull ProtoBuf.Package pkg,\n\t                       @Nonnull MetadataNameResolver resolver,\n\t                       @Nonnull KotlinMetadataVisitor visitor) {\n\t\tthis(resolver, visitor, () -> {\n\t\t\tKtClassKind kind = KtClassKind.fromKindInt(visitor.getKind());\n\t\t\tint extraFlags = visitor.getExtraInt();\n\t\t\tList<KtProperty> properties = newList(pkg.getPropertyCount());\n\t\t\tfor (var property : pkg.getPropertyList())\n\t\t\t\tproperties.add(mapProperty(resolver, property));\n\t\t\tList<KtFunction> functions = newList(pkg.getFunctionCount());\n\t\t\tfor (var function : pkg.getFunctionList())\n\t\t\t\tfunctions.add(mapFunction(resolver, function));\n\t\t\treturn new KtClass(kind, extraFlags, visitor.getDefiningClass(), null,\n\t\t\t\t\tCollections.emptyList(),\n\t\t\t\t\tCollections.emptyList(),\n\t\t\t\t\tproperties,\n\t\t\t\t\tfunctions);\n\t\t});\n\t}\n\n\tprivate KotlinMetadata(@Nonnull ProtoBuf.Function func,\n\t                       @Nonnull MetadataNameResolver resolver,\n\t                       @Nonnull KotlinMetadataVisitor visitor) {\n\t\tthis(resolver, visitor, () -> {\n\t\t\tKtClassKind kind = KtClassKind.fromKindInt(visitor.getKind());\n\t\t\tint extraFlags = visitor.getExtraInt();\n\t\t\treturn new KtClass(kind, extraFlags, visitor.getDefiningClass(), null,\n\t\t\t\t\tCollections.emptyList(),\n\t\t\t\t\tCollections.emptyList(),\n\t\t\t\t\tCollections.emptyList(),\n\t\t\t\t\tCollections.singletonList(mapFunction(resolver, func)));\n\t\t});\n\t}\n\n\t@Nonnull\n\tprivate KtClass extractKtModel() {\n\t\treturn ktModelSupplier.get();\n\t}\n\n\t/**\n\t * @param bytes\n\t * \t\tBytes of class to extract Metadata from.\n\t *\n\t * @return Data extracted from visitor.\n\t */\n\t@Nullable\n\tprotected static KotlinMetadata extractMetadata(@Nonnull byte[] bytes) {\n\t\treturn extractMetadata(new ClassReader(bytes));\n\t}\n\n\t/**\n\t * @param reader\n\t * \t\tReader wrapper of class to extract Metadata from.\n\t *\n\t * @return Data extracted from visitor.\n\t */\n\t@Nullable\n\tprotected static KotlinMetadata extractMetadata(@Nonnull ClassReader reader) {\n\t\tString className = reader.getClassName();\n\t\tKotlinMetadata[] wrapper = new KotlinMetadata[1];\n\t\treader.accept(new ClassVisitor(RecafConstants.getAsmVersion()) {\n\t\t\t@Override\n\t\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\t\tAnnotationVisitor visitor = super.visitAnnotation(descriptor, visible);\n\t\t\t\tif (METADATA_DESC.equals(descriptor))\n\t\t\t\t\tvisitor = new KotlinMetadataVisitor(className, visitor, v -> wrapper[0] = extractMetadata(v));\n\t\t\t\treturn visitor;\n\t\t\t}\n\t\t}, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);\n\t\treturn wrapper[0];\n\t}\n\n\t/**\n\t * @param visitor\n\t * \t\tMetadata visitor which has visited a class file.\n\t *\n\t * @return Data extracted from visitor.\n\t */\n\t@Nullable\n\tprotected static KotlinMetadata extractMetadata(@Nonnull KotlinMetadataVisitor visitor) {\n\t\ttry {\n\t\t\t// Skip if visitor does not have data populated.\n\t\t\tString[] data1 = visitor.getData1();\n\t\t\tString[] data2 = visitor.getData2();\n\t\t\tif (data1 == null || data2 == null)\n\t\t\t\treturn null;\n\n\t\t\t// Unpack \"d1\"\n\t\t\tbyte[] data1Decoded = BitEncoding.decodeBytes(data1);\n\t\t\tByteArrayInputStream data1Stream = new ByteArrayInputStream(data1Decoded);\n\t\t\tJvmProtoBuf.StringTableTypes types = JvmProtoBuf.StringTableTypes.parseDelimitedFrom(data1Stream, EXTENSIONS);\n\n\t\t\t// Wrap with \"d2\" so the human-readable names can be extracted\n\t\t\tMetadataNameResolver resolver = new MetadataNameResolver(types, data2);\n\n\t\t\t// Parse unpacked \"d1\" based on the metadata kind\n\t\t\tswitch (visitor.getKind()) {\n\t\t\t\tcase 1 /* Class */ -> {\n\t\t\t\t\tProtoBuf.Class cls = ProtoBuf.Class.parseFrom(data1Stream, EXTENSIONS);\n\t\t\t\t\treturn new KotlinMetadata(cls, resolver, visitor);\n\t\t\t\t}\n\t\t\t\tcase 2 /* File -> Package */,\n\t\t\t\t     5 /* Multi-file class part -> Package */ -> {\n\t\t\t\t\tProtoBuf.Package pkg = ProtoBuf.Package.parseFrom(data1Stream, EXTENSIONS);\n\t\t\t\t\treturn new KotlinMetadata(pkg, resolver, visitor);\n\t\t\t\t}\n\t\t\t\tcase 3 /* Synthetic Class -> Lambda -> Function */ -> {\n\t\t\t\t\tProtoBuf.Function func = ProtoBuf.Function.parseFrom(data1Stream, EXTENSIONS);\n\t\t\t\t\treturn new KotlinMetadata(func, resolver, visitor);\n\t\t\t\t}\n\t\t\t\tdefault -> throw new UnsupportedOperationException(\"Unsupported @Metadata type: \" + visitor.getKind());\n\t\t\t}\n\t\t} catch (Exception ex) {\n\t\t\t// If the protobuf parser doesn't like the data, there's not really much that can be done about it.\n\t\t\tif (parseFailCount++ < 5)\n\t\t\t\tlogger.warn(\"Kotlin @Metadata 'd1' format failed to parse\", ex);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * @param cls\n\t * \t\tClass model of a kotlin class with a {@code @Metadata} annotation to extract information from.\n\t *\n\t * @return Extracted class model from the {@code @Metadata} or {@code null} if no annotation was found.\n\t */\n\t@Nullable\n\tpublic static KtClass extractKtModel(@Nonnull ClassInfo cls) {\n\t\tif (cls instanceof JvmClassInfo jvm)\n\t\t\treturn extractKtModel(jvm.getClassReader());\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param bytes\n\t * \t\tBytecode of a kotlin class with a {@code @Metadata} annotation to extract information from.\n\t *\n\t * @return Extracted class model from the {@code @Metadata} or {@code null} if no annotation was found.\n\t */\n\t@Nullable\n\tpublic static KtClass extractKtModel(@Nonnull byte[] bytes) {\n\t\tKotlinMetadata meta = extractMetadata(bytes);\n\t\tif (meta == null)\n\t\t\treturn null;\n\t\treturn meta.extractKtModel();\n\t}\n\n\t/**\n\t * @param reader\n\t * \t\tClass reader for a kotlin class with a {@code @Metadata} annotation to extract information from.\n\t *\n\t * @return Extracted class model from the {@code @Metadata} or {@code null} if no annotation was found.\n\t */\n\t@Nullable\n\tpublic static KtClass extractKtModel(@Nonnull ClassReader reader) {\n\t\tKotlinMetadata meta = extractMetadata(reader);\n\t\tif (meta == null)\n\t\t\treturn null;\n\t\treturn meta.extractKtModel();\n\t}\n\n\t/**\n\t * @param visitor\n\t * \t\tVisitor of a kotlin class with a {@code @Metadata} annotation to extract information from.\n\t *\n\t * @return Extracted class model from the {@code @Metadata} or {@code null} if no annotation was found.\n\t */\n\t@Nullable\n\tpublic static KtClass extractKtModel(@Nonnull KotlinMetadataVisitor visitor) {\n\t\tKotlinMetadata meta = extractMetadata(visitor);\n\t\tif (meta == null)\n\t\t\treturn null;\n\t\treturn meta.extractKtModel();\n\t}\n\n\t@Nonnull\n\tprivate static KtFunction mapFunction(@Nonnull MetadataNameResolver res, @Nonnull ProtoBuf.Function function) {\n\t\tString name = function.hasName() ? res.resolve(function.getName()) : null;\n\t\tKtType returnType = function.hasReturnType() ? mapType(res, function.getReturnType()) : null;\n\t\tList<KtVariable> parameters = function.getValueParameterList().stream().map(vp -> mapParameter(res, vp)).toList();\n\t\treturn new KtFunction(name, returnType, parameters);\n\t}\n\n\t@Nonnull\n\tprivate static KtType mapType(@Nonnull MetadataNameResolver res, @Nonnull ProtoBuf.Type type) {\n\t\tString name;\n\t\tif (type.hasClassName())\n\t\t\tname = res.resolve(type.getClassName());\n\t\telse if (type.hasTypeParameter())\n\t\t\tname = res.resolve(type.getTypeParameter());\n\t\telse if (type.hasTypeParameterName())\n\t\t\tname = res.resolve(type.getTypeParameterName());\n\t\telse if (type.hasTypeAliasName())\n\t\t\tname = res.resolve(type.getTypeAliasName());\n\t\telse\n\t\t\tname = \"Unknown\";\n\t\tname = name.replace('.', '$');\n\t\tList<KtType> arguments = newList(type.getArgumentCount());\n\t\tfor (var argument : type.getArgumentList())\n\t\t\targuments.add(mapType(res, argument.getType()));\n\t\tKtNullability nullability;\n\t\tif (type.hasNullable())\n\t\t\tnullability = type.getNullable() ? KtNullability.NULLABLE : KtNullability.NONNULL;\n\t\telse\n\t\t\tnullability = KtNullability.UNKNOWN;\n\t\treturn new KtType(name, arguments, nullability);\n\t}\n\n\t@Nonnull\n\tprivate static KtProperty mapProperty(@Nonnull MetadataNameResolver res, @Nonnull ProtoBuf.Property property) {\n\t\tString name = property.hasName() ? res.resolve(property.getName()) : null;\n\t\tKtType type = property.hasReturnType() ? mapType(res, property.getReturnType()) : null;\n\t\treturn new KtProperty(name, type);\n\t}\n\n\t@Nonnull\n\tprivate static KtVariable mapVariable(@Nonnull MetadataNameResolver res, @Nonnull ProtoBuf.ValueParameter vp) {\n\t\tString name = vp.hasName() ? res.resolve(vp.getName()) : null;\n\t\tKtType type = vp.hasType() ? mapType(res, vp.getType()) : null;\n\t\treturn new KtVariable(name, type);\n\t}\n\n\t@Nonnull\n\tprivate static KtVariable mapParameter(@Nonnull MetadataNameResolver res, @Nonnull ProtoBuf.ValueParameter vp) {\n\t\tString name = vp.hasName() ? res.resolve(vp.getName()) : null;\n\t\tKtType type = vp.hasType() ? mapType(res, vp.getType()) : null;\n\t\treturn new KtParameter(name, type);\n\t}\n\n\t@Nonnull\n\tprivate static <T> List<T> newList(int size) {\n\t\treturn size == 0 ? Collections.emptyList() : new ArrayList<>(size);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/kotlin/model/KtClass.java",
    "content": "package software.coley.recaf.util.kotlin.model;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.kotlin.KotlinMetadata;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\n\n/**\n * Model of a class derived from {@link KotlinMetadata}.\n *\n * @author Matt Coley\n */\npublic non-sealed class KtClass implements KtElement {\n\tprivate final KtClassKind kind;\n\tprivate final int extraFlags;\n\tprivate final String name;\n\tprivate final String companionObjectName;\n\tprivate final List<KtType> superTypes;\n\tprivate final List<KtConstructor> constructors;\n\tprivate final List<KtProperty> properties;\n\tprivate final List<KtFunction> functions;\n\n\t/**\n\t * @param kind\n\t * \t\tKind of class modeled by the metadata model.\n\t * @param extraFlags\n\t * \t\tFlags from the {@code \"xi\"} field of the metadata model.\n\t * @param name\n\t * \t\tInternal name of the class.\n\t * @param companionObjectName\n\t * \t\tInternal name of the companion object.\n\t * @param superTypes\n\t * \t\tList of internal names of extended and implemented types.\n\t * @param constructors\n\t * \t\tList of declared constructors in the class.\n\t * @param properties\n\t * \t\tList of declared properties in the class.\n\t * @param functions\n\t * \t\tList of declared functions in the class.\n\t */\n\tpublic KtClass(@Nonnull KtClassKind kind,\n\t               int extraFlags,\n\t               @Nullable String name,\n\t               @Nullable String companionObjectName,\n\t               @Nonnull List<KtType> superTypes,\n\t               @Nonnull List<KtConstructor> constructors,\n\t               @Nonnull List<KtProperty> properties,\n\t               @Nonnull List<KtFunction> functions) {\n\t\tthis.kind = kind;\n\t\tthis.extraFlags = extraFlags;\n\t\tthis.name = name;\n\t\tthis.companionObjectName = companionObjectName;\n\t\tthis.superTypes = superTypes;\n\t\tthis.constructors = constructors;\n\t\tthis.properties = properties;\n\t\tthis.functions = functions;\n\t}\n\n\t/**\n\t * @return Human legible string displaying the content of this class.\n\t */\n\t@Nonnull\n\tpublic String toPrettyString() {\n\t\tString extendedTypes;\n\t\tif (superTypes.isEmpty())\n\t\t\textendedTypes = \"Object\";\n\t\telse\n\t\t\textendedTypes = superTypes.stream().map(KtType::toString).collect(Collectors.joining(\", \"));\n\t\tStringBuilder body = new StringBuilder();\n\t\tif (!properties.isEmpty())\n\t\t\tbody.append(\"\\n  // ==== Properties\");\n\t\tfor (KtProperty property : properties)\n\t\t\tbody.append(\"\\n    \").append(property).append(';');\n\t\tif (!constructors.isEmpty())\n\t\t\tbody.append(\"\\n  // ==== Constructors\");\n\t\tfor (KtConstructor property : constructors)\n\t\t\tbody.append(\"\\n    \").append(property).append(';');\n\t\tif (!functions.isEmpty())\n\t\t\tbody.append(\"\\n  // ==== Functions\");\n\t\tfor (KtFunction function : functions)\n\t\t\tbody.append(\"\\n    \").append(function).append(';');\n\t\tif (!body.isEmpty())\n\t\t\tbody.append('\\n');\n\t\treturn \"class \" + StringUtil.shortenPath(Objects.requireNonNullElse(name, \"?\")) + \" extends \" + extendedTypes + \" {\" + body + \"}\";\n\t}\n\n\t/**\n\t * @return Kind of class modeled by the metadata model.\n\t */\n\t@Nonnull\n\tpublic KtClassKind getKind() {\n\t\treturn kind;\n\t}\n\n\t/**\n\t * Flag bits:\n\t * <ul>\n\t * <li>0 - this is a multi-file class facade or part, compiled with {@code -Xmultifile-parts-inherit}.</li>\n\t * <li>1 - this class file is compiled by a pre-release version of Kotlin and is not visible to release versions.</li>\n\t * <li>2 - this class file is a compiled Kotlin script source file ({@code .kts}).</li>\n\t * <li>3 - \"strict metadata version semantics\". The metadata of this class file is not supposed to be read by the compiler,\n\t * whose major.minor version is less than the major.minor version of this metadata ({@code [metadataVersion]}).</li>\n\t * <li>4 - this class file is compiled with the new Kotlin compiler backend (JVM IR) introduced in Kotlin 1.4.</li>\n\t * <li>5 - this class file has stable metadata and ABI. This is used only for class files compiled with JVM IR\n\t * (see flag #4) or FIR (#6),and prevents metadata incompatibility diagnostics from being reported where the class is used.</li>\n\t * <li>6 - this class file is compiled with the K2 compiler frontend (FIR). Only valid before metadata version 2.0.0.\n\t * Starting from metadata version 2.0.0, this flag is not set anymore, even though FIR is always used.</li>\n\t * <li>7 - this class is used in the scope of an inline function and implicitly part of the public ABI. Only valid from metadata version 1.6.0.</li>\n\t * </ul>\n\t *\n\t * @return Flags from the {@code \"xi\"} field of the metadata model.\n\t */\n\tpublic int getExtraFlags() {\n\t\treturn extraFlags;\n\t}\n\n\t/**\n\t * @return Internal name of the class.\n\t */\n\t@Nullable\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t/**\n\t * @return Internal name of the companion object.\n\t */\n\t@Nullable\n\tpublic String getCompanionObjectName() {\n\t\treturn companionObjectName;\n\t}\n\n\t/**\n\t * @return List of internal names of extended and implemented types.\n\t */\n\t@Nonnull\n\tpublic List<KtType> getSuperTypes() {\n\t\treturn superTypes;\n\t}\n\n\t/**\n\t * @return List of declared constructors in the class.\n\t */\n\t@Nonnull\n\tpublic List<KtConstructor> getConstructors() {\n\t\treturn constructors;\n\t}\n\n\t/**\n\t * @return List of declared properties in the class.\n\t */\n\t@Nonnull\n\tpublic List<KtProperty> getProperties() {\n\t\treturn properties;\n\t}\n\n\t/**\n\t * @return List of declared functions in the class.\n\t */\n\t@Nonnull\n\tpublic List<KtFunction> getFunctions() {\n\t\treturn functions;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tKtClass that = (KtClass) o;\n\n\t\tif (!Objects.equals(name, that.name)) return false;\n\t\tif (!Objects.equals(companionObjectName, that.companionObjectName))\n\t\t\treturn false;\n\t\tif (!superTypes.equals(that.superTypes)) return false;\n\t\tif (!constructors.equals(that.constructors)) return false;\n\t\tif (!properties.equals(that.properties)) return false;\n\t\treturn functions.equals(that.functions);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = name != null ? name.hashCode() : 0;\n\t\tresult = 31 * result + (companionObjectName != null ? companionObjectName.hashCode() : 0);\n\t\tresult = 31 * result + superTypes.hashCode();\n\t\tresult = 31 * result + constructors.hashCode();\n\t\tresult = 31 * result + properties.hashCode();\n\t\tresult = 31 * result + functions.hashCode();\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn name;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/kotlin/model/KtClassKind.java",
    "content": "package software.coley.recaf.util.kotlin.model;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Enum model of the kotlin metadata {@code \"k\"} field.\n *\n * @author Matt Coley\n */\npublic enum KtClassKind {\n\tCLASS,\n\tFILE,\n\tSYNTHETIC_CLASS,\n\tMULTI_FILE_CLASS_FACADE,\n\tMULTI_FILE_CLASS_PART;\n\n\t/**\n\t * @param kind\n\t * \t\tValue of {@code \"k\"} field in the kotlin metadata.\n\t *\n\t * @return Kind enum for the value.\n\t */\n\t@Nonnull\n\tpublic static KtClassKind fromKindInt(int kind) {\n\t\tif (kind > 0 && kind < values().length)\n\t\t\treturn values()[kind];\n\n\t\t// Fallback\n\t\treturn CLASS;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/kotlin/model/KtConstructor.java",
    "content": "package software.coley.recaf.util.kotlin.model;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.kotlin.KotlinMetadata;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\n\n/**\n * Model of a constructor derived from {@link KotlinMetadata}.\n *\n * @author Matt Coley\n */\npublic class KtConstructor extends KtFunction {\n\tprivate final String className;\n\n\t/**\n\t * @param className\n\t * \t\tName of class being constructed.\n\t * @param parameters\n\t * \t\tConstructor's parameters.\n\t */\n\tpublic KtConstructor(@Nullable String className, @Nonnull List<KtVariable> parameters) {\n\t\tsuper(\"<init>\", KtType.VOID, parameters);\n\t\tthis.className = className;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn StringUtil.shortenPath(Objects.requireNonNullElse(className, \"?\")) +\n\t\t\t\t\"(\" + getParameters().stream().map(KtVariable::toString).collect(Collectors.joining(\", \")) + \")\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/kotlin/model/KtElement.java",
    "content": "package software.coley.recaf.util.kotlin.model;\n\n/**\n * Common interface for top-level kotlin metadata structures.\n *\n * @author Matt Coley\n */\npublic sealed interface KtElement permits KtClass, KtVariable, KtFunction {}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/kotlin/model/KtFunction.java",
    "content": "package software.coley.recaf.util.kotlin.model;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.kotlin.KotlinMetadata;\n\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\n\n/**\n * Model of a function derived from {@link KotlinMetadata}.\n *\n * @author Matt Coley\n */\npublic non-sealed class KtFunction implements KtElement {\n\tprivate final String name;\n\tprivate final KtType returnType;\n\tprivate final List<KtVariable> parameters;\n\n\t/**\n\t * @param name\n\t * \t\tFunction name.\n\t * @param returnType\n\t * \t\tFunction's return type.\n\t * @param parameters\n\t * \t\tFunction's parameters.\n\t */\n\tpublic KtFunction(@Nullable String name, @Nullable KtType returnType, @Nonnull List<KtVariable> parameters) {\n\t\tthis.name = name;\n\t\tthis.returnType = returnType;\n\t\tthis.parameters = parameters;\n\t}\n\n\t/**\n\t * @return Function name.\n\t */\n\t@Nullable\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t/**\n\t * @return Function's return type.\n\t */\n\t@Nullable\n\tpublic KtType getReturnType() {\n\t\treturn returnType;\n\t}\n\n\t/**\n\t * @return Function's parameters.\n\t */\n\t@Nonnull\n\tpublic List<KtVariable> getParameters() {\n\t\treturn parameters;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tKtFunction that = (KtFunction) o;\n\n\t\tif (!Objects.equals(name, that.name)) return false;\n\t\tif (!Objects.equals(returnType, that.returnType)) return false;\n\t\treturn parameters.equals(that.parameters);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = name != null ? name.hashCode() : 0;\n\t\tresult = 31 * result + (returnType != null ? returnType.hashCode() : 0);\n\t\tresult = 31 * result + parameters.hashCode();\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn StringUtil.shortenPath(Objects.requireNonNullElse(returnType, \"?\").toString()) + \" \"\n\t\t\t\t+ Objects.requireNonNullElse(name, \"?\") +\n\t\t\t\t\"(\" + parameters.stream().map(KtVariable::toString).collect(Collectors.joining(\", \")) + \")\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/kotlin/model/KtNullability.java",
    "content": "package software.coley.recaf.util.kotlin.model;\n\n/**\n * Nullability state of some kotlin value.\n *\n * @author Matt Coley\n */\npublic enum KtNullability {\n\tUNKNOWN, NULLABLE, NONNULL\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/kotlin/model/KtParameter.java",
    "content": "package software.coley.recaf.util.kotlin.model;\n\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.kotlin.KotlinMetadata;\n\n/**\n * Model of a function parameter derived from {@link KotlinMetadata}.\n *\n * @author Matt Coley\n */\npublic class KtParameter extends KtVariable {\n\t/**\n\t * @param name\n\t * \t\tParameter name.\n\t * @param type\n\t * \t\tParameter type.\n\t */\n\tpublic KtParameter(@Nullable String name, @Nullable KtType type) {\n\t\tsuper(name, type);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/kotlin/model/KtProperty.java",
    "content": "package software.coley.recaf.util.kotlin.model;\n\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.kotlin.KotlinMetadata;\n\n/**\n * Model of a class property derived from {@link KotlinMetadata}.\n *\n * @author Matt Coley\n */\npublic class KtProperty extends KtVariable {\n\t/**\n\t * @param name\n\t * \t\tProperty name.\n\t * @param type\n\t * \t\tProperty type.\n\t */\n\tpublic KtProperty(@Nullable String name, @Nullable KtType type) {\n\t\tsuper(name, type);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/kotlin/model/KtType.java",
    "content": "package software.coley.recaf.util.kotlin.model;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.util.kotlin.KotlinMetadata;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Model of a type reference derived from {@link KotlinMetadata}.\n *\n * @author Matt Coley\n */\npublic class KtType {\n\tpublic static final KtType VOID = new KtType(\"void\", Collections.emptyList(), KtNullability.NONNULL);\n\tprivate final String name;\n\tprivate final List<KtType> arguments;\n\tprivate final KtNullability nullability;\n\n\t/**\n\t * @param name\n\t * \t\tInternal name of the type.\n\t * @param arguments\n\t * \t\tGeneric type arguments.\n\t * @param nullability\n\t * \t\tNullability state of the type in this usage context.\n\t */\n\tpublic KtType(@Nonnull String name, @Nonnull List<KtType> arguments, @Nonnull KtNullability nullability) {\n\t\tthis.name = name;\n\t\tthis.arguments = arguments;\n\t\tthis.nullability = nullability;\n\t}\n\n\t@Nonnull\n\tpublic static String toDescriptor(@Nullable KtVariable variable) {\n\t\treturn toDescriptor(variable == null ? null : variable.getType());\n\t}\n\n\t@Nonnull\n\tpublic static String toDescriptor(@Nullable KtFunction function) {\n\t\tif (function == null)\n\t\t\treturn \"()Ljava/lang/Object;\";\n\t\tString args = function.getParameters().stream()\n\t\t\t\t.map(KtType::toDescriptor)\n\t\t\t\t.collect(Collectors.joining());\n\t\tString ret = toDescriptor(function.getReturnType());\n\t\treturn \"(\" + args + \")\" + ret;\n\t}\n\n\t@Nonnull\n\tpublic static String toDescriptor(@Nullable KtType type) {\n\t\tif (type == null)\n\t\t\treturn Types.OBJECT_TYPE.getDescriptor();\n\n\t\tString name = type.name;\n\t\treturn \"L\" + name + \";\";\n\t}\n\n\t/**\n\t * @return Internal name of the type.\n\t */\n\t@Nonnull\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t/**\n\t * @return Generic type arguments.\n\t */\n\t@Nullable\n\tpublic List<KtType> getArguments() {\n\t\treturn arguments;\n\t}\n\n\t/**\n\t * @return Nullability state of the type in this usage context.\n\t */\n\t@Nonnull\n\tpublic KtNullability getNullability() {\n\t\treturn nullability;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tKtType that = (KtType) o;\n\n\t\tif (!name.equals(that.name)) return false;\n\t\tif (!arguments.equals(that.arguments)) return false;\n\t\treturn nullability == that.nullability;\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = name.hashCode();\n\t\tresult = 31 * result + arguments.hashCode();\n\t\tresult = 31 * result + nullability.hashCode();\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\tString nullabilitySuffix = nullability == KtNullability.NULLABLE ? \"?\" : \"\";\n\t\tif (arguments.isEmpty())\n\t\t\treturn StringUtil.shortenPath(name) + nullabilitySuffix;\n\t\treturn StringUtil.shortenPath(name) + \"<\" + arguments.stream().map(KtType::toString).collect(Collectors.joining(\", \")) + \">\" + nullabilitySuffix;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/kotlin/model/KtVariable.java",
    "content": "package software.coley.recaf.util.kotlin.model;\n\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.kotlin.KotlinMetadata;\n\nimport java.util.Objects;\n\n/**\n * Model of a variable derived from {@link KotlinMetadata}.\n *\n * @author Matt Coley\n */\npublic non-sealed class KtVariable implements KtElement {\n\tprivate final String name;\n\tprivate final KtType type;\n\n\t/**\n\t * @param name\n\t * \t\tVariable name.\n\t * @param type\n\t * \t\tVariable type.\n\t */\n\tpublic KtVariable(@Nullable String name, @Nullable KtType type) {\n\t\tthis.name = name;\n\t\tthis.type = type;\n\t}\n\n\t/**\n\t * @return Variable name.\n\t */\n\t@Nullable\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t/**\n\t * @return Variable type.\n\t */\n\t@Nullable\n\tpublic KtType getType() {\n\t\treturn type;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tKtVariable that = (KtVariable) o;\n\n\t\tif (!Objects.equals(name, that.name)) return false;\n\t\treturn Objects.equals(type, that.type);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = name != null ? name.hashCode() : 0;\n\t\tresult = 31 * result + (type != null ? type.hashCode() : 0);\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\tString typeStr = StringUtil.shortenPath(Objects.requireNonNullElse(type, \"?\").toString());\n\t\tString nameStr = Objects.requireNonNullElse(name, \"?\");\n\t\treturn typeStr + \" \" + nameStr;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/threading/Batch.java",
    "content": "package software.coley.recaf.util.threading;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Outlines a model to wrap multiple tasks into a single execution.\n *\n * @author Matt Coley\n */\npublic interface Batch {\n\t/**\n\t * Run all registered tasks. This will remove all executed tasks once completed.\n\t */\n\tvoid execute();\n\n\t/**\n\t * Run the oldest added task. This will remove the task once executed.\n\t */\n\tvoid executeOldest();\n\n\t/**\n\t * Run the newest added task. This will remove the task once executed.\n\t */\n\tvoid executeNewest();\n\n\t/**\n\t * @param runnable\n\t * \t\tTask to execute.\n\t */\n\tvoid add(@Nonnull Runnable runnable);\n\n\t/**\n\t * Removes all tasks.\n\t */\n\tvoid clear();\n\n\t/**\n\t * Removes the oldest task.\n\t */\n\tvoid removeOldest();\n\n\t/**\n\t * Removes the newest task.\n\t */\n\tvoid removeNewest();\n\n\t/**\n\t * @return {@code true} when there are no tasks in the batch.\n\t */\n\tboolean isEmpty();\n\n\t/**\n\t * @return Number of pending tasks.\n\t */\n\tint size();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/threading/CountDown.java",
    "content": "package software.coley.recaf.util.threading;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.locks.AbstractQueuedSynchronizer;\n\n/**\n * CountDown thread synchronizer.\n *\n * @author xDark\n */\npublic final class CountDown {\n\tprivate final Impl impl = new Impl();\n\n\t/**\n\t * Waits for countdown to finish.\n\t *\n\t * @throws InterruptedException\n\t * \t\tIf thread got interrupted.\n\t */\n\tpublic void await() throws InterruptedException {\n\t\timpl.acquireSharedInterruptibly(1);\n\t}\n\n\t/**\n\t * Waits for countdown to finish.\n\t *\n\t * @param timeout\n\t * \t\tThe maximum period of time to wait.\n\t * @param unit\n\t * \t\tTime unit.\n\t *\n\t * @throws InterruptedException\n\t * \t\tIf thread got interrupted.\n\t */\n\tpublic boolean await(long timeout, TimeUnit unit) throws InterruptedException {\n\t\treturn impl.tryAcquireSharedNanos(1, unit.toNanos(timeout));\n\t}\n\n\t/**\n\t * Decrements the count by 1.\n\t */\n\tpublic void release() {\n\t\timpl.releaseShared(1);\n\t}\n\n\t/**\n\t * Increments the count by 1.\n\t */\n\tpublic void register() {\n\t\timpl.bulkRegister(1);\n\t}\n\n\t/**\n\t * Increments the count.\n\t *\n\t * @param count\n\t * \t\tNumber to increment the count by.\n\t */\n\tpublic void bulkRegister(int count) {\n\t\timpl.bulkRegister(count);\n\t}\n\n\tprivate static final class Impl extends AbstractQueuedSynchronizer {\n\n\t\tvoid bulkRegister(int count) {\n\t\t\twhile (true) {\n\t\t\t\tint oldCount = getState();\n\t\t\t\tint newCount = oldCount + count;\n\t\t\t\tif (compareAndSetState(oldCount, newCount)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tprotected int tryAcquireShared(int acquires) {\n\t\t\treturn (getState() == 0) ? 1 : -1;\n\t\t}\n\n\t\t@Override\n\t\tprotected boolean tryReleaseShared(int releases) {\n\t\t\twhile (true) {\n\t\t\t\tint state = getState();\n\t\t\t\tif (state == 0) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tint newState = state - 1;\n\t\t\t\tif (compareAndSetState(state, newState)) {\n\t\t\t\t\treturn newState == 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/threading/DirectBatch.java",
    "content": "package software.coley.recaf.util.threading;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A batch implementation that directly runs provided tasks on the calling thread.\n *\n * @author Matt Coley\n */\npublic class DirectBatch implements Batch {\n\tprivate final List<Runnable> tasks = new ArrayList<>();\n\n\t@Override\n\tpublic void add(@Nonnull Runnable runnable) {\n\t\tsynchronized (tasks) {\n\t\t\ttasks.add(runnable);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void clear() {\n\t\tsynchronized (tasks) {\n\t\t\ttasks.clear();\n\t\t}\n\t}\n\n\t@Override\n\tpublic void removeOldest() {\n\t\tsynchronized (tasks) {\n\t\t\ttasks.removeFirst();\n\t\t}\n\t}\n\n\t@Override\n\tpublic void removeNewest() {\n\t\tsynchronized (tasks) {\n\t\t\ttasks.removeLast();\n\t\t}\n\t}\n\n\t@Override\n\tpublic void execute() {\n\t\tList<Runnable> tasksCopy;\n\t\tsynchronized (tasks) {\n\t\t\ttasksCopy = new ArrayList<>(tasks);\n\t\t\ttasks.clear();\n\t\t}\n\t\tfor (Runnable task : tasksCopy)\n\t\t\ttask.run();\n\t}\n\n\t@Override\n\tpublic void executeOldest() {\n\t\tRunnable task;\n\t\tsynchronized (tasks) {\n\t\t\tif (tasks.isEmpty())\n\t\t\t\treturn;\n\t\t\ttask = tasks.removeFirst();\n\t\t}\n\t\ttask.run();\n\t}\n\n\t@Override\n\tpublic void executeNewest() {\n\t\tRunnable task;\n\t\tsynchronized (tasks) {\n\t\t\tif (tasks.isEmpty())\n\t\t\t\treturn;\n\t\t\ttask = tasks.removeLast();\n\t\t}\n\t\ttask.run();\n\t}\n\n\t@Override\n\tpublic boolean isEmpty() {\n\t\tsynchronized (tasks) {\n\t\t\treturn tasks.isEmpty();\n\t\t}\n\t}\n\n\t@Override\n\tpublic int size() {\n\t\tsynchronized (tasks) {\n\t\t\treturn tasks.size();\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/threading/ExecutorServiceDelegate.java",
    "content": "package software.coley.recaf.util.threading;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.concurrent.*;\n\n/**\n * Delegating impl of {@link ExecutorService} that also wraps tasks with {@link ThreadUtil#wrap(Runnable)}.\n *\n * @author Matt Coley\n */\npublic class ExecutorServiceDelegate implements ExecutorService {\n\tprivate final ExecutorService delegate;\n\n\t/**\n\t * @param delegate\n\t * \t\tDelegate service to pass to.\n\t */\n\tpublic ExecutorServiceDelegate(@Nonnull ExecutorService delegate) {\n\t\tthis.delegate = delegate;\n\t}\n\n\t@Override\n\tpublic void shutdown() {\n\t\tdelegate.shutdown();\n\t}\n\n\t@Override\n\tpublic List<Runnable> shutdownNow() {\n\t\treturn delegate.shutdownNow();\n\t}\n\n\t@Override\n\tpublic boolean isShutdown() {\n\t\treturn delegate.isShutdown();\n\t}\n\n\t@Override\n\tpublic boolean isTerminated() {\n\t\treturn delegate.isTerminated();\n\t}\n\n\t@Override\n\tpublic boolean awaitTermination(long timeout, @Nonnull TimeUnit unit) throws InterruptedException {\n\t\treturn delegate.awaitTermination(timeout, unit);\n\t}\n\n\t@Override\n\tpublic <T> Future<T> submit(@Nonnull Callable<T> task) {\n\t\treturn delegate.submit(ThreadUtil.wrap(task));\n\t}\n\n\t@Override\n\tpublic <T> Future<T> submit(@Nonnull Runnable task, T result) {\n\t\treturn delegate.submit(ThreadUtil.wrap(task), result);\n\t}\n\n\t@Override\n\tpublic Future<?> submit(@Nonnull Runnable task) {\n\t\treturn delegate.submit(ThreadUtil.wrap(task));\n\t}\n\n\t@Override\n\tpublic <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {\n\t\treturn delegate.invokeAll(tasks.stream().map(ThreadUtil::wrap).toList());\n\t}\n\n\t@Override\n\tpublic <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, @Nonnull TimeUnit unit) throws InterruptedException {\n\t\treturn delegate.invokeAll(tasks.stream().map(ThreadUtil::wrap).toList(), timeout, unit);\n\t}\n\n\t@Override\n\tpublic <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {\n\t\treturn delegate.invokeAny(tasks.stream().map(ThreadUtil::wrap).toList());\n\t}\n\n\t@Override\n\tpublic <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, @Nonnull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {\n\t\treturn delegate.invokeAny(tasks.stream().map(ThreadUtil::wrap).toList(), timeout, unit);\n\t}\n\n\t@Override\n\tpublic void execute(@Nonnull Runnable command) {\n\t\tdelegate.execute(ThreadUtil.wrap(command));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/threading/PhasingExecutorService.java",
    "content": "package software.coley.recaf.util.threading;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.Collectors;\n\n/**\n * Executor service that uses {@link Phaser} to track\n * currently running tasks.\n * \n * @author xDark\n */\npublic final class PhasingExecutorService implements ExecutorService {\n\tprivate final AtomicBoolean shutdown = new AtomicBoolean();\n\tprivate final Phaser phaser = new Phaser(1);\n\tprivate final ExecutorService delegate;\n\n\t/**\n\t * @param delegate\n\t * \t\tBacking executor service.\n\t */\n\tpublic PhasingExecutorService(@Nonnull ExecutorService delegate) {\n\t\tthis.delegate = delegate;\n\t}\n\n\t@Override\n\tpublic void shutdown() {\n\t\tif (shutdown.compareAndSet(false, true)) {\n\t\t\tphaser.arriveAndDeregister();\n\t\t}\n\t}\n\n\t@Override\n\tpublic List<Runnable> shutdownNow() {\n\t\tthrow new UnsupportedOperationException();\n\t}\n\n\t@Override\n\tpublic boolean isShutdown() {\n\t\treturn phaser.isTerminated();\n\t}\n\n\t@Override\n\tpublic boolean isTerminated() {\n\t\treturn phaser.isTerminated();\n\t}\n\n\t@Override\n\tpublic boolean awaitTermination(long timeout, @Nonnull TimeUnit unit) throws InterruptedException {\n\t\ttry {\n\t\t\treturn phaser.awaitAdvanceInterruptibly(0, timeout, unit) <= 0;\n\t\t} catch (TimeoutException e) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic <T> Future<T> submit(@Nonnull Callable<T> task) {\n\t\tphaser.register();\n\t\treturn delegate.submit(wrap(task));\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic <T> Future<T> submit(@Nonnull Runnable task, T result) {\n\t\tphaser.register();\n\t\treturn delegate.submit(wrap(task), result);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Future<?> submit(@Nonnull Runnable task) {\n\t\tphaser.register();\n\t\treturn delegate.submit(wrap(task));\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {\n\t\tphaser.bulkRegister(tasks.size());\n\t\treturn delegate.invokeAll(tasks.stream().map(this::wrap).collect(Collectors.toList()));\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, @Nonnull TimeUnit unit) throws InterruptedException {\n\t\tphaser.bulkRegister(tasks.size());\n\t\treturn delegate.invokeAll(tasks.stream().map(this::wrap).collect(Collectors.toList()), timeout, unit);\n\t}\n\n\t@Override\n\tpublic <T> T invokeAny(@Nonnull Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {\n\t\tthrow new UnsupportedOperationException();\n\t}\n\n\t@Override\n\tpublic <T> T invokeAny(@Nonnull Collection<? extends Callable<T>> tasks, long timeout, @Nonnull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {\n\t\tthrow new UnsupportedOperationException();\n\t}\n\n\t@Override\n\tpublic void execute(@Nonnull Runnable command) {\n\t\tphaser.register();\n\t\tdelegate.execute(wrap(command));\n\t}\n\t\n\tprivate Runnable wrap(Runnable r) {\n\t\treturn () -> {\n\t\t\ttry {\n\t\t\t\tThreadUtil.wrap(r).run();\n\t\t\t} finally {\n\t\t\t\tphaser.arriveAndDeregister();\n\t\t\t}\n\t\t};\n\t}\n\t\n\tprivate <T> Callable<T> wrap(Callable<T> c) {\n\t\treturn () -> {\n\t\t\ttry {\n\t\t\t\treturn ThreadUtil.wrap(c).call();\n\t\t\t} finally {\n\t\t\t\tphaser.arriveAndDeregister();\n\t\t\t}\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/threading/ScheduledExecutorServiceDelegate.java",
    "content": "package software.coley.recaf.util.threading;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Delegating impl of {@link ScheduledExecutorService} that also wraps tasks with {@link ThreadUtil#wrap(Runnable)}.\n *\n * @author Matt Coley\n */\npublic class ScheduledExecutorServiceDelegate extends ExecutorServiceDelegate implements ScheduledExecutorService {\n\tprivate final ScheduledExecutorService delegate;\n\n\t/**\n\t * @param delegate\n\t * \t\tDelegate service to pass to.\n\t */\n\tpublic ScheduledExecutorServiceDelegate(@Nonnull ScheduledExecutorService delegate) {\n\t\tsuper(delegate);\n\t\tthis.delegate = delegate;\n\t}\n\n\t@Override\n\tpublic ScheduledFuture<?> schedule(@Nonnull Runnable command, long delay, @Nonnull TimeUnit unit) {\n\t\treturn delegate.schedule(ThreadUtil.wrap(command), delay, unit);\n\t}\n\n\t@Override\n\tpublic <V> ScheduledFuture<V> schedule(@Nonnull Callable<V> callable, long delay, @Nonnull TimeUnit unit) {\n\t\treturn delegate.schedule(ThreadUtil.wrap(callable), delay, unit);\n\t}\n\n\t@Override\n\tpublic ScheduledFuture<?> scheduleAtFixedRate(@Nonnull Runnable command, long initialDelay, long period, @Nonnull TimeUnit unit) {\n\t\treturn delegate.scheduleAtFixedRate(ThreadUtil.wrap(command), initialDelay, period, unit);\n\t}\n\n\t@Override\n\tpublic ScheduledFuture<?> scheduleWithFixedDelay(@Nonnull Runnable command, long initialDelay, long delay, @Nonnull TimeUnit unit) {\n\t\treturn delegate.scheduleWithFixedDelay(ThreadUtil.wrap(command), initialDelay, delay, unit);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/threading/ThreadPoolFactory.java",
    "content": "package software.coley.recaf.util.threading;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ThreadFactory;\n\n/**\n * Wrapper for {@link ExecutorService} with easier inline configuration.\n *\n * @author Matt Coley\n */\npublic class ThreadPoolFactory {\n\tprivate static final int MAX = Math.max(2, Runtime.getRuntime().availableProcessors() - 2);\n\n\t/**\n\t * @param name\n\t * \t\tThread pool name.\n\t *\n\t * @return {@link Executors#newFixedThreadPool(int)}.\n\t */\n\tpublic static ExecutorService newFixedThreadPool(String name) {\n\t\treturn newFixedThreadPool(name, true);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tThread pool name.\n\t * @param daemon\n\t * \t\tFlag to set created threads as daemon threads.\n\t *\n\t * @return {@link Executors#newFixedThreadPool(int)}.\n\t */\n\tpublic static ExecutorService newFixedThreadPool(String name, boolean daemon) {\n\t\treturn newFixedThreadPool(name, MAX, daemon);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tThread pool name.\n\t * @param size\n\t * \t\tThread pool size.\n\t * @param daemon\n\t * \t\tFlag to set created threads as daemon threads.\n\t *\n\t * @return {@link Executors#newFixedThreadPool(int)}.\n\t */\n\tpublic static ExecutorService newFixedThreadPool(String name, int size, boolean daemon) {\n\t\treturn new ExecutorServiceDelegate(Executors.newFixedThreadPool(Math.min(MAX, size), new FactoryImpl(name, daemon)));\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tThread pool name.\n\t *\n\t * @return {@link Executors#newCachedThreadPool()}.\n\t */\n\tpublic static ExecutorService newCachedThreadPool(String name) {\n\t\treturn newCachedThreadPool(name, true);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tThread pool name.\n\t * @param daemon\n\t * \t\tFlag to set created threads as daemon threads.\n\t *\n\t * @return {@link Executors#newCachedThreadPool()}.\n\t */\n\tpublic static ExecutorService newCachedThreadPool(String name, boolean daemon) {\n\t\treturn new ExecutorServiceDelegate(Executors.newCachedThreadPool(new FactoryImpl(name, daemon)));\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tThread pool name.\n\t *\n\t * @return {@link Executors#newSingleThreadExecutor()}.\n\t */\n\tpublic static ExecutorService newSingleThreadExecutor(String name) {\n\t\treturn newSingleThreadExecutor(name, true);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tThread pool name.\n\t * @param daemon\n\t * \t\tFlag to set created threads as daemon threads.\n\t *\n\t * @return {@link Executors#newSingleThreadExecutor()}.\n\t */\n\tpublic static ExecutorService newSingleThreadExecutor(String name, boolean daemon) {\n\t\treturn new ExecutorServiceDelegate(Executors.newSingleThreadExecutor(new FactoryImpl(name, daemon)));\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tThread pool name.\n\t *\n\t * @return {@link Executors#newScheduledThreadPool(int)}.\n\t */\n\tpublic static ScheduledExecutorService newScheduledThreadPool(String name) {\n\t\treturn newScheduledThreadPool(name, true);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tThread pool name.\n\t * @param daemon\n\t * \t\tFlag to set created threads as daemon threads.\n\t *\n\t * @return {@link Executors#newScheduledThreadPool(int)}.\n\t */\n\tpublic static ScheduledExecutorService newScheduledThreadPool(String name, boolean daemon) {\n\t\treturn newScheduledThreadPool(name, MAX, daemon);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tThread pool name.\n\t * @param size\n\t * \t\tThread pool size.\n\t * @param daemon\n\t * \t\tFlag to set created threads as daemon threads.\n\t *\n\t * @return {@link Executors#newScheduledThreadPool(int)}.\n\t */\n\tpublic static ScheduledExecutorService newScheduledThreadPool(String name, int size, boolean daemon) {\n\t\treturn new ScheduledExecutorServiceDelegate(Executors.newScheduledThreadPool(size, new FactoryImpl(name, daemon)));\n\t}\n\n\tprivate static class FactoryImpl implements ThreadFactory {\n\t\tprivate static int fidCounter;\n\t\tprivate final String name;\n\t\tprivate final boolean daemon;\n\t\tprivate final int fid = fidCounter++;\n\t\tprivate int tid = 0;\n\n\t\tpublic FactoryImpl(String name, boolean daemon) {\n\t\t\tthis.name = name;\n\t\t\tthis.daemon = daemon;\n\t\t}\n\n\t\t@Override\n\t\tpublic Thread newThread(@Nonnull Runnable r) {\n\t\t\tThread thread = new Thread(r);\n\t\t\tthread.setDaemon(daemon);\n\t\t\tthread.setName(\"Recaf-\" + name + \" [\" + fid + \":\" + tid++ + \"]\");\n\t\t\treturn thread;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/threading/ThreadUtil.java",
    "content": "package software.coley.recaf.util.threading;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\n\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\nimport static software.coley.recaf.util.threading.ThreadPoolFactory.newScheduledThreadPool;\n\n/**\n * Common threading utility. Used for <i>\"miscellaneous\"</i> threads.\n * Larger thread operations should create their own pools using {@link ThreadPoolFactory}.\n *\n * @author Matt Coley\n */\npublic class ThreadUtil {\n\tprivate static final Logger logger = Logging.get(ThreadUtil.class);\n\tprivate static final ScheduledExecutorService scheduledService = newScheduledThreadPool(\"misc\");\n\n\t/**\n\t * @param action\n\t * \t\tRunnable to start in new thread.\n\t *\n\t * @return Thread future.\n\t */\n\t@Nonnull\n\tpublic static CompletableFuture<?> run(@Nonnull Runnable action) {\n\t\treturn CompletableFuture.runAsync(wrap(action), scheduledService);\n\t}\n\n\t/**\n\t * @param action\n\t * \t\tSupplier to start in new thread.\n\t * @param fallback\n\t * \t\tFallback value if the supplier encounters a failure.\n\t *\n\t * @return Thread future.\n\t */\n\t@Nonnull\n\tpublic static <T> CompletableFuture<T> supply(@Nonnull Supplier<T> action, @Nullable T fallback) {\n\t\treturn CompletableFuture.supplyAsync(wrap(action, fallback), scheduledService);\n\t}\n\n\t/**\n\t * @param delayMs\n\t * \t\tDelay to wait in milliseconds.\n\t * @param action\n\t * \t\tRunnable to start in new thread.\n\t *\n\t * @return Scheduled future.\n\t */\n\t@Nonnull\n\tpublic static CompletableFuture<?> runDelayed(long delayMs, @Nonnull Runnable action) {\n\t\tCompletableFuture<?> future = new CompletableFuture<>();\n\t\tscheduledService.schedule(() -> {\n\t\t\ttry {\n\t\t\t\taction.run();\n\t\t\t\tfuture.complete(null);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Unhandled exception on thread '{}'\", Thread.currentThread().getName(), t);\n\t\t\t\tfuture.completeExceptionally(t);\n\t\t\t}\n\t\t}, delayMs, TimeUnit.MILLISECONDS);\n\t\treturn future;\n\t}\n\n\t/**\n\t * Run a given action with a timeout.\n\t *\n\t * @param millis\n\t * \t\tTimeout in milliseconds.\n\t * @param action\n\t * \t\tRunnable to execute.\n\t *\n\t * @return {@code true} When thread completed before time.\n\t */\n\tpublic static boolean timeout(int millis, @Nonnull Runnable action) {\n\t\ttry {\n\t\t\tFuture<?> future = run(action);\n\t\t\treturn timeout(millis, future);\n\t\t} catch (Throwable t) {\n\t\t\t// Can be thrown by execution timeout\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Give a thread future a time limit.\n\t *\n\t * @param millis\n\t * \t\tTimeout in milliseconds.\n\t * @param future\n\t * \t\tThread future being run.\n\t *\n\t * @return {@code true} When thread completed before time.\n\t */\n\tpublic static boolean timeout(int millis, @Nonnull Future<?> future) {\n\t\ttry {\n\t\t\tfuture.get(millis, TimeUnit.MILLISECONDS);\n\t\t\treturn true;\n\t\t} catch (TimeoutException e) {\n\t\t\t// Expected: Timeout\n\t\t\treturn false;\n\t\t} catch (Throwable t) {\n\t\t\t// Other error\n\t\t\treturn true;\n\t\t}\n\t}\n\n\n\t/**\n\t * Give a thread pool a time limit to finish all of its threads.\n\t *\n\t * @param millis\n\t * \t\tTimeout in milliseconds.\n\t * @param service\n\t * \t\tThread pool being used.\n\t *\n\t * @return {@code true} when thread pool completed before time.\n\t * {@code false} when the thread pool did not finish, or was interrupted.\n\t */\n\tpublic static boolean timeout(int millis, @Nonnull ExecutorService service) {\n\t\ttry {\n\t\t\t// Shutdown so no new tasks are completed, but existing ones will finish.\n\t\t\tservice.shutdown();\n\t\t\t// Wait until they finish. The prior shutdown request is required.\n\t\t\t// Calling 'awaitTermination' without calling shutdown will hang forever.\n\t\t\treturn service.awaitTermination(millis, TimeUnit.MILLISECONDS);\n\t\t} catch (InterruptedException e) {\n\t\t\t// A thread was interrupted so operation did not complete.\n\t\t\treturn false;\n\t\t} catch (Throwable t) {\n\t\t\t// Other error\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/**\n\t * @param futures\n\t * \t\tAll futures to create a wrapper future of.\n\t *\n\t * @return Wrapper for all futures.\n\t */\n\t@Nonnull\n\tpublic static CompletableFuture<Void> allOf(@Nonnull CompletableFuture<?>... futures) {\n\t\tAtomicBoolean thrown = new AtomicBoolean();\n\t\tCompletableFuture<Void> allOf = CompletableFuture.allOf(futures);\n\t\tfor (CompletableFuture<?> f : futures) {\n\t\t\tf.exceptionally(t -> {\n\t\t\t\tif (thrown.compareAndSet(false, true)) {\n\t\t\t\t\tfor (CompletableFuture<?> f1 : futures) {\n\t\t\t\t\t\tf1.completeExceptionally(t);\n\t\t\t\t\t}\n\t\t\t\t\tallOf.completeExceptionally(t);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t});\n\t\t}\n\t\treturn allOf;\n\t}\n\n\t/**\n\t * @param future\n\t * \t\tThread future being run.\n\t *\n\t * @return {@code true} on completion. {@code false} for interruption.\n\t */\n\tpublic static boolean blockUntilComplete(@Nonnull Future<?> future) {\n\t\treturn timeout(Integer.MAX_VALUE, future);\n\t}\n\n\t/**\n\t * @param service\n\t * \t\tThread pool being used.\n\t *\n\t * @return {@code true} on completion. {@code false} for interruption.\n\t */\n\tpublic static boolean blockUntilComplete(@Nonnull ExecutorService service) {\n\t\treturn timeout(Integer.MAX_VALUE, service);\n\t}\n\n\t/**\n\t * Submits a periodic action that becomes enabled first after the given initial delay,\n\t * and subsequently with the given period.\n\t *\n\t * @param task\n\t * \t\tTask to execute.\n\t * @param initialDelay\n\t * \t\tThe time to delay first execution.\n\t * @param period\n\t * \t\tThe period between successive executions.\n\t * @param unit\n\t * \t\tThe time unit of the initialDelay\n\t * \t\tand period parameters.\n\t *\n\t * @return future representing completion of the tasks.\n\t *\n\t * @see ScheduledExecutorService#scheduleAtFixedRate(Runnable, long, long, TimeUnit)\n\t */\n\t@Nonnull\n\tpublic static ScheduledFuture<?> scheduleAtFixedRate(@Nonnull Runnable task, long initialDelay,\n\t                                                     long period, @Nonnull TimeUnit unit) {\n\t\treturn scheduledService.scheduleAtFixedRate(task, initialDelay, period, unit);\n\t}\n\n\t/**\n\t * Wrap action to handle error logging.\n\t * <br>\n\t * <b>IMPORTANT:</b> This does <i>not work</i> with {@link ThreadFactory} for all thread-pool implementations.\n\t * To support any {@link ExecutorService} type, instead wrap it with {@link ExecutorServiceDelegate} or\n\t * {@link ScheduledExecutorServiceDelegate} which will delegate passed {@link Runnable} tasks here.\n\t *\n\t * @param action\n\t * \t\tAction to run.\n\t *\n\t * @return Wrapper runnable.\n\t */\n\t@Nonnull\n\tpublic static Runnable wrap(@Nonnull Runnable action) {\n\t\treturn () -> {\n\t\t\ttry {\n\t\t\t\taction.run();\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Unhandled exception on thread '{}'\", Thread.currentThread().getName(), t);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Wrap action to handle error logging.\n\t * <br>\n\t * <b>IMPORTANT:</b> This does <i>not work</i> with {@link ThreadFactory} for all thread-pool implementations.\n\t * To support any {@link ExecutorService} type, instead wrap it with {@link ExecutorServiceDelegate} or\n\t * {@link ScheduledExecutorServiceDelegate} which will delegate passed {@link Consumer} tasks here.\n\t *\n\t * @param action\n\t * \t\tConsumer to run.\n\t * @param <T>\n\t * \t\tConsumer item type.\n\t *\n\t * @return Wrapper consumer.\n\t */\n\t@Nonnull\n\tpublic static <T> Consumer<T> wrap(@Nonnull Consumer<T> action) {\n\t\treturn in -> {\n\t\t\ttry {\n\t\t\t\taction.accept(in);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Unhandled exception on thread '{}'\", Thread.currentThread().getName(), t);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Wrap action to handle error logging.\n\t * <br>\n\t * <b>IMPORTANT:</b> This does <i>not work</i> with {@link ThreadFactory} for all thread-pool implementations.\n\t * To support any {@link ExecutorService} type, instead wrap it with {@link ExecutorServiceDelegate} or\n\t * {@link ScheduledExecutorServiceDelegate} which will delegate passed {@link Supplier} tasks here.\n\t *\n\t * @param action\n\t * \t\tSupplier to provide a value.\n\t * @param fallback\n\t * \t\tFallback value when the supplier action encounters an exception.\n\t * @param <T>\n\t * \t\tSupplier item type.\n\t *\n\t * @return Wrapper consumer.\n\t */\n\t@Nonnull\n\tpublic static <T> Supplier<T> wrap(@Nonnull Supplier<T> action, @Nullable T fallback) {\n\t\treturn () -> {\n\t\t\ttry {\n\t\t\t\treturn action.get();\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Unhandled exception on thread '{}'\", Thread.currentThread().getName(), t);\n\t\t\t\treturn fallback;\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Wrap action to handle error logging.\n\t * <br>\n\t * <b>IMPORTANT:</b> This does <i>not work</i> with {@link ThreadFactory} for all thread-pool implementations.\n\t * To support any {@link ExecutorService} type, instead wrap it with {@link ExecutorServiceDelegate} or\n\t * {@link ScheduledExecutorServiceDelegate} which will delegate passed {@link Callable} tasks here.\n\t *\n\t * @param action\n\t * \t\tAction to run.\n\t *\n\t * @return Wrapper callable.\n\t */\n\t@Nonnull\n\tpublic static <T> Callable<T> wrap(@Nonnull Callable<T> action) {\n\t\treturn () -> {\n\t\t\ttry {\n\t\t\t\treturn action.call();\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Unhandled exception on thread '{}'\", Thread.currentThread().getName(), t);\n\t\t\t\tthrow t;\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Wraps our executor service into phasing executor that allows to wait for completion of all tasks passed into it.\n\t *\n\t * @return phasing executor service.\n\t *\n\t * @see PhasingExecutorService\n\t */\n\t@Nonnull\n\tpublic static ExecutorService phasingService() {\n\t\treturn phasingService(scheduledService);\n\t}\n\n\t/**\n\t * Wraps our executor service into phasing executor that allows to wait for completion of all tasks passed into it.\n\t *\n\t * @param delegate\n\t * \t\tExecutor to delegate thread management to.\n\t *\n\t * @return phasing executor service.\n\t *\n\t * @see PhasingExecutorService\n\t */\n\t@Nonnull\n\tpublic static ExecutorService phasingService(@Nonnull ExecutorService delegate) {\n\t\treturn new PhasingExecutorService(delegate);\n\t}\n\n\t/**\n\t * @return Backing executor.\n\t */\n\t@Nonnull\n\tpublic static ScheduledExecutorService executor() {\n\t\treturn scheduledService;\n\t}\n\n\t/**\n\t * @param t\n\t * \t\tException thrown.\n\t * @param <V>\n\t * \t\tFuture type.\n\t *\n\t * @return Future of a failed execution due to a thrown error.\n\t */\n\t@Nonnull\n\tpublic static <V> CompletableFuture<V> failedFuture(@Nonnull Throwable t) {\n\t\tCompletableFuture<V> future = new CompletableFuture<>();\n\t\tfuture.completeExceptionally(t);\n\t\treturn future;\n\t}\n\n\t/**\n\t * Shutdowns executors.\n\t */\n\tpublic static void shutdown() {\n\t\tlogger.trace(\"Shutting misc executors\");\n\t\tscheduledService.shutdown();\n\t}\n\n\t/**\n\t * @return New task batch that executes all actions through the given executor.\n\t */\n\t@Nonnull\n\tpublic static Batch batch(@Nonnull Executor executor) {\n\t\treturn new ExecutorBatch(executor);\n\t}\n\n\t/**\n\t * Batch implementation that executes all tasks on a given executor.\n\t */\n\tprivate static class ExecutorBatch extends DirectBatch {\n\t\tprivate final Executor executor;\n\n\t\tprivate ExecutorBatch(@Nonnull Executor executor) {\n\t\t\tthis.executor = executor;\n\t\t}\n\n\t\t@Override\n\t\tpublic void execute() {\n\t\t\tsubmit(super::execute);\n\t\t}\n\n\t\t@Override\n\t\tpublic void executeOldest() {\n\t\t\tsubmit(super::executeOldest);\n\t\t}\n\n\t\t@Override\n\t\tpublic void executeNewest() {\n\t\t\tsubmit(super::executeNewest);\n\t\t}\n\n\t\tprivate void submit(@Nonnull Runnable execution) {\n\t\t\texecutor.execute(execution);\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/AnnotationArrayVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport software.coley.recaf.RecafConstants;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Consumer;\n\n/**\n * Visitor to extract values from annotation fields of array types.\n *\n * @author Matt Coley\n */\npublic class AnnotationArrayVisitor<T> extends AnnotationVisitor {\n\tprivate final List<T> values = new ArrayList<>();\n\tprivate final Consumer<List<T>> onComplete;\n\n\t/**\n\t * @param visitor\n\t * \t\tParent visitor.\n\t * @param onComplete\n\t * \t\tAction to run on completed array contents.\n\t */\n\tpublic AnnotationArrayVisitor(@Nullable AnnotationVisitor visitor, @Nullable Consumer<List<T>> onComplete) {\n\t\tsuper(RecafConstants.getAsmVersion(), visitor);\n\t\tthis.onComplete = onComplete;\n\t}\n\n\t@Override\n\t@SuppressWarnings(\"unchecked\")\n\tpublic void visit(String name, Object value) {\n\t\tvalues.add((T) value);\n\t\tsuper.visit(name, value);\n\t}\n\n\t@Override\n\tpublic void visitEnd() {\n\t\tsuper.visitEnd();\n\t\tif (onComplete != null)\n\t\t\tonComplete.accept(values);\n\t}\n\n\t/**\n\t * @return Collected values.\n\t */\n\tpublic List<T> getValues() {\n\t\treturn values;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/BogusNameRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.ClassVisitor;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.mapping.WorkspaceClassRemapper;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport static software.coley.recaf.util.Keywords.getKeywords;\n\n/**\n * Visitor for renaming bogus names. This is for legibility improvements only and does not ensure the\n * resulting transformed class can be compiled.\n *\n * @author Matt Coley\n */\npublic class BogusNameRemovingVisitor extends ClassVisitor {\n\tprivate final LiveMapper liveMapper;\n\n\tprivate BogusNameRemovingVisitor(@Nonnull Workspace workspace, @Nonnull ClassVisitor cv, @Nonnull LiveMapper liveMapper) {\n\t\tsuper(RecafConstants.getAsmVersion(), new WorkspaceClassRemapper(cv, workspace, liveMapper));\n\t\tthis.liveMapper = liveMapper;\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull classes from. Enhances mapping capabilities.\n\t * @param cv\n\t * \t\tParent visitor.\n\t *\n\t * @return New bogus name removing visitor.\n\t */\n\t@Nonnull\n\tpublic static BogusNameRemovingVisitor create(@Nonnull Workspace workspace, @Nonnull ClassVisitor cv) {\n\t\treturn new BogusNameRemovingVisitor(workspace, cv, new LiveMapper());\n\t}\n\n\t/**\n\t * @return Number of type references renamed.\n\t */\n\tpublic int getRenamedTypeCount() {\n\t\treturn liveMapper.mappedTypes;\n\t}\n\n\t/**\n\t * @return Number of generic names <i>(field/method names, etc)</i> renamed.\n\t */\n\tpublic int getRenamedNameCount() {\n\t\treturn liveMapper.mappedNames;\n\t}\n\n\t@Override\n\tpublic void visitEnd() {\n\t\tint types = liveMapper.mappedTypes;\n\t\tint names = liveMapper.mappedNames;\n\t\tif (types > 0 || names > 0) {\n\t\t\tAnnotationVisitor av = visitAnnotation(\"LRemapped;\", true);\n\t\t\tif (types > 0 && names > 0) {\n\t\t\t\tav.visit(\"message\", \"Recaf has remapped \" + types + \" types, \" + names + \" names\");\n\t\t\t} else if (types > 0 && names == 0) {\n\t\t\t\tav.visit(\"message\", \"Recaf has remapped \" + types + \" types\");\n\t\t\t} else if (types == 0) {\n\t\t\t\tav.visit(\"message\", \"Recaf has remapped \" + names + \" names\");\n\t\t\t}\n\t\t}\n\t\tsuper.visitEnd();\n\t}\n\n\tprivate static class LiveMapper implements Mappings {\n\t\tprivate final Map<String, String> lookup = new HashMap<>();\n\t\tprivate int mappedTypes;\n\t\tprivate int mappedNames;\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String getMappedClassName(@Nonnull String internalName) {\n\t\t\treturn lookup.computeIfAbsent(internalName, this::className);\n\t\t}\n\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String getMappedFieldName(@Nonnull String ownerName, @Nonnull String fieldName,\n\t\t                                 @Nonnull String fieldDesc) {\n\t\t\treturn lookup.computeIfAbsent(fieldName, this::itemName);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String getMappedMethodName(@Nonnull String ownerName, @Nonnull String methodName,\n\t\t                                  @Nonnull String methodDesc) {\n\t\t\tif (methodName.equals(\"<init>\") || methodName.equals(\"<clinit>\")) return methodName;\n\t\t\treturn lookup.computeIfAbsent(methodName, this::itemName);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String getMappedVariableName(@Nonnull String className, @Nonnull String methodName,\n\t\t                                    @Nonnull String methodDesc, @Nullable String name,\n\t\t                                    @Nullable String desc, int index) {\n\t\t\treturn lookup.computeIfAbsent(methodName, this::itemName);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic IntermediateMappings exportIntermediate() {\n\t\t\tthrow new UnsupportedOperationException();\n\t\t}\n\n\t\t@Nonnull\n\t\tprivate String className(@Nonnull String name) {\n\t\t\tString original = name;\n\t\t\tname = name.chars()\n\t\t\t\t\t.mapToObj(LiveMapper::mapCodePoint)\n\t\t\t\t\t.collect(Collectors.joining());\n\t\t\tname = StringUtil.fastSplit(name, true, '/').stream()\n\t\t\t\t\t.map(LiveMapper::replaceKeyword)\n\t\t\t\t\t.collect(Collectors.joining(\"/\"));\n\t\t\tif (!original.equals(name))\n\t\t\t\tmappedTypes++;\n\t\t\treturn name;\n\t\t}\n\n\n\t\t@Nonnull\n\t\tprivate String itemName(@Nonnull String name) {\n\t\t\tString original = name;\n\t\t\tname = replaceKeyword(name.chars()\n\t\t\t\t\t.mapToObj(LiveMapper::mapCodePoint)\n\t\t\t\t\t.collect(Collectors.joining()));\n\t\t\tif (!original.equals(name)) mappedNames++;\n\t\t\treturn name;\n\t\t}\n\n\t\t@Nonnull\n\t\tprivate static String mapCodePoint(int cp) {\n\t\t\treturn (Character.isLetterOrDigit(cp) || cp == '/' || cp == '$' || cp == '_') ?\n\t\t\t\t\tCharacter.toString(Character.toChars(cp)[0]) : (\"_\");\n\t\t}\n\n\t\t@Nonnull\n\t\tprivate static String replaceKeyword(@Nonnull String name) {\n\t\t\tif (getKeywords().contains(name))\n\t\t\t\treturn StringUtil.uppercaseFirstChar(name);\n\t\t\treturn name;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/ClassAnnotationInsertingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.tree.AnnotationNode;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * Simple visitor for inserting an annotation.\n *\n * @author Matt Coley\n */\npublic class ClassAnnotationInsertingVisitor extends ClassVisitor {\n\tprivate final AnnotationNode inserted;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t * @param inserted\n\t * \t\tAnnotation to insert.\n\t */\n\tpublic ClassAnnotationInsertingVisitor(@Nullable ClassVisitor cv,\n\t\t\t\t\t\t\t\t\t\t   @Nonnull AnnotationNode inserted) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.inserted = inserted;\n\t}\n\n\t@Override\n\tpublic void visitEnd() {\n\t\tvisitAnnotation(inserted.desc, true);\n\t\tsuper.visitEnd();\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/ClassAnnotationRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.TypePath;\nimport software.coley.recaf.RecafConstants;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Simple visitor for removing an annotation.\n *\n * @author Matt Coley\n */\npublic class ClassAnnotationRemovingVisitor extends ClassVisitor {\n\tprivate final Set<String> annotationTypes;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t * @param annotationType\n\t * \t\tAnnotation type to remove.\n\t */\n\tpublic ClassAnnotationRemovingVisitor(@Nullable ClassVisitor cv,\n\t\t\t\t\t\t\t\t\t\t  @Nonnull String annotationType) {\n\t\tthis(cv, Collections.singleton(annotationType));\n\t}\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t * @param annotationTypes\n\t * \t\tAnnotation types to remove.\n\t */\n\tpublic ClassAnnotationRemovingVisitor(@Nullable ClassVisitor cv,\n\t\t\t\t\t\t\t\t\t\t  @Nonnull Collection<String> annotationTypes) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.annotationTypes = new HashSet<>(annotationTypes);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\tif (annotationTypes.contains(descriptor.substring(1, descriptor.length() - 1)))\n\t\t\treturn null;\n\t\treturn super.visitAnnotation(descriptor, visible);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\tif (annotationTypes.contains(descriptor.substring(1, descriptor.length() - 1)))\n\t\t\treturn null;\n\t\treturn super.visitTypeAnnotation(typeRef, typePath, descriptor, visible);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/ClassHollowingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.RecordComponentVisitor;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.TypePath;\nimport software.coley.recaf.RecafConstants;\n\nimport java.lang.reflect.Modifier;\nimport java.util.EnumSet;\n\n/**\n * Visitor that removes most information from a class not needed for compiling against.\n *\n * @author Matt Coley\n */\npublic class ClassHollowingVisitor extends ClassVisitor {\n\tprivate final EnumSet<Item> keptItems;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t */\n\tpublic ClassHollowingVisitor(@Nullable ClassVisitor cv) {\n\t\tthis(cv, EnumSet.noneOf(Item.class));\n\t}\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t * @param keptItems\n\t * \t\tTypes of content to keep when hollowing out the class. Method bodies are always hollowed.\n\t */\n\tpublic ClassHollowingVisitor(@Nullable ClassVisitor cv, @Nonnull EnumSet<Item> keptItems) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.keptItems = keptItems;\n\t}\n\n\t@Override\n\tpublic void visitSource(String source, String debug) {\n\t\t// Skip\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\tif (keptItems.contains(Item.ANNOTATIONS))\n\t\t\treturn super.visitAnnotation(descriptor, visible);\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\tif (keptItems.contains(Item.ANNOTATIONS))\n\t\t\treturn super.visitTypeAnnotation(typeRef, typePath, desc, visible);\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {\n\t\t// Skip private fields\n\t\tif (!keptItems.contains(Item.PRIVATE_FIELDS) && Modifier.isPrivate(access))\n\t\t\treturn null;\n\t\tFieldVisitor fv = super.visitField(access, name, descriptor, signature, value);\n\t\treturn new FieldHollower(fv);\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {\n\t\t// Skip private methods\n\t\tif (!keptItems.contains(Item.PRIVATE_METHODS) && Modifier.isPrivate(access))\n\t\t\treturn null;\n\t\tboolean isAbstract = Modifier.isAbstract(access);\n\t\tMethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);\n\t\treturn new MethodHollower(mv, isAbstract, Type.getReturnType(desc));\n\t}\n\n\t@Override\n\tpublic RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {\n\t\tRecordComponentVisitor rv = super.visitRecordComponent(name, descriptor, signature);\n\t\treturn new RecordComponentHollower(rv);\n\t}\n\n\t/**\n\t * Content to keep.\n\t */\n\tpublic enum Item {\n\t\tPRIVATE_FIELDS,\n\t\tPRIVATE_METHODS,\n\t\tANNOTATIONS\n\t}\n\n\t/**\n\t * Visitor that removes most information from a field not needed for compiling against.\n\t */\n\tpublic class FieldHollower extends FieldVisitor {\n\t\t/**\n\t\t * @param fv\n\t\t * \t\tParent field visitor.\n\t\t */\n\t\tpublic FieldHollower(FieldVisitor fv) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), fv);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\tif (keptItems.contains(Item.ANNOTATIONS))\n\t\t\t\treturn super.visitAnnotation(descriptor, visible);\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\tif (keptItems.contains(Item.ANNOTATIONS))\n\t\t\t\treturn super.visitTypeAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Visitor that removes most information from a method not needed for compiling against.\n\t */\n\tpublic class MethodHollower extends MethodVisitor {\n\t\tprivate final Type retType;\n\t\tprivate final boolean isAbstract;\n\n\t\t/**\n\t\t * @param mv\n\t\t * \t\tParent method visitor.\n\t\t * @param isAbstract\n\t\t * \t\tIs the method being visited abstract?\n\t\t * @param retType\n\t\t * \t\tReturn type of the method being visited.\n\t\t */\n\t\tpublic MethodHollower(MethodVisitor mv, boolean isAbstract, Type retType) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), mv);\n\t\t\tthis.isAbstract = isAbstract;\n\t\t\tthis.retType = retType;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitEnd() {\n\t\t\t// Skip making a dummy body if the method is abstract\n\t\t\tif (isAbstract)\n\t\t\t\treturn;\n\t\t\t// Hollowed out method will only be a basic return\n\t\t\tswitch (retType.getSort()) {\n\t\t\t\tcase Type.VOID:\n\t\t\t\t\tmv.visitInsn(Opcodes.RETURN);\n\t\t\t\t\tbreak;\n\t\t\t\tcase Type.BOOLEAN:\n\t\t\t\tcase Type.CHAR:\n\t\t\t\tcase Type.BYTE:\n\t\t\t\tcase Type.SHORT:\n\t\t\t\tcase Type.INT:\n\t\t\t\t\tmv.visitInsn(Opcodes.ICONST_0);\n\t\t\t\t\tmv.visitInsn(Opcodes.IRETURN);\n\t\t\t\t\tbreak;\n\t\t\t\tcase Type.FLOAT:\n\t\t\t\t\tmv.visitInsn(Opcodes.FCONST_0);\n\t\t\t\t\tmv.visitInsn(Opcodes.FRETURN);\n\t\t\t\t\tbreak;\n\t\t\t\tcase Type.DOUBLE:\n\t\t\t\t\tmv.visitInsn(Opcodes.DCONST_0);\n\t\t\t\t\tmv.visitInsn(Opcodes.DRETURN);\n\t\t\t\t\tbreak;\n\t\t\t\tcase Type.LONG:\n\t\t\t\t\tmv.visitInsn(Opcodes.LCONST_0);\n\t\t\t\t\tmv.visitInsn(Opcodes.LRETURN);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tmv.visitInsn(Opcodes.ACONST_NULL);\n\t\t\t\t\tmv.visitInsn(Opcodes.ARETURN);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tsuper.visitEnd();\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\tif (keptItems.contains(Item.ANNOTATIONS))\n\t\t\t\treturn super.visitAnnotation(descriptor, visible);\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\tif (keptItems.contains(Item.ANNOTATIONS))\n\t\t\t\treturn super.visitTypeAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitInsn(int opcode) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitIntInsn(int opcode, int operand) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitVarInsn(int opcode, int var) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitTypeInsn(int opcode, String type) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitFieldInsn(int opcode, String owner, String name, String descriptor) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean itf) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle,\n\t\t                                   Object... bootstrapMethodArguments) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitJumpInsn(int opcode, Label label) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLabel(Label label) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLdcInsn(Object value) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitIincInsn(int var, int increment) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitMultiANewArrayInsn(String descriptor, int numDimensions) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath,\n\t\t                                             String desc, boolean visible) {\n\t\t\t// Skip\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLocalVariable(String name, String descriptor, String signature,\n\t\t                               Label start, Label end, int index) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start,\n\t\t                                                      Label[] end, int[] index, String desc,\n\t\t                                                      boolean visible) {\n\t\t\t// Skip\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLineNumber(int line, Label start) {\n\t\t\t// Skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitTryCatchBlock(Label start, Label end, Label handler, String type) {\n\t\t\t// Skip - Only the exceptions matter. Internal blocks can be tossed.\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc,\n\t\t                                                 boolean visible) {\n\t\t\t// Skip\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Visitor that removes\n\t */\n\tpublic class RecordComponentHollower extends RecordComponentVisitor {\n\t\t/**\n\t\t * @param rv\n\t\t * \t\tParent record component visitor.\n\t\t */\n\t\tpublic RecordComponentHollower(RecordComponentVisitor rv) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), rv);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\tif (keptItems.contains(Item.ANNOTATIONS))\n\t\t\t\treturn super.visitAnnotation(descriptor, visible);\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\tif (keptItems.contains(Item.ANNOTATIONS))\n\t\t\t\treturn super.visitTypeAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn null;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/DuplicateAnnotationRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.RecordComponentVisitor;\nimport org.objectweb.asm.TypePath;\nimport software.coley.recaf.RecafConstants;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * A visitor that strips duplicate annotation usage from classes.\n *\n * @author Matt Coley\n */\npublic class DuplicateAnnotationRemovingVisitor extends ClassVisitor {\n\tprivate final Set<String> cAnnosVisited = new HashSet<>();\n\tprivate final Set<TypeAnnoInfo> cTypeAnnosVisited = new HashSet<>();\n\tprivate boolean detected;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t */\n\tpublic DuplicateAnnotationRemovingVisitor(@Nullable ClassVisitor cv) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t}\n\n\t/**\n\t * @return {@code true} if any duplicate annotations were removed.\n\t */\n\tpublic boolean hasDetectedDuplicateAnnotations() {\n\t\treturn detected;\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {\n\t\tFieldVisitor fv = super.visitField(access, name, descriptor, signature, value);\n\t\treturn new FieldDupAnnoRemover(fv);\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\tMethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);\n\t\treturn new MethodDupAnnoRemover(mv);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\tif (cAnnosVisited.add(descriptor))\n\t\t\treturn super.visitAnnotation(descriptor, visible);\n\n\t\tdetected = true;\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\tif (cTypeAnnosVisited.add(new TypeAnnoInfo(typeRef, typePath, descriptor)))\n\t\t\treturn super.visitTypeAnnotation(typeRef, typePath, descriptor, visible);\n\n\t\tdetected = true;\n\t\treturn null;\n\t}\n\n\tprivate class FieldDupAnnoRemover extends FieldVisitor {\n\t\tprivate final Set<String> fAnnosVisited = new HashSet<>();\n\t\tprivate final Set<TypeAnnoInfo> fTypeAnnosVisited = new HashSet<>();\n\n\t\tprotected FieldDupAnnoRemover(@Nullable FieldVisitor fv) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), fv);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\tif (fAnnosVisited.add(descriptor))\n\t\t\t\treturn super.visitAnnotation(descriptor, visible);\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (fTypeAnnosVisited.add(new TypeAnnoInfo(typeRef, typePath, descriptor)))\n\t\t\t\treturn super.visitTypeAnnotation(typeRef, typePath, descriptor, visible);\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate class MethodDupAnnoRemover extends MethodVisitor {\n\t\tprivate final Set<String> mAnnosVisited = new HashSet<>();\n\t\tprivate final Set<TypeAnnoInfo> mTypeAnnosVisited = new HashSet<>();\n\t\tprivate final Set<TypeAnnoInfo> mInsnTypeAnnosVisited = new HashSet<>();\n\t\tprivate final Set<TypeAnnoInfo> mTryTypeAnnosVisited = new HashSet<>();\n\t\tprivate final Set<TypeAnnoInfo> mVarTypeAnnosVisited = new HashSet<>();\n\t\tprivate final Set<ParamAnnoInfo> mParamAnnosVisited = new HashSet<>();\n\n\t\tprotected MethodDupAnnoRemover(@Nullable MethodVisitor mv) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), mv);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\tif (mAnnosVisited.add(descriptor))\n\t\t\t\treturn super.visitAnnotation(descriptor, visible);\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (mTypeAnnosVisited.add(new TypeAnnoInfo(typeRef, typePath, descriptor)))\n\t\t\t\treturn super.visitTypeAnnotation(typeRef, typePath, descriptor, visible);\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) {\n\t\t\tif (mParamAnnosVisited.add(new ParamAnnoInfo(parameter, descriptor)))\n\t\t\t\treturn super.visitParameterAnnotation(parameter, descriptor, visible);\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (mInsnTypeAnnosVisited.add(new TypeAnnoInfo(typeRef, typePath, descriptor)))\n\t\t\t\treturn super.visitInsnAnnotation(typeRef, typePath, descriptor, visible);\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (mTryTypeAnnosVisited.add(new TypeAnnoInfo(typeRef, typePath, descriptor)))\n\t\t\t\treturn super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible);\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) {\n\t\t\tif (mVarTypeAnnosVisited.add(new TypeAnnoInfo(typeRef, typePath, descriptor)))\n\t\t\t\treturn super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible);\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate class RecordDupAnnoRemover extends RecordComponentVisitor {\n\t\tprivate final Set<String> rAnnosVisited = new HashSet<>();\n\t\tprivate final Set<TypeAnnoInfo> rTypeAnnosVisited = new HashSet<>();\n\n\t\tprotected RecordDupAnnoRemover(@Nullable RecordComponentVisitor rv) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), rv);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\tif (rAnnosVisited.add(descriptor))\n\t\t\t\treturn super.visitAnnotation(descriptor, visible);\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (rTypeAnnosVisited.add(new TypeAnnoInfo(typeRef, typePath, descriptor)))\n\t\t\t\treturn super.visitTypeAnnotation(typeRef, typePath, descriptor, visible);\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate record ParamAnnoInfo(int param, @Nonnull String desc) {\n\t}\n\n\tprivate record TypeAnnoInfo(int typeRef, @Nonnull String typePath, @Nonnull String desc) {\n\t\tprivate TypeAnnoInfo(int typeRef, @Nullable TypePath typePath, @Nonnull String desc) {\n\t\t\t// ASM didn't bother making a hashCode impl for typePath so we must toString() it\n\t\t\tthis(typeRef, typePath == null ? \"\" : typePath.toString(), desc);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/FieldAnnotationInsertingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.tree.AnnotationNode;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.member.FieldMember;\n\n/**\n * Simple visitor for inserting an annotation.\n *\n * @author Matt Coley\n */\npublic class FieldAnnotationInsertingVisitor extends FieldVisitor {\n\tprivate final AnnotationNode inserted;\n\n\t/**\n\t * @param fv\n\t * \t\tParent visitor.\n\t * @param inserted\n\t * \t\tAnnotation to insert.\n\t */\n\tpublic FieldAnnotationInsertingVisitor(@Nullable FieldVisitor fv,\n\t\t\t\t\t\t\t\t\t\t   @Nonnull AnnotationNode inserted) {\n\t\tsuper(RecafConstants.getAsmVersion(), fv);\n\t\tthis.inserted = inserted;\n\t}\n\n\t/**\n\t * @param cv\n\t * \t\tVisitor of a class.\n\t * @param inserted\n\t * \t\tAnnotation to insert on a field.\n\t * @param field\n\t * \t\tField to target, or {@code null} for any field.\n\t *\n\t * @return Visitor that adds a field annotation in the requested circumstances.\n\t */\n\t@Nonnull\n\tpublic static ClassVisitor forClass(@Nonnull ClassVisitor cv, @Nonnull AnnotationNode inserted, @Nullable FieldMember field) {\n\t\treturn new ClassVisitor(RecafConstants.getAsmVersion(), cv) {\n\t\t\t@Override\n\t\t\tpublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {\n\t\t\t\tFieldVisitor fv = super.visitField(access, name, descriptor, signature, value);\n\t\t\t\tif (field == null || (field.getName().equals(name) && field.getDescriptor().equals(descriptor)))\n\t\t\t\t\treturn new FieldAnnotationInsertingVisitor(fv, inserted);\n\t\t\t\treturn fv;\n\t\t\t}\n\t\t};\n\t}\n\n\t@Override\n\tpublic void visitEnd() {\n\t\tvisitAnnotation(inserted.desc, true);\n\t\tsuper.visitEnd();\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/FieldAnnotationRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.TypePath;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.member.FieldMember;\n\nimport java.util.Collection;\nimport java.util.Collections;\n\n/**\n * Simple visitor for removing an annotation.\n *\n * @author Matt Coley\n */\npublic class FieldAnnotationRemovingVisitor extends FieldVisitor {\n\tprivate final Collection<String> annotationTypes;\n\n\t/**\n\t * @param fv\n\t * \t\tParent visitor.\n\t * @param annotationType\n\t * \t\tAnnotation type to remove.\n\t */\n\tpublic FieldAnnotationRemovingVisitor(@Nullable FieldVisitor fv,\n\t                                      @Nonnull String annotationType) {\n\t\tthis(fv, Collections.singleton(annotationType));\n\t}\n\n\t/**\n\t * @param fv\n\t * \t\tParent visitor.\n\t * @param annotationTypes\n\t * \t\tAnnotation types to remove.\n\t */\n\tpublic FieldAnnotationRemovingVisitor(@Nullable FieldVisitor fv,\n\t                                      @Nonnull Collection<String> annotationTypes) {\n\t\tsuper(RecafConstants.getAsmVersion(), fv);\n\t\tthis.annotationTypes = annotationTypes;\n\t}\n\n\t/**\n\t * @param cv\n\t * \t\tVisitor of a class.\n\t * @param annotationType\n\t * \t\tAnnotation type to remove on a field.\n\t * @param field\n\t * \t\tField to target, or {@code null} for any field.\n\t *\n\t * @return Visitor that removes field annotations in the requested circumstances.\n\t */\n\t@Nonnull\n\tpublic static ClassVisitor forClass(@Nonnull ClassVisitor cv, @Nonnull String annotationType, @Nullable FieldMember field) {\n\t\treturn forClass(cv, Collections.singleton(annotationType), field);\n\t}\n\n\t/**\n\t * @param cv\n\t * \t\tVisitor of a class.\n\t * @param annotationTypes\n\t * \t\tAnnotation types to remove on a field.\n\t * @param field\n\t * \t\tField to target, or {@code null} for any field.\n\t *\n\t * @return Visitor that removes field annotations in the requested circumstances.\n\t */\n\t@Nonnull\n\tpublic static ClassVisitor forClass(@Nonnull ClassVisitor cv, @Nonnull Collection<String> annotationTypes, @Nullable FieldMember field) {\n\t\treturn new ClassVisitor(RecafConstants.getAsmVersion(), cv) {\n\t\t\t@Override\n\t\t\tpublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {\n\t\t\t\tFieldVisitor fv = super.visitField(access, name, descriptor, signature, value);\n\t\t\t\tif (field == null || (field.getName().equals(name) && field.getDescriptor().equals(descriptor)))\n\t\t\t\t\treturn new FieldAnnotationRemovingVisitor(fv, annotationTypes);\n\t\t\t\treturn fv;\n\t\t\t}\n\t\t};\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\tString type = descriptor.substring(1, descriptor.length() - 1);\n\t\tif (annotationTypes.contains(type))\n\t\t\treturn null;\n\t\treturn super.visitAnnotation(descriptor, visible);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\tString type = descriptor.substring(1, descriptor.length() - 1);\n\t\tif (annotationTypes.contains(type))\n\t\t\treturn null;\n\t\treturn super.visitTypeAnnotation(typeRef, typePath, descriptor, visible);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/FieldInsertingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.tree.FieldNode;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * Simple visitor for inserting a field.\n *\n * @author Matt Coley\n */\npublic class FieldInsertingVisitor extends ClassVisitor {\n\tprivate final FieldNode inserted;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t * @param inserted\n\t * \t\tField to insert.\n\t */\n\tpublic FieldInsertingVisitor(@Nullable ClassVisitor cv,\n\t\t\t\t\t\t\t\t @Nonnull FieldNode inserted) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.inserted = inserted;\n\t}\n\n\t@Override\n\tpublic void visitEnd() {\n\t\tvisitField(inserted.access, inserted.name, inserted.desc, inserted.signature, inserted.value);\n\t\tsuper.visitEnd();\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/FieldPredicate.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.member.FieldMember;\n\nimport java.util.Collection;\n\n/**\n * Predicate to use in {@link MemberFilteringVisitor} and {@link MemberRemovingVisitor}.\n * <br\n * Has a default always-false method matching implementation to facilitate SAM usage for fields.\n *\n * @author Matt Coley\n * @see MethodPredicate\n */\npublic interface FieldPredicate extends MemberPredicate {\n\t/**\n\t * @param field\n\t * \t\tField to match.\n\t *\n\t * @return Predicate matching a single field.\n\t */\n\t@Nonnull\n\tstatic FieldPredicate of(@Nonnull FieldMember field) {\n\t\treturn (access, name, desc, sig, value) -> field.getName().equals(name) && field.getDescriptor().equals(desc);\n\t}\n\n\t/**\n\t * @param fields\n\t * \t\tFields to match.\n\t *\n\t * @return Predicate matching a collection of fields.\n\t */\n\t@Nonnull\n\tstatic FieldPredicate of(@Nonnull Collection<FieldMember> fields) {\n\t\treturn (access, name, desc, sig, value) -> {\n\t\t\tfor (FieldMember field : fields)\n\t\t\t\tif (field.getName().equals(name) && field.getDescriptor().equals(desc))\n\t\t\t\t\treturn true;\n\t\t\treturn false;\n\t\t};\n\t}\n\n\t@Override\n\tdefault boolean matchMethod(int access, String name, String desc, String sig, String[] exceptions) {\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/FieldReplacingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.tree.FieldNode;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.member.FieldMember;\n\n/**\n * Simple visitor for replacing a matched {@link FieldMember}.\n *\n * @author Matt Coley\n */\npublic class FieldReplacingVisitor extends ClassVisitor {\n\tprivate final FieldMember fieldMember;\n\tprivate final FieldNode replacementField;\n\tprivate boolean replaced;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor where the removal will be applied in.\n\t * @param fieldMember\n\t * \t\tDetails of the field to replace.\n\t * @param replacementField\n\t * \t\tField to replace with.\n\t */\n\tpublic FieldReplacingVisitor(@Nullable ClassVisitor cv,\n\t\t\t\t\t\t\t\t @Nonnull FieldMember fieldMember,\n\t\t\t\t\t\t\t\t @Nonnull FieldNode replacementField) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.fieldMember = fieldMember;\n\t\tthis.replacementField = replacementField;\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String desc, String sig, Object value) {\n\t\tif (fieldMember.getName().equals(name) && fieldMember.getDescriptor().equals(desc)) {\n\t\t\treplaced = true;\n\t\t\treplacementField.accept(cv);\n\t\t\treturn null;\n\t\t}\n\t\treturn super.visitField(access, name, desc, sig, value);\n\t}\n\n\t/**\n\t * @return {@code true} when the field was replaced.\n\t */\n\tpublic boolean isReplaced() {\n\t\treturn replaced;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/FrameSkippingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.tree.FrameNode;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * Visitor for skipping {@link FrameNode} content.\n *\n * @author Matt Coley\n */\npublic class FrameSkippingVisitor extends ClassVisitor {\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t */\n\tpublic FrameSkippingVisitor(@Nullable ClassVisitor cv) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\tMethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);\n\t\treturn new FrameSkippingMethodVisitor(mv);\n\t}\n\n\t/**\n\t * Method visitor that does the actual skipping.\n\t */\n\tpublic static class FrameSkippingMethodVisitor extends MethodVisitor {\n\t\t/**\n\t\t * @param mv\n\t\t * \t\tParent visitor.\n\t\t */\n\t\tpublic FrameSkippingMethodVisitor(@Nullable MethodVisitor mv) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), mv);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) {\n\t\t\t// no-op\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/IllegalAnnotationRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.RecordComponentVisitor;\nimport org.objectweb.asm.TypePath;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.util.Types;\n\n/**\n * A visitor that strips illegally named annotations from classes.\n *\n * @author Matt Coley\n */\npublic class IllegalAnnotationRemovingVisitor extends ClassVisitor {\n\t/** No reasonable annotation structure used in a valid API should have a depth of 10 levels. */\n\tprivate static final int MAX_DEPTH = 10;\n\tprivate boolean detected;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t */\n\tpublic IllegalAnnotationRemovingVisitor(@Nullable ClassVisitor cv) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t}\n\n\t/**\n\t * @return {@code true} when illegal annotations have been removed from the visited class.\n\t */\n\tpublic boolean hasDetectedIllegalAnnotations() {\n\t\treturn detected;\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {\n\t\tFieldVisitor fv = super.visitField(access, name, descriptor, signature, value);\n\t\treturn new FieldIllegalAnnoRemover(fv);\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\tMethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);\n\t\treturn new MethodIllegalAnnoRemover(mv);\n\t}\n\n\t@Override\n\tpublic RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {\n\t\tRecordComponentVisitor rcv = super.visitRecordComponent(name, descriptor, signature);\n\t\treturn new RecordIllegalAnnoRemover(rcv);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\tif (!isValidAnnotationDesc(descriptor))\n\t\t\treturn null;\n\t\treturn new IllegalSubAnnoRemover(super.visitAnnotation(descriptor, visible));\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\tif (!isValidAnnotationDesc(descriptor))\n\t\t\treturn null;\n\t\treturn new IllegalSubAnnoRemover(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible));\n\t}\n\n\tprivate boolean isValidAnnotationDesc(@Nullable String descriptor) {\n\t\tboolean valid = isValidAnnotationDesc0(descriptor);\n\t\tif (!valid)\n\t\t\tdetected = true;\n\t\treturn valid;\n\t}\n\n\tprivate static boolean isValidAnnotationDesc0(@Nullable String descriptor) {\n\t\tif (descriptor == null || descriptor.isBlank())\n\t\t\treturn false; // Must not be empty\n\t\tchar c = descriptor.charAt(0);\n\t\tif (c == 'V' || c == '(')\n\t\t\treturn false; // Must not be void or method type\n\t\treturn Types.isValidDesc(descriptor);\n\t}\n\n\tprivate class IllegalSubAnnoRemover extends AnnotationVisitor {\n\t\tprivate final int depth;\n\n\t\tprotected IllegalSubAnnoRemover(@Nullable AnnotationVisitor av) {\n\t\t\tthis(av, 0);\n\t\t}\n\n\t\tprivate IllegalSubAnnoRemover(@Nullable AnnotationVisitor av, int depth) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), av);\n\t\t\tthis.depth = depth;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String name, String descriptor) {\n\t\t\tif (!isValidAnnotationDesc(descriptor))\n\t\t\t\treturn null;\n\t\t\tif (depth > MAX_DEPTH) {\n\t\t\t\tdetected = true;\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn new IllegalSubAnnoRemover(super.visitAnnotation(name, descriptor), depth + 1);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitArray(String name) {\n\t\t\tif (depth > MAX_DEPTH) {\n\t\t\t\tdetected = true;\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn new IllegalSubAnnoRemover(super.visitArray(name), depth + 1);\n\t\t}\n\t}\n\n\tprivate class FieldIllegalAnnoRemover extends FieldVisitor {\n\t\tprotected FieldIllegalAnnoRemover(@Nullable FieldVisitor fv) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), fv);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\tif (!isValidAnnotationDesc(descriptor))\n\t\t\t\treturn null;\n\t\t\treturn new IllegalSubAnnoRemover(super.visitAnnotation(descriptor, visible));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (!isValidAnnotationDesc(descriptor))\n\t\t\t\treturn null;\n\t\t\treturn new IllegalSubAnnoRemover(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible));\n\t\t}\n\t}\n\n\tprivate class MethodIllegalAnnoRemover extends MethodVisitor {\n\t\tprotected MethodIllegalAnnoRemover(@Nullable MethodVisitor mv) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), mv);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\tif (!isValidAnnotationDesc(descriptor))\n\t\t\t\treturn null;\n\t\t\treturn new IllegalSubAnnoRemover(super.visitAnnotation(descriptor, visible));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (!isValidAnnotationDesc(descriptor))\n\t\t\t\treturn null;\n\t\t\treturn new IllegalSubAnnoRemover(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) {\n\t\t\tif (!isValidAnnotationDesc(descriptor))\n\t\t\t\treturn null;\n\t\t\treturn new IllegalSubAnnoRemover(super.visitParameterAnnotation(parameter, descriptor, visible));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (!isValidAnnotationDesc(descriptor))\n\t\t\t\treturn null;\n\t\t\treturn new IllegalSubAnnoRemover(super.visitInsnAnnotation(typeRef, typePath, descriptor, visible));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (!isValidAnnotationDesc(descriptor))\n\t\t\t\treturn null;\n\t\t\treturn new IllegalSubAnnoRemover(super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) {\n\t\t\tif (!isValidAnnotationDesc(descriptor))\n\t\t\t\treturn null;\n\t\t\treturn new IllegalSubAnnoRemover(super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible));\n\t\t}\n\t}\n\n\tprivate class RecordIllegalAnnoRemover extends RecordComponentVisitor {\n\t\tpublic RecordIllegalAnnoRemover(@Nullable RecordComponentVisitor rcv) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), rcv);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\tif (!isValidAnnotationDesc(descriptor))\n\t\t\t\treturn null;\n\t\t\treturn new IllegalSubAnnoRemover(super.visitAnnotation(descriptor, visible));\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (!isValidAnnotationDesc(descriptor))\n\t\t\t\treturn null;\n\t\t\treturn new IllegalSubAnnoRemover(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible));\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/IllegalSignatureRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.RecordComponentVisitor;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.util.Types;\n\n/**\n * A visitor that strips illegal/malformed signature data from classes.\n *\n * @author Matt Coley\n */\npublic class IllegalSignatureRemovingVisitor extends ClassVisitor {\n\tprivate boolean detected;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t */\n\tpublic IllegalSignatureRemovingVisitor(@Nullable ClassVisitor cv) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t}\n\n\t/**\n\t * @return {@code true} if any illegal signatures were removed.\n\t */\n\tpublic boolean hasDetectedIllegalSignatures() {\n\t\treturn detected;\n\t}\n\n\t@Override\n\tpublic void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {\n\t\tsuper.visit(version, access, name, map(signature, Types.SignatureContext.CLASS), superName, interfaces);\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {\n\t\treturn super.visitField(access, name, desc, map(signature, Types.SignatureContext.FIELD), value);\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {\n\t\tMethodVisitor mv = super.visitMethod(access, name, desc, map(signature, Types.SignatureContext.METHOD), exceptions);\n\t\treturn new MethodVisitor(RecafConstants.getAsmVersion(), mv) {\n\t\t\t@Override\n\t\t\tpublic void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {\n\t\t\t\tsuper.visitLocalVariable(name, desc, map(signature, Types.SignatureContext.FIELD), start, end, index);\n\t\t\t}\n\t\t};\n\t}\n\n\t@Override\n\tpublic RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {\n\t\treturn super.visitRecordComponent(name, descriptor, map(signature, Types.SignatureContext.FIELD));\n\t}\n\n\t@Override\n\tpublic void visitPermittedSubclass(String permittedSubclass) {\n\t\t// While not a signature, its metadata is not used at runtime that can confuse RE tools.\n\t\tif (permittedSubclass == null || permittedSubclass.isBlank() || Types.isPrimitive(permittedSubclass)) {\n\t\t\tdetected = true;\n\t\t\treturn;\n\t\t}\n\t\tsuper.visitPermittedSubclass(permittedSubclass);\n\t}\n\n\t@Nullable\n\tprivate String map(@Nullable String signature, @Nonnull Types.SignatureContext type) {\n\t\tif (Types.isValidSignature(signature, type))\n\t\t\treturn signature;\n\t\tdetected = true;\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/IllegalVarargsRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.util.AccessFlag;\n\nimport static org.objectweb.asm.Opcodes.ACC_VARARGS;\n\n/**\n * A visitor that strips illegal varargs flags from methods.\n *\n * @author Matt Coley\n */\npublic class IllegalVarargsRemovingVisitor extends ClassVisitor {\n\tprivate boolean detected;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t */\n\tpublic IllegalVarargsRemovingVisitor(@Nullable ClassVisitor cv) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t}\n\n\t/**\n\t * @return {@code true} if any illegal varargs were removed.\n\t */\n\tpublic boolean hasDetectedIllegalVarargs() {\n\t\treturn detected;\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\tif (AccessFlag.isVarargs(access)) {\n\t\t\tType methodType = Type.getMethodType(descriptor);\n\t\t\tType[] argumentTypes = methodType.getArgumentTypes();\n\t\t\tif (argumentTypes.length == 0 || argumentTypes[argumentTypes.length - 1].getSort() != Type.ARRAY) {\n\t\t\t\taccess = AccessFlag.removeFlag(access, ACC_VARARGS);\n\t\t\t\tdetected = true;\n\t\t\t}\n\t\t}\n\t\treturn super.visitMethod(access, name, descriptor, signature, exceptions);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/IndexCountingMethodVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * Method visitor which counts the instruction index is currently being visited.\n * <p>\n * When extending this type, you will want to access the current index before calling the super visit call.\n * For example:\n * <pre>\n * {@code\n * @Override\n * public void visitTypeInsn(int opcode, String type) {\n *    // Valid place to call getIndex() in a visit is at the start.\n *    int current = getIndex();\n *\n *    // ----> Your code goes here <----\n *\n *    // You MUST keep super-calls in your implementation to keep the index position accurate.\n *    super.visitTypeInsn(opcode, type);\n * }\n * }\n * </pre>\n *\n * @author Matt Coley\n */\npublic class IndexCountingMethodVisitor extends MethodVisitor {\n\tprotected int index;\n\n\t/**\n\t * @param mv\n\t * \t\tParent visitor.\n\t */\n\tpublic IndexCountingMethodVisitor(@Nullable MethodVisitor mv) {\n\t\tsuper(RecafConstants.getAsmVersion(), mv);\n\t}\n\n\t/**\n\t * @return Current index.\n\t */\n\tpublic int getIndex() {\n\t\treturn index;\n\t}\n\n\t@Override\n\tpublic void visitCode() {\n\t\tsuper.visitCode();\n\t\tindex = 0;\n\t}\n\n\t@Override\n\tpublic void visitInsn(int opcode) {\n\t\tsuper.visitInsn(opcode);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitIntInsn(int opcode, int operand) {\n\t\tsuper.visitIntInsn(opcode, operand);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitVarInsn(int opcode, int varIndex) {\n\t\tsuper.visitVarInsn(opcode, varIndex);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitTypeInsn(int opcode, String type) {\n\t\tsuper.visitTypeInsn(opcode, type);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitFieldInsn(int opcode, String owner, String name, String descriptor) {\n\t\tsuper.visitFieldInsn(opcode, owner, name, descriptor);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {\n\t\tsuper.visitMethodInsn(opcode, owner, name, descriptor, isInterface);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {\n\t\tsuper.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitJumpInsn(int opcode, Label label) {\n\t\tsuper.visitJumpInsn(opcode, label);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitLabel(Label label) {\n\t\tsuper.visitLabel(label);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitLdcInsn(Object value) {\n\t\tsuper.visitLdcInsn(value);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitIincInsn(int varIndex, int increment) {\n\t\tsuper.visitIincInsn(varIndex, increment);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {\n\t\tsuper.visitTableSwitchInsn(min, max, dflt, labels);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {\n\t\tsuper.visitLookupSwitchInsn(dflt, keys, labels);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitMultiANewArrayInsn(String descriptor, int numDimensions) {\n\t\tsuper.visitMultiANewArrayInsn(descriptor, numDimensions);\n\t\tindex++;\n\t}\n\n\t@Override\n\tpublic void visitLineNumber(int line, Label start) {\n\t\tsuper.visitLineNumber(line, start);\n\t\tindex++;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/KotlinMetadataVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.jetbrains.kotlin.metadata.ProtoBuf;\nimport org.objectweb.asm.AnnotationVisitor;\nimport software.coley.recaf.RecafConstants;\n\nimport java.util.List;\nimport java.util.function.Consumer;\n\n/**\n * A visitor for extracting the values of kotlin {@code Metadata} annotations.\n * <br>\n * Reference:\n * <a href=\"https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/jvm/runtime/kotlin/Metadata.kt\">Metadata.kt</a>\n *\n * @author Matt Coley\n */\npublic class KotlinMetadataVisitor extends AnnotationVisitor {\n\tprivate static final String KIND_NAME = \"k\";\n\tprivate static final String PACKAGE_NAME = \"pn\";\n\tprivate static final String METADATA_VERSION_NAME = \"mv\";\n\tprivate static final String BYTECODE_VERSION_NAME = \"bv\";\n\tprivate static final String DATA1_NAME = \"d1\";\n\tprivate static final String DATA2_NAME = \"d2\";\n\tprivate static final String EXTRA_STRING_NAME = \"xs\";\n\tprivate static final String EXTRA_INT_NAME = \"xi\";\n\tprivate final String owner;\n\tprivate final Consumer<KotlinMetadataVisitor> onComplete;\n\tprivate int kind;\n\tprivate String packageName;\n\tprivate int[] metadataVersion;\n\tprivate int[] bytecodeVersion;\n\tprivate String[] data1;\n\tprivate String[] data2;\n\tprivate String extraString;\n\tprivate int extraInt;\n\n\t/**\n\t * New visitor.\n\t *\n\t * @param owner\n\t * \t\tInternal name of defining class.\n\t * @param visitor\n\t * \t\tParent visitor.\n\t * @param onComplete\n\t * \t\tAction to run when annotation is completely read.\n\t */\n\tpublic KotlinMetadataVisitor(@Nonnull String owner,\n\t                             @Nullable AnnotationVisitor visitor,\n\t                             @Nullable Consumer<KotlinMetadataVisitor> onComplete) {\n\t\tsuper(RecafConstants.getAsmVersion(), visitor);\n\t\tthis.owner = owner;\n\t\tthis.onComplete = onComplete;\n\t}\n\n\t@Override\n\tpublic void visit(String name, Object value) {\n\t\tsuper.visit(name, value);\n\t\tif (KIND_NAME.equals(name) && value instanceof Integer)\n\t\t\tkind = ((Integer) value);\n\t\telse if (PACKAGE_NAME.equals(name) && value instanceof String)\n\t\t\tpackageName = value.toString();\n\t\telse if (EXTRA_STRING_NAME.equals(name) && value instanceof String)\n\t\t\textraString = value.toString();\n\t\telse if (EXTRA_INT_NAME.equals(name) && value instanceof Integer)\n\t\t\textraInt = ((Integer) value);\n\t\t\t// Despite being arrays, ASM can call this visit for primitive array values... because why not lol\n\t\telse if (METADATA_VERSION_NAME.equals(name))\n\t\t\tmetadataVersion = (int[]) value;\n\t\telse if (BYTECODE_VERSION_NAME.equals(name))\n\t\t\tbytecodeVersion = (int[]) value;\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitArray(String name) {\n\t\tAnnotationVisitor visitor = super.visitArray(name);\n\t\tif (DATA1_NAME.equals(name)) {\n\t\t\tvisitor = new AnnotationArrayVisitor<>(visitor,\n\t\t\t\t\t(List<String> contents) -> data1 = contents.toArray(new String[0]));\n\t\t} else if (DATA2_NAME.equals(name)) {\n\t\t\tvisitor = new AnnotationArrayVisitor<>(visitor,\n\t\t\t\t\t(List<String> contents) -> data2 = contents.toArray(new String[0]));\n\t\t} else if (METADATA_VERSION_NAME.equals(name)) {\n\t\t\tvisitor = new AnnotationArrayVisitor<>(visitor,\n\t\t\t\t\t(List<Integer> contents) -> metadataVersion = contents.stream().mapToInt(i -> i).toArray());\n\t\t} else if (BYTECODE_VERSION_NAME.equals(name)) {\n\t\t\tvisitor = new AnnotationArrayVisitor<>(visitor,\n\t\t\t\t\t(List<Integer> contents) -> bytecodeVersion = contents.stream().mapToInt(i -> i).toArray());\n\t\t}\n\t\treturn visitor;\n\t}\n\n\t@Override\n\tpublic void visitEnd() {\n\t\tsuper.visitEnd();\n\t\tif (onComplete != null)\n\t\t\tonComplete.accept(this);\n\t}\n\n\t/**\n\t * @return Name of the class with the {@code @Metadata} annotation.\n\t */\n\t@Nonnull\n\tpublic String getDefiningClass() {\n\t\treturn owner;\n\t}\n\n\t/**\n\t * The <i>\"k\"</i> field.\n\t * <br>\n\t * Possible values <i>(And how to parse them according to {@code kotlin/metadata/jvm/internal/JvmReadUtils.kt})</i> in order:\n\t * <ol>\n\t *      <li>Class: {@link ProtoBuf.Class}</li>\n\t *      <li>File: {@link ProtoBuf.Package}</li>\n\t *      <li>Synthetic class: {@link ProtoBuf.Function} <i>(Lambda)</i></li>\n\t *      <li>Multi-file class facade</li>\n\t *      <li>Multi-file class part</li>\n\t * </ol>\n\t * Generally for usage in this visitor, the value should always be {@code 1} for {@code Class}.\n\t *\n\t * @return Kind of file annotated.\n\t */\n\tpublic int getKind() {\n\t\treturn kind;\n\t}\n\n\t/**\n\t * The <i>\"pn\"</i> field.\n\t * <br>\n\t * Fully qualified name of the package this class is located in, from Kotlin's point of view, or empty string if this name\n\t * does not differ from the JVM's package FQ name. These names can be different in case the [JvmPackageName] annotation is used.\n\t *\n\t * @return Fully qualified name of the package this class is located in.\n\t * Empty string if this name does not differ from the JVM's package FQ name.\n\t */\n\tpublic String getPackageName() {\n\t\treturn packageName;\n\t}\n\n\t/**\n\t * The <i>\"mv\"</i> field.\n\t *\n\t * @return The version of the metadata provided in the arguments of this annotation.\n\t */\n\tpublic int[] getMetadataVersion() {\n\t\treturn metadataVersion;\n\t}\n\n\t/**\n\t * The <i>\"bv\"</i> field.\n\t *\n\t * @return The version of the bytecode interface <i>(naming conventions, signatures)</i>\n\t * of the class file annotated with this annotation.\n\t */\n\tpublic int[] getBytecodeVersion() {\n\t\treturn bytecodeVersion;\n\t}\n\n\t/**\n\t * The <i>\"d1\"</i> field.\n\t * <br>\n\t * Contains the actual data model in a binary format, encoded into a string.\n\t *\n\t * @return Encoded data model.\n\t */\n\tpublic String[] getData1() {\n\t\treturn data1;\n\t}\n\n\t/**\n\t * The <i>\"d2\"</i> field.\n\t * <br>\n\t * Contains the string table for resolving contents of {@link #getData1() \"d1\"}.\n\t *\n\t * @return The <i>\"d2\"</i> field. Contains the string table for resolving contents of {@link #getData1() \"d1\"}.\n\t */\n\tpublic String[] getData2() {\n\t\treturn data2;\n\t}\n\n\t/**\n\t * The <i>\"xs\"</i> field.\n\t *\n\t * @return An extra string. For a multi-file part class, internal name of the facade class.\n\t */\n\tpublic String getExtraString() {\n\t\treturn extraString;\n\t}\n\n\t/**\n\t * The <i>\"xi\"</i> field.\n\t * <br>\n\t * Bits of this number represent the following flags:\n\t * <ul>\n\t * <li>0 - This is a multi-file class facade or part, compiled with `-Xmultifile-parts-inherit`.</li>\n\t * <li>1 - This class file is compiled by a pre-release version of Kotlin and is not visible to release versions.</li>\n\t * <li>2 - This class file is a compiled Kotlin script source file (.kts).</li>\n\t * <li>3 - \"strict metadata version semantics\". The metadata of this class file is not supposed to be read by the compiler, whose major.minor version is less than the major.minor version of this metadata ([metadataVersion]).</li>\n\t * <li>4 - This class file is compiled with the new Kotlin compiler backend (JVM IR) introduced in Kotlin 1.4.</li>\n\t * <li>5 - This class file has stable metadata and ABI. This is used only for class files compiled with JVM IR (see flag #4) or FIR (#6), and prevents metadata incompatibility diagnostics from being reported where the class is used.</li>\n\t * <li>6 - This class file is compiled with the new Kotlin compiler frontend (FIR).</li>\n\t * <li>7 - This class is used in the scope of an inline function and implicitly part of the public ABI. Only valid from metadata version 1.6.0.</li>\n\t * </ul>\n\t *\n\t * @return An extra int. Different bits of the number represent different flags.\n\t */\n\tpublic int getExtraInt() {\n\t\treturn extraInt;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/LongAnnotationRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.TypePath;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * A visitor that strips long named annotations from classes.\n *\n * @author Matt Coley\n */\npublic class LongAnnotationRemovingVisitor extends ClassVisitor {\n\tprivate final int maxAllowedLength;\n\tprivate boolean detected;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t * @param maxAllowedLength\n\t * \t\tMax length of allowed annotation descriptors.\n\t */\n\tpublic LongAnnotationRemovingVisitor(@Nullable ClassVisitor cv, int maxAllowedLength) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.maxAllowedLength = maxAllowedLength;\n\t}\n\n\t/**\n\t * @return {@code true} if any long annotations were removed.\n\t */\n\tpublic boolean hasDetectedLongAnnotations() {\n\t\treturn detected;\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {\n\t\tFieldVisitor fv = super.visitField(access, name, descriptor, signature, value);\n\t\treturn new FieldLongAnnoRemover(fv);\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\tMethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);\n\t\treturn new LongMethodAnnoRemover(mv);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\tif (descriptor.length() < maxAllowedLength)\n\t\t\treturn new LongSubAnnoRemover(super.visitAnnotation(descriptor, visible));\n\n\t\tdetected = true;\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\tif (descriptor.length() < maxAllowedLength)\n\t\t\treturn new LongSubAnnoRemover(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible));\n\n\t\tdetected = true;\n\t\treturn null;\n\t}\n\n\tprivate class LongSubAnnoRemover extends AnnotationVisitor {\n\t\tpublic LongSubAnnoRemover(@Nullable AnnotationVisitor av) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), av);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String name, String descriptor) {\n\t\t\tif (descriptor.length() < maxAllowedLength)\n\t\t\t\treturn new LongSubAnnoRemover(super.visitAnnotation(name, descriptor));\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitArray(String name) {\n\t\t\treturn new LongSubAnnoRemover(super.visitArray(name));\n\t\t}\n\t}\n\n\tprivate class FieldLongAnnoRemover extends FieldVisitor {\n\t\tprotected FieldLongAnnoRemover(@Nullable FieldVisitor fv) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), fv);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\tif (descriptor.length() < maxAllowedLength)\n\t\t\t\treturn new LongSubAnnoRemover(super.visitAnnotation(descriptor, visible));\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (descriptor.length() < maxAllowedLength)\n\t\t\t\treturn new LongSubAnnoRemover(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible));\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate class LongMethodAnnoRemover extends MethodVisitor {\n\t\tprotected LongMethodAnnoRemover(@Nullable MethodVisitor mv) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), mv);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\t\tif (descriptor.length() < maxAllowedLength)\n\t\t\t\treturn new LongSubAnnoRemover(super.visitAnnotation(descriptor, visible));\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (descriptor.length() < maxAllowedLength)\n\t\t\t\treturn new LongSubAnnoRemover(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible));\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) {\n\t\t\tif (descriptor.length() < maxAllowedLength)\n\t\t\t\treturn new LongSubAnnoRemover(super.visitParameterAnnotation(parameter, descriptor, visible));\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (descriptor.length() < maxAllowedLength)\n\t\t\t\treturn new LongSubAnnoRemover(super.visitInsnAnnotation(typeRef, typePath, descriptor, visible));\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\tif (descriptor.length() < maxAllowedLength)\n\t\t\t\treturn new LongSubAnnoRemover(super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible));\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) {\n\t\t\tif (descriptor.length() < maxAllowedLength)\n\t\t\t\treturn new LongSubAnnoRemover(super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible));\n\n\t\t\tdetected = true;\n\t\t\treturn null;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/LongExceptionRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.MethodVisitor;\nimport software.coley.recaf.RecafConstants;\n\nimport java.util.Arrays;\nimport java.util.Objects;\n\n/**\n * A visitor that strips long named exceptions from methods.\n * <p>\n * Not compatible with {@link ClassWriter#ClassWriter(ClassReader, int)} since exceptions get copied when you provide\n * an input reader to copy data from.\n *\n * @author Matt Coley\n */\npublic class LongExceptionRemovingVisitor extends ClassVisitor {\n\tprivate final int maxAllowedLength;\n\tprivate boolean detected;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t * @param maxAllowedLength\n\t * \t\tMax length of allowed exception types.\n\t */\n\tpublic LongExceptionRemovingVisitor(@Nullable ClassVisitor cv, int maxAllowedLength) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.maxAllowedLength = maxAllowedLength;\n\t}\n\n\t/**\n\t * @return {@code true} if any long exceptions were removed.\n\t */\n\tpublic boolean hasDetectedLongExceptions() {\n\t\treturn detected;\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\tif (exceptions != null) {\n\t\t\tboolean removed = false;\n\t\t\tfor (int i = 0; i < exceptions.length; i++) {\n\t\t\t\tif (exceptions[i].length() > maxAllowedLength) {\n\t\t\t\t\texceptions[i] = null;\n\t\t\t\t\tremoved = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (removed) {\n\t\t\t\texceptions = Arrays.stream(exceptions)\n\t\t\t\t\t\t.filter(Objects::nonNull)\n\t\t\t\t\t\t.toArray(String[]::new);\n\t\t\t\tif (exceptions.length == 0)\n\t\t\t\t\texceptions = null;\n\t\t\t\tdetected = true;\n\t\t\t}\n\t\t}\n\n\t\treturn super.visitMethod(access, name, descriptor, signature, exceptions);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/MemberCopyingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.*;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.member.ClassMember;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Visitor that copies a member with a new name.\n *\n * @author Matt Coley\n */\npublic class MemberCopyingVisitor extends ClassVisitor {\n\tprivate final ClassMember member;\n\tprivate final String copyName;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor where the copy will be applied in.\n\t * @param member\n\t * \t\tMember to copy.\n\t * @param copyName\n\t * \t\tName of copied member.\n\t */\n\tpublic MemberCopyingVisitor(@Nullable ClassVisitor cv,\n\t\t\t\t\t\t\t\t@Nonnull ClassMember member,\n\t\t\t\t\t\t\t\t@Nonnull String copyName) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.member = member;\n\t\tthis.copyName = copyName;\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String desc, String sig, Object value) {\n\t\tif (member.isField() && member.getName().equals(name) && member.getDescriptor().equals(desc)) {\n\t\t\tFieldVisitor original = super.visitField(access, name, desc, sig, value);\n\t\t\tFieldVisitor copy = super.visitField(access, copyName, desc, sig, value);\n\t\t\treturn new CopyingFieldVisitor(original, copy);\n\t\t} else {\n\t\t\treturn super.visitField(access, name, desc, sig, value);\n\t\t}\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] exceptions) {\n\t\tif (member.isMethod() && member.getName().equals(name) && member.getDescriptor().equals(desc)) {\n\t\t\tMethodVisitor original = super.visitMethod(access, name, desc, sig, exceptions);\n\t\t\tMethodVisitor copy = super.visitMethod(access, copyName, desc, sig, exceptions);\n\t\t\treturn new CopyingMethodVisitor(original, copy);\n\t\t} else {\n\t\t\treturn super.visitMethod(access, name, desc, sig, exceptions);\n\t\t}\n\t}\n\n\tprivate static class CopyingFieldVisitor extends FieldVisitor {\n\t\tprivate final FieldVisitor copy;\n\n\t\tpublic CopyingFieldVisitor(FieldVisitor original, FieldVisitor copy) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), original);\n\t\t\tthis.copy = copy;\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String desc, boolean visible) {\n\t\t\tAnnotationVisitor annoOriginal = super.visitAnnotation(desc, visible);\n\t\t\tAnnotationVisitor annoCopy = copy.visitAnnotation(desc, visible);\n\t\t\treturn new CopyingAnnotationVisitor(annoOriginal, annoCopy);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\tAnnotationVisitor annoOriginal = super.visitTypeAnnotation(typeRef, typePath, desc, visible);\n\t\t\tAnnotationVisitor annoCopy = copy.visitTypeAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn new CopyingAnnotationVisitor(annoOriginal, annoCopy);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitAttribute(Attribute attribute) {\n\t\t\tsuper.visitAttribute(attribute);\n\t\t\tcopy.visitAttribute(attribute);\n\t\t}\n\t}\n\n\tprivate static class CopyingMethodVisitor extends MethodVisitor {\n\t\tprivate final Map<Label, Label> labelMap = new HashMap<>();\n\t\tprivate final MethodVisitor copy;\n\n\t\tpublic CopyingMethodVisitor(MethodVisitor original, MethodVisitor copy) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), original);\n\t\t\tthis.copy = copy;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitParameter(String name, int access) {\n\t\t\tsuper.visitParameter(name, access);\n\t\t\tcopy.visitParameter(name, access);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotationDefault() {\n\t\t\tAnnotationVisitor annoOriginal = super.visitAnnotationDefault();\n\t\t\tAnnotationVisitor annoCopy = copy.visitAnnotationDefault();\n\t\t\treturn new CopyingAnnotationVisitor(annoOriginal, annoCopy);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String desc, boolean visible) {\n\t\t\tAnnotationVisitor annoOriginal = super.visitAnnotation(desc, visible);\n\t\t\tAnnotationVisitor annoCopy = copy.visitAnnotation(desc, visible);\n\t\t\treturn new CopyingAnnotationVisitor(annoOriginal, annoCopy);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\tAnnotationVisitor annoOriginal = super.visitTypeAnnotation(typeRef, typePath, desc, visible);\n\t\t\tAnnotationVisitor annoCopy = copy.visitTypeAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn new CopyingAnnotationVisitor(annoOriginal, annoCopy);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitAnnotableParameterCount(int parameterCount, boolean visible) {\n\t\t\tsuper.visitAnnotableParameterCount(parameterCount, visible);\n\t\t\tcopy.visitAnnotableParameterCount(parameterCount, visible);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {\n\t\t\tAnnotationVisitor annoOriginal = super.visitParameterAnnotation(parameter, desc, visible);\n\t\t\tAnnotationVisitor annoCopy = copy.visitParameterAnnotation(parameter, desc, visible);\n\t\t\treturn new CopyingAnnotationVisitor(annoOriginal, annoCopy);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitAttribute(Attribute attribute) {\n\t\t\tsuper.visitAttribute(attribute);\n\t\t\tcopy.visitAttribute(attribute);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitCode() {\n\t\t\tsuper.visitCode();\n\t\t\tcopy.visitCode();\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) {\n\t\t\tsuper.visitFrame(type, numLocal, local, numStack, stack);\n\t\t\tcopy.visitFrame(type, numLocal, local, numStack, stack);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitInsn(int opcode) {\n\t\t\tsuper.visitInsn(opcode);\n\t\t\tcopy.visitInsn(opcode);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitIntInsn(int opcode, int operand) {\n\t\t\tsuper.visitIntInsn(opcode, operand);\n\t\t\tcopy.visitIntInsn(opcode, operand);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitVarInsn(int opcode, int var) {\n\t\t\tsuper.visitVarInsn(opcode, var);\n\t\t\tcopy.visitVarInsn(opcode, var);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitTypeInsn(int opcode, String type) {\n\t\t\tsuper.visitTypeInsn(opcode, type);\n\t\t\tcopy.visitTypeInsn(opcode, type);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitFieldInsn(int opcode, String owner, String name, String desc) {\n\t\t\tsuper.visitFieldInsn(opcode, owner, name, desc);\n\t\t\tcopy.visitFieldInsn(opcode, owner, name, desc);\n\t\t}\n\n\t\t@Override\n\t\t@SuppressWarnings(\"deprecation\")\n\t\tpublic void visitMethodInsn(int opcode, String owner, String name, String desc) {\n\t\t\tsuper.visitMethodInsn(opcode, owner, name, desc);\n\t\t\tcopy.visitMethodInsn(opcode, owner, name, desc);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitMethodInsn(int opcode, String owner, String name, String desc, boolean isInterface) {\n\t\t\tsuper.visitMethodInsn(opcode, owner, name, desc, isInterface);\n\t\t\tcopy.visitMethodInsn(opcode, owner, name, desc, isInterface);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitInvokeDynamicInsn(String name, String desc, Handle bootstrapMethodHandle,\n\t\t\t\t\t\t\t\t\t\t   Object... bootstrapMethodArguments) {\n\t\t\tsuper.visitInvokeDynamicInsn(name, desc, bootstrapMethodHandle, bootstrapMethodArguments);\n\t\t\tcopy.visitInvokeDynamicInsn(name, desc, bootstrapMethodHandle, bootstrapMethodArguments);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitJumpInsn(int opcode, Label label) {\n\t\t\tsuper.visitJumpInsn(opcode, label);\n\t\t\tcopy.visitJumpInsn(opcode, clone(label));\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLabel(Label label) {\n\t\t\tsuper.visitLabel(label);\n\t\t\tcopy.visitLabel(clone(label));\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLdcInsn(Object value) {\n\t\t\tsuper.visitLdcInsn(value);\n\t\t\tcopy.visitLdcInsn(value);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitIincInsn(int var, int increment) {\n\t\t\tsuper.visitIincInsn(var, increment);\n\t\t\tcopy.visitIincInsn(var, increment);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {\n\t\t\tsuper.visitTableSwitchInsn(min, max, dflt, labels);\n\t\t\tcopy.visitTableSwitchInsn(min, max, clone(dflt), clone(labels));\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {\n\t\t\tsuper.visitLookupSwitchInsn(dflt, keys, labels);\n\t\t\tcopy.visitLookupSwitchInsn(clone(dflt), keys, clone(labels));\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitMultiANewArrayInsn(String desc, int numDimensions) {\n\t\t\tsuper.visitMultiANewArrayInsn(desc, numDimensions);\n\t\t\tcopy.visitMultiANewArrayInsn(desc, numDimensions);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\tAnnotationVisitor annoOriginal = super.visitInsnAnnotation(typeRef, typePath, desc, visible);\n\t\t\tAnnotationVisitor annoCopy = copy.visitInsnAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn new CopyingAnnotationVisitor(annoOriginal, annoCopy);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitTryCatchBlock(Label start, Label end, Label handler, String type) {\n\t\t\tsuper.visitTryCatchBlock(start, end, handler, type);\n\t\t\tcopy.visitTryCatchBlock(clone(start), clone(end), handler, type);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t\tAnnotationVisitor annoOriginal = super.visitTryCatchAnnotation(typeRef, typePath, desc, visible);\n\t\t\tAnnotationVisitor annoCopy = copy.visitTryCatchAnnotation(typeRef, typePath, desc, visible);\n\t\t\treturn new CopyingAnnotationVisitor(annoOriginal, annoCopy);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {\n\t\t\tsuper.visitLocalVariable(name, desc, signature, start, end, index);\n\t\t\tcopy.visitLocalVariable(name, desc, signature, clone(start), clone(end), index);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  Label[] start, Label[] end, int[] idx,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  String desc, boolean visible) {\n\t\t\tAnnotationVisitor annoOriginal =\n\t\t\t\t\tsuper.visitLocalVariableAnnotation(typeRef, typePath, start, end, idx, desc, visible);\n\t\t\tAnnotationVisitor annoCopy =\n\t\t\t\t\tcopy.visitLocalVariableAnnotation(typeRef, typePath, clone(start), clone(end), idx, desc, visible);\n\t\t\treturn new CopyingAnnotationVisitor(annoOriginal, annoCopy);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLineNumber(int line, Label start) {\n\t\t\tsuper.visitLineNumber(line, start);\n\t\t\tcopy.visitLineNumber(line, clone(start));\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitMaxs(int maxStack, int maxLocals) {\n\t\t\tsuper.visitMaxs(maxStack, maxLocals);\n\t\t\tcopy.visitMaxs(maxStack, maxLocals);\n\t\t}\n\n\t\tprivate Label clone(Label label) {\n\t\t\treturn labelMap.computeIfAbsent(label, l -> new Label());\n\t\t}\n\n\t\tprivate Label[] clone(Label[] labels) {\n\t\t\tLabel[] array = new Label[labels.length];\n\t\t\tfor (int i = 0; i < labels.length; i++)\n\t\t\t\tarray[i] = clone(labels[i]);\n\t\t\treturn array;\n\t\t}\n\t}\n\n\tprivate static class CopyingAnnotationVisitor extends AnnotationVisitor {\n\t\tprivate final AnnotationVisitor copy;\n\n\t\tpublic CopyingAnnotationVisitor(AnnotationVisitor original, AnnotationVisitor copy) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), original);\n\t\t\tthis.copy = copy;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visit(String name, Object value) {\n\t\t\tsuper.visit(name, value);\n\t\t\tcopy.visit(name, value);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitEnum(String name, String desc, String value) {\n\t\t\tsuper.visitEnum(name, desc, value);\n\t\t\tcopy.visitEnum(name, desc, value);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitAnnotation(String name, String desc) {\n\t\t\tAnnotationVisitor annoOriginal = super.visitAnnotation(name, desc);\n\t\t\tAnnotationVisitor annoCopy = copy.visitAnnotation(name, desc);\n\t\t\treturn new CopyingAnnotationVisitor(annoOriginal, annoCopy);\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitArray(String name) {\n\t\t\tAnnotationVisitor annoOriginal = super.visitArray(name);\n\t\t\tAnnotationVisitor annoCopy = copy.visitArray(name);\n\t\t\treturn new CopyingAnnotationVisitor(annoOriginal, annoCopy);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/MemberFilteringVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.*;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.member.ClassMember;\n\n/**\n * A visitor that keeps only matched members. Everything else is removed.\n *\n * @author Matt Coley\n */\npublic class MemberFilteringVisitor extends ClassVisitor {\n\tprivate final MemberPredicate predicate;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t * @param member\n\t * \t\tTarget member to visit.\n\t */\n\tpublic MemberFilteringVisitor(@Nullable ClassVisitor cv, @Nonnull ClassMember member) {\n\t\tthis(cv, MemberPredicate.of(member));\n\t}\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t * @param predicate\n\t * \t\tPredicate to match against the members to include.\n\t */\n\tpublic MemberFilteringVisitor(@Nullable ClassVisitor cv, @Nonnull MemberPredicate predicate) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.predicate = predicate;\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String desc, String sig, Object value) {\n\t\tif (predicate.matchField(access, name, desc, sig, value))\n\t\t\treturn super.visitField(access, name, desc, sig, value);\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] exceptions) {\n\t\tif (predicate.matchMethod(access, name, desc, sig, exceptions))\n\t\t\treturn super.visitMethod(access, name, desc, sig, exceptions);\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic ModuleVisitor visitModule(String name, int access, String version) {\n\t\t// Skip\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitNestHost(String nestHost) {\n\t\t// Skip\n\t}\n\n\t@Override\n\tpublic void visitOuterClass(String owner, String name, String desc) {\n\t\t// Skip\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotation(String desc, boolean visible) {\n\t\t// Skip\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {\n\t\t// Skip\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitAttribute(Attribute attribute) {\n\t\t// Skip\n\t}\n\n\t@Override\n\tpublic void visitNestMember(String nestMember) {\n\t\t// Skip\n\t}\n\n\t@Override\n\tpublic void visitPermittedSubclass(String permittedSubclass) {\n\t\t// Skip\n\t}\n\n\t@Override\n\tpublic void visitInnerClass(String name, String outerName, String innerName, int access) {\n\t\t// Skip\n\t}\n\n\t@Override\n\tpublic RecordComponentVisitor visitRecordComponent(String name, String desc, String sig) {\n\t\t// Skip\n\t\treturn null;\n\t}\n\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/MemberPredicate.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.member.ClassMember;\n\nimport java.util.Collection;\n\n/**\n * Predicate to use in {@link MemberFilteringVisitor} and {@link MemberRemovingVisitor}.\n *\n * @author Matt Coley\n * @see FieldPredicate\n * @see MethodPredicate\n */\npublic interface MemberPredicate {\n\t/**\n\t * @param member\n\t * \t\tField or method to match.\n\t *\n\t * @return Predicate matching a single member.\n\t */\n\t@Nonnull\n\tstatic MemberPredicate of(@Nonnull ClassMember member) {\n\t\treturn new MemberPredicate() {\n\t\t\t@Override\n\t\t\tpublic boolean matchField(int access, String name, String desc, String sig, Object value) {\n\t\t\t\tif (member.isField())\n\t\t\t\t\treturn name.equals(member.getName()) && desc.equals(member.getDescriptor());\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic boolean matchMethod(int access, String name, String desc, String sig, String[] exceptions) {\n\t\t\t\tif (member.isMethod())\n\t\t\t\t\treturn name.equals(member.getName()) && desc.equals(member.getDescriptor());\n\t\t\t\treturn false;\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * @param members\n\t * \t\tFields and methods to match.\n\t *\n\t * @return Predicate matching a collection of fields and methods.\n\t */\n\t@Nonnull\n\tstatic MemberPredicate of(@Nonnull Collection<ClassMember> members) {\n\t\treturn new MemberPredicate() {\n\t\t\t@Override\n\t\t\tpublic boolean matchField(int access, String name, String desc, String sig, Object value) {\n\t\t\t\tfor (ClassMember member : members)\n\t\t\t\t\tif (member.isField())\n\t\t\t\t\t\treturn name.equals(member.getName()) && desc.equals(member.getDescriptor());\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic boolean matchMethod(int access, String name, String desc, String sig, String[] exceptions) {\n\t\t\t\tfor (ClassMember member : members)\n\t\t\t\t\tif (member.isMethod())\n\t\t\t\t\t\treturn name.equals(member.getName()) && desc.equals(member.getDescriptor());\n\t\t\t\treturn false;\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * @param access\n\t * \t\tField access flags.\n\t * @param name\n\t * \t\tField name.\n\t * @param desc\n\t * \t\tField descriptor.\n\t * @param sig\n\t * \t\tField generic signature.\n\t * @param value\n\t * \t\tField value.\n\t *\n\t * @return Match result.\n\t */\n\tboolean matchField(int access, String name, String desc, String sig, Object value);\n\n\t/**\n\t * @param access\n\t * \t\tMethod access flags.\n\t * @param name\n\t * \t\tMethod name.\n\t * @param desc\n\t * \t\tMethod descriptor.\n\t * @param sig\n\t * \t\tMethod generic signature.\n\t * @param exceptions\n\t * \t\tMethod exceptions.\n\t *\n\t * @return Match result.\n\t */\n\tboolean matchMethod(int access, String name, String desc, String sig, String[] exceptions);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/MemberRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.MethodVisitor;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.member.ClassMember;\n\n/**\n * Simple visitor for removing a matched {@link ClassMember}.\n *\n * @author Matt Coley\n */\npublic class MemberRemovingVisitor extends ClassVisitor {\n\tprivate final MemberPredicate predicate;\n\tprivate boolean removed;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor where the removal will be applied in.\n\t * @param member\n\t * \t\tMember to remove.\n\t */\n\tpublic MemberRemovingVisitor(@Nullable ClassVisitor cv, @Nonnull ClassMember member) {\n\t\tthis(cv, MemberPredicate.of(member));\n\t}\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor where the removal will be applied in.\n\t * @param predicate\n\t * \t\tPredicate to match against the members to remove.\n\t */\n\tpublic MemberRemovingVisitor(@Nullable ClassVisitor cv, @Nonnull MemberPredicate predicate) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.predicate = predicate;\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String desc, String sig, Object value) {\n\t\tif (predicate.matchField(access, name, desc, sig, value)) {\n\t\t\tremoved = true;\n\t\t\treturn null;\n\t\t}\n\t\treturn super.visitField(access, name, desc, sig, value);\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] exceptions) {\n\t\tif (predicate.matchMethod(access, name, desc, sig, exceptions)) {\n\t\t\tremoved = true;\n\t\t\treturn null;\n\t\t}\n\t\treturn super.visitMethod(access, name, desc, sig, exceptions);\n\t}\n\n\t/**\n\t * @return {@code true} when a field or method was removed.\n\t */\n\tpublic boolean isRemoved() {\n\t\treturn removed;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/MemberStubAddingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport static org.objectweb.asm.Opcodes.*;\n\n/**\n * Visitor for adding a stubbed outline for a given member.\n *\n * @author Justus Garbe\n * @author Matt Coley\n */\npublic class MemberStubAddingVisitor extends ClassVisitor {\n\tprivate final ClassMember member;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t * @param member\n\t * \t\tMember outline to visit.\n\t */\n\tpublic MemberStubAddingVisitor(@Nullable ClassVisitor cv, @Nonnull ClassMember member) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.member = member;\n\t}\n\n\t@Override\n\tpublic void visitEnd() {\n\t\tsuper.visitEnd();\n\n\t\tint access = member.getAccess();\n\t\tString name = member.getName();\n\t\tString desc = member.getDescriptor();\n\t\tString signature = member.getSignature();\n\n\t\tif (member instanceof FieldMember fm) {\n\t\t\tObject defaultValue = fm.getDefaultValue();\n\t\t\tvisitField(access, name, desc, signature, defaultValue).visitEnd();\n\t\t} else if (member instanceof MethodMember mm) {\n\t\t\tString[] exceptions = mm.getThrownTypes().toArray(new String[0]);\n\n\t\t\tMethodVisitor mv = visitMethod(access, name, desc, signature, exceptions);\n\t\t\tif (!mm.hasAbstractModifier() || !mm.hasNativeModifier()) {\n\t\t\t\tLabel start = new Label();\n\t\t\t\tLabel end = new Label();\n\n\t\t\t\tmv.visitLabel(start);\n\t\t\t\tmv.visitTypeInsn(NEW, \"java/lang/RuntimeException\");\n\t\t\t\tmv.visitInsn(DUP);\n\t\t\t\tmv.visitLdcInsn(\"TODO: Implementation\");\n\t\t\t\tmv.visitMethodInsn(INVOKESPECIAL, \"java/lang/RuntimeException\", \"<init>\", \"(Ljava/lang/String;)V\", false);\n\t\t\t\tmv.visitInsn(ATHROW);\n\t\t\t\tmv.visitLabel(end);\n\n\t\t\t\tfor (LocalVariable lv : mm.getLocalVariables()) {\n\t\t\t\t\tmv.visitLocalVariable(lv.getName(), lv.getDescriptor(), lv.getSignature(),\n\t\t\t\t\t\t\tstart, end, lv.getIndex());\n\t\t\t\t}\n\t\t\t}\n\t\t\tmv.visitEnd();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/MethodAnnotationInsertingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.tree.AnnotationNode;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.member.MethodMember;\n\n/**\n * Simple visitor for inserting an annotation.\n *\n * @author Matt Coley\n */\npublic class MethodAnnotationInsertingVisitor extends MethodVisitor {\n\tprivate final AnnotationNode inserted;\n\n\t/**\n\t * @param mv\n\t * \t\tParent visitor.\n\t * @param inserted\n\t * \t\tAnnotation to insert.\n\t */\n\tpublic MethodAnnotationInsertingVisitor(@Nullable MethodVisitor mv,\n\t\t\t\t\t\t\t\t\t\t\t@Nonnull AnnotationNode inserted) {\n\t\tsuper(RecafConstants.getAsmVersion(), mv);\n\t\tthis.inserted = inserted;\n\t}\n\n\t/**\n\t * @param cv\n\t * \t\tVisitor of a class.\n\t * @param inserted\n\t * \t\tAnnotation to insert on a method.\n\t * @param method\n\t * \t\tMethod to target, or {@code null} for any method.\n\t *\n\t * @return Visitor that adds a method annotation in the requested circumstances.\n\t */\n\t@Nonnull\n\tpublic static ClassVisitor forClass(@Nonnull ClassVisitor cv, @Nonnull AnnotationNode inserted, @Nullable MethodMember method) {\n\t\treturn new ClassVisitor(RecafConstants.getAsmVersion(), cv) {\n\t\t\t@Override\n\t\t\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\t\t\tMethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);\n\t\t\t\tif (method == null || (method.getName().equals(name) && method.getDescriptor().equals(descriptor)))\n\t\t\t\t\treturn new MethodAnnotationInsertingVisitor(mv, inserted);\n\t\t\t\treturn mv;\n\t\t\t}\n\t\t};\n\t}\n\n\t@Override\n\tpublic void visitEnd() {\n\t\tvisitAnnotation(inserted.desc, true);\n\t\tsuper.visitEnd();\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/MethodAnnotationRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.*;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport java.util.Collection;\nimport java.util.Collections;\n\n/**\n * Simple visitor for removing an annotation.\n *\n * @author Matt Coley\n */\npublic class MethodAnnotationRemovingVisitor extends MethodVisitor {\n\tprivate final Collection<String> annotationType;\n\n\t/**\n\t * @param mv\n\t * \t\tParent visitor.\n\t * @param annotationType\n\t * \t\tAnnotation type to remove.\n\t */\n\tpublic MethodAnnotationRemovingVisitor(@Nullable MethodVisitor mv,\n\t                                       @Nonnull String annotationType) {\n\t\tthis(mv, Collections.singleton(annotationType));\n\t}\n\n\t/**\n\t * @param mv\n\t * \t\tParent visitor.\n\t * @param annotationTypes\n\t * \t\tAnnotation types to remove.\n\t */\n\tpublic MethodAnnotationRemovingVisitor(@Nullable MethodVisitor mv,\n\t                                       @Nonnull Collection<String> annotationTypes) {\n\t\tsuper(RecafConstants.getAsmVersion(), mv);\n\t\tthis.annotationType = annotationTypes;\n\t}\n\n\t/**\n\t * @param cv\n\t * \t\tVisitor of a class.\n\t * @param annotationType\n\t * \t\tAnnotation type to remove on a method.\n\t * @param method\n\t * \t\tMethod to target, or {@code null} for any method.\n\t *\n\t * @return Visitor that removes method annotations in the requested circumstances.\n\t */\n\t@Nonnull\n\tpublic static ClassVisitor forClass(@Nonnull ClassVisitor cv, @Nonnull String annotationType, @Nullable MethodMember method) {\n\t\treturn forClass(cv, Collections.singleton(annotationType), method);\n\t}\n\n\t/**\n\t * @param cv\n\t * \t\tVisitor of a class.\n\t * @param annotationTypes\n\t * \t\tAnnotation types to remove on a method.\n\t * @param method\n\t * \t\tMethod to target, or {@code null} for any method.\n\t *\n\t * @return Visitor that removes method annotations in the requested circumstances.\n\t */\n\t@Nonnull\n\tpublic static ClassVisitor forClass(@Nonnull ClassVisitor cv, @Nonnull Collection<String> annotationTypes, @Nullable MethodMember method) {\n\t\treturn new ClassVisitor(RecafConstants.getAsmVersion(), cv) {\n\t\t\t@Override\n\t\t\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\t\t\tMethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);\n\t\t\t\tif (method == null || (method.getName().equals(name) && method.getDescriptor().equals(descriptor)))\n\t\t\t\t\treturn new MethodAnnotationRemovingVisitor(mv, annotationTypes);\n\t\t\t\treturn mv;\n\t\t\t}\n\t\t};\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\tString type = descriptor.substring(1, descriptor.length() - 1);\n\t\tif (annotationType.contains(type))\n\t\t\treturn null;\n\t\treturn super.visitAnnotation(descriptor, visible);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\tString type = descriptor.substring(1, descriptor.length() - 1);\n\t\tif (annotationType.contains(type))\n\t\t\treturn null;\n\t\treturn super.visitTypeAnnotation(typeRef, typePath, descriptor, visible);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) {\n\t\tString type = descriptor.substring(1, descriptor.length() - 1);\n\t\tif (annotationType.contains(type))\n\t\t\treturn null;\n\t\treturn super.visitParameterAnnotation(parameter, descriptor, visible);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\tString type = descriptor.substring(1, descriptor.length() - 1);\n\t\tif (annotationType.contains(type))\n\t\t\treturn null;\n\t\treturn super.visitInsnAnnotation(typeRef, typePath, descriptor, visible);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\tString type = descriptor.substring(1, descriptor.length() - 1);\n\t\tif (annotationType.contains(type))\n\t\t\treturn null;\n\t\treturn super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) {\n\t\tString type = descriptor.substring(1, descriptor.length() - 1);\n\t\tif (annotationType.contains(type))\n\t\t\treturn null;\n\t\treturn super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/MethodInsertingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.tree.MethodNode;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * Simple visitor for inserting a method.\n *\n * @author Matt Coley\n */\npublic class MethodInsertingVisitor extends ClassVisitor {\n\tprivate final MethodNode inserted;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t * @param inserted\n\t * \t\tMethod to insert.\n\t */\n\tpublic MethodInsertingVisitor(@Nullable ClassVisitor cv,\n\t\t\t\t\t\t\t\t  @Nonnull MethodNode inserted) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.inserted = inserted;\n\t}\n\n\t@Override\n\tpublic void visitEnd() {\n\t\tvisitMethod(inserted.access, inserted.name, inserted.desc, inserted.signature,\n\t\t\t\tinserted.exceptions.toArray(new String[0]));\n\t\tsuper.visitEnd();\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/MethodNoopingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.*;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\n/**\n * A visitor that replaces method bodies with no-op implementations.\n *\n * @author Matt Coley\n */\npublic class MethodNoopingVisitor extends ClassVisitor {\n\tprivate final MemberPredicate predicate;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t * @param predicate\n\t * \t\tPredicate to match which methods will be noop'd, or {@code null} to noop all methods.\n\t */\n\tpublic MethodNoopingVisitor(@Nullable ClassVisitor cv, @Nullable MemberPredicate predicate) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\n\t\tthis.predicate = predicate;\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\tMethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);\n\n\t\t// Skip if already no code.\n\t\tif (AccessFlag.isAbstract(access) || AccessFlag.isNative(access))\n\t\t\treturn mv;\n\n\t\t// Only noop matched methods.\n\t\tif (predicate == null || predicate.matchMethod(access, name, descriptor, signature, exceptions))\n\t\t\treturn new MethodNoopingVisitor.NoopingMethodVisitor(mv, descriptor);\n\n\t\treturn mv;\n\t}\n\n\t/**\n\t * Method visitor that replaces the contents with a no-op return.\n\t */\n\tpublic static class NoopingMethodVisitor extends MethodVisitor implements Opcodes {\n\t\tprivate static final int MAX_STACK = 2;\n\t\tprivate static final Map<String, Consumer<MethodVisitor>> OBJECT_DEFAULTS = new HashMap<>();\n\t\tprivate final Type type;\n\n\t\tpublic NoopingMethodVisitor(@Nullable MethodVisitor mv, @Nonnull String desc) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), mv);\n\t\t\ttype = Type.getMethodType(desc);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitEnd() {\n\t\t\t// Directly pass to the delegate so these do not get no-op'd\n\t\t\tMethodVisitor mv = getDelegate();\n\t\t\tif (mv == null) return;\n\t\t\tType returnType = type.getReturnType();\n\t\t\tint sort = returnType.getSort();\n\t\t\tswitch (sort) {\n\t\t\t\tcase Type.VOID -> {\n\t\t\t\t\t// return;\n\t\t\t\t\tmv.visitInsn(RETURN);\n\t\t\t\t}\n\t\t\t\tcase Type.BOOLEAN, Type.CHAR, Type.BYTE, Type.SHORT, Type.INT -> {\n\t\t\t\t\t// return false, '\\0', 0\n\t\t\t\t\tmv.visitInsn(ICONST_0);\n\t\t\t\t\tmv.visitInsn(IRETURN);\n\t\t\t\t}\n\t\t\t\tcase Type.FLOAT -> {\n\t\t\t\t\t// return 0.0f\n\t\t\t\t\tmv.visitInsn(FCONST_0);\n\t\t\t\t\tmv.visitInsn(FRETURN);\n\t\t\t\t}\n\t\t\t\tcase Type.LONG -> {\n\t\t\t\t\t// return 0\n\t\t\t\t\tmv.visitInsn(LCONST_0);\n\t\t\t\t\tmv.visitInsn(LRETURN);\n\t\t\t\t}\n\t\t\t\tcase Type.DOUBLE -> {\n\t\t\t\t\t// return 0.0\n\t\t\t\t\tmv.visitInsn(DCONST_0);\n\t\t\t\t\tmv.visitInsn(DRETURN);\n\t\t\t\t}\n\t\t\t\tcase Type.ARRAY -> {\n\t\t\t\t\t// return new T[0]\n\t\t\t\t\tmv.visitInsn(ICONST_0);\n\t\t\t\t\tType elementType = returnType.getElementType();\n\t\t\t\t\tint elementSort = elementType.getSort();\n\t\t\t\t\tswitch (elementSort) {\n\t\t\t\t\t\tcase Type.BOOLEAN -> mv.visitIntInsn(NEWARRAY, T_BOOLEAN);\n\t\t\t\t\t\tcase Type.CHAR -> mv.visitIntInsn(NEWARRAY, T_CHAR);\n\t\t\t\t\t\tcase Type.BYTE -> mv.visitIntInsn(NEWARRAY, T_BYTE);\n\t\t\t\t\t\tcase Type.SHORT -> mv.visitIntInsn(NEWARRAY, T_SHORT);\n\t\t\t\t\t\tcase Type.INT -> mv.visitIntInsn(NEWARRAY, T_INT);\n\t\t\t\t\t\tcase Type.FLOAT -> mv.visitIntInsn(NEWARRAY, T_FLOAT);\n\t\t\t\t\t\tcase Type.LONG -> mv.visitIntInsn(NEWARRAY, T_LONG);\n\t\t\t\t\t\tcase Type.DOUBLE -> mv.visitIntInsn(NEWARRAY, T_DOUBLE);\n\t\t\t\t\t\tdefault -> mv.visitTypeInsn(ANEWARRAY, elementType.getInternalName());\n\t\t\t\t\t}\n\t\t\t\t\tmv.visitInsn(ARETURN);\n\t\t\t\t}\n\t\t\t\tdefault -> {\n\t\t\t\t\t// See if there's a specific kind of default value better than null.\n\t\t\t\t\tString className = returnType.getInternalName();\n\t\t\t\t\tConsumer<MethodVisitor> defaultValueProvider = OBJECT_DEFAULTS.get(className);\n\t\t\t\t\tif (defaultValueProvider != null) {\n\t\t\t\t\t\tdefaultValueProvider.accept(mv);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmv.visitInsn(ACONST_NULL);\n\t\t\t\t\t}\n\t\t\t\t\tmv.visitInsn(ARETURN);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitInsn(int opcode) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitIntInsn(int opcode, int operand) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitVarInsn(int opcode, int varIndex) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitTypeInsn(int opcode, String type) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitFieldInsn(int opcode, String owner, String name, String descriptor) {\n\t\t\t// skip\n\t\t}\n\n\t\t@SuppressWarnings(\"all\")\n\t\t@Override\n\t\tpublic void visitMethodInsn(int opcode, String owner, String name, String descriptor) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitJumpInsn(int opcode, Label label) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLabel(Label label) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLdcInsn(Object value) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitIincInsn(int varIndex, int increment) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitMultiANewArrayInsn(String descriptor, int numDimensions) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\t// skip\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitTryCatchBlock(Label start, Label end, Label handler, String type) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\t\t// skip\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) {\n\t\t\t// skip\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLineNumber(int line, Label start) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitMaxs(int maxStack, int maxLocals) {\n\t\t\t// Stack ---> 2 (max size of value pushed to return)\n\t\t\t// Locals --> Match parameter sizes + 1 for 'this'\n\t\t\tsuper.visitMaxs(MAX_STACK, type.getArgumentsAndReturnSizes() + 1);\n\t\t}\n\n\t\tprivate static void register(@Nonnull String name, @Nonnull Consumer<MethodVisitor> consumer) {\n\t\t\tOBJECT_DEFAULTS.put(name, consumer);\n\t\t}\n\n\t\tprivate static void registerEmptyCollection(@Nonnull String name) {\n\t\t\tString simpleName = StringUtil.shortenPath(name);\n\t\t\tregister(name, mv -> mv.visitMethodInsn(INVOKESTATIC, \"java/util/Collections\", \"empty\" + simpleName, \"()L\" + name + \";\", false));\n\t\t}\n\n\t\tprivate static void registerDefaultConstructor(@Nonnull String name) {\n\t\t\tregister(name, mv -> {\n\t\t\t\tmv.visitTypeInsn(NEW, name);\n\t\t\t\tmv.visitInsn(DUP);\n\t\t\t\tmv.visitMethodInsn(INVOKESPECIAL, name, \"<init>\", \"()V\", false);\n\t\t\t});\n\t\t}\n\n\t\tstatic {\n\t\t\tregisterEmptyCollection(\"java/util/List\");\n\t\t\tregisterEmptyCollection(\"java/util/Set\");\n\t\t\tregisterEmptyCollection(\"java/util/SortedSet\");\n\t\t\tregisterEmptyCollection(\"java/util/NavigableSet\");\n\t\t\tregisterEmptyCollection(\"java/util/Map\");\n\t\t\tregisterEmptyCollection(\"java/util/SortedMap\");\n\t\t\tregisterEmptyCollection(\"java/util/NavigableMap\");\n\t\t\tregisterEmptyCollection(\"java/util/Iterator\");\n\t\t\tregisterEmptyCollection(\"java/util/ListIterator\");\n\t\t\tregisterEmptyCollection(\"java/util/Enumeration\");\n\t\t\tregisterDefaultConstructor(\"java/util/ArrayList\");\n\t\t\tregisterDefaultConstructor(\"java/util/ArrayDeque\");\n\t\t\tregisterDefaultConstructor(\"java/util/HashMap\");\n\t\t\tregisterDefaultConstructor(\"java/util/HashSet\");\n\t\t\tregisterDefaultConstructor(\"java/util/Hashtable\");\n\t\t\tregisterDefaultConstructor(\"java/util/IdentityHashMap\");\n\t\t\tregisterDefaultConstructor(\"java/util/LinkedHashMap\");\n\t\t\tregisterDefaultConstructor(\"java/util/LinkedHashSet\");\n\t\t\tregisterDefaultConstructor(\"java/util/LinkedHashSet\");\n\t\t\tregisterDefaultConstructor(\"java/util/LinkedList\");\n\t\t\tregisterDefaultConstructor(\"java/util/PriorityQueue\");\n\t\t\tregisterDefaultConstructor(\"java/util/Properties\");\n\t\t\tregisterDefaultConstructor(\"java/util/Random\");\n\t\t\tregisterDefaultConstructor(\"java/util/Stack\");\n\t\t\tregisterDefaultConstructor(\"java/util/TreeMap\");\n\t\t\tregisterDefaultConstructor(\"java/util/TreeSet\");\n\t\t\tregisterDefaultConstructor(\"java/util/Vector\");\n\t\t\tregisterDefaultConstructor(\"java/util/WeakHashMap\");\n\t\t\tregisterDefaultConstructor(\"java/util/concurrent/ArrayBlockingQueue\");\n\t\t\tregisterDefaultConstructor(\"java/util/concurrent/ConcurrentHashMap\");\n\t\t\tregisterDefaultConstructor(\"java/util/concurrent/ConcurrentLinkedDeque\");\n\t\t\tregisterDefaultConstructor(\"java/util/concurrent/ConcurrentLinkedQueue\");\n\t\t\tregisterDefaultConstructor(\"java/util/concurrent/ConcurrentSkipListMap\");\n\t\t\tregisterDefaultConstructor(\"java/util/concurrent/ConcurrentSkipListSet\");\n\t\t\tregisterDefaultConstructor(\"java/util/concurrent/CopyOnWriteArrayList\");\n\t\t\tregisterDefaultConstructor(\"java/util/concurrent/CopyOnWriteArraySet\");\n\t\t\tregisterDefaultConstructor(\"java/util/concurrent/DelayQueue\");\n\t\t\tregisterDefaultConstructor(\"java/util/concurrent/LinkedBlockingDeque\");\n\t\t\tregisterDefaultConstructor(\"java/util/concurrent/LinkedBlockingQueue\");\n\t\t\tregisterDefaultConstructor(\"java/util/concurrent/LinkedTransferQueue\");\n\t\t\tregisterDefaultConstructor(\"java/util/concurrent/PriorityBlockingQueue\");\n\t\t\tregisterDefaultConstructor(\"java/util/Date\");\n\t\t\tregister(\"java/util/Optional\", mv -> mv.visitMethodInsn(INVOKESTATIC, \"java/util/Optional\", \"empty\", \"()Ljava/util/Optional;\", false));\n\t\t\tregister(\"java/util/OptionalDouble\", mv -> mv.visitMethodInsn(INVOKESTATIC, \"java/util/OptionalDouble\", \"empty\", \"()Ljava/util/OptionalDouble;\", false));\n\t\t\tregister(\"java/util/OptionalInt\", mv -> mv.visitMethodInsn(INVOKESTATIC, \"java/util/OptionalInt\", \"empty\", \"()Ljava/util/OptionalInt;\", false));\n\t\t\tregister(\"java/util/OptionalLong\", mv -> mv.visitMethodInsn(INVOKESTATIC, \"java/util/OptionalLong\", \"empty\", \"()Ljava/util/OptionalLong;\", false));\n\t\t\tregister(\"java/util/UUID\", mv -> mv.visitMethodInsn(INVOKESTATIC, \"java/util/UUID\", \"randomUUID\", \"()Ljava/util/UUID;\", false));\n\t\t\tregister(\"java/util/stream/Stream\", mv -> mv.visitMethodInsn(INVOKESTATIC, \"java/util/stream/Stream\", \"empty\", \"()Ljava/util/stream/Stream;\", false));\n\t\t\tregister(\"java/time/Instant\", mv -> mv.visitMethodInsn(INVOKESTATIC, \"java/time/Instant\", \"now\", \"()Ljava/time/Instant;\", false));\n\t\t\tregister(\"java/util/Collection\", OBJECT_DEFAULTS.get(\"java/util/List\"));\n\t\t\tregister(\"java/util/Queue\", OBJECT_DEFAULTS.get(\"java/util/ArrayDeque\"));\n\t\t\tregister(\"java/util/RandomAccess\", OBJECT_DEFAULTS.get(\"java/util/ArrayList\"));\n\t\t\tregister(\"java/time/temporal/Temporal\", OBJECT_DEFAULTS.get(\"java/time/Instant\"));\n\t\t\tregister(\"java/time/temporal/TemporalAdjuster\", OBJECT_DEFAULTS.get(\"java/time/Instant\"));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/MethodPredicate.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport java.util.Collection;\n\n/**\n * Predicate to use in {@link MemberFilteringVisitor} and {@link MemberRemovingVisitor}.\n * <br\n * Has a default always-false field matching implementation to facilitate SAM usage for methods.\n *\n * @author Matt Coley\n * @see FieldPredicate\n */\npublic interface MethodPredicate extends MemberPredicate {\n\t/**\n\t * @param method\n\t * \t\tMethod to match.\n\t *\n\t * @return Predicate matching a single method.\n\t */\n\t@Nonnull\n\tstatic MethodPredicate of(@Nonnull MethodMember method) {\n\t\treturn (access, name, desc, sig, exceptions) -> method.getName().equals(name) && method.getDescriptor().equals(desc);\n\t}\n\n\t/**\n\t * @param methods\n\t * \t\tMethods to match.\n\t *\n\t * @return Predicate matching a collection of methods.\n\t */\n\t@Nonnull\n\tstatic MethodPredicate of(@Nonnull Collection<MethodMember> methods) {\n\t\treturn (access, name, desc, sig, exceptions) -> {\n\t\t\tfor (MethodMember method : methods)\n\t\t\t\tif (method.getName().equals(name) && method.getDescriptor().equals(desc))\n\t\t\t\t\treturn true;\n\t\t\treturn false;\n\t\t};\n\t}\n\n\t@Override\n\tdefault boolean matchField(int access, String name, String desc, String sig, Object value) {\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/MethodReplacingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.tree.MethodNode;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.member.MethodMember;\n\n/**\n * Simple visitor for replacing a matched {@link MethodMember}.\n *\n * @author Matt Coley\n */\npublic class MethodReplacingVisitor extends ClassVisitor {\n\tprivate final MethodMember methodMember;\n\tprivate final MethodNode replacementMethod;\n\tprivate boolean replaced;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor where the removal will be applied in.\n\t * @param methodMember\n\t * \t\tDetails of the method to replace.\n\t * @param replacementMethod\n\t * \t\tMethod to replace with.\n\t */\n\tpublic MethodReplacingVisitor(@Nullable ClassVisitor cv,\n\t\t\t\t\t\t\t\t  @Nonnull MethodMember methodMember,\n\t\t\t\t\t\t\t\t  @Nonnull MethodNode replacementMethod) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.methodMember = methodMember;\n\t\tthis.replacementMethod = replacementMethod;\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] exceptions) {\n\t\tif (methodMember.getName().equals(name) && methodMember.getDescriptor().equals(desc)) {\n\t\t\t// Update from the replacement method\n\t\t\taccess = replacementMethod.access;\n\t\t\tname = replacementMethod.name;\n\t\t\tdesc = replacementMethod.desc;\n\t\t\tsig = replacementMethod.signature;\n\t\t\texceptions = replacementMethod.exceptions.toArray(new String[0]);\n\t\t\t// Visit\n\t\t\tMethodVisitor mv = super.visitMethod(access, name, desc, sig, exceptions);\n\t\t\treplaced = true;\n\t\t\treplacementMethod.accept(mv);\n\t\t\treturn null;\n\t\t}\n\t\treturn super.visitMethod(access, name, desc, sig, exceptions);\n\t}\n\n\t/**\n\t * @return {@code true} when the method was replaced.\n\t */\n\tpublic boolean isReplaced() {\n\t\treturn replaced;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/MethodVariableRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.TypePath;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.util.AccessFlag;\n\n/**\n * A visitor that removes method variables.\n * Generally useful for fixing kotlin classes since their variable tables are all sorts of wrong.\n *\n * @author Matt Coley\n */\npublic class MethodVariableRemovingVisitor extends ClassVisitor {\n\tprivate final MemberPredicate predicate;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t * @param predicate\n\t * \t\tPredicate to match which methods will be cleaned, or {@code null} to clean all methods.\n\t */\n\tpublic MethodVariableRemovingVisitor(@Nullable ClassVisitor cv, @Nullable MemberPredicate predicate) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\n\t\tthis.predicate = predicate;\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\tMethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);\n\n\t\t// Skip if already no code.\n\t\tif (AccessFlag.isAbstract(access) || AccessFlag.isNative(access))\n\t\t\treturn mv;\n\n\t\t// Only clean matched methods.\n\t\tif (predicate == null || predicate.matchMethod(access, name, descriptor, signature, exceptions))\n\t\t\treturn new VarRemovingVisitor(mv);\n\n\t\treturn mv;\n\t}\n\n\t/**\n\t * Method visitor that removes any local variable debug info.\n\t */\n\tpublic static class VarRemovingVisitor extends MethodVisitor {\n\t\tpublic VarRemovingVisitor(@Nullable MethodVisitor mv) {\n\t\t\tsuper(RecafConstants.getAsmVersion(), mv);\n\t\t}\n\n\t\t@Override\n\t\tpublic void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {\n\t\t\t// skip\n\t\t}\n\n\t\t@Override\n\t\tpublic AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) {\n\t\t\t// skip\n\t\t\treturn null;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/SignatureRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.RecordComponentVisitor;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * A visitor that strips signature data from classes.\n *\n * @author Matt Coley\n */\npublic class SignatureRemovingVisitor extends ClassVisitor {\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t */\n\tpublic SignatureRemovingVisitor(@Nullable ClassVisitor cv) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t}\n\n\t@Override\n\tpublic void visit(int version, int access, String name, String s, String superName, String[] interfaces) {\n\t\tsuper.visit(version, access, name, null, superName, interfaces);\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String desc, String s, Object value) {\n\t\treturn super.visitField(access, name, desc, null, value);\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String desc, String s, String[] exceptions) {\n\t\tMethodVisitor mv = super.visitMethod(access, name, desc, null, exceptions);\n\t\treturn new MethodVisitor(RecafConstants.getAsmVersion(), mv) {\n\t\t\t@Override\n\t\t\tpublic void visitLocalVariable(String name, String desc, String s, Label start, Label end, int index) {\n\t\t\t\tsuper.visitLocalVariable(name, desc, null, start, end, index);\n\t\t\t}\n\t\t};\n\t}\n\n\t@Override\n\tpublic RecordComponentVisitor visitRecordComponent(String name, String descriptor, String s) {\n\t\treturn super.visitRecordComponent(name, descriptor, null);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/SkippingAnnotationVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * Annotation visitor that skips over all content by default.\n *\n * @author Matt Coley\n */\npublic class SkippingAnnotationVisitor extends AnnotationVisitor {\n\tpublic SkippingAnnotationVisitor() {\n\t\tthis(null);\n\t}\n\n\tpublic SkippingAnnotationVisitor(@Nullable AnnotationVisitor av) {\n\t\tsuper(RecafConstants.getAsmVersion(), av);\n\t}\n\n\t@Override\n\tpublic void visit(String name, Object value) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitEnum(String name, String descriptor, String value) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotation(String name, String descriptor) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitArray(String name) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitEnd() {\n\t\t// no-op\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/SkippingClassVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.Attribute;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.ModuleVisitor;\nimport org.objectweb.asm.RecordComponentVisitor;\nimport org.objectweb.asm.TypePath;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * Class visitor that skips over all content by default.\n *\n * @author Matt Coley\n */\npublic class SkippingClassVisitor extends ClassVisitor {\n\tpublic SkippingClassVisitor() {\n\t\tthis(null);\n\t}\n\n\tpublic SkippingClassVisitor(@Nullable ClassVisitor cv) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t}\n\n\t@Override\n\tpublic void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitSource(String source, String debug) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic ModuleVisitor visitModule(String name, int access, String version) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitNestHost(String nestHost) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitOuterClass(String owner, String name, String descriptor) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitAttribute(Attribute attribute) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitNestMember(String nestMember) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitPermittedSubclass(String permittedSubclass) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitInnerClass(String name, String outerName, String innerName, int access) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitEnd() {\n\t\t// no-op\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/SkippingFieldVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.Attribute;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.TypePath;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * Field visitor that skips over all content by default.\n *\n * @author Matt Coley\n */\npublic class SkippingFieldVisitor extends FieldVisitor {\n\tpublic SkippingFieldVisitor() {\n\t\tthis(null);\n\t}\n\n\tpublic SkippingFieldVisitor(@Nullable FieldVisitor fv) {\n\t\tsuper(RecafConstants.getAsmVersion(), fv);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitAttribute(Attribute attribute) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitEnd() {\n\t\t// no-op\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/SkippingMethodVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.Attribute;\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.TypePath;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * Method visitor that skips over all content by default.\n *\n * @author Matt Coley\n */\npublic class SkippingMethodVisitor extends MethodVisitor {\n\tpublic SkippingMethodVisitor() {\n\t\tthis(null);\n\t}\n\n\tpublic SkippingMethodVisitor(@Nullable MethodVisitor mv) {\n\t\tsuper(RecafConstants.getAsmVersion(), mv);\n\t}\n\n\t@Override\n\tpublic void visitParameter(String name, int access) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotationDefault() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitAnnotableParameterCount(int parameterCount, boolean visible) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitAttribute(Attribute attribute) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitCode() {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitInsn(int opcode) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitIntInsn(int opcode, int operand) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitVarInsn(int opcode, int varIndex) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitTypeInsn(int opcode, String type) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitFieldInsn(int opcode, String owner, String name, String descriptor) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitMethodInsn(int opcode, String owner, String name, String descriptor) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle,\n\t                                   Object... bootstrapMethodArguments) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitJumpInsn(int opcode, Label label) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitLabel(Label label) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitLdcInsn(Object value) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitIincInsn(int varIndex, int increment) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitMultiANewArrayInsn(String descriptor, int numDimensions) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitTryCatchBlock(Label start, Label end, Label handler, String type) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String descriptor, boolean visible) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitLineNumber(int line, Label start) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitMaxs(int maxStack, int maxLocals) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitEnd() {\n\t\t// no-op\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/SyntheticRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.Opcodes;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * A visitor that strips synthetic flags from classes.\n *\n * @author Matt Coley\n */\npublic class SyntheticRemovingVisitor extends ClassVisitor {\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t */\n\tpublic SyntheticRemovingVisitor(@Nullable ClassVisitor cv) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t}\n\n\tprivate static int strip(int access) {\n\t\treturn access & ~(Opcodes.ACC_SYNTHETIC | Opcodes.ACC_BRIDGE);\n\t}\n\n\t@Override\n\tpublic void visit(int version, int access, String name, String sig, String superName, String[] interfaces) {\n\t\taccess = strip(access);\n\t\tsuper.visit(version, access, name, sig, superName, interfaces);\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String desc, String sig, Object value) {\n\t\taccess = strip(access);\n\t\treturn super.visitField(access, name, desc, sig, value);\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] exceptions) {\n\t\taccess = strip(access);\n\t\treturn super.visitMethod(access, name, desc, sig, exceptions);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/TypeVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.*;\nimport org.slf4j.Logger;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.analytics.logging.Logging;\n\nimport java.util.function.Consumer;\n\n/**\n * Visitor to accept top-level types of referenced fields, methods, annotations, and nest mates.\n *\n * @author Matt Coley\n */\npublic class TypeVisitor extends ClassVisitor {\n\tprivate static final Logger logger = Logging.get(TypeVisitor.class);\n\tprivate final Consumer<Type> typeConsumer;\n\n\t/**\n\t * @param typeConsumer\n\t * \t\tType consumer to accept seen types.\n\t * \t\tThe same type may be visited multiple times.\n\t * \t\tMethod types are also passed in.\n\t */\n\tpublic TypeVisitor(@Nonnull Consumer<Type> typeConsumer) {\n\t\tsuper(RecafConstants.getAsmVersion());\n\t\tthis.typeConsumer = typeConsumer;\n\t}\n\n\t@Override\n\tpublic void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {\n\t\tif (interfaces != null)\n\t\t\tfor (String exception : interfaces)\n\t\t\t\tacceptType(exception);\n\t\tacceptType(superName);\n\t}\n\n\t@Override\n\tpublic void visitSource(String source, String debug) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic ModuleVisitor visitModule(String name, int access, String version) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitNestHost(String nestHost) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitOuterClass(String owner, String name, String descriptor) {\n\t\tacceptDescriptor(descriptor);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {\n\t\tacceptDescriptor(descriptor);\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {\n\t\tacceptDescriptor(descriptor);\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitAttribute(Attribute attribute) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void visitNestMember(String nestMember) {\n\t\tacceptType(nestMember);\n\t}\n\n\t@Override\n\tpublic void visitPermittedSubclass(String permittedSubclass) {\n\t\tacceptType(permittedSubclass);\n\t}\n\n\t@Override\n\tpublic void visitInnerClass(String name, String outerName, String innerName, int access) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {\n\t\tacceptDescriptor(descriptor);\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {\n\t\tacceptDescriptor(descriptor);\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\tacceptDescriptor(descriptor);\n\t\tif (exceptions != null)\n\t\t\tfor (String exception : exceptions)\n\t\t\t\tacceptType(exception);\n\t\treturn null;\n\t}\n\n\tprivate void acceptType(@Nullable String internalName) {\n\t\tif (internalName == null || internalName.isEmpty()) return;\n\n\t\ttry {\n\t\t\tType methodType = Type.getObjectType(internalName);\n\t\t\ttypeConsumer.accept(methodType);\n\t\t} catch (Throwable t) {\n\t\t\tlogger.trace(\"Ignored invalid internal name: {}\", internalName, t);\n\t\t}\n\t}\n\n\tprivate void acceptDescriptor(@Nullable String descriptor) {\n\t\tif (descriptor == null || descriptor.isEmpty()) return;\n\n\t\ttry {\n\t\t\tif (descriptor.charAt(0) == '(') {\n\t\t\t\tType methodType = Type.getMethodType(descriptor);\n\t\t\t\ttypeConsumer.accept(methodType);\n\t\t\t} else {\n\t\t\t\tType type = Type.getType(descriptor);\n\t\t\t\ttypeConsumer.accept(type);\n\t\t\t}\n\t\t} catch (Throwable t) {\n\t\t\tlogger.trace(\"Ignored invalid type: {}\", descriptor, t);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/UnknownAttributeRemovingVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.Attribute;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.FieldVisitor;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.RecordComponentVisitor;\nimport software.coley.recaf.RecafConstants;\n\nimport java.util.function.Predicate;\n\n/**\n * A visitor that strips unrecognized attributes from classes.\n *\n * @author Matt Coley\n */\npublic class UnknownAttributeRemovingVisitor extends ClassVisitor {\n\tprivate final Predicate<Attribute> whitelist;\n\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t */\n\tpublic UnknownAttributeRemovingVisitor(@Nullable ClassVisitor cv) {\n\t\tthis(attr -> false, cv);\n\t}\n\n\t/**\n\t * @param whitelist\n\t * \t\tAttribute whitelist function.\n\t * @param cv\n\t * \t\tParent visitor.\n\t */\n\tpublic UnknownAttributeRemovingVisitor(@Nonnull Predicate<Attribute> whitelist, @Nullable ClassVisitor cv) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t\tthis.whitelist = whitelist;\n\t}\n\n\t@Override\n\tpublic void visitAttribute(Attribute attribute) {\n\t\tif (whitelist.test(attribute))\n\t\t\tsuper.visitAttribute(attribute);\n\t}\n\n\t@Override\n\tpublic RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {\n\t\tRecordComponentVisitor rv = super.visitRecordComponent(name, descriptor, signature);\n\t\treturn new RecordComponentVisitor(RecafConstants.getAsmVersion(), rv) {\n\t\t\t@Override\n\t\t\tpublic void visitAttribute(Attribute attribute) {\n\t\t\t\tif (whitelist.test(attribute))\n\t\t\t\t\tsuper.visitAttribute(attribute);\n\t\t\t}\n\t\t};\n\t}\n\n\t@Override\n\tpublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {\n\t\tFieldVisitor fv = super.visitField(access, name, descriptor, signature, value);\n\t\treturn new FieldVisitor(RecafConstants.getAsmVersion(), fv) {\n\t\t\t@Override\n\t\t\tpublic void visitAttribute(Attribute attribute) {\n\t\t\t\tif (whitelist.test(attribute))\n\t\t\t\t\tsuper.visitAttribute(attribute);\n\t\t\t}\n\t\t};\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\tMethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);\n\t\treturn new MethodVisitor(RecafConstants.getAsmVersion(), mv) {\n\t\t\t@Override\n\t\t\tpublic void visitAttribute(Attribute attribute) {\n\t\t\t\tif (whitelist.test(attribute))\n\t\t\t\t\tsuper.visitAttribute(attribute);\n\t\t\t}\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/VariableRemovingClassVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.*;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * A visitor that strips variable data from all methods in a class.\n *\n * @author Matt Coley\n */\npublic class VariableRemovingClassVisitor extends ClassVisitor {\n\t/**\n\t * @param cv\n\t * \t\tParent visitor.\n\t */\n\tpublic VariableRemovingClassVisitor(@Nullable ClassVisitor cv) {\n\t\tsuper(RecafConstants.getAsmVersion(), cv);\n\t}\n\n\t@Override\n\tpublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {\n\t\tMethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);\n\t\treturn new VariableRemovingMethodVisitor(mv);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/VariableRemovingMethodVisitor.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.TypePath;\nimport software.coley.recaf.RecafConstants;\n\n/**\n * A visitor that strips variable data from all methods.\n *\n * @author Matt Coley\n */\npublic class VariableRemovingMethodVisitor extends MethodVisitor {\n\t/**\n\t * @param mv\n\t * \t\tParent visitor.\n\t */\n\tpublic VariableRemovingMethodVisitor(@Nullable MethodVisitor mv) {\n\t\tsuper(RecafConstants.getAsmVersion(), mv);\n\t}\n\n\t@Override\n\tpublic AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  Label[] start, Label[] end, int[] index,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  String descriptor, boolean visible) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void visitLocalVariable(String name, String desc, String sig, Label start, Label end, int index) {\n\t\t// Do not visit\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/util/visitors/WorkspaceClassWriter.java",
    "content": "package software.coley.recaf.util.visitors;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\n\n/**\n * Class writer that pulls inheritance information from a workspace via {@link InheritanceGraph}.\n *\n * @author Matt Coley\n */\npublic class WorkspaceClassWriter extends ClassWriter {\n\tprivate final InheritanceGraph inheritanceGraph;\n\n\t/**\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph to pull inheritance relations from.\n\t * @param flags\n\t * \t\tWriter flags.\n\t */\n\tpublic WorkspaceClassWriter(@Nonnull InheritanceGraph inheritanceGraph, int flags) {\n\t\tthis(inheritanceGraph, null, flags);\n\t}\n\n\t/**\n\t * @param inheritanceGraph\n\t * \t\tInheritance graph to pull inheritance relations from.\n\t * @param reader\n\t * \t\tReader to pre-populate the constant pool with. Speeds up writing process a bit.\n\t * @param flags\n\t * \t\tWriter flags.\n\t */\n\tpublic WorkspaceClassWriter(@Nonnull InheritanceGraph inheritanceGraph, @Nullable ClassReader reader, int flags) {\n\t\tsuper(reader, flags);\n\t\tthis.inheritanceGraph = inheritanceGraph;\n\t}\n\n\t@Override\n\tprotected String getCommonSuperClass(String type1, String type2) {\n\t\t// Default assumption if a type isn't given.\n\t\tif (type1 == null || type2 == null)\n\t\t\treturn \"java/lang/Object\";\n\n\t\t// Find common parent in workspace\n\t\treturn inheritanceGraph.getCommon(type1, type2);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/BasicWorkspace.java",
    "content": "package software.coley.recaf.workspace.model;\n\nimport jakarta.annotation.Nonnull;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.Closing;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.workspace.model.resource.AndroidApiResource;\nimport software.coley.recaf.workspace.model.resource.RuntimeWorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * Basic workspace implementation.\n *\n * @author Matt Coley\n */\npublic class BasicWorkspace implements Workspace {\n\tprivate static final Logger logger = Logging.get(BasicWorkspace.class);\n\tprivate final List<WorkspaceModificationListener> modificationListeners = new CopyOnWriteArrayList<>();\n\tprivate final WorkspaceResource primary;\n\tprivate final List<WorkspaceResource> supporting = new ArrayList<>();\n\tprivate final List<WorkspaceResource> internal;\n\tprivate List<WorkspaceResource> cachedAllResourcesNoInternal;\n\tprivate List<WorkspaceResource> cachedAllResources;\n\n\t/**\n\t * @param primary\n\t * \t\tPrimary resource.\n\t */\n\tpublic BasicWorkspace(@Nonnull WorkspaceResource primary) {\n\t\tthis(primary, Collections.emptyList());\n\t}\n\n\t/**\n\t * @param primary\n\t * \t\tPrimary resource.\n\t * @param supporting\n\t * \t\tProvided supporting resources.\n\t */\n\tpublic BasicWorkspace(@Nonnull WorkspaceResource primary, @Nonnull Collection<WorkspaceResource> supporting) {\n\t\tthis(primary, supporting, true);\n\t}\n\n\t/**\n\t * @param primary\n\t * \t\tPrimary resource.\n\t * @param supporting\n\t * \t\tProvided supporting resources.\n\t * @param useInternalResources\n\t *        {@code true} to include internal resources such as {@link RuntimeWorkspaceResource} and {@link AndroidApiResource}.\n\t */\n\tpublic BasicWorkspace(@Nonnull WorkspaceResource primary, @Nonnull Collection<WorkspaceResource> supporting, boolean useInternalResources) {\n\t\tthis.primary = primary;\n\t\tthis.supporting.addAll(supporting);\n\n\t\tif (useInternalResources) {\n\t\t\tRuntimeWorkspaceResource runtimeResource = RuntimeWorkspaceResource.getInstance();\n\t\t\tif (primary.getAndroidClassBundles().isEmpty()) {\n\t\t\t\tinternal = Collections.singletonList(runtimeResource);\n\t\t\t} else {\n\t\t\t\tinternal = List.of(runtimeResource, AndroidApiResource.getInstance());\n\t\t\t}\n\t\t} else {\n\t\t\tinternal = Collections.emptyList();\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic WorkspaceResource getPrimaryResource() {\n\t\treturn primary;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<WorkspaceResource> getSupportingResources() {\n\t\treturn Collections.unmodifiableList(supporting);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<WorkspaceResource> getInternalSupportingResources() {\n\t\t// Internal list is already unmodifiable, no need to wrap.\n\t\treturn internal;\n\t}\n\n\t@Override\n\tpublic void addSupportingResource(@Nonnull WorkspaceResource resource) {\n\t\tcachedAllResources = null;\n\t\tsupporting.add(resource);\n\t\tUnchecked.checkedForEach(modificationListeners, listener -> listener.onAddLibrary(this, resource),\n\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when adding supporting resource\", t));\n\t}\n\n\t@Override\n\tpublic boolean removeSupportingResource(@Nonnull WorkspaceResource resource) {\n\t\tboolean remove = supporting.remove(resource);\n\t\tif (remove) {\n\t\t\tcachedAllResources = null;\n\t\t\tUnchecked.checkedForEach(modificationListeners, listener -> listener.onRemoveLibrary(this, resource),\n\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when removing supporting resource\", t));\n\t\t}\n\t\treturn remove;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<WorkspaceModificationListener> getWorkspaceModificationListeners() {\n\t\treturn Collections.unmodifiableList(modificationListeners);\n\t}\n\n\t@Override\n\tpublic void addWorkspaceModificationListener(@Nonnull WorkspaceModificationListener listener) {\n\t\tPrioritySortable.add(modificationListeners, listener);\n\t}\n\n\t@Override\n\tpublic void removeWorkspaceModificationListener(@Nonnull WorkspaceModificationListener listener) {\n\t\tmodificationListeners.remove(listener);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<WorkspaceResource> getAllResources(boolean includeInternal) {\n\t\t// Cache the list of all resources since it is commonly used and the underlying stream\n\t\t// collection in the base implementation is expensive to compute.\n\t\tif (includeInternal) {\n\t\t\tList<WorkspaceResource> cached = this.cachedAllResources;\n\t\t\tif (cached != null)\n\t\t\t\treturn cached;\n\t\t\treturn this.cachedAllResources = Workspace.super.getAllResources(true);\n\t\t} else {\n\t\t\tList<WorkspaceResource> cached = this.cachedAllResourcesNoInternal;\n\t\t\tif (cached != null)\n\t\t\t\treturn cached;\n\t\t\treturn this.cachedAllResourcesNoInternal = Workspace.super.getAllResources(false);\n\t\t}\n\t}\n\n\t/**\n\t * Called by {@link WorkspaceManager} when the workspace is closed.\n\t */\n\t@Override\n\tpublic void close() {\n\t\tmodificationListeners.clear();\n\t\tsupporting.forEach(Closing::close);\n\t\tprimary.close();\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"BasicWorkspace{\" +\n\t\t\t\t\"primary=\" + primary +\n\t\t\t\t\", supporting=\" + supporting +\n\t\t\t\t'}';\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tWorkspace other = (Workspace) o;\n\n\t\tif (!primary.equals(other.getPrimaryResource())) return false;\n\t\treturn supporting.equals(other.getSupportingResources());\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = primary.hashCode();\n\t\tresult = 31 * result + supporting.hashCode();\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/EmptyWorkspace.java",
    "content": "package software.coley.recaf.workspace.model;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.properties.Property;\nimport software.coley.recaf.workspace.model.bundle.*;\nimport software.coley.recaf.workspace.model.resource.*;\n\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.NavigableMap;\n\n/**\n * Empty workspace for testing.\n *\n * @author Matt Coley\n */\npublic class EmptyWorkspace extends BasicWorkspace {\n\tprivate static final EmptyWorkspace INSTANCE = new EmptyWorkspace();\n\n\t/**\n\t * New empty workspace.\n\t */\n\tprivate EmptyWorkspace() {\n\t\tsuper(new EmptyWorkspaceResource());\n\t}\n\n\t/**\n\t * @return Singleton of empty workspace.\n\t */\n\t@Nonnull\n\tpublic static EmptyWorkspace get() {\n\t\treturn INSTANCE;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn EmptyWorkspace.class.getSimpleName();\n\t}\n\n\t/**\n\t * No-op / empty resource.\n\t */\n\tprivate static class EmptyWorkspaceResource implements WorkspaceResource {\n\t\tprivate final JvmClassBundle classes = new BasicJvmClassBundle() {\n\t\t\t@Override\n\t\t\tpublic JvmClassInfo get(@Nonnull Object key) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t};\n\t\tprivate final FileBundle files = new BasicFileBundle() {\n\t\t\t@Override\n\t\t\tpublic FileInfo put(@Nonnull String key, @Nonnull FileInfo newValue) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t};\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic JvmClassBundle getJvmClassBundle() {\n\t\t\treturn classes;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic NavigableMap<Integer, VersionedJvmClassBundle> getVersionedJvmClassBundles() {\n\t\t\treturn Collections.emptyNavigableMap();\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Map<String, AndroidClassBundle> getAndroidClassBundles() {\n\t\t\treturn Collections.emptyMap();\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic FileBundle getFileBundle() {\n\t\t\treturn files;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Map<String, WorkspaceFileResource> getEmbeddedResources() {\n\t\t\treturn Collections.emptyMap();\n\t\t}\n\n\t\t@Override\n\t\tpublic WorkspaceResource getContainingResource() {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic void setContainingResource(WorkspaceResource resource) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic void addResourceJvmClassListener(ResourceJvmClassListener listener) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic void removeResourceJvmClassListener(ResourceJvmClassListener listener) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic void addResourceAndroidClassListener(ResourceAndroidClassListener listener) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic void removeResourceAndroidClassListener(ResourceAndroidClassListener listener) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic void addResourceFileListener(ResourceFileListener listener) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic void removeResourceFileListener(ResourceFileListener listener) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic void close() {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic <V> void setProperty(Property<V> property) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic void removeProperty(String key) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Map<String, Property<?>> getProperties() {\n\t\t\treturn Collections.emptyMap();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/Workspace.java",
    "content": "package software.coley.recaf.workspace.model;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.behavior.Closing;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.path.BundlePathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.DirectoryPathNode;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.VersionedJvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NavigableMap;\nimport java.util.Queue;\nimport java.util.SortedSet;\nimport java.util.TreeSet;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * Models a collection of user inputs, represented as {@link WorkspaceResource} instances.\n *\n * @author Matt Coley\n */\npublic interface Workspace extends Closing {\n\t/**\n\t * @return The primary resource, holding classes and files to modify.\n\t */\n\t@Nonnull\n\tWorkspaceResource getPrimaryResource();\n\n\t/**\n\t * @return List of <i>all</i> supporting resources, not including\n\t * {@link #getInternalSupportingResources() internal supporting resources}.\n\t */\n\t@Nonnull\n\tList<WorkspaceResource> getSupportingResources();\n\n\t/**\n\t * @return List of internal supporting resources. These are added automatically by Recaf to all workspaces.\n\t */\n\t@Nonnull\n\tList<WorkspaceResource> getInternalSupportingResources();\n\n\t/**\n\t * @param resource\n\t * \t\tResource to add to {@link #getSupportingResources()}.\n\t */\n\tvoid addSupportingResource(@Nonnull WorkspaceResource resource);\n\n\t/**\n\t * @param resource\n\t * \t\tResource to remove from {@link #getSupportingResources()}.\n\t *\n\t * @return {@code true} when the resource was removed.\n\t * {@code false} when it was not present.\n\t */\n\tboolean removeSupportingResource(@Nonnull WorkspaceResource resource);\n\n\t/**\n\t * @param includeInternal\n\t * \t\tFlag to include internal supporting resources.\n\t *\n\t * @return List of all resources in the workspace. Includes primary, supporting, and internal support resources.\n\t */\n\t@Nonnull\n\tdefault Stream<WorkspaceResource> allResourcesStream(boolean includeInternal) {\n\t\tList<WorkspaceResource> supportingResources = getSupportingResources();\n\t\tif (includeInternal) {\n\t\t\tList<WorkspaceResource> internalSupportingResources = getInternalSupportingResources();\n\t\t\treturn Stream.of(\n\t\t\t\t\tStream.of(getPrimaryResource()),\n\t\t\t\t\tsupportingResources.stream(),\n\t\t\t\t\tinternalSupportingResources.stream()\n\t\t\t).flatMap(Function.identity());\n\t\t}\n\t\treturn Stream.concat(Stream.of(getPrimaryResource()), supportingResources.stream());\n\t}\n\n\t/**\n\t * @param includeInternal\n\t * \t\tFlag to include internal supporting resources.\n\t *\n\t * @return List of all resources in the workspace. Includes primary, supporting, and internal support resources.\n\t */\n\t@Nonnull\n\tdefault List<WorkspaceResource> getAllResources(boolean includeInternal) {\n\t\treturn allResourcesStream(includeInternal).toList();\n\t}\n\n\t/**\n\t * @return Listeners for when the current workspace has its supporting resources updated.\n\t */\n\t@Nonnull\n\tList<WorkspaceModificationListener> getWorkspaceModificationListeners();\n\n\t/**\n\t * @param listener\n\t * \t\tModification listener to add.\n\t */\n\tvoid addWorkspaceModificationListener(@Nonnull WorkspaceModificationListener listener);\n\n\t/**\n\t * @param listener\n\t * \t\tModification listener to remove.\n\t */\n\tvoid removeWorkspaceModificationListener(@Nonnull WorkspaceModificationListener listener);\n\n\t/**\n\t * Searches for a class by the given name in the {@link WorkspaceResource#getJvmClassBundle()},\n\t * {@link WorkspaceResource#getVersionedJvmClassBundles()}, and {@link WorkspaceResource#getAndroidClassBundles()}\n\t * of all resources in the workspace <i>(Including embedded resources in other resources)</i>.\n\t *\n\t * @param name\n\t * \t\tClass name.\n\t *\n\t * @return Path to <i>the first</i> class matching the given name.\n\t */\n\t@Nullable\n\tdefault ClassPathNode findClass(@Nonnull String name) {\n\t\treturn findClass(true, name);\n\t}\n\n\t/**\n\t * Searches for a class by the given name in the {@link WorkspaceResource#getJvmClassBundle()},\n\t * {@link WorkspaceResource#getVersionedJvmClassBundles()}, and {@link WorkspaceResource#getAndroidClassBundles()}\n\t * of all resources in the workspace <i>(Including embedded resources in other resources)</i>.\n\t *\n\t * @param includeInternal\n\t * \t\tFlag to include internal supporting resources.\n\t * @param name\n\t * \t\tClass name.\n\t *\n\t * @return Path to <i>the first</i> class matching the given name.\n\t */\n\t@Nullable\n\tdefault ClassPathNode findClass(boolean includeInternal, @Nonnull String name) {\n\t\tClassPathNode result = findJvmClass(includeInternal, name);\n\t\tif (result == null)\n\t\t\tresult = findLatestVersionedJvmClass(name);\n\t\tif (result == null)\n\t\t\tresult = findAndroidClass(name);\n\t\treturn result;\n\t}\n\n\t/**\n\t * Searches for a class by the given name in the {@link WorkspaceResource#getJvmClassBundle()}\n\t * of all resources in the workspace <i>(Including embedded resources in other resources)</i>.\n\t *\n\t * @param name\n\t * \t\tClass name.\n\t *\n\t * @return Path to <i>the first</i> JVM class matching the given name.\n\t */\n\t@Nullable\n\tdefault ClassPathNode findJvmClass(@Nonnull String name) {\n\t\treturn findJvmClass(true, name);\n\t}\n\n\t/**\n\t * Searches for a class by the given name in the {@link WorkspaceResource#getJvmClassBundle()}\n\t * of all resources in the workspace <i>(Including embedded resources in other resources)</i>.\n\t *\n\t * @param includeInternal\n\t * \t\tFlag to include internal supporting resources.\n\t * @param name\n\t * \t\tClass name.\n\t *\n\t * @return Path to <i>the first</i> JVM class matching the given name.\n\t */\n\t@Nullable\n\tdefault ClassPathNode findJvmClass(boolean includeInternal, @Nonnull String name) {\n\t\tQueue<Collection<? extends WorkspaceResource>> resourceQueue = new ArrayDeque<>();\n\t\tCollection<? extends WorkspaceResource> resources = getAllResources(includeInternal);\n\t\tdo {\n\t\t\tfor (WorkspaceResource resource : resources) {\n\t\t\t\t// Check JVM bundles for class by the given name\n\t\t\t\tJvmClassInfo classInfo;\n\t\t\t\tfor (JvmClassBundle bundle : resource.jvmClassBundles()) {\n\t\t\t\t\tclassInfo = bundle.get(name);\n\t\t\t\t\tif (classInfo != null)\n\t\t\t\t\t\treturn PathNodes.classPath(this, resource, bundle, classInfo);\n\t\t\t\t}\n\t\t\t\tfor (VersionedJvmClassBundle versionedBundle : resource.getVersionedJvmClassBundles().values()) {\n\t\t\t\t\tclassInfo = versionedBundle.get(name);\n\t\t\t\t\tif (classInfo != null)\n\t\t\t\t\t\treturn PathNodes.classPath(this, resource, versionedBundle, classInfo);\n\t\t\t\t}\n\t\t\t\t// Queue up embedded resources\n\t\t\t\tresourceQueue.add(resource.getEmbeddedResources().values());\n\t\t\t}\n\t\t} while ((resources = resourceQueue.poll()) != null);\n\t\treturn null;\n\t}\n\n\t/**\n\t * Searches for a class by the given name in the {@link WorkspaceResource#getVersionedJvmClassBundles()}\n\t * of all resources in the workspace <i>(Including embedded resources in other resources)</i>.\n\t *\n\t * @param name\n\t * \t\tClass name.\n\t *\n\t * @return @return Path to <i>the first</i> versioned JVM class matching the given name.\n\t * If there are multiple versions of the class, the highest is used.\n\t */\n\t@Nullable\n\tdefault ClassPathNode findLatestVersionedJvmClass(@Nonnull String name) {\n\t\treturn findVersionedJvmClass(name, Integer.MAX_VALUE);\n\t}\n\n\t/**\n\t * Searches for a class by the given name in the target version bundle within the\n\t * {@link WorkspaceResource#getVersionedJvmClassBundles()} of all resources in the workspace\n\t * <i>(Including embedded resources in other resources)</i>.\n\t *\n\t * @param name\n\t * \t\tClass name.\n\t * @param version\n\t * \t\tVersion to look for.\n\t * \t\tThis value is dropped down to the first available version bundle via {@link NavigableMap#floorEntry(Object)}.\n\t *\n\t * @return Path to <i>the highest</i> versioned JVM class matching the given name, supporting the given version.\n\t */\n\t@Nullable\n\tdefault ClassPathNode findVersionedJvmClass(@Nonnull String name, int version) {\n\t\t// Internal resources don't have versioned classes, so we won't iterate over those.\n\t\tQueue<Collection<? extends WorkspaceResource>> resourceQueue = new ArrayDeque<>();\n\t\tCollection<? extends WorkspaceResource> resources = getAllResources(false);\n\t\tdo {\n\t\t\tfor (WorkspaceResource resource : resources) {\n\t\t\t\t// Check versioned bundles for class by the given name, in descending order from the given version.\n\t\t\t\tNavigableMap<Integer, VersionedJvmClassBundle> versionedBundleMap = resource.getVersionedJvmClassBundles();\n\t\t\t\tMap.Entry<Integer, VersionedJvmClassBundle> entry = versionedBundleMap.floorEntry(version);\n\t\t\t\twhile (entry != null) {\n\t\t\t\t\tVersionedJvmClassBundle versionedBundle = entry.getValue();\n\t\t\t\t\tJvmClassInfo classInfo = versionedBundle.get(name);\n\t\t\t\t\tif (classInfo != null)\n\t\t\t\t\t\treturn PathNodes.classPath(this, resource, versionedBundle, classInfo);\n\t\t\t\t\tentry = versionedBundleMap.floorEntry(entry.getKey() - 1);\n\t\t\t\t}\n\n\t\t\t\t// Queue up embedded resources.\n\t\t\t\tresourceQueue.add(resource.getEmbeddedResources().values());\n\t\t\t}\n\t\t} while ((resources = resourceQueue.poll()) != null);\n\t\treturn null;\n\t}\n\n\t/**\n\t * Searches for a class by the given name in the {@link WorkspaceResource#getAndroidClassBundles()}\n\t * of all resources in the workspace <i>(Including embedded resources in other resources)</i>.\n\t *\n\t * @param name\n\t * \t\tClass name.\n\t *\n\t * @return Path to <i>the first</i> Android class matching the given name.\n\t */\n\t@Nullable\n\tdefault ClassPathNode findAndroidClass(@Nonnull String name) {\n\t\t// Internal resources don't have android classes, so we won't iterate over those.\n\t\tQueue<WorkspaceResource> resourceQueue = new ArrayDeque<>(getAllResources(false));\n\t\twhile (!resourceQueue.isEmpty()) {\n\t\t\tWorkspaceResource resource = resourceQueue.remove();\n\n\t\t\t// Check all android bundles (dex files).\n\t\t\tfor (AndroidClassBundle bundle : resource.getAndroidClassBundles().values()) {\n\t\t\t\tAndroidClassInfo classInfo = bundle.get(name);\n\t\t\t\tif (classInfo != null)\n\t\t\t\t\treturn PathNodes.classPath(this, resource, bundle, classInfo);\n\t\t\t}\n\n\t\t\t// Queue up embedded resources.\n\t\t\tresourceQueue.addAll(resource.getEmbeddedResources().values());\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tPackage name.\n\t *\n\t * @return Path to <i>the first</i> package matching the given name.\n\t */\n\t@Nullable\n\tdefault DirectoryPathNode findPackage(@Nonnull String name) {\n\t\t// Map '.' to '/' in case users pass in the common dot format instead.\n\t\tname = name.replace('.', '/');\n\n\t\t// Modify input such that the package name we compare against ends with '/'.\n\t\t// This prevents confusing matches like \"com/example/a\" from matching against contents in \"com/example/abc\".\n\t\tString cmp;\n\t\tif (name.endsWith(\"/\")) {\n\t\t\tcmp = name;\n\t\t\tname = name.substring(0, name.length() - 1);\n\t\t} else {\n\t\t\tcmp = name + \"/\";\n\t\t}\n\n\t\t// We *may* want to allow specifying 'internal=true' some time later, but for now I haven't had\n\t\t// a particular use case for that.\n\t\tQueue<WorkspaceResource> resourceQueue = new ArrayDeque<>(getAllResources(false));\n\t\twhile (!resourceQueue.isEmpty()) {\n\t\t\tWorkspaceResource resource = resourceQueue.remove();\n\n\t\t\t// Check all class bundles for entries that contain the package name.\n\t\t\tfor (ClassBundle<? extends ClassInfo> bundle : resource.classBundleStream().toList()) {\n\t\t\t\tfor (String key : bundle.keySet()) {\n\t\t\t\t\tif (key.startsWith(cmp))\n\t\t\t\t\t\treturn PathNodes.directoryPath(this, resource, bundle, name);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Queue up embedded resources.\n\t\t\tresourceQueue.addAll(resource.getEmbeddedResources().values());\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param filter\n\t * \t\tClass filter.\n\t *\n\t * @return Classes matching the given filter.\n\t */\n\t@Nonnull\n\tdefault SortedSet<ClassPathNode> findClasses(@Nonnull Predicate<ClassInfo> filter) {\n\t\treturn findClasses(true, filter);\n\t}\n\n\t/**\n\t * @param includeInternal\n\t * \t\tFlag to include internal supporting resources.\n\t * @param filter\n\t * \t\tClass filter.\n\t *\n\t * @return Classes matching the given filter.\n\t */\n\t@Nonnull\n\tdefault SortedSet<ClassPathNode> findClasses(boolean includeInternal, @Nonnull Predicate<ClassInfo> filter) {\n\t\tSortedSet<ClassPathNode> result = new TreeSet<>();\n\t\tresult.addAll(findJvmClasses(includeInternal, Unchecked.cast(filter)));\n\t\tresult.addAll(findAndroidClasses(Unchecked.cast(filter)));\n\t\treturn result;\n\t}\n\n\tdefault void forEachClass(boolean includeInternal, @Nonnull Consumer<? super ClassInfo> consumer) {\n\t\tforEachJvmClass(includeInternal, consumer);\n\t\tforEachAndroidClass(consumer);\n\t}\n\n\t/**\n\t * @return Stream of all classes.\n\t */\n\t@Nonnull\n\tdefault Stream<ClassPathNode> classesStream() {\n\t\treturn classesStream(true);\n\t}\n\n\t/**\n\t * @param includeInternal\n\t * \t\tFlag to include internal supporting resources.\n\t *\n\t * @return Stream of all classes.\n\t */\n\t@Nonnull\n\tdefault Stream<ClassPathNode> classesStream(boolean includeInternal) {\n\t\treturn Stream.concat(jvmClassesStream(includeInternal), androidClassesStream());\n\t}\n\n\t/**\n\t * @return Stream of JVM classes.\n\t */\n\t@Nonnull\n\tdefault Stream<ClassPathNode> jvmClassesStream() {\n\t\treturn jvmClassesStream(true);\n\t}\n\n\t/**\n\t * @param includeInternal\n\t * \t\tFlag to include internal supporting resources.\n\t *\n\t * @return Stream of JVM classes.\n\t */\n\t@Nonnull\n\tdefault Stream<ClassPathNode> jvmClassesStream(boolean includeInternal) {\n\t\treturn allResourcesStream(includeInternal)\n\t\t\t\t.flatMap(resource -> {\n\t\t\t\t\tFunction<WorkspaceResource, Stream<ClassPathNode>> streamBuilder = res -> {\n\t\t\t\t\t\tStream<ClassPathNode> stream = null;\n\t\t\t\t\t\tList<JvmClassBundle> bundles = new ArrayList<>();\n\t\t\t\t\t\tbundles.addAll(res.jvmClassBundleStream().toList());\n\t\t\t\t\t\tbundles.addAll(res.versionedJvmClassBundleStream().toList());\n\t\t\t\t\t\tfor (JvmClassBundle bundle : bundles) {\n\t\t\t\t\t\t\tBundlePathNode bundlePath = PathNodes.bundlePath(this, res, bundle);\n\t\t\t\t\t\t\tStream<ClassPathNode> localStream = bundle.values()\n\t\t\t\t\t\t\t\t\t.stream()\n\t\t\t\t\t\t\t\t\t.map(cls -> bundlePath.child(cls.getPackageName()).child(cls));\n\t\t\t\t\t\t\tif (stream == null) stream = localStream;\n\t\t\t\t\t\t\telse stream = Stream.concat(stream, localStream);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn stream;\n\t\t\t\t\t};\n\t\t\t\t\tStream<ClassPathNode> stream = streamBuilder.apply(resource);\n\n\t\t\t\t\t// Visit embedded resources\n\t\t\t\t\tQueue<WorkspaceFileResource> embeddedResources = new ArrayDeque<>(resource.getEmbeddedResources().values());\n\t\t\t\t\twhile (!embeddedResources.isEmpty()) {\n\t\t\t\t\t\tWorkspaceFileResource embeddedResource = embeddedResources.remove();\n\t\t\t\t\t\tstream = Stream.concat(stream, streamBuilder.apply(embeddedResource));\n\t\t\t\t\t\tembeddedResources.addAll(embeddedResource.getEmbeddedResources().values());\n\t\t\t\t\t}\n\n\t\t\t\t\treturn stream;\n\t\t\t\t});\n\t}\n\n\t/**\n\t * @return Stream of Android classes.\n\t */\n\t@Nonnull\n\tdefault Stream<ClassPathNode> androidClassesStream() {\n\t\t// Internal resources don't have android classes, so we won't iterate over those.\n\t\treturn allResourcesStream(false)\n\t\t\t\t.flatMap(resource -> {\n\t\t\t\t\tFunction<WorkspaceResource, Stream<ClassPathNode>> streamBuilder = res -> {\n\t\t\t\t\t\tStream<ClassPathNode> stream = null;\n\t\t\t\t\t\tfor (AndroidClassBundle bundle : res.getAndroidClassBundles().values()) {\n\t\t\t\t\t\t\tBundlePathNode bundlePath = PathNodes.bundlePath(this, res, bundle);\n\t\t\t\t\t\t\tStream<ClassPathNode> localStream = bundle.values()\n\t\t\t\t\t\t\t\t\t.stream()\n\t\t\t\t\t\t\t\t\t.map(cls -> bundlePath.child(cls.getPackageName()).child(cls));\n\t\t\t\t\t\t\tif (stream == null) stream = localStream;\n\t\t\t\t\t\t\telse stream = Stream.concat(stream, localStream);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn stream == null ? Stream.empty() : stream;\n\t\t\t\t\t};\n\t\t\t\t\tStream<ClassPathNode> stream = streamBuilder.apply(resource);\n\n\t\t\t\t\t// Visit embedded resources\n\t\t\t\t\tQueue<WorkspaceFileResource> embeddedResources = new ArrayDeque<>(resource.getEmbeddedResources().values());\n\t\t\t\t\twhile (!embeddedResources.isEmpty()) {\n\t\t\t\t\t\tWorkspaceFileResource embeddedResource = embeddedResources.remove();\n\t\t\t\t\t\tstream = Stream.concat(stream, streamBuilder.apply(embeddedResource));\n\t\t\t\t\t\tembeddedResources.addAll(embeddedResource.getEmbeddedResources().values());\n\t\t\t\t\t}\n\n\t\t\t\t\treturn stream;\n\t\t\t\t});\n\t}\n\n\t/**\n\t * @return Stream of all files.\n\t */\n\t@Nonnull\n\tdefault Stream<FilePathNode> filesStream() {\n\t\t// Internal resources don't have files, so we won't iterate over those.\n\t\treturn allResourcesStream(false)\n\t\t\t\t.flatMap(resource -> {\n\t\t\t\t\tFunction<WorkspaceResource, Stream<FilePathNode>> streamBuilder = res -> {\n\t\t\t\t\t\tFileBundle bundle = res.getFileBundle();\n\t\t\t\t\t\tBundlePathNode bundlePath = PathNodes.bundlePath(this, res, bundle);\n\t\t\t\t\t\treturn bundle.values()\n\t\t\t\t\t\t\t\t.stream()\n\t\t\t\t\t\t\t\t.map(cls -> bundlePath.child(cls.getDirectoryName()).child(cls));\n\t\t\t\t\t};\n\t\t\t\t\tStream<FilePathNode> stream = streamBuilder.apply(resource);\n\n\t\t\t\t\t// Visit embedded resources\n\t\t\t\t\tQueue<WorkspaceFileResource> embeddedResources = new ArrayDeque<>(resource.getEmbeddedResources().values());\n\t\t\t\t\twhile (!embeddedResources.isEmpty()) {\n\t\t\t\t\t\tWorkspaceFileResource embeddedResource = embeddedResources.remove();\n\t\t\t\t\t\tstream = Stream.concat(stream, streamBuilder.apply(embeddedResource));\n\t\t\t\t\t\tembeddedResources.addAll(embeddedResource.getEmbeddedResources().values());\n\t\t\t\t\t}\n\n\t\t\t\t\treturn stream;\n\t\t\t\t});\n\t}\n\n\t/**\n\t * @param filter\n\t * \t\tJVM class filter.\n\t *\n\t * @return Classes matching the given filter.\n\t */\n\t@Nonnull\n\tdefault SortedSet<ClassPathNode> findJvmClasses(@Nonnull Predicate<JvmClassInfo> filter) {\n\t\treturn findJvmClasses(true, filter);\n\t}\n\n\t/**\n\t * @param includeInternal\n\t * \t\tFlag to include internal supporting resources.\n\t * @param filter\n\t * \t\tJVM class filter.\n\t *\n\t * @return Classes matching the given filter.\n\t */\n\t@Nonnull\n\tdefault SortedSet<ClassPathNode> findJvmClasses(boolean includeInternal, @Nonnull Predicate<JvmClassInfo> filter) {\n\t\treturn jvmClassesStream(includeInternal)\n\t\t\t\t.filter(node -> filter.test((JvmClassInfo) node.getValue()))\n\t\t\t\t.collect(Collectors.toCollection(TreeSet::new));\n\t}\n\n\tdefault void forEachJvmClass(boolean includeInternal, @Nonnull Consumer<? super JvmClassInfo> consumer) {\n\t\tjvmClassesStream(includeInternal)\n\t\t\t\t.map(node -> (JvmClassInfo) node.getValue())\n\t\t\t\t.forEach(consumer);\n\t}\n\n\t/**\n\t * @param filter\n\t * \t\tAndroid class filter.\n\t *\n\t * @return Classes matching the given filter.\n\t */\n\t@Nonnull\n\tdefault SortedSet<ClassPathNode> findAndroidClasses(@Nonnull Predicate<AndroidClassInfo> filter) {\n\t\treturn androidClassesStream()\n\t\t\t\t.filter(node -> filter.test((AndroidClassInfo) node.getValue()))\n\t\t\t\t.collect(Collectors.toCollection(TreeSet::new));\n\t}\n\n\tdefault void forEachAndroidClass(@Nonnull Consumer<? super AndroidClassInfo> consumer) {\n\t\tandroidClassesStream()\n\t\t\t\t.map(node -> (AndroidClassInfo) node.getValue())\n\t\t\t\t.forEach(consumer);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tFile name.\n\t *\n\t * @return Path to <i>the first</i> file matching the given name.\n\t */\n\t@Nullable\n\tdefault FilePathNode findFile(@Nonnull String name) {\n\t\t// Internal resources don't have files, so we won't iterate over those.\n\t\tQueue<WorkspaceResource> resourceQueue = new ArrayDeque<>(getAllResources(false));\n\t\twhile (!resourceQueue.isEmpty()) {\n\t\t\tWorkspaceResource resource = resourceQueue.remove();\n\n\t\t\tFileBundle bundle = resource.getFileBundle();\n\t\t\tFileInfo fileInfo = bundle.get(name);\n\t\t\tif (fileInfo != null)\n\t\t\t\treturn PathNodes.filePath(this, resource, bundle, fileInfo);\n\n\t\t\t// Queue up embedded resources.\n\t\t\tresourceQueue.addAll(resource.getEmbeddedResources().values());\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param filter\n\t * \t\tFile filter.\n\t *\n\t * @return Files matching the given filter.\n\t */\n\t@Nonnull\n\tdefault SortedSet<FilePathNode> findFiles(@Nonnull Predicate<FileInfo> filter) {\n\t\treturn filesStream()\n\t\t\t\t.filter(node -> filter.test(node.getValue()))\n\t\t\t\t.collect(Collectors.toCollection(TreeSet::new));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/WorkspaceModificationListener.java",
    "content": "package software.coley.recaf.workspace.model;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Listener for receiving workspace update events.\n *\n * @author Matt Coley\n */\npublic interface WorkspaceModificationListener extends PrioritySortable {\n\t/**\n\t * @param workspace\n\t * \t\tThe workspace.\n\t * @param library\n\t * \t\tLibrary added to the workspace.\n\t */\n\tvoid onAddLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library);\n\n\t/**\n\t * @param workspace\n\t * \t\tThe workspace.\n\t * @param library\n\t * \t\tLibrary removed from the workspace.\n\t */\n\tvoid onRemoveLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library);\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/bundle/AndroidClassBundle.java",
    "content": "package software.coley.recaf.workspace.model.bundle;\n\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Bundle of Android classes in a {@link WorkspaceResource}.\n *\n * @author Matt Coley\n */\npublic interface AndroidClassBundle extends ClassBundle<AndroidClassInfo> {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/bundle/BasicAndroidClassBundle.java",
    "content": "package software.coley.recaf.workspace.model.bundle;\n\nimport software.coley.recaf.info.AndroidClassInfo;\n\n/**\n * Basic Android class bundle implementation.\n *\n * @author Matt Coley\n */\npublic class BasicAndroidClassBundle extends BasicBundle<AndroidClassInfo> implements AndroidClassBundle {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/bundle/BasicBundle.java",
    "content": "package software.coley.recaf.workspace.model.bundle;\n\nimport jakarta.annotation.Nonnull;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.workspace.model.resource.BasicWorkspaceResource;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NavigableSet;\nimport java.util.Set;\nimport java.util.Stack;\nimport java.util.TreeSet;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * Basic bundle implementation.\n *\n * @param <I>\n * \t\tItem type.\n *\n * @author Matt Coley\n */\npublic class BasicBundle<I extends Info> implements Bundle<I> {\n\tprivate static final Logger logger = Logging.get(BasicBundle.class);\n\tprivate final Map<String, Stack<I>> history = new ConcurrentHashMap<>();\n\tprivate final List<BundleListener<I>> listeners = new CopyOnWriteArrayList<>();\n\tprivate final Map<String, I> backing = new ConcurrentHashMap<>();\n\tprivate final Set<String> initialKeys = ConcurrentHashMap.newKeySet();\n\tprivate final NavigableSet<String> removed = Collections.synchronizedNavigableSet(new TreeSet<>());\n\tprivate int hash;\n\n\t/**\n\t * Create initial history item.\n\t *\n\t * @param info\n\t * \t\tOrigin item.\n\t */\n\tprivate void initHistory(@Nonnull I info) {\n\t\tStack<I> itemHistory = new Stack<>();\n\t\titemHistory.push(info);\n\t\thistory.put(info.getName(), itemHistory);\n\t}\n\n\t/**\n\t * Utility call for {@link #put(String, Info)}, without invoking the listener.\n\t *\n\t * @param info\n\t * \t\tItem to put.\n\t *\n\t * @see #markInitialState()\n\t */\n\tpublic void initialPut(@Nonnull I info) {\n\t\tbacking.put(info.getName(), info);\n\t\tinitHistory(info);\n\t}\n\n\t/**\n\t * Mark the current snapshot of items as the initial state of the bundle.\n\t * <p>\n\t * Called when {@link BasicWorkspaceResource} is constructed, so any bundle assigned to a resource should be\n\t * automatically marked.\n\t */\n\tpublic void markInitialState() {\n\t\tinitialKeys.addAll(backing.keySet());\n\t}\n\n\t/**\n\t * Utility call for {@link #put(String, Info)}\n\t *\n\t * @param info\n\t * \t\tItem to put.\n\t *\n\t * @return Prior associated value, if any.\n\t */\n\t@Override\n\tpublic I put(@Nonnull I info) {\n\t\treturn put(info.getName(), info);\n\t}\n\n\t/**\n\t * History contains a stack of prior states of items.\n\t * If an item has not been modified there is no entry in this map.\n\t *\n\t * @return Map of historical states of items within this bundle.\n\t */\n\t@Nonnull\n\tprotected Map<String, Stack<I>> getHistory() {\n\t\treturn history;\n\t}\n\n\t@Override\n\tpublic Stack<I> getHistory(@Nonnull String key) {\n\t\treturn history.get(key);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> getDirtyKeys() {\n\t\tSet<String> dirty = new TreeSet<>();\n\t\thistory.forEach((key, itemHistory) -> {\n\t\t\tif (itemHistory.size() > 1)\n\t\t\t\tdirty.add(key);\n\t\t});\n\t\treturn dirty;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> getRemovedKeys() {\n\t\treturn Collections.unmodifiableNavigableSet(removed);\n\t}\n\n\t@Override\n\tpublic boolean hasHistory(@Nonnull String key) {\n\t\t// History implies there are past entries for the current value, hence more than one entry.\n\t\tStack<I> stack = history.get(key);\n\t\treturn stack != null && stack.size() > 1;\n\t}\n\n\t@Override\n\tpublic void incrementHistory(@Nonnull I info) {\n\t\tString key = info.getName();\n\t\tStack<I> itemHistory = getHistory(key);\n\t\tif (itemHistory == null)\n\t\t\tthrow new IllegalStateException(\"Failed history increment, no prior history to build on for: \" + key);\n\t\titemHistory.push(info);\n\n\t\t// Clear cached hash\n\t\tresetHash();\n\t}\n\n\t@Override\n\tpublic void decrementHistory(@Nonnull String key) {\n\t\tStack<I> itemHistory = getHistory(key);\n\t\tif (itemHistory == null) {\n\t\t\tthrow new IllegalStateException(\"Failed history decrement, no prior history to read from for: \" + key);\n\t\t}\n\t\tint size = itemHistory.size();\n\n\t\t// Update map with prior entry\n\t\tI currentItem = get(key);\n\t\tI priorItem;\n\t\tif (size > 1) {\n\t\t\titemHistory.pop(); // Pop current value off stack.\n\t\t\tpriorItem = itemHistory.peek(); // Yield prior value.\n\t\t} else {\n\t\t\tpriorItem = itemHistory.peek();\n\t\t}\n\t\tbacking.put(key, priorItem);\n\n\t\t// Clear cached hash\n\t\tresetHash();\n\n\t\t// Notify listeners\n\t\tUnchecked.checkedForEach(listeners, listener -> listener.onUpdateItem(key, currentItem, priorItem),\n\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when decrementing bundle history\", t));\n\t}\n\n\t@Override\n\tpublic void addBundleListener(@Nonnull BundleListener<I> listener) {\n\t\tPrioritySortable.add(listeners, listener);\n\t}\n\n\t@Override\n\tpublic void removeBundleListener(@Nonnull BundleListener<I> listener) {\n\t\tlisteners.remove(listener);\n\t}\n\n\t@Override\n\tpublic Iterator<I> iterator() {\n\t\treturn backing.values().iterator();\n\t}\n\n\t@Override\n\tpublic int size() {\n\t\treturn backing.size();\n\t}\n\n\t@Override\n\tpublic boolean isEmpty() {\n\t\treturn backing.isEmpty();\n\t}\n\n\t@Override\n\tpublic boolean containsKey(@Nonnull Object key) {\n\t\treturn backing.containsKey(key);\n\t}\n\n\t@Override\n\tpublic boolean containsValue(@Nonnull Object value) {\n\t\treturn backing.containsValue(value);\n\t}\n\n\t@Override\n\tpublic I get(@Nonnull Object key) {\n\t\treturn backing.get(key);\n\t}\n\n\t@Override\n\tpublic I put(@Nonnull String key, @Nonnull I newValue) {\n\t\tI oldValue = backing.put(key, newValue);\n\n\t\t// Ensure we don't track entries by this name as 'removed'\n\t\tremoved.remove(key);\n\n\t\t// Update history\n\t\tif (oldValue == null)\n\t\t\tinitHistory(newValue);\n\t\telse\n\t\t\tincrementHistory(newValue);\n\n\t\t// Clear cached hash\n\t\tresetHash();\n\n\t\t// Notify listeners\n\t\tUnchecked.checkedForEach(listeners, listener -> {\n\t\t\tif (oldValue == null) {\n\t\t\t\tlistener.onNewItem(key, newValue);\n\t\t\t} else {\n\t\t\t\tlistener.onUpdateItem(key, oldValue, newValue);\n\t\t\t}\n\t\t}, (listener, t) -> logger.error(\"Exception thrown when putting bundle item\", t));\n\t\treturn oldValue;\n\t}\n\n\t@Override\n\tpublic I remove(@Nonnull Object key) {\n\t\tI info = backing.remove(key);\n\t\tif (info != null) {\n\t\t\tString keyStr = (String) key;\n\n\t\t\t// Mark the entry key as being removed, but only if it was in the initial key-set.\n\t\t\t// Adding a file and removing it should not be tracked as a net-removal.\n\t\t\tif (initialKeys.contains(keyStr))\n\t\t\t\tremoved.add(keyStr);\n\n\t\t\t// Update history\n\t\t\thistory.remove(key);\n\n\t\t\t// Clear cached hash\n\t\t\tresetHash();\n\n\t\t\t// Notify listeners\n\t\t\tUnchecked.checkedForEach(listeners, listener -> listener.onRemoveItem(keyStr, info),\n\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when removing bundle item\", t));\n\t\t}\n\t\treturn info;\n\t}\n\n\t@Override\n\tpublic void putAll(@Nonnull Map<? extends String, ? extends I> map) {\n\t\tthrow new UnsupportedOperationException(\"Bundles cannot use 'putAll'\");\n\t}\n\n\t@Override\n\tpublic void clear() {\n\t\tMap<String, I> copy = new HashMap<>(backing);\n\n\t\tremoved.addAll(initialKeys);\n\t\tbacking.clear();\n\t\thistory.clear();\n\t\tresetHash();\n\n\t\t// Notify listeners\n\t\tcopy.forEach((keyStr, info) -> {\n\t\t\tUnchecked.checkedForEach(listeners, listener -> listener.onRemoveItem(keyStr, info),\n\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when removing bundle item\", t));\n\t\t});\n\t}\n\n\t@Override\n\tpublic Set<String> keySet() {\n\t\treturn backing.keySet();\n\t}\n\n\t@Override\n\tpublic Collection<I> values() {\n\t\treturn backing.values();\n\t}\n\n\t@Override\n\tpublic Set<Entry<String, I>> entrySet() {\n\t\treturn backing.entrySet();\n\t}\n\n\t@Override\n\tpublic void close() {\n\t\tlisteners.clear();\n\t\tclear();\n\t}\n\n\tprivate void resetHash() {\n\t\thash = 0;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn getClass().getSimpleName() + \"[\" + backing.size() + \" items]\";\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o)\n\t\t\treturn true;\n\t\tif (o == null || getClass() != o.getClass())\n\t\t\treturn false;\n\n\t\tBasicBundle<?> other = (BasicBundle<?>) o;\n\n\t\tif (!history.equals(other.history))\n\t\t\treturn false;\n\t\treturn backing.equals(other.backing);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = hash;\n\t\tif (result == 0) {\n\t\t\tresult = history.hashCode();\n\t\t\tresult = 31 * result + backing.hashCode();\n\t\t\thash = result;\n\t\t}\n\t\treturn result;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/bundle/BasicFileBundle.java",
    "content": "package software.coley.recaf.workspace.model.bundle;\n\nimport software.coley.recaf.info.FileInfo;\n\n/**\n * Basic file bundle implementation.\n *\n * @author Matt Coley\n */\npublic class BasicFileBundle extends BasicBundle<FileInfo> implements FileBundle {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/bundle/BasicJvmClassBundle.java",
    "content": "package software.coley.recaf.workspace.model.bundle;\n\nimport software.coley.recaf.info.JvmClassInfo;\n\n/**\n * Basic JVM class bundle implementation.\n *\n * @author Matt Coley\n */\npublic class BasicJvmClassBundle extends BasicBundle<JvmClassInfo> implements JvmClassBundle {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/bundle/BasicVersionedJvmClassBundle.java",
    "content": "package software.coley.recaf.workspace.model.bundle;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.properties.builtin.VersionedClassProperty;\n\n/**\n * Basic versioned JVM class bundle implementation.\n *\n * @author Matt Coley\n */\npublic class BasicVersionedJvmClassBundle extends BasicJvmClassBundle implements VersionedJvmClassBundle, BundleListener<JvmClassInfo> {\n\tprivate final int version;\n\n\t/**\n\t * @param version\n\t * \t\tAssociated version.\n\t */\n\tpublic BasicVersionedJvmClassBundle(int version) {\n\t\tthis.version = version;\n\n\t\t// Register self as listener. We'll apply the versioned class property to items added to this bundle.\n\t\taddBundleListener(this);\n\t}\n\n\t@Override\n\tpublic int version() {\n\t\treturn version;\n\t}\n\n\t@Override\n\tpublic void onNewItem(@Nonnull String key, @Nonnull JvmClassInfo value) {\n\t\tVersionedClassProperty.set(value, version);\n\t}\n\n\t@Override\n\tpublic void onUpdateItem(@Nonnull String key, @Nonnull JvmClassInfo oldValue, @Nonnull JvmClassInfo newValue) {\n\t\tVersionedClassProperty.set(newValue, version);\n\t}\n\n\t@Override\n\tpublic void onRemoveItem(@Nonnull String key, @Nonnull JvmClassInfo value) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn super.toString() + \" - version=\" + version;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/bundle/Bundle.java",
    "content": "package software.coley.recaf.workspace.model.bundle;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.behavior.Closing;\nimport software.coley.recaf.info.Info;\n\nimport java.util.*;\nimport java.util.stream.Stream;\n\n/**\n * Base bundle type.\n *\n * @param <I>\n * \t\tBundle value type.\n *\n * @author Matt Coley\n */\npublic interface Bundle<I extends Info> extends Map<String, I>, Iterable<I>, Closing {\n\t/**\n\t * History stack for the given item key.\n\t *\n\t * @param key\n\t * \t\tItem key.\n\t *\n\t * @return History of item.\n\t */\n\t@Nullable\n\tStack<I> getHistory(String key);\n\n\t/**\n\t * @return Keys of items that have been modified <i>(Containing any history values)</i>.\n\t */\n\t@Nonnull\n\tSet<String> getDirtyKeys();\n\n\t/**\n\t * @return Keys of items that were part of the initial bundle contents but have since been removed.\n\t */\n\t@Nonnull\n\tSet<String> getRemovedKeys();\n\n\t/**\n\t * @param info\n\t * \t\tItem to write.\n\t *\n\t * @return Prior value if any.\n\t */\n\t@Nullable\n\tI put(I info);\n\n\t/**\n\t * @return A copied collection of {@link #values()}, allowing modification during iteration.\n\t */\n\t@Nonnull\n\tdefault Collection<I> valuesAsCopy() {\n\t\treturn new ArrayList<>(values());\n\t}\n\n\t/**\n\t * @return Stream of items.\n\t */\n\t@Nonnull\n\tdefault Stream<I> stream() {\n\t\treturn values().stream();\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tItem key.\n\t *\n\t * @return {@code true} if the item has a history. Any such item will also be present in {@link #getDirtyKeys()}.\n\t */\n\tboolean hasHistory(String key);\n\n\t/**\n\t * If the given item isn't part of the bundle, it is added and no historical record is kept.\n\t * Otherwise, the existing item's history is incremented.\n\t *\n\t * @param info\n\t * \t\tItem to update.\n\t */\n\tvoid incrementHistory(I info);\n\n\t/**\n\t * Decrement the history of the value associated with the key.\n\t * The value removed in this operation then replaces the value of the item map.\n\t *\n\t * @param key\n\t * \t\tItem key.\n\t */\n\tvoid decrementHistory(String key);\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tvoid addBundleListener(BundleListener<I> listener);\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tvoid removeBundleListener(BundleListener<I> listener);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/bundle/BundleListener.java",
    "content": "package software.coley.recaf.workspace.model.bundle;\n\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\n\n/**\n * Listener for updates to contents within a {@link Bundle}.\n *\n * @param <I>\n * \t\tBundle item type.\n *\n * @author Matt Coley\n */\npublic interface BundleListener<I> extends PrioritySortable {\n\t/**\n\t * @param key\n\t * \t\tItem key.\n\t * @param value\n\t * \t\tItem value.\n\t */\n\tvoid onNewItem(@Nonnull String key, @Nonnull I value);\n\n\t/**\n\t * @param key\n\t * \t\tItem key.\n\t * @param oldValue\n\t * \t\tPrior item value.\n\t * @param newValue\n\t * \t\tNew item value.\n\t */\n\tvoid onUpdateItem(@Nonnull String key, @Nonnull I oldValue, @Nonnull I newValue);\n\n\t/**\n\t * @param key\n\t * \t\tItem key.\n\t * @param value\n\t * \t\tItem value.\n\t */\n\tvoid onRemoveItem(@Nonnull String key, @Nonnull I value);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/bundle/ClassBundle.java",
    "content": "package software.coley.recaf.workspace.model.bundle;\n\nimport software.coley.recaf.info.ClassInfo;\n\n/**\n * Common base for bundles containing {@link ClassInfo} child types.\n *\n * @param <I>\n * \t\tBundle value type.\n *\n * @author Matt Coley\n * @see JvmClassBundle\n * @see AndroidClassBundle\n */\npublic interface ClassBundle<I extends ClassInfo> extends Bundle<I> {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/bundle/FileBundle.java",
    "content": "package software.coley.recaf.workspace.model.bundle;\n\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Bundle of files in a {@link WorkspaceResource}.\n *\n * @author Matt Coley\n */\npublic interface FileBundle extends Bundle<FileInfo> {\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/bundle/JvmClassBundle.java",
    "content": "package software.coley.recaf.workspace.model.bundle;\n\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Bundle of JVM classes in a {@link WorkspaceResource}.\n *\n * @author Matt Coley\n */\npublic interface JvmClassBundle extends ClassBundle<JvmClassInfo> {}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/bundle/VersionedJvmClassBundle.java",
    "content": "package software.coley.recaf.workspace.model.bundle;\n\nimport software.coley.recaf.info.JarFileInfo;\n\n/**\n * Bundle of versioned JVM classes in a JAR under the {@link JarFileInfo#MULTI_RELEASE_PREFIX} .\n *\n * @author Matt Coley\n */\npublic interface VersionedJvmClassBundle extends JvmClassBundle {\n\t/**\n\t * @return The associated version of classes in this bundle. Uses standard version, so Java 8 is 8.\n\t */\n\tint version();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/AgentServerRemoteVmResource.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport com.google.common.collect.Iterables;\nimport com.sun.tools.attach.VirtualMachine;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.instrument.ApiConstants;\nimport software.coley.instrument.Client;\nimport software.coley.instrument.data.ClassData;\nimport software.coley.instrument.data.ClassLoaderInfo;\nimport software.coley.instrument.message.MessageConstants;\nimport software.coley.instrument.message.broadcast.BroadcastClassMessage;\nimport software.coley.instrument.message.broadcast.BroadcastClassloaderMessage;\nimport software.coley.instrument.message.request.RequestClassMessage;\nimport software.coley.instrument.message.request.RequestClassloaderClassesMessage;\nimport software.coley.instrument.message.request.RequestClassloadersMessage;\nimport software.coley.instrument.message.request.RequestRedefineMessage;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.info.properties.builtin.RemoteClassloaderProperty;\nimport software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.BundleListener;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\n\nimport java.io.IOException;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentSkipListSet;\nimport java.util.stream.Stream;\n\n/**\n * Implementation of {@link WorkspaceRemoteVmResource} via {@link Client}.\n *\n * @author Matt Coley\n */\npublic class AgentServerRemoteVmResource extends BasicWorkspaceResource implements WorkspaceRemoteVmResource {\n\tprivate static final DebuggingLogger logger = Logging.get(AgentServerRemoteVmResource.class);\n\tprivate final Map<Integer, RemoteJvmClassBundle> remoteBundleMap = new ConcurrentHashMap<>();\n\tprivate final Map<Integer, ClassLoaderInfo> remoteLoaders = new ConcurrentHashMap<>();\n\tprivate final Map<Integer, Set<ClassData>> queuedClasses = new ConcurrentHashMap<>();\n\tprivate final Set<String> queuedRedefines = new ConcurrentSkipListSet<>();\n\tprivate final VirtualMachine virtualMachine;\n\tprivate final Client client;\n\tprivate boolean closed;\n\n\t/**\n\t * @param virtualMachine\n\t * \t\tInstance of remote VM.\n\t * @param client\n\t * \t\tClient to communicate to the remote VM.\n\t */\n\tpublic AgentServerRemoteVmResource(VirtualMachine virtualMachine, Client client) {\n\t\tsuper(new WorkspaceResourceBuilder());\n\t\tthis.virtualMachine = virtualMachine;\n\t\tthis.client = client;\n\n\t\t// Call the parent setup method.\n\t\tsuper.setup();\n\t}\n\n\t@Override\n\tprotected void setup() {\n\t\t// No-op here so the constructor doesn't call it before the fields in THIS class are initialized\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic VirtualMachine getVirtualMachine() {\n\t\treturn virtualMachine;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<Integer, ClassLoaderInfo> getRemoteLoaders() {\n\t\treturn remoteLoaders;\n\t}\n\n\t@Nonnull\n\t@Override\n\t@SuppressWarnings(\"unchecked\")\n\tpublic Map<Integer, JvmClassBundle> getJvmClassloaderBundles() {\n\t\treturn (Map<Integer, JvmClassBundle>) (Object) remoteBundleMap;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Iterable<JvmClassBundle> jvmClassBundles() {\n\t\treturn Iterables.concat(super.jvmClassBundles(), new ArrayList<>(remoteBundleMap.values()));\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Stream<JvmClassBundle> jvmClassBundleStream() {\n\t\treturn Stream.concat(super.jvmClassBundleStream(), new ArrayList<>(remoteBundleMap.values()).stream());\n\t}\n\n\t@Override\n\tpublic void close() {\n\t\ttry {\n\t\t\tsuper.close();\n\t\t} finally {\n\t\t\tclosed = true;\n\n\t\t\t// Close client connection\n\t\t\ttry {\n\t\t\t\tclient.close();\n\t\t\t} catch (IOException ex) {\n\t\t\t\tlogger.info(\"Failed to close client connection to remote VM: {}\", virtualMachine.id());\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void connect() throws IOException {\n\t\tif (closed)\n\t\t\tthrow new IOException(\"Cannot re-connect to closed resource. Please create a new one.\");\n\t\tclient.setBroadcastListener((messageType, message) -> {\n\t\t\tswitch (messageType) {\n\t\t\t\tcase MessageConstants.ID_BROADCAST_LOADER:\n\t\t\t\t\t// New loader reported\n\t\t\t\t\tBroadcastClassloaderMessage loaderMessage = (BroadcastClassloaderMessage) message;\n\t\t\t\t\tClassLoaderInfo loaderInfo = loaderMessage.getClassLoader();\n\t\t\t\t\tint loaderId = loaderInfo.getId();\n\t\t\t\t\tremoteLoaders.put(loaderId, loaderInfo);\n\n\t\t\t\t\t// If the loader was one that we saw classes for earlier, now we can\n\t\t\t\t\t// link those to this loader.\n\t\t\t\t\tSet<ClassData> pendingClasses = queuedClasses.remove(loaderId);\n\t\t\t\t\tif (pendingClasses != null) {\n\t\t\t\t\t\tfor (ClassData pendingClass : pendingClasses) {\n\t\t\t\t\t\t\thandleReceiveClassData(pendingClass, null);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase MessageConstants.ID_BROADCAST_CLASS:\n\t\t\t\t\t// New class, or update to existing class reported\n\t\t\t\t\tBroadcastClassMessage classMessage = (BroadcastClassMessage) message;\n\t\t\t\t\tClassData data = classMessage.getData();\n\t\t\t\t\thandleReceiveClassData(data, null);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\t// unknown broadcast packet\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t});\n\n\t\t// Try to connect\n\t\tlogger.info(\"Connecting to remote JVM '{}' over port {}\", virtualMachine.id(), client.getPort());\n\t\ttry {\n\t\t\tclient.connectThrowing();\n\t\t} catch (Exception ex) {\n\t\t\tthrow new IOException(\"Could not connect to remote JVM \" + virtualMachine.id() +\n\t\t\t\t\t\" over \" + client.getIp() + \":\" + client.getPort(), ex);\n\t\t}\n\n\t\t// Request known classloaders\n\t\tlogger.info(\"Sending initial request for classloaders & initial classes...\");\n\t\tclient.sendAsync(new RequestClassloadersMessage(), loaderReply -> {\n\t\t\tCollection<ClassLoaderInfo> classLoaders = loaderReply.getClassLoaders();\n\t\t\tlogger.info(\"Received initial response for classloaders, count={}\", classLoaders.size());\n\t\t\tfor (ClassLoaderInfo loader : classLoaders) {\n\t\t\t\tif (loader.isBootstrap())\n\t\t\t\t\tcontinue;\n\t\t\t\tint loaderId = loader.getId();\n\t\t\t\tremoteLoaders.put(loaderId, loader);\n\n\t\t\t\t// Get/create bundle for loader\n\t\t\t\tClassLoaderInfo loaderInfo = remoteLoaders.get(loaderId);\n\t\t\t\tRemoteJvmClassBundle bundle = remoteBundleMap\n\t\t\t\t\t\t.computeIfAbsent(loaderId, id -> createRemoteBundle(loaderInfo));\n\n\t\t\t\t// Request all classes from classloader\n\t\t\t\tlogger.info(\"Sending initial request for class names in classloader {}...\", loader.getName());\n\t\t\t\tclient.sendAsync(new RequestClassloaderClassesMessage(loaderId), classesReply -> {\n\t\t\t\t\tCollection<String> classes = classesReply.getClasses();\n\t\t\t\t\tlogger.info(\"Received initial response for class names in classloader {}, count={}\",\n\t\t\t\t\t\t\tloader.getName(), classes.size());\n\t\t\t\t\tfor (String className : classes) {\n\t\t\t\t\t\t// If class does not exist in bundle, then request it from remote server\n\t\t\t\t\t\tif (bundle.get(className) == null) {\n\t\t\t\t\t\t\tclient.sendAsync(new RequestClassMessage(loaderId, className), reply -> {\n\t\t\t\t\t\t\t\tif (reply.hasData()) {\n\t\t\t\t\t\t\t\t\tClassData data = reply.getData();\n\t\t\t\t\t\t\t\t\thandleReceiveClassData(data, bundle);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * @param data\n\t * \t\tClass data to handle adding to the resource.\n\t * @param bundle\n\t * \t\tBundle to check within.\n\t * \t\tMay be {@code null} to be lazily fetched in this method.\n\t */\n\tprivate void handleReceiveClassData(@Nonnull ClassData data, @Nullable RemoteJvmClassBundle bundle) {\n\t\t// If it belongs to the bootstrap classloader, it's a core JVM class.\n\t\tif (data.getClassLoaderId() == ApiConstants.BOOTSTRAP_CLASSLOADER_ID)\n\t\t\treturn;\n\n\t\t// If this class broadcast isn't for one of our redefine requests, it's a new class.\n\t\tif (!queuedRedefines.remove(data.getName())) {\n\t\t\tint loaderId = data.getClassLoaderId();\n\n\t\t\t// Get the bundle for the remote classloader if not specified by parameter\n\t\t\tif (bundle == null) {\n\t\t\t\tClassLoaderInfo loaderInfo = remoteLoaders.get(loaderId);\n\t\t\t\tif (loaderInfo == null) {\n\t\t\t\t\tqueuedClasses.computeIfAbsent(loaderId, i -> new HashSet<>()).add(data);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbundle = remoteBundleMap.computeIfAbsent(loaderId, id -> createRemoteBundle(loaderInfo));\n\t\t\t}\n\n\t\t\t// Add the class\n\t\t\tJvmClassInfo classInfo = new JvmClassInfoBuilder(data.getCode()).build();\n\t\t\tRemoteClassloaderProperty.set(classInfo, loaderId);\n\t\t\tbundle.put(classInfo);\n\t\t}\n\t}\n\n\t/**\n\t * Creates a new {@link RemoteJvmClassBundle} and sets up listener delegation.\n\t *\n\t * @param loaderInfo\n\t * \t\tLoader to associate with some classes in a bundle.\n\t *\n\t * @return New bundle for classes within that loader.\n\t */\n\t@Nonnull\n\tprivate RemoteJvmClassBundle createRemoteBundle(@Nonnull ClassLoaderInfo loaderInfo) {\n\t\tRemoteJvmClassBundle bundle = new RemoteJvmClassBundle(loaderInfo);\n\t\tdelegateJvmClassBundle(this, bundle);\n\t\treturn bundle;\n\t}\n\n\t/**\n\t * JVM bundle extension adding a listener to handle syncing local changes with the remote server.\n\t */\n\tpublic class RemoteJvmClassBundle extends BasicJvmClassBundle {\n\t\tprivate final ClassLoaderInfo loaderInfo;\n\n\t\tprivate RemoteJvmClassBundle(@Nonnull ClassLoaderInfo loaderInfo) {\n\t\t\tthis.loaderInfo = loaderInfo;\n\n\t\t\taddBundleListener(new BundleListener<>() {\n\t\t\t\t@Override\n\t\t\t\tpublic void onNewItem(@Nonnull String key, @Nonnull JvmClassInfo value) {\n\t\t\t\t\t// Should occur when we get data from the client.\n\t\t\t\t\t// No action needed.\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic void onUpdateItem(@Nonnull String key, @Nonnull JvmClassInfo oldValue, @Nonnull JvmClassInfo newValue) {\n\t\t\t\t\t// Should occur when the user makes changes to a class from recaf.\n\t\t\t\t\t// We need to send this definition to the remote server.\n\n\t\t\t\t\t// Record that we expect acknowledgement from the remote server for our redefine request.\n\t\t\t\t\tqueuedRedefines.add(key);\n\n\t\t\t\t\t// Request class update\n\t\t\t\t\tbyte[] definition = newValue.getBytecode();\n\t\t\t\t\tclient.sendAsync(new RequestRedefineMessage(loaderInfo.getId(), key, definition), reply -> {\n\t\t\t\t\t\tif (reply.isSuccess()) {\n\t\t\t\t\t\t\tlogger.debug(\"Redefine '{}' success\", key);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlogger.debug(\"Redefine '{}' failed: {}\", key, reply.getMessage());\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tpublic void onRemoveItem(@Nonnull String key, @Nonnull JvmClassInfo value) {\n\t\t\t\t\t// Should not occur\n\t\t\t\t\tthrow new IllegalStateException(\"Remove operations should not occur for remote VM resource!\");\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\t/**\n\t\t * @return Loader information for this bundle.\n\t\t */\n\t\t@Nonnull\n\t\tpublic ClassLoaderInfo getLoaderInfo() {\n\t\t\treturn loaderInfo;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/AndroidApiResource.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.slf4j.Logger;\nimport software.coley.lljzip.ZipIO;\nimport software.coley.lljzip.format.model.LocalFileHeader;\nimport software.coley.lljzip.format.model.ZipArchive;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.info.properties.BasicPropertyContainer;\nimport software.coley.recaf.util.io.LocalFileHeaderSource;\nimport software.coley.recaf.workspace.model.bundle.*;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.NavigableMap;\n\n/**\n * Implementation of a workspace resource sourced from the Android API.\n * This is a special case of resource which is automatically added to all workspaces with android content.\n * Listeners and such are not implemented and are ignored by design.\n *\n * @author Matt Coley\n */\npublic class AndroidApiResource extends BasicPropertyContainer implements WorkspaceResource {\n\tprivate static final Logger logger = Logging.get(AndroidApiResource.class);\n\tprivate static AndroidApiResource instance;\n\tprivate final FileBundle files = new BasicFileBundle();\n\tprivate final JvmClassBundle bundle;\n\n\tprivate AndroidApiResource(@Nonnull JvmClassBundle bundle) {\n\t\tthis.bundle = bundle;\n\t}\n\n\t/**\n\t * @return Shared instance of the Android API resource.\n\t */\n\t@Nonnull\n\tpublic static AndroidApiResource getInstance() {\n\t\tif (instance == null) {\n\t\t\tlogger.info(\"Initializing Android API support resource...\");\n\t\t\ttry {\n\t\t\t\t// We provide our own archive containing Android API classes.\n\t\t\t\tJvmClassBundle bundle = new BasicJvmClassBundle();\n\t\t\t\tbyte[] jar = AndroidApiResource.class.getResourceAsStream(\"/android/api-outline-30.jar\").readAllBytes();\n\t\t\t\tZipArchive archive = ZipIO.readJvm(jar);\n\t\t\t\tfor (LocalFileHeader fileEntry : archive.getLocalFiles()) {\n\t\t\t\t\tString name = fileEntry.getFileNameAsString();\n\t\t\t\t\tif (name.endsWith(\".class\")) {\n\t\t\t\t\t\tbyte[] bytecode = new LocalFileHeaderSource(fileEntry).readAll();\n\t\t\t\t\t\tJvmClassInfo info = new JvmClassInfoBuilder(bytecode).build();\n\t\t\t\t\t\tbundle.put(info);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tinstance = new AndroidApiResource(bundle);\n\t\t\t} catch (IOException ex) {\n\t\t\t\tthrow new IllegalStateException(ex);\n\t\t\t}\n\t\t}\n\t\treturn instance;\n\t}\n\n\t@Override\n\tpublic void close() {\n\t\t// no-op\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic JvmClassBundle getJvmClassBundle() {\n\t\treturn bundle;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic NavigableMap<Integer, VersionedJvmClassBundle> getVersionedJvmClassBundles() {\n\t\treturn Collections.emptyNavigableMap();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, AndroidClassBundle> getAndroidClassBundles() {\n\t\treturn Collections.emptyMap();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic FileBundle getFileBundle() {\n\t\treturn files;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, WorkspaceFileResource> getEmbeddedResources() {\n\t\treturn Collections.emptyMap();\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic WorkspaceResource getContainingResource() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void setContainingResource(WorkspaceResource resource) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void addResourceJvmClassListener(ResourceJvmClassListener listener) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void removeResourceJvmClassListener(ResourceJvmClassListener listener) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void addResourceAndroidClassListener(ResourceAndroidClassListener listener) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void removeResourceAndroidClassListener(ResourceAndroidClassListener listener) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void addResourceFileListener(ResourceFileListener listener) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void removeResourceFileListener(ResourceFileListener listener) {\n\t\t// no-op\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/BasicWorkspaceDirectoryResource.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.nio.file.Path;\n\n/**\n * Basic implementation of a workspace resource sourced from a directory.\n *\n * @author Matt Coley\n */\npublic class BasicWorkspaceDirectoryResource extends BasicWorkspaceResource implements WorkspaceDirectoryResource {\n\tprivate final Path directoryPath;\n\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull info from.\n\t */\n\tpublic BasicWorkspaceDirectoryResource(WorkspaceDirectoryResourceBuilder builder) {\n\t\tsuper(builder);\n\t\tthis.directoryPath = builder.getDirectoryPath();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Path getDirectoryPath() {\n\t\treturn directoryPath;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/BasicWorkspaceFileResource.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.FileInfo;\n\nimport java.util.Objects;\n\n/**\n * Basic implementation of a workspace resource sourced from a file.\n *\n * @author Matt Coley\n */\npublic class BasicWorkspaceFileResource extends BasicWorkspaceResource implements WorkspaceFileResource {\n\tprivate final FileInfo fileInfo;\n\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull info from.\n\t */\n\tpublic BasicWorkspaceFileResource(WorkspaceFileResourceBuilder builder) {\n\t\tsuper(builder);\n\t\tthis.fileInfo = Objects.requireNonNull(builder.getFileInfo(), \"Cannot construct file resource without an associated file\");\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic FileInfo getFileInfo() {\n\t\treturn fileInfo;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn super.toString() + \" \" + fileInfo.getName();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/BasicWorkspaceResource.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport jakarta.annotation.Nonnull;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.Closing;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.properties.BasicPropertyContainer;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicBundle;\nimport software.coley.recaf.workspace.model.bundle.BundleListener;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.VersionedJvmClassBundle;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NavigableMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * Basic workspace resource implementation.\n *\n * @author Matt Coley\n * @see WorkspaceResourceBuilder Helper for creating instances.\n */\npublic class BasicWorkspaceResource extends BasicPropertyContainer implements WorkspaceResource {\n\tprivate static final Logger logger = Logging.get(BasicWorkspaceResource.class);\n\tprivate final List<ResourceJvmClassListener> jvmClassListeners = new CopyOnWriteArrayList<>();\n\tprivate final List<ResourceAndroidClassListener> androidClassListeners = new CopyOnWriteArrayList<>();\n\tprivate final List<ResourceFileListener> fileListeners = new CopyOnWriteArrayList<>();\n\tprivate final JvmClassBundle jvmClassBundle;\n\tprivate final NavigableMap<Integer, VersionedJvmClassBundle> versionedJvmClassBundles;\n\tprivate final Map<String, AndroidClassBundle> androidClassBundles;\n\tprivate final FileBundle fileBundle;\n\tprivate final Map<String, WorkspaceFileResource> embeddedResources;\n\tprivate transient WorkspaceResource containingResource;\n\n\t/**\n\t * @param builder\n\t * \t\tBuilder to pull information from.\n\t */\n\tpublic BasicWorkspaceResource(@Nonnull WorkspaceResourceBuilder builder) {\n\t\tthis(builder.getJvmClassBundle(),\n\t\t\t\tbuilder.getFileBundle(),\n\t\t\t\tbuilder.getVersionedJvmClassBundles(),\n\t\t\t\tbuilder.getAndroidClassBundles(),\n\t\t\t\tbuilder.getEmbeddedResources(),\n\t\t\t\tbuilder.getContainingResource());\n\t}\n\n\t/**\n\t * @param jvmClassBundle\n\t * \t\tImmediate classes.\n\t * @param fileBundle\n\t * \t\tImmediate files.\n\t * @param versionedJvmClassBundles\n\t * \t\tVersion specific classes.\n\t * @param androidClassBundles\n\t * \t\tAndroid bundles.\n\t * @param embeddedResources\n\t * \t\tEmbedded resources <i>(like JAR in JAR)</i>\n\t * @param containingResource\n\t * \t\tParent resource <i>(If we are the JAR within a JAR)</i>.\n\t */\n\tpublic BasicWorkspaceResource(JvmClassBundle jvmClassBundle,\n\t                              FileBundle fileBundle,\n\t                              NavigableMap<Integer, VersionedJvmClassBundle> versionedJvmClassBundles,\n\t                              Map<String, AndroidClassBundle> androidClassBundles,\n\t                              Map<String, WorkspaceFileResource> embeddedResources,\n\t                              WorkspaceResource containingResource) {\n\t\tthis.jvmClassBundle = jvmClassBundle;\n\t\tthis.fileBundle = fileBundle;\n\t\tthis.versionedJvmClassBundles = versionedJvmClassBundles;\n\t\tthis.androidClassBundles = androidClassBundles;\n\t\tthis.embeddedResources = embeddedResources;\n\t\tthis.containingResource = containingResource;\n\t\tsetup();\n\t}\n\n\t/**\n\t * Calls other setup methods.\n\t */\n\tprotected void setup() {\n\t\tsetupListenerDelegation();\n\t\tlinkToEmbedded();\n\t\tmarkInitialBundleStates();\n\t}\n\n\t/**\n\t * Add listeners to all bundles contained in the resource,\n\t * which forward to the appropriate resource-level listener types.\n\t */\n\tprivate void setupListenerDelegation() {\n\t\tWorkspaceResource resource = this;\n\t\tjvmAllClassBundleStream().forEach(bundle -> delegateJvmClassBundle(resource, bundle));\n\t\tandroidClassBundleStream().forEach(bundle -> delegateAndroidClassBundle(resource, bundle));\n\t\tfileBundleStream().forEach(bundle -> delegateFileBundle(resource, bundle));\n\n\t\t// Embedded resources will notify listeners of their containing resource when they are updated.\n\t\tembeddedResources.values().forEach(embeddedResource -> {\n\t\t\tembeddedResource.jvmAllClassBundleStream().forEach(bundle -> delegateJvmClassBundle(embeddedResource, bundle));\n\t\t\tembeddedResource.androidClassBundleStream().forEach(bundle -> delegateAndroidClassBundle(embeddedResource, bundle));\n\t\t\tembeddedResource.fileBundleStream().forEach(bundle -> delegateFileBundle(embeddedResource, bundle));\n\t\t});\n\t}\n\n\t/**\n\t * Adds listeners to the given bundle to forward to the appropriate resource-level listener types.\n\t *\n\t * @param resource\n\t * \t\tResource to delegate to.\n\t * @param bundle\n\t * \t\tBundle to delegate from.\n\t */\n\tprotected void delegateJvmClassBundle(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle) {\n\t\tbundle.addBundleListener(new BundleListener<>() {\n\t\t\t@Override\n\t\t\tpublic void onNewItem(@Nonnull String key, @Nonnull JvmClassInfo cls) {\n\t\t\t\tUnchecked.checkedForEach(jvmClassListeners, listener -> listener.onNewClass(resource, bundle, cls),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when adding class\", t));\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onUpdateItem(@Nonnull String key, @Nonnull JvmClassInfo oldCls, @Nonnull JvmClassInfo newCls) {\n\t\t\t\tUnchecked.checkedForEach(jvmClassListeners, listener -> listener.onUpdateClass(resource, bundle, oldCls, newCls),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when updating class\", t));\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onRemoveItem(@Nonnull String key, @Nonnull JvmClassInfo cls) {\n\t\t\t\tUnchecked.checkedForEach(jvmClassListeners, listener -> listener.onRemoveClass(resource, bundle, cls),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when removing class\", t));\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Adds listeners to the given bundle to forward to the appropriate resource-level listener types.\n\t *\n\t * @param resource\n\t * \t\tResource to delegate to.\n\t * @param bundle\n\t * \t\tBundle to delegate from.\n\t */\n\tprotected void delegateAndroidClassBundle(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle) {\n\t\tbundle.addBundleListener(new BundleListener<>() {\n\t\t\t@Override\n\t\t\tpublic void onNewItem(@Nonnull String key, @Nonnull AndroidClassInfo cls) {\n\t\t\t\tUnchecked.checkedForEach(androidClassListeners, listener -> listener.onNewClass(resource, bundle, cls),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when adding class\", t));\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onUpdateItem(@Nonnull String key, @Nonnull AndroidClassInfo oldCls, @Nonnull AndroidClassInfo newCls) {\n\t\t\t\tUnchecked.checkedForEach(androidClassListeners, listener -> listener.onUpdateClass(resource, bundle, oldCls, newCls),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when updating class\", t));\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onRemoveItem(@Nonnull String key, @Nonnull AndroidClassInfo cls) {\n\t\t\t\tUnchecked.checkedForEach(androidClassListeners, listener -> listener.onRemoveClass(resource, bundle, cls),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when removing class\", t));\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Adds listeners to the given bundle to forward to the appropriate resource-level listener types.\n\t *\n\t * @param resource\n\t * \t\tResource to delegate to.\n\t * @param bundle\n\t * \t\tBundle to delegate from.\n\t */\n\tprotected void delegateFileBundle(@Nonnull WorkspaceResource resource, @Nonnull FileBundle bundle) {\n\t\tbundle.addBundleListener(new BundleListener<>() {\n\t\t\t@Override\n\t\t\tpublic void onNewItem(@Nonnull String key, @Nonnull FileInfo file) {\n\t\t\t\tUnchecked.checkedForEach(fileListeners, listener -> listener.onNewFile(resource, bundle, file),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when adding file\", t));\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onUpdateItem(@Nonnull String key, @Nonnull FileInfo oldFile, @Nonnull FileInfo newFile) {\n\t\t\t\tUnchecked.checkedForEach(fileListeners, listener -> listener.onUpdateFile(resource, bundle, oldFile, newFile),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when updating file\", t));\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onRemoveItem(@Nonnull String key, @Nonnull FileInfo file) {\n\t\t\t\tUnchecked.checkedForEach(fileListeners, listener -> listener.onRemoveFile(resource, bundle, file),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when removing file\", t));\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Link all the embedded resources to the current instance as their container.\n\t */\n\tprivate void linkToEmbedded() {\n\t\tembeddedResources.values().forEach(resource -> resource.setContainingResource(this));\n\t}\n\n\t/**\n\t * Mark the bundle as being in its initial state.\n\t */\n\tprivate void markInitialBundleStates() {\n\t\t// Since the bundles have been passed to our resource, we're going to assume that its fully constructed.\n\t\t// Any changes after this point will be tracked as deviations from the state at this point.\n\t\tbundleStream().forEach(bundle -> {\n\t\t\tif (bundle instanceof BasicBundle<Info> basicBundle) {\n\t\t\t\tbasicBundle.markInitialState();\n\t\t\t}\n\t\t});\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic JvmClassBundle getJvmClassBundle() {\n\t\treturn jvmClassBundle;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic NavigableMap<Integer, VersionedJvmClassBundle> getVersionedJvmClassBundles() {\n\t\treturn versionedJvmClassBundles;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, AndroidClassBundle> getAndroidClassBundles() {\n\t\tif (androidClassBundles == null) return Collections.emptyMap();\n\t\treturn androidClassBundles;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic FileBundle getFileBundle() {\n\t\treturn fileBundle;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, WorkspaceFileResource> getEmbeddedResources() {\n\t\treturn embeddedResources;\n\t}\n\n\t@Override\n\tpublic WorkspaceResource getContainingResource() {\n\t\treturn containingResource;\n\t}\n\n\t@Override\n\tpublic void setContainingResource(WorkspaceResource containingResource) {\n\t\tthis.containingResource = containingResource;\n\t}\n\n\t@Override\n\tpublic void addResourceJvmClassListener(ResourceJvmClassListener listener) {\n\t\tPrioritySortable.add(jvmClassListeners, listener);\n\t}\n\n\t@Override\n\tpublic void removeResourceJvmClassListener(ResourceJvmClassListener listener) {\n\t\tjvmClassListeners.remove(listener);\n\t}\n\n\t@Override\n\tpublic void addResourceAndroidClassListener(ResourceAndroidClassListener listener) {\n\t\tPrioritySortable.add(androidClassListeners, listener);\n\t}\n\n\t@Override\n\tpublic void removeResourceAndroidClassListener(ResourceAndroidClassListener listener) {\n\t\tandroidClassListeners.remove(listener);\n\t}\n\n\t@Override\n\tpublic void addResourceFileListener(ResourceFileListener listener) {\n\t\tPrioritySortable.add(fileListeners, listener);\n\t}\n\n\t@Override\n\tpublic void removeResourceFileListener(ResourceFileListener listener) {\n\t\tfileListeners.remove(listener);\n\t}\n\n\t/**\n\t * Called by containing {@link Workspace#close()}.\n\t */\n\t@Override\n\tpublic void close() {\n\t\t// Clear all listeners\n\t\tjvmClassListeners.clear();\n\t\tandroidClassListeners.clear();\n\t\tfileListeners.clear();\n\n\t\t// Close embedded resources\n\t\tembeddedResources.values().forEach(WorkspaceResource::close);\n\n\t\t// Close all bundles\n\t\tbundleStream().forEach(Closing::close);\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\t// NOTE: Do NOT check the containing resource (we want to prevent cycles)\n\t\tWorkspaceResource other = (WorkspaceResource) o;\n\t\tif (!jvmClassBundle.equals(other.getJvmClassBundle())) return false;\n\t\tif (!versionedJvmClassBundles.equals(other.getVersionedJvmClassBundles())) return false;\n\t\tif (!androidClassBundles.equals(other.getAndroidClassBundles())) return false;\n\t\tif (!fileBundle.equals(other.getFileBundle())) return false;\n\t\tif (!embeddedResources.equals(other.getEmbeddedResources())) return false;\n\t\treturn getProperties().equals(other.getProperties());\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\t// NOTE: Do NOT consider the containing resource (we want to prevent cycles)\n\t\tint result = jvmClassBundle.hashCode();\n\t\tresult = 31 * result + versionedJvmClassBundles.hashCode();\n\t\tresult = 31 * result + androidClassBundles.hashCode();\n\t\tresult = 31 * result + fileBundle.hashCode();\n\t\tresult = 31 * result + embeddedResources.hashCode();\n\t\tresult = 31 * result + getProperties().hashCode();\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/ResourceAndroidClassListener.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\n\n/**\n * Listener for handling updates to {@link AndroidClassInfo} values within a {@link AndroidClassBundle}\n * contained in a {@link WorkspaceResource}.\n *\n * @author Matt Coley\n */\npublic interface ResourceAndroidClassListener extends PrioritySortable {\n\t/**\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param cls\n\t * \t\tThe new class.\n\t */\n\tvoid onNewClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo cls);\n\n\t/**\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param oldCls\n\t * \t\tThe old class value.\n\t * @param newCls\n\t * \t\tThe new class value.\n\t */\n\tvoid onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle,\n\t\t\t\t\t   @Nonnull AndroidClassInfo oldCls, @Nonnull AndroidClassInfo newCls);\n\n\t/**\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param cls\n\t * \t\tThe removed class.\n\t */\n\tvoid onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo cls);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/ResourceFileListener.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\n\n/**\n * Listener for handling updates to {@link FileInfo} values within a {@link FileBundle}\n * contained in a {@link WorkspaceResource}.\n *\n * @author Matt Coley\n */\npublic interface ResourceFileListener extends PrioritySortable {\n\t/**\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param file\n\t * \t\tThe new file.\n\t */\n\tvoid onNewFile(@Nonnull WorkspaceResource resource, @Nonnull FileBundle bundle, @Nonnull FileInfo file);\n\n\t/**\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param oldFile\n\t * \t\tThe old file value.\n\t * @param newFile\n\t * \t\tThe new file value.\n\t */\n\tvoid onUpdateFile(@Nonnull WorkspaceResource resource, @Nonnull FileBundle bundle,\n\t\t\t\t\t  @Nonnull FileInfo oldFile, @Nonnull FileInfo newFile);\n\n\t/**\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param file\n\t * \t\tThe removed file.\n\t */\n\tvoid onRemoveFile(@Nonnull WorkspaceResource resource, @Nonnull FileBundle bundle, @Nonnull FileInfo file);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/ResourceJvmClassListener.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\n\n/**\n * Listener for handling updates to {@link JvmClassInfo} values within a {@link JvmClassBundle}\n * contained in a {@link WorkspaceResource}.\n *\n * @author Matt Coley\n */\npublic interface ResourceJvmClassListener extends PrioritySortable {\n\t/**\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param cls\n\t * \t\tThe new class.\n\t */\n\tvoid onNewClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls);\n\n\t/**\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param oldCls\n\t * \t\tThe old class value.\n\t * @param newCls\n\t * \t\tThe new class value.\n\t */\n\tvoid onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t\t\t\t\t   @Nonnull JvmClassInfo oldCls, @Nonnull JvmClassInfo newCls);\n\n\t/**\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param cls\n\t * \t\tThe removed class.\n\t */\n\tvoid onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls);\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/RuntimeWorkspaceResource.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.info.properties.BasicPropertyContainer;\nimport software.coley.recaf.util.ClasspathUtil;\nimport software.coley.recaf.util.IOUtil;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicFileBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.VersionedJvmClassBundle;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.NavigableMap;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutorService;\n\n/**\n * Implementation of a workspace resource sourced from runtime classes.\n * This is a special case of resource which is automatically added to all workspaces.\n * Listeners and such are not implemented and are ignored by design.\n *\n * @author Matt Coley\n */\npublic class RuntimeWorkspaceResource extends BasicPropertyContainer implements WorkspaceResource {\n\tprivate static final Logger logger = Logging.get(RuntimeWorkspaceResource.class);\n\tprivate static final Map<String, JvmClassInfo> cache = new ConcurrentHashMap<>();\n\tprivate static final Set<String> stubClasses = ConcurrentHashMap.newKeySet();\n\tprivate final JvmClassBundle classes;\n\tprivate final FileBundle files;\n\n\t/**\n\t * @return Instance of runtime workspace resource.\n\t */\n\tpublic static RuntimeWorkspaceResource getInstance() {\n\t\treturn InstanceHolder.INSTANCE;\n\t}\n\n\t/**\n\t * @param cls\n\t * \t\tClass to get bytecode of.\n\t *\n\t * @return Bytecode of class if found.\n\t */\n\t@Nullable\n\tpublic static byte[] getRuntimeClass(@Nonnull Class<?> cls) {\n\t\tString name = cls.getName().replace('.', '/');\n\t\treturn getRuntimeClass(name);\n\t}\n\n\t/**\n\t * @param className\n\t * \t\tName of class to get bytecode of.\n\t *\n\t * @return Bytecode of class if found.\n\t */\n\t@Nullable\n\tpublic static byte[] getRuntimeClass(@Nonnull String className) {\n\t\tJvmClassInfo info = getInstance().getJvmClassBundle().get(className);\n\t\tif (info == null)\n\t\t\treturn null;\n\t\treturn info.getBytecode();\n\t}\n\n\tprivate RuntimeWorkspaceResource() {\n\t\tclasses = new BasicJvmClassBundle() {\n\t\t\tprivate final byte[] loadBuffer = IOUtil.newByteBuffer();\n\n\t\t\t@Override\n\t\t\tpublic JvmClassInfo get(@Nonnull Object name) {\n\t\t\t\tString key = name.toString();\n\t\t\t\tif (key.indexOf('.') >= 0)\n\t\t\t\t\tkey = key.replace('.', '/');\n\n\t\t\t\t// Check if we have a cached value.\n\t\t\t\tJvmClassInfo present = cache.get(key);\n\t\t\t\tif (present != null)\n\t\t\t\t\treturn present;\n\n\t\t\t\t// Check if the class is a known failure case.\n\t\t\t\tif (stubClasses.contains(key))\n\t\t\t\t\treturn null;\n\n\t\t\t\t// Get the class bytes.\n\t\t\t\tbyte[] classBytes = getClassBytes(key);\n\t\t\t\tif (classBytes == null) {\n\t\t\t\t\tstubClasses.add(key);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\t// Try and parse the class and yield the result.\n\t\t\t\ttry {\n\t\t\t\t\tJvmClassInfo info = new JvmClassInfoBuilder(classBytes, ClassReader.SKIP_CODE).build();\n\t\t\t\t\tcache.put(key, info);\n\t\t\t\t\treturn info;\n\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t// There are some weird auto-generated classes in the VM like 'accessibility_ja'\n\t\t\t\t\t// which have invalid constant pools and kill our class parser. Ignore those.\n\t\t\t\t\tstubClasses.add(key);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t@Nullable\n\t\t\tprivate byte[] getClassBytes(@Nonnull String key) {\n\t\t\t\t// First try doing a system lookup (for types like 'java/lang/String')\n\t\t\t\tbyte[] value = null;\n\t\t\t\ttry (InputStream in = ClassLoader.getSystemResourceAsStream(key + \".class\")) {\n\t\t\t\t\tif (in != null)\n\t\t\t\t\t\tsynchronized (loadBuffer) {\n\t\t\t\t\t\t\tvalue = IOUtil.toByteArray(in, loadBuffer);\n\t\t\t\t\t\t}\n\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\tlogger.error(\"Failed to fetch runtime (system-resource) bytecode of class: \" + key, ex);\n\t\t\t\t}\n\n\t\t\t\t// Then try doing a classpath lookup (for types bundled into Recaf)\n\t\t\t\tif (value == null) {\n\t\t\t\t\ttry (InputStream in = RuntimeWorkspaceResource.class.getResourceAsStream(\"/\" + key + \".class\")) {\n\t\t\t\t\t\tif (in != null)\n\t\t\t\t\t\t\tsynchronized (loadBuffer) {\n\t\t\t\t\t\t\t\tvalue = IOUtil.toByteArray(in, loadBuffer);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\t\tlogger.error(\"Failed to fetch runtime (recaf-resource) bytecode of class: \" + key, ex);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic Set<String> keySet() {\n\t\t\t\treturn cache.keySet();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic Collection<JvmClassInfo> values() {\n\t\t\t\treturn cache.values();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic Set<Entry<String, JvmClassInfo>> entrySet() {\n\t\t\t\treturn cache.entrySet();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void incrementHistory(@Nonnull JvmClassInfo info) {\n\t\t\t\t// no-op\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void decrementHistory(@Nonnull String key) {\n\t\t\t\t// no-op\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic JvmClassInfo remove(@Nonnull Object key) {\n\t\t\t\treturn get(key);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic boolean containsKey(@Nonnull Object key) {\n\t\t\t\treturn get(key) != null;\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic boolean isEmpty() {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic int size() {\n\t\t\t\treturn cache.size();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void clear() {\n\t\t\t\t// no-op\n\t\t\t}\n\t\t};\n\t\tfiles = new BasicFileBundle();\n\n\t\t// Populate the system classes in the background.\n\t\ttry (ExecutorService ex = ThreadPoolFactory.newSingleThreadExecutor(\"runtime-class-population\")) {\n\t\t\tCompletableFuture.runAsync(() -> {\n\t\t\t\tfor (String name : ClasspathUtil.getSystemClassSet())\n\t\t\t\t\tclasses.get(name);\n\t\t\t}, ex);\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic JvmClassBundle getJvmClassBundle() {\n\t\treturn classes;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic FileBundle getFileBundle() {\n\t\treturn files;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic NavigableMap<Integer, VersionedJvmClassBundle> getVersionedJvmClassBundles() {\n\t\treturn Collections.emptyNavigableMap();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, AndroidClassBundle> getAndroidClassBundles() {\n\t\treturn Collections.emptyMap();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Map<String, WorkspaceFileResource> getEmbeddedResources() {\n\t\treturn Collections.emptyMap();\n\t}\n\n\t@Override\n\tpublic WorkspaceResource getContainingResource() {\n\t\t// Not applicable\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void setContainingResource(WorkspaceResource resource) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void addResourceJvmClassListener(ResourceJvmClassListener listener) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void removeResourceJvmClassListener(ResourceJvmClassListener listener) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void addResourceAndroidClassListener(ResourceAndroidClassListener listener) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void removeResourceAndroidClassListener(ResourceAndroidClassListener listener) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void addResourceFileListener(ResourceFileListener listener) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void removeResourceFileListener(ResourceFileListener listener) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void close() {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic boolean isInternal() {\n\t\treturn true;\n\t}\n\n\t/** Statically initialized inner class used as a lightweight alternative to double-sync block. */\n\tprivate static final class InstanceHolder {\n\t\tprivate static final RuntimeWorkspaceResource INSTANCE = new RuntimeWorkspaceResource();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/WorkspaceDirectoryResource.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.nio.file.Path;\n\n/**\n * A resource sourced from a directory.\n *\n * @author Matt Coley\n */\npublic interface WorkspaceDirectoryResource extends WorkspaceResource {\n\t/**\n\t * @return Path of the directory the contents of this resource originate from.\n\t */\n\t@Nonnull\n\tPath getDirectoryPath();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/WorkspaceDirectoryResourceBuilder.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\n\nimport java.nio.file.Path;\n\n/**\n * Builder for {@link WorkspaceDirectoryResource}.\n *\n * @author Matt Coley\n */\npublic class WorkspaceDirectoryResourceBuilder extends WorkspaceResourceBuilder {\n\tprivate Path directoryPath;\n\n\t/**\n\t * Empty builder.\n\t */\n\tpublic WorkspaceDirectoryResourceBuilder() {\n\t\t// default\n\t}\n\n\t/**\n\t * Builder with required inputs.\n\t *\n\t * @param classes\n\t * \t\tPrimary classes.\n\t * @param files\n\t * \t\tPrimary files.\n\t */\n\tpublic WorkspaceDirectoryResourceBuilder(JvmClassBundle classes, FileBundle files) {\n\t\tsuper(classes, files);\n\t}\n\n\t/**\n\t * @param other\n\t * \t\tBuilder to copy from.\n\t */\n\tpublic WorkspaceDirectoryResourceBuilder(WorkspaceResourceBuilder other) {\n\t\tsuper(other);\n\t\tif (other instanceof WorkspaceDirectoryResourceBuilder otherFileBuilder) {\n\t\t\twithDirectoryPath(otherFileBuilder.getDirectoryPath());\n\t\t}\n\t}\n\n\t@Override\n\tpublic WorkspaceDirectoryResourceBuilder withDirectoryPath(Path directoryPath) {\n\t\tthis.directoryPath = directoryPath;\n\t\treturn this;\n\t}\n\n\tpublic Path getDirectoryPath() {\n\t\treturn directoryPath;\n\t}\n\n\t@Override\n\tpublic WorkspaceDirectoryResource build() {\n\t\treturn new BasicWorkspaceDirectoryResource(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/WorkspaceFileResource.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.FileInfo;\n\n/**\n * A resource sourced from a file.\n *\n * @author Matt Coley\n */\npublic interface WorkspaceFileResource extends WorkspaceResource {\n\t/**\n\t * @return Information about the file loaded from.\n\t */\n\t@Nonnull\n\tFileInfo getFileInfo();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/WorkspaceFileResourceBuilder.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.VersionedJvmClassBundle;\n\nimport java.util.Map;\nimport java.util.NavigableMap;\n\n/**\n * Builder for {@link WorkspaceFileResource}.\n *\n * @author Matt Coley\n */\npublic class WorkspaceFileResourceBuilder extends WorkspaceResourceBuilder {\n\tprivate FileInfo fileInfo;\n\n\t/**\n\t * Empty builder.\n\t */\n\tpublic WorkspaceFileResourceBuilder() {\n\t\t// default\n\t}\n\n\t/**\n\t * Builder with required inputs.\n\t *\n\t * @param classes\n\t * \t\tPrimary classes.\n\t * @param files\n\t * \t\tPrimary files.\n\t */\n\tpublic WorkspaceFileResourceBuilder(JvmClassBundle classes, FileBundle files) {\n\t\tsuper(classes, files);\n\t}\n\n\t/**\n\t * @param other\n\t * \t\tBuilder to copy from.\n\t */\n\tpublic WorkspaceFileResourceBuilder(WorkspaceResourceBuilder other) {\n\t\tsuper(other);\n\t\tif (other instanceof WorkspaceFileResourceBuilder otherFileBuilder) {\n\t\t\twithFileInfo(otherFileBuilder.getFileInfo());\n\t\t}\n\t}\n\n\t@Override\n\tpublic WorkspaceFileResourceBuilder withFileInfo(FileInfo fileInfo) {\n\t\tthis.fileInfo = fileInfo;\n\t\treturn this;\n\t}\n\n\tpublic FileInfo getFileInfo() {\n\t\treturn fileInfo;\n\t}\n\n\t@Override\n\tpublic WorkspaceFileResource build() {\n\t\treturn new BasicWorkspaceFileResource(this);\n\t}\n\n\t@Override\n\tpublic WorkspaceFileResourceBuilder withJvmClassBundle(JvmClassBundle primaryJvmClassBundle) {\n\t\treturn (WorkspaceFileResourceBuilder) super.withJvmClassBundle(primaryJvmClassBundle);\n\t}\n\n\t@Override\n\tpublic WorkspaceFileResourceBuilder withVersionedJvmClassBundles(NavigableMap<Integer, VersionedJvmClassBundle> versionedJvmClassBundles) {\n\t\treturn (WorkspaceFileResourceBuilder) super.withVersionedJvmClassBundles(versionedJvmClassBundles);\n\t}\n\n\t@Override\n\tpublic WorkspaceFileResourceBuilder withAndroidClassBundles(Map<String, AndroidClassBundle> androidClassBundles) {\n\t\treturn (WorkspaceFileResourceBuilder) super.withAndroidClassBundles(androidClassBundles);\n\t}\n\n\t@Override\n\tpublic WorkspaceFileResourceBuilder withFileBundle(FileBundle primaryFileBundle) {\n\t\treturn (WorkspaceFileResourceBuilder) super.withFileBundle(primaryFileBundle);\n\t}\n\n\t@Override\n\tpublic WorkspaceFileResourceBuilder withEmbeddedResources(Map<String, WorkspaceFileResource> embeddedResources) {\n\t\treturn (WorkspaceFileResourceBuilder) super.withEmbeddedResources(embeddedResources);\n\t}\n\n\t@Override\n\tpublic WorkspaceFileResourceBuilder withContainingResource(WorkspaceResource containingResource) {\n\t\treturn (WorkspaceFileResourceBuilder) super.withContainingResource(containingResource);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/WorkspaceRemoteVmResource.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport com.sun.tools.attach.VirtualMachine;\nimport jakarta.annotation.Nonnull;\nimport software.coley.instrument.data.ClassLoaderInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.properties.builtin.RemoteClassloaderProperty;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\n\nimport java.io.IOException;\nimport java.util.Map;\n\n/**\n * A resource sourced from a remote {@link VirtualMachine}.\n *\n * @author Matt Coley\n * @see RemoteClassloaderProperty Property on {@link JvmClassInfo} instances, indcating which {@link ClassLoaderInfo}\n * the info is associated with in {@link #getRemoteLoaders()} and {@link #getJvmClassloaderBundles()}.\n */\npublic interface WorkspaceRemoteVmResource extends WorkspaceResource {\n\t/**\n\t * Connects to the remote VM.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the connection fails.\n\t */\n\tvoid connect() throws IOException;\n\n\t/**\n\t * @return Virtual machine of the remote process attached to.\n\t */\n\t@Nonnull\n\tVirtualMachine getVirtualMachine();\n\n\t/**\n\t * @return Map of remote classloaders.\n\t */\n\t@Nonnull\n\tMap<Integer, ClassLoaderInfo> getRemoteLoaders();\n\n\t/**\n\t * @return Map of {@code ClassLoader} id to the classes defined by the loader.\n\t *\n\t * @see #getRemoteLoaders() Classloader values, keys of which are {@link ClassLoaderInfo#getId()}.\n\t */\n\t@Nonnull\n\tMap<Integer, JvmClassBundle> getJvmClassloaderBundles();\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/WorkspaceResource.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.behavior.Closing;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.properties.PropertyContainer;\nimport software.coley.recaf.util.Streams;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.VersionedJvmClassBundle;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NavigableMap;\nimport java.util.stream.Stream;\n\nimport static java.util.stream.Stream.concat;\nimport static java.util.stream.Stream.of;\n\n/**\n * Component of a {@link Workspace}. Contain classes and files.\n *\n * @author Matt Coley\n */\npublic interface WorkspaceResource extends PropertyContainer, Closing {\n\t/**\n\t * Contains the classes within the resource.\n\t * <br>\n\t * For JAR files, copies of these classes can also be provided for specific JVM versions, which are available\n\t * via {@link #getVersionedJvmClassBundles()}.\n\t * <br>\n\t * For Android files (APK) this will always be empty.\n\t * Android APK's classes reside in embedded dex files, which are accessible via {@link #getAndroidClassBundles()}.\n\t *\n\t * @return Immediate classes within the resource.\n\t */\n\t@Nonnull\n\tJvmClassBundle getJvmClassBundle();\n\n\t/**\n\t * Contains additional class bundles for versioned classes.\n\t * These exist in multi-release JAR files <i>(New feature to Java 9+)</i>.\n\t *\n\t * @return Map of versions, to JVM class bundles.\n\t */\n\t@Nonnull\n\tNavigableMap<Integer, VersionedJvmClassBundle> getVersionedJvmClassBundles();\n\n\t/**\n\t * Contains Android class bundles.\n\t * <br>\n\t * Android bundles one or more DEX files into an APK. Each DEX becomes a bundle with its classes\n\t * accessible here.\n\t *\n\t * @return Map of Android class bundles.\n\t */\n\t@Nonnull\n\tMap<String, AndroidClassBundle> getAndroidClassBundles();\n\n\t// TODO: Specific Android resource bundle for 'resources.arsc'?\n\t//       - Or make custom FileInfo?\n\n\t/**\n\t * @return Immediate files within the resource.\n\t */\n\t@Nonnull\n\tFileBundle getFileBundle();\n\n\t/**\n\t * Suppose you have a Spring boot JAR that has a structure like the following:\n\t * <ul>\n\t *     <li>WEB-INF/lib-provided/BundledLibrary.jar</li>\n\t *     <li>com.example.Launcher.class</li>\n\t * </ul>\n\t * <p>\n\t * If you open the boot JAR in Recaf you would probably want to be able to inspect the contents of\n\t * {@code BundledLibrary.jar}. So for that sort of case we extract those containers into their own resources.\n\t *\n\t * @return Map of container files <i>(JAR/ZIP/WAR/etc)</i> represented as their own workspace resources.\n\t */\n\t@Nonnull\n\tMap<String, WorkspaceFileResource> getEmbeddedResources();\n\n\t/**\n\t * @return Containing resource of this one if this represents a <i>\"JAR in JAR\"</i> kind of situation.\n\t * May be {@code null} for a root-resource.\n\t */\n\t@Nullable\n\tWorkspaceResource getContainingResource();\n\n\t/**\n\t * Searches within this resource and all embedded resources for containment of the given bundle.\n\t * To reconstruct the path to the root from the returned result here use {@link #getContainingResource()}.\n\t *\n\t * @param bundle\n\t * \t\tBundle that belongs to this resource, or a {@link #getEmbeddedResources() embedded resource}.\n\t *\n\t * @return The containing resource.\n\t * <ul>\n\t *     <li>Can be the current resource.</li>\n\t *     <li>Can be any level of nested embedded resource.</li>\n\t *     <li>Can be {@code null} if no embedded resource contains the given bundle.</li>\n\t * </ul>\n\t */\n\t@Nullable\n\tdefault WorkspaceResource resolveBundleContainer(@Nonnull Bundle<?> bundle) {\n\t\t// Check for containment in this resource\n\t\tif (bundleStream().anyMatch(b -> b == bundle)) return this;\n\n\t\t// Check for containment in any embedded resource\n\t\tfor (WorkspaceFileResource embedded : getEmbeddedResources().values()) {\n\t\t\tWorkspaceResource resource = embedded.resolveBundleContainer(bundle);\n\t\t\tif (resource != null) return resource;\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param resource\n\t * \t\tContaining resource to assign.\n\t */\n\tvoid setContainingResource(WorkspaceResource resource);\n\n\t/**\n\t * @return {@code true} when there is another resource that contains this one.\n\t */\n\tdefault boolean isEmbeddedResource() {\n\t\treturn getContainingResource() != null;\n\t}\n\n\t/**\n\t * @return Iterable of all immediate JVM class bundles in the resource.\n\t */\n\t@Nonnull\n\tdefault Iterable<JvmClassBundle> jvmClassBundles() {\n\t\treturn List.of(getJvmClassBundle());\n\t}\n\n\t/**\n\t * @return Stream of all immediate JVM class bundles in the resource.\n\t */\n\t@Nonnull\n\tdefault Stream<JvmClassBundle> jvmClassBundleStream() {\n\t\treturn of(getJvmClassBundle());\n\t}\n\n\t/**\n\t * @return Stream of all JVM class bundles in the resource, and in any embedded resources.\n\t */\n\t@Nonnull\n\tdefault Stream<JvmClassBundle> jvmClassBundleStreamRecursive() {\n\t\treturn concat(jvmClassBundleStream(), getEmbeddedResources().values().stream()\n\t\t\t\t.flatMap(WorkspaceResource::jvmClassBundleStreamRecursive));\n\t}\n\n\t/**\n\t * @return Stream of all versioned JVM class bundles in the resource.\n\t */\n\t@Nonnull\n\tdefault Stream<VersionedJvmClassBundle> versionedJvmClassBundleStream() {\n\t\treturn getVersionedJvmClassBundles().values().stream();\n\t}\n\n\t/**\n\t * @return Stream of all versioned JVM class bundles in the resource, and in any embedded resources.\n\t */\n\t@Nonnull\n\tdefault Stream<VersionedJvmClassBundle> versionedJvmClassBundleStreamRecursive() {\n\t\treturn concat(versionedJvmClassBundleStream(), getEmbeddedResources().values().stream()\n\t\t\t\t.flatMap(WorkspaceResource::versionedJvmClassBundleStreamRecursive));\n\t}\n\n\t/**\n\t * Use this if you don't want to manually concat the jvm and versioned-jvm bundle streams.\n\t *\n\t * @return Stream of all immediate and versioned JVM class bundles in the resource, and in any embedded resources.\n\t */\n\t@Nonnull\n\tdefault Stream<JvmClassBundle> jvmAllClassBundleStream() {\n\t\treturn concat(jvmClassBundleStream(), versionedJvmClassBundleStream());\n\t}\n\n\t/**\n\t * Use this if you don't want to manually concat the jvm and versioned-jvm bundle streams.\n\t *\n\t * @return Stream of all immediate and versioned JVM class bundles in the resource, and in any embedded resources.\n\t */\n\t@Nonnull\n\tdefault Stream<JvmClassBundle> jvmAllClassBundleStreamRecursive() {\n\t\treturn concat(jvmClassBundleStreamRecursive(), versionedJvmClassBundleStreamRecursive());\n\t}\n\n\t/**\n\t * @return Stream of all immediate Android class bundles in the resource.\n\t */\n\t@Nonnull\n\tdefault Stream<AndroidClassBundle> androidClassBundleStream() {\n\t\treturn getAndroidClassBundles().values().stream();\n\t}\n\n\t/**\n\t * @return Stream of all Android class bundles in the resource, and in any embedded resources.\n\t */\n\t@Nonnull\n\tdefault Stream<AndroidClassBundle> androidClassBundleStreamRecursive() {\n\t\treturn concat(androidClassBundleStream(), getEmbeddedResources().values().stream()\n\t\t\t\t.flatMap(WorkspaceResource::androidClassBundleStreamRecursive));\n\t}\n\n\t/**\n\t * @return Stream of all immediate class bundles in the resource.\n\t */\n\t@Nonnull\n\tdefault Stream<ClassBundle<? extends ClassInfo>> classBundleStream() {\n\t\treturn concat(jvmClassBundleStream(), concat(versionedJvmClassBundleStream(), androidClassBundleStream()));\n\t}\n\n\t/**\n\t * @return Stream of all class bundles in the resource, and in any embedded resources.\n\t */\n\t@Nonnull\n\tdefault Stream<ClassBundle<? extends ClassInfo>> classBundleStreamRecursive() {\n\t\treturn concat(classBundleStream(), getEmbeddedResources().values().stream()\n\t\t\t\t.flatMap(WorkspaceResource::classBundleStreamRecursive));\n\t}\n\n\t/**\n\t * @return Stream of all immediate file bundles in the resource.\n\t */\n\t@Nonnull\n\tdefault Stream<FileBundle> fileBundleStream() {\n\t\treturn of(getFileBundle());\n\t}\n\n\t/**\n\t * @return Stream of all file bundles in the resource, and in any embedded resources.\n\t */\n\t@Nonnull\n\tdefault Stream<FileBundle> fileBundleStreamRecursive() {\n\t\treturn concat(fileBundleStream(), getEmbeddedResources().values().stream()\n\t\t\t\t.flatMap(WorkspaceResource::fileBundleStreamRecursive));\n\t}\n\n\t/**\n\t * @return Stream of all immediate bundles in the resource.\n\t */\n\t@Nonnull\n\t@SuppressWarnings(\"unchecked\")\n\tdefault <I extends Info> Stream<Bundle<I>> bundleStream() {\n\t\t// Cast to object is a hack to allow generic usage of this method with <Info>.\n\t\t// Using <? extends Info> prevents <Info> usage.\n\t\t//  noinspection RedundantCast\n\t\treturn (Stream<Bundle<I>>) (Object)\n\t\t\t\tStreams.of(\n\t\t\t\t\t\tjvmClassBundleStream(),\n\t\t\t\t\t\tversionedJvmClassBundleStream(),\n\t\t\t\t\t\tandroidClassBundleStream(),\n\t\t\t\t\t\tfileBundleStream()\n\t\t\t\t);\n\t}\n\n\t/**\n\t * @return Stream of all bundles in the resource, and in any embedded resources.\n\t */\n\t@Nonnull\n\t@SuppressWarnings(\"unchecked\")\n\tdefault <I extends Info> Stream<Bundle<I>> bundleStreamRecursive() {\n\t\t// Cast to object is a hack to allow generic usage of this method with <Info>.\n\t\t// Using <? extends Info> prevents <Info> usage.\n\t\t//  noinspection RedundantCast\n\t\treturn (Stream<Bundle<I>>) (Object)\n\t\t\t\tStreams.of(\n\t\t\t\t\t\tjvmClassBundleStreamRecursive(),\n\t\t\t\t\t\tversionedJvmClassBundleStreamRecursive(),\n\t\t\t\t\t\tandroidClassBundleStreamRecursive(),\n\t\t\t\t\t\tfileBundleStreamRecursive()\n\t\t\t\t);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tGeneric object to add as any supported listener type.\n\t */\n\tdefault void addListener(Object listener) {\n\t\tif (listener instanceof ResourceJvmClassListener)\n\t\t\taddResourceJvmClassListener((ResourceJvmClassListener) listener);\n\t\tif (listener instanceof ResourceAndroidClassListener)\n\t\t\taddResourceAndroidClassListener((ResourceAndroidClassListener) listener);\n\t\tif (listener instanceof ResourceFileListener)\n\t\t\taddResourceFileListener((ResourceFileListener) listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tGeneric object to remove as any supported listener type.\n\t */\n\tdefault void removeListener(Object listener) {\n\t\tif (listener instanceof ResourceJvmClassListener)\n\t\t\tremoveResourceJvmClassListener((ResourceJvmClassListener) listener);\n\t\tif (listener instanceof ResourceAndroidClassListener)\n\t\t\tremoveResourceAndroidClassListener((ResourceAndroidClassListener) listener);\n\t\tif (listener instanceof ResourceFileListener)\n\t\t\tremoveResourceFileListener((ResourceFileListener) listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tvoid addResourceJvmClassListener(ResourceJvmClassListener listener);\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tvoid removeResourceJvmClassListener(ResourceJvmClassListener listener);\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tvoid addResourceAndroidClassListener(ResourceAndroidClassListener listener);\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tvoid removeResourceAndroidClassListener(ResourceAndroidClassListener listener);\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tvoid addResourceFileListener(ResourceFileListener listener);\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tvoid removeResourceFileListener(ResourceFileListener listener);\n\n\t/**\n\t * @return {@code true} when this resource represents an internally managed resource within a {@link Workspace}.\n\t * These resources are not explicitly created by users and thus should not be visible to them. However, they will\n\t * supplement workspace capabilities as any other supporting resource.\n\t */\n\tdefault boolean isInternal() {\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/java/software/coley/recaf/workspace/model/resource/WorkspaceResourceBuilder.java",
    "content": "package software.coley.recaf.workspace.model.resource;\n\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicFileBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.VersionedJvmClassBundle;\n\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.NavigableMap;\nimport java.util.TreeMap;\n\n/**\n * Builder for {@link WorkspaceResource} models.\n *\n * @author Matt Coley\n * @see WorkspaceFileResourceBuilder\n * @see WorkspaceDirectoryResourceBuilder\n * @see #withFileInfo(FileInfo)  Wither to convert to a file resource builder\n * @see #withDirectoryPath(Path) Wither to convert to a directory resource builder\n */\npublic class WorkspaceResourceBuilder {\n\tprivate JvmClassBundle jvmClassBundle = new BasicJvmClassBundle();\n\tprivate NavigableMap<Integer, VersionedJvmClassBundle> versionedJvmClassBundles = new TreeMap<>();\n\tprivate Map<String, AndroidClassBundle> androidClassBundles = Collections.emptyMap();\n\tprivate FileBundle fileBundle = new BasicFileBundle();\n\tprivate Map<String, WorkspaceFileResource> embeddedResources = Collections.emptyMap();\n\tprivate WorkspaceResource containingResource;\n\n\t/**\n\t * Empty builder.\n\t */\n\tpublic WorkspaceResourceBuilder() {\n\t\t// default\n\t}\n\n\t/**\n\t * Builder with required inputs.\n\t *\n\t * @param classes\n\t * \t\tPrimary classes.\n\t * @param files\n\t * \t\tPrimary files.\n\t */\n\tpublic WorkspaceResourceBuilder(JvmClassBundle classes, FileBundle files) {\n\t\twithJvmClassBundle(classes);\n\t\twithFileBundle(files);\n\t}\n\n\tprotected WorkspaceResourceBuilder(WorkspaceResourceBuilder other) {\n\t\twithJvmClassBundle(other.getJvmClassBundle());\n\t\twithAndroidClassBundles(other.getAndroidClassBundles());\n\t\twithVersionedJvmClassBundles(other.getVersionedJvmClassBundles());\n\t\twithFileBundle(other.getFileBundle());\n\t\twithEmbeddedResources(other.getEmbeddedResources());\n\t\twithContainingResource(other.getContainingResource());\n\t}\n\n\tpublic WorkspaceResourceBuilder withJvmClassBundle(JvmClassBundle primaryJvmClassBundle) {\n\t\tthis.jvmClassBundle = primaryJvmClassBundle;\n\t\treturn this;\n\t}\n\n\tpublic WorkspaceResourceBuilder withVersionedJvmClassBundles(NavigableMap<Integer, VersionedJvmClassBundle> versionedJvmClassBundles) {\n\t\tthis.versionedJvmClassBundles = versionedJvmClassBundles;\n\t\treturn this;\n\t}\n\n\tpublic WorkspaceResourceBuilder withAndroidClassBundles(Map<String, AndroidClassBundle> androidClassBundles) {\n\t\tthis.androidClassBundles = androidClassBundles;\n\t\treturn this;\n\t}\n\n\tpublic WorkspaceResourceBuilder withFileBundle(FileBundle primaryFileBundle) {\n\t\tthis.fileBundle = primaryFileBundle;\n\t\treturn this;\n\t}\n\n\tpublic WorkspaceResourceBuilder withEmbeddedResources(Map<String, WorkspaceFileResource> embeddedResources) {\n\t\tthis.embeddedResources = embeddedResources;\n\t\treturn this;\n\t}\n\n\tpublic WorkspaceResourceBuilder withContainingResource(WorkspaceResource containingResource) {\n\t\tthis.containingResource = containingResource;\n\t\treturn this;\n\t}\n\n\tpublic WorkspaceFileResourceBuilder withFileInfo(FileInfo fileInfo) {\n\t\treturn new WorkspaceFileResourceBuilder(this)\n\t\t\t\t.withFileInfo(fileInfo);\n\t}\n\n\tpublic WorkspaceDirectoryResourceBuilder withDirectoryPath(Path directoryPath) {\n\t\treturn new WorkspaceDirectoryResourceBuilder(this)\n\t\t\t\t.withDirectoryPath(directoryPath);\n\t}\n\n\tpublic JvmClassBundle getJvmClassBundle() {\n\t\treturn jvmClassBundle;\n\t}\n\n\tpublic NavigableMap<Integer, VersionedJvmClassBundle> getVersionedJvmClassBundles() {\n\t\treturn versionedJvmClassBundles;\n\t}\n\n\tpublic Map<String, AndroidClassBundle> getAndroidClassBundles() {\n\t\treturn androidClassBundles;\n\t}\n\n\tpublic FileBundle getFileBundle() {\n\t\treturn fileBundle;\n\t}\n\n\tpublic Map<String, WorkspaceFileResource> getEmbeddedResources() {\n\t\treturn embeddedResources;\n\t}\n\n\tpublic WorkspaceResource getContainingResource() {\n\t\treturn containingResource;\n\t}\n\n\t/**\n\t * @return New resource from builder.\n\t */\n\tpublic WorkspaceResource build() {\n\t\treturn new BasicWorkspaceResource(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/main/resources/android/attrs.json",
    "content": "{\"attrs_manifest\":{\"eat-comment\":\"\",\"attr\":[{\"name\":\"theme\",\"format\":\"reference\"},{\"name\":\"label\",\"format\":\"reference|string\"},{\"name\":\"icon\",\"format\":\"reference\"},{\"name\":\"roundIcon\",\"format\":\"reference\"},{\"name\":\"banner\",\"format\":\"reference\"},{\"name\":\"logo\",\"format\":\"reference\"},{\"name\":\"manageSpaceActivity\",\"format\":\"string\"},{\"name\":\"allowClearUserData\",\"format\":\"boolean\"},{\"name\":\"testOnly\",\"format\":\"boolean\"},{\"name\":\"name\",\"format\":\"string\"},{\"name\":\"permission\",\"format\":\"string\"},{\"name\":\"readPermission\",\"format\":\"string\"},{\"name\":\"writePermission\",\"format\":\"string\"},{\"name\":\"grantUriPermissions\",\"format\":\"boolean\"},{\"name\":\"forceUriPermissions\",\"format\":\"boolean\"},{\"name\":\"protectionLevel\",\"flag\":[{\"name\":\"normal\",\"value\":\"0\"},{\"name\":\"dangerous\",\"value\":\"1\"},{\"name\":\"signature\",\"value\":\"2\"},{\"name\":\"signatureOrSystem\",\"value\":\"3\"},{\"name\":\"internal\",\"value\":\"4\"},{\"name\":\"privileged\",\"value\":\"0x10\"},{\"name\":\"system\",\"value\":\"0x10\"},{\"name\":\"development\",\"value\":\"0x20\"},{\"name\":\"appop\",\"value\":\"0x40\"},{\"name\":\"pre23\",\"value\":\"0x80\"},{\"name\":\"installer\",\"value\":\"0x100\"},{\"name\":\"verifier\",\"value\":\"0x200\"},{\"name\":\"preinstalled\",\"value\":\"0x400\"},{\"name\":\"setup\",\"value\":\"0x800\"},{\"name\":\"instant\",\"value\":\"0x1000\"},{\"name\":\"runtime\",\"value\":\"0x2000\"},{\"name\":\"oem\",\"value\":\"0x4000\"},{\"name\":\"vendorPrivileged\",\"value\":\"0x8000\"},{\"name\":\"textClassifier\",\"value\":\"0x10000\"},{\"name\":\"documenter\",\"value\":\"0x40000\"},{\"name\":\"configurator\",\"value\":\"0x80000\"},{\"name\":\"incidentReportApprover\",\"value\":\"0x100000\"},{\"name\":\"appPredictor\",\"value\":\"0x200000\"},{\"name\":\"companion\",\"value\":\"0x800000\"},{\"name\":\"retailDemo\",\"value\":\"0x1000000\"},{\"name\":\"recents\",\"value\":\"0x2000000\"},{\"name\":\"role\",\"value\":\"0x4000000\"},{\"name\":\"knownSigner\",\"value\":\"0x8000000\"}]},{\"name\":\"permissionGroupFlags\",\"flag\":{\"name\":\"personalInfo\",\"value\":\"0x0001\"}},{\"name\":\"permissionFlags\",\"flag\":[{\"name\":\"costsMoney\",\"value\":\"0x1\"},{\"name\":\"removed\",\"value\":\"0x2\"},{\"name\":\"hardRestricted\",\"value\":\"0x4\"},{\"name\":\"softRestricted\",\"value\":\"0x8\"},{\"name\":\"immutablyRestricted\",\"value\":\"0x10\"},{\"name\":\"installerExemptIgnored\",\"value\":\"0x20\"}]},{\"name\":\"permissionGroup\",\"format\":\"string\"},{\"name\":\"knownCerts\",\"format\":\"reference|string\"},{\"name\":\"sharedUserId\",\"format\":\"string\"},{\"name\":\"sharedUserLabel\",\"format\":\"reference\"},{\"name\":\"versionCode\",\"format\":\"integer\"},{\"name\":\"versionCodeMajor\",\"format\":\"integer\"},{\"name\":\"revisionCode\",\"format\":\"integer\"},{\"name\":\"versionName\",\"format\":\"string\"},{\"name\":\"persistent\",\"format\":\"boolean\"},{\"name\":\"persistentWhenFeatureAvailable\",\"format\":\"string\"},{\"name\":\"requiredForAllUsers\",\"format\":\"boolean\"},{\"name\":\"debuggable\",\"format\":\"boolean\"},{\"name\":\"vmSafeMode\",\"format\":\"boolean\"},{\"name\":\"hardwareAccelerated\",\"format\":\"boolean\"},{\"name\":\"exported\",\"format\":\"boolean\"},{\"name\":\"isGame\",\"format\":\"boolean\"},{\"name\":\"singleUser\",\"format\":\"boolean\"},{\"name\":\"process\",\"format\":\"string\"},{\"name\":\"taskAffinity\",\"format\":\"string\"},{\"name\":\"allowTaskReparenting\",\"format\":\"boolean\"},{\"name\":\"usesCleartextTraffic\",\"format\":\"boolean\"},{\"name\":\"multiArch\",\"format\":\"boolean\"},{\"name\":\"use32bitAbi\"},{\"name\":\"multiprocess\",\"format\":\"boolean\"},{\"name\":\"finishOnTaskLaunch\",\"format\":\"boolean\"},{\"name\":\"finishOnCloseSystemDialogs\",\"format\":\"boolean\"},{\"name\":\"clearTaskOnLaunch\",\"format\":\"boolean\"},{\"name\":\"noHistory\",\"format\":\"boolean\"},{\"name\":\"alwaysRetainTaskState\",\"format\":\"boolean\"},{\"name\":\"stateNotNeeded\",\"format\":\"boolean\"},{\"name\":\"excludeFromRecents\",\"format\":\"boolean\"},{\"name\":\"showOnLockScreen\",\"format\":\"boolean\"},{\"name\":\"showForAllUsers\",\"format\":\"boolean\"},{\"name\":\"showWhenLocked\",\"format\":\"boolean\"},{\"name\":\"turnScreenOn\",\"format\":\"boolean\"},{\"name\":\"authorities\",\"format\":\"string\"},{\"name\":\"syncable\",\"format\":\"boolean\"},{\"name\":\"immersive\",\"format\":\"boolean\"},{\"name\":\"enableVrMode\",\"format\":\"string\"},{\"name\":\"rotationAnimation\",\"flag\":[{\"name\":\"rotate\",\"value\":\"0\"},{\"name\":\"crossfade\",\"value\":\"1\"},{\"name\":\"jumpcut\",\"value\":\"2\"},{\"name\":\"seamless\",\"value\":\"3\"}]},{\"name\":\"initOrder\",\"format\":\"integer\"},{\"name\":\"priority\",\"format\":\"integer\"},{\"name\":\"directBootAware\",\"format\":\"boolean\"},{\"name\":\"launchMode\",\"enum\":[{\"name\":\"standard\",\"value\":\"0\"},{\"name\":\"singleTop\",\"value\":\"1\"},{\"name\":\"singleTask\",\"value\":\"2\"},{\"name\":\"singleInstance\",\"value\":\"3\"},{\"name\":\"singleInstancePerTask\",\"value\":\"4\"}]},{\"name\":\"screenOrientation\",\"enum\":[{\"name\":\"unspecified\",\"value\":\"-1\"},{\"name\":\"landscape\",\"value\":\"0\"},{\"name\":\"portrait\",\"value\":\"1\"},{\"name\":\"user\",\"value\":\"2\"},{\"name\":\"behind\",\"value\":\"3\"},{\"name\":\"sensor\",\"value\":\"4\"},{\"name\":\"nosensor\",\"value\":\"5\"},{\"name\":\"sensorLandscape\",\"value\":\"6\"},{\"name\":\"sensorPortrait\",\"value\":\"7\"},{\"name\":\"reverseLandscape\",\"value\":\"8\"},{\"name\":\"reversePortrait\",\"value\":\"9\"},{\"name\":\"fullSensor\",\"value\":\"10\"},{\"name\":\"userLandscape\",\"value\":\"11\"},{\"name\":\"userPortrait\",\"value\":\"12\"},{\"name\":\"fullUser\",\"value\":\"13\"},{\"name\":\"locked\",\"value\":\"14\"}]},{\"name\":\"recreateOnConfigChanges\",\"flag\":[{\"name\":\"mcc\",\"value\":\"0x0001\"},{\"name\":\"mnc\",\"value\":\"0x0002\"}]},{\"name\":\"configChanges\",\"flag\":[{\"name\":\"mcc\",\"value\":\"0x0001\"},{\"name\":\"mnc\",\"value\":\"0x0002\"},{\"name\":\"locale\",\"value\":\"0x0004\"},{\"name\":\"touchscreen\",\"value\":\"0x0008\"},{\"name\":\"keyboard\",\"value\":\"0x0010\"},{\"name\":\"keyboardHidden\",\"value\":\"0x0020\"},{\"name\":\"navigation\",\"value\":\"0x0040\"},{\"name\":\"orientation\",\"value\":\"0x0080\"},{\"name\":\"screenLayout\",\"value\":\"0x0100\"},{\"name\":\"uiMode\",\"value\":\"0x0200\"},{\"name\":\"screenSize\",\"value\":\"0x0400\"},{\"name\":\"smallestScreenSize\",\"value\":\"0x0800\"},{\"name\":\"density\",\"value\":\"0x1000\"},{\"name\":\"layoutDirection\",\"value\":\"0x2000\"},{\"name\":\"colorMode\",\"value\":\"0x4000\"},{\"name\":\"fontScale\",\"value\":\"0x40000000\"},{\"name\":\"fontWeightAdjustment\",\"value\":\"0x10000000\"}]},{\"name\":\"allowEmbedded\",\"format\":\"boolean\"},{\"name\":\"inheritShowWhenLocked\",\"format\":\"boolean\"},{\"name\":\"description\",\"format\":\"reference\"},{\"name\":\"targetPackage\",\"format\":\"string\"},{\"name\":\"targetProcesses\",\"format\":\"string\"},{\"name\":\"handleProfiling\",\"format\":\"boolean\"},{\"name\":\"functionalTest\",\"format\":\"boolean\"},{\"name\":\"reqTouchScreen\",\"enum\":[{\"name\":\"undefined\",\"value\":\"0\"},{\"name\":\"notouch\",\"value\":\"1\"},{\"name\":\"stylus\",\"value\":\"2\"},{\"name\":\"finger\",\"value\":\"3\"}]},{\"name\":\"reqKeyboardType\",\"enum\":[{\"name\":\"undefined\",\"value\":\"0\"},{\"name\":\"nokeys\",\"value\":\"1\"},{\"name\":\"qwerty\",\"value\":\"2\"},{\"name\":\"twelvekey\",\"value\":\"3\"}]},{\"name\":\"reqHardKeyboard\",\"format\":\"boolean\"},{\"name\":\"reqNavigation\",\"enum\":[{\"name\":\"undefined\",\"value\":\"0\"},{\"name\":\"nonav\",\"value\":\"1\"},{\"name\":\"dpad\",\"value\":\"2\"},{\"name\":\"trackball\",\"value\":\"3\"},{\"name\":\"wheel\",\"value\":\"4\"}]},{\"name\":\"reqFiveWayNav\",\"format\":\"boolean\"},{\"name\":\"backupAgent\",\"format\":\"string\"},{\"name\":\"allowBackup\",\"format\":\"boolean\"},{\"name\":\"fullBackupContent\",\"format\":\"reference|boolean\"},{\"name\":\"fullBackupOnly\",\"format\":\"boolean\"},{\"name\":\"killAfterRestore\",\"format\":\"boolean\"},{\"name\":\"restoreNeedsApplication\",\"format\":\"boolean\"},{\"name\":\"restoreAnyVersion\",\"format\":\"boolean\"},{\"name\":\"backupInForeground\",\"format\":\"boolean\"},{\"name\":\"installLocation\",\"enum\":[{\"name\":\"auto\",\"value\":\"0\"},{\"name\":\"internalOnly\",\"value\":\"1\"},{\"name\":\"preferExternal\",\"value\":\"2\"}]},{\"name\":\"isolatedSplits\",\"format\":\"boolean\"},{\"name\":\"classLoader\",\"format\":\"string\"},{\"name\":\"zygotePreloadName\",\"format\":\"string\"},{\"name\":\"isFeatureSplit\",\"format\":\"boolean\"},{\"name\":\"isSplitRequired\",\"format\":\"boolean\"},{\"name\":\"useEmbeddedDex\",\"format\":\"boolean\"},{\"name\":\"uiOptions\",\"flag\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"splitActionBarWhenNarrow\",\"value\":\"1\"}]},{\"name\":\"parentActivityName\",\"format\":\"string\"},{\"name\":\"persistableMode\",\"enum\":[{\"name\":\"persistRootOnly\",\"value\":\"0\"},{\"name\":\"persistNever\",\"value\":\"1\"},{\"name\":\"persistAcrossReboots\",\"value\":\"2\"}]},{\"name\":\"documentLaunchMode\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"intoExisting\",\"value\":\"1\"},{\"name\":\"always\",\"value\":\"2\"},{\"name\":\"never\",\"value\":\"3\"}]},{\"name\":\"maxRecents\",\"format\":\"integer\"},{\"name\":\"autoRemoveFromRecents\",\"format\":\"boolean\"},{\"name\":\"relinquishTaskIdentity\",\"format\":\"boolean\"},{\"name\":\"resumeWhilePausing\",\"format\":\"boolean\"},{\"name\":\"resizeableActivity\",\"format\":\"boolean\"},{\"name\":\"supportsPictureInPicture\",\"format\":\"boolean\"},{\"name\":\"maxAspectRatio\",\"format\":\"float\"},{\"name\":\"minAspectRatio\",\"format\":\"float\"},{\"name\":\"lockTaskMode\",\"enum\":[{\"name\":\"normal\",\"value\":\"0\"},{\"name\":\"never\",\"value\":\"1\"},{\"name\":\"always\",\"value\":\"2\"},{\"name\":\"if_whitelisted\",\"value\":\"3\"}]},{\"name\":\"extractNativeLibs\",\"format\":\"boolean\"},{\"name\":\"autoVerify\",\"format\":\"boolean\"},{\"name\":\"visibleToInstantApps\",\"format\":\"boolean\"},{\"name\":\"networkSecurityConfig\",\"format\":\"reference\"},{\"name\":\"splitName\",\"format\":\"string\"},{\"name\":\"targetSandboxVersion\",\"format\":\"integer\"},{\"name\":\"compileSdkVersion\",\"format\":\"integer\"},{\"name\":\"compileSdkVersionCodename\",\"format\":\"string\"},{\"name\":\"appComponentFactory\",\"format\":\"string\"},{\"name\":\"usesNonSdkApi\",\"format\":\"boolean\"},{\"name\":\"attributionsAreUserVisible\",\"format\":\"boolean\"},{\"name\":\"foregroundServiceType\",\"flag\":[{\"name\":\"dataSync\",\"value\":\"0x01\"},{\"name\":\"mediaPlayback\",\"value\":\"0x02\"},{\"name\":\"phoneCall\",\"value\":\"0x04\"},{\"name\":\"location\",\"value\":\"0x08\"},{\"name\":\"connectedDevice\",\"value\":\"0x10\"},{\"name\":\"mediaProjection\",\"value\":\"0x20\"},{\"name\":\"camera\",\"value\":\"0x40\"},{\"name\":\"microphone\",\"value\":\"0x80\"}]},{\"name\":\"gwpAsanMode\",\"enum\":[{\"name\":\"default\",\"value\":\"-1\"},{\"name\":\"never\",\"value\":\"0\"},{\"name\":\"always\",\"value\":\"1\"}]},{\"name\":\"memtagMode\",\"enum\":[{\"name\":\"default\",\"value\":\"-1\"},{\"name\":\"off\",\"value\":\"0\"},{\"name\":\"async\",\"value\":\"1\"},{\"name\":\"sync\",\"value\":\"2\"}]},{\"name\":\"attributionTags\",\"format\":\"string\"},{\"name\":\"publicKey\",\"format\":\"string\"},{\"name\":\"keySet\"}],\"declare-styleable\":[{\"name\":\"AndroidManifest\",\"attr\":[{\"name\":\"versionCode\"},{\"name\":\"versionCodeMajor\"},{\"name\":\"versionName\"},{\"name\":\"revisionCode\"},{\"name\":\"sharedUserId\"},{\"name\":\"sharedUserLabel\"},{\"name\":\"installLocation\"},{\"name\":\"isolatedSplits\"},{\"name\":\"isFeatureSplit\"},{\"name\":\"targetSandboxVersion\"},{\"name\":\"compileSdkVersion\"},{\"name\":\"compileSdkVersionCodename\"},{\"name\":\"isSplitRequired\"}]},{\"name\":\"AndroidManifestApplication\",\"parent\":\"AndroidManifest\",\"attr\":[{\"name\":\"name\"},{\"name\":\"theme\"},{\"name\":\"label\"},{\"name\":\"icon\"},{\"name\":\"roundIcon\"},{\"name\":\"banner\"},{\"name\":\"logo\"},{\"name\":\"description\"},{\"name\":\"permission\"},{\"name\":\"process\"},{\"name\":\"taskAffinity\"},{\"name\":\"allowTaskReparenting\"},{\"name\":\"hasCode\",\"format\":\"boolean\"},{\"name\":\"persistent\"},{\"name\":\"persistentWhenFeatureAvailable\"},{\"name\":\"requiredForAllUsers\"},{\"name\":\"enabled\"},{\"name\":\"debuggable\"},{\"name\":\"vmSafeMode\"},{\"name\":\"hardwareAccelerated\"},{\"name\":\"manageSpaceActivity\"},{\"name\":\"allowClearUserData\"},{\"name\":\"testOnly\"},{\"name\":\"backupAgent\"},{\"name\":\"allowBackup\"},{\"name\":\"fullBackupOnly\"},{\"name\":\"fullBackupContent\"},{\"name\":\"killAfterRestore\"},{\"name\":\"restoreNeedsApplication\"},{\"name\":\"restoreAnyVersion\"},{\"name\":\"backupInForeground\"},{\"name\":\"largeHeap\",\"format\":\"boolean\"},{\"name\":\"cantSaveState\",\"format\":\"boolean\"},{\"name\":\"uiOptions\"},{\"name\":\"supportsRtl\",\"format\":\"boolean\"},{\"name\":\"restrictedAccountType\",\"format\":\"string\"},{\"name\":\"requiredAccountType\",\"format\":\"string\"},{\"name\":\"isGame\"},{\"name\":\"usesCleartextTraffic\"},{\"name\":\"multiArch\"},{\"name\":\"useEmbeddedDex\"},{\"name\":\"extractNativeLibs\"},{\"name\":\"defaultToDeviceProtectedStorage\",\"format\":\"boolean\"},{\"name\":\"directBootAware\"},{\"name\":\"resizeableActivity\"},{\"name\":\"maxAspectRatio\"},{\"name\":\"minAspectRatio\"},{\"name\":\"networkSecurityConfig\"},{\"name\":\"appCategory\",\"enum\":[{\"name\":\"game\",\"value\":\"0\"},{\"name\":\"audio\",\"value\":\"1\"},{\"name\":\"video\",\"value\":\"2\"},{\"name\":\"image\",\"value\":\"3\"},{\"name\":\"social\",\"value\":\"4\"},{\"name\":\"news\",\"value\":\"5\"},{\"name\":\"maps\",\"value\":\"6\"},{\"name\":\"productivity\",\"value\":\"7\"},{\"name\":\"accessibility\",\"value\":\"8\"}]},{\"name\":\"classLoader\"},{\"name\":\"appComponentFactory\"},{\"name\":\"usesNonSdkApi\"},{\"name\":\"hasFragileUserData\",\"format\":\"boolean\"},{\"name\":\"zygotePreloadName\"},{\"name\":\"allowClearUserDataOnFailedRestore\",\"format\":\"boolean\"},{\"name\":\"allowAudioPlaybackCapture\",\"format\":\"boolean\"},{\"name\":\"requestLegacyExternalStorage\",\"format\":\"boolean\"},{\"name\":\"preserveLegacyExternalStorage\",\"format\":\"boolean\"},{\"name\":\"requestRawExternalStorageAccess\",\"format\":\"boolean\"},{\"name\":\"forceQueryable\",\"format\":\"boolean\"},{\"name\":\"crossProfile\",\"format\":\"boolean\"},{\"name\":\"allowNativeHeapPointerTagging\",\"format\":\"boolean\"},{\"name\":\"gwpAsanMode\"},{\"name\":\"memtagMode\"},{\"name\":\"nativeHeapZeroInitialized\",\"format\":\"boolean\"},{\"name\":\"allowAutoRevokePermissionsExemption\",\"format\":\"boolean\"},{\"name\":\"autoRevokePermissions\",\"enum\":[{\"name\":\"allowed\",\"value\":\"0\"},{\"name\":\"discouraged\",\"value\":\"1\"},{\"name\":\"disallowed\",\"value\":\"2\"}]},{\"name\":\"rollbackDataPolicy\",\"enum\":[{\"name\":\"restore\",\"value\":\"0\"},{\"name\":\"wipe\",\"value\":\"1\"},{\"name\":\"retain\",\"value\":\"2\"}]},{\"name\":\"dataExtractionRules\",\"format\":\"reference\"},{\"name\":\"requestForegroundServiceExemption\",\"format\":\"boolean\"},{\"name\":\"attributionsAreUserVisible\",\"format\":\"boolean\"}]},{\"name\":\"AndroidManifestAttribution\",\"parent\":\"AndroidManifest\",\"attr\":[{\"name\":\"tag\",\"format\":\"string\"},{\"name\":\"label\",\"format\":\"string\"}]},{\"name\":\"AndroidManifestAttributionInheritFrom\",\"parent\":\"AndroidManifestAttribution\",\"attr\":{\"name\":\"tag\",\"format\":\"string\"}},{\"name\":\"AndroidManifestPermission\",\"parent\":\"AndroidManifest\",\"attr\":[{\"name\":\"name\"},{\"name\":\"label\"},{\"name\":\"icon\"},{\"name\":\"roundIcon\"},{\"name\":\"banner\"},{\"name\":\"logo\"},{\"name\":\"permissionGroup\"},{\"name\":\"backgroundPermission\",\"format\":\"string\"},{\"name\":\"description\"},{\"name\":\"request\"},{\"name\":\"protectionLevel\"},{\"name\":\"permissionFlags\"},{\"name\":\"knownCerts\"}]},{\"name\":\"AndroidManifestPermissionGroup\",\"parent\":\"AndroidManifest\",\"attr\":[{\"name\":\"name\"},{\"name\":\"label\"},{\"name\":\"icon\"},{\"name\":\"roundIcon\"},{\"name\":\"banner\"},{\"name\":\"logo\"},{\"name\":\"description\"},{\"name\":\"request\",\"format\":\"string\"},{\"name\":\"requestDetail\",\"format\":\"string\"},{\"name\":\"backgroundRequest\",\"format\":\"string\"},{\"name\":\"backgroundRequestDetail\",\"format\":\"string\"},{\"name\":\"permissionGroupFlags\"},{\"name\":\"priority\"}]},{\"name\":\"AndroidManifestPermissionTree\",\"parent\":\"AndroidManifest\",\"attr\":[{\"name\":\"name\"},{\"name\":\"label\"},{\"name\":\"icon\"},{\"name\":\"roundIcon\"},{\"name\":\"banner\"},{\"name\":\"logo\"}]},{\"name\":\"AndroidManifestUsesPermission\",\"parent\":\"AndroidManifest\",\"attr\":[{\"name\":\"name\"},{\"name\":\"maxSdkVersion\",\"format\":\"integer\"},{\"name\":\"requiredFeature\",\"format\":\"string\"},{\"name\":\"requiredNotFeature\",\"format\":\"string\"},{\"name\":\"usesPermissionFlags\",\"flag\":{\"name\":\"neverForLocation\",\"value\":\"0x00010000\"}}]},{\"name\":\"AndroidManifestRequiredFeature\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AndroidManifestRequiredNotFeature\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AndroidManifestUsesConfiguration\",\"parent\":\"AndroidManifest\",\"attr\":[{\"name\":\"reqTouchScreen\"},{\"name\":\"reqKeyboardType\"},{\"name\":\"reqHardKeyboard\"},{\"name\":\"reqNavigation\"},{\"name\":\"reqFiveWayNav\"}]},{\"name\":\"AndroidManifestUsesFeature\",\"parent\":\"AndroidManifest\",\"attr\":[{\"name\":\"name\"},{\"name\":\"version\",\"format\":\"integer\"},{\"name\":\"glEsVersion\",\"format\":\"integer\"},{\"name\":\"required\",\"format\":\"boolean\"}]},{\"name\":\"AndroidManifestFeatureGroup\",\"attr\":{\"name\":\"label\"}},{\"name\":\"AndroidManifestUsesSdk\",\"parent\":\"AndroidManifest\",\"attr\":[{\"name\":\"minSdkVersion\",\"format\":\"integer|string\"},{\"name\":\"targetSdkVersion\",\"format\":\"integer|string\"},{\"name\":\"maxSdkVersion\"}]},{\"name\":\"AndroidManifestExtensionSdk\",\"attr\":[{\"name\":\"sdkVersion\",\"format\":\"integer\"},{\"name\":\"minExtensionVersion\",\"format\":\"integer\"}]},{\"name\":\"AndroidManifestLibrary\",\"parent\":\"AndroidManifest\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AndroidManifestQueries\",\"parent\":\"AndroidManifest\"},{\"name\":\"AndroidManifestQueriesPackage\",\"parent\":\"AndroidManifestQueries\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AndroidManifestQueriesIntent\",\"parent\":\"AndroidManifestQueries\"},{\"name\":\"AndroidManifestQueriesProvider\",\"parent\":\"AndroidManifestQueries\",\"attr\":{\"name\":\"authorities\"}},{\"name\":\"AndroidManifestStaticLibrary\",\"parent\":\"AndroidManifestApplication\",\"attr\":[{\"name\":\"name\"},{\"name\":\"version\"},{\"name\":\"versionMajor\",\"format\":\"integer\"}]},{\"name\":\"AndroidManifestUsesLibrary\",\"parent\":\"AndroidManifestApplication\",\"attr\":[{\"name\":\"name\"},{\"name\":\"required\"}]},{\"name\":\"AndroidManifestUsesNativeLibrary\",\"parent\":\"AndroidManifestApplication\",\"attr\":[{\"name\":\"name\"},{\"name\":\"required\"}]},{\"name\":\"AndroidManifestUsesStaticLibrary\",\"parent\":\"AndroidManifestApplication\",\"attr\":[{\"name\":\"name\"},{\"name\":\"version\"},{\"name\":\"certDigest\",\"format\":\"string\"}]},{\"name\":\"AndroidManifestAdditionalCertificate\",\"parent\":\"AndroidManifestUsesStaticLibrary\",\"attr\":{\"name\":\"certDigest\"}},{\"name\":\"AndroidManifestUsesPackage\",\"parent\":\"AndroidManifestApplication\",\"attr\":[{\"name\":\"packageType\",\"format\":\"string\"},{\"name\":\"name\"},{\"name\":\"version\"},{\"name\":\"versionMajor\",\"format\":\"integer\"},{\"name\":\"certDigest\",\"format\":\"string\"}]},{\"name\":\"AndroidManifestSupportsScreens\",\"parent\":\"AndroidManifest\",\"attr\":[{\"name\":\"requiresSmallestWidthDp\",\"format\":\"integer\"},{\"name\":\"compatibleWidthLimitDp\",\"format\":\"integer\"},{\"name\":\"largestWidthLimitDp\",\"format\":\"integer\"},{\"name\":\"smallScreens\",\"format\":\"boolean\"},{\"name\":\"normalScreens\",\"format\":\"boolean\"},{\"name\":\"largeScreens\",\"format\":\"boolean\"},{\"name\":\"xlargeScreens\",\"format\":\"boolean\"},{\"name\":\"resizeable\",\"format\":\"boolean\"},{\"name\":\"anyDensity\",\"format\":\"boolean\"}]},{\"name\":\"AndroidManifestProtectedBroadcast\",\"parent\":\"AndroidManifest\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AndroidManifestOriginalPackage\",\"parent\":\"AndroidManifest\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AndroidManifestProcesses\",\"parent\":\"AndroidManifestApplication\"},{\"name\":\"AndroidManifestProcess\",\"parent\":\"AndroidManifestProcesses\",\"attr\":[{\"name\":\"process\"},{\"name\":\"gwpAsanMode\"},{\"name\":\"memtagMode\"},{\"name\":\"nativeHeapZeroInitialized\"}]},{\"name\":\"AndroidManifestDenyPermission\",\"parent\":\"AndroidManifestProcesses\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AndroidManifestAllowPermission\",\"parent\":\"AndroidManifestProcesses\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AndroidManifestProvider\",\"parent\":\"AndroidManifestApplication\",\"attr\":[{\"name\":\"name\"},{\"name\":\"label\"},{\"name\":\"description\"},{\"name\":\"icon\"},{\"name\":\"roundIcon\"},{\"name\":\"banner\"},{\"name\":\"logo\"},{\"name\":\"process\"},{\"name\":\"authorities\"},{\"name\":\"syncable\"},{\"name\":\"readPermission\"},{\"name\":\"writePermission\"},{\"name\":\"grantUriPermissions\"},{\"name\":\"forceUriPermissions\"},{\"name\":\"permission\"},{\"name\":\"multiprocess\"},{\"name\":\"initOrder\"},{\"name\":\"enabled\"},{\"name\":\"exported\"},{\"name\":\"singleUser\"},{\"name\":\"directBootAware\"},{\"name\":\"visibleToInstantApps\"},{\"name\":\"splitName\"},{\"name\":\"attributionTags\"}]},{\"name\":\"AndroidManifestGrantUriPermission\",\"parent\":\"AndroidManifestProvider\",\"attr\":[{\"name\":\"path\",\"format\":\"string\"},{\"name\":\"pathPrefix\",\"format\":\"string\"},{\"name\":\"pathPattern\",\"format\":\"string\"},{\"name\":\"pathAdvancedPattern\",\"format\":\"string\"},{\"name\":\"pathSuffix\",\"format\":\"string\"}]},{\"name\":\"AndroidManifestPathPermission\",\"parent\":\"AndroidManifestProvider\",\"attr\":[{\"name\":\"path\"},{\"name\":\"pathPrefix\"},{\"name\":\"pathPattern\"},{\"name\":\"pathAdvancedPattern\",\"format\":\"string\"},{\"name\":\"pathSuffix\"},{\"name\":\"permission\"},{\"name\":\"readPermission\"},{\"name\":\"writePermission\"}]},{\"name\":\"AndroidManifestService\",\"parent\":\"AndroidManifestApplication\",\"attr\":[{\"name\":\"name\"},{\"name\":\"label\"},{\"name\":\"description\"},{\"name\":\"icon\"},{\"name\":\"roundIcon\"},{\"name\":\"banner\"},{\"name\":\"logo\"},{\"name\":\"permission\"},{\"name\":\"process\"},{\"name\":\"enabled\"},{\"name\":\"exported\"},{\"name\":\"stopWithTask\",\"format\":\"boolean\"},{\"name\":\"isolatedProcess\",\"format\":\"boolean\"},{\"name\":\"singleUser\"},{\"name\":\"directBootAware\"},{\"name\":\"externalService\",\"format\":\"boolean\"},{\"name\":\"visibleToInstantApps\"},{\"name\":\"splitName\"},{\"name\":\"useAppZygote\",\"format\":\"boolean\"},{\"name\":\"foregroundServiceType\"},{\"name\":\"attributionTags\"}]},{\"name\":\"AndroidManifestReceiver\",\"parent\":\"AndroidManifestApplication\",\"attr\":[{\"name\":\"name\"},{\"name\":\"label\"},{\"name\":\"description\"},{\"name\":\"icon\"},{\"name\":\"roundIcon\"},{\"name\":\"banner\"},{\"name\":\"logo\"},{\"name\":\"permission\"},{\"name\":\"process\"},{\"name\":\"enabled\"},{\"name\":\"exported\"},{\"name\":\"singleUser\"},{\"name\":\"directBootAware\"},{\"name\":\"attributionTags\"}]},{\"name\":\"AndroidManifestActivity\",\"parent\":\"AndroidManifestApplication\",\"attr\":[{\"name\":\"name\"},{\"name\":\"theme\"},{\"name\":\"label\"},{\"name\":\"description\"},{\"name\":\"icon\"},{\"name\":\"roundIcon\"},{\"name\":\"banner\"},{\"name\":\"logo\"},{\"name\":\"launchMode\"},{\"name\":\"screenOrientation\"},{\"name\":\"configChanges\"},{\"name\":\"recreateOnConfigChanges\"},{\"name\":\"permission\"},{\"name\":\"multiprocess\"},{\"name\":\"process\"},{\"name\":\"taskAffinity\"},{\"name\":\"allowTaskReparenting\"},{\"name\":\"finishOnTaskLaunch\"},{\"name\":\"finishOnCloseSystemDialogs\"},{\"name\":\"clearTaskOnLaunch\"},{\"name\":\"noHistory\"},{\"name\":\"alwaysRetainTaskState\"},{\"name\":\"stateNotNeeded\"},{\"name\":\"excludeFromRecents\"},{\"name\":\"showOnLockScreen\"},{\"name\":\"enabled\"},{\"name\":\"exported\"},{\"name\":\"windowSoftInputMode\"},{\"name\":\"immersive\"},{\"name\":\"hardwareAccelerated\"},{\"name\":\"uiOptions\"},{\"name\":\"parentActivityName\"},{\"name\":\"singleUser\"},{\"name\":\"systemUserOnly\",\"format\":\"boolean\"},{\"name\":\"persistableMode\"},{\"name\":\"allowEmbedded\"},{\"name\":\"documentLaunchMode\"},{\"name\":\"maxRecents\"},{\"name\":\"autoRemoveFromRecents\"},{\"name\":\"relinquishTaskIdentity\"},{\"name\":\"resumeWhilePausing\"},{\"name\":\"resizeableActivity\"},{\"name\":\"supportsPictureInPicture\"},{\"name\":\"maxAspectRatio\"},{\"name\":\"minAspectRatio\"},{\"name\":\"lockTaskMode\"},{\"name\":\"showForAllUsers\"},{\"name\":\"showWhenLocked\"},{\"name\":\"inheritShowWhenLocked\"},{\"name\":\"turnScreenOn\"},{\"name\":\"directBootAware\"},{\"name\":\"alwaysFocusable\",\"format\":\"boolean\"},{\"name\":\"enableVrMode\"},{\"name\":\"rotationAnimation\"},{\"name\":\"visibleToInstantApps\"},{\"name\":\"splitName\"},{\"name\":\"colorMode\",\"enum\":[{\"name\":\"default\",\"value\":\"0\"},{\"name\":\"wideColorGamut\",\"value\":\"1\"},{\"name\":\"hdr\",\"value\":\"2\"}]},{\"name\":\"forceQueryable\",\"format\":\"boolean\"},{\"name\":\"preferMinimalPostProcessing\",\"format\":\"boolean\"},{\"name\":\"attributionTags\"},{\"name\":\"playHomeTransitionSound\",\"format\":\"boolean\"}]},{\"name\":\"AndroidManifestActivityAlias\",\"parent\":\"AndroidManifestApplication\",\"attr\":[{\"name\":\"name\"},{\"name\":\"targetActivity\",\"format\":\"string\"},{\"name\":\"label\"},{\"name\":\"description\"},{\"name\":\"icon\"},{\"name\":\"roundIcon\"},{\"name\":\"banner\"},{\"name\":\"logo\"},{\"name\":\"permission\"},{\"name\":\"enabled\"},{\"name\":\"exported\"},{\"name\":\"parentActivityName\"},{\"name\":\"attributionTags\"}]},{\"name\":\"AndroidManifestMetaData\",\"parent\":\"AndroidManifestApplication                  AndroidManifestActivity                  AndroidManifestReceiver                  AndroidManifestProvider                  AndroidManifestService                  AndroidManifestPermission                  AndroidManifestPermissionGroup                  AndroidManifestInstrumentation\",\"attr\":[{\"name\":\"name\"},{\"name\":\"value\",\"format\":\"string|integer|color|float|boolean\"},{\"name\":\"resource\",\"format\":\"reference\"}]},{\"name\":\"AndroidManifestProperty\",\"parent\":\"AndroidManifestApplication                  AndroidManifestActivity                  AndroidManifestReceiver                  AndroidManifestProvider                  AndroidManifestService\",\"attr\":[{\"name\":\"name\"},{\"name\":\"value\"},{\"name\":\"resource\"}]},{\"name\":\"AndroidManifestIntentFilter\",\"parent\":\"AndroidManifestActivity AndroidManifestReceiver AndroidManifestService\",\"attr\":[{\"name\":\"label\"},{\"name\":\"icon\"},{\"name\":\"roundIcon\"},{\"name\":\"banner\"},{\"name\":\"logo\"},{\"name\":\"priority\"},{\"name\":\"autoVerify\"},{\"name\":\"order\"}]},{\"name\":\"AndroidManifestAction\",\"parent\":\"AndroidManifestIntentFilter\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AndroidManifestData\",\"parent\":\"AndroidManifestIntentFilter\",\"attr\":[{\"name\":\"mimeType\",\"format\":\"string\"},{\"name\":\"mimeGroup\",\"format\":\"string\"},{\"name\":\"scheme\",\"format\":\"string\"},{\"name\":\"ssp\",\"format\":\"string\"},{\"name\":\"sspPrefix\",\"format\":\"string\"},{\"name\":\"sspPattern\",\"format\":\"string\"},{\"name\":\"sspAdvancedPattern\",\"format\":\"string\"},{\"name\":\"sspSuffix\",\"format\":\"string\"},{\"name\":\"host\",\"format\":\"string\"},{\"name\":\"port\",\"format\":\"string\"},{\"name\":\"path\"},{\"name\":\"pathPrefix\"},{\"name\":\"pathPattern\"},{\"name\":\"pathAdvancedPattern\"},{\"name\":\"pathSuffix\"}]},{\"name\":\"AndroidManifestCategory\",\"parent\":\"AndroidManifestIntentFilter\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AndroidManifestInstrumentation\",\"parent\":\"AndroidManifest\",\"attr\":[{\"name\":\"name\"},{\"name\":\"targetPackage\"},{\"name\":\"targetProcesses\"},{\"name\":\"label\"},{\"name\":\"icon\"},{\"name\":\"roundIcon\"},{\"name\":\"banner\"},{\"name\":\"logo\"},{\"name\":\"handleProfiling\"},{\"name\":\"functionalTest\"}]},{\"name\":\"AndroidManifestCompatibleScreensScreen\",\"parent\":\"AndroidManifest.AndroidManifestCompatibleScreens\",\"attr\":[{\"name\":\"screenSize\",\"enum\":[{\"name\":\"small\",\"value\":\"200\"},{\"name\":\"normal\",\"value\":\"300\"},{\"name\":\"large\",\"value\":\"400\"},{\"name\":\"xlarge\",\"value\":\"500\"}]},{\"name\":\"screenDensity\",\"format\":\"integer\",\"enum\":[{\"name\":\"ldpi\",\"value\":\"120\"},{\"name\":\"mdpi\",\"value\":\"160\"},{\"name\":\"hdpi\",\"value\":\"240\"},{\"name\":\"xhdpi\",\"value\":\"320\"},{\"name\":\"xxhdpi\",\"value\":\"480\"},{\"name\":\"xxxhdpi\",\"value\":\"640\"}]}]},{\"name\":\"AndroidManifestSupportsInputInputType\",\"parent\":\"AndroidManifest.AndroidManifestSupportsInput\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AndroidManifestPackageVerifier\",\"parent\":\"AndroidManifest\",\"attr\":[{\"name\":\"name\"},{\"name\":\"publicKey\"}]},{\"name\":\"AndroidManifestResourceOverlay\",\"parent\":\"AndroidManifest\",\"attr\":[{\"name\":\"targetPackage\"},{\"name\":\"category\",\"format\":\"string\"},{\"name\":\"priority\"},{\"name\":\"isStatic\",\"format\":\"boolean\"},{\"name\":\"requiredSystemPropertyName\",\"format\":\"string\"},{\"name\":\"requiredSystemPropertyValue\",\"format\":\"string\"},{\"name\":\"targetName\"},{\"name\":\"resourcesMap\",\"format\":\"reference\"}]},{\"name\":\"Intent\",\"attr\":[{\"name\":\"action\",\"format\":\"string\"},{\"name\":\"data\",\"format\":\"string\"},{\"name\":\"mimeType\"},{\"name\":\"identifier\",\"format\":\"string\"},{\"name\":\"targetPackage\"},{\"name\":\"targetClass\",\"format\":\"string\"}]},{\"name\":\"IntentCategory\",\"parent\":\"Intent\",\"attr\":{\"name\":\"name\"}},{\"name\":\"Extra\",\"parent\":\"Intent\",\"attr\":[{\"name\":\"name\"},{\"name\":\"value\"}]},{\"name\":\"AndroidManifestPublicKey\",\"attr\":[{\"name\":\"name\"},{\"name\":\"value\"}]},{\"name\":\"AndroidManifestKeySet\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AndroidManifestUpgradeKeySet\",\"parent\":\"AndroidManifest\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AndroidManifestLayout\",\"parent\":\"AndroidManifestActivity\",\"attr\":[{\"name\":\"defaultWidth\",\"format\":\"dimension|fraction\"},{\"name\":\"defaultHeight\",\"format\":\"dimension|fraction\"},{\"name\":\"gravity\"},{\"name\":\"minWidth\"},{\"name\":\"minHeight\"},{\"name\":\"windowLayoutAffinity\",\"format\":\"string\"}]},{\"name\":\"AndroidManifestRestrictUpdate\",\"parent\":\"AndroidManifest\",\"attr\":{\"name\":\"hash\",\"format\":\"string\"}},{\"name\":\"AndroidManifestUsesSplit\",\"parent\":\"AndroidManifest\",\"attr\":{\"name\":\"name\",\"format\":\"string\"}},{\"name\":\"AndroidManifestProfileable\",\"parent\":\"AndroidManifestApplication\",\"attr\":[{\"name\":\"shell\",\"format\":\"boolean\"},{\"name\":\"enabled\",\"format\":\"boolean\"}]}]},\"attrs\":{\"declare-styleable\":[{\"name\":\"Theme\",\"eat-comment\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\"attr\":[{\"name\":\"isLightTheme\",\"format\":\"boolean\"},{\"name\":\"colorForeground\",\"format\":\"color\"},{\"name\":\"colorForegroundInverse\",\"format\":\"color\"},{\"name\":\"colorBackground\",\"format\":\"color\"},{\"name\":\"colorBackgroundFloating\",\"format\":\"color\"},{\"name\":\"colorBackgroundCacheHint\",\"format\":\"color\"},{\"name\":\"colorPressedHighlight\",\"format\":\"color\"},{\"name\":\"colorLongPressedHighlight\",\"format\":\"color\"},{\"name\":\"colorFocusedHighlight\",\"format\":\"color\"},{\"name\":\"colorActivatedHighlight\",\"format\":\"color\"},{\"name\":\"colorMultiSelectHighlight\",\"format\":\"color\"},{\"name\":\"autofilledHighlight\",\"format\":\"reference\"},{\"name\":\"autofillDatasetPickerMaxWidth\",\"format\":\"reference\"},{\"name\":\"autofillDatasetPickerMaxHeight\",\"format\":\"reference\"},{\"name\":\"autofillSaveCustomSubtitleMaxHeight\",\"format\":\"reference\"},{\"name\":\"disabledAlpha\",\"format\":\"float\"},{\"name\":\"primaryContentAlpha\",\"format\":\"float\"},{\"name\":\"secondaryContentAlpha\",\"format\":\"float\"},{\"name\":\"colorError\",\"format\":\"reference|color\"},{\"name\":\"backgroundDimAmount\",\"format\":\"float\"},{\"name\":\"backgroundDimEnabled\",\"format\":\"boolean\"},{\"name\":\"windowBlurBehindRadius\",\"format\":\"dimension\"},{\"name\":\"windowBlurBehindEnabled\",\"format\":\"boolean\"},{\"name\":\"colorPopupBackground\",\"format\":\"color\"},{\"name\":\"colorListDivider\",\"format\":\"color\"},{\"name\":\"opacityListDivider\",\"format\":\"color\"},{\"name\":\"textAppearance\",\"format\":\"reference\"},{\"name\":\"textAppearanceInverse\",\"format\":\"reference\"},{\"name\":\"textColorPrimary\",\"format\":\"reference|color\"},{\"name\":\"textColorSecondary\",\"format\":\"reference|color\"},{\"name\":\"textColorTertiary\",\"format\":\"reference|color\"},{\"name\":\"textColorPrimaryInverse\",\"format\":\"reference|color\"},{\"name\":\"textColorSecondaryInverse\",\"format\":\"reference|color\"},{\"name\":\"textColorTertiaryInverse\",\"format\":\"reference|color\"},{\"name\":\"textColorHintInverse\",\"format\":\"reference|color\"},{\"name\":\"textColorPrimaryDisableOnly\",\"format\":\"reference|color\"},{\"name\":\"textColorPrimaryInverseDisableOnly\",\"format\":\"reference|color\"},{\"name\":\"textColorPrimaryNoDisable\",\"format\":\"reference|color\"},{\"name\":\"textColorSecondaryNoDisable\",\"format\":\"reference|color\"},{\"name\":\"textColorPrimaryInverseNoDisable\",\"format\":\"reference|color\"},{\"name\":\"textColorSecondaryInverseNoDisable\",\"format\":\"reference|color\"},{\"name\":\"textColorPrimaryActivated\",\"format\":\"reference|color\"},{\"name\":\"textColorSecondaryActivated\",\"format\":\"reference|color\"},{\"name\":\"textColorSearchUrl\",\"format\":\"reference|color\"},{\"name\":\"textColorHighlightInverse\",\"format\":\"reference|color\"},{\"name\":\"textColorLinkInverse\",\"format\":\"reference|color\"},{\"name\":\"textColorAlertDialogListItem\",\"format\":\"reference|color\"},{\"name\":\"searchWidgetCorpusItemBackground\",\"format\":\"reference|color\"},{\"name\":\"textAppearanceLarge\",\"format\":\"reference\"},{\"name\":\"textAppearanceMedium\",\"format\":\"reference\"},{\"name\":\"textAppearanceSmall\",\"format\":\"reference\"},{\"name\":\"textAppearanceLargeInverse\",\"format\":\"reference\"},{\"name\":\"textAppearanceMediumInverse\",\"format\":\"reference\"},{\"name\":\"textAppearanceSmallInverse\",\"format\":\"reference\"},{\"name\":\"textAppearanceSearchResultTitle\",\"format\":\"reference\"},{\"name\":\"textAppearanceSearchResultSubtitle\",\"format\":\"reference\"},{\"name\":\"textAppearanceButton\",\"format\":\"reference\"},{\"name\":\"textAppearanceLargePopupMenu\",\"format\":\"reference\"},{\"name\":\"textAppearanceSmallPopupMenu\",\"format\":\"reference\"},{\"name\":\"textAppearancePopupMenuHeader\",\"format\":\"reference\"},{\"name\":\"textAppearanceEasyCorrectSuggestion\",\"format\":\"reference\"},{\"name\":\"textAppearanceMisspelledSuggestion\",\"format\":\"reference\"},{\"name\":\"textAppearanceAutoCorrectionSuggestion\",\"format\":\"reference\"},{\"name\":\"textAppearanceGrammarErrorSuggestion\",\"format\":\"reference\"},{\"name\":\"textUnderlineColor\",\"format\":\"reference|color\"},{\"name\":\"textUnderlineThickness\",\"format\":\"reference|dimension\"},{\"name\":\"editTextColor\",\"format\":\"reference|color\"},{\"name\":\"editTextBackground\",\"format\":\"reference\"},{\"name\":\"errorMessageBackground\",\"format\":\"reference\"},{\"name\":\"errorMessageAboveBackground\",\"format\":\"reference\"},{\"name\":\"candidatesTextStyleSpans\",\"format\":\"reference|string\"},{\"name\":\"textCheckMark\",\"format\":\"reference\"},{\"name\":\"textCheckMarkInverse\",\"format\":\"reference\"},{\"name\":\"listChoiceIndicatorMultiple\",\"format\":\"reference\"},{\"name\":\"listChoiceIndicatorSingle\",\"format\":\"reference\"},{\"name\":\"listChoiceBackgroundIndicator\",\"format\":\"reference\"},{\"name\":\"activatedBackgroundIndicator\",\"format\":\"reference\"},{\"name\":\"buttonStyle\",\"format\":\"reference\"},{\"name\":\"buttonStyleSmall\",\"format\":\"reference\"},{\"name\":\"buttonStyleInset\",\"format\":\"reference\"},{\"name\":\"buttonStyleToggle\",\"format\":\"reference\"},{\"name\":\"galleryItemBackground\",\"format\":\"reference\"},{\"name\":\"listPreferredItemHeight\",\"format\":\"dimension\"},{\"name\":\"listPreferredItemHeightSmall\",\"format\":\"dimension\"},{\"name\":\"listPreferredItemHeightLarge\",\"format\":\"dimension\"},{\"name\":\"searchResultListItemHeight\",\"format\":\"dimension\"},{\"name\":\"listPreferredItemPaddingLeft\",\"format\":\"dimension\"},{\"name\":\"listPreferredItemPaddingRight\",\"format\":\"dimension\"},{\"name\":\"textAppearanceListItem\",\"format\":\"reference\"},{\"name\":\"textAppearanceListItemSecondary\",\"format\":\"reference\"},{\"name\":\"textAppearanceListItemSmall\",\"format\":\"reference\"},{\"name\":\"listDivider\",\"format\":\"reference\"},{\"name\":\"listDividerAlertDialog\",\"format\":\"reference\"},{\"name\":\"listSeparatorTextViewStyle\",\"format\":\"reference\"},{\"name\":\"expandableListPreferredItemPaddingLeft\",\"format\":\"dimension\"},{\"name\":\"expandableListPreferredChildPaddingLeft\",\"format\":\"dimension\"},{\"name\":\"expandableListPreferredItemIndicatorLeft\",\"format\":\"dimension\"},{\"name\":\"expandableListPreferredItemIndicatorRight\",\"format\":\"dimension\"},{\"name\":\"expandableListPreferredChildIndicatorLeft\",\"format\":\"dimension\"},{\"name\":\"expandableListPreferredChildIndicatorRight\",\"format\":\"dimension\"},{\"name\":\"dropdownListPreferredItemHeight\",\"format\":\"dimension\"},{\"name\":\"listPreferredItemPaddingStart\",\"format\":\"dimension\"},{\"name\":\"listPreferredItemPaddingEnd\",\"format\":\"dimension\"},{\"name\":\"windowBackground\",\"format\":\"reference|color\"},{\"name\":\"windowBackgroundFallback\",\"format\":\"reference|color\"},{\"name\":\"windowBackgroundBlurRadius\",\"format\":\"dimension\"},{\"name\":\"windowFrame\",\"format\":\"reference\"},{\"name\":\"windowNoTitle\",\"format\":\"boolean\"},{\"name\":\"windowFullscreen\",\"format\":\"boolean\"},{\"name\":\"windowOverscan\",\"format\":\"boolean\"},{\"name\":\"windowIsFloating\",\"format\":\"boolean\"},{\"name\":\"windowIsTranslucent\",\"format\":\"boolean\"},{\"name\":\"windowShowWallpaper\",\"format\":\"boolean\"},{\"name\":\"windowContentOverlay\",\"format\":\"reference\"},{\"name\":\"windowTitleSize\",\"format\":\"dimension\"},{\"name\":\"windowTitleStyle\",\"format\":\"reference\"},{\"name\":\"windowTitleBackgroundStyle\",\"format\":\"reference\"},{\"name\":\"windowAnimationStyle\",\"format\":\"reference\"},{\"name\":\"windowActionBar\",\"format\":\"boolean\"},{\"name\":\"windowActionBarOverlay\",\"format\":\"boolean\"},{\"name\":\"windowActionModeOverlay\",\"format\":\"boolean\"},{\"name\":\"windowSoftInputMode\",\"flag\":[{\"name\":\"stateUnspecified\",\"value\":\"0\"},{\"name\":\"stateUnchanged\",\"value\":\"1\"},{\"name\":\"stateHidden\",\"value\":\"2\"},{\"name\":\"stateAlwaysHidden\",\"value\":\"3\"},{\"name\":\"stateVisible\",\"value\":\"4\"},{\"name\":\"stateAlwaysVisible\",\"value\":\"5\"},{\"name\":\"adjustUnspecified\",\"value\":\"0x00\"},{\"name\":\"adjustResize\",\"value\":\"0x10\"},{\"name\":\"adjustPan\",\"value\":\"0x20\"},{\"name\":\"adjustNothing\",\"value\":\"0x30\"}]},{\"name\":\"windowDisablePreview\",\"format\":\"boolean\"},{\"name\":\"windowNoDisplay\",\"format\":\"boolean\"},{\"name\":\"windowEnableSplitTouch\",\"format\":\"boolean\"},{\"name\":\"windowCloseOnTouchOutside\",\"format\":\"boolean\"},{\"name\":\"windowTranslucentStatus\",\"format\":\"boolean\"},{\"name\":\"windowTranslucentNavigation\",\"format\":\"boolean\"},{\"name\":\"windowSwipeToDismiss\",\"format\":\"boolean\"},{\"name\":\"windowContentTransitions\",\"format\":\"boolean\"},{\"name\":\"windowContentTransitionManager\",\"format\":\"reference\"},{\"name\":\"windowActivityTransitions\",\"format\":\"boolean\"},{\"name\":\"windowEnterTransition\",\"format\":\"reference\"},{\"name\":\"windowReturnTransition\",\"format\":\"reference\"},{\"name\":\"windowExitTransition\",\"format\":\"reference\"},{\"name\":\"windowReenterTransition\",\"format\":\"reference\"},{\"name\":\"windowSharedElementEnterTransition\",\"format\":\"reference\"},{\"name\":\"windowSharedElementReturnTransition\",\"format\":\"reference\"},{\"name\":\"windowSharedElementExitTransition\",\"format\":\"reference\"},{\"name\":\"windowSharedElementReenterTransition\",\"format\":\"reference\"},{\"name\":\"windowAllowEnterTransitionOverlap\",\"format\":\"boolean\"},{\"name\":\"windowAllowReturnTransitionOverlap\",\"format\":\"boolean\"},{\"name\":\"windowSharedElementsUseOverlay\",\"format\":\"boolean\"},{\"name\":\"windowActionBarFullscreenDecorLayout\",\"format\":\"reference\"},{\"name\":\"windowTransitionBackgroundFadeDuration\",\"format\":\"integer\"},{\"name\":\"floatingToolbarCloseDrawable\",\"format\":\"reference\"},{\"name\":\"floatingToolbarForegroundColor\",\"format\":\"reference|color\"},{\"name\":\"floatingToolbarItemBackgroundBorderlessDrawable\",\"format\":\"reference\"},{\"name\":\"floatingToolbarItemBackgroundDrawable\",\"format\":\"reference\"},{\"name\":\"floatingToolbarOpenDrawable\",\"format\":\"reference\"},{\"name\":\"floatingToolbarPopupBackgroundDrawable\",\"format\":\"reference\"},{\"name\":\"floatingToolbarDividerColor\",\"format\":\"reference\"},{\"name\":\"alertDialogStyle\",\"format\":\"reference\"},{\"name\":\"alertDialogButtonGroupStyle\",\"format\":\"reference\"},{\"name\":\"alertDialogCenterButtons\",\"format\":\"boolean\"},{\"name\":\"detailsElementBackground\",\"format\":\"reference\"},{\"name\":\"fingerprintAuthDrawable\",\"format\":\"reference\"},{\"name\":\"panelBackground\",\"format\":\"reference|color\"},{\"name\":\"panelFullBackground\",\"format\":\"reference|color\"},{\"name\":\"panelColorForeground\",\"format\":\"reference|color\"},{\"name\":\"panelColorBackground\",\"format\":\"reference|color\"},{\"name\":\"panelTextAppearance\",\"format\":\"reference\"},{\"name\":\"panelMenuIsCompact\",\"format\":\"boolean\"},{\"name\":\"panelMenuListWidth\",\"format\":\"dimension\"},{\"name\":\"panelMenuListTheme\",\"format\":\"reference\"},{\"name\":\"absListViewStyle\",\"format\":\"reference\"},{\"name\":\"autoCompleteTextViewStyle\",\"format\":\"reference\"},{\"name\":\"checkboxStyle\",\"format\":\"reference\"},{\"name\":\"checkedTextViewStyle\",\"format\":\"reference\"},{\"name\":\"dropDownListViewStyle\",\"format\":\"reference\"},{\"name\":\"editTextStyle\",\"format\":\"reference\"},{\"name\":\"expandableListViewStyle\",\"format\":\"reference\"},{\"name\":\"expandableListViewWhiteStyle\",\"format\":\"reference\"},{\"name\":\"galleryStyle\",\"format\":\"reference\"},{\"name\":\"gestureOverlayViewStyle\",\"format\":\"reference\"},{\"name\":\"gridViewStyle\",\"format\":\"reference\"},{\"name\":\"imageButtonStyle\",\"format\":\"reference\"},{\"name\":\"imageWellStyle\",\"format\":\"reference\"},{\"name\":\"listMenuViewStyle\",\"format\":\"reference\"},{\"name\":\"listViewStyle\",\"format\":\"reference\"},{\"name\":\"listViewWhiteStyle\",\"format\":\"reference\"},{\"name\":\"popupWindowStyle\",\"format\":\"reference\"},{\"name\":\"progressBarStyle\",\"format\":\"reference\"},{\"name\":\"progressBarStyleHorizontal\",\"format\":\"reference\"},{\"name\":\"progressBarStyleSmall\",\"format\":\"reference\"},{\"name\":\"progressBarStyleSmallTitle\",\"format\":\"reference\"},{\"name\":\"progressBarStyleLarge\",\"format\":\"reference\"},{\"name\":\"progressBarStyleInverse\",\"format\":\"reference\"},{\"name\":\"progressBarStyleSmallInverse\",\"format\":\"reference\"},{\"name\":\"progressBarStyleLargeInverse\",\"format\":\"reference\"},{\"name\":\"seekBarStyle\",\"format\":\"reference\"},{\"name\":\"ratingBarStyle\",\"format\":\"reference\"},{\"name\":\"ratingBarStyleIndicator\",\"format\":\"reference\"},{\"name\":\"ratingBarStyleSmall\",\"format\":\"reference\"},{\"name\":\"radioButtonStyle\",\"format\":\"reference\"},{\"name\":\"scrollViewStyle\",\"format\":\"reference\"},{\"name\":\"horizontalScrollViewStyle\",\"format\":\"reference\"},{\"name\":\"spinnerStyle\",\"format\":\"reference\"},{\"name\":\"dropDownSpinnerStyle\",\"format\":\"reference\"},{\"name\":\"actionDropDownStyle\",\"format\":\"reference\"},{\"name\":\"actionButtonStyle\",\"format\":\"reference\"},{\"name\":\"starStyle\",\"format\":\"reference\"},{\"name\":\"tabWidgetStyle\",\"format\":\"reference\"},{\"name\":\"textViewStyle\",\"format\":\"reference\"},{\"name\":\"webTextViewStyle\",\"format\":\"reference\"},{\"name\":\"webViewStyle\",\"format\":\"reference\"},{\"name\":\"dropDownItemStyle\",\"format\":\"reference\"},{\"name\":\"spinnerDropDownItemStyle\",\"format\":\"reference\"},{\"name\":\"dropDownHintAppearance\",\"format\":\"reference\"},{\"name\":\"spinnerItemStyle\",\"format\":\"reference\"},{\"name\":\"mapViewStyle\",\"format\":\"reference\"},{\"name\":\"quickContactBadgeOverlay\",\"format\":\"reference\"},{\"name\":\"quickContactBadgeStyleWindowSmall\",\"format\":\"reference\"},{\"name\":\"quickContactBadgeStyleWindowMedium\",\"format\":\"reference\"},{\"name\":\"quickContactBadgeStyleWindowLarge\",\"format\":\"reference\"},{\"name\":\"quickContactBadgeStyleSmallWindowSmall\",\"format\":\"reference\"},{\"name\":\"quickContactBadgeStyleSmallWindowMedium\",\"format\":\"reference\"},{\"name\":\"quickContactBadgeStyleSmallWindowLarge\",\"format\":\"reference\"},{\"name\":\"textSelectHandleWindowStyle\",\"format\":\"reference\"},{\"name\":\"textSuggestionsWindowStyle\",\"format\":\"reference\"},{\"name\":\"listPopupWindowStyle\",\"format\":\"reference\"},{\"name\":\"popupMenuStyle\",\"format\":\"reference\"},{\"name\":\"contextPopupMenuStyle\",\"format\":\"reference\"},{\"name\":\"stackViewStyle\",\"format\":\"reference\"},{\"name\":\"magnifierStyle\",\"format\":\"reference\"},{\"name\":\"fragmentBreadCrumbsStyle\",\"format\":\"reference\"},{\"name\":\"numberPickerStyle\",\"format\":\"reference\"},{\"name\":\"calendarViewStyle\",\"format\":\"reference\"},{\"name\":\"timePickerStyle\",\"format\":\"reference\"},{\"name\":\"timePickerDialogTheme\",\"format\":\"reference\"},{\"name\":\"datePickerStyle\",\"format\":\"reference\"},{\"name\":\"datePickerDialogTheme\",\"format\":\"reference\"},{\"name\":\"activityChooserViewStyle\",\"format\":\"reference\"},{\"name\":\"toolbarStyle\",\"format\":\"reference\"},{\"name\":\"fastScrollThumbDrawable\",\"format\":\"reference\"},{\"name\":\"fastScrollPreviewBackgroundRight\",\"format\":\"reference\"},{\"name\":\"fastScrollPreviewBackgroundLeft\",\"format\":\"reference\"},{\"name\":\"fastScrollTrackDrawable\",\"format\":\"reference\"},{\"name\":\"fastScrollOverlayPosition\",\"enum\":[{\"name\":\"floating\",\"value\":\"0\"},{\"name\":\"atThumb\",\"value\":\"1\"},{\"name\":\"aboveThumb\",\"value\":\"2\"}]},{\"name\":\"fastScrollTextColor\",\"format\":\"color\"},{\"name\":\"actionBarTabStyle\",\"format\":\"reference\"},{\"name\":\"actionBarTabBarStyle\",\"format\":\"reference\"},{\"name\":\"actionBarTabTextStyle\",\"format\":\"reference\"},{\"name\":\"actionOverflowButtonStyle\",\"format\":\"reference\"},{\"name\":\"actionOverflowMenuStyle\",\"format\":\"reference\"},{\"name\":\"actionBarPopupTheme\",\"format\":\"reference\"},{\"name\":\"actionBarStyle\",\"format\":\"reference\"},{\"name\":\"actionBarSplitStyle\",\"format\":\"reference\"},{\"name\":\"actionBarTheme\",\"format\":\"reference\"},{\"name\":\"actionBarWidgetTheme\",\"format\":\"reference\"},{\"name\":\"actionBarSize\",\"format\":\"dimension\",\"enum\":{\"name\":\"wrap_content\",\"value\":\"0\"}},{\"name\":\"actionBarDivider\",\"format\":\"reference\"},{\"name\":\"actionBarItemBackground\",\"format\":\"reference\"},{\"name\":\"actionMenuTextAppearance\",\"format\":\"reference\"},{\"name\":\"actionMenuTextColor\",\"format\":\"color|reference\"},{\"name\":\"actionModeStyle\",\"format\":\"reference\"},{\"name\":\"actionModeCloseButtonStyle\",\"format\":\"reference\"},{\"name\":\"actionModeBackground\",\"format\":\"reference\"},{\"name\":\"actionModeSplitBackground\",\"format\":\"reference\"},{\"name\":\"actionModeCloseDrawable\",\"format\":\"reference\"},{\"name\":\"actionModeCutDrawable\",\"format\":\"reference\"},{\"name\":\"actionModeCopyDrawable\",\"format\":\"reference\"},{\"name\":\"actionModePasteDrawable\",\"format\":\"reference\"},{\"name\":\"actionModeSelectAllDrawable\",\"format\":\"reference\"},{\"name\":\"actionModeShareDrawable\",\"format\":\"reference\"},{\"name\":\"actionModeFindDrawable\",\"format\":\"reference\"},{\"name\":\"actionModeWebSearchDrawable\",\"format\":\"reference\"},{\"name\":\"actionModePopupWindowStyle\",\"format\":\"reference\"},{\"name\":\"preferenceScreenStyle\",\"format\":\"reference\"},{\"name\":\"preferenceActivityStyle\",\"format\":\"reference\"},{\"name\":\"preferenceFragmentStyle\",\"format\":\"reference\"},{\"name\":\"preferenceCategoryStyle\",\"format\":\"reference\"},{\"name\":\"preferenceStyle\",\"format\":\"reference\"},{\"name\":\"preferenceInformationStyle\",\"format\":\"reference\"},{\"name\":\"checkBoxPreferenceStyle\",\"format\":\"reference\"},{\"name\":\"yesNoPreferenceStyle\",\"format\":\"reference\"},{\"name\":\"dialogPreferenceStyle\",\"format\":\"reference\"},{\"name\":\"editTextPreferenceStyle\",\"format\":\"reference\"},{\"name\":\"seekBarDialogPreferenceStyle\",\"format\":\"reference\"},{\"name\":\"ringtonePreferenceStyle\",\"format\":\"reference\"},{\"name\":\"preferenceLayoutChild\",\"format\":\"reference\"},{\"name\":\"preferencePanelStyle\",\"format\":\"reference\"},{\"name\":\"preferenceHeaderPanelStyle\",\"format\":\"reference\"},{\"name\":\"preferenceListStyle\",\"format\":\"reference\"},{\"name\":\"preferenceFragmentListStyle\",\"format\":\"reference\"},{\"name\":\"preferenceFragmentPaddingSide\",\"format\":\"dimension\"},{\"name\":\"switchPreferenceStyle\",\"format\":\"reference\"},{\"name\":\"seekBarPreferenceStyle\",\"format\":\"reference\"},{\"name\":\"textSelectHandleLeft\",\"format\":\"reference\"},{\"name\":\"textSelectHandleRight\",\"format\":\"reference\"},{\"name\":\"textSelectHandle\",\"format\":\"reference\"},{\"name\":\"textEditPasteWindowLayout\",\"format\":\"reference\"},{\"name\":\"textEditNoPasteWindowLayout\",\"format\":\"reference\"},{\"name\":\"textEditSidePasteWindowLayout\",\"format\":\"reference\"},{\"name\":\"textEditSideNoPasteWindowLayout\",\"format\":\"reference\"},{\"name\":\"textEditSuggestionItemLayout\",\"format\":\"reference\"},{\"name\":\"textEditSuggestionContainerLayout\",\"format\":\"reference\"},{\"name\":\"textEditSuggestionHighlightStyle\",\"format\":\"reference\"},{\"name\":\"dialogTheme\",\"format\":\"reference\"},{\"name\":\"dialogTitleIconsDecorLayout\",\"format\":\"reference\"},{\"name\":\"dialogCustomTitleDecorLayout\",\"format\":\"reference\"},{\"name\":\"dialogTitleDecorLayout\",\"format\":\"reference\"},{\"name\":\"dialogPreferredPadding\",\"format\":\"dimension\"},{\"name\":\"dialogCornerRadius\",\"format\":\"dimension\"},{\"name\":\"alertDialogTheme\",\"format\":\"reference\"},{\"name\":\"alertDialogIcon\",\"format\":\"reference\"},{\"name\":\"presentationTheme\",\"format\":\"reference\"},{\"name\":\"dividerVertical\",\"format\":\"reference\"},{\"name\":\"dividerHorizontal\",\"format\":\"reference\"},{\"name\":\"buttonBarStyle\",\"format\":\"reference\"},{\"name\":\"buttonBarButtonStyle\",\"format\":\"reference\"},{\"name\":\"buttonBarPositiveButtonStyle\",\"format\":\"reference\"},{\"name\":\"buttonBarNegativeButtonStyle\",\"format\":\"reference\"},{\"name\":\"buttonBarNeutralButtonStyle\",\"format\":\"reference\"},{\"name\":\"buttonCornerRadius\",\"format\":\"dimension\"},{\"name\":\"progressBarCornerRadius\",\"format\":\"dimension\"},{\"name\":\"searchViewStyle\",\"format\":\"reference\"},{\"name\":\"segmentedButtonStyle\",\"format\":\"reference\"},{\"name\":\"selectableItemBackground\",\"format\":\"reference\"},{\"name\":\"selectableItemBackgroundBorderless\",\"format\":\"reference\"},{\"name\":\"borderlessButtonStyle\",\"format\":\"reference\"},{\"name\":\"toastFrameBackground\",\"format\":\"reference\"},{\"name\":\"tooltipFrameBackground\",\"format\":\"reference\"},{\"name\":\"tooltipForegroundColor\",\"format\":\"reference|color\"},{\"name\":\"tooltipBackgroundColor\",\"format\":\"reference|color\"},{\"name\":\"searchDialogTheme\",\"format\":\"reference\"},{\"name\":\"homeAsUpIndicator\",\"format\":\"reference\"},{\"name\":\"preferenceFrameLayoutStyle\",\"format\":\"reference\"},{\"name\":\"switchStyle\",\"format\":\"reference\"},{\"name\":\"mediaRouteButtonStyle\",\"format\":\"reference\"},{\"name\":\"accessibilityFocusedDrawable\",\"format\":\"reference\"},{\"name\":\"findOnPageNextDrawable\",\"format\":\"reference\"},{\"name\":\"findOnPagePreviousDrawable\",\"format\":\"reference\"},{\"name\":\"colorPrimary\",\"format\":\"color\"},{\"name\":\"colorPrimaryDark\",\"format\":\"color\"},{\"name\":\"colorSecondary\",\"format\":\"color\"},{\"name\":\"colorAccent\",\"format\":\"color\"},{\"name\":\"colorAccentPrimary\",\"format\":\"color\"},{\"name\":\"colorAccentSecondary\",\"format\":\"color\"},{\"name\":\"colorAccentTertiary\",\"format\":\"color\"},{\"name\":\"colorAccentPrimaryVariant\",\"format\":\"color\"},{\"name\":\"textColorOnAccent\",\"format\":\"color\"},{\"name\":\"colorAccentSecondaryVariant\",\"format\":\"color\"},{\"name\":\"colorAccentTertiaryVariant\",\"format\":\"color\"},{\"name\":\"colorControlNormal\",\"format\":\"color\"},{\"name\":\"colorControlActivated\",\"format\":\"color\"},{\"name\":\"colorControlHighlight\",\"format\":\"color\"},{\"name\":\"colorButtonNormal\",\"format\":\"color\"},{\"name\":\"colorSwitchThumbNormal\",\"format\":\"color\"},{\"name\":\"colorProgressBackgroundNormal\",\"format\":\"color\"},{\"name\":\"colorEdgeEffect\",\"format\":\"color\"},{\"name\":\"colorSurface\",\"format\":\"color\"},{\"name\":\"colorSurfaceHighlight\",\"format\":\"color\"},{\"name\":\"colorSurfaceVariant\",\"format\":\"color\"},{\"name\":\"colorSurfaceHeader\",\"format\":\"color\"},{\"name\":\"effectColor\",\"format\":\"color\"},{\"name\":\"lightY\",\"format\":\"dimension\"},{\"name\":\"lightZ\",\"format\":\"dimension\"},{\"name\":\"lightRadius\",\"format\":\"dimension\"},{\"name\":\"ambientShadowAlpha\",\"format\":\"float\"},{\"name\":\"spotShadowAlpha\",\"format\":\"float\"},{\"name\":\"forceDarkAllowed\",\"format\":\"boolean\"}]},{\"name\":\"Window\",\"attr\":[{\"name\":\"windowBackground\"},{\"name\":\"windowBackgroundFallback\"},{\"name\":\"windowBackgroundBlurRadius\"},{\"name\":\"windowContentOverlay\"},{\"name\":\"windowFrame\"},{\"name\":\"windowNoTitle\"},{\"name\":\"windowFullscreen\"},{\"name\":\"windowOverscan\"},{\"name\":\"windowIsFloating\"},{\"name\":\"windowIsTranslucent\"},{\"name\":\"windowShowWallpaper\"},{\"name\":\"windowAnimationStyle\"},{\"name\":\"windowSoftInputMode\"},{\"name\":\"windowDisablePreview\"},{\"name\":\"windowNoDisplay\"},{\"name\":\"textColor\"},{\"name\":\"backgroundDimEnabled\"},{\"name\":\"backgroundDimAmount\"},{\"name\":\"windowBlurBehindEnabled\"},{\"name\":\"windowBlurBehindRadius\"},{\"name\":\"windowActionBar\"},{\"name\":\"windowActionModeOverlay\"},{\"name\":\"windowActionBarOverlay\"},{\"name\":\"windowEnableSplitTouch\"},{\"name\":\"windowCloseOnTouchOutside\"},{\"name\":\"windowTranslucentStatus\"},{\"name\":\"windowTranslucentNavigation\"},{\"name\":\"windowContentTransitions\"},{\"name\":\"windowActivityTransitions\"},{\"name\":\"windowContentTransitionManager\"},{\"name\":\"windowActionBarFullscreenDecorLayout\"},{\"name\":\"windowMinWidthMajor\",\"format\":\"dimension|fraction\"},{\"name\":\"windowMinWidthMinor\",\"format\":\"dimension|fraction\"},{\"name\":\"windowFixedWidthMajor\",\"format\":\"dimension|fraction\"},{\"name\":\"windowFixedHeightMinor\",\"format\":\"dimension|fraction\"},{\"name\":\"windowFixedWidthMinor\",\"format\":\"dimension|fraction\"},{\"name\":\"windowFixedHeightMajor\",\"format\":\"dimension|fraction\"},{\"name\":\"windowOutsetBottom\",\"format\":\"dimension\"},{\"name\":\"windowEnterTransition\"},{\"name\":\"windowReturnTransition\"},{\"name\":\"windowExitTransition\"},{\"name\":\"windowReenterTransition\"},{\"name\":\"windowSharedElementEnterTransition\"},{\"name\":\"windowSharedElementReturnTransition\"},{\"name\":\"windowSharedElementExitTransition\"},{\"name\":\"windowSharedElementReenterTransition\"},{\"name\":\"windowAllowEnterTransitionOverlap\"},{\"name\":\"windowAllowReturnTransitionOverlap\"},{\"name\":\"windowSharedElementsUseOverlay\"},{\"name\":\"windowDrawsSystemBarBackgrounds\",\"format\":\"boolean\"},{\"name\":\"statusBarColor\",\"format\":\"color\"},{\"name\":\"navigationBarColor\",\"format\":\"color\"},{\"name\":\"navigationBarDividerColor\",\"format\":\"color\"},{\"name\":\"enforceStatusBarContrast\",\"format\":\"boolean\"},{\"name\":\"enforceNavigationBarContrast\",\"format\":\"boolean\"},{\"name\":\"windowTransitionBackgroundFadeDuration\"},{\"name\":\"windowElevation\",\"format\":\"dimension\"},{\"name\":\"windowClipToOutline\",\"format\":\"boolean\"},{\"name\":\"windowLightStatusBar\",\"format\":\"boolean\"},{\"name\":\"windowSplashscreenContent\",\"format\":\"reference\"},{\"name\":\"windowLightNavigationBar\",\"format\":\"boolean\"},{\"name\":\"windowLayoutInDisplayCutoutMode\",\"enum\":[{\"name\":\"default\",\"value\":\"0\"},{\"name\":\"shortEdges\",\"value\":\"1\"},{\"name\":\"never\",\"value\":\"2\"},{\"name\":\"always\",\"value\":\"3\"}]},{\"name\":\"windowSplashScreenBackground\",\"format\":\"color\"},{\"name\":\"windowSplashScreenAnimatedIcon\",\"format\":\"reference\"},{\"name\":\"windowSplashScreenAnimationDuration\",\"format\":\"integer\"},{\"name\":\"windowSplashScreenBrandingImage\",\"format\":\"reference\"},{\"name\":\"windowSplashScreenIconBackgroundColor\",\"format\":\"color\"}]},{\"name\":\"AlertDialog\",\"attr\":[{\"name\":\"fullDark\",\"format\":\"reference|color\"},{\"name\":\"topDark\",\"format\":\"reference|color\"},{\"name\":\"centerDark\",\"format\":\"reference|color\"},{\"name\":\"bottomDark\",\"format\":\"reference|color\"},{\"name\":\"fullBright\",\"format\":\"reference|color\"},{\"name\":\"topBright\",\"format\":\"reference|color\"},{\"name\":\"centerBright\",\"format\":\"reference|color\"},{\"name\":\"bottomBright\",\"format\":\"reference|color\"},{\"name\":\"bottomMedium\",\"format\":\"reference|color\"},{\"name\":\"centerMedium\",\"format\":\"reference|color\"},{\"name\":\"layout\"},{\"name\":\"buttonPanelSideLayout\",\"format\":\"reference\"},{\"name\":\"listLayout\",\"format\":\"reference\"},{\"name\":\"multiChoiceItemLayout\",\"format\":\"reference\"},{\"name\":\"singleChoiceItemLayout\",\"format\":\"reference\"},{\"name\":\"listItemLayout\",\"format\":\"reference\"},{\"name\":\"progressLayout\",\"format\":\"reference\"},{\"name\":\"horizontalProgressLayout\",\"format\":\"reference\"},{\"name\":\"showTitle\",\"format\":\"boolean\"},{\"name\":\"needsDefaultBackgrounds\",\"format\":\"boolean\"},{\"name\":\"controllerType\",\"enum\":[{\"name\":\"normal\",\"value\":\"0\"},{\"name\":\"micro\",\"value\":\"1\"}]},{\"name\":\"selectionScrollOffset\",\"format\":\"dimension\"}]},{\"name\":\"ButtonBarLayout\",\"attr\":{\"name\":\"allowStacking\",\"format\":\"boolean\"}},{\"name\":\"FragmentAnimation\",\"attr\":[{\"name\":\"fragmentOpenEnterAnimation\",\"format\":\"reference\"},{\"name\":\"fragmentOpenExitAnimation\",\"format\":\"reference\"},{\"name\":\"fragmentCloseEnterAnimation\",\"format\":\"reference\"},{\"name\":\"fragmentCloseExitAnimation\",\"format\":\"reference\"},{\"name\":\"fragmentFadeEnterAnimation\",\"format\":\"reference\"},{\"name\":\"fragmentFadeExitAnimation\",\"format\":\"reference\"}]},{\"name\":\"WindowAnimation\",\"attr\":[{\"name\":\"windowEnterAnimation\",\"format\":\"reference\"},{\"name\":\"windowExitAnimation\",\"format\":\"reference\"},{\"name\":\"windowShowAnimation\",\"format\":\"reference\"},{\"name\":\"windowHideAnimation\",\"format\":\"reference\"},{\"name\":\"activityOpenEnterAnimation\",\"format\":\"reference\"},{\"name\":\"activityOpenExitAnimation\",\"format\":\"reference\"},{\"name\":\"activityCloseEnterAnimation\",\"format\":\"reference\"},{\"name\":\"activityCloseExitAnimation\",\"format\":\"reference\"},{\"name\":\"taskOpenEnterAnimation\",\"format\":\"reference\"},{\"name\":\"taskOpenExitAnimation\",\"format\":\"reference\"},{\"name\":\"launchTaskBehindTargetAnimation\",\"format\":\"reference\"},{\"name\":\"launchTaskBehindSourceAnimation\",\"format\":\"reference\"},{\"name\":\"taskCloseEnterAnimation\",\"format\":\"reference\"},{\"name\":\"taskCloseExitAnimation\",\"format\":\"reference\"},{\"name\":\"taskToFrontEnterAnimation\",\"format\":\"reference\"},{\"name\":\"taskToFrontExitAnimation\",\"format\":\"reference\"},{\"name\":\"taskToBackEnterAnimation\",\"format\":\"reference\"},{\"name\":\"taskToBackExitAnimation\",\"format\":\"reference\"},{\"name\":\"wallpaperOpenEnterAnimation\",\"format\":\"reference\"},{\"name\":\"wallpaperOpenExitAnimation\",\"format\":\"reference\"},{\"name\":\"wallpaperCloseEnterAnimation\",\"format\":\"reference\"},{\"name\":\"wallpaperCloseExitAnimation\",\"format\":\"reference\"},{\"name\":\"wallpaperIntraOpenEnterAnimation\",\"format\":\"reference\"},{\"name\":\"wallpaperIntraOpenExitAnimation\",\"format\":\"reference\"},{\"name\":\"wallpaperIntraCloseEnterAnimation\",\"format\":\"reference\"},{\"name\":\"wallpaperIntraCloseExitAnimation\",\"format\":\"reference\"},{\"name\":\"activityOpenRemoteViewsEnterAnimation\",\"format\":\"reference\"}]},{\"name\":\"View\",\"attr\":[{\"name\":\"id\",\"format\":\"reference\"},{\"name\":\"tag\",\"format\":\"string\"},{\"name\":\"scrollX\",\"format\":\"dimension\"},{\"name\":\"scrollY\",\"format\":\"dimension\"},{\"name\":\"background\",\"format\":\"reference|color\"},{\"name\":\"padding\",\"format\":\"dimension\"},{\"name\":\"paddingHorizontal\",\"format\":\"dimension\"},{\"name\":\"paddingVertical\",\"format\":\"dimension\"},{\"name\":\"paddingLeft\",\"format\":\"dimension\"},{\"name\":\"paddingTop\",\"format\":\"dimension\"},{\"name\":\"paddingRight\",\"format\":\"dimension\"},{\"name\":\"paddingBottom\",\"format\":\"dimension\"},{\"name\":\"paddingStart\",\"format\":\"dimension\"},{\"name\":\"paddingEnd\",\"format\":\"dimension\"},{\"name\":\"focusable\",\"format\":\"boolean|enum\",\"enum\":{\"name\":\"auto\",\"value\":\"0x00000010\"}},{\"name\":\"__removed3\"},{\"name\":\"__removed4\"},{\"name\":\"__removed5\"},{\"name\":\"autofillHints\",\"format\":\"string|reference\"},{\"name\":\"importantForAutofill\",\"flag\":[{\"name\":\"auto\",\"value\":\"0\"},{\"name\":\"yes\",\"value\":\"0x1\"},{\"name\":\"no\",\"value\":\"0x2\"},{\"name\":\"yesExcludeDescendants\",\"value\":\"0x4\"},{\"name\":\"noExcludeDescendants\",\"value\":\"0x8\"}]},{\"name\":\"importantForContentCapture\",\"flag\":[{\"name\":\"auto\",\"value\":\"0\"},{\"name\":\"yes\",\"value\":\"0x1\"},{\"name\":\"no\",\"value\":\"0x2\"},{\"name\":\"yesExcludeDescendants\",\"value\":\"0x4\"},{\"name\":\"noExcludeDescendants\",\"value\":\"0x8\"}]},{\"name\":\"scrollCaptureHint\",\"flag\":[{\"name\":\"auto\",\"value\":\"0\"},{\"name\":\"include\",\"value\":\"0x1\"},{\"name\":\"exclude\",\"value\":\"0x2\"},{\"name\":\"excludeDescendants\",\"value\":\"0x4\"}]},{\"name\":\"focusableInTouchMode\",\"format\":\"boolean\"},{\"name\":\"visibility\",\"enum\":[{\"name\":\"visible\",\"value\":\"0\"},{\"name\":\"invisible\",\"value\":\"1\"},{\"name\":\"gone\",\"value\":\"2\"}]},{\"name\":\"fitsSystemWindows\",\"format\":\"boolean\"},{\"name\":\"scrollbars\",\"flag\":[{\"name\":\"none\",\"value\":\"0x00000000\"},{\"name\":\"horizontal\",\"value\":\"0x00000100\"},{\"name\":\"vertical\",\"value\":\"0x00000200\"}]},{\"name\":\"scrollbarStyle\",\"enum\":[{\"name\":\"insideOverlay\",\"value\":\"0x0\"},{\"name\":\"insideInset\",\"value\":\"0x01000000\"},{\"name\":\"outsideOverlay\",\"value\":\"0x02000000\"},{\"name\":\"outsideInset\",\"value\":\"0x03000000\"}]},{\"name\":\"isScrollContainer\",\"format\":\"boolean\"},{\"name\":\"fadeScrollbars\",\"format\":\"boolean\"},{\"name\":\"scrollbarFadeDuration\",\"format\":\"integer\"},{\"name\":\"scrollbarDefaultDelayBeforeFade\",\"format\":\"integer\"},{\"name\":\"scrollbarSize\",\"format\":\"dimension\"},{\"name\":\"scrollbarThumbHorizontal\",\"format\":\"reference\"},{\"name\":\"scrollbarThumbVertical\",\"format\":\"reference\"},{\"name\":\"scrollbarTrackHorizontal\",\"format\":\"reference\"},{\"name\":\"scrollbarTrackVertical\",\"format\":\"reference\"},{\"name\":\"scrollbarAlwaysDrawHorizontalTrack\",\"format\":\"boolean\"},{\"name\":\"scrollbarAlwaysDrawVerticalTrack\",\"format\":\"boolean\"},{\"name\":\"fadingEdge\",\"flag\":[{\"name\":\"none\",\"value\":\"0x00000000\"},{\"name\":\"horizontal\",\"value\":\"0x00001000\"},{\"name\":\"vertical\",\"value\":\"0x00002000\"}]},{\"name\":\"requiresFadingEdge\",\"flag\":[{\"name\":\"none\",\"value\":\"0x00000000\"},{\"name\":\"horizontal\",\"value\":\"0x00001000\"},{\"name\":\"vertical\",\"value\":\"0x00002000\"}]},{\"name\":\"fadingEdgeLength\",\"format\":\"dimension\"},{\"name\":\"nextFocusLeft\",\"format\":\"reference\"},{\"name\":\"nextFocusRight\",\"format\":\"reference\"},{\"name\":\"nextFocusUp\",\"format\":\"reference\"},{\"name\":\"nextFocusDown\",\"format\":\"reference\"},{\"name\":\"nextFocusForward\",\"format\":\"reference\"},{\"name\":\"clickable\",\"format\":\"boolean\"},{\"name\":\"longClickable\",\"format\":\"boolean\"},{\"name\":\"contextClickable\",\"format\":\"boolean\"},{\"name\":\"saveEnabled\",\"format\":\"boolean\"},{\"name\":\"filterTouchesWhenObscured\",\"format\":\"boolean\"},{\"name\":\"drawingCacheQuality\",\"enum\":[{\"name\":\"auto\",\"value\":\"0\"},{\"name\":\"low\",\"value\":\"1\"},{\"name\":\"high\",\"value\":\"2\"}]},{\"name\":\"keepScreenOn\",\"format\":\"boolean\"},{\"name\":\"duplicateParentState\",\"format\":\"boolean\"},{\"name\":\"minHeight\"},{\"name\":\"minWidth\"},{\"name\":\"soundEffectsEnabled\",\"format\":\"boolean\"},{\"name\":\"hapticFeedbackEnabled\",\"format\":\"boolean\"},{\"name\":\"contentDescription\",\"format\":\"string\",\"localization\":\"suggested\"},{\"name\":\"accessibilityTraversalBefore\",\"format\":\"integer\"},{\"name\":\"accessibilityTraversalAfter\",\"format\":\"integer\"},{\"name\":\"onClick\",\"format\":\"string\"},{\"name\":\"overScrollMode\",\"enum\":[{\"name\":\"always\",\"value\":\"0\"},{\"name\":\"ifContentScrolls\",\"value\":\"1\"},{\"name\":\"never\",\"value\":\"2\"}]},{\"name\":\"alpha\",\"format\":\"float\"},{\"name\":\"elevation\",\"format\":\"dimension\"},{\"name\":\"translationX\",\"format\":\"dimension\"},{\"name\":\"translationY\",\"format\":\"dimension\"},{\"name\":\"translationZ\",\"format\":\"dimension\"},{\"name\":\"transformPivotX\",\"format\":\"dimension\"},{\"name\":\"transformPivotY\",\"format\":\"dimension\"},{\"name\":\"rotation\",\"format\":\"float\"},{\"name\":\"rotationX\",\"format\":\"float\"},{\"name\":\"rotationY\",\"format\":\"float\"},{\"name\":\"scaleX\",\"format\":\"float\"},{\"name\":\"scaleY\",\"format\":\"float\"},{\"name\":\"verticalScrollbarPosition\",\"enum\":[{\"name\":\"defaultPosition\",\"value\":\"0\"},{\"name\":\"left\",\"value\":\"1\"},{\"name\":\"right\",\"value\":\"2\"}]},{\"name\":\"layerType\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"software\",\"value\":\"1\"},{\"name\":\"hardware\",\"value\":\"2\"}]},{\"name\":\"layoutDirection\",\"enum\":[{\"name\":\"ltr\",\"value\":\"0\"},{\"name\":\"rtl\",\"value\":\"1\"},{\"name\":\"inherit\",\"value\":\"2\"},{\"name\":\"locale\",\"value\":\"3\"}]},{\"name\":\"textDirection\",\"format\":\"integer\",\"enum\":[{\"name\":\"inherit\",\"value\":\"0\"},{\"name\":\"firstStrong\",\"value\":\"1\"},{\"name\":\"anyRtl\",\"value\":\"2\"},{\"name\":\"ltr\",\"value\":\"3\"},{\"name\":\"rtl\",\"value\":\"4\"},{\"name\":\"locale\",\"value\":\"5\"},{\"name\":\"firstStrongLtr\",\"value\":\"6\"},{\"name\":\"firstStrongRtl\",\"value\":\"7\"}]},{\"name\":\"textAlignment\",\"format\":\"integer\",\"enum\":[{\"name\":\"inherit\",\"value\":\"0\"},{\"name\":\"gravity\",\"value\":\"1\"},{\"name\":\"textStart\",\"value\":\"2\"},{\"name\":\"textEnd\",\"value\":\"3\"},{\"name\":\"center\",\"value\":\"4\"},{\"name\":\"viewStart\",\"value\":\"5\"},{\"name\":\"viewEnd\",\"value\":\"6\"}]},{\"name\":\"importantForAccessibility\",\"format\":\"integer\",\"enum\":[{\"name\":\"auto\",\"value\":\"0\"},{\"name\":\"yes\",\"value\":\"1\"},{\"name\":\"no\",\"value\":\"2\"},{\"name\":\"noHideDescendants\",\"value\":\"4\"}]},{\"name\":\"accessibilityLiveRegion\",\"format\":\"integer\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"polite\",\"value\":\"1\"},{\"name\":\"assertive\",\"value\":\"2\"}]},{\"name\":\"labelFor\",\"format\":\"reference\"},{\"name\":\"theme\"},{\"name\":\"transitionName\",\"format\":\"string\"},{\"name\":\"nestedScrollingEnabled\",\"format\":\"boolean\"},{\"name\":\"stateListAnimator\",\"format\":\"reference\"},{\"name\":\"backgroundTint\",\"format\":\"color\"},{\"name\":\"backgroundTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"outlineProvider\",\"enum\":[{\"name\":\"background\",\"value\":\"0\"},{\"name\":\"none\",\"value\":\"1\"},{\"name\":\"bounds\",\"value\":\"2\"},{\"name\":\"paddedBounds\",\"value\":\"3\"}]},{\"name\":\"foreground\",\"format\":\"reference|color\"},{\"name\":\"foregroundGravity\",\"flag\":[{\"name\":\"top\",\"value\":\"0x30\"},{\"name\":\"bottom\",\"value\":\"0x50\"},{\"name\":\"left\",\"value\":\"0x03\"},{\"name\":\"right\",\"value\":\"0x05\"},{\"name\":\"center_vertical\",\"value\":\"0x10\"},{\"name\":\"fill_vertical\",\"value\":\"0x70\"},{\"name\":\"center_horizontal\",\"value\":\"0x01\"},{\"name\":\"fill_horizontal\",\"value\":\"0x07\"},{\"name\":\"center\",\"value\":\"0x11\"},{\"name\":\"fill\",\"value\":\"0x77\"},{\"name\":\"clip_vertical\",\"value\":\"0x80\"},{\"name\":\"clip_horizontal\",\"value\":\"0x08\"}]},{\"name\":\"foregroundInsidePadding\",\"format\":\"boolean\"},{\"name\":\"foregroundTint\",\"format\":\"color\"},{\"name\":\"foregroundTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"scrollIndicators\",\"flag\":[{\"name\":\"none\",\"value\":\"0x00\"},{\"name\":\"top\",\"value\":\"0x01\"},{\"name\":\"bottom\",\"value\":\"0x02\"},{\"name\":\"left\",\"value\":\"0x04\"},{\"name\":\"right\",\"value\":\"0x08\"},{\"name\":\"start\",\"value\":\"0x10\"},{\"name\":\"end\",\"value\":\"0x20\"}]},{\"name\":\"pointerIcon\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"arrow\",\"value\":\"1000\"},{\"name\":\"context_menu\",\"value\":\"1001\"},{\"name\":\"hand\",\"value\":\"1002\"},{\"name\":\"help\",\"value\":\"1003\"},{\"name\":\"wait\",\"value\":\"1004\"},{\"name\":\"cell\",\"value\":\"1006\"},{\"name\":\"crosshair\",\"value\":\"1007\"},{\"name\":\"text\",\"value\":\"1008\"},{\"name\":\"vertical_text\",\"value\":\"1009\"},{\"name\":\"alias\",\"value\":\"1010\"},{\"name\":\"copy\",\"value\":\"1011\"},{\"name\":\"no_drop\",\"value\":\"1012\"},{\"name\":\"all_scroll\",\"value\":\"1013\"},{\"name\":\"horizontal_double_arrow\",\"value\":\"1014\"},{\"name\":\"vertical_double_arrow\",\"value\":\"1015\"},{\"name\":\"top_right_diagonal_double_arrow\",\"value\":\"1016\"},{\"name\":\"top_left_diagonal_double_arrow\",\"value\":\"1017\"},{\"name\":\"zoom_in\",\"value\":\"1018\"},{\"name\":\"zoom_out\",\"value\":\"1019\"},{\"name\":\"grab\",\"value\":\"1020\"},{\"name\":\"grabbing\",\"value\":\"1021\"}]},{\"name\":\"forceHasOverlappingRendering\",\"format\":\"boolean\"},{\"name\":\"tooltipText\",\"format\":\"string\",\"localization\":\"suggested\"},{\"name\":\"keyboardNavigationCluster\",\"format\":\"boolean\"},{\"name\":\"__removed0\",\"format\":\"boolean\"},{\"name\":\"nextClusterForward\",\"format\":\"reference\"},{\"name\":\"__removed1\",\"format\":\"reference\"},{\"name\":\"focusedByDefault\",\"format\":\"boolean\"},{\"name\":\"defaultFocusHighlightEnabled\",\"format\":\"boolean\"},{\"name\":\"screenReaderFocusable\",\"format\":\"boolean\"},{\"name\":\"accessibilityPaneTitle\",\"format\":\"string\"},{\"name\":\"accessibilityHeading\",\"format\":\"boolean\"},{\"name\":\"allowClickWhenDisabled\",\"format\":\"boolean\"},{\"name\":\"outlineSpotShadowColor\",\"format\":\"color\"},{\"name\":\"outlineAmbientShadowColor\",\"format\":\"color\"},{\"name\":\"forceDarkAllowed\",\"format\":\"boolean\"},{\"name\":\"clipToOutline\",\"format\":\"boolean\"}]},{\"name\":\"ViewTag\",\"attr\":[{\"name\":\"id\"},{\"name\":\"value\"}]},{\"name\":\"Include\",\"attr\":[{\"name\":\"id\"},{\"name\":\"visibility\"}]},{\"name\":\"ViewGroup\",\"attr\":[{\"name\":\"animateLayoutChanges\",\"format\":\"boolean\"},{\"name\":\"clipChildren\",\"format\":\"boolean\"},{\"name\":\"clipToPadding\",\"format\":\"boolean\"},{\"name\":\"layoutAnimation\",\"format\":\"reference\"},{\"name\":\"animationCache\",\"format\":\"boolean\"},{\"name\":\"persistentDrawingCache\",\"flag\":[{\"name\":\"none\",\"value\":\"0x0\"},{\"name\":\"animation\",\"value\":\"0x1\"},{\"name\":\"scrolling\",\"value\":\"0x2\"},{\"name\":\"all\",\"value\":\"0x3\"}]},{\"name\":\"alwaysDrawnWithCache\",\"format\":\"boolean\"},{\"name\":\"addStatesFromChildren\",\"format\":\"boolean\"},{\"name\":\"descendantFocusability\",\"enum\":[{\"name\":\"beforeDescendants\",\"value\":\"0\"},{\"name\":\"afterDescendants\",\"value\":\"1\"},{\"name\":\"blocksDescendants\",\"value\":\"2\"}]},{\"name\":\"touchscreenBlocksFocus\",\"format\":\"boolean\"},{\"name\":\"splitMotionEvents\",\"format\":\"boolean\"},{\"name\":\"layoutMode\",\"enum\":[{\"name\":\"clipBounds\",\"value\":\"0\"},{\"name\":\"opticalBounds\",\"value\":\"1\"}]},{\"name\":\"transitionGroup\",\"format\":\"boolean\"}]},{\"name\":\"ViewStub\",\"attr\":[{\"name\":\"id\"},{\"name\":\"layout\",\"format\":\"reference\"},{\"name\":\"inflatedId\",\"format\":\"reference\"}]},{\"name\":\"ViewGroup_Layout\",\"attr\":[{\"name\":\"layout_width\",\"format\":\"dimension\",\"enum\":[{\"name\":\"fill_parent\",\"value\":\"-1\"},{\"name\":\"match_parent\",\"value\":\"-1\"},{\"name\":\"wrap_content\",\"value\":\"-2\"}]},{\"name\":\"layout_height\",\"format\":\"dimension\",\"enum\":[{\"name\":\"fill_parent\",\"value\":\"-1\"},{\"name\":\"match_parent\",\"value\":\"-1\"},{\"name\":\"wrap_content\",\"value\":\"-2\"}]}]},{\"name\":\"ViewGroup_MarginLayout\",\"attr\":[{\"name\":\"layout_width\"},{\"name\":\"layout_height\"},{\"name\":\"layout_margin\",\"format\":\"dimension\"},{\"name\":\"layout_marginLeft\",\"format\":\"dimension\"},{\"name\":\"layout_marginTop\",\"format\":\"dimension\"},{\"name\":\"layout_marginRight\",\"format\":\"dimension\"},{\"name\":\"layout_marginBottom\",\"format\":\"dimension\"},{\"name\":\"layout_marginStart\",\"format\":\"dimension\"},{\"name\":\"layout_marginEnd\",\"format\":\"dimension\"},{\"name\":\"layout_marginHorizontal\",\"format\":\"dimension\"},{\"name\":\"layout_marginVertical\",\"format\":\"dimension\"}]},{\"name\":\"InputMethod\",\"attr\":[{\"name\":\"settingsActivity\",\"format\":\"string\"},{\"name\":\"isDefault\",\"format\":\"boolean\"},{\"name\":\"supportsSwitchingToNextInputMethod\",\"format\":\"boolean\"},{\"name\":\"isVrOnly\",\"format\":\"boolean\"},{\"name\":\"__removed2\",\"format\":\"boolean\"},{\"name\":\"supportsInlineSuggestions\",\"format\":\"boolean\"},{\"name\":\"suppressesSpellChecker\",\"format\":\"boolean\"},{\"name\":\"showInInputMethodPicker\",\"format\":\"boolean\"},{\"name\":\"configChanges\"}]},{\"name\":\"InputMethod_Subtype\",\"attr\":[{\"name\":\"label\"},{\"name\":\"icon\"},{\"name\":\"imeSubtypeLocale\",\"format\":\"string\"},{\"name\":\"imeSubtypeMode\",\"format\":\"string\"},{\"name\":\"isAuxiliary\",\"format\":\"boolean\"},{\"name\":\"overridesImplicitlyEnabledSubtype\",\"format\":\"boolean\"},{\"name\":\"imeSubtypeExtraValue\",\"format\":\"string\"},{\"name\":\"subtypeId\",\"format\":\"integer\"},{\"name\":\"isAsciiCapable\",\"format\":\"boolean\"},{\"name\":\"languageTag\",\"format\":\"string\"}]},{\"name\":\"SpellChecker\",\"attr\":[{\"name\":\"label\"},{\"name\":\"settingsActivity\"}]},{\"name\":\"SpellChecker_Subtype\",\"attr\":[{\"name\":\"label\"},{\"name\":\"subtypeLocale\",\"format\":\"string\"},{\"name\":\"subtypeExtraValue\",\"format\":\"string\"},{\"name\":\"subtypeId\"},{\"name\":\"languageTag\"}]},{\"name\":\"AccessibilityService\",\"attr\":[{\"name\":\"accessibilityEventTypes\",\"flag\":[{\"name\":\"typeViewClicked\",\"value\":\"0x00000001\"},{\"name\":\"typeViewLongClicked\",\"value\":\"0x00000002\"},{\"name\":\"typeViewSelected\",\"value\":\"0x00000004\"},{\"name\":\"typeViewFocused\",\"value\":\"0x00000008\"},{\"name\":\"typeViewTextChanged\",\"value\":\"0x00000010\"},{\"name\":\"typeWindowStateChanged\",\"value\":\"0x00000020\"},{\"name\":\"typeNotificationStateChanged\",\"value\":\"0x00000040\"},{\"name\":\"typeViewHoverEnter\",\"value\":\"0x00000080\"},{\"name\":\"typeViewHoverExit\",\"value\":\"0x00000100\"},{\"name\":\"typeTouchExplorationGestureStart\",\"value\":\"0x00000200\"},{\"name\":\"typeTouchExplorationGestureEnd\",\"value\":\"0x00000400\"},{\"name\":\"typeWindowContentChanged\",\"value\":\"0x00000800\"},{\"name\":\"typeViewScrolled\",\"value\":\"0x000001000\"},{\"name\":\"typeViewTextSelectionChanged\",\"value\":\"0x000002000\"},{\"name\":\"typeAnnouncement\",\"value\":\"0x00004000\"},{\"name\":\"typeViewAccessibilityFocused\",\"value\":\"0x00008000\"},{\"name\":\"typeViewAccessibilityFocusCleared\",\"value\":\"0x00010000\"},{\"name\":\"typeViewTextTraversedAtMovementGranularity\",\"value\":\"0x00020000\"},{\"name\":\"typeGestureDetectionStart\",\"value\":\"0x00040000\"},{\"name\":\"typeGestureDetectionEnd\",\"value\":\"0x00080000\"},{\"name\":\"typeTouchInteractionStart\",\"value\":\"0x00100000\"},{\"name\":\"typeTouchInteractionEnd\",\"value\":\"0x00200000\"},{\"name\":\"typeWindowsChanged\",\"value\":\"0x00400000\"},{\"name\":\"typeContextClicked\",\"value\":\"0x00800000\"},{\"name\":\"typeAssistReadingContext\",\"value\":\"0x01000000\"},{\"name\":\"typeAllMask\",\"value\":\"0xffffffff\"}]},{\"name\":\"packageNames\",\"format\":\"string\"},{\"name\":\"accessibilityFeedbackType\",\"flag\":[{\"name\":\"feedbackSpoken\",\"value\":\"0x00000001\"},{\"name\":\"feedbackHaptic\",\"value\":\"0x00000002\"},{\"name\":\"feedbackAudible\",\"value\":\"0x00000004\"},{\"name\":\"feedbackVisual\",\"value\":\"0x00000008\"},{\"name\":\"feedbackGeneric\",\"value\":\"0x00000010\"},{\"name\":\"feedbackAllMask\",\"value\":\"0xffffffff\"}]},{\"name\":\"notificationTimeout\",\"format\":\"integer\"},{\"name\":\"nonInteractiveUiTimeout\",\"format\":\"integer\"},{\"name\":\"interactiveUiTimeout\",\"format\":\"integer\"},{\"name\":\"accessibilityFlags\",\"flag\":[{\"name\":\"flagDefault\",\"value\":\"0x00000001\"},{\"name\":\"flagIncludeNotImportantViews\",\"value\":\"0x00000002\"},{\"name\":\"flagRequestTouchExplorationMode\",\"value\":\"0x00000004\"},{\"name\":\"flagRequestEnhancedWebAccessibility\",\"value\":\"0x00000008\"},{\"name\":\"flagReportViewIds\",\"value\":\"0x00000010\"},{\"name\":\"flagRequestFilterKeyEvents\",\"value\":\"0x00000020\"},{\"name\":\"flagRetrieveInteractiveWindows\",\"value\":\"0x00000040\"},{\"name\":\"flagEnableAccessibilityVolume\",\"value\":\"0x00000080\"},{\"name\":\"flagRequestAccessibilityButton\",\"value\":\"0x00000100\"},{\"name\":\"flagRequestFingerprintGestures\",\"value\":\"0x00000200\"},{\"name\":\"flagRequestShortcutWarningDialogSpokenFeedback\",\"value\":\"0x00000400\"},{\"name\":\"flagServiceHandlesDoubleTap\",\"value\":\"0x00000800\"},{\"name\":\"flagRequestMultiFingerGestures\",\"value\":\"0x00001000\"},{\"name\":\"flagSendMotionEvents\",\"value\":\"0x0004000\"}]},{\"name\":\"settingsActivity\"},{\"name\":\"canRetrieveWindowContent\",\"format\":\"boolean\"},{\"name\":\"canRequestTouchExplorationMode\",\"format\":\"boolean\"},{\"name\":\"canRequestEnhancedWebAccessibility\",\"format\":\"boolean\"},{\"name\":\"canRequestFilterKeyEvents\",\"format\":\"boolean\"},{\"name\":\"canControlMagnification\",\"format\":\"boolean\"},{\"name\":\"canPerformGestures\",\"format\":\"boolean\"},{\"name\":\"canRequestFingerprintGestures\",\"format\":\"boolean\"},{\"name\":\"canTakeScreenshot\",\"format\":\"boolean\"},{\"name\":\"isAccessibilityTool\",\"format\":\"boolean\"},{\"name\":\"animatedImageDrawable\",\"format\":\"reference\"},{\"name\":\"htmlDescription\",\"format\":\"reference\"},{\"name\":\"description\"},{\"name\":\"summary\"}]},{\"name\":\"AccessibilityShortcutTarget\",\"attr\":[{\"name\":\"description\"},{\"name\":\"summary\"},{\"name\":\"animatedImageDrawable\",\"format\":\"reference\"},{\"name\":\"htmlDescription\",\"format\":\"reference\"},{\"name\":\"settingsActivity\"}]},{\"name\":\"PrintService\",\"attr\":[{\"name\":\"settingsActivity\"},{\"name\":\"addPrintersActivity\",\"format\":\"string\"},{\"name\":\"advancedPrintOptionsActivity\",\"format\":\"string\"},{\"name\":\"vendor\",\"format\":\"string\"}]},{\"name\":\"HostApduService\",\"attr\":[{\"name\":\"description\"},{\"name\":\"requireDeviceUnlock\",\"format\":\"boolean\"},{\"name\":\"apduServiceBanner\",\"format\":\"reference\"},{\"name\":\"settingsActivity\"},{\"name\":\"requireDeviceScreenOn\",\"format\":\"boolean\"}]},{\"name\":\"OffHostApduService\",\"attr\":[{\"name\":\"description\"},{\"name\":\"apduServiceBanner\"},{\"name\":\"settingsActivity\"},{\"name\":\"secureElementName\",\"format\":\"string\"},{\"name\":\"requireDeviceUnlock\"},{\"name\":\"requireDeviceScreenOn\"}]},{\"name\":\"AidGroup\",\"attr\":[{\"name\":\"description\"},{\"name\":\"category\",\"format\":\"string\"}]},{\"name\":\"AidFilter\",\"attr\":{\"name\":\"name\"}},{\"name\":\"AidPrefixFilter\",\"attr\":{\"name\":\"name\"}},{\"name\":\"HostNfcFService\",\"attr\":{\"name\":\"description\"}},{\"name\":\"SystemCodeFilter\",\"attr\":{\"name\":\"name\"}},{\"name\":\"Nfcid2Filter\",\"attr\":{\"name\":\"name\"}},{\"name\":\"T3tPmmFilter\",\"attr\":{\"name\":\"name\"}},{\"name\":\"ActionMenuItemView\",\"attr\":{\"name\":\"minWidth\"}},{\"name\":\"AbsListView\",\"attr\":[{\"name\":\"listSelector\",\"format\":\"color|reference\"},{\"name\":\"drawSelectorOnTop\",\"format\":\"boolean\"},{\"name\":\"stackFromBottom\",\"format\":\"boolean\"},{\"name\":\"scrollingCache\",\"format\":\"boolean\"},{\"name\":\"textFilterEnabled\",\"format\":\"boolean\"},{\"name\":\"transcriptMode\",\"enum\":[{\"name\":\"disabled\",\"value\":\"0\"},{\"name\":\"normal\",\"value\":\"1\"},{\"name\":\"alwaysScroll\",\"value\":\"2\"}]},{\"name\":\"cacheColorHint\",\"format\":\"color\"},{\"name\":\"fastScrollEnabled\",\"format\":\"boolean\"},{\"name\":\"fastScrollStyle\",\"format\":\"reference\"},{\"name\":\"smoothScrollbar\",\"format\":\"boolean\"},{\"name\":\"choiceMode\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"singleChoice\",\"value\":\"1\"},{\"name\":\"multipleChoice\",\"value\":\"2\"},{\"name\":\"multipleChoiceModal\",\"value\":\"3\"}]},{\"name\":\"fastScrollAlwaysVisible\",\"format\":\"boolean\"}]},{\"name\":\"RecycleListView\",\"attr\":[{\"name\":\"paddingBottomNoButtons\",\"format\":\"dimension\"},{\"name\":\"paddingTopNoTitle\",\"format\":\"dimension\"}]},{\"name\":\"AbsSpinner\",\"attr\":{\"name\":\"entries\"}},{\"name\":\"AnalogClock\",\"attr\":[{\"name\":\"dial\",\"format\":\"reference\"},{\"name\":\"hand_hour\",\"format\":\"reference\"},{\"name\":\"hand_minute\",\"format\":\"reference\"},{\"name\":\"hand_second\",\"format\":\"reference\"},{\"name\":\"timeZone\",\"format\":\"string\"},{\"name\":\"dialTint\",\"format\":\"color\"},{\"name\":\"dialTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"hand_hourTint\",\"format\":\"color\"},{\"name\":\"hand_hourTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"hand_minuteTint\",\"format\":\"color\"},{\"name\":\"hand_minuteTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"hand_secondTint\",\"format\":\"color\"},{\"name\":\"hand_secondTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]}]},{\"name\":\"Button\"},{\"name\":\"Chronometer\",\"attr\":[{\"name\":\"format\",\"format\":\"string\",\"localization\":\"suggested\"},{\"name\":\"countDown\",\"format\":\"boolean\"}]},{\"name\":\"CompoundButton\",\"attr\":[{\"name\":\"checked\",\"format\":\"boolean\"},{\"name\":\"button\",\"format\":\"reference\"},{\"name\":\"buttonTint\",\"format\":\"color\"},{\"name\":\"buttonTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]}]},{\"name\":\"CheckedTextView\",\"attr\":[{\"name\":\"checked\"},{\"name\":\"checkMark\",\"format\":\"reference\"},{\"name\":\"checkMarkTint\",\"format\":\"color\"},{\"name\":\"checkMarkTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"checkMarkGravity\",\"flag\":[{\"name\":\"left\",\"value\":\"0x03\"},{\"name\":\"right\",\"value\":\"0x05\"},{\"name\":\"start\",\"value\":\"0x00800003\"},{\"name\":\"end\",\"value\":\"0x00800005\"}]}]},{\"name\":\"EditText\"},{\"name\":\"FastScroll\",\"attr\":[{\"name\":\"thumbDrawable\",\"format\":\"reference\"},{\"name\":\"thumbMinWidth\",\"format\":\"dimension\"},{\"name\":\"thumbMinHeight\",\"format\":\"dimension\"},{\"name\":\"trackDrawable\",\"format\":\"reference\"},{\"name\":\"backgroundRight\",\"format\":\"reference\"},{\"name\":\"backgroundLeft\",\"format\":\"reference\"},{\"name\":\"position\",\"enum\":[{\"name\":\"floating\",\"value\":\"0\"},{\"name\":\"atThumb\",\"value\":\"1\"},{\"name\":\"aboveThumb\",\"value\":\"2\"}]},{\"name\":\"textAppearance\"},{\"name\":\"textColor\"},{\"name\":\"textSize\"},{\"name\":\"minWidth\"},{\"name\":\"minHeight\"},{\"name\":\"padding\"},{\"name\":\"thumbPosition\",\"enum\":[{\"name\":\"midpoint\",\"value\":\"0\"},{\"name\":\"inside\",\"value\":\"1\"}]}]},{\"name\":\"FrameLayout\",\"attr\":{\"name\":\"measureAllChildren\",\"format\":\"boolean\"}},{\"name\":\"ExpandableListView\",\"attr\":[{\"name\":\"groupIndicator\",\"format\":\"reference\"},{\"name\":\"childIndicator\",\"format\":\"reference\"},{\"name\":\"indicatorLeft\",\"format\":\"dimension\"},{\"name\":\"indicatorRight\",\"format\":\"dimension\"},{\"name\":\"childIndicatorLeft\",\"format\":\"dimension\"},{\"name\":\"childIndicatorRight\",\"format\":\"dimension\"},{\"name\":\"childDivider\",\"format\":\"reference|color\"},{\"name\":\"indicatorStart\",\"format\":\"dimension\"},{\"name\":\"indicatorEnd\",\"format\":\"dimension\"},{\"name\":\"childIndicatorStart\",\"format\":\"dimension\"},{\"name\":\"childIndicatorEnd\",\"format\":\"dimension\"}]},{\"name\":\"Gallery\",\"attr\":[{\"name\":\"gravity\"},{\"name\":\"animationDuration\",\"format\":\"integer\",\"min\":\"0\"},{\"name\":\"spacing\",\"format\":\"dimension\"},{\"name\":\"unselectedAlpha\",\"format\":\"float\"}]},{\"name\":\"GridView\",\"attr\":[{\"name\":\"horizontalSpacing\",\"format\":\"dimension\"},{\"name\":\"verticalSpacing\",\"format\":\"dimension\"},{\"name\":\"stretchMode\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"spacingWidth\",\"value\":\"1\"},{\"name\":\"columnWidth\",\"value\":\"2\"},{\"name\":\"spacingWidthUniform\",\"value\":\"3\"}]},{\"name\":\"columnWidth\",\"format\":\"dimension\"},{\"name\":\"numColumns\",\"format\":\"integer\",\"min\":\"0\",\"enum\":{\"name\":\"auto_fit\",\"value\":\"-1\"}},{\"name\":\"gravity\"}]},{\"name\":\"ImageSwitcher\"},{\"name\":\"ImageView\",\"attr\":[{\"name\":\"src\",\"format\":\"reference|color\"},{\"name\":\"scaleType\",\"enum\":[{\"name\":\"matrix\",\"value\":\"0\"},{\"name\":\"fitXY\",\"value\":\"1\"},{\"name\":\"fitStart\",\"value\":\"2\"},{\"name\":\"fitCenter\",\"value\":\"3\"},{\"name\":\"fitEnd\",\"value\":\"4\"},{\"name\":\"center\",\"value\":\"5\"},{\"name\":\"centerCrop\",\"value\":\"6\"},{\"name\":\"centerInside\",\"value\":\"7\"}]},{\"name\":\"adjustViewBounds\",\"format\":\"boolean\"},{\"name\":\"maxWidth\",\"format\":\"dimension\"},{\"name\":\"maxHeight\",\"format\":\"dimension\"},{\"name\":\"tint\",\"format\":\"color\"},{\"name\":\"baselineAlignBottom\",\"format\":\"boolean\"},{\"name\":\"cropToPadding\",\"format\":\"boolean\"},{\"name\":\"baseline\",\"format\":\"dimension\"},{\"name\":\"drawableAlpha\",\"format\":\"integer\"},{\"name\":\"tintMode\"}]},{\"name\":\"ToggleButton\",\"attr\":[{\"name\":\"textOn\",\"format\":\"string\"},{\"name\":\"textOff\",\"format\":\"string\"},{\"name\":\"disabledAlpha\"}]},{\"name\":\"RelativeLayout\",\"attr\":[{\"name\":\"gravity\"},{\"name\":\"ignoreGravity\",\"format\":\"reference\"}]},{\"name\":\"LinearLayout\",\"attr\":[{\"name\":\"orientation\"},{\"name\":\"gravity\"},{\"name\":\"baselineAligned\",\"format\":\"boolean\"},{\"name\":\"baselineAlignedChildIndex\",\"format\":\"integer\",\"min\":\"0\"},{\"name\":\"weightSum\",\"format\":\"float\"},{\"name\":\"measureWithLargestChild\",\"format\":\"boolean\"},{\"name\":\"divider\"},{\"name\":\"showDividers\",\"flag\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"beginning\",\"value\":\"1\"},{\"name\":\"middle\",\"value\":\"2\"},{\"name\":\"end\",\"value\":\"4\"}]},{\"name\":\"dividerPadding\",\"format\":\"dimension\"}]},{\"name\":\"GridLayout\",\"attr\":[{\"name\":\"orientation\"},{\"name\":\"rowCount\",\"format\":\"integer\"},{\"name\":\"columnCount\",\"format\":\"integer\"},{\"name\":\"useDefaultMargins\",\"format\":\"boolean\"},{\"name\":\"alignmentMode\"},{\"name\":\"rowOrderPreserved\",\"format\":\"boolean\"},{\"name\":\"columnOrderPreserved\",\"format\":\"boolean\"}]},{\"name\":\"ListView\",\"attr\":[{\"name\":\"entries\"},{\"name\":\"divider\",\"format\":\"reference|color\"},{\"name\":\"dividerHeight\",\"format\":\"dimension\"},{\"name\":\"headerDividersEnabled\",\"format\":\"boolean\"},{\"name\":\"footerDividersEnabled\",\"format\":\"boolean\"},{\"name\":\"overScrollHeader\",\"format\":\"reference|color\"},{\"name\":\"overScrollFooter\",\"format\":\"reference|color\"}]},{\"name\":\"PreferenceFrameLayout\",\"attr\":[{\"name\":\"borderTop\",\"format\":\"dimension\"},{\"name\":\"borderBottom\",\"format\":\"dimension\"},{\"name\":\"borderLeft\",\"format\":\"dimension\"},{\"name\":\"borderRight\",\"format\":\"dimension\"}]},{\"name\":\"PreferenceFrameLayout_Layout\",\"attr\":{\"name\":\"layout_removeBorders\",\"format\":\"boolean\"}},{\"name\":\"MenuView\",\"attr\":[{\"name\":\"itemTextAppearance\",\"format\":\"reference\"},{\"name\":\"horizontalDivider\",\"format\":\"reference\"},{\"name\":\"verticalDivider\",\"format\":\"reference\"},{\"name\":\"headerBackground\",\"format\":\"color|reference\"},{\"name\":\"itemBackground\",\"format\":\"color|reference\"},{\"name\":\"windowAnimationStyle\"},{\"name\":\"itemIconDisabledAlpha\",\"format\":\"float\"},{\"name\":\"preserveIconSpacing\",\"format\":\"boolean\"},{\"name\":\"subMenuArrow\",\"format\":\"reference\"}]},{\"name\":\"IconMenuView\",\"attr\":[{\"name\":\"rowHeight\",\"format\":\"dimension\"},{\"name\":\"maxRows\",\"format\":\"integer\"},{\"name\":\"maxItemsPerRow\",\"format\":\"integer\"},{\"name\":\"maxItems\",\"format\":\"integer\"},{\"name\":\"moreIcon\",\"format\":\"reference\"}]},{\"name\":\"ProgressBar\",\"attr\":[{\"name\":\"min\",\"format\":\"integer\"},{\"name\":\"max\",\"format\":\"integer\"},{\"name\":\"progress\",\"format\":\"integer\"},{\"name\":\"secondaryProgress\",\"format\":\"integer\"},{\"name\":\"indeterminate\",\"format\":\"boolean\"},{\"name\":\"indeterminateOnly\",\"format\":\"boolean\"},{\"name\":\"indeterminateDrawable\",\"format\":\"reference\"},{\"name\":\"progressDrawable\",\"format\":\"reference\"},{\"name\":\"indeterminateDuration\",\"format\":\"integer\",\"min\":\"1\"},{\"name\":\"indeterminateBehavior\",\"enum\":[{\"name\":\"repeat\",\"value\":\"1\"},{\"name\":\"cycle\",\"value\":\"2\"}]},{\"name\":\"minWidth\",\"format\":\"dimension\"},{\"name\":\"maxWidth\"},{\"name\":\"minHeight\",\"format\":\"dimension\"},{\"name\":\"maxHeight\"},{\"name\":\"interpolator\",\"format\":\"reference\"},{\"name\":\"animationResolution\",\"format\":\"integer\"},{\"name\":\"mirrorForRtl\",\"format\":\"boolean\"},{\"name\":\"progressTint\",\"format\":\"color\"},{\"name\":\"progressTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"progressBackgroundTint\",\"format\":\"color\"},{\"name\":\"progressBackgroundTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"secondaryProgressTint\",\"format\":\"color\"},{\"name\":\"secondaryProgressTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"indeterminateTint\",\"format\":\"color\"},{\"name\":\"indeterminateTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"backgroundTint\"},{\"name\":\"backgroundTintMode\"}]},{\"name\":\"SeekBar\",\"attr\":[{\"name\":\"thumb\",\"format\":\"reference\"},{\"name\":\"thumbOffset\",\"format\":\"dimension\"},{\"name\":\"splitTrack\",\"format\":\"boolean\"},{\"name\":\"useDisabledAlpha\",\"format\":\"boolean\"},{\"name\":\"thumbTint\",\"format\":\"color\"},{\"name\":\"thumbTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"tickMark\",\"format\":\"reference\"},{\"name\":\"tickMarkTint\",\"format\":\"color\"},{\"name\":\"tickMarkTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]}]},{\"name\":\"StackView\",\"attr\":[{\"name\":\"resOutColor\",\"format\":\"color\"},{\"name\":\"clickColor\",\"format\":\"color\"}]},{\"name\":\"RatingBar\",\"attr\":[{\"name\":\"numStars\",\"format\":\"integer\"},{\"name\":\"rating\",\"format\":\"float\"},{\"name\":\"stepSize\",\"format\":\"float\"},{\"name\":\"isIndicator\",\"format\":\"boolean\"}]},{\"name\":\"RadioGroup\",\"attr\":[{\"name\":\"checkedButton\",\"format\":\"integer\"},{\"name\":\"orientation\"}]},{\"name\":\"TableLayout\",\"attr\":[{\"name\":\"stretchColumns\",\"format\":\"string\"},{\"name\":\"shrinkColumns\",\"format\":\"string\"},{\"name\":\"collapseColumns\",\"format\":\"string\"}]},{\"name\":\"TableRow\"},{\"name\":\"TableRow_Cell\",\"attr\":[{\"name\":\"layout_column\",\"format\":\"integer\"},{\"name\":\"layout_span\",\"format\":\"integer\"}]},{\"name\":\"TabWidget\",\"attr\":[{\"name\":\"divider\"},{\"name\":\"tabStripEnabled\",\"format\":\"boolean\"},{\"name\":\"tabStripLeft\",\"format\":\"reference\"},{\"name\":\"tabStripRight\",\"format\":\"reference\"},{\"name\":\"tabLayout\",\"format\":\"reference\"}]},{\"name\":\"TextAppearance\",\"attr\":[{\"name\":\"textColor\"},{\"name\":\"textSize\"},{\"name\":\"textStyle\"},{\"name\":\"textFontWeight\"},{\"name\":\"typeface\"},{\"name\":\"fontFamily\"},{\"name\":\"textLocale\",\"format\":\"string\"},{\"name\":\"textColorHighlight\"},{\"name\":\"textColorHint\"},{\"name\":\"textColorLink\"},{\"name\":\"textAllCaps\",\"format\":\"boolean\"},{\"name\":\"shadowColor\",\"format\":\"color\"},{\"name\":\"shadowDx\",\"format\":\"float\"},{\"name\":\"shadowDy\",\"format\":\"float\"},{\"name\":\"shadowRadius\",\"format\":\"float\"},{\"name\":\"elegantTextHeight\",\"format\":\"boolean\"},{\"name\":\"fallbackLineSpacing\",\"format\":\"boolean\"},{\"name\":\"letterSpacing\",\"format\":\"float\"},{\"name\":\"fontFeatureSettings\",\"format\":\"string\"},{\"name\":\"fontVariationSettings\",\"format\":\"string\"}]},{\"name\":\"TextClock\",\"attr\":[{\"name\":\"format12Hour\",\"format\":\"string\"},{\"name\":\"format24Hour\",\"format\":\"string\"},{\"name\":\"timeZone\",\"format\":\"string\"}]},{\"name\":\"TextSwitcher\"},{\"name\":\"TextView\",\"attr\":[{\"name\":\"bufferType\",\"enum\":[{\"name\":\"normal\",\"value\":\"0\"},{\"name\":\"spannable\",\"value\":\"1\"},{\"name\":\"editable\",\"value\":\"2\"}]},{\"name\":\"text\",\"format\":\"string\",\"localization\":\"suggested\"},{\"name\":\"hint\",\"format\":\"string\"},{\"name\":\"textColor\"},{\"name\":\"textColorHighlight\"},{\"name\":\"textColorHint\"},{\"name\":\"textAppearance\"},{\"name\":\"textSize\"},{\"name\":\"textScaleX\",\"format\":\"float\"},{\"name\":\"typeface\"},{\"name\":\"textStyle\"},{\"name\":\"textFontWeight\",\"format\":\"integer\"},{\"name\":\"fontFamily\"},{\"name\":\"textLocale\",\"format\":\"string\"},{\"name\":\"textColorLink\"},{\"name\":\"cursorVisible\",\"format\":\"boolean\"},{\"name\":\"maxLines\",\"format\":\"integer\",\"min\":\"0\"},{\"name\":\"maxHeight\"},{\"name\":\"lines\",\"format\":\"integer\",\"min\":\"0\"},{\"name\":\"height\",\"format\":\"dimension\"},{\"name\":\"minLines\",\"format\":\"integer\",\"min\":\"0\"},{\"name\":\"minHeight\"},{\"name\":\"maxEms\",\"format\":\"integer\",\"min\":\"0\"},{\"name\":\"maxWidth\"},{\"name\":\"ems\",\"format\":\"integer\",\"min\":\"0\"},{\"name\":\"width\",\"format\":\"dimension\"},{\"name\":\"minEms\",\"format\":\"integer\",\"min\":\"0\"},{\"name\":\"minWidth\"},{\"name\":\"gravity\"},{\"name\":\"scrollHorizontally\",\"format\":\"boolean\"},{\"name\":\"password\",\"format\":\"boolean\"},{\"name\":\"singleLine\",\"format\":\"boolean\"},{\"name\":\"enabled\",\"format\":\"boolean\"},{\"name\":\"selectAllOnFocus\",\"format\":\"boolean\"},{\"name\":\"includeFontPadding\",\"format\":\"boolean\"},{\"name\":\"maxLength\",\"format\":\"integer\",\"min\":\"0\"},{\"name\":\"shadowColor\"},{\"name\":\"shadowDx\"},{\"name\":\"shadowDy\"},{\"name\":\"shadowRadius\"},{\"name\":\"autoLink\"},{\"name\":\"linksClickable\",\"format\":\"boolean\"},{\"name\":\"numeric\",\"flag\":[{\"name\":\"integer\",\"value\":\"0x01\"},{\"name\":\"signed\",\"value\":\"0x03\"},{\"name\":\"decimal\",\"value\":\"0x05\"}]},{\"name\":\"digits\",\"format\":\"string\"},{\"name\":\"phoneNumber\",\"format\":\"boolean\"},{\"name\":\"inputMethod\",\"format\":\"string\"},{\"name\":\"capitalize\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"sentences\",\"value\":\"1\"},{\"name\":\"words\",\"value\":\"2\"},{\"name\":\"characters\",\"value\":\"3\"}]},{\"name\":\"autoText\",\"format\":\"boolean\"},{\"name\":\"editable\",\"format\":\"boolean\"},{\"name\":\"freezesText\",\"format\":\"boolean\"},{\"name\":\"ellipsize\"},{\"name\":\"drawableTop\",\"format\":\"reference|color\"},{\"name\":\"drawableBottom\",\"format\":\"reference|color\"},{\"name\":\"drawableLeft\",\"format\":\"reference|color\"},{\"name\":\"drawableRight\",\"format\":\"reference|color\"},{\"name\":\"drawableStart\",\"format\":\"reference|color\"},{\"name\":\"drawableEnd\",\"format\":\"reference|color\"},{\"name\":\"drawablePadding\",\"format\":\"dimension\"},{\"name\":\"drawableTint\",\"format\":\"color\"},{\"name\":\"drawableTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"lineSpacingExtra\",\"format\":\"dimension\"},{\"name\":\"lineSpacingMultiplier\",\"format\":\"float\"},{\"name\":\"lineHeight\",\"format\":\"dimension\"},{\"name\":\"firstBaselineToTopHeight\",\"format\":\"dimension\"},{\"name\":\"lastBaselineToBottomHeight\",\"format\":\"dimension\"},{\"name\":\"marqueeRepeatLimit\",\"format\":\"integer\",\"enum\":{\"name\":\"marquee_forever\",\"value\":\"-1\"}},{\"name\":\"inputType\"},{\"name\":\"allowUndo\",\"format\":\"boolean\"},{\"name\":\"imeOptions\"},{\"name\":\"privateImeOptions\",\"format\":\"string\"},{\"name\":\"imeActionLabel\",\"format\":\"string\"},{\"name\":\"imeActionId\",\"format\":\"integer\"},{\"name\":\"editorExtras\",\"format\":\"reference\"},{\"name\":\"textSelectHandleLeft\"},{\"name\":\"textSelectHandleRight\"},{\"name\":\"textSelectHandle\"},{\"name\":\"textEditPasteWindowLayout\"},{\"name\":\"textEditNoPasteWindowLayout\"},{\"name\":\"textEditSidePasteWindowLayout\"},{\"name\":\"textEditSideNoPasteWindowLayout\"},{\"name\":\"textEditSuggestionItemLayout\"},{\"name\":\"textEditSuggestionContainerLayout\"},{\"name\":\"textEditSuggestionHighlightStyle\"},{\"name\":\"textCursorDrawable\"},{\"name\":\"textIsSelectable\"},{\"name\":\"textAllCaps\"},{\"name\":\"elegantTextHeight\"},{\"name\":\"fallbackLineSpacing\",\"format\":\"boolean\"},{\"name\":\"letterSpacing\"},{\"name\":\"fontFeatureSettings\"},{\"name\":\"fontVariationSettings\"},{\"name\":\"breakStrategy\",\"enum\":[{\"name\":\"simple\",\"value\":\"0\"},{\"name\":\"high_quality\",\"value\":\"1\"},{\"name\":\"balanced\",\"value\":\"2\"}]},{\"name\":\"hyphenationFrequency\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"normal\",\"value\":\"1\"},{\"name\":\"full\",\"value\":\"2\"}]},{\"name\":\"autoSizeTextType\",\"format\":\"enum\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"uniform\",\"value\":\"1\"}]},{\"name\":\"autoSizeStepGranularity\",\"format\":\"dimension\"},{\"name\":\"autoSizePresetSizes\"},{\"name\":\"autoSizeMinTextSize\",\"format\":\"dimension\"},{\"name\":\"autoSizeMaxTextSize\",\"format\":\"dimension\"},{\"name\":\"justificationMode\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"inter_word\",\"value\":\"1\"}]}]},{\"name\":\"TextViewAppearance\",\"attr\":{\"name\":\"textAppearance\"}},{\"name\":\"SelectionModeDrawables\",\"attr\":[{\"name\":\"actionModeSelectAllDrawable\"},{\"name\":\"actionModeCutDrawable\"},{\"name\":\"actionModeCopyDrawable\"},{\"name\":\"actionModePasteDrawable\"}]},{\"name\":\"SuggestionSpan\",\"attr\":[{\"name\":\"textUnderlineColor\"},{\"name\":\"textUnderlineThickness\"}]},{\"name\":\"InputExtras\"},{\"name\":\"AutoCompleteTextView\",\"attr\":[{\"name\":\"completionHint\",\"format\":\"string\"},{\"name\":\"completionHintView\",\"format\":\"reference\"},{\"name\":\"completionThreshold\",\"format\":\"integer\",\"min\":\"1\"},{\"name\":\"dropDownSelector\",\"format\":\"reference|color\"},{\"name\":\"dropDownAnchor\",\"format\":\"reference\"},{\"name\":\"dropDownWidth\",\"format\":\"dimension\",\"enum\":[{\"name\":\"fill_parent\",\"value\":\"-1\"},{\"name\":\"match_parent\",\"value\":\"-1\"},{\"name\":\"wrap_content\",\"value\":\"-2\"}]},{\"name\":\"dropDownHeight\",\"format\":\"dimension\",\"enum\":[{\"name\":\"fill_parent\",\"value\":\"-1\"},{\"name\":\"match_parent\",\"value\":\"-1\"},{\"name\":\"wrap_content\",\"value\":\"-2\"}]},{\"name\":\"inputType\"},{\"name\":\"popupTheme\"}]},{\"name\":\"PopupWindow\",\"attr\":[{\"name\":\"popupBackground\",\"format\":\"reference|color\"},{\"name\":\"popupElevation\",\"format\":\"dimension\"},{\"name\":\"popupAnimationStyle\",\"format\":\"reference\"},{\"name\":\"overlapAnchor\",\"format\":\"boolean\"},{\"name\":\"popupEnterTransition\",\"format\":\"reference\"},{\"name\":\"popupExitTransition\",\"format\":\"reference\"}]},{\"name\":\"ListPopupWindow\",\"attr\":[{\"name\":\"dropDownVerticalOffset\",\"format\":\"dimension\"},{\"name\":\"dropDownHorizontalOffset\",\"format\":\"dimension\"}]},{\"name\":\"ViewAnimator\",\"attr\":[{\"name\":\"inAnimation\",\"format\":\"reference\"},{\"name\":\"outAnimation\",\"format\":\"reference\"},{\"name\":\"animateFirstView\",\"format\":\"boolean\"}]},{\"name\":\"ViewFlipper\",\"attr\":[{\"name\":\"flipInterval\",\"format\":\"integer\",\"min\":\"0\"},{\"name\":\"autoStart\",\"format\":\"boolean\"}]},{\"name\":\"AdapterViewAnimator\",\"attr\":[{\"name\":\"inAnimation\"},{\"name\":\"outAnimation\"},{\"name\":\"loopViews\",\"format\":\"boolean\"},{\"name\":\"animateFirstView\"}]},{\"name\":\"AdapterViewFlipper\",\"attr\":[{\"name\":\"flipInterval\"},{\"name\":\"autoStart\"}]},{\"name\":\"ViewSwitcher\"},{\"name\":\"ScrollView\",\"attr\":{\"name\":\"fillViewport\",\"format\":\"boolean\"}},{\"name\":\"HorizontalScrollView\",\"attr\":{\"name\":\"fillViewport\"}},{\"name\":\"Spinner\",\"attr\":[{\"name\":\"prompt\",\"format\":\"reference\"},{\"name\":\"spinnerMode\",\"format\":\"enum\",\"enum\":[{\"name\":\"dialog\",\"value\":\"0\"},{\"name\":\"dropdown\",\"value\":\"1\"}]},{\"name\":\"dropDownSelector\"},{\"name\":\"popupTheme\"},{\"name\":\"popupBackground\"},{\"name\":\"popupElevation\"},{\"name\":\"dropDownWidth\"},{\"name\":\"popupPromptView\",\"format\":\"reference\"},{\"name\":\"gravity\"},{\"name\":\"disableChildrenWhenDisabled\",\"format\":\"boolean\"}]},{\"name\":\"DatePicker\",\"attr\":[{\"name\":\"firstDayOfWeek\"},{\"name\":\"minDate\",\"format\":\"string\"},{\"name\":\"maxDate\",\"format\":\"string\"},{\"name\":\"spinnersShown\",\"format\":\"boolean\"},{\"name\":\"calendarViewShown\",\"format\":\"boolean\"},{\"name\":\"internalLayout\",\"format\":\"reference\"},{\"name\":\"legacyLayout\"},{\"name\":\"headerTextColor\",\"format\":\"color\"},{\"name\":\"headerBackground\"},{\"name\":\"yearListItemTextAppearance\",\"format\":\"reference\"},{\"name\":\"yearListItemActivatedTextAppearance\",\"format\":\"reference\"},{\"name\":\"calendarTextColor\",\"format\":\"color\"},{\"name\":\"datePickerMode\",\"enum\":[{\"name\":\"spinner\",\"value\":\"1\"},{\"name\":\"calendar\",\"value\":\"2\"}]},{\"name\":\"startYear\",\"format\":\"integer\"},{\"name\":\"endYear\",\"format\":\"integer\"},{\"name\":\"headerMonthTextAppearance\",\"format\":\"reference\"},{\"name\":\"headerDayOfMonthTextAppearance\",\"format\":\"reference\"},{\"name\":\"headerYearTextAppearance\",\"format\":\"reference\"},{\"name\":\"dayOfWeekBackground\",\"format\":\"color\"},{\"name\":\"dayOfWeekTextAppearance\",\"format\":\"reference\"},{\"name\":\"yearListSelectorColor\",\"format\":\"color\"},{\"name\":\"dialogMode\",\"format\":\"boolean\"}]},{\"name\":\"TwoLineListItem\",\"attr\":{\"name\":\"mode\",\"enum\":[{\"name\":\"oneLine\",\"value\":\"1\"},{\"name\":\"collapsing\",\"value\":\"2\"},{\"name\":\"twoLine\",\"value\":\"3\"}]}},{\"name\":\"SlidingDrawer\",\"attr\":[{\"name\":\"handle\",\"format\":\"reference\"},{\"name\":\"content\",\"format\":\"reference\"},{\"name\":\"orientation\"},{\"name\":\"bottomOffset\",\"format\":\"dimension\"},{\"name\":\"topOffset\",\"format\":\"dimension\"},{\"name\":\"allowSingleTap\",\"format\":\"boolean\"},{\"name\":\"animateOnClick\",\"format\":\"boolean\"}]},{\"name\":\"GestureOverlayView\",\"attr\":[{\"name\":\"gestureStrokeWidth\",\"format\":\"float\"},{\"name\":\"gestureColor\",\"format\":\"color\"},{\"name\":\"uncertainGestureColor\",\"format\":\"color\"},{\"name\":\"fadeOffset\",\"format\":\"integer\"},{\"name\":\"fadeDuration\",\"format\":\"integer\"},{\"name\":\"gestureStrokeType\",\"enum\":[{\"name\":\"single\",\"value\":\"0\"},{\"name\":\"multiple\",\"value\":\"1\"}]},{\"name\":\"gestureStrokeLengthThreshold\",\"format\":\"float\"},{\"name\":\"gestureStrokeSquarenessThreshold\",\"format\":\"float\"},{\"name\":\"gestureStrokeAngleThreshold\",\"format\":\"float\"},{\"name\":\"eventsInterceptionEnabled\",\"format\":\"boolean\"},{\"name\":\"fadeEnabled\",\"format\":\"boolean\"},{\"name\":\"orientation\"}]},{\"name\":\"QuickContactBadge\",\"attr\":{\"name\":\"quickContactWindowSize\",\"enum\":[{\"name\":\"modeSmall\",\"value\":\"1\"},{\"name\":\"modeMedium\",\"value\":\"2\"},{\"name\":\"modeLarge\",\"value\":\"3\"}]}},{\"name\":\"AbsoluteLayout_Layout\",\"attr\":[{\"name\":\"layout_x\",\"format\":\"dimension\"},{\"name\":\"layout_y\",\"format\":\"dimension\"}]},{\"name\":\"LinearLayout_Layout\",\"attr\":[{\"name\":\"layout_width\"},{\"name\":\"layout_height\"},{\"name\":\"layout_weight\",\"format\":\"float\"},{\"name\":\"layout_gravity\"}]},{\"name\":\"GridLayout_Layout\",\"attr\":[{\"name\":\"layout_row\",\"format\":\"integer\"},{\"name\":\"layout_rowSpan\",\"format\":\"integer\",\"min\":\"1\"},{\"name\":\"layout_rowWeight\",\"format\":\"float\"},{\"name\":\"layout_column\"},{\"name\":\"layout_columnSpan\",\"format\":\"integer\",\"min\":\"1\"},{\"name\":\"layout_columnWeight\",\"format\":\"float\"},{\"name\":\"layout_gravity\"}]},{\"name\":\"FrameLayout_Layout\",\"attr\":{\"name\":\"layout_gravity\"}},{\"name\":\"RelativeLayout_Layout\",\"attr\":[{\"name\":\"layout_toLeftOf\",\"format\":\"reference\"},{\"name\":\"layout_toRightOf\",\"format\":\"reference\"},{\"name\":\"layout_above\",\"format\":\"reference\"},{\"name\":\"layout_below\",\"format\":\"reference\"},{\"name\":\"layout_alignBaseline\",\"format\":\"reference\"},{\"name\":\"layout_alignLeft\",\"format\":\"reference\"},{\"name\":\"layout_alignTop\",\"format\":\"reference\"},{\"name\":\"layout_alignRight\",\"format\":\"reference\"},{\"name\":\"layout_alignBottom\",\"format\":\"reference\"},{\"name\":\"layout_alignParentLeft\",\"format\":\"boolean\"},{\"name\":\"layout_alignParentTop\",\"format\":\"boolean\"},{\"name\":\"layout_alignParentRight\",\"format\":\"boolean\"},{\"name\":\"layout_alignParentBottom\",\"format\":\"boolean\"},{\"name\":\"layout_centerInParent\",\"format\":\"boolean\"},{\"name\":\"layout_centerHorizontal\",\"format\":\"boolean\"},{\"name\":\"layout_centerVertical\",\"format\":\"boolean\"},{\"name\":\"layout_alignWithParentIfMissing\",\"format\":\"boolean\"},{\"name\":\"layout_toStartOf\",\"format\":\"reference\"},{\"name\":\"layout_toEndOf\",\"format\":\"reference\"},{\"name\":\"layout_alignStart\",\"format\":\"reference\"},{\"name\":\"layout_alignEnd\",\"format\":\"reference\"},{\"name\":\"layout_alignParentStart\",\"format\":\"boolean\"},{\"name\":\"layout_alignParentEnd\",\"format\":\"boolean\"}]},{\"name\":\"VerticalSlider_Layout\",\"attr\":{\"name\":\"layout_scale\",\"format\":\"float\"}},{\"name\":\"WeightedLinearLayout\",\"attr\":[{\"name\":\"majorWeightMin\",\"format\":\"float\"},{\"name\":\"minorWeightMin\",\"format\":\"float\"},{\"name\":\"majorWeightMax\",\"format\":\"float\"},{\"name\":\"minorWeightMax\",\"format\":\"float\"}]},{\"name\":\"CalendarView\",\"attr\":[{\"name\":\"firstDayOfWeek\",\"format\":\"integer\"},{\"name\":\"minDate\"},{\"name\":\"maxDate\"},{\"name\":\"monthTextAppearance\",\"format\":\"reference\"},{\"name\":\"weekDayTextAppearance\",\"format\":\"reference\"},{\"name\":\"dateTextAppearance\",\"format\":\"reference\"},{\"name\":\"daySelectorColor\",\"format\":\"color\"},{\"name\":\"dayHighlightColor\",\"format\":\"color\"},{\"name\":\"calendarViewMode\",\"enum\":[{\"name\":\"holo\",\"value\":\"0\"},{\"name\":\"material\",\"value\":\"1\"}]},{\"name\":\"showWeekNumber\",\"format\":\"boolean\"},{\"name\":\"shownWeekCount\",\"format\":\"integer\"},{\"name\":\"selectedWeekBackgroundColor\",\"format\":\"color|reference\"},{\"name\":\"focusedMonthDateColor\",\"format\":\"color|reference\"},{\"name\":\"unfocusedMonthDateColor\",\"format\":\"color|reference\"},{\"name\":\"weekNumberColor\",\"format\":\"color|reference\"},{\"name\":\"weekSeparatorLineColor\",\"format\":\"color|reference\"},{\"name\":\"selectedDateVerticalBar\",\"format\":\"reference\"}]},{\"name\":\"NumberPicker\",\"attr\":[{\"name\":\"solidColor\",\"format\":\"color|reference\"},{\"name\":\"selectionDivider\",\"format\":\"reference\"},{\"name\":\"selectionDividerHeight\",\"format\":\"dimension\"},{\"name\":\"selectionDividersDistance\",\"format\":\"dimension\"},{\"name\":\"internalMinHeight\",\"format\":\"dimension\"},{\"name\":\"internalMaxHeight\",\"format\":\"dimension\"},{\"name\":\"internalMinWidth\",\"format\":\"dimension\"},{\"name\":\"internalMaxWidth\",\"format\":\"dimension\"},{\"name\":\"internalLayout\"},{\"name\":\"virtualButtonPressedDrawable\",\"format\":\"reference\"},{\"name\":\"hideWheelUntilFocused\",\"format\":\"boolean\"}]},{\"name\":\"TimePicker\",\"attr\":[{\"name\":\"legacyLayout\",\"format\":\"reference\"},{\"name\":\"internalLayout\"},{\"name\":\"headerTextColor\"},{\"name\":\"headerBackground\"},{\"name\":\"numbersTextColor\",\"format\":\"color\"},{\"name\":\"numbersInnerTextColor\",\"format\":\"color\"},{\"name\":\"numbersBackgroundColor\",\"format\":\"color\"},{\"name\":\"numbersSelectorColor\",\"format\":\"color\"},{\"name\":\"timePickerMode\",\"enum\":[{\"name\":\"spinner\",\"value\":\"1\"},{\"name\":\"clock\",\"value\":\"2\"}]},{\"name\":\"headerAmPmTextAppearance\",\"format\":\"reference\"},{\"name\":\"headerTimeTextAppearance\",\"format\":\"reference\"},{\"name\":\"amPmTextColor\",\"format\":\"color\"},{\"name\":\"amPmBackgroundColor\",\"format\":\"color\"},{\"name\":\"dialogMode\"}]},{\"name\":\"Drawable\",\"attr\":[{\"name\":\"visible\",\"format\":\"boolean\"},{\"name\":\"autoMirrored\",\"format\":\"boolean\"}]},{\"name\":\"DrawableWrapper\",\"attr\":{\"name\":\"drawable\"}},{\"name\":\"StateListDrawable\",\"attr\":[{\"name\":\"visible\"},{\"name\":\"variablePadding\",\"format\":\"boolean\"},{\"name\":\"constantSize\",\"format\":\"boolean\"},{\"name\":\"dither\",\"format\":\"boolean\"},{\"name\":\"enterFadeDuration\",\"format\":\"integer\"},{\"name\":\"exitFadeDuration\",\"format\":\"integer\"},{\"name\":\"autoMirrored\"}]},{\"name\":\"AnimatedStateListDrawable\",\"attr\":[{\"name\":\"visible\"},{\"name\":\"variablePadding\"},{\"name\":\"constantSize\"},{\"name\":\"dither\"},{\"name\":\"enterFadeDuration\"},{\"name\":\"exitFadeDuration\"},{\"name\":\"autoMirrored\"}]},{\"name\":\"StateListDrawableItem\",\"attr\":{\"name\":\"drawable\"}},{\"name\":\"AnimatedStateListDrawableItem\",\"attr\":[{\"name\":\"drawable\"},{\"name\":\"id\"}]},{\"name\":\"AnimatedStateListDrawableTransition\",\"attr\":[{\"name\":\"fromId\",\"format\":\"reference\"},{\"name\":\"toId\",\"format\":\"reference\"},{\"name\":\"drawable\"},{\"name\":\"reversible\",\"format\":\"boolean\"}]},{\"name\":\"AnimationDrawable\",\"attr\":[{\"name\":\"visible\"},{\"name\":\"variablePadding\"},{\"name\":\"oneshot\",\"format\":\"boolean\"}]},{\"name\":\"AnimationDrawableItem\",\"attr\":[{\"name\":\"duration\",\"format\":\"integer\"},{\"name\":\"drawable\",\"format\":\"reference\"}]},{\"name\":\"StateListAnimatorItem\",\"attr\":{\"name\":\"animation\"}},{\"name\":\"ColorStateListItem\",\"attr\":[{\"name\":\"color\"},{\"name\":\"alpha\"},{\"name\":\"lStar\",\"format\":\"float\"}]},{\"name\":\"AnimationScaleListDrawable\"},{\"name\":\"AnimationScaleListDrawableItem\",\"attr\":{\"name\":\"drawable\"}},{\"name\":\"GradientDrawable\",\"attr\":[{\"name\":\"visible\"},{\"name\":\"dither\"},{\"name\":\"shape\",\"enum\":[{\"name\":\"rectangle\",\"value\":\"0\"},{\"name\":\"oval\",\"value\":\"1\"},{\"name\":\"line\",\"value\":\"2\"},{\"name\":\"ring\",\"value\":\"3\"}]},{\"name\":\"innerRadiusRatio\",\"format\":\"float\"},{\"name\":\"thicknessRatio\",\"format\":\"float\"},{\"name\":\"innerRadius\",\"format\":\"dimension\"},{\"name\":\"thickness\",\"format\":\"dimension\"},{\"name\":\"useLevel\"},{\"name\":\"tint\"},{\"name\":\"tintMode\"},{\"name\":\"opticalInsetLeft\"},{\"name\":\"opticalInsetTop\"},{\"name\":\"opticalInsetRight\"},{\"name\":\"opticalInsetBottom\"}]},{\"name\":\"GradientDrawableSize\",\"attr\":[{\"name\":\"width\"},{\"name\":\"height\"}]},{\"name\":\"GradientDrawableGradient\",\"attr\":[{\"name\":\"startColor\",\"format\":\"color\"},{\"name\":\"centerColor\",\"format\":\"color\"},{\"name\":\"endColor\",\"format\":\"color\"},{\"name\":\"useLevel\",\"format\":\"boolean\"},{\"name\":\"angle\",\"format\":\"float\"},{\"name\":\"type\",\"enum\":[{\"name\":\"linear\",\"value\":\"0\"},{\"name\":\"radial\",\"value\":\"1\"},{\"name\":\"sweep\",\"value\":\"2\"}]},{\"name\":\"centerX\",\"format\":\"float|fraction\"},{\"name\":\"centerY\",\"format\":\"float|fraction\"},{\"name\":\"gradientRadius\",\"format\":\"float|fraction|dimension\"}]},{\"name\":\"GradientDrawableSolid\",\"attr\":{\"name\":\"color\",\"format\":\"color\"}},{\"name\":\"GradientDrawableStroke\",\"attr\":[{\"name\":\"width\"},{\"name\":\"color\"},{\"name\":\"dashWidth\",\"format\":\"dimension\"},{\"name\":\"dashGap\",\"format\":\"dimension\"}]},{\"name\":\"DrawableCorners\",\"attr\":[{\"name\":\"radius\",\"format\":\"dimension\"},{\"name\":\"topLeftRadius\",\"format\":\"dimension\"},{\"name\":\"topRightRadius\",\"format\":\"dimension\"},{\"name\":\"bottomLeftRadius\",\"format\":\"dimension\"},{\"name\":\"bottomRightRadius\",\"format\":\"dimension\"}]},{\"name\":\"GradientDrawablePadding\",\"attr\":[{\"name\":\"left\",\"format\":\"dimension\"},{\"name\":\"top\",\"format\":\"dimension\"},{\"name\":\"right\",\"format\":\"dimension\"},{\"name\":\"bottom\",\"format\":\"dimension\"}]},{\"name\":\"LayerDrawable\",\"attr\":[{\"name\":\"opacity\",\"enum\":[{\"name\":\"opaque\",\"value\":\"-1\"},{\"name\":\"transparent\",\"value\":\"-2\"},{\"name\":\"translucent\",\"value\":\"-3\"}]},{\"name\":\"autoMirrored\"},{\"name\":\"paddingMode\",\"enum\":[{\"name\":\"nest\",\"value\":\"0\"},{\"name\":\"stack\",\"value\":\"1\"}]},{\"name\":\"paddingTop\"},{\"name\":\"paddingBottom\"},{\"name\":\"paddingLeft\"},{\"name\":\"paddingRight\"},{\"name\":\"paddingStart\"},{\"name\":\"paddingEnd\"}]},{\"name\":\"LayerDrawableItem\",\"attr\":[{\"name\":\"left\"},{\"name\":\"top\"},{\"name\":\"right\"},{\"name\":\"bottom\"},{\"name\":\"start\",\"format\":\"dimension\"},{\"name\":\"end\",\"format\":\"dimension\"},{\"name\":\"width\"},{\"name\":\"height\"},{\"name\":\"gravity\"},{\"name\":\"drawable\"},{\"name\":\"id\"}]},{\"name\":\"LevelListDrawableItem\",\"attr\":[{\"name\":\"minLevel\",\"format\":\"integer\"},{\"name\":\"maxLevel\",\"format\":\"integer\"},{\"name\":\"drawable\"}]},{\"name\":\"RotateDrawable\",\"attr\":[{\"name\":\"visible\"},{\"name\":\"fromDegrees\",\"format\":\"float\"},{\"name\":\"toDegrees\",\"format\":\"float\"},{\"name\":\"pivotX\",\"format\":\"float|fraction\"},{\"name\":\"pivotY\",\"format\":\"float|fraction\"},{\"name\":\"drawable\"}]},{\"name\":\"AnimatedRotateDrawable\",\"attr\":[{\"name\":\"visible\"},{\"name\":\"frameDuration\",\"format\":\"integer\"},{\"name\":\"framesCount\",\"format\":\"integer\"},{\"name\":\"pivotX\"},{\"name\":\"pivotY\"},{\"name\":\"drawable\"}]},{\"name\":\"MaterialProgressDrawable\",\"attr\":[{\"name\":\"visible\"},{\"name\":\"thickness\"},{\"name\":\"innerRadius\"},{\"name\":\"width\"},{\"name\":\"height\"},{\"name\":\"color\"}]},{\"name\":\"InsetDrawable\",\"attr\":[{\"name\":\"visible\"},{\"name\":\"drawable\"},{\"name\":\"inset\",\"format\":\"fraction|dimension\"},{\"name\":\"insetLeft\",\"format\":\"fraction|dimension\"},{\"name\":\"insetRight\",\"format\":\"fraction|dimension\"},{\"name\":\"insetTop\",\"format\":\"fraction|dimension\"},{\"name\":\"insetBottom\",\"format\":\"fraction|dimension\"}]},{\"name\":\"AnimatedImageDrawable\",\"attr\":[{\"name\":\"src\"},{\"name\":\"autoMirrored\"},{\"name\":\"repeatCount\"},{\"name\":\"autoStart\"}]},{\"name\":\"BitmapDrawable\",\"attr\":[{\"name\":\"src\"},{\"name\":\"antialias\",\"format\":\"boolean\"},{\"name\":\"filter\",\"format\":\"boolean\"},{\"name\":\"dither\"},{\"name\":\"gravity\"},{\"name\":\"tileMode\",\"enum\":[{\"name\":\"disabled\",\"value\":\"-1\"},{\"name\":\"clamp\",\"value\":\"0\"},{\"name\":\"repeat\",\"value\":\"1\"},{\"name\":\"mirror\",\"value\":\"2\"}]},{\"name\":\"tileModeX\",\"enum\":[{\"name\":\"disabled\",\"value\":\"-1\"},{\"name\":\"clamp\",\"value\":\"0\"},{\"name\":\"repeat\",\"value\":\"1\"},{\"name\":\"mirror\",\"value\":\"2\"}]},{\"name\":\"tileModeY\",\"enum\":[{\"name\":\"disabled\",\"value\":\"-1\"},{\"name\":\"clamp\",\"value\":\"0\"},{\"name\":\"repeat\",\"value\":\"1\"},{\"name\":\"mirror\",\"value\":\"2\"}]},{\"name\":\"mipMap\",\"format\":\"boolean\"},{\"name\":\"autoMirrored\"},{\"name\":\"tint\"},{\"name\":\"tintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"alpha\"}]},{\"name\":\"NinePatchDrawable\",\"attr\":[{\"name\":\"src\"},{\"name\":\"dither\"},{\"name\":\"autoMirrored\"},{\"name\":\"tint\"},{\"name\":\"tintMode\"},{\"name\":\"alpha\"}]},{\"name\":\"ColorDrawable\",\"attr\":{\"name\":\"color\"}},{\"name\":\"AdaptiveIconDrawableLayer\",\"attr\":{\"name\":\"drawable\"}},{\"name\":\"RippleDrawable\",\"attr\":[{\"name\":\"color\"},{\"name\":\"radius\"},{\"name\":\"effectColor\"}]},{\"name\":\"ScaleDrawable\",\"attr\":[{\"name\":\"scaleWidth\",\"format\":\"string\"},{\"name\":\"scaleHeight\",\"format\":\"string\"},{\"name\":\"scaleGravity\",\"flag\":[{\"name\":\"top\",\"value\":\"0x30\"},{\"name\":\"bottom\",\"value\":\"0x50\"},{\"name\":\"left\",\"value\":\"0x03\"},{\"name\":\"right\",\"value\":\"0x05\"},{\"name\":\"center_vertical\",\"value\":\"0x10\"},{\"name\":\"fill_vertical\",\"value\":\"0x70\"},{\"name\":\"center_horizontal\",\"value\":\"0x01\"},{\"name\":\"fill_horizontal\",\"value\":\"0x07\"},{\"name\":\"center\",\"value\":\"0x11\"},{\"name\":\"fill\",\"value\":\"0x77\"},{\"name\":\"clip_vertical\",\"value\":\"0x80\"},{\"name\":\"clip_horizontal\",\"value\":\"0x08\"},{\"name\":\"start\",\"value\":\"0x00800003\"},{\"name\":\"end\",\"value\":\"0x00800005\"}]},{\"name\":\"level\",\"format\":\"integer\"},{\"name\":\"drawable\"},{\"name\":\"useIntrinsicSizeAsMinimum\",\"format\":\"boolean\"}]},{\"name\":\"ClipDrawable\",\"attr\":[{\"name\":\"clipOrientation\",\"flag\":[{\"name\":\"horizontal\",\"value\":\"1\"},{\"name\":\"vertical\",\"value\":\"2\"}]},{\"name\":\"gravity\"},{\"name\":\"drawable\"}]},{\"name\":\"ShapeDrawablePadding\",\"attr\":[{\"name\":\"left\"},{\"name\":\"top\"},{\"name\":\"right\"},{\"name\":\"bottom\"}]},{\"name\":\"ShapeDrawable\",\"attr\":[{\"name\":\"color\"},{\"name\":\"width\"},{\"name\":\"height\"},{\"name\":\"dither\"},{\"name\":\"tint\"},{\"name\":\"tintMode\"}]},{\"name\":\"VectorDrawable\",\"attr\":[{\"name\":\"tint\"},{\"name\":\"tintMode\"},{\"name\":\"autoMirrored\"},{\"name\":\"width\"},{\"name\":\"height\"},{\"name\":\"viewportWidth\",\"format\":\"float\"},{\"name\":\"viewportHeight\",\"format\":\"float\"},{\"name\":\"name\"},{\"name\":\"alpha\"},{\"name\":\"opticalInsetLeft\",\"format\":\"dimension\"},{\"name\":\"opticalInsetTop\",\"format\":\"dimension\"},{\"name\":\"opticalInsetRight\",\"format\":\"dimension\"},{\"name\":\"opticalInsetBottom\",\"format\":\"dimension\"}]},{\"name\":\"VectorDrawableGroup\",\"attr\":[{\"name\":\"name\"},{\"name\":\"rotation\"},{\"name\":\"pivotX\"},{\"name\":\"pivotY\"},{\"name\":\"translateX\",\"format\":\"float\"},{\"name\":\"translateY\",\"format\":\"float\"},{\"name\":\"scaleX\"},{\"name\":\"scaleY\"}]},{\"name\":\"VectorDrawablePath\",\"attr\":[{\"name\":\"name\"},{\"name\":\"strokeWidth\",\"format\":\"float\"},{\"name\":\"strokeColor\",\"format\":\"color\"},{\"name\":\"strokeAlpha\",\"format\":\"float\"},{\"name\":\"fillColor\",\"format\":\"color\"},{\"name\":\"fillAlpha\",\"format\":\"float\"},{\"name\":\"pathData\",\"format\":\"string\"},{\"name\":\"trimPathStart\",\"format\":\"float\"},{\"name\":\"trimPathEnd\",\"format\":\"float\"},{\"name\":\"trimPathOffset\",\"format\":\"float\"},{\"name\":\"strokeLineCap\",\"format\":\"enum\",\"enum\":[{\"name\":\"butt\",\"value\":\"0\"},{\"name\":\"round\",\"value\":\"1\"},{\"name\":\"square\",\"value\":\"2\"}]},{\"name\":\"strokeLineJoin\",\"format\":\"enum\",\"enum\":[{\"name\":\"miter\",\"value\":\"0\"},{\"name\":\"round\",\"value\":\"1\"},{\"name\":\"bevel\",\"value\":\"2\"}]},{\"name\":\"strokeMiterLimit\",\"format\":\"float\"},{\"name\":\"fillType\",\"format\":\"enum\",\"enum\":[{\"name\":\"nonZero\",\"value\":\"0\"},{\"name\":\"evenOdd\",\"value\":\"1\"}]}]},{\"name\":\"VectorDrawableClipPath\",\"attr\":[{\"name\":\"name\"},{\"name\":\"pathData\"}]},{\"name\":\"AnimatedVectorDrawable\",\"attr\":{\"name\":\"drawable\"}},{\"name\":\"AnimatedVectorDrawableTarget\",\"attr\":[{\"name\":\"name\"},{\"name\":\"animation\"}]},{\"name\":\"Animation\",\"attr\":[{\"name\":\"interpolator\"},{\"name\":\"fillEnabled\",\"format\":\"boolean\"},{\"name\":\"fillBefore\",\"format\":\"boolean\"},{\"name\":\"fillAfter\",\"format\":\"boolean\"},{\"name\":\"duration\"},{\"name\":\"startOffset\",\"format\":\"integer\"},{\"name\":\"repeatCount\",\"format\":\"integer\",\"enum\":{\"name\":\"infinite\",\"value\":\"-1\"}},{\"name\":\"repeatMode\",\"enum\":[{\"name\":\"restart\",\"value\":\"1\"},{\"name\":\"reverse\",\"value\":\"2\"}]},{\"name\":\"zAdjustment\",\"enum\":[{\"name\":\"normal\",\"value\":\"0\"},{\"name\":\"top\",\"value\":\"1\"},{\"name\":\"bottom\",\"value\":\"-1\"}]},{\"name\":\"background\"},{\"name\":\"detachWallpaper\",\"format\":\"boolean\"},{\"name\":\"showWallpaper\",\"format\":\"boolean\"},{\"name\":\"hasRoundedCorners\",\"format\":\"boolean\"}]},{\"name\":\"AnimationSet\",\"attr\":[{\"name\":\"shareInterpolator\",\"format\":\"boolean\"},{\"name\":\"fillBefore\"},{\"name\":\"fillAfter\"},{\"name\":\"duration\"},{\"name\":\"startOffset\"},{\"name\":\"repeatMode\"}]},{\"name\":\"RotateAnimation\",\"attr\":[{\"name\":\"fromDegrees\"},{\"name\":\"toDegrees\"},{\"name\":\"pivotX\"},{\"name\":\"pivotY\"}]},{\"name\":\"ScaleAnimation\",\"attr\":[{\"name\":\"fromXScale\",\"format\":\"float|fraction|dimension\"},{\"name\":\"toXScale\",\"format\":\"float|fraction|dimension\"},{\"name\":\"fromYScale\",\"format\":\"float|fraction|dimension\"},{\"name\":\"toYScale\",\"format\":\"float|fraction|dimension\"},{\"name\":\"pivotX\"},{\"name\":\"pivotY\"}]},{\"name\":\"TranslateAnimation\",\"attr\":[{\"name\":\"fromXDelta\",\"format\":\"float|fraction\"},{\"name\":\"toXDelta\",\"format\":\"float|fraction\"},{\"name\":\"fromYDelta\",\"format\":\"float|fraction\"},{\"name\":\"toYDelta\",\"format\":\"float|fraction\"}]},{\"name\":\"AlphaAnimation\",\"attr\":[{\"name\":\"fromAlpha\",\"format\":\"float\"},{\"name\":\"toAlpha\",\"format\":\"float\"}]},{\"name\":\"ClipRectAnimation\",\"attr\":[{\"name\":\"fromLeft\",\"format\":\"fraction\"},{\"name\":\"fromTop\",\"format\":\"fraction\"},{\"name\":\"fromRight\",\"format\":\"fraction\"},{\"name\":\"fromBottom\",\"format\":\"fraction\"},{\"name\":\"toLeft\",\"format\":\"fraction\"},{\"name\":\"toTop\",\"format\":\"fraction\"},{\"name\":\"toRight\",\"format\":\"fraction\"},{\"name\":\"toBottom\",\"format\":\"fraction\"}]},{\"name\":\"LayoutAnimation\",\"attr\":[{\"name\":\"delay\",\"format\":\"float|fraction\"},{\"name\":\"animation\",\"format\":\"reference\"},{\"name\":\"animationOrder\",\"enum\":[{\"name\":\"normal\",\"value\":\"0\"},{\"name\":\"reverse\",\"value\":\"1\"},{\"name\":\"random\",\"value\":\"2\"}]},{\"name\":\"interpolator\"}]},{\"name\":\"GridLayoutAnimation\",\"attr\":[{\"name\":\"columnDelay\",\"format\":\"float|fraction\"},{\"name\":\"rowDelay\",\"format\":\"float|fraction\"},{\"name\":\"direction\",\"flag\":[{\"name\":\"left_to_right\",\"value\":\"0x0\"},{\"name\":\"right_to_left\",\"value\":\"0x1\"},{\"name\":\"top_to_bottom\",\"value\":\"0x0\"},{\"name\":\"bottom_to_top\",\"value\":\"0x2\"}]},{\"name\":\"directionPriority\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"column\",\"value\":\"1\"},{\"name\":\"row\",\"value\":\"2\"}]}]},{\"name\":\"AccelerateInterpolator\",\"attr\":{\"name\":\"factor\",\"format\":\"float\"}},{\"name\":\"DecelerateInterpolator\",\"attr\":{\"name\":\"factor\"}},{\"name\":\"CycleInterpolator\",\"attr\":{\"name\":\"cycles\",\"format\":\"float\"}},{\"name\":\"AnticipateInterpolator\",\"attr\":{\"name\":\"tension\",\"format\":\"float\"}},{\"name\":\"OvershootInterpolator\",\"attr\":{\"name\":\"tension\"}},{\"name\":\"AnticipateOvershootInterpolator\",\"attr\":[{\"name\":\"tension\"},{\"name\":\"extraTension\",\"format\":\"float\"}]},{\"name\":\"PathInterpolator\",\"attr\":[{\"name\":\"controlX1\",\"format\":\"float\"},{\"name\":\"controlY1\",\"format\":\"float\"},{\"name\":\"controlX2\",\"format\":\"float\"},{\"name\":\"controlY2\",\"format\":\"float\"},{\"name\":\"pathData\"}]},{\"name\":\"Transition\",\"attr\":[{\"name\":\"duration\"},{\"name\":\"startDelay\",\"format\":\"integer\"},{\"name\":\"interpolator\"},{\"name\":\"matchOrder\",\"format\":\"string\"}]},{\"name\":\"EpicenterTranslateClipReveal\",\"attr\":[{\"name\":\"interpolatorX\",\"format\":\"reference\"},{\"name\":\"interpolatorY\",\"format\":\"reference\"},{\"name\":\"interpolatorZ\",\"format\":\"reference\"}]},{\"name\":\"Fade\",\"attr\":{\"name\":\"fadingMode\",\"enum\":[{\"name\":\"fade_in\",\"value\":\"1\"},{\"name\":\"fade_out\",\"value\":\"2\"},{\"name\":\"fade_in_out\",\"value\":\"3\"}]}},{\"name\":\"Slide\",\"attr\":{\"name\":\"slideEdge\",\"enum\":[{\"name\":\"left\",\"value\":\"0x03\"},{\"name\":\"top\",\"value\":\"0x30\"},{\"name\":\"right\",\"value\":\"0x05\"},{\"name\":\"bottom\",\"value\":\"0x50\"},{\"name\":\"start\",\"value\":\"0x00800003\"},{\"name\":\"end\",\"value\":\"0x00800005\"}]}},{\"name\":\"VisibilityTransition\",\"attr\":{\"name\":\"transitionVisibilityMode\",\"flag\":[{\"name\":\"mode_in\",\"value\":\"1\"},{\"name\":\"mode_out\",\"value\":\"2\"}]}},{\"name\":\"TransitionTarget\",\"attr\":[{\"name\":\"targetId\",\"format\":\"reference\"},{\"name\":\"excludeId\",\"format\":\"reference\"},{\"name\":\"targetClass\"},{\"name\":\"excludeClass\",\"format\":\"string\"},{\"name\":\"targetName\",\"format\":\"string\"},{\"name\":\"excludeName\",\"format\":\"string\"}]},{\"name\":\"TransitionSet\",\"attr\":{\"name\":\"transitionOrdering\",\"enum\":[{\"name\":\"together\",\"value\":\"0\"},{\"name\":\"sequential\",\"value\":\"1\"}]}},{\"name\":\"ChangeTransform\",\"attr\":[{\"name\":\"reparentWithOverlay\",\"format\":\"boolean\"},{\"name\":\"reparent\",\"format\":\"boolean\"}]},{\"name\":\"ChangeBounds\",\"attr\":{\"name\":\"resizeClip\",\"format\":\"boolean\"}},{\"name\":\"TransitionManager\",\"attr\":[{\"name\":\"transition\",\"format\":\"reference\"},{\"name\":\"fromScene\",\"format\":\"reference\"},{\"name\":\"toScene\",\"format\":\"reference\"}]},{\"name\":\"ArcMotion\",\"attr\":[{\"name\":\"minimumHorizontalAngle\",\"format\":\"float\"},{\"name\":\"minimumVerticalAngle\",\"format\":\"float\"},{\"name\":\"maximumAngle\",\"format\":\"float\"}]},{\"name\":\"PatternPathMotion\",\"attr\":{\"name\":\"patternPathData\",\"format\":\"string\"}},{\"name\":\"Animator\",\"attr\":[{\"name\":\"interpolator\"},{\"name\":\"duration\"},{\"name\":\"startOffset\"},{\"name\":\"repeatCount\"},{\"name\":\"repeatMode\"},{\"name\":\"valueFrom\",\"format\":\"float|integer|color|dimension|string\"},{\"name\":\"valueTo\",\"format\":\"float|integer|color|dimension|string\"},{\"name\":\"valueType\",\"enum\":[{\"name\":\"floatType\",\"value\":\"0\"},{\"name\":\"intType\",\"value\":\"1\"},{\"name\":\"pathType\",\"value\":\"2\"},{\"name\":\"colorType\",\"value\":\"3\"}]},{\"name\":\"removeBeforeMRelease\",\"format\":\"integer\"}]},{\"name\":\"PropertyValuesHolder\",\"attr\":[{\"name\":\"valueType\"},{\"name\":\"propertyName\"},{\"name\":\"valueFrom\"},{\"name\":\"valueTo\"}]},{\"name\":\"Keyframe\",\"attr\":[{\"name\":\"valueType\"},{\"name\":\"value\"},{\"name\":\"fraction\",\"format\":\"float\"},{\"name\":\"interpolator\"}]},{\"name\":\"PropertyAnimator\",\"attr\":[{\"name\":\"propertyName\",\"format\":\"string\"},{\"name\":\"propertyXName\",\"format\":\"string\"},{\"name\":\"propertyYName\",\"format\":\"string\"},{\"name\":\"pathData\"}]},{\"name\":\"AnimatorSet\",\"attr\":{\"name\":\"ordering\",\"enum\":[{\"name\":\"together\",\"value\":\"0\"},{\"name\":\"sequentially\",\"value\":\"1\"}]}},{\"name\":\"DrawableStates\",\"attr\":[{\"name\":\"state_focused\",\"format\":\"boolean\"},{\"name\":\"state_window_focused\",\"format\":\"boolean\"},{\"name\":\"state_enabled\",\"format\":\"boolean\"},{\"name\":\"state_checkable\",\"format\":\"boolean\"},{\"name\":\"state_checked\",\"format\":\"boolean\"},{\"name\":\"state_selected\",\"format\":\"boolean\"},{\"name\":\"state_pressed\",\"format\":\"boolean\"},{\"name\":\"state_activated\",\"format\":\"boolean\"},{\"name\":\"state_active\",\"format\":\"boolean\"},{\"name\":\"state_single\",\"format\":\"boolean\"},{\"name\":\"state_first\",\"format\":\"boolean\"},{\"name\":\"state_middle\",\"format\":\"boolean\"},{\"name\":\"state_last\",\"format\":\"boolean\"},{\"name\":\"state_accelerated\",\"format\":\"boolean\"},{\"name\":\"state_hovered\",\"format\":\"boolean\"},{\"name\":\"state_drag_can_accept\",\"format\":\"boolean\"},{\"name\":\"state_drag_hovered\",\"format\":\"boolean\"},{\"name\":\"state_accessibility_focused\",\"format\":\"boolean\"}]},{\"name\":\"ViewDrawableStates\",\"attr\":[{\"name\":\"state_pressed\"},{\"name\":\"state_focused\"},{\"name\":\"state_selected\"},{\"name\":\"state_window_focused\"},{\"name\":\"state_enabled\"},{\"name\":\"state_activated\"},{\"name\":\"state_accelerated\"},{\"name\":\"state_hovered\"},{\"name\":\"state_drag_can_accept\"},{\"name\":\"state_drag_hovered\"}]},{\"name\":\"MenuItemCheckedState\",\"attr\":[{\"name\":\"state_checkable\"},{\"name\":\"state_checked\"}]},{\"name\":\"MenuItemUncheckedState\",\"attr\":{\"name\":\"state_checkable\"}},{\"name\":\"MenuItemCheckedFocusedState\",\"attr\":[{\"name\":\"state_checkable\"},{\"name\":\"state_checked\"},{\"name\":\"state_focused\"}]},{\"name\":\"MenuItemUncheckedFocusedState\",\"attr\":[{\"name\":\"state_checkable\"},{\"name\":\"state_focused\"}]},{\"name\":\"ExpandableListChildIndicatorState\",\"attr\":{\"name\":\"state_last\"}},{\"name\":\"ExpandableListGroupIndicatorState\",\"attr\":[{\"name\":\"state_expanded\",\"format\":\"boolean\"},{\"name\":\"state_empty\",\"format\":\"boolean\"}]},{\"name\":\"PopupWindowBackgroundState\",\"attr\":{\"name\":\"state_above_anchor\",\"format\":\"boolean\"}},{\"name\":\"TextViewMultiLineBackgroundState\",\"attr\":{\"name\":\"state_multiline\",\"format\":\"boolean\"}},{\"name\":\"Searchable\",\"attr\":[{\"name\":\"icon\"},{\"name\":\"label\"},{\"name\":\"hint\"},{\"name\":\"searchButtonText\",\"format\":\"string\"},{\"name\":\"inputType\"},{\"name\":\"imeOptions\"},{\"name\":\"searchMode\",\"flag\":[{\"name\":\"showSearchLabelAsBadge\",\"value\":\"0x04\"},{\"name\":\"showSearchIconAsBadge\",\"value\":\"0x08\"},{\"name\":\"queryRewriteFromData\",\"value\":\"0x10\"},{\"name\":\"queryRewriteFromText\",\"value\":\"0x20\"}]},{\"name\":\"voiceSearchMode\",\"flag\":[{\"name\":\"showVoiceSearchButton\",\"value\":\"0x01\"},{\"name\":\"launchWebSearch\",\"value\":\"0x02\"},{\"name\":\"launchRecognizer\",\"value\":\"0x04\"}]},{\"name\":\"voiceLanguageModel\",\"format\":\"string\"},{\"name\":\"voicePromptText\",\"format\":\"string\"},{\"name\":\"voiceLanguage\",\"format\":\"string\"},{\"name\":\"voiceMaxResults\",\"format\":\"integer\"},{\"name\":\"searchSuggestAuthority\",\"format\":\"string\"},{\"name\":\"searchSuggestPath\",\"format\":\"string\"},{\"name\":\"searchSuggestSelection\",\"format\":\"string\"},{\"name\":\"searchSuggestIntentAction\",\"format\":\"string\"},{\"name\":\"searchSuggestIntentData\",\"format\":\"string\"},{\"name\":\"searchSuggestThreshold\",\"format\":\"integer\"},{\"name\":\"includeInGlobalSearch\",\"format\":\"boolean\"},{\"name\":\"queryAfterZeroResults\",\"format\":\"boolean\"},{\"name\":\"searchSettingsDescription\",\"format\":\"string\"},{\"name\":\"autoUrlDetect\",\"format\":\"boolean\"}]},{\"name\":\"SearchableActionKey\",\"attr\":[{\"name\":\"keycode\"},{\"name\":\"queryActionMsg\",\"format\":\"string\"},{\"name\":\"suggestActionMsg\",\"format\":\"string\"},{\"name\":\"suggestActionMsgColumn\",\"format\":\"string\"}]},{\"name\":\"MapView\",\"attr\":{\"name\":\"apiKey\",\"format\":\"string\"}},{\"name\":\"Menu\"},{\"name\":\"MenuGroup\",\"attr\":[{\"name\":\"id\"},{\"name\":\"menuCategory\",\"enum\":[{\"name\":\"container\",\"value\":\"0x00010000\"},{\"name\":\"system\",\"value\":\"0x00020000\"},{\"name\":\"secondary\",\"value\":\"0x00030000\"},{\"name\":\"alternative\",\"value\":\"0x00040000\"}]},{\"name\":\"orderInCategory\",\"format\":\"integer\"},{\"name\":\"checkableBehavior\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"all\",\"value\":\"1\"},{\"name\":\"single\",\"value\":\"2\"}]},{\"name\":\"visible\"},{\"name\":\"enabled\"}]},{\"name\":\"MenuItem\",\"attr\":[{\"name\":\"id\"},{\"name\":\"menuCategory\"},{\"name\":\"orderInCategory\"},{\"name\":\"title\",\"format\":\"string\"},{\"name\":\"titleCondensed\",\"format\":\"string\"},{\"name\":\"icon\"},{\"name\":\"iconTint\",\"format\":\"color\"},{\"name\":\"iconTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"alphabeticShortcut\",\"format\":\"string\"},{\"name\":\"alphabeticModifiers\",\"flag\":[{\"name\":\"META\",\"value\":\"0x10000\"},{\"name\":\"CTRL\",\"value\":\"0x1000\"},{\"name\":\"ALT\",\"value\":\"0x02\"},{\"name\":\"SHIFT\",\"value\":\"0x1\"},{\"name\":\"SYM\",\"value\":\"0x4\"},{\"name\":\"FUNCTION\",\"value\":\"0x8\"}]},{\"name\":\"numericShortcut\",\"format\":\"string\"},{\"name\":\"numericModifiers\",\"flag\":[{\"name\":\"META\",\"value\":\"0x10000\"},{\"name\":\"CTRL\",\"value\":\"0x1000\"},{\"name\":\"ALT\",\"value\":\"0x02\"},{\"name\":\"SHIFT\",\"value\":\"0x1\"},{\"name\":\"SYM\",\"value\":\"0x4\"},{\"name\":\"FUNCTION\",\"value\":\"0x8\"}]},{\"name\":\"checkable\",\"format\":\"boolean\"},{\"name\":\"checked\"},{\"name\":\"visible\"},{\"name\":\"enabled\"},{\"name\":\"onClick\"},{\"name\":\"showAsAction\",\"flag\":[{\"name\":\"never\",\"value\":\"0\"},{\"name\":\"ifRoom\",\"value\":\"1\"},{\"name\":\"always\",\"value\":\"2\"},{\"name\":\"withText\",\"value\":\"4\"},{\"name\":\"collapseActionView\",\"value\":\"8\"}]},{\"name\":\"actionLayout\",\"format\":\"reference\"},{\"name\":\"actionViewClass\",\"format\":\"string\"},{\"name\":\"actionProviderClass\",\"format\":\"string\"},{\"name\":\"contentDescription\",\"format\":\"string\"},{\"name\":\"tooltipText\",\"format\":\"string\"}]},{\"name\":\"ActivityChooserView\",\"attr\":[{\"name\":\"initialActivityCount\",\"format\":\"string\"},{\"name\":\"expandActivityOverflowButtonDrawable\",\"format\":\"reference\"}]},{\"name\":\"PreferenceGroup\",\"attr\":{\"name\":\"orderingFromXml\",\"format\":\"boolean\"}},{\"name\":\"PreferenceHeader\",\"attr\":[{\"name\":\"id\"},{\"name\":\"title\"},{\"name\":\"summary\",\"format\":\"string\"},{\"name\":\"breadCrumbTitle\",\"format\":\"string\"},{\"name\":\"breadCrumbShortTitle\",\"format\":\"string\"},{\"name\":\"icon\"},{\"name\":\"fragment\",\"format\":\"string\"}]},{\"name\":\"Preference\",\"attr\":[{\"name\":\"icon\"},{\"name\":\"key\",\"format\":\"string\"},{\"name\":\"title\"},{\"name\":\"summary\"},{\"name\":\"order\",\"format\":\"integer\"},{\"name\":\"fragment\"},{\"name\":\"layout\"},{\"name\":\"widgetLayout\",\"format\":\"reference\"},{\"name\":\"enabled\"},{\"name\":\"selectable\",\"format\":\"boolean\"},{\"name\":\"dependency\",\"format\":\"string\"},{\"name\":\"persistent\"},{\"name\":\"defaultValue\",\"format\":\"string|boolean|integer|reference|float\"},{\"name\":\"shouldDisableView\",\"format\":\"boolean\"},{\"name\":\"recycleEnabled\",\"format\":\"boolean\"},{\"name\":\"singleLineTitle\",\"format\":\"boolean\"},{\"name\":\"iconSpaceReserved\",\"format\":\"boolean\"}]},{\"name\":\"CheckBoxPreference\",\"attr\":[{\"name\":\"summaryOn\",\"format\":\"string\"},{\"name\":\"summaryOff\",\"format\":\"string\"},{\"name\":\"disableDependentsState\",\"format\":\"boolean\"}]},{\"name\":\"DialogPreference\",\"attr\":[{\"name\":\"dialogTitle\",\"format\":\"string\"},{\"name\":\"dialogMessage\",\"format\":\"string\"},{\"name\":\"dialogIcon\",\"format\":\"reference\"},{\"name\":\"positiveButtonText\",\"format\":\"string\"},{\"name\":\"negativeButtonText\",\"format\":\"string\"},{\"name\":\"dialogLayout\",\"format\":\"reference\"}]},{\"name\":\"ListPreference\",\"attr\":[{\"name\":\"entries\"},{\"name\":\"entryValues\",\"format\":\"reference\"}]},{\"name\":\"MultiSelectListPreference\",\"attr\":[{\"name\":\"entries\"},{\"name\":\"entryValues\"}]},{\"name\":\"RingtonePreference\",\"attr\":[{\"name\":\"ringtoneType\",\"flag\":[{\"name\":\"ringtone\",\"value\":\"1\"},{\"name\":\"notification\",\"value\":\"2\"},{\"name\":\"alarm\",\"value\":\"4\"},{\"name\":\"all\",\"value\":\"7\"}]},{\"name\":\"showDefault\",\"format\":\"boolean\"},{\"name\":\"showSilent\",\"format\":\"boolean\"}]},{\"name\":\"VolumePreference\",\"attr\":{\"name\":\"streamType\",\"enum\":[{\"name\":\"voice\",\"value\":\"0\"},{\"name\":\"system\",\"value\":\"1\"},{\"name\":\"ring\",\"value\":\"2\"},{\"name\":\"music\",\"value\":\"3\"},{\"name\":\"alarm\",\"value\":\"4\"}]}},{\"name\":\"InputMethodService\",\"attr\":[{\"name\":\"imeFullscreenBackground\",\"format\":\"reference|color\"},{\"name\":\"imeExtractEnterAnimation\",\"format\":\"reference\"},{\"name\":\"imeExtractExitAnimation\",\"format\":\"reference\"}]},{\"name\":\"VoiceInteractionSession\"},{\"name\":\"KeyboardView\",\"attr\":[{\"name\":\"keyboardViewStyle\",\"format\":\"reference\"},{\"name\":\"keyBackground\",\"format\":\"reference\"},{\"name\":\"keyTextSize\",\"format\":\"dimension\"},{\"name\":\"labelTextSize\",\"format\":\"dimension\"},{\"name\":\"keyTextColor\",\"format\":\"color\"},{\"name\":\"keyPreviewLayout\",\"format\":\"reference\"},{\"name\":\"keyPreviewOffset\",\"format\":\"dimension\"},{\"name\":\"keyPreviewHeight\",\"format\":\"dimension\"},{\"name\":\"verticalCorrection\",\"format\":\"dimension\"},{\"name\":\"popupLayout\",\"format\":\"reference\"},{\"name\":\"shadowColor\"},{\"name\":\"shadowRadius\"}]},{\"name\":\"KeyboardViewPreviewState\",\"attr\":{\"name\":\"state_long_pressable\",\"format\":\"boolean\"}},{\"name\":\"Keyboard\",\"attr\":[{\"name\":\"keyWidth\",\"format\":\"dimension|fraction\"},{\"name\":\"keyHeight\",\"format\":\"dimension|fraction\"},{\"name\":\"horizontalGap\",\"format\":\"dimension|fraction\"},{\"name\":\"verticalGap\",\"format\":\"dimension|fraction\"}]},{\"name\":\"Keyboard_Row\",\"attr\":[{\"name\":\"rowEdgeFlags\",\"flag\":[{\"name\":\"top\",\"value\":\"4\"},{\"name\":\"bottom\",\"value\":\"8\"}]},{\"name\":\"keyboardMode\",\"format\":\"reference\"}]},{\"name\":\"Keyboard_Key\",\"attr\":[{\"name\":\"codes\",\"format\":\"integer|string\"},{\"name\":\"popupKeyboard\",\"format\":\"reference\"},{\"name\":\"popupCharacters\",\"format\":\"string\"},{\"name\":\"keyEdgeFlags\",\"flag\":[{\"name\":\"left\",\"value\":\"1\"},{\"name\":\"right\",\"value\":\"2\"}]},{\"name\":\"isModifier\",\"format\":\"boolean\"},{\"name\":\"isSticky\",\"format\":\"boolean\"},{\"name\":\"isRepeatable\",\"format\":\"boolean\"},{\"name\":\"iconPreview\",\"format\":\"reference\"},{\"name\":\"keyOutputText\",\"format\":\"string\"},{\"name\":\"keyLabel\",\"format\":\"string\"},{\"name\":\"keyIcon\",\"format\":\"reference\"},{\"name\":\"keyboardMode\"}]},{\"name\":\"AppWidgetProviderInfo\",\"attr\":[{\"name\":\"minWidth\"},{\"name\":\"minHeight\"},{\"name\":\"minResizeWidth\",\"format\":\"dimension\"},{\"name\":\"minResizeHeight\",\"format\":\"dimension\"},{\"name\":\"maxResizeWidth\",\"format\":\"dimension\"},{\"name\":\"maxResizeHeight\",\"format\":\"dimension\"},{\"name\":\"targetCellWidth\",\"format\":\"integer\"},{\"name\":\"targetCellHeight\",\"format\":\"integer\"},{\"name\":\"updatePeriodMillis\",\"format\":\"integer\"},{\"name\":\"initialLayout\",\"format\":\"reference\"},{\"name\":\"initialKeyguardLayout\",\"format\":\"reference\"},{\"name\":\"configure\",\"format\":\"string\"},{\"name\":\"previewImage\",\"format\":\"reference\"},{\"name\":\"previewLayout\",\"format\":\"reference\"},{\"name\":\"autoAdvanceViewId\",\"format\":\"reference\"},{\"name\":\"resizeMode\",\"format\":\"integer\",\"flag\":[{\"name\":\"none\",\"value\":\"0x0\"},{\"name\":\"horizontal\",\"value\":\"0x1\"},{\"name\":\"vertical\",\"value\":\"0x2\"}]},{\"name\":\"widgetCategory\",\"format\":\"integer\",\"flag\":[{\"name\":\"home_screen\",\"value\":\"0x1\"},{\"name\":\"keyguard\",\"value\":\"0x2\"},{\"name\":\"searchbox\",\"value\":\"0x4\"}]},{\"name\":\"widgetFeatures\",\"format\":\"integer\",\"flag\":[{\"name\":\"reconfigurable\",\"value\":\"0x1\"},{\"name\":\"hide_from_picker\",\"value\":\"0x2\"},{\"name\":\"configuration_optional\",\"value\":\"0x4\"}]},{\"name\":\"description\"}]},{\"name\":\"WallpaperPreviewInfo\",\"attr\":{\"name\":\"staticWallpaperPreview\",\"format\":\"reference\"}},{\"name\":\"Fragment\",\"attr\":[{\"name\":\"name\"},{\"name\":\"id\"},{\"name\":\"tag\"},{\"name\":\"fragmentExitTransition\",\"format\":\"reference\"},{\"name\":\"fragmentEnterTransition\",\"format\":\"reference\"},{\"name\":\"fragmentSharedElementEnterTransition\",\"format\":\"reference\"},{\"name\":\"fragmentReturnTransition\",\"format\":\"reference\"},{\"name\":\"fragmentSharedElementReturnTransition\",\"format\":\"reference\"},{\"name\":\"fragmentReenterTransition\",\"format\":\"reference\"},{\"name\":\"fragmentAllowEnterTransitionOverlap\",\"format\":\"reference\"},{\"name\":\"fragmentAllowReturnTransitionOverlap\",\"format\":\"reference\"}]},{\"name\":\"DeviceAdmin\",\"attr\":{\"name\":\"visible\"}},{\"name\":\"Wallpaper\",\"attr\":[{\"name\":\"settingsActivity\"},{\"name\":\"thumbnail\",\"format\":\"reference\"},{\"name\":\"author\",\"format\":\"reference\"},{\"name\":\"description\"},{\"name\":\"contextUri\",\"format\":\"reference\"},{\"name\":\"contextDescription\",\"format\":\"reference\"},{\"name\":\"showMetadataInPreview\",\"format\":\"boolean\"},{\"name\":\"supportsAmbientMode\",\"format\":\"boolean\"},{\"name\":\"settingsSliceUri\",\"format\":\"string\"},{\"name\":\"supportsMultipleDisplays\",\"format\":\"boolean\"}]},{\"name\":\"Dream\",\"attr\":{\"name\":\"settingsActivity\"}},{\"name\":\"TrustAgent\",\"attr\":[{\"name\":\"settingsActivity\"},{\"name\":\"title\"},{\"name\":\"summary\"},{\"name\":\"unlockProfile\",\"format\":\"boolean\"}]},{\"name\":\"AccountAuthenticator\",\"attr\":[{\"name\":\"accountType\",\"format\":\"string\"},{\"name\":\"label\"},{\"name\":\"icon\"},{\"name\":\"smallIcon\",\"format\":\"reference\"},{\"name\":\"accountPreferences\",\"format\":\"reference\"},{\"name\":\"customTokens\",\"format\":\"boolean\"}]},{\"name\":\"SyncAdapter\",\"attr\":[{\"name\":\"contentAuthority\",\"format\":\"string\"},{\"name\":\"accountType\"},{\"name\":\"userVisible\",\"format\":\"boolean\"},{\"name\":\"supportsUploading\",\"format\":\"boolean\"},{\"name\":\"allowParallelSyncs\",\"format\":\"boolean\"},{\"name\":\"isAlwaysSyncable\",\"format\":\"boolean\"},{\"name\":\"settingsActivity\"}]},{\"name\":\"AutofillService\",\"attr\":[{\"name\":\"settingsActivity\"},{\"name\":\"passwordsActivity\",\"format\":\"string\"},{\"name\":\"supportsInlineSuggestions\",\"format\":\"boolean\"}]},{\"name\":\"AutofillService_CompatibilityPackage\",\"attr\":[{\"name\":\"name\"},{\"name\":\"maxLongVersionCode\",\"format\":\"string\"}]},{\"name\":\"OnDeviceRecognitionService\",\"attr\":{\"name\":\"settingsActivity\"}},{\"name\":\"ContentCaptureService\",\"attr\":{\"name\":\"settingsActivity\"}},{\"name\":\"TranslationService\",\"attr\":{\"name\":\"settingsActivity\"}},{\"name\":\"Icon\",\"attr\":[{\"name\":\"icon\"},{\"name\":\"mimeType\"}]},{\"name\":\"IconDefault\",\"attr\":{\"name\":\"icon\"}},{\"name\":\"ContactsDataKind\",\"attr\":[{\"name\":\"mimeType\"},{\"name\":\"icon\"},{\"name\":\"summaryColumn\",\"format\":\"string\"},{\"name\":\"detailColumn\",\"format\":\"string\"},{\"name\":\"detailSocialSummary\",\"format\":\"boolean\"},{\"name\":\"allContactsName\",\"format\":\"string\"}]},{\"name\":\"SlidingTab\",\"attr\":{\"name\":\"orientation\"}},{\"name\":\"GlowPadView\",\"attr\":[{\"name\":\"targetDescriptions\",\"format\":\"reference\"},{\"name\":\"directionDescriptions\",\"format\":\"reference\"}]},{\"name\":\"SettingInjectorService\",\"attr\":[{\"name\":\"title\"},{\"name\":\"icon\"},{\"name\":\"settingsActivity\"},{\"name\":\"userRestriction\",\"format\":\"string\"}]},{\"name\":\"LockPatternView\",\"attr\":[{\"name\":\"aspect\",\"format\":\"string\"},{\"name\":\"pathColor\",\"format\":\"color|reference\"},{\"name\":\"regularColor\",\"format\":\"color|reference\"},{\"name\":\"errorColor\",\"format\":\"color|reference\"},{\"name\":\"successColor\",\"format\":\"color|reference\"},{\"name\":\"dotColor\",\"format\":\"color|reference\"}]},{\"name\":\"QuickAccessWalletService\",\"attr\":[{\"name\":\"settingsActivity\",\"format\":\"string\"},{\"name\":\"targetActivity\",\"format\":\"string\"},{\"name\":\"shortcutLongLabel\"},{\"name\":\"shortcutShortLabel\"}]},{\"name\":\"RecognitionService\",\"attr\":[{\"name\":\"settingsActivity\"},{\"name\":\"selectableAsDefault\",\"format\":\"boolean\"}]},{\"name\":\"VoiceInteractionService\",\"attr\":[{\"name\":\"sessionService\",\"format\":\"string\"},{\"name\":\"recognitionService\",\"format\":\"string\"},{\"name\":\"settingsActivity\"},{\"name\":\"supportsAssist\",\"format\":\"boolean\"},{\"name\":\"supportsLaunchVoiceAssistFromKeyguard\",\"format\":\"boolean\"},{\"name\":\"supportsLocalInteraction\",\"format\":\"boolean\"},{\"name\":\"hotwordDetectionService\",\"format\":\"string\"}]},{\"name\":\"VoiceEnrollmentApplication\",\"attr\":[{\"name\":\"searchKeyphraseId\",\"format\":\"integer\"},{\"name\":\"searchKeyphrase\",\"format\":\"string\"},{\"name\":\"searchKeyphraseSupportedLocales\",\"format\":\"string\"},{\"name\":\"searchKeyphraseRecognitionFlags\",\"flag\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"voiceTrigger\",\"value\":\"0x1\"},{\"name\":\"userIdentification\",\"value\":\"0x2\"}]}]},{\"name\":\"ActionBar\",\"attr\":[{\"name\":\"navigationMode\",\"enum\":[{\"name\":\"normal\",\"value\":\"0\"},{\"name\":\"listMode\",\"value\":\"1\"},{\"name\":\"tabMode\",\"value\":\"2\"}]},{\"name\":\"displayOptions\",\"flag\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"useLogo\",\"value\":\"0x1\"},{\"name\":\"showHome\",\"value\":\"0x2\"},{\"name\":\"homeAsUp\",\"value\":\"0x4\"},{\"name\":\"showTitle\",\"value\":\"0x8\"},{\"name\":\"showCustom\",\"value\":\"0x10\"},{\"name\":\"disableHome\",\"value\":\"0x20\"}]},{\"name\":\"title\"},{\"name\":\"subtitle\",\"format\":\"string\"},{\"name\":\"titleTextStyle\",\"format\":\"reference\"},{\"name\":\"subtitleTextStyle\",\"format\":\"reference\"},{\"name\":\"icon\"},{\"name\":\"logo\"},{\"name\":\"divider\"},{\"name\":\"background\"},{\"name\":\"backgroundStacked\",\"format\":\"reference|color\"},{\"name\":\"backgroundSplit\",\"format\":\"reference|color\"},{\"name\":\"customNavigationLayout\",\"format\":\"reference\"},{\"name\":\"height\"},{\"name\":\"homeLayout\",\"format\":\"reference\"},{\"name\":\"progressBarStyle\"},{\"name\":\"indeterminateProgressStyle\",\"format\":\"reference\"},{\"name\":\"progressBarPadding\",\"format\":\"dimension\"},{\"name\":\"homeAsUpIndicator\"},{\"name\":\"itemPadding\",\"format\":\"dimension\"},{\"name\":\"hideOnContentScroll\",\"format\":\"boolean\"},{\"name\":\"contentInsetStart\",\"format\":\"dimension\"},{\"name\":\"contentInsetEnd\",\"format\":\"dimension\"},{\"name\":\"contentInsetLeft\",\"format\":\"dimension\"},{\"name\":\"contentInsetRight\",\"format\":\"dimension\"},{\"name\":\"contentInsetStartWithNavigation\",\"format\":\"dimension\"},{\"name\":\"contentInsetEndWithActions\",\"format\":\"dimension\"},{\"name\":\"elevation\"},{\"name\":\"popupTheme\"}]},{\"name\":\"ActionMode\",\"attr\":[{\"name\":\"titleTextStyle\"},{\"name\":\"subtitleTextStyle\"},{\"name\":\"background\"},{\"name\":\"backgroundSplit\"},{\"name\":\"height\"},{\"name\":\"closeItemLayout\",\"format\":\"reference\"}]},{\"name\":\"SearchView\",\"attr\":[{\"name\":\"layout\"},{\"name\":\"iconifiedByDefault\",\"format\":\"boolean\"},{\"name\":\"maxWidth\"},{\"name\":\"queryHint\",\"format\":\"string\"},{\"name\":\"defaultQueryHint\",\"format\":\"string\"},{\"name\":\"imeOptions\"},{\"name\":\"inputType\"},{\"name\":\"closeIcon\",\"format\":\"reference\"},{\"name\":\"goIcon\",\"format\":\"reference\"},{\"name\":\"searchIcon\",\"format\":\"reference\"},{\"name\":\"searchHintIcon\",\"format\":\"reference\"},{\"name\":\"voiceIcon\",\"format\":\"reference\"},{\"name\":\"commitIcon\",\"format\":\"reference\"},{\"name\":\"suggestionRowLayout\",\"format\":\"reference\"},{\"name\":\"queryBackground\",\"format\":\"reference\"},{\"name\":\"submitBackground\",\"format\":\"reference\"}]},{\"name\":\"Switch\",\"attr\":[{\"name\":\"thumb\"},{\"name\":\"thumbTint\"},{\"name\":\"thumbTintMode\"},{\"name\":\"track\",\"format\":\"reference\"},{\"name\":\"trackTint\",\"format\":\"color\"},{\"name\":\"trackTintMode\",\"enum\":[{\"name\":\"src_over\",\"value\":\"3\"},{\"name\":\"src_in\",\"value\":\"5\"},{\"name\":\"src_atop\",\"value\":\"9\"},{\"name\":\"multiply\",\"value\":\"14\"},{\"name\":\"screen\",\"value\":\"15\"},{\"name\":\"add\",\"value\":\"16\"}]},{\"name\":\"textOn\"},{\"name\":\"textOff\"},{\"name\":\"thumbTextPadding\",\"format\":\"dimension\"},{\"name\":\"switchTextAppearance\",\"format\":\"reference\"},{\"name\":\"switchMinWidth\",\"format\":\"dimension\"},{\"name\":\"switchPadding\",\"format\":\"dimension\"},{\"name\":\"splitTrack\"},{\"name\":\"showText\",\"format\":\"boolean\"}]},{\"name\":\"Pointer\",\"attr\":[{\"name\":\"pointerIconArrow\",\"format\":\"reference\"},{\"name\":\"pointerIconSpotHover\",\"format\":\"reference\"},{\"name\":\"pointerIconSpotTouch\",\"format\":\"reference\"},{\"name\":\"pointerIconSpotAnchor\",\"format\":\"reference\"},{\"name\":\"pointerIconContextMenu\",\"format\":\"reference\"},{\"name\":\"pointerIconHand\",\"format\":\"reference\"},{\"name\":\"pointerIconHelp\",\"format\":\"reference\"},{\"name\":\"pointerIconWait\",\"format\":\"reference\"},{\"name\":\"pointerIconCell\",\"format\":\"reference\"},{\"name\":\"pointerIconCrosshair\",\"format\":\"reference\"},{\"name\":\"pointerIconText\",\"format\":\"reference\"},{\"name\":\"pointerIconVerticalText\",\"format\":\"reference\"},{\"name\":\"pointerIconAlias\",\"format\":\"reference\"},{\"name\":\"pointerIconCopy\",\"format\":\"reference\"},{\"name\":\"pointerIconNodrop\",\"format\":\"reference\"},{\"name\":\"pointerIconAllScroll\",\"format\":\"reference\"},{\"name\":\"pointerIconHorizontalDoubleArrow\",\"format\":\"reference\"},{\"name\":\"pointerIconVerticalDoubleArrow\",\"format\":\"reference\"},{\"name\":\"pointerIconTopRightDiagonalDoubleArrow\",\"format\":\"reference\"},{\"name\":\"pointerIconTopLeftDiagonalDoubleArrow\",\"format\":\"reference\"},{\"name\":\"pointerIconZoomIn\",\"format\":\"reference\"},{\"name\":\"pointerIconZoomOut\",\"format\":\"reference\"},{\"name\":\"pointerIconGrab\",\"format\":\"reference\"},{\"name\":\"pointerIconGrabbing\",\"format\":\"reference\"}]},{\"name\":\"PointerIcon\",\"attr\":[{\"name\":\"bitmap\",\"format\":\"reference\"},{\"name\":\"hotSpotX\",\"format\":\"dimension\"},{\"name\":\"hotSpotY\",\"format\":\"dimension\"}]},{\"name\":\"Storage\",\"attr\":[{\"name\":\"mountPoint\",\"format\":\"string\"},{\"name\":\"storageDescription\",\"format\":\"string\"},{\"name\":\"primary\",\"format\":\"boolean\"},{\"name\":\"removable\",\"format\":\"boolean\"},{\"name\":\"emulated\",\"format\":\"boolean\"},{\"name\":\"mtpReserve\",\"format\":\"integer\"},{\"name\":\"allowMassStorage\",\"format\":\"boolean\"},{\"name\":\"maxFileSize\",\"format\":\"integer\"}]},{\"name\":\"SwitchPreference\",\"attr\":[{\"name\":\"summaryOn\"},{\"name\":\"summaryOff\"},{\"name\":\"switchTextOn\",\"format\":\"string\"},{\"name\":\"switchTextOff\",\"format\":\"string\"},{\"name\":\"disableDependentsState\"}]},{\"name\":\"SeekBarPreference\",\"attr\":[{\"name\":\"layout\"},{\"name\":\"adjustable\",\"format\":\"boolean\"},{\"name\":\"showSeekBarValue\",\"format\":\"boolean\"}]},{\"name\":\"PreferenceFragment\",\"attr\":[{\"name\":\"layout\"},{\"name\":\"divider\"}]},{\"name\":\"PreferenceScreen\",\"attr\":[{\"name\":\"screenLayout\",\"format\":\"reference\"},{\"name\":\"divider\"}]},{\"name\":\"PreferenceActivity\",\"attr\":[{\"name\":\"layout\"},{\"name\":\"headerLayout\",\"format\":\"reference\"},{\"name\":\"headerRemoveIconIfEmpty\",\"format\":\"boolean\"}]},{\"name\":\"TextToSpeechEngine\",\"attr\":{\"name\":\"settingsActivity\"}},{\"name\":\"KeyboardLayout\",\"attr\":[{\"name\":\"name\"},{\"name\":\"label\"},{\"name\":\"keyboardLayout\",\"format\":\"reference\"},{\"name\":\"locale\",\"format\":\"string\"},{\"name\":\"vendorId\",\"format\":\"integer\"},{\"name\":\"productId\",\"format\":\"integer\"}]},{\"name\":\"MediaRouteButton\",\"attr\":[{\"name\":\"externalRouteEnabledDrawable\",\"format\":\"reference\"},{\"name\":\"mediaRouteTypes\",\"format\":\"integer\",\"enum\":[{\"name\":\"liveAudio\",\"value\":\"0x1\"},{\"name\":\"user\",\"value\":\"0x800000\"}]},{\"name\":\"minWidth\"},{\"name\":\"minHeight\"}]},{\"name\":\"PagedView\",\"attr\":[{\"name\":\"pageSpacing\",\"format\":\"dimension\"},{\"name\":\"scrollIndicatorPaddingLeft\",\"format\":\"dimension\"},{\"name\":\"scrollIndicatorPaddingRight\",\"format\":\"dimension\"}]},{\"name\":\"KeyguardGlowStripView\",\"attr\":[{\"name\":\"dotSize\",\"format\":\"dimension\"},{\"name\":\"numDots\",\"format\":\"integer\"},{\"name\":\"glowDot\",\"format\":\"reference\"},{\"name\":\"leftToRight\",\"format\":\"boolean\"}]},{\"name\":\"FragmentBreadCrumbs\",\"attr\":[{\"name\":\"gravity\"},{\"name\":\"itemLayout\",\"format\":\"reference\"},{\"name\":\"itemColor\",\"format\":\"color|reference\"}]},{\"name\":\"Toolbar\",\"attr\":[{\"name\":\"titleTextAppearance\",\"format\":\"reference\"},{\"name\":\"subtitleTextAppearance\",\"format\":\"reference\"},{\"name\":\"title\"},{\"name\":\"subtitle\"},{\"name\":\"gravity\"},{\"name\":\"titleMargin\",\"format\":\"dimension\"},{\"name\":\"titleMarginStart\",\"format\":\"dimension\"},{\"name\":\"titleMarginEnd\",\"format\":\"dimension\"},{\"name\":\"titleMarginTop\",\"format\":\"dimension\"},{\"name\":\"titleMarginBottom\",\"format\":\"dimension\"},{\"name\":\"contentInsetStart\"},{\"name\":\"contentInsetEnd\"},{\"name\":\"contentInsetLeft\"},{\"name\":\"contentInsetRight\"},{\"name\":\"contentInsetStartWithNavigation\"},{\"name\":\"contentInsetEndWithActions\"},{\"name\":\"maxButtonHeight\",\"format\":\"dimension\"},{\"name\":\"navigationButtonStyle\",\"format\":\"reference\"},{\"name\":\"buttonGravity\",\"flag\":[{\"name\":\"top\",\"value\":\"0x30\"},{\"name\":\"bottom\",\"value\":\"0x50\"}]},{\"name\":\"collapseIcon\",\"format\":\"reference\"},{\"name\":\"collapseContentDescription\",\"format\":\"string\"},{\"name\":\"popupTheme\",\"format\":\"reference\"},{\"name\":\"navigationIcon\",\"format\":\"reference\"},{\"name\":\"navigationContentDescription\",\"format\":\"string\"},{\"name\":\"logo\"},{\"name\":\"logoDescription\",\"format\":\"string\"},{\"name\":\"titleTextColor\",\"format\":\"color\"},{\"name\":\"subtitleTextColor\",\"format\":\"color\"}]},{\"name\":\"Toolbar_LayoutParams\",\"attr\":{\"name\":\"layout_gravity\"}},{\"name\":\"ActionBar_LayoutParams\",\"attr\":{\"name\":\"layout_gravity\"}},{\"name\":\"EdgeEffect\",\"attr\":{\"name\":\"colorEdgeEffect\"}},{\"name\":\"TvInputService\",\"attr\":[{\"name\":\"setupActivity\",\"format\":\"string\"},{\"name\":\"settingsActivity\"},{\"name\":\"canRecord\",\"format\":\"boolean\"},{\"name\":\"tunerCount\",\"format\":\"integer\"},{\"name\":\"canPauseRecording\",\"format\":\"boolean\"}]},{\"name\":\"RatingSystemDefinition\",\"attr\":[{\"name\":\"name\"},{\"name\":\"title\"},{\"name\":\"description\"},{\"name\":\"country\",\"format\":\"string\"}]},{\"name\":\"RatingDefinition\",\"attr\":[{\"name\":\"name\"},{\"name\":\"title\"},{\"name\":\"description\"},{\"name\":\"contentAgeHint\",\"format\":\"integer\"}]},{\"name\":\"ResolverDrawerLayout\",\"attr\":[{\"name\":\"maxWidth\"},{\"name\":\"maxCollapsedHeight\",\"format\":\"dimension\"},{\"name\":\"maxCollapsedHeightSmall\",\"format\":\"dimension\"},{\"name\":\"showAtTop\",\"format\":\"boolean\"}]},{\"name\":\"MessagingLinearLayout\",\"attr\":{\"name\":\"spacing\"}},{\"name\":\"DateTimeView\",\"attr\":{\"name\":\"showRelative\",\"format\":\"boolean\"}},{\"name\":\"ResolverDrawerLayout_LayoutParams\",\"attr\":[{\"name\":\"layout_alwaysShow\",\"format\":\"boolean\"},{\"name\":\"layout_ignoreOffset\",\"format\":\"boolean\"},{\"name\":\"layout_gravity\"},{\"name\":\"layout_hasNestedScrollIndicator\",\"format\":\"boolean\"},{\"name\":\"layout_maxHeight\",\"format\":\"dimension\"}]},{\"name\":\"Lighting\",\"attr\":[{\"name\":\"lightY\"},{\"name\":\"lightZ\"},{\"name\":\"lightRadius\"},{\"name\":\"ambientShadowAlpha\"},{\"name\":\"spotShadowAlpha\"}]},{\"name\":\"RestrictionEntry\",\"attr\":[{\"name\":\"key\"},{\"name\":\"restrictionType\",\"enum\":[{\"name\":\"hidden\",\"value\":\"0\"},{\"name\":\"bool\",\"value\":\"1\"},{\"name\":\"choice\",\"value\":\"2\"},{\"name\":\"multi-select\",\"value\":\"4\"},{\"name\":\"integer\",\"value\":\"5\"},{\"name\":\"string\",\"value\":\"6\"},{\"name\":\"bundle\",\"value\":\"7\"},{\"name\":\"bundle_array\",\"value\":\"8\"}]},{\"name\":\"title\"},{\"name\":\"description\"},{\"name\":\"defaultValue\"},{\"name\":\"entries\"},{\"name\":\"entryValues\"}]},{\"name\":\"GradientColor\",\"attr\":[{\"name\":\"startColor\"},{\"name\":\"centerColor\"},{\"name\":\"endColor\"},{\"name\":\"type\"},{\"name\":\"gradientRadius\"},{\"name\":\"centerX\"},{\"name\":\"centerY\"},{\"name\":\"startX\",\"format\":\"float\"},{\"name\":\"startY\",\"format\":\"float\"},{\"name\":\"endX\",\"format\":\"float\"},{\"name\":\"endY\",\"format\":\"float\"},{\"name\":\"tileMode\"}]},{\"name\":\"GradientColorItem\",\"attr\":[{\"name\":\"offset\",\"format\":\"float\"},{\"name\":\"color\"}]},{\"name\":\"ActivityTaskDescription\",\"attr\":[{\"name\":\"colorPrimary\"},{\"name\":\"colorBackground\"},{\"name\":\"colorBackgroundFloating\"},{\"name\":\"statusBarColor\"},{\"name\":\"navigationBarColor\"},{\"name\":\"enforceStatusBarContrast\"},{\"name\":\"enforceNavigationBarContrast\"}]},{\"name\":\"Shortcut\",\"attr\":[{\"name\":\"shortcutId\",\"format\":\"string\"},{\"name\":\"enabled\"},{\"name\":\"icon\"},{\"name\":\"shortcutShortLabel\",\"format\":\"reference\"},{\"name\":\"shortcutLongLabel\",\"format\":\"reference\"},{\"name\":\"shortcutDisabledMessage\",\"format\":\"reference\"},{\"name\":\"splashScreenTheme\",\"format\":\"reference\"}]},{\"name\":\"ShortcutCategories\",\"attr\":{\"name\":\"name\"}},{\"name\":\"FontFamilyFont\",\"attr\":[{\"name\":\"fontStyle\",\"enum\":[{\"name\":\"normal\",\"value\":\"0\"},{\"name\":\"italic\",\"value\":\"1\"}]},{\"name\":\"font\",\"format\":\"reference\"},{\"name\":\"fontWeight\",\"format\":\"integer\"},{\"name\":\"ttcIndex\",\"format\":\"integer\"},{\"name\":\"fontVariationSettings\",\"format\":\"string\"}]},{\"name\":\"FontFamily\",\"attr\":[{\"name\":\"fontProviderAuthority\",\"format\":\"string\"},{\"name\":\"fontProviderPackage\",\"format\":\"string\"},{\"name\":\"fontProviderQuery\",\"format\":\"string\"},{\"name\":\"fontProviderCerts\",\"format\":\"reference\"},{\"name\":\"fontProviderSystemFontFamily\",\"format\":\"string\"}]},{\"name\":\"VideoView2\",\"attr\":[{\"name\":\"enableControlView\",\"format\":\"boolean\"},{\"name\":\"enableSubtitle\",\"format\":\"boolean\"},{\"name\":\"viewType\",\"format\":\"enum\",\"enum\":[{\"name\":\"surfaceView\",\"value\":\"0\"},{\"name\":\"textureView\",\"value\":\"1\"}]}]},{\"name\":\"RecyclerView\",\"attr\":[{\"name\":\"layoutManager\",\"format\":\"string\"},{\"name\":\"orientation\"},{\"name\":\"descendantFocusability\"},{\"name\":\"spanCount\",\"format\":\"integer\"},{\"name\":\"reverseLayout\",\"format\":\"boolean\"},{\"name\":\"stackFromEnd\",\"format\":\"boolean\"}]},{\"name\":\"NotificationTheme\",\"attr\":[{\"name\":\"notificationHeaderStyle\",\"format\":\"reference\"},{\"name\":\"notificationHeaderTextAppearance\",\"format\":\"reference\"},{\"name\":\"notificationHeaderIconSize\",\"format\":\"dimension\"},{\"name\":\"notificationHeaderAppNameVisibility\",\"format\":\"enum\",\"enum\":[{\"name\":\"visible\",\"value\":\"0\"},{\"name\":\"invisible\",\"value\":\"1\"},{\"name\":\"gone\",\"value\":\"2\"}]}]},{\"name\":\"Magnifier\",\"attr\":[{\"name\":\"magnifierWidth\",\"format\":\"dimension\"},{\"name\":\"magnifierHeight\",\"format\":\"dimension\"},{\"name\":\"magnifierZoom\",\"format\":\"float\"},{\"name\":\"magnifierElevation\",\"format\":\"dimension\"},{\"name\":\"magnifierVerticalOffset\",\"format\":\"dimension\"},{\"name\":\"magnifierHorizontalOffset\",\"format\":\"dimension\"},{\"name\":\"magnifierColorOverlay\",\"format\":\"color\"}]}],\"eat-comment\":[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\"attr\":[{\"name\":\"textSize\",\"format\":\"dimension\"},{\"name\":\"fontFamily\",\"format\":\"string\"},{\"name\":\"typeface\",\"enum\":[{\"name\":\"normal\",\"value\":\"0\"},{\"name\":\"sans\",\"value\":\"1\"},{\"name\":\"serif\",\"value\":\"2\"},{\"name\":\"monospace\",\"value\":\"3\"}]},{\"name\":\"textStyle\",\"flag\":[{\"name\":\"normal\",\"value\":\"0\"},{\"name\":\"bold\",\"value\":\"1\"},{\"name\":\"italic\",\"value\":\"2\"}]},{\"name\":\"textColor\",\"format\":\"reference|color\"},{\"name\":\"textColorHighlight\",\"format\":\"reference|color\"},{\"name\":\"textColorHint\",\"format\":\"reference|color\"},{\"name\":\"textColorLink\",\"format\":\"reference|color\"},{\"name\":\"textCursorDrawable\",\"format\":\"reference\"},{\"name\":\"textIsSelectable\",\"format\":\"boolean\"},{\"name\":\"ellipsize\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"start\",\"value\":\"1\"},{\"name\":\"middle\",\"value\":\"2\"},{\"name\":\"end\",\"value\":\"3\"},{\"name\":\"marquee\",\"value\":\"4\"}]},{\"name\":\"inputType\",\"flag\":[{\"name\":\"none\",\"value\":\"0x00000000\"},{\"name\":\"text\",\"value\":\"0x00000001\"},{\"name\":\"textCapCharacters\",\"value\":\"0x00001001\"},{\"name\":\"textCapWords\",\"value\":\"0x00002001\"},{\"name\":\"textCapSentences\",\"value\":\"0x00004001\"},{\"name\":\"textAutoCorrect\",\"value\":\"0x00008001\"},{\"name\":\"textAutoComplete\",\"value\":\"0x00010001\"},{\"name\":\"textMultiLine\",\"value\":\"0x00020001\"},{\"name\":\"textImeMultiLine\",\"value\":\"0x00040001\"},{\"name\":\"textNoSuggestions\",\"value\":\"0x00080001\"},{\"name\":\"textUri\",\"value\":\"0x00000011\"},{\"name\":\"textEmailAddress\",\"value\":\"0x00000021\"},{\"name\":\"textEmailSubject\",\"value\":\"0x00000031\"},{\"name\":\"textShortMessage\",\"value\":\"0x00000041\"},{\"name\":\"textLongMessage\",\"value\":\"0x00000051\"},{\"name\":\"textPersonName\",\"value\":\"0x00000061\"},{\"name\":\"textPostalAddress\",\"value\":\"0x00000071\"},{\"name\":\"textPassword\",\"value\":\"0x00000081\"},{\"name\":\"textVisiblePassword\",\"value\":\"0x00000091\"},{\"name\":\"textWebEditText\",\"value\":\"0x000000a1\"},{\"name\":\"textFilter\",\"value\":\"0x000000b1\"},{\"name\":\"textPhonetic\",\"value\":\"0x000000c1\"},{\"name\":\"textWebEmailAddress\",\"value\":\"0x000000d1\"},{\"name\":\"textWebPassword\",\"value\":\"0x000000e1\"},{\"name\":\"number\",\"value\":\"0x00000002\"},{\"name\":\"numberSigned\",\"value\":\"0x00001002\"},{\"name\":\"numberDecimal\",\"value\":\"0x00002002\"},{\"name\":\"numberPassword\",\"value\":\"0x00000012\"},{\"name\":\"phone\",\"value\":\"0x00000003\"},{\"name\":\"datetime\",\"value\":\"0x00000004\"},{\"name\":\"date\",\"value\":\"0x00000014\"},{\"name\":\"time\",\"value\":\"0x00000024\"}]},{\"name\":\"imeOptions\",\"flag\":[{\"name\":\"normal\",\"value\":\"0x00000000\"},{\"name\":\"actionUnspecified\",\"value\":\"0x00000000\"},{\"name\":\"actionNone\",\"value\":\"0x00000001\"},{\"name\":\"actionGo\",\"value\":\"0x00000002\"},{\"name\":\"actionSearch\",\"value\":\"0x00000003\"},{\"name\":\"actionSend\",\"value\":\"0x00000004\"},{\"name\":\"actionNext\",\"value\":\"0x00000005\"},{\"name\":\"actionDone\",\"value\":\"0x00000006\"},{\"name\":\"actionPrevious\",\"value\":\"0x00000007\"},{\"name\":\"flagNoPersonalizedLearning\",\"value\":\"0x1000000\"},{\"name\":\"flagNoFullscreen\",\"value\":\"0x2000000\"},{\"name\":\"flagNavigatePrevious\",\"value\":\"0x4000000\"},{\"name\":\"flagNavigateNext\",\"value\":\"0x8000000\"},{\"name\":\"flagNoExtractUi\",\"value\":\"0x10000000\"},{\"name\":\"flagNoAccessoryAction\",\"value\":\"0x20000000\"},{\"name\":\"flagNoEnterAction\",\"value\":\"0x40000000\"},{\"name\":\"flagForceAscii\",\"value\":\"0x80000000\"}]},{\"name\":\"x\",\"format\":\"dimension\"},{\"name\":\"y\",\"format\":\"dimension\"},{\"name\":\"gravity\",\"flag\":[{\"name\":\"top\",\"value\":\"0x30\"},{\"name\":\"bottom\",\"value\":\"0x50\"},{\"name\":\"left\",\"value\":\"0x03\"},{\"name\":\"right\",\"value\":\"0x05\"},{\"name\":\"center_vertical\",\"value\":\"0x10\"},{\"name\":\"fill_vertical\",\"value\":\"0x70\"},{\"name\":\"center_horizontal\",\"value\":\"0x01\"},{\"name\":\"fill_horizontal\",\"value\":\"0x07\"},{\"name\":\"center\",\"value\":\"0x11\"},{\"name\":\"fill\",\"value\":\"0x77\"},{\"name\":\"clip_vertical\",\"value\":\"0x80\"},{\"name\":\"clip_horizontal\",\"value\":\"0x08\"},{\"name\":\"start\",\"value\":\"0x00800003\"},{\"name\":\"end\",\"value\":\"0x00800005\"}]},{\"name\":\"autoLink\",\"flag\":[{\"name\":\"none\",\"value\":\"0x00\"},{\"name\":\"web\",\"value\":\"0x01\"},{\"name\":\"email\",\"value\":\"0x02\"},{\"name\":\"phone\",\"value\":\"0x04\"},{\"name\":\"map\",\"value\":\"0x08\"},{\"name\":\"all\",\"value\":\"0x0f\"}]},{\"name\":\"entries\",\"format\":\"reference\"},{\"name\":\"layout_gravity\",\"flag\":[{\"name\":\"top\",\"value\":\"0x30\"},{\"name\":\"bottom\",\"value\":\"0x50\"},{\"name\":\"left\",\"value\":\"0x03\"},{\"name\":\"right\",\"value\":\"0x05\"},{\"name\":\"center_vertical\",\"value\":\"0x10\"},{\"name\":\"fill_vertical\",\"value\":\"0x70\"},{\"name\":\"center_horizontal\",\"value\":\"0x01\"},{\"name\":\"fill_horizontal\",\"value\":\"0x07\"},{\"name\":\"center\",\"value\":\"0x11\"},{\"name\":\"fill\",\"value\":\"0x77\"},{\"name\":\"clip_vertical\",\"value\":\"0x80\"},{\"name\":\"clip_horizontal\",\"value\":\"0x08\"},{\"name\":\"start\",\"value\":\"0x00800003\"},{\"name\":\"end\",\"value\":\"0x00800005\"}]},{\"name\":\"orientation\",\"enum\":[{\"name\":\"horizontal\",\"value\":\"0\"},{\"name\":\"vertical\",\"value\":\"1\"}]},{\"name\":\"alignmentMode\",\"enum\":[{\"name\":\"alignBounds\",\"value\":\"0\"},{\"name\":\"alignMargins\",\"value\":\"1\"}]},{\"name\":\"keycode\",\"enum\":[{\"name\":\"KEYCODE_UNKNOWN\",\"value\":\"0\"},{\"name\":\"KEYCODE_SOFT_LEFT\",\"value\":\"1\"},{\"name\":\"KEYCODE_SOFT_RIGHT\",\"value\":\"2\"},{\"name\":\"KEYCODE_HOME\",\"value\":\"3\"},{\"name\":\"KEYCODE_BACK\",\"value\":\"4\"},{\"name\":\"KEYCODE_CALL\",\"value\":\"5\"},{\"name\":\"KEYCODE_ENDCALL\",\"value\":\"6\"},{\"name\":\"KEYCODE_0\",\"value\":\"7\"},{\"name\":\"KEYCODE_1\",\"value\":\"8\"},{\"name\":\"KEYCODE_2\",\"value\":\"9\"},{\"name\":\"KEYCODE_3\",\"value\":\"10\"},{\"name\":\"KEYCODE_4\",\"value\":\"11\"},{\"name\":\"KEYCODE_5\",\"value\":\"12\"},{\"name\":\"KEYCODE_6\",\"value\":\"13\"},{\"name\":\"KEYCODE_7\",\"value\":\"14\"},{\"name\":\"KEYCODE_8\",\"value\":\"15\"},{\"name\":\"KEYCODE_9\",\"value\":\"16\"},{\"name\":\"KEYCODE_STAR\",\"value\":\"17\"},{\"name\":\"KEYCODE_POUND\",\"value\":\"18\"},{\"name\":\"KEYCODE_DPAD_UP\",\"value\":\"19\"},{\"name\":\"KEYCODE_DPAD_DOWN\",\"value\":\"20\"},{\"name\":\"KEYCODE_DPAD_LEFT\",\"value\":\"21\"},{\"name\":\"KEYCODE_DPAD_RIGHT\",\"value\":\"22\"},{\"name\":\"KEYCODE_DPAD_CENTER\",\"value\":\"23\"},{\"name\":\"KEYCODE_VOLUME_UP\",\"value\":\"24\"},{\"name\":\"KEYCODE_VOLUME_DOWN\",\"value\":\"25\"},{\"name\":\"KEYCODE_POWER\",\"value\":\"26\"},{\"name\":\"KEYCODE_CAMERA\",\"value\":\"27\"},{\"name\":\"KEYCODE_CLEAR\",\"value\":\"28\"},{\"name\":\"KEYCODE_A\",\"value\":\"29\"},{\"name\":\"KEYCODE_B\",\"value\":\"30\"},{\"name\":\"KEYCODE_C\",\"value\":\"31\"},{\"name\":\"KEYCODE_D\",\"value\":\"32\"},{\"name\":\"KEYCODE_E\",\"value\":\"33\"},{\"name\":\"KEYCODE_F\",\"value\":\"34\"},{\"name\":\"KEYCODE_G\",\"value\":\"35\"},{\"name\":\"KEYCODE_H\",\"value\":\"36\"},{\"name\":\"KEYCODE_I\",\"value\":\"37\"},{\"name\":\"KEYCODE_J\",\"value\":\"38\"},{\"name\":\"KEYCODE_K\",\"value\":\"39\"},{\"name\":\"KEYCODE_L\",\"value\":\"40\"},{\"name\":\"KEYCODE_M\",\"value\":\"41\"},{\"name\":\"KEYCODE_N\",\"value\":\"42\"},{\"name\":\"KEYCODE_O\",\"value\":\"43\"},{\"name\":\"KEYCODE_P\",\"value\":\"44\"},{\"name\":\"KEYCODE_Q\",\"value\":\"45\"},{\"name\":\"KEYCODE_R\",\"value\":\"46\"},{\"name\":\"KEYCODE_S\",\"value\":\"47\"},{\"name\":\"KEYCODE_T\",\"value\":\"48\"},{\"name\":\"KEYCODE_U\",\"value\":\"49\"},{\"name\":\"KEYCODE_V\",\"value\":\"50\"},{\"name\":\"KEYCODE_W\",\"value\":\"51\"},{\"name\":\"KEYCODE_X\",\"value\":\"52\"},{\"name\":\"KEYCODE_Y\",\"value\":\"53\"},{\"name\":\"KEYCODE_Z\",\"value\":\"54\"},{\"name\":\"KEYCODE_COMMA\",\"value\":\"55\"},{\"name\":\"KEYCODE_PERIOD\",\"value\":\"56\"},{\"name\":\"KEYCODE_ALT_LEFT\",\"value\":\"57\"},{\"name\":\"KEYCODE_ALT_RIGHT\",\"value\":\"58\"},{\"name\":\"KEYCODE_SHIFT_LEFT\",\"value\":\"59\"},{\"name\":\"KEYCODE_SHIFT_RIGHT\",\"value\":\"60\"},{\"name\":\"KEYCODE_TAB\",\"value\":\"61\"},{\"name\":\"KEYCODE_SPACE\",\"value\":\"62\"},{\"name\":\"KEYCODE_SYM\",\"value\":\"63\"},{\"name\":\"KEYCODE_EXPLORER\",\"value\":\"64\"},{\"name\":\"KEYCODE_ENVELOPE\",\"value\":\"65\"},{\"name\":\"KEYCODE_ENTER\",\"value\":\"66\"},{\"name\":\"KEYCODE_DEL\",\"value\":\"67\"},{\"name\":\"KEYCODE_GRAVE\",\"value\":\"68\"},{\"name\":\"KEYCODE_MINUS\",\"value\":\"69\"},{\"name\":\"KEYCODE_EQUALS\",\"value\":\"70\"},{\"name\":\"KEYCODE_LEFT_BRACKET\",\"value\":\"71\"},{\"name\":\"KEYCODE_RIGHT_BRACKET\",\"value\":\"72\"},{\"name\":\"KEYCODE_BACKSLASH\",\"value\":\"73\"},{\"name\":\"KEYCODE_SEMICOLON\",\"value\":\"74\"},{\"name\":\"KEYCODE_APOSTROPHE\",\"value\":\"75\"},{\"name\":\"KEYCODE_SLASH\",\"value\":\"76\"},{\"name\":\"KEYCODE_AT\",\"value\":\"77\"},{\"name\":\"KEYCODE_NUM\",\"value\":\"78\"},{\"name\":\"KEYCODE_HEADSETHOOK\",\"value\":\"79\"},{\"name\":\"KEYCODE_FOCUS\",\"value\":\"80\"},{\"name\":\"KEYCODE_PLUS\",\"value\":\"81\"},{\"name\":\"KEYCODE_MENU\",\"value\":\"82\"},{\"name\":\"KEYCODE_NOTIFICATION\",\"value\":\"83\"},{\"name\":\"KEYCODE_SEARCH\",\"value\":\"84\"},{\"name\":\"KEYCODE_MEDIA_PLAY_PAUSE\",\"value\":\"85\"},{\"name\":\"KEYCODE_MEDIA_STOP\",\"value\":\"86\"},{\"name\":\"KEYCODE_MEDIA_NEXT\",\"value\":\"87\"},{\"name\":\"KEYCODE_MEDIA_PREVIOUS\",\"value\":\"88\"},{\"name\":\"KEYCODE_MEDIA_REWIND\",\"value\":\"89\"},{\"name\":\"KEYCODE_MEDIA_FAST_FORWARD\",\"value\":\"90\"},{\"name\":\"KEYCODE_MUTE\",\"value\":\"91\"},{\"name\":\"KEYCODE_PAGE_UP\",\"value\":\"92\"},{\"name\":\"KEYCODE_PAGE_DOWN\",\"value\":\"93\"},{\"name\":\"KEYCODE_PICTSYMBOLS\",\"value\":\"94\"},{\"name\":\"KEYCODE_SWITCH_CHARSET\",\"value\":\"95\"},{\"name\":\"KEYCODE_BUTTON_A\",\"value\":\"96\"},{\"name\":\"KEYCODE_BUTTON_B\",\"value\":\"97\"},{\"name\":\"KEYCODE_BUTTON_C\",\"value\":\"98\"},{\"name\":\"KEYCODE_BUTTON_X\",\"value\":\"99\"},{\"name\":\"KEYCODE_BUTTON_Y\",\"value\":\"100\"},{\"name\":\"KEYCODE_BUTTON_Z\",\"value\":\"101\"},{\"name\":\"KEYCODE_BUTTON_L1\",\"value\":\"102\"},{\"name\":\"KEYCODE_BUTTON_R1\",\"value\":\"103\"},{\"name\":\"KEYCODE_BUTTON_L2\",\"value\":\"104\"},{\"name\":\"KEYCODE_BUTTON_R2\",\"value\":\"105\"},{\"name\":\"KEYCODE_BUTTON_THUMBL\",\"value\":\"106\"},{\"name\":\"KEYCODE_BUTTON_THUMBR\",\"value\":\"107\"},{\"name\":\"KEYCODE_BUTTON_START\",\"value\":\"108\"},{\"name\":\"KEYCODE_BUTTON_SELECT\",\"value\":\"109\"},{\"name\":\"KEYCODE_BUTTON_MODE\",\"value\":\"110\"},{\"name\":\"KEYCODE_ESCAPE\",\"value\":\"111\"},{\"name\":\"KEYCODE_FORWARD_DEL\",\"value\":\"112\"},{\"name\":\"KEYCODE_CTRL_LEFT\",\"value\":\"113\"},{\"name\":\"KEYCODE_CTRL_RIGHT\",\"value\":\"114\"},{\"name\":\"KEYCODE_CAPS_LOCK\",\"value\":\"115\"},{\"name\":\"KEYCODE_SCROLL_LOCK\",\"value\":\"116\"},{\"name\":\"KEYCODE_META_LEFT\",\"value\":\"117\"},{\"name\":\"KEYCODE_META_RIGHT\",\"value\":\"118\"},{\"name\":\"KEYCODE_FUNCTION\",\"value\":\"119\"},{\"name\":\"KEYCODE_SYSRQ\",\"value\":\"120\"},{\"name\":\"KEYCODE_BREAK\",\"value\":\"121\"},{\"name\":\"KEYCODE_MOVE_HOME\",\"value\":\"122\"},{\"name\":\"KEYCODE_MOVE_END\",\"value\":\"123\"},{\"name\":\"KEYCODE_INSERT\",\"value\":\"124\"},{\"name\":\"KEYCODE_FORWARD\",\"value\":\"125\"},{\"name\":\"KEYCODE_MEDIA_PLAY\",\"value\":\"126\"},{\"name\":\"KEYCODE_MEDIA_PAUSE\",\"value\":\"127\"},{\"name\":\"KEYCODE_MEDIA_CLOSE\",\"value\":\"128\"},{\"name\":\"KEYCODE_MEDIA_EJECT\",\"value\":\"129\"},{\"name\":\"KEYCODE_MEDIA_RECORD\",\"value\":\"130\"},{\"name\":\"KEYCODE_F1\",\"value\":\"131\"},{\"name\":\"KEYCODE_F2\",\"value\":\"132\"},{\"name\":\"KEYCODE_F3\",\"value\":\"133\"},{\"name\":\"KEYCODE_F4\",\"value\":\"134\"},{\"name\":\"KEYCODE_F5\",\"value\":\"135\"},{\"name\":\"KEYCODE_F6\",\"value\":\"136\"},{\"name\":\"KEYCODE_F7\",\"value\":\"137\"},{\"name\":\"KEYCODE_F8\",\"value\":\"138\"},{\"name\":\"KEYCODE_F9\",\"value\":\"139\"},{\"name\":\"KEYCODE_F10\",\"value\":\"140\"},{\"name\":\"KEYCODE_F11\",\"value\":\"141\"},{\"name\":\"KEYCODE_F12\",\"value\":\"142\"},{\"name\":\"KEYCODE_NUM_LOCK\",\"value\":\"143\"},{\"name\":\"KEYCODE_NUMPAD_0\",\"value\":\"144\"},{\"name\":\"KEYCODE_NUMPAD_1\",\"value\":\"145\"},{\"name\":\"KEYCODE_NUMPAD_2\",\"value\":\"146\"},{\"name\":\"KEYCODE_NUMPAD_3\",\"value\":\"147\"},{\"name\":\"KEYCODE_NUMPAD_4\",\"value\":\"148\"},{\"name\":\"KEYCODE_NUMPAD_5\",\"value\":\"149\"},{\"name\":\"KEYCODE_NUMPAD_6\",\"value\":\"150\"},{\"name\":\"KEYCODE_NUMPAD_7\",\"value\":\"151\"},{\"name\":\"KEYCODE_NUMPAD_8\",\"value\":\"152\"},{\"name\":\"KEYCODE_NUMPAD_9\",\"value\":\"153\"},{\"name\":\"KEYCODE_NUMPAD_DIVIDE\",\"value\":\"154\"},{\"name\":\"KEYCODE_NUMPAD_MULTIPLY\",\"value\":\"155\"},{\"name\":\"KEYCODE_NUMPAD_SUBTRACT\",\"value\":\"156\"},{\"name\":\"KEYCODE_NUMPAD_ADD\",\"value\":\"157\"},{\"name\":\"KEYCODE_NUMPAD_DOT\",\"value\":\"158\"},{\"name\":\"KEYCODE_NUMPAD_COMMA\",\"value\":\"159\"},{\"name\":\"KEYCODE_NUMPAD_ENTER\",\"value\":\"160\"},{\"name\":\"KEYCODE_NUMPAD_EQUALS\",\"value\":\"161\"},{\"name\":\"KEYCODE_NUMPAD_LEFT_PAREN\",\"value\":\"162\"},{\"name\":\"KEYCODE_NUMPAD_RIGHT_PAREN\",\"value\":\"163\"},{\"name\":\"KEYCODE_VOLUME_MUTE\",\"value\":\"164\"},{\"name\":\"KEYCODE_INFO\",\"value\":\"165\"},{\"name\":\"KEYCODE_CHANNEL_UP\",\"value\":\"166\"},{\"name\":\"KEYCODE_CHANNEL_DOWN\",\"value\":\"167\"},{\"name\":\"KEYCODE_ZOOM_IN\",\"value\":\"168\"},{\"name\":\"KEYCODE_ZOOM_OUT\",\"value\":\"169\"},{\"name\":\"KEYCODE_TV\",\"value\":\"170\"},{\"name\":\"KEYCODE_WINDOW\",\"value\":\"171\"},{\"name\":\"KEYCODE_GUIDE\",\"value\":\"172\"},{\"name\":\"KEYCODE_DVR\",\"value\":\"173\"},{\"name\":\"KEYCODE_BOOKMARK\",\"value\":\"174\"},{\"name\":\"KEYCODE_CAPTIONS\",\"value\":\"175\"},{\"name\":\"KEYCODE_SETTINGS\",\"value\":\"176\"},{\"name\":\"KEYCODE_TV_POWER\",\"value\":\"177\"},{\"name\":\"KEYCODE_TV_INPUT\",\"value\":\"178\"},{\"name\":\"KEYCODE_STB_POWER\",\"value\":\"179\"},{\"name\":\"KEYCODE_STB_INPUT\",\"value\":\"180\"},{\"name\":\"KEYCODE_AVR_POWER\",\"value\":\"181\"},{\"name\":\"KEYCODE_AVR_INPUT\",\"value\":\"182\"},{\"name\":\"KEYCODE_PROG_GRED\",\"value\":\"183\"},{\"name\":\"KEYCODE_PROG_GREEN\",\"value\":\"184\"},{\"name\":\"KEYCODE_PROG_YELLOW\",\"value\":\"185\"},{\"name\":\"KEYCODE_PROG_BLUE\",\"value\":\"186\"},{\"name\":\"KEYCODE_APP_SWITCH\",\"value\":\"187\"},{\"name\":\"KEYCODE_BUTTON_1\",\"value\":\"188\"},{\"name\":\"KEYCODE_BUTTON_2\",\"value\":\"189\"},{\"name\":\"KEYCODE_BUTTON_3\",\"value\":\"190\"},{\"name\":\"KEYCODE_BUTTON_4\",\"value\":\"191\"},{\"name\":\"KEYCODE_BUTTON_5\",\"value\":\"192\"},{\"name\":\"KEYCODE_BUTTON_6\",\"value\":\"193\"},{\"name\":\"KEYCODE_BUTTON_7\",\"value\":\"194\"},{\"name\":\"KEYCODE_BUTTON_8\",\"value\":\"195\"},{\"name\":\"KEYCODE_BUTTON_9\",\"value\":\"196\"},{\"name\":\"KEYCODE_BUTTON_10\",\"value\":\"197\"},{\"name\":\"KEYCODE_BUTTON_11\",\"value\":\"198\"},{\"name\":\"KEYCODE_BUTTON_12\",\"value\":\"199\"},{\"name\":\"KEYCODE_BUTTON_13\",\"value\":\"200\"},{\"name\":\"KEYCODE_BUTTON_14\",\"value\":\"201\"},{\"name\":\"KEYCODE_BUTTON_15\",\"value\":\"202\"},{\"name\":\"KEYCODE_BUTTON_16\",\"value\":\"203\"},{\"name\":\"KEYCODE_LANGUAGE_SWITCH\",\"value\":\"204\"},{\"name\":\"KEYCODE_MANNER_MODE\",\"value\":\"205\"},{\"name\":\"KEYCODE_3D_MODE\",\"value\":\"206\"},{\"name\":\"KEYCODE_CONTACTS\",\"value\":\"207\"},{\"name\":\"KEYCODE_CALENDAR\",\"value\":\"208\"},{\"name\":\"KEYCODE_MUSIC\",\"value\":\"209\"},{\"name\":\"KEYCODE_CALCULATOR\",\"value\":\"210\"},{\"name\":\"KEYCODE_ZENKAKU_HANKAKU\",\"value\":\"211\"},{\"name\":\"KEYCODE_EISU\",\"value\":\"212\"},{\"name\":\"KEYCODE_MUHENKAN\",\"value\":\"213\"},{\"name\":\"KEYCODE_HENKAN\",\"value\":\"214\"},{\"name\":\"KEYCODE_KATAKANA_HIRAGANA\",\"value\":\"215\"},{\"name\":\"KEYCODE_YEN\",\"value\":\"216\"},{\"name\":\"KEYCODE_RO\",\"value\":\"217\"},{\"name\":\"KEYCODE_KANA\",\"value\":\"218\"},{\"name\":\"KEYCODE_ASSIST\",\"value\":\"219\"},{\"name\":\"KEYCODE_BRIGHTNESS_DOWN\",\"value\":\"220\"},{\"name\":\"KEYCODE_BRIGHTNESS_UP\",\"value\":\"221\"},{\"name\":\"KEYCODE_MEDIA_AUDIO_TRACK\",\"value\":\"222\"},{\"name\":\"KEYCODE_MEDIA_SLEEP\",\"value\":\"223\"},{\"name\":\"KEYCODE_MEDIA_WAKEUP\",\"value\":\"224\"},{\"name\":\"KEYCODE_PAIRING\",\"value\":\"225\"},{\"name\":\"KEYCODE_MEDIA_TOP_MENU\",\"value\":\"226\"},{\"name\":\"KEYCODE_11\",\"value\":\"227\"},{\"name\":\"KEYCODE_12\",\"value\":\"228\"},{\"name\":\"KEYCODE_LAST_CHANNEL\",\"value\":\"229\"},{\"name\":\"KEYCODE_TV_DATA_SERVICE\",\"value\":\"230\"},{\"name\":\"KEYCODE_VOICE_ASSIST\",\"value\":\"231\"},{\"name\":\"KEYCODE_TV_RADIO_SERVICE\",\"value\":\"232\"},{\"name\":\"KEYCODE_TV_TELETEXT\",\"value\":\"233\"},{\"name\":\"KEYCODE_TV_NUMBER_ENTRY\",\"value\":\"234\"},{\"name\":\"KEYCODE_TV_TERRESTRIAL_ANALOG\",\"value\":\"235\"},{\"name\":\"KEYCODE_TV_TERRESTRIAL_DIGITAL\",\"value\":\"236\"},{\"name\":\"KEYCODE_TV_SATELLITE\",\"value\":\"237\"},{\"name\":\"KEYCODE_TV_SATELLITE_BS\",\"value\":\"238\"},{\"name\":\"KEYCODE_TV_SATELLITE_CS\",\"value\":\"239\"},{\"name\":\"KEYCODE_TV_SATELLITE_SERVICE\",\"value\":\"240\"},{\"name\":\"KEYCODE_TV_NETWORK\",\"value\":\"241\"},{\"name\":\"KEYCODE_TV_ANTENNA_CABLE\",\"value\":\"242\"},{\"name\":\"KEYCODE_TV_INPUT_HDMI_1\",\"value\":\"243\"},{\"name\":\"KEYCODE_TV_INPUT_HDMI_2\",\"value\":\"244\"},{\"name\":\"KEYCODE_TV_INPUT_HDMI_3\",\"value\":\"245\"},{\"name\":\"KEYCODE_TV_INPUT_HDMI_4\",\"value\":\"246\"},{\"name\":\"KEYCODE_TV_INPUT_COMPOSITE_1\",\"value\":\"247\"},{\"name\":\"KEYCODE_TV_INPUT_COMPOSITE_2\",\"value\":\"248\"},{\"name\":\"KEYCODE_TV_INPUT_COMPONENT_1\",\"value\":\"249\"},{\"name\":\"KEYCODE_TV_INPUT_COMPONENT_2\",\"value\":\"250\"},{\"name\":\"KEYCODE_TV_INPUT_VGA_1\",\"value\":\"251\"},{\"name\":\"KEYCODE_TV_AUDIO_DESCRIPTION\",\"value\":\"252\"},{\"name\":\"KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP\",\"value\":\"253\"},{\"name\":\"KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN\",\"value\":\"254\"},{\"name\":\"KEYCODE_TV_ZOOM_MODE\",\"value\":\"255\"},{\"name\":\"KEYCODE_TV_CONTENTS_MENU\",\"value\":\"256\"},{\"name\":\"KEYCODE_TV_MEDIA_CONTEXT_MENU\",\"value\":\"257\"},{\"name\":\"KEYCODE_TV_TIMER_PROGRAMMING\",\"value\":\"258\"},{\"name\":\"KEYCODE_HELP\",\"value\":\"259\"},{\"name\":\"KEYCODE_NAVIGATE_PREVIOUS\",\"value\":\"260\"},{\"name\":\"KEYCODE_NAVIGATE_NEXT\",\"value\":\"261\"},{\"name\":\"KEYCODE_NAVIGATE_IN\",\"value\":\"262\"},{\"name\":\"KEYCODE_NAVIGATE_OUT\",\"value\":\"263\"},{\"name\":\"KEYCODE_STEM_PRIMARY\",\"value\":\"264\"},{\"name\":\"KEYCODE_STEM_1\",\"value\":\"265\"},{\"name\":\"KEYCODE_STEM_2\",\"value\":\"266\"},{\"name\":\"KEYCODE_STEM_3\",\"value\":\"267\"},{\"name\":\"KEYCODE_DPAD_UP_LEFT\",\"value\":\"268\"},{\"name\":\"KEYCODE_DPAD_DOWN_LEFT\",\"value\":\"269\"},{\"name\":\"KEYCODE_DPAD_UP_RIGHT\",\"value\":\"270\"},{\"name\":\"KEYCODE_DPAD_DOWN_RIGHT\",\"value\":\"271\"},{\"name\":\"KEYCODE_MEDIA_SKIP_FORWARD\",\"value\":\"272\"},{\"name\":\"KEYCODE_MEDIA_SKIP_BACKWARD\",\"value\":\"273\"},{\"name\":\"KEYCODE_MEDIA_STEP_FORWARD\",\"value\":\"274\"},{\"name\":\"KEYCODE_MEDIA_STEP_BACKWARD\",\"value\":\"275\"},{\"name\":\"KEYCODE_SOFT_SLEEP\",\"value\":\"276\"},{\"name\":\"KEYCODE_CUT\",\"value\":\"277\"},{\"name\":\"KEYCODE_COPY\",\"value\":\"278\"},{\"name\":\"KEYCODE_PASTE\",\"value\":\"279\"},{\"name\":\"KEYCODE_SYSTEM_NAVIGATION_UP\",\"value\":\"280\"},{\"name\":\"KEYCODE_SYSTEM_NAVIGATION_DOWN\",\"value\":\"281\"},{\"name\":\"KEYCODE_SYSTEM_NAVIGATION_LEFT\",\"value\":\"282\"},{\"name\":\"KEYCODE_SYSTEM_NAVIGATION_RIGHT\",\"value\":\"283\"},{\"name\":\"KEYCODE_ALL_APPS\",\"value\":\"284\"},{\"name\":\"KEYCODE_REFRESH\",\"value\":\"285\"},{\"name\":\"KEYCODE_THUMBS_UP\",\"value\":\"286\"},{\"name\":\"KEYCODE_THUMBS_DOWN\",\"value\":\"287\"},{\"name\":\"KEYCODE_PROFILE_SWITCH\",\"value\":\"288\"}]},{\"name\":\"__removed3\"},{\"name\":\"__removed4\"},{\"name\":\"__removed5\"},{\"name\":\"__removed6\"},{\"name\":\"layout_childType\",\"enum\":[{\"name\":\"none\",\"value\":\"0\"},{\"name\":\"widget\",\"value\":\"1\"},{\"name\":\"challenge\",\"value\":\"2\"},{\"name\":\"userSwitcher\",\"value\":\"3\"},{\"name\":\"scrim\",\"value\":\"4\"},{\"name\":\"widgets\",\"value\":\"5\"},{\"name\":\"expandChallengeHandle\",\"value\":\"6\"},{\"name\":\"pageDeleteDropTarget\",\"value\":\"7\"}]},{\"name\":\"lockPatternStyle\",\"format\":\"reference\"},{\"name\":\"autoSizePresetSizes\"},{\"name\":\"iconfactoryIconSize\",\"format\":\"dimension\"},{\"name\":\"iconfactoryBadgeSize\",\"format\":\"dimension\"},{\"name\":\"lStar\",\"format\":\"float\"}]}}"
  },
  {
    "path": "recaf-core/src/main/resources/android/res-map.txt",
    "content": "01010000=attr/theme\n01010001=attr/label\n01010002=attr/icon\n01010003=attr/name\n01010004=attr/manageSpaceActivity\n01010005=attr/allowClearUserData\n01010006=attr/permission\n01010007=attr/readPermission\n01010008=attr/writePermission\n01010009=attr/protectionLevel\n0101000a=attr/permissionGroup\n0101000b=attr/sharedUserId\n0101000c=attr/hasCode\n0101000d=attr/persistent\n0101000e=attr/enabled\n0101000f=attr/debuggable\n01010010=attr/exported\n01010011=attr/process\n01010012=attr/taskAffinity\n01010013=attr/multiprocess\n01010014=attr/finishOnTaskLaunch\n01010015=attr/clearTaskOnLaunch\n01010016=attr/stateNotNeeded\n01010017=attr/excludeFromRecents\n01010018=attr/authorities\n01010019=attr/syncable\n0101001a=attr/initOrder\n0101001b=attr/grantUriPermissions\n0101001c=attr/priority\n0101001d=attr/launchMode\n0101001e=attr/screenOrientation\n0101001f=attr/configChanges\n01010020=attr/description\n01010021=attr/targetPackage\n01010022=attr/handleProfiling\n01010023=attr/functionalTest\n01010024=attr/value\n01010025=attr/resource\n01010026=attr/mimeType\n01010027=attr/scheme\n01010028=attr/host\n01010029=attr/port\n0101002a=attr/path\n0101002b=attr/pathPrefix\n0101002c=attr/pathPattern\n0101002d=attr/action\n0101002e=attr/data\n0101002f=attr/targetClass\n01010030=attr/colorForeground\n01010031=attr/colorBackground\n01010032=attr/backgroundDimAmount\n01010033=attr/disabledAlpha\n01010034=attr/textAppearance\n01010035=attr/textAppearanceInverse\n01010036=attr/textColorPrimary\n01010037=attr/textColorPrimaryDisableOnly\n01010038=attr/textColorSecondary\n01010039=attr/textColorPrimaryInverse\n0101003a=attr/textColorSecondaryInverse\n0101003b=attr/textColorPrimaryNoDisable\n0101003c=attr/textColorSecondaryNoDisable\n0101003d=attr/textColorPrimaryInverseNoDisable\n0101003e=attr/textColorSecondaryInverseNoDisable\n0101003f=attr/textColorHintInverse\n01010040=attr/textAppearanceLarge\n01010041=attr/textAppearanceMedium\n01010042=attr/textAppearanceSmall\n01010043=attr/textAppearanceLargeInverse\n01010044=attr/textAppearanceMediumInverse\n01010045=attr/textAppearanceSmallInverse\n01010046=attr/textCheckMark\n01010047=attr/textCheckMarkInverse\n01010048=attr/buttonStyle\n01010049=attr/buttonStyleSmall\n0101004a=attr/buttonStyleInset\n0101004b=attr/buttonStyleToggle\n0101004c=attr/galleryItemBackground\n0101004d=attr/listPreferredItemHeight\n0101004e=attr/expandableListPreferredItemPaddingLeft\n0101004f=attr/expandableListPreferredChildPaddingLeft\n01010050=attr/expandableListPreferredItemIndicatorLeft\n01010051=attr/expandableListPreferredItemIndicatorRight\n01010052=attr/expandableListPreferredChildIndicatorLeft\n01010053=attr/expandableListPreferredChildIndicatorRight\n01010054=attr/windowBackground\n01010055=attr/windowFrame\n01010056=attr/windowNoTitle\n01010057=attr/windowIsFloating\n01010058=attr/windowIsTranslucent\n01010059=attr/windowContentOverlay\n0101005a=attr/windowTitleSize\n0101005b=attr/windowTitleStyle\n0101005c=attr/windowTitleBackgroundStyle\n0101005d=attr/alertDialogStyle\n0101005e=attr/panelBackground\n0101005f=attr/panelFullBackground\n01010060=attr/panelColorForeground\n01010061=attr/panelColorBackground\n01010062=attr/panelTextAppearance\n01010063=attr/scrollbarSize\n01010064=attr/scrollbarThumbHorizontal\n01010065=attr/scrollbarThumbVertical\n01010066=attr/scrollbarTrackHorizontal\n01010067=attr/scrollbarTrackVertical\n01010068=attr/scrollbarAlwaysDrawHorizontalTrack\n01010069=attr/scrollbarAlwaysDrawVerticalTrack\n0101006a=attr/absListViewStyle\n0101006b=attr/autoCompleteTextViewStyle\n0101006c=attr/checkboxStyle\n0101006d=attr/dropDownListViewStyle\n0101006e=attr/editTextStyle\n0101006f=attr/expandableListViewStyle\n01010070=attr/galleryStyle\n01010071=attr/gridViewStyle\n01010072=attr/imageButtonStyle\n01010073=attr/imageWellStyle\n01010074=attr/listViewStyle\n01010075=attr/listViewWhiteStyle\n01010076=attr/popupWindowStyle\n01010077=attr/progressBarStyle\n01010078=attr/progressBarStyleHorizontal\n01010079=attr/progressBarStyleSmall\n0101007a=attr/progressBarStyleLarge\n0101007b=attr/seekBarStyle\n0101007c=attr/ratingBarStyle\n0101007d=attr/ratingBarStyleSmall\n0101007e=attr/radioButtonStyle\n0101007f=attr/scrollbarStyle\n01010080=attr/scrollViewStyle\n01010081=attr/spinnerStyle\n01010082=attr/starStyle\n01010083=attr/tabWidgetStyle\n01010084=attr/textViewStyle\n01010085=attr/webViewStyle\n01010086=attr/dropDownItemStyle\n01010087=attr/spinnerDropDownItemStyle\n01010088=attr/dropDownHintAppearance\n01010089=attr/spinnerItemStyle\n0101008a=attr/mapViewStyle\n0101008b=attr/preferenceScreenStyle\n0101008c=attr/preferenceCategoryStyle\n0101008d=attr/preferenceInformationStyle\n0101008e=attr/preferenceStyle\n0101008f=attr/checkBoxPreferenceStyle\n01010090=attr/yesNoPreferenceStyle\n01010091=attr/dialogPreferenceStyle\n01010092=attr/editTextPreferenceStyle\n01010093=attr/ringtonePreferenceStyle\n01010094=attr/preferenceLayoutChild\n01010095=attr/textSize\n01010096=attr/typeface\n01010097=attr/textStyle\n01010098=attr/textColor\n01010099=attr/textColorHighlight\n0101009a=attr/textColorHint\n0101009b=attr/textColorLink\n0101009c=attr/state_focused\n0101009d=attr/state_window_focused\n0101009e=attr/state_enabled\n0101009f=attr/state_checkable\n010100a0=attr/state_checked\n010100a1=attr/state_selected\n010100a2=attr/state_active\n010100a3=attr/state_single\n010100a4=attr/state_first\n010100a5=attr/state_middle\n010100a6=attr/state_last\n010100a7=attr/state_pressed\n010100a8=attr/state_expanded\n010100a9=attr/state_empty\n010100aa=attr/state_above_anchor\n010100ab=attr/ellipsize\n010100ac=attr/x\n010100ad=attr/y\n010100ae=attr/windowAnimationStyle\n010100af=attr/gravity\n010100b0=attr/autoLink\n010100b1=attr/linksClickable\n010100b2=attr/entries\n010100b3=attr/layout_gravity\n010100b4=attr/windowEnterAnimation\n010100b5=attr/windowExitAnimation\n010100b6=attr/windowShowAnimation\n010100b7=attr/windowHideAnimation\n010100b8=attr/activityOpenEnterAnimation\n010100b9=attr/activityOpenExitAnimation\n010100ba=attr/activityCloseEnterAnimation\n010100bb=attr/activityCloseExitAnimation\n010100bc=attr/taskOpenEnterAnimation\n010100bd=attr/taskOpenExitAnimation\n010100be=attr/taskCloseEnterAnimation\n010100bf=attr/taskCloseExitAnimation\n010100c0=attr/taskToFrontEnterAnimation\n010100c1=attr/taskToFrontExitAnimation\n010100c2=attr/taskToBackEnterAnimation\n010100c3=attr/taskToBackExitAnimation\n010100c4=attr/orientation\n010100c5=attr/keycode\n010100c6=attr/fullDark\n010100c7=attr/topDark\n010100c8=attr/centerDark\n010100c9=attr/bottomDark\n010100ca=attr/fullBright\n010100cb=attr/topBright\n010100cc=attr/centerBright\n010100cd=attr/bottomBright\n010100ce=attr/bottomMedium\n010100cf=attr/centerMedium\n010100d0=attr/id\n010100d1=attr/tag\n010100d2=attr/scrollX\n010100d3=attr/scrollY\n010100d4=attr/background\n010100d5=attr/padding\n010100d6=attr/paddingLeft\n010100d7=attr/paddingTop\n010100d8=attr/paddingRight\n010100d9=attr/paddingBottom\n010100da=attr/focusable\n010100db=attr/focusableInTouchMode\n010100dc=attr/visibility\n010100dd=attr/fitsSystemWindows\n010100de=attr/scrollbars\n010100df=attr/fadingEdge\n010100e0=attr/fadingEdgeLength\n010100e1=attr/nextFocusLeft\n010100e2=attr/nextFocusRight\n010100e3=attr/nextFocusUp\n010100e4=attr/nextFocusDown\n010100e5=attr/clickable\n010100e6=attr/longClickable\n010100e7=attr/saveEnabled\n010100e8=attr/drawingCacheQuality\n010100e9=attr/duplicateParentState\n010100ea=attr/clipChildren\n010100eb=attr/clipToPadding\n010100ec=attr/layoutAnimation\n010100ed=attr/animationCache\n010100ee=attr/persistentDrawingCache\n010100ef=attr/alwaysDrawnWithCache\n010100f0=attr/addStatesFromChildren\n010100f1=attr/descendantFocusability\n010100f2=attr/layout\n010100f3=attr/inflatedId\n010100f4=attr/layout_width\n010100f5=attr/layout_height\n010100f6=attr/layout_margin\n010100f7=attr/layout_marginLeft\n010100f8=attr/layout_marginTop\n010100f9=attr/layout_marginRight\n010100fa=attr/layout_marginBottom\n010100fb=attr/listSelector\n010100fc=attr/drawSelectorOnTop\n010100fd=attr/stackFromBottom\n010100fe=attr/scrollingCache\n010100ff=attr/textFilterEnabled\n01010100=attr/transcriptMode\n01010101=attr/cacheColorHint\n01010102=attr/dial\n01010103=attr/hand_hour\n01010104=attr/hand_minute\n01010105=attr/format\n01010106=attr/checked\n01010107=attr/button\n01010108=attr/checkMark\n01010109=attr/foreground\n0101010a=attr/measureAllChildren\n0101010b=attr/groupIndicator\n0101010c=attr/childIndicator\n0101010d=attr/indicatorLeft\n0101010e=attr/indicatorRight\n0101010f=attr/childIndicatorLeft\n01010110=attr/childIndicatorRight\n01010111=attr/childDivider\n01010112=attr/animationDuration\n01010113=attr/spacing\n01010114=attr/horizontalSpacing\n01010115=attr/verticalSpacing\n01010116=attr/stretchMode\n01010117=attr/columnWidth\n01010118=attr/numColumns\n01010119=attr/src\n0101011a=attr/antialias\n0101011b=attr/filter\n0101011c=attr/dither\n0101011d=attr/scaleType\n0101011e=attr/adjustViewBounds\n0101011f=attr/maxWidth\n01010120=attr/maxHeight\n01010121=attr/tint\n01010122=attr/baselineAlignBottom\n01010123=attr/cropToPadding\n01010124=attr/textOn\n01010125=attr/textOff\n01010126=attr/baselineAligned\n01010127=attr/baselineAlignedChildIndex\n01010128=attr/weightSum\n01010129=attr/divider\n0101012a=attr/dividerHeight\n0101012b=attr/choiceMode\n0101012c=attr/itemTextAppearance\n0101012d=attr/horizontalDivider\n0101012e=attr/verticalDivider\n0101012f=attr/headerBackground\n01010130=attr/itemBackground\n01010131=attr/itemIconDisabledAlpha\n01010132=attr/rowHeight\n01010133=attr/maxRows\n01010134=attr/maxItemsPerRow\n01010135=attr/moreIcon\n01010136=attr/max\n01010137=attr/progress\n01010138=attr/secondaryProgress\n01010139=attr/indeterminate\n0101013a=attr/indeterminateOnly\n0101013b=attr/indeterminateDrawable\n0101013c=attr/progressDrawable\n0101013d=attr/indeterminateDuration\n0101013e=attr/indeterminateBehavior\n0101013f=attr/minWidth\n01010140=attr/minHeight\n01010141=attr/interpolator\n01010142=attr/thumb\n01010143=attr/thumbOffset\n01010144=attr/numStars\n01010145=attr/rating\n01010146=attr/stepSize\n01010147=attr/isIndicator\n01010148=attr/checkedButton\n01010149=attr/stretchColumns\n0101014a=attr/shrinkColumns\n0101014b=attr/collapseColumns\n0101014c=attr/layout_column\n0101014d=attr/layout_span\n0101014e=attr/bufferType\n0101014f=attr/text\n01010150=attr/hint\n01010151=attr/textScaleX\n01010152=attr/cursorVisible\n01010153=attr/maxLines\n01010154=attr/lines\n01010155=attr/height\n01010156=attr/minLines\n01010157=attr/maxEms\n01010158=attr/ems\n01010159=attr/width\n0101015a=attr/minEms\n0101015b=attr/scrollHorizontally\n0101015c=attr/password\n0101015d=attr/singleLine\n0101015e=attr/selectAllOnFocus\n0101015f=attr/includeFontPadding\n01010160=attr/maxLength\n01010161=attr/shadowColor\n01010162=attr/shadowDx\n01010163=attr/shadowDy\n01010164=attr/shadowRadius\n01010165=attr/numeric\n01010166=attr/digits\n01010167=attr/phoneNumber\n01010168=attr/inputMethod\n01010169=attr/capitalize\n0101016a=attr/autoText\n0101016b=attr/editable\n0101016c=attr/freezesText\n0101016d=attr/drawableTop\n0101016e=attr/drawableBottom\n0101016f=attr/drawableLeft\n01010170=attr/drawableRight\n01010171=attr/drawablePadding\n01010172=attr/completionHint\n01010173=attr/completionHintView\n01010174=attr/completionThreshold\n01010175=attr/dropDownSelector\n01010176=attr/popupBackground\n01010177=attr/inAnimation\n01010178=attr/outAnimation\n01010179=attr/flipInterval\n0101017a=attr/fillViewport\n0101017b=attr/prompt\n0101017c=attr/startYear\n0101017d=attr/endYear\n0101017e=attr/mode\n0101017f=attr/layout_x\n01010180=attr/layout_y\n01010181=attr/layout_weight\n01010182=attr/layout_toLeftOf\n01010183=attr/layout_toRightOf\n01010184=attr/layout_above\n01010185=attr/layout_below\n01010186=attr/layout_alignBaseline\n01010187=attr/layout_alignLeft\n01010188=attr/layout_alignTop\n01010189=attr/layout_alignRight\n0101018a=attr/layout_alignBottom\n0101018b=attr/layout_alignParentLeft\n0101018c=attr/layout_alignParentTop\n0101018d=attr/layout_alignParentRight\n0101018e=attr/layout_alignParentBottom\n0101018f=attr/layout_centerInParent\n01010190=attr/layout_centerHorizontal\n01010191=attr/layout_centerVertical\n01010192=attr/layout_alignWithParentIfMissing\n01010193=attr/layout_scale\n01010194=attr/visible\n01010195=attr/variablePadding\n01010196=attr/constantSize\n01010197=attr/oneshot\n01010198=attr/duration\n01010199=attr/drawable\n0101019a=attr/shape\n0101019b=attr/innerRadiusRatio\n0101019c=attr/thicknessRatio\n0101019d=attr/startColor\n0101019e=attr/endColor\n0101019f=attr/useLevel\n010101a0=attr/angle\n010101a1=attr/type\n010101a2=attr/centerX\n010101a3=attr/centerY\n010101a4=attr/gradientRadius\n010101a5=attr/color\n010101a6=attr/dashWidth\n010101a7=attr/dashGap\n010101a8=attr/radius\n010101a9=attr/topLeftRadius\n010101aa=attr/topRightRadius\n010101ab=attr/bottomLeftRadius\n010101ac=attr/bottomRightRadius\n010101ad=attr/left\n010101ae=attr/top\n010101af=attr/right\n010101b0=attr/bottom\n010101b1=attr/minLevel\n010101b2=attr/maxLevel\n010101b3=attr/fromDegrees\n010101b4=attr/toDegrees\n010101b5=attr/pivotX\n010101b6=attr/pivotY\n010101b7=attr/insetLeft\n010101b8=attr/insetRight\n010101b9=attr/insetTop\n010101ba=attr/insetBottom\n010101bb=attr/shareInterpolator\n010101bc=attr/fillBefore\n010101bd=attr/fillAfter\n010101be=attr/startOffset\n010101bf=attr/repeatCount\n010101c0=attr/repeatMode\n010101c1=attr/zAdjustment\n010101c2=attr/fromXScale\n010101c3=attr/toXScale\n010101c4=attr/fromYScale\n010101c5=attr/toYScale\n010101c6=attr/fromXDelta\n010101c7=attr/toXDelta\n010101c8=attr/fromYDelta\n010101c9=attr/toYDelta\n010101ca=attr/fromAlpha\n010101cb=attr/toAlpha\n010101cc=attr/delay\n010101cd=attr/animation\n010101ce=attr/animationOrder\n010101cf=attr/columnDelay\n010101d0=attr/rowDelay\n010101d1=attr/direction\n010101d2=attr/directionPriority\n010101d3=attr/factor\n010101d4=attr/cycles\n010101d5=attr/searchMode\n010101d6=attr/searchSuggestAuthority\n010101d7=attr/searchSuggestPath\n010101d8=attr/searchSuggestSelection\n010101d9=attr/searchSuggestIntentAction\n010101da=attr/searchSuggestIntentData\n010101db=attr/queryActionMsg\n010101dc=attr/suggestActionMsg\n010101dd=attr/suggestActionMsgColumn\n010101de=attr/menuCategory\n010101df=attr/orderInCategory\n010101e0=attr/checkableBehavior\n010101e1=attr/title\n010101e2=attr/titleCondensed\n010101e3=attr/alphabeticShortcut\n010101e4=attr/numericShortcut\n010101e5=attr/checkable\n010101e6=attr/selectable\n010101e7=attr/orderingFromXml\n010101e8=attr/key\n010101e9=attr/summary\n010101ea=attr/order\n010101eb=attr/widgetLayout\n010101ec=attr/dependency\n010101ed=attr/defaultValue\n010101ee=attr/shouldDisableView\n010101ef=attr/summaryOn\n010101f0=attr/summaryOff\n010101f1=attr/disableDependentsState\n010101f2=attr/dialogTitle\n010101f3=attr/dialogMessage\n010101f4=attr/dialogIcon\n010101f5=attr/positiveButtonText\n010101f6=attr/negativeButtonText\n010101f7=attr/dialogLayout\n010101f8=attr/entryValues\n010101f9=attr/ringtoneType\n010101fa=attr/showDefault\n010101fb=attr/showSilent\n010101fc=attr/scaleWidth\n010101fd=attr/scaleHeight\n010101fe=attr/scaleGravity\n010101ff=attr/ignoreGravity\n01010200=attr/foregroundGravity\n01010201=attr/tileMode\n01010202=attr/targetActivity\n01010203=attr/alwaysRetainTaskState\n01010204=attr/allowTaskReparenting\n01010205=attr/searchButtonText\n01010206=attr/colorForegroundInverse\n01010207=attr/textAppearanceButton\n01010208=attr/listSeparatorTextViewStyle\n01010209=attr/streamType\n0101020a=attr/clipOrientation\n0101020b=attr/centerColor\n0101020c=attr/minSdkVersion\n0101020d=attr/windowFullscreen\n0101020e=attr/unselectedAlpha\n0101020f=attr/progressBarStyleSmallTitle\n01010210=attr/ratingBarStyleIndicator\n01010211=attr/apiKey\n01010212=attr/textColorTertiary\n01010213=attr/textColorTertiaryInverse\n01010214=attr/listDivider\n01010215=attr/soundEffectsEnabled\n01010216=attr/keepScreenOn\n01010217=attr/lineSpacingExtra\n01010218=attr/lineSpacingMultiplier\n01010219=attr/listChoiceIndicatorSingle\n0101021a=attr/listChoiceIndicatorMultiple\n0101021b=attr/versionCode\n0101021c=attr/versionName\n0101021d=attr/marqueeRepeatLimit\n0101021e=attr/windowNoDisplay\n0101021f=attr/backgroundDimEnabled\n01010220=attr/inputType\n01010221=attr/isDefault\n01010222=attr/windowDisablePreview\n01010223=attr/privateImeOptions\n01010224=attr/editorExtras\n01010225=attr/settingsActivity\n01010226=attr/fastScrollEnabled\n01010227=attr/reqTouchScreen\n01010228=attr/reqKeyboardType\n01010229=attr/reqHardKeyboard\n0101022a=attr/reqNavigation\n0101022b=attr/windowSoftInputMode\n0101022c=attr/imeFullscreenBackground\n0101022d=attr/noHistory\n0101022e=attr/headerDividersEnabled\n0101022f=attr/footerDividersEnabled\n01010230=attr/candidatesTextStyleSpans\n01010231=attr/smoothScrollbar\n01010232=attr/reqFiveWayNav\n01010233=attr/keyBackground\n01010234=attr/keyTextSize\n01010235=attr/labelTextSize\n01010236=attr/keyTextColor\n01010237=attr/keyPreviewLayout\n01010238=attr/keyPreviewOffset\n01010239=attr/keyPreviewHeight\n0101023a=attr/verticalCorrection\n0101023b=attr/popupLayout\n0101023c=attr/state_long_pressable\n0101023d=attr/keyWidth\n0101023e=attr/keyHeight\n0101023f=attr/horizontalGap\n01010240=attr/verticalGap\n01010241=attr/rowEdgeFlags\n01010242=attr/codes\n01010243=attr/popupKeyboard\n01010244=attr/popupCharacters\n01010245=attr/keyEdgeFlags\n01010246=attr/isModifier\n01010247=attr/isSticky\n01010248=attr/isRepeatable\n01010249=attr/iconPreview\n0101024a=attr/keyOutputText\n0101024b=attr/keyLabel\n0101024c=attr/keyIcon\n0101024d=attr/keyboardMode\n0101024e=attr/isScrollContainer\n0101024f=attr/fillEnabled\n01010250=attr/updatePeriodMillis\n01010251=attr/initialLayout\n01010252=attr/voiceSearchMode\n01010253=attr/voiceLanguageModel\n01010254=attr/voicePromptText\n01010255=attr/voiceLanguage\n01010256=attr/voiceMaxResults\n01010257=attr/bottomOffset\n01010258=attr/topOffset\n01010259=attr/allowSingleTap\n0101025a=attr/handle\n0101025b=attr/content\n0101025c=attr/animateOnClick\n0101025d=attr/configure\n0101025e=attr/hapticFeedbackEnabled\n0101025f=attr/innerRadius\n01010260=attr/thickness\n01010261=attr/sharedUserLabel\n01010262=attr/dropDownWidth\n01010263=attr/dropDownAnchor\n01010264=attr/imeOptions\n01010265=attr/imeActionLabel\n01010266=attr/imeActionId\n01010267=attr/textColorPrimaryActivated\n01010268=attr/imeExtractEnterAnimation\n01010269=attr/imeExtractExitAnimation\n0101026a=attr/tension\n0101026b=attr/extraTension\n0101026c=attr/anyDensity\n0101026d=attr/searchSuggestThreshold\n0101026e=attr/includeInGlobalSearch\n0101026f=attr/onClick\n01010270=attr/targetSdkVersion\n01010271=attr/maxSdkVersion\n01010272=attr/testOnly\n01010273=attr/contentDescription\n01010274=attr/gestureStrokeWidth\n01010275=attr/gestureColor\n01010276=attr/uncertainGestureColor\n01010277=attr/fadeOffset\n01010278=attr/fadeDuration\n01010279=attr/gestureStrokeType\n0101027a=attr/gestureStrokeLengthThreshold\n0101027b=attr/gestureStrokeSquarenessThreshold\n0101027c=attr/gestureStrokeAngleThreshold\n0101027d=attr/eventsInterceptionEnabled\n0101027e=attr/fadeEnabled\n0101027f=attr/backupAgent\n01010280=attr/allowBackup\n01010281=attr/glEsVersion\n01010282=attr/queryAfterZeroResults\n01010283=attr/dropDownHeight\n01010284=attr/smallScreens\n01010285=attr/normalScreens\n01010286=attr/largeScreens\n01010287=attr/progressBarStyleInverse\n01010288=attr/progressBarStyleSmallInverse\n01010289=attr/progressBarStyleLargeInverse\n0101028a=attr/searchSettingsDescription\n0101028b=attr/textColorPrimaryInverseDisableOnly\n0101028c=attr/autoUrlDetect\n0101028d=attr/resizeable\n0101028e=attr/required\n0101028f=attr/accountType\n01010290=attr/contentAuthority\n01010291=attr/userVisible\n01010292=attr/windowShowWallpaper\n01010293=attr/wallpaperOpenEnterAnimation\n01010294=attr/wallpaperOpenExitAnimation\n01010295=attr/wallpaperCloseEnterAnimation\n01010296=attr/wallpaperCloseExitAnimation\n01010297=attr/wallpaperIntraOpenEnterAnimation\n01010298=attr/wallpaperIntraOpenExitAnimation\n01010299=attr/wallpaperIntraCloseEnterAnimation\n0101029a=attr/wallpaperIntraCloseExitAnimation\n0101029b=attr/supportsUploading\n0101029c=attr/killAfterRestore\n0101029d=attr/restoreNeedsApplication\n0101029e=attr/smallIcon\n0101029f=attr/accountPreferences\n010102a0=attr/textAppearanceSearchResultSubtitle\n010102a1=attr/textAppearanceSearchResultTitle\n010102a2=attr/summaryColumn\n010102a3=attr/detailColumn\n010102a4=attr/detailSocialSummary\n010102a5=attr/thumbnail\n010102a6=attr/detachWallpaper\n010102a7=attr/finishOnCloseSystemDialogs\n010102a8=attr/scrollbarFadeDuration\n010102a9=attr/scrollbarDefaultDelayBeforeFade\n010102aa=attr/fadeScrollbars\n010102ab=attr/colorBackgroundCacheHint\n010102ac=attr/dropDownHorizontalOffset\n010102ad=attr/dropDownVerticalOffset\n010102ae=attr/quickContactBadgeStyleWindowSmall\n010102af=attr/quickContactBadgeStyleWindowMedium\n010102b0=attr/quickContactBadgeStyleWindowLarge\n010102b1=attr/quickContactBadgeStyleSmallWindowSmall\n010102b2=attr/quickContactBadgeStyleSmallWindowMedium\n010102b3=attr/quickContactBadgeStyleSmallWindowLarge\n010102b4=attr/author\n010102b5=attr/autoStart\n010102b6=attr/expandableListViewWhiteStyle\n010102b7=attr/installLocation\n010102b8=attr/vmSafeMode\n010102b9=attr/webTextViewStyle\n010102ba=attr/restoreAnyVersion\n010102bb=attr/tabStripLeft\n010102bc=attr/tabStripRight\n010102bd=attr/tabStripEnabled\n010102be=attr/logo\n010102bf=attr/xlargeScreens\n010102c0=attr/immersive\n010102c1=attr/overScrollMode\n010102c2=attr/overScrollHeader\n010102c3=attr/overScrollFooter\n010102c4=attr/filterTouchesWhenObscured\n010102c5=attr/textSelectHandleLeft\n010102c6=attr/textSelectHandleRight\n010102c7=attr/textSelectHandle\n010102c8=attr/textSelectHandleWindowStyle\n010102c9=attr/popupAnimationStyle\n010102ca=attr/screenSize\n010102cb=attr/screenDensity\n010102cc=attr/allContactsName\n010102cd=attr/windowActionBar\n010102ce=attr/actionBarStyle\n010102cf=attr/navigationMode\n010102d0=attr/displayOptions\n010102d1=attr/subtitle\n010102d2=attr/customNavigationLayout\n010102d3=attr/hardwareAccelerated\n010102d4=attr/measureWithLargestChild\n010102d5=attr/animateFirstView\n010102d6=attr/dropDownSpinnerStyle\n010102d7=attr/actionDropDownStyle\n010102d8=attr/actionButtonStyle\n010102d9=attr/showAsAction\n010102da=attr/previewImage\n010102db=attr/actionModeBackground\n010102dc=attr/actionModeCloseDrawable\n010102dd=attr/windowActionModeOverlay\n010102de=attr/valueFrom\n010102df=attr/valueTo\n010102e0=attr/valueType\n010102e1=attr/propertyName\n010102e2=attr/ordering\n010102e3=attr/fragment\n010102e4=attr/windowActionBarOverlay\n010102e5=attr/fragmentOpenEnterAnimation\n010102e6=attr/fragmentOpenExitAnimation\n010102e7=attr/fragmentCloseEnterAnimation\n010102e8=attr/fragmentCloseExitAnimation\n010102e9=attr/fragmentFadeEnterAnimation\n010102ea=attr/fragmentFadeExitAnimation\n010102eb=attr/actionBarSize\n010102ec=attr/imeSubtypeLocale\n010102ed=attr/imeSubtypeMode\n010102ee=attr/imeSubtypeExtraValue\n010102ef=attr/splitMotionEvents\n010102f0=attr/listChoiceBackgroundIndicator\n010102f1=attr/spinnerMode\n010102f2=attr/animateLayoutChanges\n010102f3=attr/actionBarTabStyle\n010102f4=attr/actionBarTabBarStyle\n010102f5=attr/actionBarTabTextStyle\n010102f6=attr/actionOverflowButtonStyle\n010102f7=attr/actionModeCloseButtonStyle\n010102f8=attr/titleTextStyle\n010102f9=attr/subtitleTextStyle\n010102fa=attr/iconifiedByDefault\n010102fb=attr/actionLayout\n010102fc=attr/actionViewClass\n010102fd=attr/activatedBackgroundIndicator\n010102fe=attr/state_activated\n010102ff=attr/listPopupWindowStyle\n01010300=attr/popupMenuStyle\n01010301=attr/textAppearanceLargePopupMenu\n01010302=attr/textAppearanceSmallPopupMenu\n01010303=attr/breadCrumbTitle\n01010304=attr/breadCrumbShortTitle\n01010305=attr/listDividerAlertDialog\n01010306=attr/textColorAlertDialogListItem\n01010307=attr/loopViews\n01010308=attr/dialogTheme\n01010309=attr/alertDialogTheme\n0101030a=attr/dividerVertical\n0101030b=attr/homeAsUpIndicator\n0101030c=attr/enterFadeDuration\n0101030d=attr/exitFadeDuration\n0101030e=attr/selectableItemBackground\n0101030f=attr/autoAdvanceViewId\n01010310=attr/useIntrinsicSizeAsMinimum\n01010311=attr/actionModeCutDrawable\n01010312=attr/actionModeCopyDrawable\n01010313=attr/actionModePasteDrawable\n01010314=attr/textEditPasteWindowLayout\n01010315=attr/textEditNoPasteWindowLayout\n01010316=attr/textIsSelectable\n01010317=attr/windowEnableSplitTouch\n01010318=attr/indeterminateProgressStyle\n01010319=attr/progressBarPadding\n0101031a=attr/animationResolution\n0101031b=attr/state_accelerated\n0101031c=attr/baseline\n0101031d=attr/homeLayout\n0101031e=attr/opacity\n0101031f=attr/alpha\n01010320=attr/transformPivotX\n01010321=attr/transformPivotY\n01010322=attr/translationX\n01010323=attr/translationY\n01010324=attr/scaleX\n01010325=attr/scaleY\n01010326=attr/rotation\n01010327=attr/rotationX\n01010328=attr/rotationY\n01010329=attr/showDividers\n0101032a=attr/dividerPadding\n0101032b=attr/borderlessButtonStyle\n0101032c=attr/dividerHorizontal\n0101032d=attr/itemPadding\n0101032e=attr/buttonBarStyle\n0101032f=attr/buttonBarButtonStyle\n01010330=attr/segmentedButtonStyle\n01010331=attr/staticWallpaperPreview\n01010332=attr/allowParallelSyncs\n01010333=attr/isAlwaysSyncable\n01010334=attr/verticalScrollbarPosition\n01010335=attr/fastScrollAlwaysVisible\n01010336=attr/fastScrollThumbDrawable\n01010337=attr/fastScrollPreviewBackgroundLeft\n01010338=attr/fastScrollPreviewBackgroundRight\n01010339=attr/fastScrollTrackDrawable\n0101033a=attr/fastScrollOverlayPosition\n0101033b=attr/customTokens\n0101033c=attr/nextFocusForward\n0101033d=attr/firstDayOfWeek\n0101033e=attr/showWeekNumber\n0101033f=attr/minDate\n01010340=attr/maxDate\n01010341=attr/shownWeekCount\n01010342=attr/selectedWeekBackgroundColor\n01010343=attr/focusedMonthDateColor\n01010344=attr/unfocusedMonthDateColor\n01010345=attr/weekNumberColor\n01010346=attr/weekSeparatorLineColor\n01010347=attr/selectedDateVerticalBar\n01010348=attr/weekDayTextAppearance\n01010349=attr/dateTextAppearance\n0101034a=attr/solidColor\n0101034b=attr/spinnersShown\n0101034c=attr/calendarViewShown\n0101034d=attr/state_multiline\n0101034e=attr/detailsElementBackground\n0101034f=attr/textColorHighlightInverse\n01010350=attr/textColorLinkInverse\n01010351=attr/editTextColor\n01010352=attr/editTextBackground\n01010353=attr/horizontalScrollViewStyle\n01010354=attr/layerType\n01010355=attr/alertDialogIcon\n01010356=attr/windowMinWidthMajor\n01010357=attr/windowMinWidthMinor\n01010358=attr/queryHint\n01010359=attr/fastScrollTextColor\n0101035a=attr/largeHeap\n0101035b=attr/windowCloseOnTouchOutside\n0101035c=attr/datePickerStyle\n0101035d=attr/calendarViewStyle\n0101035e=attr/textEditSidePasteWindowLayout\n0101035f=attr/textEditSideNoPasteWindowLayout\n01010360=attr/actionMenuTextAppearance\n01010361=attr/actionMenuTextColor\n01010362=attr/textCursorDrawable\n01010363=attr/resizeMode\n01010364=attr/requiresSmallestWidthDp\n01010365=attr/compatibleWidthLimitDp\n01010366=attr/largestWidthLimitDp\n01010367=attr/state_hovered\n01010368=attr/state_drag_can_accept\n01010369=attr/state_drag_hovered\n0101036a=attr/stopWithTask\n0101036b=attr/switchTextOn\n0101036c=attr/switchTextOff\n0101036d=attr/switchPreferenceStyle\n0101036e=attr/switchTextAppearance\n0101036f=attr/track\n01010370=attr/switchMinWidth\n01010371=attr/switchPadding\n01010372=attr/thumbTextPadding\n01010373=attr/textSuggestionsWindowStyle\n01010374=attr/textEditSuggestionItemLayout\n01010375=attr/rowCount\n01010376=attr/rowOrderPreserved\n01010377=attr/columnCount\n01010378=attr/columnOrderPreserved\n01010379=attr/useDefaultMargins\n0101037a=attr/alignmentMode\n0101037b=attr/layout_row\n0101037c=attr/layout_rowSpan\n0101037d=attr/layout_columnSpan\n0101037e=attr/actionModeSelectAllDrawable\n0101037f=attr/isAuxiliary\n01010380=attr/accessibilityEventTypes\n01010381=attr/packageNames\n01010382=attr/accessibilityFeedbackType\n01010383=attr/notificationTimeout\n01010384=attr/accessibilityFlags\n01010385=attr/canRetrieveWindowContent\n01010386=attr/listPreferredItemHeightLarge\n01010387=attr/listPreferredItemHeightSmall\n01010388=attr/actionBarSplitStyle\n01010389=attr/actionProviderClass\n0101038a=attr/backgroundStacked\n0101038b=attr/backgroundSplit\n0101038c=attr/textAllCaps\n0101038d=attr/colorPressedHighlight\n0101038e=attr/colorLongPressedHighlight\n0101038f=attr/colorFocusedHighlight\n01010390=attr/colorActivatedHighlight\n01010391=attr/colorMultiSelectHighlight\n01010392=attr/drawableStart\n01010393=attr/drawableEnd\n01010394=attr/actionModeStyle\n01010395=attr/minResizeWidth\n01010396=attr/minResizeHeight\n01010397=attr/actionBarWidgetTheme\n01010398=attr/uiOptions\n01010399=attr/subtypeLocale\n0101039a=attr/subtypeExtraValue\n0101039b=attr/actionBarDivider\n0101039c=attr/actionBarItemBackground\n0101039d=attr/actionModeSplitBackground\n0101039e=attr/textAppearanceListItem\n0101039f=attr/textAppearanceListItemSmall\n010103a0=attr/targetDescriptions\n010103a1=attr/directionDescriptions\n010103a2=attr/overridesImplicitlyEnabledSubtype\n010103a3=attr/listPreferredItemPaddingLeft\n010103a4=attr/listPreferredItemPaddingRight\n010103a5=attr/requiresFadingEdge\n010103a6=attr/publicKey\n010103a7=attr/parentActivityName\n010103a8=attr/textColorSecondaryActivated\n010103a9=attr/isolatedProcess\n010103aa=attr/importantForAccessibility\n010103ab=attr/keyboardLayout\n010103ac=attr/fontFamily\n010103ad=attr/mediaRouteButtonStyle\n010103ae=attr/mediaRouteTypes\n010103af=attr/supportsRtl\n010103b0=attr/textDirection\n010103b1=attr/textAlignment\n010103b2=attr/layoutDirection\n010103b3=attr/paddingStart\n010103b4=attr/paddingEnd\n010103b5=attr/layout_marginStart\n010103b6=attr/layout_marginEnd\n010103b7=attr/layout_toStartOf\n010103b8=attr/layout_toEndOf\n010103b9=attr/layout_alignStart\n010103ba=attr/layout_alignEnd\n010103bb=attr/layout_alignParentStart\n010103bc=attr/layout_alignParentEnd\n010103bd=attr/listPreferredItemPaddingStart\n010103be=attr/listPreferredItemPaddingEnd\n010103bf=attr/singleUser\n010103c0=attr/presentationTheme\n010103c1=attr/subtypeId\n010103c2=attr/initialKeyguardLayout\n010103c3=attr/textColorSearchUrl\n010103c4=attr/widgetCategory\n010103c5=attr/permissionGroupFlags\n010103c6=attr/labelFor\n010103c7=attr/permissionFlags\n010103c8=attr/checkedTextViewStyle\n010103c9=attr/showOnLockScreen\n010103ca=attr/format12Hour\n010103cb=attr/format24Hour\n010103cc=attr/timeZone\n010103cd=attr/mipMap\n010103ce=attr/mirrorForRtl\n010103cf=attr/windowOverscan\n010103d0=attr/requiredForAllUsers\n010103d1=attr/indicatorStart\n010103d2=attr/indicatorEnd\n010103d3=attr/childIndicatorStart\n010103d4=attr/childIndicatorEnd\n010103d5=attr/restrictedAccountType\n010103d6=attr/requiredAccountType\n010103d7=attr/canRequestTouchExplorationMode\n010103d8=attr/canRequestEnhancedWebAccessibility\n010103d9=attr/canRequestFilterKeyEvents\n010103da=attr/layoutMode\n010103db=attr/keySet\n010103dc=attr/targetId\n010103dd=attr/fromScene\n010103de=attr/toScene\n010103df=attr/transition\n010103e0=attr/transitionOrdering\n010103e1=attr/fadingMode\n010103e2=attr/startDelay\n010103e3=attr/ssp\n010103e4=attr/sspPrefix\n010103e5=attr/sspPattern\n010103e6=attr/addPrintersActivity\n010103e7=attr/vendor\n010103e8=attr/category\n010103e9=attr/isAsciiCapable\n010103ea=attr/autoMirrored\n010103eb=attr/supportsSwitchingToNextInputMethod\n010103ec=attr/requireDeviceUnlock\n010103ed=attr/apduServiceBanner\n010103ee=attr/accessibilityLiveRegion\n010103ef=attr/windowTranslucentStatus\n010103f0=attr/windowTranslucentNavigation\n010103f1=attr/advancedPrintOptionsActivity\n010103f2=attr/banner\n010103f3=attr/windowSwipeToDismiss\n010103f4=attr/isGame\n010103f5=attr/allowEmbedded\n010103f6=attr/setupActivity\n010103f7=attr/fastScrollStyle\n010103f8=attr/windowContentTransitions\n010103f9=attr/windowContentTransitionManager\n010103fa=attr/translationZ\n010103fb=attr/tintMode\n010103fc=attr/controlX1\n010103fd=attr/controlY1\n010103fe=attr/controlX2\n010103ff=attr/controlY2\n01010400=attr/transitionName\n01010401=attr/transitionGroup\n01010402=attr/viewportWidth\n01010403=attr/viewportHeight\n01010404=attr/fillColor\n01010405=attr/pathData\n01010406=attr/strokeColor\n01010407=attr/strokeWidth\n01010408=attr/trimPathStart\n01010409=attr/trimPathEnd\n0101040a=attr/trimPathOffset\n0101040b=attr/strokeLineCap\n0101040c=attr/strokeLineJoin\n0101040d=attr/strokeMiterLimit\n0101040e=attr/searchWidgetCorpusItemBackground\n0101040f=attr/textAppearanceEasyCorrectSuggestion\n01010410=attr/textAppearanceMisspelledSuggestion\n01010411=attr/textAppearanceAutoCorrectionSuggestion\n01010412=attr/textUnderlineColor\n01010413=attr/textUnderlineThickness\n01010414=attr/errorMessageBackground\n01010415=attr/errorMessageAboveBackground\n01010416=attr/searchResultListItemHeight\n01010417=attr/dropdownListPreferredItemHeight\n01010418=attr/windowBackgroundFallback\n01010419=attr/windowActionBarFullscreenDecorLayout\n0101041a=attr/alertDialogButtonGroupStyle\n0101041b=attr/alertDialogCenterButtons\n0101041c=attr/panelMenuIsCompact\n0101041d=attr/panelMenuListWidth\n0101041e=attr/panelMenuListTheme\n0101041f=attr/gestureOverlayViewStyle\n01010420=attr/quickContactBadgeOverlay\n01010421=attr/fragmentBreadCrumbsStyle\n01010422=attr/numberPickerStyle\n01010423=attr/activityChooserViewStyle\n01010424=attr/actionModePopupWindowStyle\n01010425=attr/preferenceActivityStyle\n01010426=attr/preferenceFragmentStyle\n01010427=attr/preferencePanelStyle\n01010428=attr/preferenceHeaderPanelStyle\n01010429=attr/colorControlNormal\n0101042a=attr/colorControlActivated\n0101042b=attr/colorButtonNormal\n0101042c=attr/colorControlHighlight\n0101042d=attr/persistableMode\n0101042e=attr/titleTextAppearance\n0101042f=attr/subtitleTextAppearance\n01010430=attr/slideEdge\n01010431=attr/actionBarTheme\n01010432=attr/textAppearanceListItemSecondary\n01010433=attr/colorPrimary\n01010434=attr/colorPrimaryDark\n01010435=attr/colorAccent\n01010436=attr/nestedScrollingEnabled\n01010437=attr/windowEnterTransition\n01010438=attr/windowExitTransition\n01010439=attr/windowSharedElementEnterTransition\n0101043a=attr/windowSharedElementExitTransition\n0101043b=attr/windowAllowReturnTransitionOverlap\n0101043c=attr/windowAllowEnterTransitionOverlap\n0101043d=attr/sessionService\n0101043e=attr/stackViewStyle\n0101043f=attr/switchStyle\n01010440=attr/elevation\n01010441=attr/excludeId\n01010442=attr/excludeClass\n01010443=attr/hideOnContentScroll\n01010444=attr/actionOverflowMenuStyle\n01010445=attr/documentLaunchMode\n01010446=attr/maxRecents\n01010447=attr/autoRemoveFromRecents\n01010448=attr/stateListAnimator\n01010449=attr/toId\n0101044a=attr/fromId\n0101044b=attr/reversible\n0101044c=attr/splitTrack\n0101044d=attr/targetName\n0101044e=attr/excludeName\n0101044f=attr/matchOrder\n01010450=attr/windowDrawsSystemBarBackgrounds\n01010451=attr/statusBarColor\n01010452=attr/navigationBarColor\n01010453=attr/contentInsetStart\n01010454=attr/contentInsetEnd\n01010455=attr/contentInsetLeft\n01010456=attr/contentInsetRight\n01010457=attr/paddingMode\n01010458=attr/layout_rowWeight\n01010459=attr/layout_columnWeight\n0101045a=attr/translateX\n0101045b=attr/translateY\n0101045c=attr/selectableItemBackgroundBorderless\n0101045d=attr/elegantTextHeight\n0101045e=attr/searchKeyphraseId\n0101045f=attr/searchKeyphrase\n01010460=attr/searchKeyphraseSupportedLocales\n01010461=attr/windowTransitionBackgroundFadeDuration\n01010462=attr/overlapAnchor\n01010463=attr/progressTint\n01010464=attr/progressTintMode\n01010465=attr/progressBackgroundTint\n01010466=attr/progressBackgroundTintMode\n01010467=attr/secondaryProgressTint\n01010468=attr/secondaryProgressTintMode\n01010469=attr/indeterminateTint\n0101046a=attr/indeterminateTintMode\n0101046b=attr/backgroundTint\n0101046c=attr/backgroundTintMode\n0101046d=attr/foregroundTint\n0101046e=attr/foregroundTintMode\n0101046f=attr/buttonTint\n01010470=attr/buttonTintMode\n01010471=attr/thumbTint\n01010472=attr/thumbTintMode\n01010473=attr/fullBackupOnly\n01010474=attr/propertyXName\n01010475=attr/propertyYName\n01010476=attr/relinquishTaskIdentity\n01010477=attr/tileModeX\n01010478=attr/tileModeY\n01010479=attr/actionModeShareDrawable\n0101047a=attr/actionModeFindDrawable\n0101047b=attr/actionModeWebSearchDrawable\n0101047c=attr/transitionVisibilityMode\n0101047d=attr/minimumHorizontalAngle\n0101047e=attr/minimumVerticalAngle\n0101047f=attr/maximumAngle\n01010480=attr/searchViewStyle\n01010481=attr/closeIcon\n01010482=attr/goIcon\n01010483=attr/searchIcon\n01010484=attr/voiceIcon\n01010485=attr/commitIcon\n01010486=attr/suggestionRowLayout\n01010487=attr/queryBackground\n01010488=attr/submitBackground\n01010489=attr/buttonBarPositiveButtonStyle\n0101048a=attr/buttonBarNeutralButtonStyle\n0101048b=attr/buttonBarNegativeButtonStyle\n0101048c=attr/popupElevation\n0101048d=attr/actionBarPopupTheme\n0101048e=attr/multiArch\n0101048f=attr/touchscreenBlocksFocus\n01010490=attr/windowElevation\n01010491=attr/launchTaskBehindTargetAnimation\n01010492=attr/launchTaskBehindSourceAnimation\n01010493=attr/restrictionType\n01010494=attr/dayOfWeekBackground\n01010495=attr/dayOfWeekTextAppearance\n01010496=attr/headerMonthTextAppearance\n01010497=attr/headerDayOfMonthTextAppearance\n01010498=attr/headerYearTextAppearance\n01010499=attr/yearListItemTextAppearance\n0101049a=attr/yearListSelectorColor\n0101049b=attr/calendarTextColor\n0101049c=attr/recognitionService\n0101049d=attr/timePickerStyle\n0101049e=attr/timePickerDialogTheme\n0101049f=attr/headerTimeTextAppearance\n010104a0=attr/headerAmPmTextAppearance\n010104a1=attr/numbersTextColor\n010104a2=attr/numbersBackgroundColor\n010104a3=attr/numbersSelectorColor\n010104a4=attr/amPmTextColor\n010104a5=attr/amPmBackgroundColor\n010104a6=attr/searchKeyphraseRecognitionFlags\n010104a7=attr/checkMarkTint\n010104a8=attr/checkMarkTintMode\n010104a9=attr/popupTheme\n010104aa=attr/toolbarStyle\n010104ab=attr/windowClipToOutline\n010104ac=attr/datePickerDialogTheme\n010104ad=attr/showText\n010104ae=attr/windowReturnTransition\n010104af=attr/windowReenterTransition\n010104b0=attr/windowSharedElementReturnTransition\n010104b1=attr/windowSharedElementReenterTransition\n010104b2=attr/resumeWhilePausing\n010104b3=attr/datePickerMode\n010104b4=attr/timePickerMode\n010104b5=attr/inset\n010104b6=attr/letterSpacing\n010104b7=attr/fontFeatureSettings\n010104b8=attr/outlineProvider\n010104b9=attr/contentAgeHint\n010104ba=attr/country\n010104bb=attr/windowSharedElementsUseOverlay\n010104bc=attr/reparent\n010104bd=attr/reparentWithOverlay\n010104be=attr/ambientShadowAlpha\n010104bf=attr/spotShadowAlpha\n010104c0=attr/navigationIcon\n010104c1=attr/navigationContentDescription\n010104c2=attr/fragmentExitTransition\n010104c3=attr/fragmentEnterTransition\n010104c4=attr/fragmentSharedElementEnterTransition\n010104c5=attr/fragmentReturnTransition\n010104c6=attr/fragmentSharedElementReturnTransition\n010104c7=attr/fragmentReenterTransition\n010104c8=attr/fragmentAllowEnterTransitionOverlap\n010104c9=attr/fragmentAllowReturnTransitionOverlap\n010104ca=attr/patternPathData\n010104cb=attr/strokeAlpha\n010104cc=attr/fillAlpha\n010104cd=attr/windowActivityTransitions\n010104ce=attr/colorEdgeEffect\n010104cf=attr/resizeClip\n010104d0=attr/collapseContentDescription\n010104d1=attr/accessibilityTraversalBefore\n010104d2=attr/accessibilityTraversalAfter\n010104d3=attr/dialogPreferredPadding\n010104d4=attr/searchHintIcon\n010104d5=attr/revisionCode\n010104d6=attr/drawableTint\n010104d7=attr/drawableTintMode\n010104d8=attr/fraction\n010104d9=attr/trackTint\n010104da=attr/trackTintMode\n010104db=attr/start\n010104dc=attr/end\n010104dd=attr/breakStrategy\n010104de=attr/hyphenationFrequency\n010104df=attr/allowUndo\n010104e0=attr/windowLightStatusBar\n010104e1=attr/numbersInnerTextColor\n010104e2=attr/colorBackgroundFloating\n010104e3=attr/titleTextColor\n010104e4=attr/subtitleTextColor\n010104e5=attr/thumbPosition\n010104e6=attr/scrollIndicators\n010104e7=attr/contextClickable\n010104e8=attr/fingerprintAuthDrawable\n010104e9=attr/logoDescription\n010104ea=attr/extractNativeLibs\n010104eb=attr/fullBackupContent\n010104ec=attr/usesCleartextTraffic\n010104ed=attr/lockTaskMode\n010104ee=attr/autoVerify\n010104ef=attr/showForAllUsers\n010104f0=attr/supportsAssist\n010104f1=attr/supportsLaunchVoiceAssistFromKeyguard\n010104f2=attr/listMenuViewStyle\n010104f3=attr/subMenuArrow\n010104f4=attr/defaultWidth\n010104f5=attr/defaultHeight\n010104f6=attr/resizeableActivity\n010104f7=attr/supportsPictureInPicture\n010104f8=attr/titleMargin\n010104f9=attr/titleMarginStart\n010104fa=attr/titleMarginEnd\n010104fb=attr/titleMarginTop\n010104fc=attr/titleMarginBottom\n010104fd=attr/maxButtonHeight\n010104fe=attr/buttonGravity\n010104ff=attr/collapseIcon\n01010500=attr/level\n01010501=attr/contextPopupMenuStyle\n01010502=attr/textAppearancePopupMenuHeader\n01010503=attr/windowBackgroundFallback\n01010504=attr/defaultToDeviceProtectedStorage\n01010505=attr/directBootAware\n01010506=attr/preferenceFragmentStyle\n01010507=attr/canControlMagnification\n01010508=attr/languageTag\n01010509=attr/pointerIcon\n0101050a=attr/tickMark\n0101050b=attr/tickMarkTint\n0101050c=attr/tickMarkTintMode\n0101050d=attr/canPerformGestures\n0101050e=attr/externalService\n0101050f=attr/supportsLocalInteraction\n01010510=attr/startX\n01010511=attr/startY\n01010512=attr/endX\n01010513=attr/endY\n01010514=attr/offset\n01010515=attr/use32bitAbi\n01010516=attr/bitmap\n01010517=attr/hotSpotX\n01010518=attr/hotSpotY\n01010519=attr/version\n0101051a=attr/backupInForeground\n0101051b=attr/countDown\n0101051c=attr/canRecord\n0101051d=attr/tunerCount\n0101051e=attr/fillType\n0101051f=attr/popupEnterTransition\n01010520=attr/popupExitTransition\n01010521=attr/forceHasOverlappingRendering\n01010522=attr/contentInsetStartWithNavigation\n01010523=attr/contentInsetEndWithActions\n01010524=attr/numberPickerStyle\n01010525=attr/enableVrMode\n01010526=attr/hash\n01010527=attr/networkSecurityConfig\n01010528=attr/shortcutId\n01010529=attr/shortcutShortLabel\n0101052a=attr/shortcutLongLabel\n0101052b=attr/shortcutDisabledMessage\n0101052c=attr/roundIcon\n0101052d=attr/contextUri\n0101052e=attr/contextDescription\n0101052f=attr/showMetadataInPreview\n01010530=attr/colorSecondary\n01010531=attr/visibleToInstantApps\n01010532=attr/font\n01010533=attr/fontWeight\n01010534=attr/tooltipText\n01010535=attr/autoSizeTextType\n01010536=attr/autoSizeStepGranularity\n01010537=attr/autoSizePresetSizes\n01010538=attr/autoSizeMinTextSize\n01010539=attr/min\n0101053a=attr/rotationAnimation\n0101053b=attr/layout_marginHorizontal\n0101053c=attr/layout_marginVertical\n0101053d=attr/paddingHorizontal\n0101053e=attr/paddingVertical\n0101053f=attr/fontStyle\n01010540=attr/keyboardNavigationCluster\n01010541=attr/targetProcesses\n01010542=attr/nextClusterForward\n01010543=attr/colorError\n01010544=attr/focusedByDefault\n01010545=attr/appCategory\n01010546=attr/autoSizeMaxTextSize\n01010547=attr/recreateOnConfigChanges\n01010548=attr/certDigest\n01010549=attr/splitName\n0101054a=attr/colorMode\n0101054b=attr/isolatedSplits\n0101054c=attr/targetSandboxVersion\n0101054d=attr/canRequestFingerprintGestures\n0101054e=attr/alphabeticModifiers\n0101054f=attr/numericModifiers\n01010550=attr/fontProviderAuthority\n01010551=attr/fontProviderQuery\n01010552=attr/primaryContentAlpha\n01010553=attr/secondaryContentAlpha\n01010554=attr/requiredFeature\n01010555=attr/requiredNotFeature\n01010556=attr/autofillHints\n01010557=attr/fontProviderPackage\n01010558=attr/importantForAutofill\n01010559=attr/recycleEnabled\n0101055a=attr/isStatic\n0101055b=attr/isFeatureSplit\n0101055c=attr/singleLineTitle\n0101055d=attr/fontProviderCerts\n0101055e=attr/iconTint\n0101055f=attr/iconTintMode\n01010560=attr/maxAspectRatio\n01010561=attr/iconSpaceReserved\n01010562=attr/defaultFocusHighlightEnabled\n01010563=attr/persistentWhenFeatureAvailable\n01010564=attr/windowSplashscreenContent\n01010565=attr/requiredSystemPropertyName\n01010566=attr/requiredSystemPropertyValue\n01010567=attr/justificationMode\n01010568=attr/autofilledHighlight\n01010569=attr/showWhenLocked\n0101056a=attr/turnScreenOn\n0101056b=attr/classLoader\n0101056c=attr/windowLightNavigationBar\n0101056d=attr/navigationBarDividerColor\n0101056e=attr/cantSaveState\n0101056f=attr/ttcIndex\n01010570=attr/fontVariationSettings\n01010571=attr/dialogCornerRadius\n01010572=attr/compileSdkVersion\n01010573=attr/compileSdkVersionCodename\n01010574=attr/screenReaderFocusable\n01010575=attr/buttonCornerRadius\n01010576=attr/versionCodeMajor\n01010577=attr/versionMajor\n01010578=attr/isVrOnly\n01010579=attr/widgetFeatures\n0101057a=attr/appComponentFactory\n0101057b=attr/fallbackLineSpacing\n0101057c=attr/accessibilityPaneTitle\n0101057d=attr/firstBaselineToTopHeight\n0101057e=attr/lastBaselineToBottomHeight\n0101057f=attr/lineHeight\n01010580=attr/accessibilityHeading\n01010581=attr/outlineSpotShadowColor\n01010582=attr/outlineAmbientShadowColor\n01010583=attr/maxLongVersionCode\n01010584=attr/userRestriction\n01010585=attr/textFontWeight\n01010586=attr/windowLayoutInDisplayCutoutMode\n01010587=attr/packageType\n01010588=attr/opticalInsetLeft\n01010589=attr/opticalInsetTop\n0101058a=attr/opticalInsetRight\n0101058b=attr/opticalInsetBottom\n0101058c=attr/forceDarkAllowed\n0101058d=attr/supportsAmbientMode\n0101058e=attr/usesNonSdkApi\n0101058f=attr/nonInteractiveUiTimeout\n01010590=attr/isLightTheme\n01010591=attr/isSplitRequired\n01010592=attr/textLocale\n01010593=attr/settingsSliceUri\n01010594=attr/shell\n01010595=attr/interactiveUiTimeout\n01010596=attr/supportsMultipleDisplays\n01010597=attr/useAppZygote\n01010598=attr/selectionDividerHeight\n01010599=attr/foregroundServiceType\n0101059a=attr/hasFragileUserData\n0101059b=attr/minAspectRatio\n0101059c=attr/inheritShowWhenLocked\n0101059d=attr/zygotePreloadName\n0101059e=attr/useEmbeddedDex\n0101059f=attr/forceUriPermissions\n01010600=attr/allowClearUserDataOnFailedRestore\n01010601=attr/allowAudioPlaybackCapture\n01010602=attr/secureElementName\n01010603=attr/requestLegacyExternalStorage\n01010604=attr/enforceStatusBarContrast\n01010605=attr/enforceNavigationBarContrast\n01010606=attr/identifier\n01010607=attr/importantForContentCapture\n01010608=attr/forceQueryable\n01010609=attr/resourcesMap\n0101060a=attr/animatedImageDrawable\n0101060b=attr/htmlDescription\n0101060c=attr/preferMinimalPostProcessing\n0101060d=attr/supportsInlineSuggestions\n0101060e=attr/crossProfile\n0101060f=attr/canTakeScreenshot\n01010610=attr/sdkVersion\n01010611=attr/minExtensionVersion\n01010612=attr/allowNativeHeapPointerTagging\n01010613=attr/autoRevokePermissions\n01010614=attr/preserveLegacyExternalStorage\n01010615=attr/mimeGroup\n01010616=attr/gwpAsanMode\n01020000=id/background\n01020001=id/checkbox\n01020002=id/content\n01020003=id/edit\n01020004=id/empty\n01020005=id/hint\n01020006=id/icon\n01020007=id/icon1\n01020008=id/icon2\n01020009=id/input\n0102000a=id/list\n0102000b=id/message\n0102000c=id/primary\n0102000d=id/progress\n0102000e=id/selectedIcon\n0102000f=id/secondaryProgress\n01020010=id/summary\n01020011=id/tabcontent\n01020012=id/tabhost\n01020013=id/tabs\n01020014=id/text1\n01020015=id/text2\n01020016=id/title\n01020017=id/toggle\n01020018=id/widget_frame\n01020019=id/button1\n0102001a=id/button2\n0102001b=id/button3\n0102001c=id/extractArea\n0102001d=id/candidatesArea\n0102001e=id/inputArea\n0102001f=id/selectAll\n01020020=id/cut\n01020021=id/copy\n01020022=id/paste\n01020023=id/copyUrl\n01020024=id/switchInputMethod\n01020025=id/inputExtractEditText\n01020026=id/keyboardView\n01020027=id/closeButton\n01020028=id/startSelectingText\n01020029=id/stopSelectingText\n0102002a=id/addToDictionary\n0102002b=id/custom\n0102002c=id/home\n0102002d=id/selectTextMode\n0102002e=id/mask\n0102002f=id/statusBarBackground\n01020030=id/navigationBarBackground\n01020031=id/pasteAsPlainText\n01020032=id/undo\n01020033=id/redo\n01020034=id/replaceText\n01020035=id/shareText\n01020036=id/accessibilityActionShowOnScreen\n01020037=id/accessibilityActionScrollToPosition\n01020038=id/accessibilityActionScrollUp\n01020039=id/accessibilityActionScrollLeft\n0102003a=id/accessibilityActionScrollDown\n0102003b=id/accessibilityActionScrollRight\n0102003c=id/accessibilityActionContextClick\n0102003d=id/accessibilityActionSetProgress\n0102003e=id/icon_frame\n0102003f=id/list_container\n01020040=id/switch_widget\n01020041=id/textAssist\n01020042=id/accessibilityActionMoveWindow\n01020043=id/autofill\n01020044=id/accessibilityActionShowTooltip\n01020045=id/accessibilityActionHideTooltip\n01020046=id/accessibilityActionPageUp\n01020047=id/accessibilityActionPageDown\n01020048=id/accessibilityActionPageLeft\n01020049=id/accessibilityActionPageRight\n0102004a=id/accessibilityActionPressAndHold\n0102004b=id/accessibilitySystemActionBack\n0102004c=id/accessibilitySystemActionHome\n0102004d=id/accessibilitySystemActionRecents\n0102004e=id/accessibilitySystemActionNotifications\n0102004f=id/accessibilitySystemActionQuickSettings\n01020050=id/accessibilitySystemActionPowerDialog\n01020051=id/accessibilitySystemActionToggleSplitScreen\n01020052=id/accessibilitySystemActionLockScreen\n01020053=id/accessibilitySystemActionTakeScreenshot\n01020054=id/accessibilityActionImeEnter\n01020055=id/ALT\n01020056=id/CTRL\n01020057=id/FUNCTION\n01020058=id/KEYCODE_0\n01020059=id/KEYCODE_1\n0102005a=id/KEYCODE_11\n0102005b=id/KEYCODE_12\n0102005c=id/KEYCODE_2\n0102005d=id/KEYCODE_3\n0102005e=id/KEYCODE_3D_MODE\n0102005f=id/KEYCODE_4\n01020060=id/KEYCODE_5\n01020061=id/KEYCODE_6\n01020062=id/KEYCODE_7\n01020063=id/KEYCODE_8\n01020064=id/KEYCODE_9\n01020065=id/KEYCODE_A\n01020066=id/KEYCODE_ALL_APPS\n01020067=id/KEYCODE_ALT_LEFT\n01020068=id/KEYCODE_ALT_RIGHT\n01020069=id/KEYCODE_APOSTROPHE\n0102006a=id/KEYCODE_APP_SWITCH\n0102006b=id/KEYCODE_ASSIST\n0102006c=id/KEYCODE_AT\n0102006d=id/KEYCODE_AVR_INPUT\n0102006e=id/KEYCODE_AVR_POWER\n0102006f=id/KEYCODE_B\n01020070=id/KEYCODE_BACK\n01020071=id/KEYCODE_BACKSLASH\n01020072=id/KEYCODE_BOOKMARK\n01020073=id/KEYCODE_BREAK\n01020074=id/KEYCODE_BRIGHTNESS_DOWN\n01020075=id/KEYCODE_BRIGHTNESS_UP\n01020076=id/KEYCODE_BUTTON_1\n01020077=id/KEYCODE_BUTTON_10\n01020078=id/KEYCODE_BUTTON_11\n01020079=id/KEYCODE_BUTTON_12\n0102007a=id/KEYCODE_BUTTON_13\n0102007b=id/KEYCODE_BUTTON_14\n0102007c=id/KEYCODE_BUTTON_15\n0102007d=id/KEYCODE_BUTTON_16\n0102007e=id/KEYCODE_BUTTON_2\n0102007f=id/KEYCODE_BUTTON_3\n01020080=id/KEYCODE_BUTTON_4\n01020081=id/KEYCODE_BUTTON_5\n01020082=id/KEYCODE_BUTTON_6\n01020083=id/KEYCODE_BUTTON_7\n01020084=id/KEYCODE_BUTTON_8\n01020085=id/KEYCODE_BUTTON_9\n01020086=id/KEYCODE_BUTTON_A\n01020087=id/KEYCODE_BUTTON_B\n01020088=id/KEYCODE_BUTTON_C\n01020089=id/KEYCODE_BUTTON_L1\n0102008a=id/KEYCODE_BUTTON_L2\n0102008b=id/KEYCODE_BUTTON_MODE\n0102008c=id/KEYCODE_BUTTON_R1\n0102008d=id/KEYCODE_BUTTON_R2\n0102008e=id/KEYCODE_BUTTON_SELECT\n0102008f=id/KEYCODE_BUTTON_START\n01020090=id/KEYCODE_BUTTON_THUMBL\n01020091=id/KEYCODE_BUTTON_THUMBR\n01020092=id/KEYCODE_BUTTON_X\n01020093=id/KEYCODE_BUTTON_Y\n01020094=id/KEYCODE_BUTTON_Z\n01020095=id/KEYCODE_C\n01020096=id/KEYCODE_CALCULATOR\n01020097=id/KEYCODE_CALENDAR\n01020098=id/KEYCODE_CALL\n01020099=id/KEYCODE_CAMERA\n0102009a=id/KEYCODE_CAPS_LOCK\n0102009b=id/KEYCODE_CAPTIONS\n0102009c=id/KEYCODE_CHANNEL_DOWN\n0102009d=id/KEYCODE_CHANNEL_UP\n0102009e=id/KEYCODE_CLEAR\n0102009f=id/KEYCODE_COMMA\n010200a0=id/KEYCODE_CONTACTS\n010200a1=id/KEYCODE_COPY\n010200a2=id/KEYCODE_CTRL_LEFT\n010200a3=id/KEYCODE_CTRL_RIGHT\n010200a4=id/KEYCODE_CUT\n010200a5=id/KEYCODE_D\n010200a6=id/KEYCODE_DEL\n010200a7=id/KEYCODE_DPAD_CENTER\n010200a8=id/KEYCODE_DPAD_DOWN\n010200a9=id/KEYCODE_DPAD_DOWN_LEFT\n010200aa=id/KEYCODE_DPAD_DOWN_RIGHT\n010200ab=id/KEYCODE_DPAD_LEFT\n010200ac=id/KEYCODE_DPAD_RIGHT\n010200ad=id/KEYCODE_DPAD_UP\n010200ae=id/KEYCODE_DPAD_UP_LEFT\n010200af=id/KEYCODE_DPAD_UP_RIGHT\n010200b0=id/KEYCODE_DVR\n010200b1=id/KEYCODE_E\n010200b2=id/KEYCODE_EISU\n010200b3=id/KEYCODE_ENDCALL\n010200b4=id/KEYCODE_ENTER\n010200b5=id/KEYCODE_ENVELOPE\n010200b6=id/KEYCODE_EQUALS\n010200b7=id/KEYCODE_ESCAPE\n010200b8=id/KEYCODE_EXPLORER\n010200b9=id/KEYCODE_F\n010200ba=id/KEYCODE_F1\n010200bb=id/KEYCODE_F10\n010200bc=id/KEYCODE_F11\n010200bd=id/KEYCODE_F12\n010200be=id/KEYCODE_F2\n010200bf=id/KEYCODE_F3\n010200c0=id/KEYCODE_F4\n010200c1=id/KEYCODE_F5\n010200c2=id/KEYCODE_F6\n010200c3=id/KEYCODE_F7\n010200c4=id/KEYCODE_F8\n010200c5=id/KEYCODE_F9\n010200c6=id/KEYCODE_FOCUS\n010200c7=id/KEYCODE_FORWARD\n010200c8=id/KEYCODE_FORWARD_DEL\n010200c9=id/KEYCODE_FUNCTION\n010200ca=id/KEYCODE_G\n010200cb=id/KEYCODE_GRAVE\n010200cc=id/KEYCODE_GUIDE\n010200cd=id/KEYCODE_H\n010200ce=id/KEYCODE_HEADSETHOOK\n010200cf=id/KEYCODE_HELP\n010200d0=id/KEYCODE_HENKAN\n010200d1=id/KEYCODE_HOME\n010200d2=id/KEYCODE_I\n010200d3=id/KEYCODE_INFO\n010200d4=id/KEYCODE_INSERT\n010200d5=id/KEYCODE_J\n010200d6=id/KEYCODE_K\n010200d7=id/KEYCODE_KANA\n010200d8=id/KEYCODE_KATAKANA_HIRAGANA\n010200d9=id/KEYCODE_L\n010200da=id/KEYCODE_LANGUAGE_SWITCH\n010200db=id/KEYCODE_LAST_CHANNEL\n010200dc=id/KEYCODE_LEFT_BRACKET\n010200dd=id/KEYCODE_M\n010200de=id/KEYCODE_MANNER_MODE\n010200df=id/KEYCODE_MEDIA_AUDIO_TRACK\n010200e0=id/KEYCODE_MEDIA_CLOSE\n010200e1=id/KEYCODE_MEDIA_EJECT\n010200e2=id/KEYCODE_MEDIA_FAST_FORWARD\n010200e3=id/KEYCODE_MEDIA_NEXT\n010200e4=id/KEYCODE_MEDIA_PAUSE\n010200e5=id/KEYCODE_MEDIA_PLAY\n010200e6=id/KEYCODE_MEDIA_PLAY_PAUSE\n010200e7=id/KEYCODE_MEDIA_PREVIOUS\n010200e8=id/KEYCODE_MEDIA_RECORD\n010200e9=id/KEYCODE_MEDIA_REWIND\n010200ea=id/KEYCODE_MEDIA_SKIP_BACKWARD\n010200eb=id/KEYCODE_MEDIA_SKIP_FORWARD\n010200ec=id/KEYCODE_MEDIA_SLEEP\n010200ed=id/KEYCODE_MEDIA_STEP_BACKWARD\n010200ee=id/KEYCODE_MEDIA_STEP_FORWARD\n010200ef=id/KEYCODE_MEDIA_STOP\n010200f0=id/KEYCODE_MEDIA_TOP_MENU\n010200f1=id/KEYCODE_MEDIA_WAKEUP\n010200f2=id/KEYCODE_MENU\n010200f3=id/KEYCODE_META_LEFT\n010200f4=id/KEYCODE_META_RIGHT\n010200f5=id/KEYCODE_MINUS\n010200f6=id/KEYCODE_MOVE_END\n010200f7=id/KEYCODE_MOVE_HOME\n010200f8=id/KEYCODE_MUHENKAN\n010200f9=id/KEYCODE_MUSIC\n010200fa=id/KEYCODE_MUTE\n010200fb=id/KEYCODE_N\n010200fc=id/KEYCODE_NAVIGATE_IN\n010200fd=id/KEYCODE_NAVIGATE_NEXT\n010200fe=id/KEYCODE_NAVIGATE_OUT\n010200ff=id/KEYCODE_NAVIGATE_PREVIOUS\n01020100=id/KEYCODE_NOTIFICATION\n01020101=id/KEYCODE_NUM\n01020102=id/KEYCODE_NUMPAD_0\n01020103=id/KEYCODE_NUMPAD_1\n01020104=id/KEYCODE_NUMPAD_2\n01020105=id/KEYCODE_NUMPAD_3\n01020106=id/KEYCODE_NUMPAD_4\n01020107=id/KEYCODE_NUMPAD_5\n01020108=id/KEYCODE_NUMPAD_6\n01020109=id/KEYCODE_NUMPAD_7\n0102010a=id/KEYCODE_NUMPAD_8\n0102010b=id/KEYCODE_NUMPAD_9\n0102010c=id/KEYCODE_NUMPAD_ADD\n0102010d=id/KEYCODE_NUMPAD_COMMA\n0102010e=id/KEYCODE_NUMPAD_DIVIDE\n0102010f=id/KEYCODE_NUMPAD_DOT\n01020110=id/KEYCODE_NUMPAD_ENTER\n01020111=id/KEYCODE_NUMPAD_EQUALS\n01020112=id/KEYCODE_NUMPAD_LEFT_PAREN\n01020113=id/KEYCODE_NUMPAD_MULTIPLY\n01020114=id/KEYCODE_NUMPAD_RIGHT_PAREN\n01020115=id/KEYCODE_NUMPAD_SUBTRACT\n01020116=id/KEYCODE_NUM_LOCK\n01020117=id/KEYCODE_O\n01020118=id/KEYCODE_P\n01020119=id/KEYCODE_PAGE_DOWN\n0102011a=id/KEYCODE_PAGE_UP\n0102011b=id/KEYCODE_PAIRING\n0102011c=id/KEYCODE_PASTE\n0102011d=id/KEYCODE_PERIOD\n0102011e=id/KEYCODE_PICTSYMBOLS\n0102011f=id/KEYCODE_PLUS\n01020120=id/KEYCODE_POUND\n01020121=id/KEYCODE_POWER\n01020122=id/KEYCODE_PROFILE_SWITCH\n01020123=id/KEYCODE_PROG_BLUE\n01020124=id/KEYCODE_PROG_GRED\n01020125=id/KEYCODE_PROG_GREEN\n01020126=id/KEYCODE_PROG_YELLOW\n01020127=id/KEYCODE_Q\n01020128=id/KEYCODE_R\n01020129=id/KEYCODE_REFRESH\n0102012a=id/KEYCODE_RIGHT_BRACKET\n0102012b=id/KEYCODE_RO\n0102012c=id/KEYCODE_S\n0102012d=id/KEYCODE_SCROLL_LOCK\n0102012e=id/KEYCODE_SEARCH\n0102012f=id/KEYCODE_SEMICOLON\n01020130=id/KEYCODE_SETTINGS\n01020131=id/KEYCODE_SHIFT_LEFT\n01020132=id/KEYCODE_SHIFT_RIGHT\n01020133=id/KEYCODE_SLASH\n01020134=id/KEYCODE_SOFT_LEFT\n01020135=id/KEYCODE_SOFT_RIGHT\n01020136=id/KEYCODE_SOFT_SLEEP\n01020137=id/KEYCODE_SPACE\n01020138=id/KEYCODE_STAR\n01020139=id/KEYCODE_STB_INPUT\n0102013a=id/KEYCODE_STB_POWER\n0102013b=id/KEYCODE_STEM_1\n0102013c=id/KEYCODE_STEM_2\n0102013d=id/KEYCODE_STEM_3\n0102013e=id/KEYCODE_STEM_PRIMARY\n0102013f=id/KEYCODE_SWITCH_CHARSET\n01020140=id/KEYCODE_SYM\n01020141=id/KEYCODE_SYSRQ\n01020142=id/KEYCODE_SYSTEM_NAVIGATION_DOWN\n01020143=id/KEYCODE_SYSTEM_NAVIGATION_LEFT\n01020144=id/KEYCODE_SYSTEM_NAVIGATION_RIGHT\n01020145=id/KEYCODE_SYSTEM_NAVIGATION_UP\n01020146=id/KEYCODE_T\n01020147=id/KEYCODE_TAB\n01020148=id/KEYCODE_THUMBS_DOWN\n01020149=id/KEYCODE_THUMBS_UP\n0102014a=id/KEYCODE_TV\n0102014b=id/KEYCODE_TV_ANTENNA_CABLE\n0102014c=id/KEYCODE_TV_AUDIO_DESCRIPTION\n0102014d=id/KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN\n0102014e=id/KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP\n0102014f=id/KEYCODE_TV_CONTENTS_MENU\n01020150=id/KEYCODE_TV_DATA_SERVICE\n01020151=id/KEYCODE_TV_INPUT\n01020152=id/KEYCODE_TV_INPUT_COMPONENT_1\n01020153=id/KEYCODE_TV_INPUT_COMPONENT_2\n01020154=id/KEYCODE_TV_INPUT_COMPOSITE_1\n01020155=id/KEYCODE_TV_INPUT_COMPOSITE_2\n01020156=id/KEYCODE_TV_INPUT_HDMI_1\n01020157=id/KEYCODE_TV_INPUT_HDMI_2\n01020158=id/KEYCODE_TV_INPUT_HDMI_3\n01020159=id/KEYCODE_TV_INPUT_HDMI_4\n0102015a=id/KEYCODE_TV_INPUT_VGA_1\n0102015b=id/KEYCODE_TV_MEDIA_CONTEXT_MENU\n0102015c=id/KEYCODE_TV_NETWORK\n0102015d=id/KEYCODE_TV_NUMBER_ENTRY\n0102015e=id/KEYCODE_TV_POWER\n0102015f=id/KEYCODE_TV_RADIO_SERVICE\n01020160=id/KEYCODE_TV_SATELLITE\n01020161=id/KEYCODE_TV_SATELLITE_BS\n01020162=id/KEYCODE_TV_SATELLITE_CS\n01020163=id/KEYCODE_TV_SATELLITE_SERVICE\n01020164=id/KEYCODE_TV_TELETEXT\n01020165=id/KEYCODE_TV_TERRESTRIAL_ANALOG\n01020166=id/KEYCODE_TV_TERRESTRIAL_DIGITAL\n01020167=id/KEYCODE_TV_TIMER_PROGRAMMING\n01020168=id/KEYCODE_TV_ZOOM_MODE\n01020169=id/KEYCODE_U\n0102016a=id/KEYCODE_UNKNOWN\n0102016b=id/KEYCODE_V\n0102016c=id/KEYCODE_VOICE_ASSIST\n0102016d=id/KEYCODE_VOLUME_DOWN\n0102016e=id/KEYCODE_VOLUME_MUTE\n0102016f=id/KEYCODE_VOLUME_UP\n01020170=id/KEYCODE_W\n01020171=id/KEYCODE_WINDOW\n01020172=id/KEYCODE_X\n01020173=id/KEYCODE_Y\n01020174=id/KEYCODE_YEN\n01020175=id/KEYCODE_Z\n01020176=id/KEYCODE_ZENKAKU_HANKAKU\n01020177=id/KEYCODE_ZOOM_IN\n01020178=id/KEYCODE_ZOOM_OUT\n01020179=id/META\n0102017a=id/SHIFT\n0102017b=id/SYM\n0102017c=id/aboveThumb\n0102017d=id/accessibilityActionClickOnClickableSpan\n0102017e=id/accessibility_button_chooser_grid\n0102017f=id/accessibility_button_prompt\n01020180=id/accessibility_button_prompt_prologue\n01020181=id/accessibility_button_target_icon\n01020182=id/accessibility_button_target_label\n01020183=id/accessibility_controlScreen_description\n01020184=id/accessibility_controlScreen_icon\n01020185=id/accessibility_controlScreen_title\n01020186=id/accessibility_encryption_warning\n01020187=id/accessibility_performAction_description\n01020188=id/accessibility_performAction_icon\n01020189=id/accessibility_performAction_title\n0102018a=id/accessibility_permissionDialog_description\n0102018b=id/accessibility_permissionDialog_icon\n0102018c=id/accessibility_permissionDialog_title\n0102018d=id/accessibility_permission_enable_allow_button\n0102018e=id/accessibility_permission_enable_deny_button\n0102018f=id/accessibility_shortcut_target_checkbox\n01020190=id/accessibility_shortcut_target_icon\n01020191=id/accessibility_shortcut_target_label\n01020192=id/accessibility_shortcut_target_status\n01020193=id/accountPreferences\n01020194=id/account_name\n01020195=id/account_row_icon\n01020196=id/account_row_text\n01020197=id/account_type\n01020198=id/action0\n01020199=id/action1\n0102019a=id/action2\n0102019b=id/action3\n0102019c=id/action4\n0102019d=id/actionDone\n0102019e=id/actionGo\n0102019f=id/actionNext\n010201a0=id/actionNone\n010201a1=id/actionPrevious\n010201a2=id/actionSearch\n010201a3=id/actionSend\n010201a4=id/actionUnspecified\n010201a5=id/action_bar\n010201a6=id/action_bar_container\n010201a7=id/action_bar_spinner\n010201a8=id/action_bar_subtitle\n010201a9=id/action_bar_title\n010201aa=id/action_context_bar\n010201ab=id/action_divider\n010201ac=id/action_menu_divider\n010201ad=id/action_menu_presenter\n010201ae=id/action_mode_bar\n010201af=id/action_mode_bar_stub\n010201b0=id/action_mode_close_button\n010201b1=id/actions\n010201b2=id/actions_container\n010201b3=id/actions_container_layout\n010201b4=id/activity_chooser_view_content\n010201b5=id/add\n010201b6=id/addToDictionaryButton\n010201b7=id/adjustNothing\n010201b8=id/adjustPan\n010201b9=id/adjustResize\n010201ba=id/adjustUnspecified\n010201bb=id/aerr_app_info\n010201bc=id/aerr_close\n010201bd=id/aerr_mute\n010201be=id/aerr_report\n010201bf=id/aerr_restart\n010201c0=id/aerr_wait\n010201c1=id/afterDescendants\n010201c2=id/alarm\n010201c3=id/alertTitle\n010201c4=id/alerted_icon\n010201c5=id/alias\n010201c6=id/alignBounds\n010201c7=id/alignMargins\n010201c8=id/all\n010201c9=id/all_scroll\n010201ca=id/allow_button\n010201cb=id/allowed\n010201cc=id/alternative\n010201cd=id/always\n010201ce=id/alwaysScroll\n010201cf=id/alwaysUse\n010201d0=id/amPm\n010201d1=id/am_label\n010201d2=id/am_pm_spinner\n010201d3=id/ampm_layout\n010201d4=id/animation\n010201d5=id/animator\n010201d6=id/anyRtl\n010201d7=id/appPredictor\n010201d8=id/app_name_divider\n010201d9=id/app_name_text\n010201da=id/app_ops\n010201db=id/appop\n010201dc=id/arrow\n010201dd=id/ask_checkbox\n010201de=id/assertive\n010201df=id/atThumb\n010201e0=id/audio\n010201e1=id/authtoken_type\n010201e2=id/auto\n010201e3=id/auto_fit\n010201e4=id/autofill_dataset_footer\n010201e5=id/autofill_dataset_header\n010201e6=id/autofill_dataset_icon\n010201e7=id/autofill_dataset_list\n010201e8=id/autofill_dataset_picker\n010201e9=id/autofill_dataset_title\n010201ea=id/autofill_save\n010201eb=id/autofill_save_custom_subtitle\n010201ec=id/autofill_save_icon\n010201ed=id/autofill_save_no\n010201ee=id/autofill_save_title\n010201ef=id/autofill_save_yes\n010201f0=id/back_button\n010201f1=id/balanced\n010201f2=id/beforeDescendants\n010201f3=id/beginning\n010201f4=id/behind\n010201f5=id/bevel\n010201f6=id/big_picture\n010201f7=id/big_text\n010201f8=id/blocksDescendants\n010201f9=id/body\n010201fa=id/bold\n010201fb=id/bool\n010201fc=id/bottom\n010201fd=id/bottom_to_top\n010201fe=id/bounds\n010201ff=id/breadcrumb_section\n01020200=id/bubble_button\n01020201=id/bundle\n01020202=id/bundle_array\n01020203=id/butt\n01020204=id/button0\n01020205=id/button4\n01020206=id/button5\n01020207=id/button6\n01020208=id/button7\n01020209=id/buttonPanel\n0102020a=id/button_always\n0102020b=id/button_bar\n0102020c=id/button_bar_container\n0102020d=id/button_once\n0102020e=id/buttons\n0102020f=id/by_common\n01020210=id/by_common_header\n01020211=id/by_org\n01020212=id/by_org_header\n01020213=id/by_org_unit\n01020214=id/by_org_unit_header\n01020215=id/calendar\n01020216=id/calendar_view\n01020217=id/camera\n01020218=id/cancel\n01020219=id/caption\n0102021a=id/cell\n0102021b=id/center\n0102021c=id/centerCrop\n0102021d=id/centerInside\n0102021e=id/center_horizontal\n0102021f=id/center_vertical\n01020220=id/challenge\n01020221=id/characterPicker\n01020222=id/characters\n01020223=id/check\n01020224=id/checked\n01020225=id/choice\n01020226=id/chooser_action_row\n01020227=id/chooser_copy_button\n01020228=id/chooser_header\n01020229=id/chooser_row_text_option\n0102022a=id/chronometer\n0102022b=id/clamp\n0102022c=id/clearDefaultHint\n0102022d=id/clipBounds\n0102022e=id/clip_children_set_tag\n0102022f=id/clip_children_tag\n01020230=id/clip_horizontal\n01020231=id/clip_to_padding_tag\n01020232=id/clip_vertical\n01020233=id/clock\n01020234=id/close_window\n01020235=id/collapseActionView\n01020236=id/collapsing\n01020237=id/colorMode\n01020238=id/colorType\n01020239=id/column\n0102023a=id/columnWidth\n0102023b=id/companion\n0102023c=id/compat_checkbox\n0102023d=id/configurator\n0102023e=id/connectedDevice\n0102023f=id/container\n01020240=id/contentPanel\n01020241=id/content_preview_container\n01020242=id/content_preview_file_area\n01020243=id/content_preview_file_icon\n01020244=id/content_preview_file_layout\n01020245=id/content_preview_file_thumbnail\n01020246=id/content_preview_filename\n01020247=id/content_preview_image_1_large\n01020248=id/content_preview_image_2_large\n01020249=id/content_preview_image_2_small\n0102024a=id/content_preview_image_3_small\n0102024b=id/content_preview_image_area\n0102024c=id/content_preview_text\n0102024d=id/content_preview_text_area\n0102024e=id/content_preview_text_layout\n0102024f=id/content_preview_thumbnail\n01020250=id/content_preview_title\n01020251=id/content_preview_title_layout\n01020252=id/context_menu\n01020253=id/conversation_face_pile\n01020254=id/conversation_face_pile_bottom\n01020255=id/conversation_face_pile_bottom_background\n01020256=id/conversation_face_pile_top\n01020257=id/conversation_header\n01020258=id/conversation_icon\n01020259=id/conversation_icon_badge\n0102025a=id/conversation_icon_badge_bg\n0102025b=id/conversation_icon_badge_ring\n0102025c=id/conversation_icon_container\n0102025d=id/conversation_image_message_container\n0102025e=id/conversation_text\n0102025f=id/conversation_unread_count\n01020260=id/costsMoney\n01020261=id/cross_task_transition\n01020262=id/crossfade\n01020263=id/crosshair\n01020264=id/current_scene\n01020265=id/customPanel\n01020266=id/cycle\n01020267=id/dangerous\n01020268=id/dataSync\n01020269=id/date\n0102026a=id/datePicker\n0102026b=id/date_picker_day_picker\n0102026c=id/date_picker_header\n0102026d=id/date_picker_header_date\n0102026e=id/date_picker_header_year\n0102026f=id/date_picker_year_picker\n01020270=id/datetime\n01020271=id/day\n01020272=id/day_names\n01020273=id/day_picker_view_pager\n01020274=id/decimal\n01020275=id/decor_content_parent\n01020276=id/decrement\n01020277=id/default\n01020278=id/defaultPosition\n01020279=id/default_activity_button\n0102027a=id/default_loading_view\n0102027b=id/deleteButton\n0102027c=id/density\n0102027d=id/deny_button\n0102027e=id/description\n0102027f=id/development\n01020280=id/dialog\n01020281=id/disableHome\n01020282=id/disabled\n01020283=id/disallowed\n01020284=id/discouraged\n01020285=id/divider\n01020286=id/documenter\n01020287=id/dpad\n01020288=id/drag\n01020289=id/dropdown\n0102028a=id/edit_query\n0102028b=id/editable\n0102028c=id/edittext_container\n0102028d=id/eight\n0102028e=id/email\n0102028f=id/end\n01020290=id/evenOdd\n01020291=id/exclude\n01020292=id/excludeDescendants\n01020293=id/expandChallengeHandle\n01020294=id/expand_activities_button\n01020295=id/expand_button\n01020296=id/expand_button_and_content_container\n01020297=id/expand_button_container\n01020298=id/expand_button_inner_container\n01020299=id/expanded_menu\n0102029a=id/expires_on\n0102029b=id/expires_on_header\n0102029c=id/fade_in\n0102029d=id/fade_in_out\n0102029e=id/fade_out\n0102029f=id/feedbackAllMask\n010202a0=id/feedbackAudible\n010202a1=id/feedbackGeneric\n010202a2=id/feedbackHaptic\n010202a3=id/feedbackSpoken\n010202a4=id/feedbackVisual\n010202a5=id/ffwd\n010202a6=id/fill\n010202a7=id/fillInIntent\n010202a8=id/fill_horizontal\n010202a9=id/fill_parent\n010202aa=id/fill_vertical\n010202ab=id/find\n010202ac=id/find_next\n010202ad=id/find_prev\n010202ae=id/finger\n010202af=id/fingerprints\n010202b0=id/firstStrong\n010202b1=id/firstStrongLtr\n010202b2=id/firstStrongRtl\n010202b3=id/fitCenter\n010202b4=id/fitEnd\n010202b5=id/fitStart\n010202b6=id/fitXY\n010202b7=id/five\n010202b8=id/flagDefault\n010202b9=id/flagEnableAccessibilityVolume\n010202ba=id/flagForceAscii\n010202bb=id/flagIncludeNotImportantViews\n010202bc=id/flagNavigateNext\n010202bd=id/flagNavigatePrevious\n010202be=id/flagNoAccessoryAction\n010202bf=id/flagNoEnterAction\n010202c0=id/flagNoExtractUi\n010202c1=id/flagNoFullscreen\n010202c2=id/flagNoPersonalizedLearning\n010202c3=id/flagReportViewIds\n010202c4=id/flagRequestAccessibilityButton\n010202c5=id/flagRequestEnhancedWebAccessibility\n010202c6=id/flagRequestFilterKeyEvents\n010202c7=id/flagRequestFingerprintGestures\n010202c8=id/flagRequestMultiFingerGestures\n010202c9=id/flagRequestShortcutWarningDialogSpokenFeedback\n010202ca=id/flagRequestTouchExplorationMode\n010202cb=id/flagRetrieveInteractiveWindows\n010202cc=id/flagServiceHandlesDoubleTap\n010202cd=id/floatType\n010202ce=id/floating\n010202cf=id/floating_toolbar_menu_item_image\n010202d0=id/floating_toolbar_menu_item_image_button\n010202d1=id/floating_toolbar_menu_item_text\n010202d2=id/fontScale\n010202d3=id/four\n010202d4=id/full\n010202d5=id/fullSensor\n010202d6=id/fullUser\n010202d7=id/fullscreenArea\n010202d8=id/game\n010202d9=id/gone\n010202da=id/grab\n010202db=id/grabbing\n010202dc=id/grant_credentials_permission_message_footer\n010202dd=id/grant_credentials_permission_message_header\n010202de=id/gravity\n010202df=id/group_divider\n010202e0=id/group_message_container\n010202e1=id/hand\n010202e2=id/hardRestricted\n010202e3=id/hard_keyboard_section\n010202e4=id/hard_keyboard_switch\n010202e5=id/hardware\n010202e6=id/hdpi\n010202e7=id/hdr\n010202e8=id/header_icon_container\n010202e9=id/header_text\n010202ea=id/header_text_divider\n010202eb=id/header_text_secondary\n010202ec=id/header_text_secondary_divider\n010202ed=id/headers\n010202ee=id/help\n010202ef=id/hidden\n010202f0=id/hide_from_picker\n010202f1=id/high\n010202f2=id/high_quality\n010202f3=id/holo\n010202f4=id/homeAsUp\n010202f5=id/home_screen\n010202f6=id/horizontal\n010202f7=id/horizontal_double_arrow\n010202f8=id/hour\n010202f9=id/hours\n010202fa=id/icon_badge\n010202fb=id/icon_menu\n010202fc=id/icon_menu_presenter\n010202fd=id/ifContentScrolls\n010202fe=id/ifRoom\n010202ff=id/if_whitelisted\n01020300=id/image\n01020301=id/immersive_cling_back_bg\n01020302=id/immersive_cling_back_bg_light\n01020303=id/immersive_cling_chevron\n01020304=id/immersive_cling_description\n01020305=id/immersive_cling_title\n01020306=id/immutablyRestricted\n01020307=id/inbox_text0\n01020308=id/inbox_text1\n01020309=id/inbox_text2\n0102030a=id/inbox_text3\n0102030b=id/inbox_text4\n0102030c=id/inbox_text5\n0102030d=id/inbox_text6\n0102030e=id/incidentReportApprover\n0102030f=id/include\n01020310=id/increment\n01020311=id/index\n01020312=id/infinite\n01020313=id/inherit\n01020314=id/inputExtractAccessories\n01020315=id/inputExtractAction\n01020316=id/input_block\n01020317=id/input_header\n01020318=id/input_hour\n01020319=id/input_minute\n0102031a=id/input_mode\n0102031b=id/input_separator\n0102031c=id/insertion_handle\n0102031d=id/inside\n0102031e=id/insideInset\n0102031f=id/insideOverlay\n01020320=id/installer\n01020321=id/instant\n01020322=id/intType\n01020323=id/integer\n01020324=id/inter_word\n01020325=id/internalEmpty\n01020326=id/internalOnly\n01020327=id/intoExisting\n01020328=id/invisible\n01020329=id/issued_on\n0102032a=id/issued_on_header\n0102032b=id/issued_to_header\n0102032c=id/italic\n0102032d=id/item_touch_helper_previous_elevation\n0102032e=id/jumpcut\n0102032f=id/keyboard\n01020330=id/keyboardHidden\n01020331=id/keyguard\n01020332=id/keyguard_click_area\n01020333=id/keyguard_message_area\n01020334=id/label\n01020335=id/label_error\n01020336=id/label_hour\n01020337=id/label_minute\n01020338=id/landscape\n01020339=id/large\n0102033a=id/launchRecognizer\n0102033b=id/launchWebSearch\n0102033c=id/layoutDirection\n0102033d=id/ldpi\n0102033e=id/left\n0102033f=id/leftPanel\n01020340=id/leftSpacer\n01020341=id/left_icon\n01020342=id/left_to_right\n01020343=id/line\n01020344=id/line1\n01020345=id/linear\n01020346=id/listContainer\n01020347=id/listMode\n01020348=id/list_footer\n01020349=id/list_item\n0102034a=id/list_menu_presenter\n0102034b=id/liveAudio\n0102034c=id/locale\n0102034d=id/locale_search_menu\n0102034e=id/location\n0102034f=id/lock_screen\n01020350=id/locked\n01020351=id/low\n01020352=id/ltr\n01020353=id/map\n01020354=id/maps\n01020355=id/marquee\n01020356=id/marquee_forever\n01020357=id/match_parent\n01020358=id/matches\n01020359=id/material\n0102035a=id/matrix\n0102035b=id/maximize_window\n0102035c=id/mcc\n0102035d=id/mdpi\n0102035e=id/mediaPlayback\n0102035f=id/mediaProjection\n01020360=id/media_actions\n01020361=id/media_route_control_frame\n01020362=id/media_route_extended_settings_button\n01020363=id/media_route_list\n01020364=id/media_route_volume_layout\n01020365=id/media_route_volume_slider\n01020366=id/media_seamless\n01020367=id/media_seamless_image\n01020368=id/media_seamless_text\n01020369=id/mediacontroller_progress\n0102036a=id/menu\n0102036b=id/message_icon\n0102036c=id/message_icon_container\n0102036d=id/message_name\n0102036e=id/message_text\n0102036f=id/messaging_group_content_container\n01020370=id/messaging_group_icon_container\n01020371=id/messaging_group_sending_progress\n01020372=id/messaging_group_sending_progress_container\n01020373=id/mic\n01020374=id/micro\n01020375=id/microphone\n01020376=id/middle\n01020377=id/midpoint\n01020378=id/minute\n01020379=id/minutes\n0102037a=id/mirror\n0102037b=id/miter\n0102037c=id/mnc\n0102037d=id/modeLarge\n0102037e=id/modeMedium\n0102037f=id/modeSmall\n01020380=id/mode_in\n01020381=id/mode_normal\n01020382=id/mode_out\n01020383=id/monospace\n01020384=id/month\n01020385=id/month_name\n01020386=id/month_view\n01020387=id/multi-select\n01020388=id/multiple\n01020389=id/multipleChoice\n0102038a=id/multipleChoiceModal\n0102038b=id/multiply\n0102038c=id/music\n0102038d=id/navigation\n0102038e=id/nest\n0102038f=id/never\n01020390=id/new_app_action\n01020391=id/new_app_description\n01020392=id/new_app_icon\n01020393=id/news\n01020394=id/next\n01020395=id/next_button\n01020396=id/nine\n01020397=id/no\n01020398=id/noExcludeDescendants\n01020399=id/noHideDescendants\n0102039a=id/no_applications_message\n0102039b=id/no_drop\n0102039c=id/no_permissions\n0102039d=id/nokeys\n0102039e=id/nonZero\n0102039f=id/nonav\n010203a0=id/none\n010203a1=id/normal\n010203a2=id/nosensor\n010203a3=id/notification\n010203a4=id/notification_action_index_tag\n010203a5=id/notification_action_list_margin_target\n010203a6=id/notification_content_container\n010203a7=id/notification_custom_view_index_tag\n010203a8=id/notification_header\n010203a9=id/notification_main_column\n010203aa=id/notification_material_reply_container\n010203ab=id/notification_material_reply_progress\n010203ac=id/notification_material_reply_text_1\n010203ad=id/notification_material_reply_text_1_container\n010203ae=id/notification_material_reply_text_2\n010203af=id/notification_material_reply_text_3\n010203b0=id/notification_media_content\n010203b1=id/notification_media_elapsed_time\n010203b2=id/notification_media_progress\n010203b3=id/notification_media_progress_bar\n010203b4=id/notification_media_progress_time\n010203b5=id/notification_media_seekbar_container\n010203b6=id/notification_media_total_time\n010203b7=id/notification_messaging\n010203b8=id/notouch\n010203b9=id/number\n010203ba=id/numberDecimal\n010203bb=id/numberPassword\n010203bc=id/numberSigned\n010203bd=id/numberpicker_input\n010203be=id/oem\n010203bf=id/off\n010203c0=id/ok\n010203c1=id/old_app_action\n010203c2=id/old_app_icon\n010203c3=id/on\n010203c4=id/one\n010203c5=id/oneLine\n010203c6=id/opaque\n010203c7=id/opticalBounds\n010203c8=id/option1\n010203c9=id/option2\n010203ca=id/option3\n010203cb=id/orientation\n010203cc=id/original_app_icon\n010203cd=id/original_message\n010203ce=id/outsideInset\n010203cf=id/outsideOverlay\n010203d0=id/oval\n010203d1=id/overflow\n010203d2=id/overflow_menu_presenter\n010203d3=id/overlay\n010203d4=id/overlay_display_window_texture\n010203d5=id/overlay_display_window_title\n010203d6=id/package_icon\n010203d7=id/package_label\n010203d8=id/packages_list\n010203d9=id/paddedBounds\n010203da=id/pageDeleteDropTarget\n010203db=id/parentMatrix\n010203dc=id/parentPanel\n010203dd=id/pathType\n010203de=id/pause\n010203df=id/pending_intent_tag\n010203e0=id/perm_icon\n010203e1=id/perm_money_icon\n010203e2=id/perm_money_label\n010203e3=id/perm_name\n010203e4=id/permission_group\n010203e5=id/permission_icon\n010203e6=id/permission_list\n010203e7=id/perms_list\n010203e8=id/persistAcrossReboots\n010203e9=id/persistNever\n010203ea=id/persistRootOnly\n010203eb=id/personalInfo\n010203ec=id/phone\n010203ed=id/phoneCall\n010203ee=id/pickers\n010203ef=id/pin_cancel_button\n010203f0=id/pin_confirm_text\n010203f1=id/pin_error_message\n010203f2=id/pin_message\n010203f3=id/pin_new_text\n010203f4=id/pin_ok_button\n010203f5=id/pin_text\n010203f6=id/placeholder\n010203f7=id/pm_label\n010203f8=id/polite\n010203f9=id/popup_submenu_presenter\n010203fa=id/portrait\n010203fb=id/pre23\n010203fc=id/preferExternal\n010203fd=id/prefs\n010203fe=id/prefs_container\n010203ff=id/prefs_frame\n01020400=id/preinstalled\n01020401=id/pressed\n01020402=id/prev\n01020403=id/privileged\n01020404=id/productivity\n01020405=id/profile_badge\n01020406=id/profile_button\n01020407=id/profile_pager\n01020408=id/profile_tabhost\n01020409=id/progressContainer\n0102040a=id/progress_circular\n0102040b=id/progress_horizontal\n0102040c=id/progress_number\n0102040d=id/progress_percent\n0102040e=id/queryRewriteFromData\n0102040f=id/queryRewriteFromText\n01020410=id/qwerty\n01020411=id/radial\n01020412=id/radial_picker\n01020413=id/radio\n01020414=id/radio_power\n01020415=id/random\n01020416=id/reask_hint\n01020417=id/reconfigurable\n01020418=id/rectangle\n01020419=id/remote_input\n0102041a=id/remote_input_progress\n0102041b=id/remote_input_send\n0102041c=id/remote_input_tag\n0102041d=id/remote_input_text\n0102041e=id/removed\n0102041f=id/repeat\n01020420=id/replace_app_icon\n01020421=id/replace_message\n01020422=id/reply_icon_action\n01020423=id/resolver_button_bar_divider\n01020424=id/resolver_empty_state\n01020425=id/resolver_empty_state_button\n01020426=id/resolver_empty_state_container\n01020427=id/resolver_empty_state_icon\n01020428=id/resolver_empty_state_progress\n01020429=id/resolver_empty_state_subtitle\n0102042a=id/resolver_empty_state_title\n0102042b=id/resolver_list\n0102042c=id/resolver_tab_divider\n0102042d=id/restart\n0102042e=id/retailDemo\n0102042f=id/reverse\n01020430=id/reverseLandscape\n01020431=id/reversePortrait\n01020432=id/rew\n01020433=id/right\n01020434=id/rightSpacer\n01020435=id/right_container\n01020436=id/right_icon\n01020437=id/right_icon_container\n01020438=id/right_to_left\n01020439=id/ring\n0102043a=id/ringtone\n0102043b=id/rotate\n0102043c=id/round\n0102043d=id/row\n0102043e=id/rowTypeId\n0102043f=id/rtl\n01020440=id/runtime\n01020441=id/sans\n01020442=id/scene_layoutid_cache\n01020443=id/screen\n01020444=id/screenLayout\n01020445=id/screenSize\n01020446=id/scrim\n01020447=id/scrollView\n01020448=id/scrolling\n01020449=id/seamless\n0102044a=id/search_app_icon\n0102044b=id/search_badge\n0102044c=id/search_bar\n0102044d=id/search_button\n0102044e=id/search_close_btn\n0102044f=id/search_edit_frame\n01020450=id/search_go_btn\n01020451=id/search_mag_icon\n01020452=id/search_plate\n01020453=id/search_src_text\n01020454=id/search_view\n01020455=id/search_voice_btn\n01020456=id/searchbox\n01020457=id/secondary\n01020458=id/seekbar\n01020459=id/select_all\n0102045a=id/select_dialog_listview\n0102045b=id/selection_end_handle\n0102045c=id/selection_start_handle\n0102045d=id/sensor\n0102045e=id/sensorLandscape\n0102045f=id/sensorPortrait\n01020460=id/sentences\n01020461=id/separator\n01020462=id/sequential\n01020463=id/sequentially\n01020464=id/serial_number\n01020465=id/serial_number_header\n01020466=id/serif\n01020467=id/setup\n01020468=id/seven\n01020469=id/sha1_fingerprint\n0102046a=id/sha1_fingerprint_header\n0102046b=id/sha256_fingerprint\n0102046c=id/sha256_fingerprint_header\n0102046d=id/share\n0102046e=id/shortEdges\n0102046f=id/shortcut\n01020470=id/showCustom\n01020471=id/showHome\n01020472=id/showSearchIconAsBadge\n01020473=id/showSearchLabelAsBadge\n01020474=id/showTitle\n01020475=id/showVoiceSearchButton\n01020476=id/signature\n01020477=id/signatureOrSystem\n01020478=id/signed\n01020479=id/silent\n0102047a=id/simple\n0102047b=id/single\n0102047c=id/singleChoice\n0102047d=id/singleInstance\n0102047e=id/singleTask\n0102047f=id/singleTop\n01020480=id/six\n01020481=id/skip_button\n01020482=id/small\n01020483=id/smallIcon\n01020484=id/smallestScreenSize\n01020485=id/smart_reply_container\n01020486=id/sms_short_code_coins_icon\n01020487=id/sms_short_code_confirm_message\n01020488=id/sms_short_code_detail_layout\n01020489=id/sms_short_code_detail_message\n0102048a=id/sms_short_code_remember_choice_checkbox\n0102048b=id/sms_short_code_remember_choice_text\n0102048c=id/sms_short_code_remember_undo_instruction\n0102048d=id/social\n0102048e=id/softRestricted\n0102048f=id/software\n01020490=id/spacer\n01020491=id/spacingWidth\n01020492=id/spacingWidthUniform\n01020493=id/spannable\n01020494=id/spinner\n01020495=id/splashscreen\n01020496=id/splitActionBarWhenNarrow\n01020497=id/split_action_bar\n01020498=id/square\n01020499=id/src_atop\n0102049a=id/src_in\n0102049b=id/src_over\n0102049c=id/stack\n0102049d=id/standard\n0102049e=id/start\n0102049f=id/stateAlwaysHidden\n010204a0=id/stateAlwaysVisible\n010204a1=id/stateHidden\n010204a2=id/stateUnchanged\n010204a3=id/stateUnspecified\n010204a4=id/stateVisible\n010204a5=id/status\n010204a6=id/status_bar_latest_event_content\n010204a7=id/string\n010204a8=id/stub\n010204a9=id/stylus\n010204aa=id/sub_color\n010204ab=id/sub_name\n010204ac=id/sub_number\n010204ad=id/sub_short_number\n010204ae=id/submenuarrow\n010204af=id/submit_area\n010204b0=id/suggestionContainer\n010204b1=id/suggestionWindowContainer\n010204b2=id/surfaceView\n010204b3=id/sweep\n010204b4=id/switch_new\n010204b5=id/switch_old\n010204b6=id/system\n010204b7=id/tabMode\n010204b8=id/tabs_container\n010204b9=id/tag_alpha_animator\n010204ba=id/tag_is_first_layout\n010204bb=id/tag_layout_top\n010204bc=id/tag_top_animator\n010204bd=id/tag_top_override\n010204be=id/text\n010204bf=id/textAutoComplete\n010204c0=id/textAutoCorrect\n010204c1=id/textCapCharacters\n010204c2=id/textCapSentences\n010204c3=id/textCapWords\n010204c4=id/textClassifier\n010204c5=id/textEmailAddress\n010204c6=id/textEmailSubject\n010204c7=id/textEnd\n010204c8=id/textFilter\n010204c9=id/textImeMultiLine\n010204ca=id/textLongMessage\n010204cb=id/textMultiLine\n010204cc=id/textNoSuggestions\n010204cd=id/textPassword\n010204ce=id/textPersonName\n010204cf=id/textPhonetic\n010204d0=id/textPostalAddress\n010204d1=id/textShortMessage\n010204d2=id/textSpacerNoButtons\n010204d3=id/textSpacerNoTitle\n010204d4=id/textStart\n010204d5=id/textUri\n010204d6=id/textVisiblePassword\n010204d7=id/textWebEditText\n010204d8=id/textWebEmailAddress\n010204d9=id/textWebPassword\n010204da=id/text_line_1\n010204db=id/textureView\n010204dc=id/three\n010204dd=id/time\n010204de=id/timePicker\n010204df=id/timePickerLayout\n010204e0=id/time_current\n010204e1=id/time_divider\n010204e2=id/time_header\n010204e3=id/time_layout\n010204e4=id/titleDivider\n010204e5=id/titleDividerNoCustom\n010204e6=id/titleDividerTop\n010204e7=id/title_container\n010204e8=id/title_separator\n010204e9=id/title_template\n010204ea=id/to_common\n010204eb=id/to_common_header\n010204ec=id/to_org\n010204ed=id/to_org_header\n010204ee=id/to_org_unit\n010204ef=id/to_org_unit_header\n010204f0=id/together\n010204f1=id/toggle_mode\n010204f2=id/top\n010204f3=id/topPanel\n010204f4=id/top_label\n010204f5=id/top_left_diagonal_double_arrow\n010204f6=id/top_right_diagonal_double_arrow\n010204f7=id/top_to_bottom\n010204f8=id/touchscreen\n010204f9=id/trackball\n010204fa=id/transitionPosition\n010204fb=id/transitionTransform\n010204fc=id/transition_overlay_view_tag\n010204fd=id/translucent\n010204fe=id/transparent\n010204ff=id/twelvekey\n01020500=id/two\n01020501=id/twoLine\n01020502=id/typeAllMask\n01020503=id/typeAnnouncement\n01020504=id/typeAssistReadingContext\n01020505=id/typeContextClicked\n01020506=id/typeGestureDetectionEnd\n01020507=id/typeGestureDetectionStart\n01020508=id/typeNotificationStateChanged\n01020509=id/typeTouchExplorationGestureEnd\n0102050a=id/typeTouchExplorationGestureStart\n0102050b=id/typeTouchInteractionEnd\n0102050c=id/typeTouchInteractionStart\n0102050d=id/typeViewAccessibilityFocusCleared\n0102050e=id/typeViewAccessibilityFocused\n0102050f=id/typeViewClicked\n01020510=id/typeViewFocused\n01020511=id/typeViewHoverEnter\n01020512=id/typeViewHoverExit\n01020513=id/typeViewLongClicked\n01020514=id/typeViewScrolled\n01020515=id/typeViewSelected\n01020516=id/typeViewTextChanged\n01020517=id/typeViewTextSelectionChanged\n01020518=id/typeViewTextTraversedAtMovementGranularity\n01020519=id/typeWindowContentChanged\n0102051a=id/typeWindowStateChanged\n0102051b=id/typeWindowsChanged\n0102051c=id/uiMode\n0102051d=id/unchecked\n0102051e=id/undefined\n0102051f=id/uniform\n01020520=id/unpressed\n01020521=id/unspecified\n01020522=id/up\n01020523=id/useLogo\n01020524=id/user\n01020525=id/userIdentification\n01020526=id/userLandscape\n01020527=id/userPortrait\n01020528=id/userSwitcher\n01020529=id/validity_header\n0102052a=id/value\n0102052b=id/vendorPrivileged\n0102052c=id/verifier\n0102052d=id/vertical\n0102052e=id/vertical_double_arrow\n0102052f=id/vertical_text\n01020530=id/video\n01020531=id/viewEnd\n01020532=id/viewStart\n01020533=id/visible\n01020534=id/voice\n01020535=id/voiceTrigger\n01020536=id/wait\n01020537=id/web\n01020538=id/websearch\n01020539=id/webview\n0102053a=id/wellbeing\n0102053b=id/wheel\n0102053c=id/wideColorGamut\n0102053d=id/widget\n0102053e=id/widgets\n0102053f=id/withText\n01020540=id/words\n01020541=id/work_widget_app_icon\n01020542=id/work_widget_badge_icon\n01020543=id/work_widget_mask_frame\n01020544=id/wrap_content\n01020545=id/xhdpi\n01020546=id/xlarge\n01020547=id/xxhdpi\n01020548=id/xxxhdpi\n01020549=id/year\n0102054a=id/yes\n0102054b=id/yesExcludeDescendants\n0102054c=id/zero\n0102054d=id/zoomControls\n0102054e=id/zoomIn\n0102054f=id/zoomMagnify\n01020550=id/zoomOut\n01020551=id/zoom_fit_page\n01020552=id/zoom_in\n01020553=id/zoom_out\n01020554=id/zoom_page_overview\n01030000=style/Animation\n01030001=style/Animation.Activity\n01030002=style/Animation.Dialog\n01030003=style/Animation.Translucent\n01030004=style/Animation.Toast\n01030005=style/Theme\n01030006=style/Theme.NoTitleBar\n01030007=style/Theme.NoTitleBar.Fullscreen\n01030008=style/Theme.Black\n01030009=style/Theme.Black.NoTitleBar\n0103000a=style/Theme.Black.NoTitleBar.Fullscreen\n0103000b=style/Theme.Dialog\n0103000c=style/Theme.Light\n0103000d=style/Theme.Light.NoTitleBar\n0103000e=style/Theme.Light.NoTitleBar.Fullscreen\n0103000f=style/Theme.Translucent\n01030010=style/Theme.Translucent.NoTitleBar\n01030011=style/Theme.Translucent.NoTitleBar.Fullscreen\n01030012=style/Widget\n01030013=style/Widget.AbsListView\n01030014=style/Widget.Button\n01030015=style/Widget.Button.Inset\n01030016=style/Widget.Button.Small\n01030017=style/Widget.Button.Toggle\n01030018=style/Widget.CompoundButton\n01030019=style/Widget.CompoundButton.CheckBox\n0103001a=style/Widget.CompoundButton.RadioButton\n0103001b=style/Widget.CompoundButton.Star\n0103001c=style/Widget.ProgressBar\n0103001d=style/Widget.ProgressBar.Large\n0103001e=style/Widget.ProgressBar.Small\n0103001f=style/Widget.ProgressBar.Horizontal\n01030020=style/Widget.SeekBar\n01030021=style/Widget.RatingBar\n01030022=style/Widget.TextView\n01030023=style/Widget.EditText\n01030024=style/Widget.ExpandableListView\n01030025=style/Widget.ImageWell\n01030026=style/Widget.ImageButton\n01030027=style/Widget.AutoCompleteTextView\n01030028=style/Widget.Spinner\n01030029=style/Widget.TextView.PopupMenu\n0103002a=style/Widget.TextView.SpinnerItem\n0103002b=style/Widget.DropDownItem\n0103002c=style/Widget.DropDownItem.Spinner\n0103002d=style/Widget.ScrollView\n0103002e=style/Widget.ListView\n0103002f=style/Widget.ListView.White\n01030030=style/Widget.ListView.DropDown\n01030031=style/Widget.ListView.Menu\n01030032=style/Widget.GridView\n01030033=style/Widget.WebView\n01030034=style/Widget.TabWidget\n01030035=style/Widget.Gallery\n01030036=style/Widget.PopupWindow\n01030037=style/MediaButton\n01030038=style/MediaButton.Previous\n01030039=style/MediaButton.Next\n0103003a=style/MediaButton.Play\n0103003b=style/MediaButton.Ffwd\n0103003c=style/MediaButton.Rew\n0103003d=style/MediaButton.Pause\n0103003e=style/TextAppearance\n0103003f=style/TextAppearance.Inverse\n01030040=style/TextAppearance.Theme\n01030041=style/TextAppearance.DialogWindowTitle\n01030042=style/TextAppearance.Large\n01030043=style/TextAppearance.Large.Inverse\n01030044=style/TextAppearance.Medium\n01030045=style/TextAppearance.Medium.Inverse\n01030046=style/TextAppearance.Small\n01030047=style/TextAppearance.Small.Inverse\n01030048=style/TextAppearance.Theme.Dialog\n01030049=style/TextAppearance.Widget\n0103004a=style/TextAppearance.Widget.Button\n0103004b=style/TextAppearance.Widget.IconMenu.Item\n0103004c=style/TextAppearance.Widget.EditText\n0103004d=style/TextAppearance.Widget.TabWidget\n0103004e=style/TextAppearance.Widget.TextView\n0103004f=style/TextAppearance.Widget.TextView.PopupMenu\n01030050=style/TextAppearance.Widget.DropDownHint\n01030051=style/TextAppearance.Widget.DropDownItem\n01030052=style/TextAppearance.Widget.TextView.SpinnerItem\n01030053=style/TextAppearance.WindowTitle\n01030054=style/Theme.InputMethod\n01030055=style/Theme.NoDisplay\n01030056=style/Animation.InputMethod\n01030057=style/Widget.KeyboardView\n01030058=style/ButtonBar\n01030059=style/Theme.Panel\n0103005a=style/Theme.Light.Panel\n0103005b=style/Widget.ProgressBar.Inverse\n0103005c=style/Widget.ProgressBar.Large.Inverse\n0103005d=style/Widget.ProgressBar.Small.Inverse\n0103005e=style/Theme.Wallpaper\n0103005f=style/Theme.Wallpaper.NoTitleBar\n01030060=style/Theme.Wallpaper.NoTitleBar.Fullscreen\n01030061=style/Theme.WallpaperSettings\n01030062=style/Theme.Light.WallpaperSettings\n01030063=style/TextAppearance.SearchResult.Title\n01030064=style/TextAppearance.SearchResult.Subtitle\n01030065=style/TextAppearance.StatusBar.Title\n01030066=style/TextAppearance.StatusBar.Icon\n01030067=style/TextAppearance.StatusBar.EventContent\n01030068=style/TextAppearance.StatusBar.EventContent.Title\n01030069=style/Theme.WithActionBar\n0103006a=style/Theme.NoTitleBar.OverlayActionModes\n0103006b=style/Theme.Holo\n0103006c=style/Theme.Holo.NoActionBar\n0103006d=style/Theme.Holo.NoActionBar.Fullscreen\n0103006e=style/Theme.Holo.Light\n0103006f=style/Theme.Holo.Dialog\n01030070=style/Theme.Holo.Dialog.MinWidth\n01030071=style/Theme.Holo.Dialog.NoActionBar\n01030072=style/Theme.Holo.Dialog.NoActionBar.MinWidth\n01030073=style/Theme.Holo.Light.Dialog\n01030074=style/Theme.Holo.Light.Dialog.MinWidth\n01030075=style/Theme.Holo.Light.Dialog.NoActionBar\n01030076=style/Theme.Holo.Light.Dialog.NoActionBar.MinWidth\n01030077=style/Theme.Holo.DialogWhenLarge\n01030078=style/Theme.Holo.DialogWhenLarge.NoActionBar\n01030079=style/Theme.Holo.Light.DialogWhenLarge\n0103007a=style/Theme.Holo.Light.DialogWhenLarge.NoActionBar\n0103007b=style/Theme.Holo.Panel\n0103007c=style/Theme.Holo.Light.Panel\n0103007d=style/Theme.Holo.Wallpaper\n0103007e=style/Theme.Holo.Wallpaper.NoTitleBar\n0103007f=style/Theme.Holo.InputMethod\n01030080=style/TextAppearance.Widget.PopupMenu.Large\n01030081=style/TextAppearance.Widget.PopupMenu.Small\n01030082=style/Widget.ActionBar\n01030083=style/Widget.Spinner.DropDown\n01030084=style/Widget.ActionButton\n01030085=style/Widget.ListPopupWindow\n01030086=style/Widget.PopupMenu\n01030087=style/Widget.ActionButton.Overflow\n01030088=style/Widget.ActionButton.CloseMode\n01030089=style/Widget.FragmentBreadCrumbs\n0103008a=style/Widget.Holo\n0103008b=style/Widget.Holo.Button\n0103008c=style/Widget.Holo.Button.Small\n0103008d=style/Widget.Holo.Button.Inset\n0103008e=style/Widget.Holo.Button.Toggle\n0103008f=style/Widget.Holo.TextView\n01030090=style/Widget.Holo.AutoCompleteTextView\n01030091=style/Widget.Holo.CompoundButton.CheckBox\n01030092=style/Widget.Holo.ListView.DropDown\n01030093=style/Widget.Holo.EditText\n01030094=style/Widget.Holo.ExpandableListView\n01030095=style/Widget.Holo.GridView\n01030096=style/Widget.Holo.ImageButton\n01030097=style/Widget.Holo.ListView\n01030098=style/Widget.Holo.PopupWindow\n01030099=style/Widget.Holo.ProgressBar\n0103009a=style/Widget.Holo.ProgressBar.Horizontal\n0103009b=style/Widget.Holo.ProgressBar.Small\n0103009c=style/Widget.Holo.ProgressBar.Small.Title\n0103009d=style/Widget.Holo.ProgressBar.Large\n0103009e=style/Widget.Holo.SeekBar\n0103009f=style/Widget.Holo.RatingBar\n010300a0=style/Widget.Holo.RatingBar.Indicator\n010300a1=style/Widget.Holo.RatingBar.Small\n010300a2=style/Widget.Holo.CompoundButton.RadioButton\n010300a3=style/Widget.Holo.ScrollView\n010300a4=style/Widget.Holo.HorizontalScrollView\n010300a5=style/Widget.Holo.Spinner\n010300a6=style/Widget.Holo.CompoundButton.Star\n010300a7=style/Widget.Holo.TabWidget\n010300a8=style/Widget.Holo.WebTextView\n010300a9=style/Widget.Holo.WebView\n010300aa=style/Widget.Holo.DropDownItem\n010300ab=style/Widget.Holo.DropDownItem.Spinner\n010300ac=style/Widget.Holo.TextView.SpinnerItem\n010300ad=style/Widget.Holo.ListPopupWindow\n010300ae=style/Widget.Holo.PopupMenu\n010300af=style/Widget.Holo.ActionButton\n010300b0=style/Widget.Holo.ActionButton.Overflow\n010300b1=style/Widget.Holo.ActionButton.TextButton\n010300b2=style/Widget.Holo.ActionMode\n010300b3=style/Widget.Holo.ActionButton.CloseMode\n010300b4=style/Widget.Holo.ActionBar\n010300b5=style/Widget.Holo.Light\n010300b6=style/Widget.Holo.Light.Button\n010300b7=style/Widget.Holo.Light.Button.Small\n010300b8=style/Widget.Holo.Light.Button.Inset\n010300b9=style/Widget.Holo.Light.Button.Toggle\n010300ba=style/Widget.Holo.Light.TextView\n010300bb=style/Widget.Holo.Light.AutoCompleteTextView\n010300bc=style/Widget.Holo.Light.CompoundButton.CheckBox\n010300bd=style/Widget.Holo.Light.ListView.DropDown\n010300be=style/Widget.Holo.Light.EditText\n010300bf=style/Widget.Holo.Light.ExpandableListView\n010300c0=style/Widget.Holo.Light.GridView\n010300c1=style/Widget.Holo.Light.ImageButton\n010300c2=style/Widget.Holo.Light.ListView\n010300c3=style/Widget.Holo.Light.PopupWindow\n010300c4=style/Widget.Holo.Light.ProgressBar\n010300c5=style/Widget.Holo.Light.ProgressBar.Horizontal\n010300c6=style/Widget.Holo.Light.ProgressBar.Small\n010300c7=style/Widget.Holo.Light.ProgressBar.Small.Title\n010300c8=style/Widget.Holo.Light.ProgressBar.Large\n010300c9=style/Widget.Holo.Light.ProgressBar.Inverse\n010300ca=style/Widget.Holo.Light.ProgressBar.Small.Inverse\n010300cb=style/Widget.Holo.Light.ProgressBar.Large.Inverse\n010300cc=style/Widget.Holo.Light.SeekBar\n010300cd=style/Widget.Holo.Light.RatingBar\n010300ce=style/Widget.Holo.Light.RatingBar.Indicator\n010300cf=style/Widget.Holo.Light.RatingBar.Small\n010300d0=style/Widget.Holo.Light.CompoundButton.RadioButton\n010300d1=style/Widget.Holo.Light.ScrollView\n010300d2=style/Widget.Holo.Light.HorizontalScrollView\n010300d3=style/Widget.Holo.Light.Spinner\n010300d4=style/Widget.Holo.Light.CompoundButton.Star\n010300d5=style/Widget.Holo.Light.TabWidget\n010300d6=style/Widget.Holo.Light.WebTextView\n010300d7=style/Widget.Holo.Light.WebView\n010300d8=style/Widget.Holo.Light.DropDownItem\n010300d9=style/Widget.Holo.Light.DropDownItem.Spinner\n010300da=style/Widget.Holo.Light.TextView.SpinnerItem\n010300db=style/Widget.Holo.Light.ListPopupWindow\n010300dc=style/Widget.Holo.Light.PopupMenu\n010300dd=style/Widget.Holo.Light.ActionButton\n010300de=style/Widget.Holo.Light.ActionButton.Overflow\n010300df=style/Widget.Holo.Light.ActionMode\n010300e0=style/Widget.Holo.Light.ActionButton.CloseMode\n010300e1=style/Widget.Holo.Light.ActionBar\n010300e2=style/Widget.Holo.Button.Borderless\n010300e3=style/Widget.Holo.Tab\n010300e4=style/Widget.Holo.Light.Tab\n010300e5=style/Holo.ButtonBar\n010300e6=style/Holo.Light.ButtonBar\n010300e7=style/Holo.ButtonBar.AlertDialog\n010300e8=style/Holo.Light.ButtonBar.AlertDialog\n010300e9=style/Holo.SegmentedButton\n010300ea=style/Holo.Light.SegmentedButton\n010300eb=style/Widget.CalendarView\n010300ec=style/Widget.Holo.CalendarView\n010300ed=style/Widget.Holo.Light.CalendarView\n010300ee=style/Widget.DatePicker\n010300ef=style/Widget.Holo.DatePicker\n010300f0=style/Theme.Holo.Light.NoActionBar\n010300f1=style/Theme.Holo.Light.NoActionBar.Fullscreen\n010300f2=style/Widget.ActionBar.TabView\n010300f3=style/Widget.ActionBar.TabText\n010300f4=style/Widget.ActionBar.TabBar\n010300f5=style/Widget.Holo.ActionBar.TabView\n010300f6=style/Widget.Holo.ActionBar.TabText\n010300f7=style/Widget.Holo.ActionBar.TabBar\n010300f8=style/Widget.Holo.Light.ActionBar.TabView\n010300f9=style/Widget.Holo.Light.ActionBar.TabText\n010300fa=style/Widget.Holo.Light.ActionBar.TabBar\n010300fb=style/TextAppearance.Holo\n010300fc=style/TextAppearance.Holo.Inverse\n010300fd=style/TextAppearance.Holo.Large\n010300fe=style/TextAppearance.Holo.Large.Inverse\n010300ff=style/TextAppearance.Holo.Medium\n01030100=style/TextAppearance.Holo.Medium.Inverse\n01030101=style/TextAppearance.Holo.Small\n01030102=style/TextAppearance.Holo.Small.Inverse\n01030103=style/TextAppearance.Holo.SearchResult.Title\n01030104=style/TextAppearance.Holo.SearchResult.Subtitle\n01030105=style/TextAppearance.Holo.Widget\n01030106=style/TextAppearance.Holo.Widget.Button\n01030107=style/TextAppearance.Holo.Widget.IconMenu.Item\n01030108=style/TextAppearance.Holo.Widget.TabWidget\n01030109=style/TextAppearance.Holo.Widget.TextView\n0103010a=style/TextAppearance.Holo.Widget.TextView.PopupMenu\n0103010b=style/TextAppearance.Holo.Widget.DropDownHint\n0103010c=style/TextAppearance.Holo.Widget.DropDownItem\n0103010d=style/TextAppearance.Holo.Widget.TextView.SpinnerItem\n0103010e=style/TextAppearance.Holo.Widget.EditText\n0103010f=style/TextAppearance.Holo.Widget.PopupMenu\n01030110=style/TextAppearance.Holo.Widget.PopupMenu.Large\n01030111=style/TextAppearance.Holo.Widget.PopupMenu.Small\n01030112=style/TextAppearance.Holo.Widget.ActionBar.Title\n01030113=style/TextAppearance.Holo.Widget.ActionBar.Subtitle\n01030114=style/TextAppearance.Holo.Widget.ActionMode.Title\n01030115=style/TextAppearance.Holo.Widget.ActionMode.Subtitle\n01030116=style/TextAppearance.Holo.WindowTitle\n01030117=style/TextAppearance.Holo.DialogWindowTitle\n01030118=style/TextAppearance.SuggestionHighlight\n01030119=style/Theme.Holo.Light.DarkActionBar\n0103011a=style/Widget.Holo.Button.Borderless.Small\n0103011b=style/Widget.Holo.Light.Button.Borderless.Small\n0103011c=style/TextAppearance.Holo.Widget.ActionBar.Title.Inverse\n0103011d=style/TextAppearance.Holo.Widget.ActionBar.Subtitle.Inverse\n0103011e=style/TextAppearance.Holo.Widget.ActionMode.Title.Inverse\n0103011f=style/TextAppearance.Holo.Widget.ActionMode.Subtitle.Inverse\n01030120=style/TextAppearance.Holo.Widget.ActionBar.Menu\n01030121=style/Widget.Holo.ActionBar.Solid\n01030122=style/Widget.Holo.Light.ActionBar.Solid\n01030123=style/Widget.Holo.Light.ActionBar.Solid.Inverse\n01030124=style/Widget.Holo.Light.ActionBar.TabBar.Inverse\n01030125=style/Widget.Holo.Light.ActionBar.TabView.Inverse\n01030126=style/Widget.Holo.Light.ActionBar.TabText.Inverse\n01030127=style/Widget.Holo.Light.ActionMode.Inverse\n01030128=style/Theme.DeviceDefault\n01030129=style/Theme.DeviceDefault.NoActionBar\n0103012a=style/Theme.DeviceDefault.NoActionBar.Fullscreen\n0103012b=style/Theme.DeviceDefault.Light\n0103012c=style/Theme.DeviceDefault.Light.NoActionBar\n0103012d=style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen\n0103012e=style/Theme.DeviceDefault.Dialog\n0103012f=style/Theme.DeviceDefault.Dialog.MinWidth\n01030130=style/Theme.DeviceDefault.Dialog.NoActionBar\n01030131=style/Theme.DeviceDefault.Dialog.NoActionBar.MinWidth\n01030132=style/Theme.DeviceDefault.Light.Dialog\n01030133=style/Theme.DeviceDefault.Light.Dialog.MinWidth\n01030134=style/Theme.DeviceDefault.Light.Dialog.NoActionBar\n01030135=style/Theme.DeviceDefault.Light.Dialog.NoActionBar.MinWidth\n01030136=style/Theme.DeviceDefault.DialogWhenLarge\n01030137=style/Theme.DeviceDefault.DialogWhenLarge.NoActionBar\n01030138=style/Theme.DeviceDefault.Light.DialogWhenLarge\n01030139=style/Theme.DeviceDefault.Light.DialogWhenLarge.NoActionBar\n0103013a=style/Theme.DeviceDefault.Panel\n0103013b=style/Theme.DeviceDefault.Light.Panel\n0103013c=style/Theme.DeviceDefault.Wallpaper\n0103013d=style/Theme.DeviceDefault.Wallpaper.NoTitleBar\n0103013e=style/Theme.DeviceDefault.InputMethod\n0103013f=style/Theme.DeviceDefault.Light.DarkActionBar\n01030140=style/Widget.DeviceDefault\n01030141=style/Widget.DeviceDefault.Button\n01030142=style/Widget.DeviceDefault.Button.Small\n01030143=style/Widget.DeviceDefault.Button.Inset\n01030144=style/Widget.DeviceDefault.Button.Toggle\n01030145=style/Widget.DeviceDefault.Button.Borderless.Small\n01030146=style/Widget.DeviceDefault.TextView\n01030147=style/Widget.DeviceDefault.AutoCompleteTextView\n01030148=style/Widget.DeviceDefault.CompoundButton.CheckBox\n01030149=style/Widget.DeviceDefault.ListView.DropDown\n0103014a=style/Widget.DeviceDefault.EditText\n0103014b=style/Widget.DeviceDefault.ExpandableListView\n0103014c=style/Widget.DeviceDefault.GridView\n0103014d=style/Widget.DeviceDefault.ImageButton\n0103014e=style/Widget.DeviceDefault.ListView\n0103014f=style/Widget.DeviceDefault.PopupWindow\n01030150=style/Widget.DeviceDefault.ProgressBar\n01030151=style/Widget.DeviceDefault.ProgressBar.Horizontal\n01030152=style/Widget.DeviceDefault.ProgressBar.Small\n01030153=style/Widget.DeviceDefault.ProgressBar.Small.Title\n01030154=style/Widget.DeviceDefault.ProgressBar.Large\n01030155=style/Widget.DeviceDefault.SeekBar\n01030156=style/Widget.DeviceDefault.RatingBar\n01030157=style/Widget.DeviceDefault.RatingBar.Indicator\n01030158=style/Widget.DeviceDefault.RatingBar.Small\n01030159=style/Widget.DeviceDefault.CompoundButton.RadioButton\n0103015a=style/Widget.DeviceDefault.ScrollView\n0103015b=style/Widget.DeviceDefault.HorizontalScrollView\n0103015c=style/Widget.DeviceDefault.Spinner\n0103015d=style/Widget.DeviceDefault.CompoundButton.Star\n0103015e=style/Widget.DeviceDefault.TabWidget\n0103015f=style/Widget.DeviceDefault.WebTextView\n01030160=style/Widget.DeviceDefault.WebView\n01030161=style/Widget.DeviceDefault.DropDownItem\n01030162=style/Widget.DeviceDefault.DropDownItem.Spinner\n01030163=style/Widget.DeviceDefault.TextView.SpinnerItem\n01030164=style/Widget.DeviceDefault.ListPopupWindow\n01030165=style/Widget.DeviceDefault.PopupMenu\n01030166=style/Widget.DeviceDefault.ActionButton\n01030167=style/Widget.DeviceDefault.ActionButton.Overflow\n01030168=style/Widget.DeviceDefault.ActionButton.TextButton\n01030169=style/Widget.DeviceDefault.ActionMode\n0103016a=style/Widget.DeviceDefault.ActionButton.CloseMode\n0103016b=style/Widget.DeviceDefault.ActionBar\n0103016c=style/Widget.DeviceDefault.Button.Borderless\n0103016d=style/Widget.DeviceDefault.Tab\n0103016e=style/Widget.DeviceDefault.CalendarView\n0103016f=style/Widget.DeviceDefault.DatePicker\n01030170=style/Widget.DeviceDefault.ActionBar.TabView\n01030171=style/Widget.DeviceDefault.ActionBar.TabText\n01030172=style/Widget.DeviceDefault.ActionBar.TabBar\n01030173=style/Widget.DeviceDefault.ActionBar.Solid\n01030174=style/Widget.DeviceDefault.Light\n01030175=style/Widget.DeviceDefault.Light.Button\n01030176=style/Widget.DeviceDefault.Light.Button.Small\n01030177=style/Widget.DeviceDefault.Light.Button.Inset\n01030178=style/Widget.DeviceDefault.Light.Button.Toggle\n01030179=style/Widget.DeviceDefault.Light.Button.Borderless.Small\n0103017a=style/Widget.DeviceDefault.Light.TextView\n0103017b=style/Widget.DeviceDefault.Light.AutoCompleteTextView\n0103017c=style/Widget.DeviceDefault.Light.CompoundButton.CheckBox\n0103017d=style/Widget.DeviceDefault.Light.ListView.DropDown\n0103017e=style/Widget.DeviceDefault.Light.EditText\n0103017f=style/Widget.DeviceDefault.Light.ExpandableListView\n01030180=style/Widget.DeviceDefault.Light.GridView\n01030181=style/Widget.DeviceDefault.Light.ImageButton\n01030182=style/Widget.DeviceDefault.Light.ListView\n01030183=style/Widget.DeviceDefault.Light.PopupWindow\n01030184=style/Widget.DeviceDefault.Light.ProgressBar\n01030185=style/Widget.DeviceDefault.Light.ProgressBar.Horizontal\n01030186=style/Widget.DeviceDefault.Light.ProgressBar.Small\n01030187=style/Widget.DeviceDefault.Light.ProgressBar.Small.Title\n01030188=style/Widget.DeviceDefault.Light.ProgressBar.Large\n01030189=style/Widget.DeviceDefault.Light.ProgressBar.Inverse\n0103018a=style/Widget.DeviceDefault.Light.ProgressBar.Small.Inverse\n0103018b=style/Widget.DeviceDefault.Light.ProgressBar.Large.Inverse\n0103018c=style/Widget.DeviceDefault.Light.SeekBar\n0103018d=style/Widget.DeviceDefault.Light.RatingBar\n0103018e=style/Widget.DeviceDefault.Light.RatingBar.Indicator\n0103018f=style/Widget.DeviceDefault.Light.RatingBar.Small\n01030190=style/Widget.DeviceDefault.Light.CompoundButton.RadioButton\n01030191=style/Widget.DeviceDefault.Light.ScrollView\n01030192=style/Widget.DeviceDefault.Light.HorizontalScrollView\n01030193=style/Widget.DeviceDefault.Light.Spinner\n01030194=style/Widget.DeviceDefault.Light.CompoundButton.Star\n01030195=style/Widget.DeviceDefault.Light.TabWidget\n01030196=style/Widget.DeviceDefault.Light.WebTextView\n01030197=style/Widget.DeviceDefault.Light.WebView\n01030198=style/Widget.DeviceDefault.Light.DropDownItem\n01030199=style/Widget.DeviceDefault.Light.DropDownItem.Spinner\n0103019a=style/Widget.DeviceDefault.Light.TextView.SpinnerItem\n0103019b=style/Widget.DeviceDefault.Light.ListPopupWindow\n0103019c=style/Widget.DeviceDefault.Light.PopupMenu\n0103019d=style/Widget.DeviceDefault.Light.Tab\n0103019e=style/Widget.DeviceDefault.Light.CalendarView\n0103019f=style/Widget.DeviceDefault.Light.ActionButton\n010301a0=style/Widget.DeviceDefault.Light.ActionButton.Overflow\n010301a1=style/Widget.DeviceDefault.Light.ActionMode\n010301a2=style/Widget.DeviceDefault.Light.ActionButton.CloseMode\n010301a3=style/Widget.DeviceDefault.Light.ActionBar\n010301a4=style/Widget.DeviceDefault.Light.ActionBar.TabView\n010301a5=style/Widget.DeviceDefault.Light.ActionBar.TabText\n010301a6=style/Widget.DeviceDefault.Light.ActionBar.TabBar\n010301a7=style/Widget.DeviceDefault.Light.ActionBar.Solid\n010301a8=style/Widget.DeviceDefault.Light.ActionBar.Solid.Inverse\n010301a9=style/Widget.DeviceDefault.Light.ActionBar.TabBar.Inverse\n010301aa=style/Widget.DeviceDefault.Light.ActionBar.TabView.Inverse\n010301ab=style/Widget.DeviceDefault.Light.ActionBar.TabText.Inverse\n010301ac=style/Widget.DeviceDefault.Light.ActionMode.Inverse\n010301ad=style/TextAppearance.DeviceDefault\n010301ae=style/TextAppearance.DeviceDefault.Inverse\n010301af=style/TextAppearance.DeviceDefault.Large\n010301b0=style/TextAppearance.DeviceDefault.Large.Inverse\n010301b1=style/TextAppearance.DeviceDefault.Medium\n010301b2=style/TextAppearance.DeviceDefault.Medium.Inverse\n010301b3=style/TextAppearance.DeviceDefault.Small\n010301b4=style/TextAppearance.DeviceDefault.Small.Inverse\n010301b5=style/TextAppearance.DeviceDefault.SearchResult.Title\n010301b6=style/TextAppearance.DeviceDefault.SearchResult.Subtitle\n010301b7=style/TextAppearance.DeviceDefault.WindowTitle\n010301b8=style/TextAppearance.DeviceDefault.DialogWindowTitle\n010301b9=style/TextAppearance.DeviceDefault.Widget\n010301ba=style/TextAppearance.DeviceDefault.Widget.Button\n010301bb=style/TextAppearance.DeviceDefault.Widget.IconMenu.Item\n010301bc=style/TextAppearance.DeviceDefault.Widget.TabWidget\n010301bd=style/TextAppearance.DeviceDefault.Widget.TextView\n010301be=style/TextAppearance.DeviceDefault.Widget.TextView.PopupMenu\n010301bf=style/TextAppearance.DeviceDefault.Widget.DropDownHint\n010301c0=style/TextAppearance.DeviceDefault.Widget.DropDownItem\n010301c1=style/TextAppearance.DeviceDefault.Widget.TextView.SpinnerItem\n010301c2=style/TextAppearance.DeviceDefault.Widget.EditText\n010301c3=style/TextAppearance.DeviceDefault.Widget.PopupMenu\n010301c4=style/TextAppearance.DeviceDefault.Widget.PopupMenu.Large\n010301c5=style/TextAppearance.DeviceDefault.Widget.PopupMenu.Small\n010301c6=style/TextAppearance.DeviceDefault.Widget.ActionBar.Title\n010301c7=style/TextAppearance.DeviceDefault.Widget.ActionBar.Subtitle\n010301c8=style/TextAppearance.DeviceDefault.Widget.ActionMode.Title\n010301c9=style/TextAppearance.DeviceDefault.Widget.ActionMode.Subtitle\n010301ca=style/TextAppearance.DeviceDefault.Widget.ActionBar.Title.Inverse\n010301cb=style/TextAppearance.DeviceDefault.Widget.ActionBar.Subtitle.Inverse\n010301cc=style/TextAppearance.DeviceDefault.Widget.ActionMode.Title.Inverse\n010301cd=style/TextAppearance.DeviceDefault.Widget.ActionMode.Subtitle.Inverse\n010301ce=style/TextAppearance.DeviceDefault.Widget.ActionBar.Menu\n010301cf=style/DeviceDefault.ButtonBar\n010301d0=style/DeviceDefault.ButtonBar.AlertDialog\n010301d1=style/DeviceDefault.SegmentedButton\n010301d2=style/DeviceDefault.Light.ButtonBar\n010301d3=style/DeviceDefault.Light.ButtonBar.AlertDialog\n010301d4=style/DeviceDefault.Light.SegmentedButton\n010301d5=style/Widget.Holo.MediaRouteButton\n010301d6=style/Widget.Holo.Light.MediaRouteButton\n010301d7=style/Widget.DeviceDefault.MediaRouteButton\n010301d8=style/Widget.DeviceDefault.Light.MediaRouteButton\n010301d9=style/Widget.Holo.CheckedTextView\n010301da=style/Widget.Holo.Light.CheckedTextView\n010301db=style/Widget.DeviceDefault.CheckedTextView\n010301dc=style/Widget.DeviceDefault.Light.CheckedTextView\n010301dd=style/Theme.Holo.NoActionBar.Overscan\n010301de=style/Theme.Holo.Light.NoActionBar.Overscan\n010301df=style/Theme.DeviceDefault.NoActionBar.Overscan\n010301e0=style/Theme.DeviceDefault.Light.NoActionBar.Overscan\n010301e1=style/Theme.Holo.NoActionBar.TranslucentDecor\n010301e2=style/Theme.Holo.Light.NoActionBar.TranslucentDecor\n010301e3=style/Theme.DeviceDefault.NoActionBar.TranslucentDecor\n010301e4=style/Theme.DeviceDefault.Light.NoActionBar.TranslucentDecor\n010301e5=style/Widget.FastScroll\n010301e6=style/Widget.StackView\n010301e7=style/Widget.Toolbar\n010301e8=style/Widget.Toolbar.Button.Navigation\n010301e9=style/Widget.DeviceDefault.FastScroll\n010301ea=style/Widget.DeviceDefault.StackView\n010301eb=style/Widget.DeviceDefault.Light.FastScroll\n010301ec=style/Widget.DeviceDefault.Light.StackView\n010301ed=style/TextAppearance.Material\n010301ee=style/TextAppearance.Material.Button\n010301ef=style/TextAppearance.Material.Body2\n010301f0=style/TextAppearance.Material.Body1\n010301f1=style/TextAppearance.Material.Caption\n010301f2=style/TextAppearance.Material.DialogWindowTitle\n010301f3=style/TextAppearance.Material.Display4\n010301f4=style/TextAppearance.Material.Display3\n010301f5=style/TextAppearance.Material.Display2\n010301f6=style/TextAppearance.Material.Display1\n010301f7=style/TextAppearance.Material.Headline\n010301f8=style/TextAppearance.Material.Inverse\n010301f9=style/TextAppearance.Material.Large\n010301fa=style/TextAppearance.Material.Large.Inverse\n010301fb=style/TextAppearance.Material.Medium\n010301fc=style/TextAppearance.Material.Medium.Inverse\n010301fd=style/TextAppearance.Material.Menu\n010301fe=style/TextAppearance.Material.Notification\n010301ff=style/TextAppearance.Material.Notification.Emphasis\n01030200=style/TextAppearance.Material.Notification.Info\n01030201=style/TextAppearance.Material.Notification.Line2\n01030202=style/TextAppearance.Material.Notification.Time\n01030203=style/TextAppearance.Material.Notification.Title\n01030204=style/TextAppearance.Material.SearchResult.Subtitle\n01030205=style/TextAppearance.Material.SearchResult.Title\n01030206=style/TextAppearance.Material.Small\n01030207=style/TextAppearance.Material.Small.Inverse\n01030208=style/TextAppearance.Material.Subhead\n01030209=style/TextAppearance.Material.Title\n0103020a=style/TextAppearance.Material.WindowTitle\n0103020b=style/TextAppearance.Material.Widget\n0103020c=style/TextAppearance.Material.Widget.ActionBar.Menu\n0103020d=style/TextAppearance.Material.Widget.ActionBar.Subtitle\n0103020e=style/TextAppearance.Material.Widget.ActionBar.Subtitle.Inverse\n0103020f=style/TextAppearance.Material.Widget.ActionBar.Title\n01030210=style/TextAppearance.Material.Widget.ActionBar.Title.Inverse\n01030211=style/TextAppearance.Material.Widget.ActionMode.Subtitle\n01030212=style/TextAppearance.Material.Widget.ActionMode.Subtitle.Inverse\n01030213=style/TextAppearance.Material.Widget.ActionMode.Title\n01030214=style/TextAppearance.Material.Widget.ActionMode.Title.Inverse\n01030215=style/TextAppearance.Material.Widget.Button\n01030216=style/TextAppearance.Material.Widget.DropDownHint\n01030217=style/TextAppearance.Material.Widget.DropDownItem\n01030218=style/TextAppearance.Material.Widget.EditText\n01030219=style/TextAppearance.Material.Widget.IconMenu.Item\n0103021a=style/TextAppearance.Material.Widget.PopupMenu\n0103021b=style/TextAppearance.Material.Widget.PopupMenu.Large\n0103021c=style/TextAppearance.Material.Widget.PopupMenu.Small\n0103021d=style/TextAppearance.Material.Widget.TabWidget\n0103021e=style/TextAppearance.Material.Widget.TextView\n0103021f=style/TextAppearance.Material.Widget.TextView.PopupMenu\n01030220=style/TextAppearance.Material.Widget.TextView.SpinnerItem\n01030221=style/TextAppearance.Material.Widget.Toolbar.Subtitle\n01030222=style/TextAppearance.Material.Widget.Toolbar.Title\n01030223=style/Theme.DeviceDefault.Settings\n01030224=style/Theme.Material\n01030225=style/Theme.Material.Dialog\n01030226=style/Theme.Material.Dialog.Alert\n01030227=style/Theme.Material.Dialog.MinWidth\n01030228=style/Theme.Material.Dialog.NoActionBar\n01030229=style/Theme.Material.Dialog.NoActionBar.MinWidth\n0103022a=style/Theme.Material.Dialog.Presentation\n0103022b=style/Theme.Material.DialogWhenLarge\n0103022c=style/Theme.Material.DialogWhenLarge.NoActionBar\n0103022d=style/Theme.Material.InputMethod\n0103022e=style/Theme.Material.NoActionBar\n0103022f=style/Theme.Material.NoActionBar.Fullscreen\n01030230=style/Theme.Material.NoActionBar.Overscan\n01030231=style/Theme.Material.NoActionBar.TranslucentDecor\n01030232=style/Theme.Material.Panel\n01030233=style/Theme.Material.Settings\n01030234=style/Theme.Material.Voice\n01030235=style/Theme.Material.Wallpaper\n01030236=style/Theme.Material.Wallpaper.NoTitleBar\n01030237=style/Theme.Material.Light\n01030238=style/Theme.Material.Light.DarkActionBar\n01030239=style/Theme.Material.Light.Dialog\n0103023a=style/Theme.Material.Light.Dialog.Alert\n0103023b=style/Theme.Material.Light.Dialog.MinWidth\n0103023c=style/Theme.Material.Light.Dialog.NoActionBar\n0103023d=style/Theme.Material.Light.Dialog.NoActionBar.MinWidth\n0103023e=style/Theme.Material.Light.Dialog.Presentation\n0103023f=style/Theme.Material.Light.DialogWhenLarge\n01030240=style/Theme.Material.Light.DialogWhenLarge.NoActionBar\n01030241=style/Theme.Material.Light.NoActionBar\n01030242=style/Theme.Material.Light.NoActionBar.Fullscreen\n01030243=style/Theme.Material.Light.NoActionBar.Overscan\n01030244=style/Theme.Material.Light.NoActionBar.TranslucentDecor\n01030245=style/Theme.Material.Light.Panel\n01030246=style/Theme.Material.Light.Voice\n01030247=style/ThemeOverlay\n01030248=style/ThemeOverlay.Material\n01030249=style/ThemeOverlay.Material.ActionBar\n0103024a=style/ThemeOverlay.Material.Light\n0103024b=style/ThemeOverlay.Material.Dark\n0103024c=style/ThemeOverlay.Material.Dark.ActionBar\n0103024d=style/Widget.Material\n0103024e=style/Widget.Material.ActionBar\n0103024f=style/Widget.Material.ActionBar.Solid\n01030250=style/Widget.Material.ActionBar.TabBar\n01030251=style/Widget.Material.ActionBar.TabText\n01030252=style/Widget.Material.ActionBar.TabView\n01030253=style/Widget.Material.ActionButton\n01030254=style/Widget.Material.ActionButton.CloseMode\n01030255=style/Widget.Material.ActionButton.Overflow\n01030256=style/Widget.Material.ActionMode\n01030257=style/Widget.Material.AutoCompleteTextView\n01030258=style/Widget.Material.Button\n01030259=style/Widget.Material.Button.Borderless\n0103025a=style/Widget.Material.Button.Borderless.Colored\n0103025b=style/Widget.Material.Button.Borderless.Small\n0103025c=style/Widget.Material.Button.Inset\n0103025d=style/Widget.Material.Button.Small\n0103025e=style/Widget.Material.Button.Toggle\n0103025f=style/Widget.Material.ButtonBar\n01030260=style/Widget.Material.ButtonBar.AlertDialog\n01030261=style/Widget.Material.CalendarView\n01030262=style/Widget.Material.CheckedTextView\n01030263=style/Widget.Material.CompoundButton.CheckBox\n01030264=style/Widget.Material.CompoundButton.RadioButton\n01030265=style/Widget.Material.CompoundButton.Star\n01030266=style/Widget.Material.DatePicker\n01030267=style/Widget.Material.DropDownItem\n01030268=style/Widget.Material.DropDownItem.Spinner\n01030269=style/Widget.Material.EditText\n0103026a=style/Widget.Material.ExpandableListView\n0103026b=style/Widget.Material.FastScroll\n0103026c=style/Widget.Material.GridView\n0103026d=style/Widget.Material.HorizontalScrollView\n0103026e=style/Widget.Material.ImageButton\n0103026f=style/Widget.Material.ListPopupWindow\n01030270=style/Widget.Material.ListView\n01030271=style/Widget.Material.ListView.DropDown\n01030272=style/Widget.Material.MediaRouteButton\n01030273=style/Widget.Material.PopupMenu\n01030274=style/Widget.Material.PopupMenu.Overflow\n01030275=style/Widget.Material.PopupWindow\n01030276=style/Widget.Material.ProgressBar\n01030277=style/Widget.Material.ProgressBar.Horizontal\n01030278=style/Widget.Material.ProgressBar.Large\n01030279=style/Widget.Material.ProgressBar.Small\n0103027a=style/Widget.Material.ProgressBar.Small.Title\n0103027b=style/Widget.Material.RatingBar\n0103027c=style/Widget.Material.RatingBar.Indicator\n0103027d=style/Widget.Material.RatingBar.Small\n0103027e=style/Widget.Material.ScrollView\n0103027f=style/Widget.Material.SearchView\n01030280=style/Widget.Material.SeekBar\n01030281=style/Widget.Material.SegmentedButton\n01030282=style/Widget.Material.StackView\n01030283=style/Widget.Material.Spinner\n01030284=style/Widget.Material.Spinner.Underlined\n01030285=style/Widget.Material.Tab\n01030286=style/Widget.Material.TabWidget\n01030287=style/Widget.Material.TextView\n01030288=style/Widget.Material.TextView.SpinnerItem\n01030289=style/Widget.Material.TimePicker\n0103028a=style/Widget.Material.Toolbar\n0103028b=style/Widget.Material.Toolbar.Button.Navigation\n0103028c=style/Widget.Material.WebTextView\n0103028d=style/Widget.Material.WebView\n0103028e=style/Widget.Material.Light\n0103028f=style/Widget.Material.Light.ActionBar\n01030290=style/Widget.Material.Light.ActionBar.Solid\n01030291=style/Widget.Material.Light.ActionBar.TabBar\n01030292=style/Widget.Material.Light.ActionBar.TabText\n01030293=style/Widget.Material.Light.ActionBar.TabView\n01030294=style/Widget.Material.Light.ActionButton\n01030295=style/Widget.Material.Light.ActionButton.CloseMode\n01030296=style/Widget.Material.Light.ActionButton.Overflow\n01030297=style/Widget.Material.Light.ActionMode\n01030298=style/Widget.Material.Light.AutoCompleteTextView\n01030299=style/Widget.Material.Light.Button\n0103029a=style/Widget.Material.Light.Button.Borderless\n0103029b=style/Widget.Material.Light.Button.Borderless.Colored\n0103029c=style/Widget.Material.Light.Button.Borderless.Small\n0103029d=style/Widget.Material.Light.Button.Inset\n0103029e=style/Widget.Material.Light.Button.Small\n0103029f=style/Widget.Material.Light.Button.Toggle\n010302a0=style/Widget.Material.Light.ButtonBar\n010302a1=style/Widget.Material.Light.ButtonBar.AlertDialog\n010302a2=style/Widget.Material.Light.CalendarView\n010302a3=style/Widget.Material.Light.CheckedTextView\n010302a4=style/Widget.Material.Light.CompoundButton.CheckBox\n010302a5=style/Widget.Material.Light.CompoundButton.RadioButton\n010302a6=style/Widget.Material.Light.CompoundButton.Star\n010302a7=style/Widget.Material.Light.DatePicker\n010302a8=style/Widget.Material.Light.DropDownItem\n010302a9=style/Widget.Material.Light.DropDownItem.Spinner\n010302aa=style/Widget.Material.Light.EditText\n010302ab=style/Widget.Material.Light.ExpandableListView\n010302ac=style/Widget.Material.Light.FastScroll\n010302ad=style/Widget.Material.Light.GridView\n010302ae=style/Widget.Material.Light.HorizontalScrollView\n010302af=style/Widget.Material.Light.ImageButton\n010302b0=style/Widget.Material.Light.ListPopupWindow\n010302b1=style/Widget.Material.Light.ListView\n010302b2=style/Widget.Material.Light.ListView.DropDown\n010302b3=style/Widget.Material.Light.MediaRouteButton\n010302b4=style/Widget.Material.Light.PopupMenu\n010302b5=style/Widget.Material.Light.PopupMenu.Overflow\n010302b6=style/Widget.Material.Light.PopupWindow\n010302b7=style/Widget.Material.Light.ProgressBar\n010302b8=style/Widget.Material.Light.ProgressBar.Horizontal\n010302b9=style/Widget.Material.Light.ProgressBar.Inverse\n010302ba=style/Widget.Material.Light.ProgressBar.Large\n010302bb=style/Widget.Material.Light.ProgressBar.Large.Inverse\n010302bc=style/Widget.Material.Light.ProgressBar.Small\n010302bd=style/Widget.Material.Light.ProgressBar.Small.Inverse\n010302be=style/Widget.Material.Light.ProgressBar.Small.Title\n010302bf=style/Widget.Material.Light.RatingBar\n010302c0=style/Widget.Material.Light.RatingBar.Indicator\n010302c1=style/Widget.Material.Light.RatingBar.Small\n010302c2=style/Widget.Material.Light.ScrollView\n010302c3=style/Widget.Material.Light.SearchView\n010302c4=style/Widget.Material.Light.SeekBar\n010302c5=style/Widget.Material.Light.SegmentedButton\n010302c6=style/Widget.Material.Light.StackView\n010302c7=style/Widget.Material.Light.Spinner\n010302c8=style/Widget.Material.Light.Spinner.Underlined\n010302c9=style/Widget.Material.Light.Tab\n010302ca=style/Widget.Material.Light.TabWidget\n010302cb=style/Widget.Material.Light.TextView\n010302cc=style/Widget.Material.Light.TextView.SpinnerItem\n010302cd=style/Widget.Material.Light.TimePicker\n010302ce=style/Widget.Material.Light.WebTextView\n010302cf=style/Widget.Material.Light.WebView\n010302d0=style/Theme.Leanback.FormWizard\n010302d1=style/Theme.DeviceDefault.Dialog.Alert\n010302d2=style/Theme.DeviceDefault.Light.Dialog.Alert\n010302d3=style/Widget.Material.Button.Colored\n010302d4=style/TextAppearance.Material.Widget.Button.Inverse\n010302d5=style/Theme.Material.Light.LightStatusBar\n010302d6=style/ThemeOverlay.Material.Dialog\n010302d7=style/ThemeOverlay.Material.Dialog.Alert\n010302d8=style/Theme.Material.Light.DialogWhenLarge.DarkActionBar\n010302d9=style/Widget.Material.SeekBar.Discrete\n010302da=style/Widget.Material.CompoundButton.Switch\n010302db=style/Widget.Material.Light.CompoundButton.Switch\n010302dc=style/Widget.Material.NumberPicker\n010302dd=style/Widget.Material.Light.NumberPicker\n010302de=style/TextAppearance.Material.Widget.Button.Colored\n010302df=style/TextAppearance.Material.Widget.Button.Borderless.Colored\n010302e0=style/Widget.DeviceDefault.Button.Colored\n010302e1=style/Widget.DeviceDefault.Button.Borderless.Colored\n010302e2=style/Theme.DeviceDefault.DocumentsUI\n010302e3=style/Theme.DeviceDefault.DayNight\n010302e4=style/ThemeOverlay.DeviceDefault.Accent.DayNight\n010302e5=style/ActionBarButton\n010302e6=style/ActionBarButtonTextAppearance\n010302e7=style/ActionBarTitle\n010302e8=style/ActiveWallpaperSettings\n010302e9=style/AlertDialog\n010302ea=style/AlertDialog.DeviceDefault\n010302eb=style/AlertDialog.DeviceDefault.Light\n010302ec=style/AlertDialog.Holo\n010302ed=style/AlertDialog.Holo.Light\n010302ee=style/AlertDialog.Leanback\n010302ef=style/AlertDialog.Leanback.Light\n010302f0=style/AlertDialog.Material\n010302f1=style/AlertDialog.Material.Light\n010302f2=style/Animation.DeviceDefault.Activity\n010302f3=style/Animation.DeviceDefault.Activity.Resolver\n010302f4=style/Animation.DeviceDefault.Dialog\n010302f5=style/Animation.DropDownDown\n010302f6=style/Animation.DropDownUp\n010302f7=style/Animation.Holo\n010302f8=style/Animation.Holo.Activity\n010302f9=style/Animation.Holo.Dialog\n010302fa=style/Animation.ImmersiveModeConfirmation\n010302fb=style/Animation.InputMethodFancy\n010302fc=style/Animation.LockScreen\n010302fd=style/Animation.Material\n010302fe=style/Animation.Material.Activity\n010302ff=style/Animation.Material.Dialog\n01030300=style/Animation.Material.Popup\n01030301=style/Animation.OptionsPanel\n01030302=style/Animation.PopupWindow\n01030303=style/Animation.PopupWindow.ActionMode\n01030304=style/Animation.RecentApplications\n01030305=style/Animation.SearchBar\n01030306=style/Animation.SubMenuPanel\n01030307=style/Animation.TextSelectHandle\n01030308=style/Animation.Tooltip\n01030309=style/Animation.TypingFilter\n0103030a=style/Animation.TypingFilterRestore\n0103030b=style/Animation.VoiceActivity\n0103030c=style/Animation.VoiceInteractionSession\n0103030d=style/Animation.VolumePanel\n0103030e=style/Animation.Wallpaper\n0103030f=style/Animation.ZoomButtons\n01030310=style/AutofillDatasetPicker\n01030311=style/AutofillHalfScreenAnimation\n01030312=style/AutofillSaveAnimation\n01030313=style/BasePreferenceFragment\n01030314=style/CarAction1\n01030315=style/CarAction1.Dark\n01030316=style/CarAction1.Light\n01030317=style/CarBody1\n01030318=style/CarBody1.Dark\n01030319=style/CarBody1.Light\n0103031a=style/CarBody2\n0103031b=style/CarBody2.Dark\n0103031c=style/CarBody2.Light\n0103031d=style/CarBody3\n0103031e=style/CarBody4\n0103031f=style/CarTitle\n01030320=style/CarTitle.Dark\n01030321=style/CarTitle.Light\n01030322=style/DatePickerDialog.Material\n01030323=style/DialogWindowTitle\n01030324=style/DialogWindowTitle.DeviceDefault\n01030325=style/DialogWindowTitle.DeviceDefault.Light\n01030326=style/DialogWindowTitle.Holo\n01030327=style/DialogWindowTitle.Holo.Light\n01030328=style/DialogWindowTitle.Material\n01030329=style/DialogWindowTitle.Material.Light\n0103032a=style/DialogWindowTitleBackground\n0103032b=style/DialogWindowTitleBackground.Material\n0103032c=style/DialogWindowTitleBackground.Material.Light\n0103032d=style/Holo\n0103032e=style/Holo.Light\n0103032f=style/LargePointer\n01030330=style/Material\n01030331=style/Material.Light\n01030332=style/Notification.Header\n01030333=style/NotificationAction\n01030334=style/NotificationEmphasizedAction\n01030335=style/NotificationMediaActionContainer\n01030336=style/NotificationTombstoneAction\n01030337=style/Pointer\n01030338=style/Preference\n01030339=style/Preference.Category\n0103033a=style/Preference.CheckBoxPreference\n0103033b=style/Preference.DeviceDefault\n0103033c=style/Preference.DeviceDefault.Category\n0103033d=style/Preference.DeviceDefault.CheckBoxPreference\n0103033e=style/Preference.DeviceDefault.DialogPreference\n0103033f=style/Preference.DeviceDefault.DialogPreference.EditTextPreference\n01030340=style/Preference.DeviceDefault.DialogPreference.YesNoPreference\n01030341=style/Preference.DeviceDefault.Information\n01030342=style/Preference.DeviceDefault.PreferenceScreen\n01030343=style/Preference.DeviceDefault.RingtonePreference\n01030344=style/Preference.DeviceDefault.SeekBarPreference\n01030345=style/Preference.DeviceDefault.SwitchPreference\n01030346=style/Preference.DialogPreference\n01030347=style/Preference.DialogPreference.EditTextPreference\n01030348=style/Preference.DialogPreference.SeekBarPreference\n01030349=style/Preference.DialogPreference.YesNoPreference\n0103034a=style/Preference.Holo\n0103034b=style/Preference.Holo.Category\n0103034c=style/Preference.Holo.CheckBoxPreference\n0103034d=style/Preference.Holo.DialogPreference\n0103034e=style/Preference.Holo.DialogPreference.EditTextPreference\n0103034f=style/Preference.Holo.DialogPreference.YesNoPreference\n01030350=style/Preference.Holo.Information\n01030351=style/Preference.Holo.PreferenceScreen\n01030352=style/Preference.Holo.RingtonePreference\n01030353=style/Preference.Holo.SeekBarPreference\n01030354=style/Preference.Holo.SwitchPreference\n01030355=style/Preference.Information\n01030356=style/Preference.Material\n01030357=style/Preference.Material.BasePreferenceScreen\n01030358=style/Preference.Material.Category\n01030359=style/Preference.Material.CheckBoxPreference\n0103035a=style/Preference.Material.DialogPreference\n0103035b=style/Preference.Material.DialogPreference.EditTextPreference\n0103035c=style/Preference.Material.DialogPreference.SeekBarPreference\n0103035d=style/Preference.Material.DialogPreference.YesNoPreference\n0103035e=style/Preference.Material.Information\n0103035f=style/Preference.Material.PreferenceScreen\n01030360=style/Preference.Material.RingtonePreference\n01030361=style/Preference.Material.SeekBarPreference\n01030362=style/Preference.Material.SwitchPreference\n01030363=style/Preference.PreferenceScreen\n01030364=style/Preference.RingtonePreference\n01030365=style/Preference.SeekBarPreference\n01030366=style/Preference.SwitchPreference\n01030367=style/PreferenceActivity\n01030368=style/PreferenceActivity.Material\n01030369=style/PreferenceFragment\n0103036a=style/PreferenceFragment.Holo\n0103036b=style/PreferenceFragment.Material\n0103036c=style/PreferenceFragmentList\n0103036d=style/PreferenceFragmentList.Material\n0103036e=style/PreferenceHeaderList\n0103036f=style/PreferenceHeaderList.Material\n01030370=style/PreferenceHeaderPanel\n01030371=style/PreferenceHeaderPanel.Material\n01030372=style/PreferencePanel\n01030373=style/PreferencePanel.Dialog\n01030374=style/PreferencePanel.Material\n01030375=style/PreferencePanel.Material.Dialog\n01030376=style/PreviewWallpaperSettings\n01030377=style/SegmentedButton\n01030378=style/TextAppearance.AutoCorrectionSuggestion\n01030379=style/TextAppearance.DeviceDefault.Body1\n0103037a=style/TextAppearance.DeviceDefault.Body2\n0103037b=style/TextAppearance.DeviceDefault.Caption\n0103037c=style/TextAppearance.DeviceDefault.Display1\n0103037d=style/TextAppearance.DeviceDefault.Headline\n0103037e=style/TextAppearance.DeviceDefault.ListItem\n0103037f=style/TextAppearance.DeviceDefault.ListItemSecondary\n01030380=style/TextAppearance.DeviceDefault.Notification\n01030381=style/TextAppearance.DeviceDefault.Notification.Conversation.AppName\n01030382=style/TextAppearance.DeviceDefault.Notification.Info\n01030383=style/TextAppearance.DeviceDefault.Notification.Reply\n01030384=style/TextAppearance.DeviceDefault.Notification.Time\n01030385=style/TextAppearance.DeviceDefault.Notification.Title\n01030386=style/TextAppearance.DeviceDefault.Subhead\n01030387=style/TextAppearance.DeviceDefault.Title\n01030388=style/TextAppearance.DeviceDefault.Widget.Button.Borderless.Colored\n01030389=style/TextAppearance.DeviceDefault.Widget.Switch\n0103038a=style/TextAppearance.DeviceDefault.Widget.Toolbar.Subtitle\n0103038b=style/TextAppearance.DeviceDefault.Widget.Toolbar.Title\n0103038c=style/TextAppearance.EasyCorrectSuggestion\n0103038d=style/TextAppearance.Holo.CalendarViewWeekDayView\n0103038e=style/TextAppearance.Holo.Light\n0103038f=style/TextAppearance.Holo.Light.CalendarViewWeekDayView\n01030390=style/TextAppearance.Holo.Light.DialogWindowTitle\n01030391=style/TextAppearance.Holo.Light.Inverse\n01030392=style/TextAppearance.Holo.Light.Large\n01030393=style/TextAppearance.Holo.Light.Large.Inverse\n01030394=style/TextAppearance.Holo.Light.Medium\n01030395=style/TextAppearance.Holo.Light.Medium.Inverse\n01030396=style/TextAppearance.Holo.Light.SearchResult\n01030397=style/TextAppearance.Holo.Light.SearchResult.Subtitle\n01030398=style/TextAppearance.Holo.Light.SearchResult.Title\n01030399=style/TextAppearance.Holo.Light.Small\n0103039a=style/TextAppearance.Holo.Light.Small.Inverse\n0103039b=style/TextAppearance.Holo.Light.Widget\n0103039c=style/TextAppearance.Holo.Light.Widget.ActionMode.Subtitle\n0103039d=style/TextAppearance.Holo.Light.Widget.ActionMode.Title\n0103039e=style/TextAppearance.Holo.Light.Widget.Button\n0103039f=style/TextAppearance.Holo.Light.Widget.DropDownHint\n010303a0=style/TextAppearance.Holo.Light.Widget.EditText\n010303a1=style/TextAppearance.Holo.Light.Widget.PopupMenu\n010303a2=style/TextAppearance.Holo.Light.Widget.PopupMenu.Large\n010303a3=style/TextAppearance.Holo.Light.Widget.PopupMenu.Small\n010303a4=style/TextAppearance.Holo.Light.Widget.Switch\n010303a5=style/TextAppearance.Holo.Light.WindowTitle\n010303a6=style/TextAppearance.Holo.SearchResult\n010303a7=style/TextAppearance.Holo.SuggestionHighlight\n010303a8=style/TextAppearance.Holo.Widget.ActionMode\n010303a9=style/TextAppearance.Holo.Widget.Switch\n010303aa=style/TextAppearance.Large.Inverse.NumberPickerInputText\n010303ab=style/TextAppearance.Leanback.FormWizard\n010303ac=style/TextAppearance.Leanback.FormWizard.Large\n010303ad=style/TextAppearance.Leanback.FormWizard.ListItem\n010303ae=style/TextAppearance.Leanback.FormWizard.Medium\n010303af=style/TextAppearance.Leanback.FormWizard.Small\n010303b0=style/TextAppearance.Material.DatePicker.DateLabel\n010303b1=style/TextAppearance.Material.DatePicker.List.YearLabel\n010303b2=style/TextAppearance.Material.DatePicker.List.YearLabel.Activated\n010303b3=style/TextAppearance.Material.DatePicker.YearLabel\n010303b4=style/TextAppearance.Material.ListItem\n010303b5=style/TextAppearance.Material.ListItemSecondary\n010303b6=style/TextAppearance.Material.Menu.Inverse\n010303b7=style/TextAppearance.Material.Notification.Conversation.AppName\n010303b8=style/TextAppearance.Material.Notification.Reply\n010303b9=style/TextAppearance.Material.NumberPicker\n010303ba=style/TextAppearance.Material.SearchResult\n010303bb=style/TextAppearance.Material.Subhead.Inverse\n010303bc=style/TextAppearance.Material.TextSuggestionHighlight\n010303bd=style/TextAppearance.Material.TimePicker.AmPmLabel\n010303be=style/TextAppearance.Material.TimePicker.InputField\n010303bf=style/TextAppearance.Material.TimePicker.InputHeader\n010303c0=style/TextAppearance.Material.TimePicker.PromptLabel\n010303c1=style/TextAppearance.Material.TimePicker.TimeLabel\n010303c2=style/TextAppearance.Material.Title.Inverse\n010303c3=style/TextAppearance.Material.Widget.ActionBar.Menu.Inverse\n010303c4=style/TextAppearance.Material.Widget.ActionMode\n010303c5=style/TextAppearance.Material.Widget.Calendar.Day\n010303c6=style/TextAppearance.Material.Widget.Calendar.DayOfWeek\n010303c7=style/TextAppearance.Material.Widget.Calendar.Month\n010303c8=style/TextAppearance.Material.Widget.PopupMenu.Header\n010303c9=style/TextAppearance.Material.Widget.Switch\n010303ca=style/TextAppearance.MisspelledSuggestion\n010303cb=style/TextAppearance.SearchResult\n010303cc=style/TextAppearance.SlidingTabActive\n010303cd=style/TextAppearance.SlidingTabNormal\n010303ce=style/TextAppearance.Small.CalendarViewWeekDayView\n010303cf=style/TextAppearance.StatusBar\n010303d0=style/TextAppearance.StatusBar.EventContent.Emphasis\n010303d1=style/TextAppearance.StatusBar.EventContent.Info\n010303d2=style/TextAppearance.StatusBar.EventContent.Line2\n010303d3=style/TextAppearance.StatusBar.EventContent.Time\n010303d4=style/TextAppearance.StatusBar.Ticker\n010303d5=style/TextAppearance.Suggestion\n010303d6=style/TextAppearance.Toast\n010303d7=style/TextAppearance.Tooltip\n010303d8=style/TextAppearance.Widget.ActionBar.Subtitle\n010303d9=style/TextAppearance.Widget.ActionBar.Title\n010303da=style/TextAppearance.Widget.ActionMode.Subtitle\n010303db=style/TextAppearance.Widget.ActionMode.Title\n010303dc=style/TextAppearance.Widget.PopupMenu\n010303dd=style/TextAppearance.Widget.Toolbar.Subtitle\n010303de=style/TextAppearance.Widget.Toolbar.Title\n010303df=style/Theme.DeviceDefault.Autofill\n010303e0=style/Theme.DeviceDefault.Autofill.Light\n010303e1=style/Theme.DeviceDefault.Autofill.Save\n010303e2=style/Theme.DeviceDefault.Chooser\n010303e3=style/Theme.DeviceDefault.Dialog.Alert.DayNight\n010303e4=style/Theme.DeviceDefault.Dialog.AppError\n010303e5=style/Theme.DeviceDefault.Dialog.FixedSize\n010303e6=style/Theme.DeviceDefault.Dialog.NoActionBar.FixedSize\n010303e7=style/Theme.DeviceDefault.Dialog.NoFrame\n010303e8=style/Theme.DeviceDefault.Dialog.Presentation\n010303e9=style/Theme.DeviceDefault.Light.Autofill\n010303ea=style/Theme.DeviceDefault.Light.Autofill.Save\n010303eb=style/Theme.DeviceDefault.Light.Dialog.Alert.UserSwitchingDialog\n010303ec=style/Theme.DeviceDefault.Light.Dialog.FixedSize\n010303ed=style/Theme.DeviceDefault.Light.Dialog.NoActionBar.FixedSize\n010303ee=style/Theme.DeviceDefault.Light.Dialog.Presentation\n010303ef=style/Theme.DeviceDefault.Light.SearchBar\n010303f0=style/Theme.DeviceDefault.Light.Voice\n010303f1=style/Theme.DeviceDefault.Notification\n010303f2=style/Theme.DeviceDefault.QuickSettings\n010303f3=style/Theme.DeviceDefault.QuickSettings.Dialog\n010303f4=style/Theme.DeviceDefault.Resolver\n010303f5=style/Theme.DeviceDefault.ResolverCommon\n010303f6=style/Theme.DeviceDefault.SearchBar\n010303f7=style/Theme.DeviceDefault.Settings.BaseDialog\n010303f8=style/Theme.DeviceDefault.Settings.CompactMenu\n010303f9=style/Theme.DeviceDefault.Settings.Dark.NoActionBar\n010303fa=style/Theme.DeviceDefault.Settings.Dialog\n010303fb=style/Theme.DeviceDefault.Settings.Dialog.Alert\n010303fc=style/Theme.DeviceDefault.Settings.Dialog.NoActionBar\n010303fd=style/Theme.DeviceDefault.Settings.Dialog.Presentation\n010303fe=style/Theme.DeviceDefault.Settings.DialogBase\n010303ff=style/Theme.DeviceDefault.Settings.DialogWhenLarge\n01030400=style/Theme.DeviceDefault.Settings.DialogWhenLarge.NoActionBar\n01030401=style/Theme.DeviceDefault.Settings.NoActionBar\n01030402=style/Theme.DeviceDefault.Settings.SearchBar\n01030403=style/Theme.DeviceDefault.System\n01030404=style/Theme.DeviceDefault.System.Dialog\n01030405=style/Theme.DeviceDefault.System.Dialog.Alert\n01030406=style/Theme.DeviceDefault.VoiceInteractionSession\n01030407=style/Theme.DeviceDefaultBase\n01030408=style/Theme.Dialog.Alert\n01030409=style/Theme.Dialog.Confirmation\n0103040a=style/Theme.Dialog.NoFrame\n0103040b=style/Theme.Dialog.RecentApplications\n0103040c=style/Theme.Dream\n0103040d=style/Theme.ExpandedMenu\n0103040e=style/Theme.GlobalSearchBar\n0103040f=style/Theme.Holo.CompactMenu\n01030410=style/Theme.Holo.Dialog.Alert\n01030411=style/Theme.Holo.Dialog.BaseAlert\n01030412=style/Theme.Holo.Dialog.FixedSize\n01030413=style/Theme.Holo.Dialog.NoActionBar.FixedSize\n01030414=style/Theme.Holo.Dialog.NoFrame\n01030415=style/Theme.Holo.Dialog.Presentation\n01030416=style/Theme.Holo.Light.CompactMenu\n01030417=style/Theme.Holo.Light.Dialog.Alert\n01030418=style/Theme.Holo.Light.Dialog.BaseAlert\n01030419=style/Theme.Holo.Light.Dialog.FixedSize\n0103041a=style/Theme.Holo.Light.Dialog.NoActionBar.FixedSize\n0103041b=style/Theme.Holo.Light.Dialog.Presentation\n0103041c=style/Theme.Holo.Light.SearchBar\n0103041d=style/Theme.Holo.SearchBar\n0103041e=style/Theme.IconMenu\n0103041f=style/Theme.Leanback.Dialog\n01030420=style/Theme.Leanback.Dialog.Alert\n01030421=style/Theme.Leanback.Dialog.AppError\n01030422=style/Theme.Leanback.Dialog.Confirmation\n01030423=style/Theme.Leanback.Light.Dialog\n01030424=style/Theme.Leanback.Light.Dialog.Alert\n01030425=style/Theme.Leanback.Resolver\n01030426=style/Theme.Leanback.Settings.Dialog\n01030427=style/Theme.Leanback.Settings.Dialog.Alert\n01030428=style/Theme.Material.BaseDialog\n01030429=style/Theme.Material.CompactMenu\n0103042a=style/Theme.Material.Dialog.BaseAlert\n0103042b=style/Theme.Material.Dialog.FixedSize\n0103042c=style/Theme.Material.Dialog.NoActionBar.FixedSize\n0103042d=style/Theme.Material.Dialog.NoFrame\n0103042e=style/Theme.Material.Light.BaseDialog\n0103042f=style/Theme.Material.Light.CompactMenu\n01030430=style/Theme.Material.Light.Dialog.BaseAlert\n01030431=style/Theme.Material.Light.Dialog.FixedSize\n01030432=style/Theme.Material.Light.Dialog.NoActionBar.FixedSize\n01030433=style/Theme.Material.Light.SearchBar\n01030434=style/Theme.Material.Notification\n01030435=style/Theme.Material.SearchBar\n01030436=style/Theme.Material.Settings.BaseDialog\n01030437=style/Theme.Material.Settings.CompactMenu\n01030438=style/Theme.Material.Settings.Dialog\n01030439=style/Theme.Material.Settings.Dialog.Alert\n0103043a=style/Theme.Material.Settings.Dialog.BaseAlert\n0103043b=style/Theme.Material.Settings.Dialog.Presentation\n0103043c=style/Theme.Material.Settings.DialogWhenLarge\n0103043d=style/Theme.Material.Settings.DialogWhenLarge.NoActionBar\n0103043e=style/Theme.Material.Settings.NoActionBar\n0103043f=style/Theme.Material.Settings.SearchBar\n01030440=style/Theme.Material.VoiceInteractionSession\n01030441=style/Theme.SearchBar\n01030442=style/Theme.Toast\n01030443=style/Theme.VoiceInteractionSession\n01030444=style/ThemeOverlay.DeviceDefault\n01030445=style/ThemeOverlay.DeviceDefault.Accent\n01030446=style/ThemeOverlay.DeviceDefault.Accent.Light\n01030447=style/ThemeOverlay.DeviceDefault.ActionBar\n01030448=style/ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent\n01030449=style/ThemeOverlay.DeviceDefault.Popup.Light\n0103044a=style/ThemeOverlay.Material.BaseDialog\n0103044b=style/ThemeOverlay.Material.Dialog.DatePicker\n0103044c=style/ThemeOverlay.Material.Dialog.TimePicker\n0103044d=style/TimePickerDialog.Material\n0103044e=style/Widget.ActionMode\n0103044f=style/Widget.ActivityChooserView\n01030450=style/Widget.Button.Transparent\n01030451=style/Widget.CheckedTextView\n01030452=style/Widget.CompoundButton.Switch\n01030453=style/Widget.DeviceDefault.AbsListView\n01030454=style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog\n01030455=style/Widget.DeviceDefault.CompoundButton.Switch\n01030456=style/Widget.DeviceDefault.ExpandableListView.White\n01030457=style/Widget.DeviceDefault.FragmentBreadCrumbs\n01030458=style/Widget.DeviceDefault.Gallery\n01030459=style/Widget.DeviceDefault.GestureOverlayView\n0103045a=style/Widget.DeviceDefault.ImageWell\n0103045b=style/Widget.DeviceDefault.KeyboardView\n0103045c=style/Widget.DeviceDefault.Light.AbsListView\n0103045d=style/Widget.DeviceDefault.Light.Button.Borderless\n0103045e=style/Widget.DeviceDefault.Light.DatePicker\n0103045f=style/Widget.DeviceDefault.Light.ExpandableListView.White\n01030460=style/Widget.DeviceDefault.Light.FragmentBreadCrumbs\n01030461=style/Widget.DeviceDefault.Light.Gallery\n01030462=style/Widget.DeviceDefault.Light.GestureOverlayView\n01030463=style/Widget.DeviceDefault.Light.ImageWell\n01030464=style/Widget.DeviceDefault.Light.ListView.White\n01030465=style/Widget.DeviceDefault.Light.NumberPicker\n01030466=style/Widget.DeviceDefault.Light.PopupWindow.ActionMode\n01030467=style/Widget.DeviceDefault.Light.Spinner.DropDown\n01030468=style/Widget.DeviceDefault.Light.Spinner.DropDown.ActionBar\n01030469=style/Widget.DeviceDefault.Light.TextView.ListSeparator\n0103046a=style/Widget.DeviceDefault.Light.TimePicker\n0103046b=style/Widget.DeviceDefault.ListView.White\n0103046c=style/Widget.DeviceDefault.Notification.MessagingName\n0103046d=style/Widget.DeviceDefault.Notification.MessagingText\n0103046e=style/Widget.DeviceDefault.Notification.Text\n0103046f=style/Widget.DeviceDefault.NumberPicker\n01030470=style/Widget.DeviceDefault.PopupWindow.ActionMode\n01030471=style/Widget.DeviceDefault.PreferenceFrameLayout\n01030472=style/Widget.DeviceDefault.ProgressBar.Inverse\n01030473=style/Widget.DeviceDefault.ProgressBar.Large.Inverse\n01030474=style/Widget.DeviceDefault.ProgressBar.Small.Inverse\n01030475=style/Widget.DeviceDefault.QuickContactBadge.WindowLarge\n01030476=style/Widget.DeviceDefault.QuickContactBadge.WindowMedium\n01030477=style/Widget.DeviceDefault.QuickContactBadge.WindowSmall\n01030478=style/Widget.DeviceDefault.QuickContactBadgeSmall.WindowLarge\n01030479=style/Widget.DeviceDefault.QuickContactBadgeSmall.WindowMedium\n0103047a=style/Widget.DeviceDefault.QuickContactBadgeSmall.WindowSmall\n0103047b=style/Widget.DeviceDefault.Resolver.TabWidget\n0103047c=style/Widget.DeviceDefault.Spinner.DropDown\n0103047d=style/Widget.DeviceDefault.Spinner.DropDown.ActionBar\n0103047e=style/Widget.DeviceDefault.TextSelectHandle\n0103047f=style/Widget.DeviceDefault.TextView.ListSeparator\n01030480=style/Widget.DeviceDefault.TimePicker\n01030481=style/Widget.DeviceDefault.Toolbar\n01030482=style/Widget.ExpandableListView.White\n01030483=style/Widget.GenericQuickContactBadge\n01030484=style/Widget.GestureOverlayView\n01030485=style/Widget.GestureOverlayView.White\n01030486=style/Widget.Holo.AbsListView\n01030487=style/Widget.Holo.ActivityChooserView\n01030488=style/Widget.Holo.ButtonBar\n01030489=style/Widget.Holo.ButtonBar.Button\n0103048a=style/Widget.Holo.CompoundButton\n0103048b=style/Widget.Holo.CompoundButton.Switch\n0103048c=style/Widget.Holo.ExpandableListView.White\n0103048d=style/Widget.Holo.FastScroll\n0103048e=style/Widget.Holo.FragmentBreadCrumbs\n0103048f=style/Widget.Holo.Gallery\n01030490=style/Widget.Holo.GestureOverlayView\n01030491=style/Widget.Holo.ImageWell\n01030492=style/Widget.Holo.KeyboardView\n01030493=style/Widget.Holo.Light.AbsListView\n01030494=style/Widget.Holo.Light.ActivityChooserView\n01030495=style/Widget.Holo.Light.Button.Borderless\n01030496=style/Widget.Holo.Light.CompoundButton.Switch\n01030497=style/Widget.Holo.Light.DatePicker\n01030498=style/Widget.Holo.Light.ExpandableListView.White\n01030499=style/Widget.Holo.Light.FastScroll\n0103049a=style/Widget.Holo.Light.FragmentBreadCrumbs\n0103049b=style/Widget.Holo.Light.Gallery\n0103049c=style/Widget.Holo.Light.GestureOverlayView\n0103049d=style/Widget.Holo.Light.ImageWell\n0103049e=style/Widget.Holo.Light.KeyboardView\n0103049f=style/Widget.Holo.Light.ListView.White\n010304a0=style/Widget.Holo.Light.NumberPicker\n010304a1=style/Widget.Holo.Light.PopupWindow.ActionMode\n010304a2=style/Widget.Holo.Light.QuickContactBadge.WindowLarge\n010304a3=style/Widget.Holo.Light.QuickContactBadge.WindowMedium\n010304a4=style/Widget.Holo.Light.QuickContactBadge.WindowSmall\n010304a5=style/Widget.Holo.Light.QuickContactBadgeSmall.WindowLarge\n010304a6=style/Widget.Holo.Light.QuickContactBadgeSmall.WindowMedium\n010304a7=style/Widget.Holo.Light.QuickContactBadgeSmall.WindowSmall\n010304a8=style/Widget.Holo.Light.SearchView\n010304a9=style/Widget.Holo.Light.Spinner.DropDown\n010304aa=style/Widget.Holo.Light.Spinner.DropDown.ActionBar\n010304ab=style/Widget.Holo.Light.StackView\n010304ac=style/Widget.Holo.Light.TextSelectHandle\n010304ad=style/Widget.Holo.Light.TextView.ListSeparator\n010304ae=style/Widget.Holo.Light.TimePicker\n010304af=style/Widget.Holo.ListView.White\n010304b0=style/Widget.Holo.NumberPicker\n010304b1=style/Widget.Holo.PopupWindow.ActionMode\n010304b2=style/Widget.Holo.PreferenceFrameLayout\n010304b3=style/Widget.Holo.ProgressBar.Inverse\n010304b4=style/Widget.Holo.ProgressBar.Large.Inverse\n010304b5=style/Widget.Holo.ProgressBar.Small.Inverse\n010304b6=style/Widget.Holo.QuickContactBadge.WindowLarge\n010304b7=style/Widget.Holo.QuickContactBadge.WindowMedium\n010304b8=style/Widget.Holo.QuickContactBadge.WindowSmall\n010304b9=style/Widget.Holo.QuickContactBadgeSmall.WindowLarge\n010304ba=style/Widget.Holo.QuickContactBadgeSmall.WindowMedium\n010304bb=style/Widget.Holo.QuickContactBadgeSmall.WindowSmall\n010304bc=style/Widget.Holo.SearchView\n010304bd=style/Widget.Holo.Spinner.DropDown\n010304be=style/Widget.Holo.Spinner.DropDown.ActionBar\n010304bf=style/Widget.Holo.StackView\n010304c0=style/Widget.Holo.SuggestionButton\n010304c1=style/Widget.Holo.SuggestionItem\n010304c2=style/Widget.Holo.TabText\n010304c3=style/Widget.Holo.TextSelectHandle\n010304c4=style/Widget.Holo.TextView.ListSeparator\n010304c5=style/Widget.Holo.TimePicker\n010304c6=style/Widget.HorizontalScrollView\n010304c7=style/Widget.Leanback.DatePicker\n010304c8=style/Widget.Leanback.NumberPicker\n010304c9=style/Widget.Leanback.TimePicker\n010304ca=style/Widget.LockPatternView\n010304cb=style/Widget.Magnifier\n010304cc=style/Widget.Material.AbsListView\n010304cd=style/Widget.Material.ActivityChooserView\n010304ce=style/Widget.Material.Button.ButtonBar.AlertDialog\n010304cf=style/Widget.Material.CompoundButton\n010304d0=style/Widget.Material.ContextPopupMenu\n010304d1=style/Widget.Material.ExpandableListView.White\n010304d2=style/Widget.Material.FragmentBreadCrumbs\n010304d3=style/Widget.Material.Gallery\n010304d4=style/Widget.Material.GestureOverlayView\n010304d5=style/Widget.Material.ImageWell\n010304d6=style/Widget.Material.KeyboardView\n010304d7=style/Widget.Material.Light.AbsListView\n010304d8=style/Widget.Material.Light.ActivityChooserView\n010304d9=style/Widget.Material.Light.Button.ButtonBar.AlertDialog\n010304da=style/Widget.Material.Light.CompoundButton\n010304db=style/Widget.Material.Light.ExpandableListView.White\n010304dc=style/Widget.Material.Light.FragmentBreadCrumbs\n010304dd=style/Widget.Material.Light.Gallery\n010304de=style/Widget.Material.Light.GestureOverlayView\n010304df=style/Widget.Material.Light.ImageWell\n010304e0=style/Widget.Material.Light.KeyboardView\n010304e1=style/Widget.Material.Light.ListView.White\n010304e2=style/Widget.Material.Light.PopupWindow.ActionMode\n010304e3=style/Widget.Material.Light.QuickContactBadge.WindowLarge\n010304e4=style/Widget.Material.Light.QuickContactBadge.WindowMedium\n010304e5=style/Widget.Material.Light.QuickContactBadge.WindowSmall\n010304e6=style/Widget.Material.Light.QuickContactBadgeSmall.WindowLarge\n010304e7=style/Widget.Material.Light.QuickContactBadgeSmall.WindowMedium\n010304e8=style/Widget.Material.Light.QuickContactBadgeSmall.WindowSmall\n010304e9=style/Widget.Material.Light.SearchView.ActionBar\n010304ea=style/Widget.Material.Light.Spinner.DropDown\n010304eb=style/Widget.Material.Light.Spinner.DropDown.ActionBar\n010304ec=style/Widget.Material.Light.TextSelectHandle\n010304ed=style/Widget.Material.Light.TextView.ListSeparator\n010304ee=style/Widget.Material.ListMenuView\n010304ef=style/Widget.Material.ListView.White\n010304f0=style/Widget.Material.Notification.MessagingName\n010304f1=style/Widget.Material.Notification.MessagingText\n010304f2=style/Widget.Material.Notification.ProgressBar\n010304f3=style/Widget.Material.Notification.Text\n010304f4=style/Widget.Material.PopupWindow.ActionMode\n010304f5=style/Widget.Material.PreferenceFrameLayout\n010304f6=style/Widget.Material.ProgressBar.Inverse\n010304f7=style/Widget.Material.ProgressBar.Large.Inverse\n010304f8=style/Widget.Material.ProgressBar.Small.Inverse\n010304f9=style/Widget.Material.QuickContactBadge.WindowLarge\n010304fa=style/Widget.Material.QuickContactBadge.WindowMedium\n010304fb=style/Widget.Material.QuickContactBadge.WindowSmall\n010304fc=style/Widget.Material.QuickContactBadgeSmall.WindowLarge\n010304fd=style/Widget.Material.QuickContactBadgeSmall.WindowMedium\n010304fe=style/Widget.Material.QuickContactBadgeSmall.WindowSmall\n010304ff=style/Widget.Material.Resolver.Tab\n01030500=style/Widget.Material.SearchView.ActionBar\n01030501=style/Widget.Material.Spinner.DropDown\n01030502=style/Widget.Material.Spinner.DropDown.ActionBar\n01030503=style/Widget.Material.SuggestionButton\n01030504=style/Widget.Material.SuggestionItem\n01030505=style/Widget.Material.TabText\n01030506=style/Widget.Material.TextSelectHandle\n01030507=style/Widget.Material.TextView.ListSeparator\n01030508=style/Widget.NumberPicker\n01030509=style/Widget.PreferenceFrameLayout\n0103050a=style/Widget.ProgressBar.Small.Title\n0103050b=style/Widget.QuickContactBadge\n0103050c=style/Widget.QuickContactBadge.WindowLarge\n0103050d=style/Widget.QuickContactBadge.WindowMedium\n0103050e=style/Widget.QuickContactBadge.WindowSmall\n0103050f=style/Widget.QuickContactBadgeSmall\n01030510=style/Widget.QuickContactBadgeSmall.WindowLarge\n01030511=style/Widget.QuickContactBadgeSmall.WindowMedium\n01030512=style/Widget.QuickContactBadgeSmall.WindowSmall\n01030513=style/Widget.RatingBar.Indicator\n01030514=style/Widget.RatingBar.Small\n01030515=style/Widget.TextSelectHandle\n01030516=style/Widget.TextView.ListSeparator\n01030517=style/Widget.TextView.ListSeparator.White\n01030518=style/Widget.TimePicker\n01030519=style/Widget.WebTextView\n0103051a=style/WindowAnimationStyle.Leanback.Setup\n0103051b=style/WindowTitle\n0103051c=style/WindowTitle.DeviceDefault\n0103051d=style/WindowTitle.Holo\n0103051e=style/WindowTitle.Material\n0103051f=style/WindowTitleBackground\n01030520=style/WindowTitleBackground.DeviceDefault\n01030521=style/WindowTitleBackground.Holo\n01030522=style/WindowTitleBackground.Material\n01030523=style/ZoomControls\n01030524=style/aerr_list_item\n01040000=string/cancel\n01040001=string/copy\n01040002=string/copyUrl\n01040003=string/cut\n01040004=string/defaultVoiceMailAlphaTag\n01040005=string/defaultMsisdnAlphaTag\n01040006=string/emptyPhoneNumber\n01040007=string/httpErrorBadUrl\n01040008=string/httpErrorUnsupportedScheme\n01040009=string/no\n0104000a=string/ok\n0104000b=string/paste\n0104000c=string/search_go\n0104000d=string/selectAll\n0104000e=string/unknownName\n0104000f=string/untitled\n01040010=string/VideoView_error_button\n01040011=string/VideoView_error_text_unknown\n01040012=string/VideoView_error_title\n01040013=string/yes\n01040014=string/dialog_alert_title\n01040015=string/VideoView_error_text_invalid_progressive_playback\n01040016=string/selectTextMode\n01040017=string/status_bar_notification_info_overflow\n01040018=string/fingerprint_icon_content_description\n01040019=string/paste_as_plain_text\n0104001a=string/autofill\n0104001b=string/config_helpPackageNameKey\n0104001c=string/config_helpPackageNameValue\n0104001d=string/config_helpIntentExtraKey\n0104001e=string/config_helpIntentNameKey\n0104001f=string/config_feedbackIntentExtraKey\n01040020=string/config_feedbackIntentNameKey\n01040021=string/config_defaultAssistant\n01040022=string/config_defaultBrowser\n01040023=string/config_defaultDialer\n01040024=string/config_defaultSms\n01040025=string/config_defaultCallRedirection\n01040026=string/config_defaultCallScreening\n01040027=string/config_systemGallery\n01040028=string/BaMmi\n01040029=string/CLIRDefaultOffNextCallOff\n0104002a=string/CLIRDefaultOffNextCallOn\n0104002b=string/CLIRDefaultOnNextCallOff\n0104002c=string/CLIRDefaultOnNextCallOn\n0104002d=string/CLIRPermanent\n0104002e=string/CfMmi\n0104002f=string/ClipMmi\n01040030=string/ClirMmi\n01040031=string/CndMmi\n01040032=string/CnipMmi\n01040033=string/CnirMmi\n01040034=string/ColpMmi\n01040035=string/ColrMmi\n01040036=string/CwMmi\n01040037=string/DndMmi\n01040038=string/EmergencyCallWarningSummary\n01040039=string/EmergencyCallWarningTitle\n0104003a=string/Midnight\n0104003b=string/NetworkPreferenceSwitchSummary\n0104003c=string/NetworkPreferenceSwitchTitle\n0104003d=string/Noon\n0104003e=string/PERSOSUBSTATE_RUIM_CORPORATE_ENTRY\n0104003f=string/PERSOSUBSTATE_RUIM_CORPORATE_ERROR\n01040040=string/PERSOSUBSTATE_RUIM_CORPORATE_IN_PROGRESS\n01040041=string/PERSOSUBSTATE_RUIM_CORPORATE_PUK_ENTRY\n01040042=string/PERSOSUBSTATE_RUIM_CORPORATE_PUK_ERROR\n01040043=string/PERSOSUBSTATE_RUIM_CORPORATE_PUK_IN_PROGRESS\n01040044=string/PERSOSUBSTATE_RUIM_CORPORATE_PUK_SUCCESS\n01040045=string/PERSOSUBSTATE_RUIM_CORPORATE_SUCCESS\n01040046=string/PERSOSUBSTATE_RUIM_HRPD_ENTRY\n01040047=string/PERSOSUBSTATE_RUIM_HRPD_ERROR\n01040048=string/PERSOSUBSTATE_RUIM_HRPD_IN_PROGRESS\n01040049=string/PERSOSUBSTATE_RUIM_HRPD_PUK_ENTRY\n0104004a=string/PERSOSUBSTATE_RUIM_HRPD_PUK_ERROR\n0104004b=string/PERSOSUBSTATE_RUIM_HRPD_PUK_IN_PROGRESS\n0104004c=string/PERSOSUBSTATE_RUIM_HRPD_PUK_SUCCESS\n0104004d=string/PERSOSUBSTATE_RUIM_HRPD_SUCCESS\n0104004e=string/PERSOSUBSTATE_RUIM_NETWORK1_ENTRY\n0104004f=string/PERSOSUBSTATE_RUIM_NETWORK1_ERROR\n01040050=string/PERSOSUBSTATE_RUIM_NETWORK1_IN_PROGRESS\n01040051=string/PERSOSUBSTATE_RUIM_NETWORK1_PUK_ENTRY\n01040052=string/PERSOSUBSTATE_RUIM_NETWORK1_PUK_ERROR\n01040053=string/PERSOSUBSTATE_RUIM_NETWORK1_PUK_IN_PROGRESS\n01040054=string/PERSOSUBSTATE_RUIM_NETWORK1_PUK_SUCCESS\n01040055=string/PERSOSUBSTATE_RUIM_NETWORK1_SUCCESS\n01040056=string/PERSOSUBSTATE_RUIM_NETWORK2_ENTRY\n01040057=string/PERSOSUBSTATE_RUIM_NETWORK2_ERROR\n01040058=string/PERSOSUBSTATE_RUIM_NETWORK2_IN_PROGRESS\n01040059=string/PERSOSUBSTATE_RUIM_NETWORK2_PUK_ENTRY\n0104005a=string/PERSOSUBSTATE_RUIM_NETWORK2_PUK_ERROR\n0104005b=string/PERSOSUBSTATE_RUIM_NETWORK2_PUK_IN_PROGRESS\n0104005c=string/PERSOSUBSTATE_RUIM_NETWORK2_PUK_SUCCESS\n0104005d=string/PERSOSUBSTATE_RUIM_NETWORK2_SUCCESS\n0104005e=string/PERSOSUBSTATE_RUIM_RUIM_ENTRY\n0104005f=string/PERSOSUBSTATE_RUIM_RUIM_ERROR\n01040060=string/PERSOSUBSTATE_RUIM_RUIM_IN_PROGRESS\n01040061=string/PERSOSUBSTATE_RUIM_RUIM_PUK_ENTRY\n01040062=string/PERSOSUBSTATE_RUIM_RUIM_PUK_ERROR\n01040063=string/PERSOSUBSTATE_RUIM_RUIM_PUK_IN_PROGRESS\n01040064=string/PERSOSUBSTATE_RUIM_RUIM_PUK_SUCCESS\n01040065=string/PERSOSUBSTATE_RUIM_RUIM_SUCCESS\n01040066=string/PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ENTRY\n01040067=string/PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ERROR\n01040068=string/PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_IN_PROGRESS\n01040069=string/PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_ENTRY\n0104006a=string/PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_ERROR\n0104006b=string/PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_IN_PROGRESS\n0104006c=string/PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_SUCCESS\n0104006d=string/PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_SUCCESS\n0104006e=string/PERSOSUBSTATE_SIM_CORPORATE_ENTRY\n0104006f=string/PERSOSUBSTATE_SIM_CORPORATE_ERROR\n01040070=string/PERSOSUBSTATE_SIM_CORPORATE_IN_PROGRESS\n01040071=string/PERSOSUBSTATE_SIM_CORPORATE_PUK_ENTRY\n01040072=string/PERSOSUBSTATE_SIM_CORPORATE_PUK_ERROR\n01040073=string/PERSOSUBSTATE_SIM_CORPORATE_PUK_IN_PROGRESS\n01040074=string/PERSOSUBSTATE_SIM_CORPORATE_PUK_SUCCESS\n01040075=string/PERSOSUBSTATE_SIM_CORPORATE_SUCCESS\n01040076=string/PERSOSUBSTATE_SIM_ICCID_ENTRY\n01040077=string/PERSOSUBSTATE_SIM_ICCID_ERROR\n01040078=string/PERSOSUBSTATE_SIM_ICCID_IN_PROGRESS\n01040079=string/PERSOSUBSTATE_SIM_ICCID_SUCCESS\n0104007a=string/PERSOSUBSTATE_SIM_IMPI_ENTRY\n0104007b=string/PERSOSUBSTATE_SIM_IMPI_ERROR\n0104007c=string/PERSOSUBSTATE_SIM_IMPI_IN_PROGRESS\n0104007d=string/PERSOSUBSTATE_SIM_IMPI_SUCCESS\n0104007e=string/PERSOSUBSTATE_SIM_NETWORK_ENTRY\n0104007f=string/PERSOSUBSTATE_SIM_NETWORK_ERROR\n01040080=string/PERSOSUBSTATE_SIM_NETWORK_IN_PROGRESS\n01040081=string/PERSOSUBSTATE_SIM_NETWORK_PUK_ENTRY\n01040082=string/PERSOSUBSTATE_SIM_NETWORK_PUK_ERROR\n01040083=string/PERSOSUBSTATE_SIM_NETWORK_PUK_IN_PROGRESS\n01040084=string/PERSOSUBSTATE_SIM_NETWORK_PUK_SUCCESS\n01040085=string/PERSOSUBSTATE_SIM_NETWORK_SUBSET_ENTRY\n01040086=string/PERSOSUBSTATE_SIM_NETWORK_SUBSET_ERROR\n01040087=string/PERSOSUBSTATE_SIM_NETWORK_SUBSET_IN_PROGRESS\n01040088=string/PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_ENTRY\n01040089=string/PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_ERROR\n0104008a=string/PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_IN_PROGRESS\n0104008b=string/PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_SUCCESS\n0104008c=string/PERSOSUBSTATE_SIM_NETWORK_SUBSET_SUCCESS\n0104008d=string/PERSOSUBSTATE_SIM_NETWORK_SUCCESS\n0104008e=string/PERSOSUBSTATE_SIM_NS_SP_ENTRY\n0104008f=string/PERSOSUBSTATE_SIM_NS_SP_ERROR\n01040090=string/PERSOSUBSTATE_SIM_NS_SP_IN_PROGRESS\n01040091=string/PERSOSUBSTATE_SIM_NS_SP_SUCCESS\n01040092=string/PERSOSUBSTATE_SIM_SERVICE_PROVIDER_ENTRY\n01040093=string/PERSOSUBSTATE_SIM_SERVICE_PROVIDER_ERROR\n01040094=string/PERSOSUBSTATE_SIM_SERVICE_PROVIDER_IN_PROGRESS\n01040095=string/PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK_ENTRY\n01040096=string/PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK_ERROR\n01040097=string/PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK_IN_PROGRESS\n01040098=string/PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK_SUCCESS\n01040099=string/PERSOSUBSTATE_SIM_SERVICE_PROVIDER_SUCCESS\n0104009a=string/PERSOSUBSTATE_SIM_SIM_ENTRY\n0104009b=string/PERSOSUBSTATE_SIM_SIM_ERROR\n0104009c=string/PERSOSUBSTATE_SIM_SIM_IN_PROGRESS\n0104009d=string/PERSOSUBSTATE_SIM_SIM_PUK_ENTRY\n0104009e=string/PERSOSUBSTATE_SIM_SIM_PUK_ERROR\n0104009f=string/PERSOSUBSTATE_SIM_SIM_PUK_IN_PROGRESS\n010400a0=string/PERSOSUBSTATE_SIM_SIM_PUK_SUCCESS\n010400a1=string/PERSOSUBSTATE_SIM_SIM_SUCCESS\n010400a2=string/PERSOSUBSTATE_SIM_SPN_ENTRY\n010400a3=string/PERSOSUBSTATE_SIM_SPN_ERROR\n010400a4=string/PERSOSUBSTATE_SIM_SPN_IN_PROGRESS\n010400a5=string/PERSOSUBSTATE_SIM_SPN_SUCCESS\n010400a6=string/PERSOSUBSTATE_SIM_SP_EHPLMN_ENTRY\n010400a7=string/PERSOSUBSTATE_SIM_SP_EHPLMN_ERROR\n010400a8=string/PERSOSUBSTATE_SIM_SP_EHPLMN_IN_PROGRESS\n010400a9=string/PERSOSUBSTATE_SIM_SP_EHPLMN_SUCCESS\n010400aa=string/PinMmi\n010400ab=string/PwdMmi\n010400ac=string/RestrictedOnAllVoiceTitle\n010400ad=string/RestrictedOnDataTitle\n010400ae=string/RestrictedOnEmergencyTitle\n010400af=string/RestrictedOnNormalTitle\n010400b0=string/RestrictedStateContent\n010400b1=string/RestrictedStateContentMsimTemplate\n010400b2=string/RuacMmi\n010400b3=string/SetupCallDefault\n010400b4=string/ThreeWCMmi\n010400b5=string/accept\n010400b6=string/accessibility_binding_label\n010400b7=string/accessibility_button_instructional_text\n010400b8=string/accessibility_button_prompt_text\n010400b9=string/accessibility_dialog_button_allow\n010400ba=string/accessibility_dialog_button_deny\n010400bb=string/accessibility_edit_shortcut_menu_button_title\n010400bc=string/accessibility_edit_shortcut_menu_volume_title\n010400bd=string/accessibility_enable_service_encryption_warning\n010400be=string/accessibility_enable_service_title\n010400bf=string/accessibility_freeform_caption\n010400c0=string/accessibility_gesture_3finger_instructional_text\n010400c1=string/accessibility_gesture_3finger_prompt_text\n010400c2=string/accessibility_gesture_instructional_text\n010400c3=string/accessibility_gesture_prompt_text\n010400c4=string/accessibility_magnification_chooser_text\n010400c5=string/accessibility_select_shortcut_menu_title\n010400c6=string/accessibility_service_action_perform_description\n010400c7=string/accessibility_service_action_perform_title\n010400c8=string/accessibility_service_screen_control_description\n010400c9=string/accessibility_service_screen_control_title\n010400ca=string/accessibility_service_warning_description\n010400cb=string/accessibility_shortcut_disabling_service\n010400cc=string/accessibility_shortcut_enabling_service\n010400cd=string/accessibility_shortcut_menu_item_status_off\n010400ce=string/accessibility_shortcut_menu_item_status_on\n010400cf=string/accessibility_shortcut_multiple_service_list\n010400d0=string/accessibility_shortcut_multiple_service_warning\n010400d1=string/accessibility_shortcut_multiple_service_warning_title\n010400d2=string/accessibility_shortcut_off\n010400d3=string/accessibility_shortcut_on\n010400d4=string/accessibility_shortcut_single_service_warning\n010400d5=string/accessibility_shortcut_single_service_warning_title\n010400d6=string/accessibility_shortcut_spoken_feedback\n010400d7=string/accessibility_shortcut_toogle_warning\n010400d8=string/accessibility_shortcut_warning_dialog_title\n010400d9=string/accessibility_system_action_back_label\n010400da=string/accessibility_system_action_hardware_a11y_shortcut_label\n010400db=string/accessibility_system_action_home_label\n010400dc=string/accessibility_system_action_lock_screen_label\n010400dd=string/accessibility_system_action_notifications_label\n010400de=string/accessibility_system_action_on_screen_a11y_shortcut_chooser_label\n010400df=string/accessibility_system_action_on_screen_a11y_shortcut_label\n010400e0=string/accessibility_system_action_power_dialog_label\n010400e1=string/accessibility_system_action_quick_settings_label\n010400e2=string/accessibility_system_action_recents_label\n010400e3=string/accessibility_system_action_screenshot_label\n010400e4=string/accessibility_uncheck_legacy_item_warning\n010400e5=string/action_bar_home_description\n010400e6=string/action_bar_home_description_format\n010400e7=string/action_bar_home_subtitle_description_format\n010400e8=string/action_bar_up_description\n010400e9=string/action_menu_overflow_description\n010400ea=string/action_mode_done\n010400eb=string/activity_chooser_view_dialog_title_default\n010400ec=string/activity_chooser_view_see_all\n010400ed=string/activity_list_empty\n010400ee=string/activity_resolver_use_always\n010400ef=string/activity_resolver_use_once\n010400f0=string/activity_resolver_work_profiles_support\n010400f1=string/activitychooserview_choose_application\n010400f2=string/activitychooserview_choose_application_error\n010400f3=string/adb_active_notification_message\n010400f4=string/adb_active_notification_title\n010400f5=string/adb_debugging_notification_channel_tv\n010400f6=string/adbwifi_active_notification_message\n010400f7=string/adbwifi_active_notification_title\n010400f8=string/addToDictionary\n010400f9=string/add_account_button_label\n010400fa=string/add_account_label\n010400fb=string/aerr_application\n010400fc=string/aerr_application_repeated\n010400fd=string/aerr_close\n010400fe=string/aerr_close_app\n010400ff=string/aerr_mute\n01040100=string/aerr_process\n01040101=string/aerr_process_repeated\n01040102=string/aerr_report\n01040103=string/aerr_restart\n01040104=string/aerr_wait\n01040105=string/alert_windows_notification_channel_group_name\n01040106=string/alert_windows_notification_channel_name\n01040107=string/alert_windows_notification_message\n01040108=string/alert_windows_notification_title\n01040109=string/alert_windows_notification_turn_off_action\n0104010a=string/allow\n0104010b=string/allow_while_in_use_permission_in_fgs\n0104010c=string/alternate_eri_file\n0104010d=string/alwaysUse\n0104010e=string/android_preparing_apk\n0104010f=string/android_start_title\n01040110=string/android_system_label\n01040111=string/android_upgrading_apk\n01040112=string/android_upgrading_complete\n01040113=string/android_upgrading_fstrim\n01040114=string/android_upgrading_notification_title\n01040115=string/android_upgrading_starting_apps\n01040116=string/android_upgrading_title\n01040117=string/anr_activity_application\n01040118=string/anr_activity_process\n01040119=string/anr_application_process\n0104011a=string/anr_process\n0104011b=string/anr_title\n0104011c=string/app_blocked_message\n0104011d=string/app_blocked_title\n0104011e=string/app_category_audio\n0104011f=string/app_category_game\n01040120=string/app_category_image\n01040121=string/app_category_maps\n01040122=string/app_category_news\n01040123=string/app_category_productivity\n01040124=string/app_category_social\n01040125=string/app_category_video\n01040126=string/app_info\n01040127=string/app_not_found\n01040128=string/app_running_notification_text\n01040129=string/app_running_notification_title\n0104012a=string/app_suspended_default_message\n0104012b=string/app_suspended_more_details\n0104012c=string/app_suspended_title\n0104012d=string/app_suspended_unsuspend_message\n0104012e=string/app_upgrading_toast\n0104012f=string/as_app_forced_to_restricted_bucket\n01040130=string/autofill_address_line_1_label_re\n01040131=string/autofill_address_line_1_re\n01040132=string/autofill_address_line_2_re\n01040133=string/autofill_address_line_3_re\n01040134=string/autofill_address_name_separator\n01040135=string/autofill_address_summary_format\n01040136=string/autofill_address_summary_name_format\n01040137=string/autofill_address_summary_separator\n01040138=string/autofill_address_type_same_as_re\n01040139=string/autofill_address_type_use_my_re\n0104013a=string/autofill_area\n0104013b=string/autofill_area_code_notext_re\n0104013c=string/autofill_area_code_re\n0104013d=string/autofill_attention_ignored_re\n0104013e=string/autofill_billing_designator_re\n0104013f=string/autofill_card_cvc_re\n01040140=string/autofill_card_ignored_re\n01040141=string/autofill_card_number_re\n01040142=string/autofill_city_re\n01040143=string/autofill_company_re\n01040144=string/autofill_continue_yes\n01040145=string/autofill_country_code_re\n01040146=string/autofill_country_re\n01040147=string/autofill_county\n01040148=string/autofill_department\n01040149=string/autofill_district\n0104014a=string/autofill_email_re\n0104014b=string/autofill_emirate\n0104014c=string/autofill_error_cannot_autofill\n0104014d=string/autofill_expiration_date_re\n0104014e=string/autofill_expiration_month_re\n0104014f=string/autofill_fax_re\n01040150=string/autofill_first_name_re\n01040151=string/autofill_island\n01040152=string/autofill_last_name_re\n01040153=string/autofill_middle_initial_re\n01040154=string/autofill_middle_name_re\n01040155=string/autofill_name_on_card_contextual_re\n01040156=string/autofill_name_on_card_re\n01040157=string/autofill_name_re\n01040158=string/autofill_name_specific_re\n01040159=string/autofill_parish\n0104015a=string/autofill_phone_extension_re\n0104015b=string/autofill_phone_prefix_re\n0104015c=string/autofill_phone_prefix_separator_re\n0104015d=string/autofill_phone_re\n0104015e=string/autofill_phone_suffix_re\n0104015f=string/autofill_phone_suffix_separator_re\n01040160=string/autofill_picker_accessibility_title\n01040161=string/autofill_picker_no_suggestions\n01040162=string/autofill_postal_code\n01040163=string/autofill_prefecture\n01040164=string/autofill_province\n01040165=string/autofill_region_ignored_re\n01040166=string/autofill_save_accessibility_title\n01040167=string/autofill_save_never\n01040168=string/autofill_save_no\n01040169=string/autofill_save_notnow\n0104016a=string/autofill_save_title\n0104016b=string/autofill_save_title_with_2types\n0104016c=string/autofill_save_title_with_3types\n0104016d=string/autofill_save_title_with_type\n0104016e=string/autofill_save_type_address\n0104016f=string/autofill_save_type_credit_card\n01040170=string/autofill_save_type_debit_card\n01040171=string/autofill_save_type_email_address\n01040172=string/autofill_save_type_generic_card\n01040173=string/autofill_save_type_password\n01040174=string/autofill_save_type_payment_card\n01040175=string/autofill_save_type_username\n01040176=string/autofill_save_yes\n01040177=string/autofill_shipping_designator_re\n01040178=string/autofill_state\n01040179=string/autofill_state_re\n0104017a=string/autofill_this_form\n0104017b=string/autofill_update_title\n0104017c=string/autofill_update_title_with_2types\n0104017d=string/autofill_update_title_with_3types\n0104017e=string/autofill_update_title_with_type\n0104017f=string/autofill_update_yes\n01040180=string/autofill_username_re\n01040181=string/autofill_window_title\n01040182=string/autofill_zip_4_re\n01040183=string/autofill_zip_code\n01040184=string/autofill_zip_code_re\n01040185=string/back_button_label\n01040186=string/badPin\n01040187=string/badPuk\n01040188=string/battery_saver_charged_notification_summary\n01040189=string/battery_saver_description\n0104018a=string/battery_saver_description_with_learn_more\n0104018b=string/battery_saver_notification_channel_name\n0104018c=string/battery_saver_off_notification_title\n0104018d=string/beforeOneMonthDurationPast\n0104018e=string/biometric_dialog_default_title\n0104018f=string/biometric_error_canceled\n01040190=string/biometric_error_device_not_secured\n01040191=string/biometric_error_hw_unavailable\n01040192=string/biometric_error_user_canceled\n01040193=string/biometric_not_recognized\n01040194=string/bluetooth_a2dp_audio_route_name\n01040195=string/bluetooth_airplane_mode_toast\n01040196=string/bugreport_message\n01040197=string/bugreport_option_full_summary\n01040198=string/bugreport_option_full_title\n01040199=string/bugreport_option_interactive_summary\n0104019a=string/bugreport_option_interactive_title\n0104019b=string/bugreport_screenshot_failure_toast\n0104019c=string/bugreport_screenshot_success_toast\n0104019d=string/bugreport_status\n0104019e=string/bugreport_title\n0104019f=string/byteShort\n010401a0=string/candidates_style\n010401a1=string/capability_desc_canCaptureFingerprintGestures\n010401a2=string/capability_desc_canControlMagnification\n010401a3=string/capability_desc_canPerformGestures\n010401a4=string/capability_desc_canRequestFilterKeyEvents\n010401a5=string/capability_desc_canRequestTouchExploration\n010401a6=string/capability_desc_canRetrieveWindowContent\n010401a7=string/capability_desc_canTakeScreenshot\n010401a8=string/capability_title_canCaptureFingerprintGestures\n010401a9=string/capability_title_canControlMagnification\n010401aa=string/capability_title_canPerformGestures\n010401ab=string/capability_title_canRequestFilterKeyEvents\n010401ac=string/capability_title_canRequestTouchExploration\n010401ad=string/capability_title_canRetrieveWindowContent\n010401ae=string/capability_title_canTakeScreenshot\n010401af=string/capital_off\n010401b0=string/capital_on\n010401b1=string/car_loading_profile\n010401b2=string/car_mode_disable_notification_message\n010401b3=string/car_mode_disable_notification_title\n010401b4=string/carrier_app_notification_text\n010401b5=string/carrier_app_notification_title\n010401b6=string/cfTemplateForwarded\n010401b7=string/cfTemplateForwardedTime\n010401b8=string/cfTemplateNotForwarded\n010401b9=string/cfTemplateRegistered\n010401ba=string/cfTemplateRegisteredTime\n010401bb=string/checked\n010401bc=string/chooseActivity\n010401bd=string/chooseUsbActivity\n010401be=string/choose_account_label\n010401bf=string/chooser_all_apps_button_label\n010401c0=string/chooser_no_direct_share_targets\n010401c1=string/chooser_wallpaper\n010401c2=string/clearDefaultHintMsg\n010401c3=string/close_button_text\n010401c4=string/color_correction_feature_name\n010401c5=string/color_inversion_feature_name\n010401c6=string/common_last_name_prefixes\n010401c7=string/common_name\n010401c8=string/common_name_conjunctions\n010401c9=string/common_name_prefixes\n010401ca=string/common_name_suffixes\n010401cb=string/condition_provider_service_binding_label\n010401cc=string/conference_call\n010401cd=string/config_UsbDeviceConnectionHandling_component\n010401ce=string/config_activityRecognitionHardwarePackageName\n010401cf=string/config_appsAuthorizedForSharedAccounts\n010401d0=string/config_appsNotReportingCrashes\n010401d1=string/config_bandwidthEstimateSource\n010401d2=string/config_batterySaverDeviceSpecificConfig\n010401d3=string/config_batterySaverScheduleProvider\n010401d4=string/config_batterymeterBoltPath\n010401d5=string/config_batterymeterErrorPerimeterPath\n010401d6=string/config_batterymeterFillMask\n010401d7=string/config_batterymeterPerimeterPath\n010401d8=string/config_batterymeterPowersavePath\n010401d9=string/config_bodyFontFamily\n010401da=string/config_bodyFontFamilyMedium\n010401db=string/config_cameraLaunchGestureSensorStringType\n010401dc=string/config_cameraLiftTriggerSensorStringType\n010401dd=string/config_carrierAppInstallDialogComponent\n010401de=string/config_chooseAccountActivity\n010401df=string/config_chooseTypeAndAccountActivity\n010401e0=string/config_chooserActivity\n010401e1=string/config_companionDeviceManagerPackage\n010401e2=string/config_controlsPackage\n010401e3=string/config_customAdbPublicKeyConfirmationComponent\n010401e4=string/config_customAdbPublicKeyConfirmationSecondaryUserComponent\n010401e5=string/config_customAdbWifiNetworkConfirmationComponent\n010401e6=string/config_customAdbWifiNetworkConfirmationSecondaryUserComponent\n010401e7=string/config_customCountryDetector\n010401e8=string/config_customMediaKeyDispatcher\n010401e9=string/config_customResolverActivity\n010401ea=string/config_customSessionPolicyProvider\n010401eb=string/config_customVpnAlwaysOnDisconnectedDialogComponent\n010401ec=string/config_customVpnConfirmDialogComponent\n010401ed=string/config_dataUsageSummaryComponent\n010401ee=string/config_datause_iface\n010401ef=string/config_defaultAccessibilityService\n010401f0=string/config_defaultAppPredictionService\n010401f1=string/config_defaultAssistantAccessComponent\n010401f2=string/config_defaultAttentionService\n010401f3=string/config_defaultAugmentedAutofillService\n010401f4=string/config_defaultAutofillService\n010401f5=string/config_defaultBugReportHandlerApp\n010401f6=string/config_defaultContentCaptureService\n010401f7=string/config_defaultContentSuggestionsService\n010401f8=string/config_defaultDndAccessPackages\n010401f9=string/config_defaultListenerAccessPackages\n010401fa=string/config_defaultModuleMetadataProvider\n010401fb=string/config_defaultNearbySharingComponent\n010401fc=string/config_defaultNetworkRecommendationProviderPackage\n010401fd=string/config_defaultNetworkScorerPackageName\n010401fe=string/config_defaultPictureInPictureScreenEdgeInsets\n010401ff=string/config_defaultSupervisionProfileOwnerComponent\n01040200=string/config_defaultSystemCaptionsManagerService\n01040201=string/config_defaultSystemCaptionsService\n01040202=string/config_defaultTextClassifierPackage\n01040203=string/config_defaultTrustAgent\n01040204=string/config_defaultWellbeingPackage\n01040205=string/config_default_dns_server\n01040206=string/config_deviceConfiguratorPackageName\n01040207=string/config_deviceProvisioningPackage\n01040208=string/config_deviceSpecificAudioService\n01040209=string/config_deviceSpecificDevicePolicyManagerService\n0104020a=string/config_deviceSpecificDisplayAreaPolicyProvider\n0104020b=string/config_displayLightSensorType\n0104020c=string/config_displayWhiteBalanceColorTemperatureSensorName\n0104020d=string/config_doubleTouchGestureEnableFile\n0104020e=string/config_dozeComponent\n0104020f=string/config_dozeDoubleTapSensorType\n01040210=string/config_dozeLongPressSensorType\n01040211=string/config_dozeTapSensorType\n01040212=string/config_dreamsDefaultComponent\n01040213=string/config_emergency_call_number\n01040214=string/config_emergency_dialer_package\n01040215=string/config_ethernet_iface_regex\n01040216=string/config_ethernet_tcp_buffers\n01040217=string/config_factoryResetPackage\n01040218=string/config_foldedArea\n01040219=string/config_forceVoiceInteractionServicePackage\n0104021a=string/config_fusedLocationProviderPackageName\n0104021b=string/config_geocoderProviderPackageName\n0104021c=string/config_geofenceProviderPackageName\n0104021d=string/config_headlineFontFamily\n0104021e=string/config_headlineFontFamilyMedium\n0104021f=string/config_headlineFontFeatureSettings\n01040220=string/config_iccHotswapPromptForRestartDialogComponent\n01040221=string/config_icon_mask\n01040222=string/config_inCallNotificationSound\n01040223=string/config_incidentReportApproverPackage\n01040224=string/config_inputEventCompatProcessorOverrideClassName\n01040225=string/config_isoImagePath\n01040226=string/config_keyguardComponent\n01040227=string/config_mainBuiltInDisplayCutout\n01040228=string/config_mainBuiltInDisplayCutoutRectApproximation\n01040229=string/config_managed_provisioning_package\n0104022a=string/config_mediaProjectionPermissionDialogComponent\n0104022b=string/config_misprovisionedBrandValue\n0104022c=string/config_misprovisionedDeviceModel\n0104022d=string/config_mms_user_agent\n0104022e=string/config_mms_user_agent_profile_url\n0104022f=string/config_mobile_hotspot_provision_app_no_ui\n01040230=string/config_mobile_hotspot_provision_response\n01040231=string/config_networkCaptivePortalServerUrl\n01040232=string/config_networkLocationProviderPackageName\n01040233=string/config_networkOverLimitComponent\n01040234=string/config_networkPolicyNotificationComponent\n01040235=string/config_ntpServer\n01040236=string/config_overrideComponentUiPackage\n01040237=string/config_packagedKeyboardName\n01040238=string/config_pdp_reject_dialog_title\n01040239=string/config_pdp_reject_multi_conn_to_same_pdn_not_allowed\n0104023a=string/config_pdp_reject_service_not_subscribed\n0104023b=string/config_pdp_reject_user_authentication_failed\n0104023c=string/config_persistentDataPackageName\n0104023d=string/config_platformVpnConfirmDialogComponent\n0104023e=string/config_powerSaveModeChangedListenerPackage\n0104023f=string/config_qualified_networks_service_class\n01040240=string/config_qualified_networks_service_package\n01040241=string/config_radio_access_family\n01040242=string/config_rawContactsLocalAccountName\n01040243=string/config_rawContactsLocalAccountType\n01040244=string/config_recentsComponentName\n01040245=string/config_retailDemoPackage\n01040246=string/config_retailDemoPackageSignature\n01040247=string/config_screenRecorderComponent\n01040248=string/config_screenshotErrorReceiverComponent\n01040249=string/config_screenshotServiceComponent\n0104024a=string/config_secondaryHomePackage\n0104024b=string/config_servicesExtensionPackage\n0104024c=string/config_signalXPath\n0104024d=string/config_slicePermissionComponent\n0104024e=string/config_somnambulatorComponent\n0104024f=string/config_systemUIServiceComponent\n01040250=string/config_timeZoneRulesDataPackage\n01040251=string/config_timeZoneRulesUpdaterPackage\n01040252=string/config_tvRemoteServicePackage\n01040253=string/config_usbAccessoryUriActivity\n01040254=string/config_usbConfirmActivity\n01040255=string/config_usbContaminantActivity\n01040256=string/config_usbPermissionActivity\n01040257=string/config_usbResolverActivity\n01040258=string/config_useragentprofile_url\n01040259=string/config_wallpaperCropperPackage\n0104025a=string/config_wallpaperManagerServiceName\n0104025b=string/config_wifi_tether_enable\n0104025c=string/config_wimaxManagerClassname\n0104025d=string/config_wimaxNativeLibLocation\n0104025e=string/config_wimaxServiceClassname\n0104025f=string/config_wimaxServiceJarLocation\n01040260=string/config_wimaxStateTrackerClassname\n01040261=string/config_wlan_data_service_class\n01040262=string/config_wlan_data_service_package\n01040263=string/config_wlan_network_service_class\n01040264=string/config_wlan_network_service_package\n01040265=string/config_wwan_data_service_class\n01040266=string/config_wwan_data_service_package\n01040267=string/config_wwan_network_service_class\n01040268=string/config_wwan_network_service_package\n01040269=string/confirm_battery_saver\n0104026a=string/console_running_notification_message\n0104026b=string/console_running_notification_title\n0104026c=string/contentServiceSync\n0104026d=string/contentServiceSyncNotificationTitle\n0104026e=string/contentServiceTooManyDeletesNotificationDesc\n0104026f=string/content_description_sliding_handle\n01040270=string/conversation_single_line_image_placeholder\n01040271=string/conversation_single_line_name_display\n01040272=string/conversation_title_fallback_group_chat\n01040273=string/conversation_title_fallback_one_to_one\n01040274=string/copied\n01040275=string/country_detector\n01040276=string/country_selection_title\n01040277=string/create_contact_using\n01040278=string/data_saver_description\n01040279=string/data_saver_enable_button\n0104027a=string/data_saver_enable_title\n0104027b=string/data_usage_limit_body\n0104027c=string/data_usage_limit_snoozed_body\n0104027d=string/data_usage_mobile_limit_snoozed_title\n0104027e=string/data_usage_mobile_limit_title\n0104027f=string/data_usage_rapid_app_body\n01040280=string/data_usage_rapid_body\n01040281=string/data_usage_rapid_title\n01040282=string/data_usage_restricted_body\n01040283=string/data_usage_restricted_title\n01040284=string/data_usage_warning_body\n01040285=string/data_usage_warning_title\n01040286=string/data_usage_wifi_limit_snoozed_title\n01040287=string/data_usage_wifi_limit_title\n01040288=string/date_and_time\n01040289=string/date_picker_day_of_week_typeface\n0104028a=string/date_picker_day_typeface\n0104028b=string/date_picker_decrement_day_button\n0104028c=string/date_picker_decrement_month_button\n0104028d=string/date_picker_decrement_year_button\n0104028e=string/date_picker_dialog_title\n0104028f=string/date_picker_increment_day_button\n01040290=string/date_picker_increment_month_button\n01040291=string/date_picker_increment_year_button\n01040292=string/date_picker_mode\n01040293=string/date_picker_month_typeface\n01040294=string/date_picker_next_month_button\n01040295=string/date_picker_prev_month_button\n01040296=string/date_time\n01040297=string/date_time_done\n01040298=string/date_time_set\n01040299=string/day\n0104029a=string/days\n0104029b=string/db_default_journal_mode\n0104029c=string/db_default_sync_mode\n0104029d=string/db_wal_sync_mode\n0104029e=string/decline\n0104029f=string/decline_remote_bugreport_action\n010402a0=string/default_audio_route_category_name\n010402a1=string/default_audio_route_name\n010402a2=string/default_audio_route_name_dock_speakers\n010402a3=string/default_audio_route_name_hdmi\n010402a4=string/default_audio_route_name_headphones\n010402a5=string/default_audio_route_name_usb\n010402a6=string/default_browser\n010402a7=string/default_notification_channel_label\n010402a8=string/default_sms_application\n010402a9=string/default_wallpaper_component\n010402aa=string/delete\n010402ab=string/deleteText\n010402ac=string/deleted_key\n010402ad=string/demo_restarting_message\n010402ae=string/demo_starting_message\n010402af=string/deny\n010402b0=string/deprecated_target_sdk_app_store\n010402b1=string/deprecated_target_sdk_message\n010402b2=string/description_target_unlock_tablet\n010402b3=string/device_ownership_relinquished\n010402b4=string/device_storage_monitor_notification_channel\n010402b5=string/dial_number_using\n010402b6=string/disable_accessibility_shortcut\n010402b7=string/display_manager_built_in_display_name\n010402b8=string/display_manager_hdmi_display_name\n010402b9=string/display_manager_overlay_display_name\n010402ba=string/display_manager_overlay_display_secure_suffix\n010402bb=string/display_manager_overlay_display_title\n010402bc=string/dlg_ok\n010402bd=string/done_accessibility_shortcut_menu_button\n010402be=string/done_label\n010402bf=string/double_tap_toast\n010402c0=string/dump_heap_notification\n010402c1=string/dump_heap_notification_detail\n010402c2=string/dump_heap_ready_notification\n010402c3=string/dump_heap_ready_text\n010402c4=string/dump_heap_system_text\n010402c5=string/dump_heap_text\n010402c6=string/dump_heap_title\n010402c7=string/dynamic_mode_notification_channel_name\n010402c8=string/dynamic_mode_notification_summary\n010402c9=string/dynamic_mode_notification_title\n010402ca=string/editTextMenuTitle\n010402cb=string/edit_accessibility_shortcut_menu_button\n010402cc=string/elapsed_time_short_format_h_mm_ss\n010402cd=string/elapsed_time_short_format_mm_ss\n010402ce=string/emailTypeCustom\n010402cf=string/emailTypeHome\n010402d0=string/emailTypeMobile\n010402d1=string/emailTypeOther\n010402d2=string/emailTypeWork\n010402d3=string/emergency_call_dialog_number_for_display\n010402d4=string/emergency_calls_only\n010402d5=string/enablePin\n010402d6=string/enable_explore_by_touch_warning_message\n010402d7=string/enable_explore_by_touch_warning_title\n010402d8=string/error_message_change_not_allowed\n010402d9=string/error_message_title\n010402da=string/etws_primary_default_message_earthquake\n010402db=string/etws_primary_default_message_earthquake_and_tsunami\n010402dc=string/etws_primary_default_message_others\n010402dd=string/etws_primary_default_message_test\n010402de=string/etws_primary_default_message_tsunami\n010402df=string/eventTypeAnniversary\n010402e0=string/eventTypeBirthday\n010402e1=string/eventTypeCustom\n010402e2=string/eventTypeOther\n010402e3=string/expand_action_accessibility\n010402e4=string/expand_button_content_description_collapsed\n010402e5=string/expand_button_content_description_expanded\n010402e6=string/expires_on\n010402e7=string/ext_media_badremoval_notification_message\n010402e8=string/ext_media_badremoval_notification_title\n010402e9=string/ext_media_browse_action\n010402ea=string/ext_media_checking_notification_message\n010402eb=string/ext_media_checking_notification_title\n010402ec=string/ext_media_init_action\n010402ed=string/ext_media_missing_message\n010402ee=string/ext_media_missing_title\n010402ef=string/ext_media_move_failure_message\n010402f0=string/ext_media_move_failure_title\n010402f1=string/ext_media_move_specific_title\n010402f2=string/ext_media_move_success_message\n010402f3=string/ext_media_move_success_title\n010402f4=string/ext_media_move_title\n010402f5=string/ext_media_new_notification_message\n010402f6=string/ext_media_new_notification_title\n010402f7=string/ext_media_nomedia_notification_message\n010402f8=string/ext_media_nomedia_notification_title\n010402f9=string/ext_media_ready_notification_message\n010402fa=string/ext_media_seamless_action\n010402fb=string/ext_media_status_bad_removal\n010402fc=string/ext_media_status_checking\n010402fd=string/ext_media_status_ejecting\n010402fe=string/ext_media_status_formatting\n010402ff=string/ext_media_status_missing\n01040300=string/ext_media_status_mounted\n01040301=string/ext_media_status_mounted_ro\n01040302=string/ext_media_status_removed\n01040303=string/ext_media_status_unmountable\n01040304=string/ext_media_status_unmounted\n01040305=string/ext_media_status_unsupported\n01040306=string/ext_media_unmount_action\n01040307=string/ext_media_unmountable_notification_message\n01040308=string/ext_media_unmountable_notification_title\n01040309=string/ext_media_unmounting_notification_message\n0104030a=string/ext_media_unmounting_notification_title\n0104030b=string/ext_media_unsupported_notification_message\n0104030c=string/ext_media_unsupported_notification_title\n0104030d=string/extract_edit_menu_button\n0104030e=string/face_acquired_insufficient\n0104030f=string/face_acquired_not_detected\n01040310=string/face_acquired_obscured\n01040311=string/face_acquired_pan_too_extreme\n01040312=string/face_acquired_poor_gaze\n01040313=string/face_acquired_recalibrate\n01040314=string/face_acquired_roll_too_extreme\n01040315=string/face_acquired_sensor_dirty\n01040316=string/face_acquired_tilt_too_extreme\n01040317=string/face_acquired_too_bright\n01040318=string/face_acquired_too_close\n01040319=string/face_acquired_too_dark\n0104031a=string/face_acquired_too_different\n0104031b=string/face_acquired_too_far\n0104031c=string/face_acquired_too_high\n0104031d=string/face_acquired_too_left\n0104031e=string/face_acquired_too_low\n0104031f=string/face_acquired_too_much_motion\n01040320=string/face_acquired_too_right\n01040321=string/face_acquired_too_similar\n01040322=string/face_authenticated_confirmation_required\n01040323=string/face_authenticated_no_confirmation_required\n01040324=string/face_error_canceled\n01040325=string/face_error_hw_not_available\n01040326=string/face_error_hw_not_present\n01040327=string/face_error_lockout\n01040328=string/face_error_lockout_permanent\n01040329=string/face_error_no_space\n0104032a=string/face_error_not_enrolled\n0104032b=string/face_error_security_update_required\n0104032c=string/face_error_timeout\n0104032d=string/face_error_unable_to_process\n0104032e=string/face_error_user_canceled\n0104032f=string/face_icon_content_description\n01040330=string/face_name_template\n01040331=string/face_recalibrate_notification_content\n01040332=string/face_recalibrate_notification_name\n01040333=string/face_recalibrate_notification_title\n01040334=string/faceunlock_multiple_failures\n01040335=string/factory_reset_message\n01040336=string/factory_reset_warning\n01040337=string/factorytest_failed\n01040338=string/factorytest_no_action\n01040339=string/factorytest_not_system\n0104033a=string/factorytest_reboot\n0104033b=string/failed_to_copy_to_clipboard\n0104033c=string/fast_scroll_alphabet\n0104033d=string/fast_scroll_numeric_alphabet\n0104033e=string/fcComplete\n0104033f=string/fcError\n01040340=string/fileSizeSuffix\n01040341=string/find\n01040342=string/find_next\n01040343=string/find_on_page\n01040344=string/find_previous\n01040345=string/fingerprint_acquired_imager_dirty\n01040346=string/fingerprint_acquired_insufficient\n01040347=string/fingerprint_acquired_partial\n01040348=string/fingerprint_acquired_too_fast\n01040349=string/fingerprint_acquired_too_slow\n0104034a=string/fingerprint_authenticated\n0104034b=string/fingerprint_error_canceled\n0104034c=string/fingerprint_error_hw_not_available\n0104034d=string/fingerprint_error_hw_not_present\n0104034e=string/fingerprint_error_lockout\n0104034f=string/fingerprint_error_lockout_permanent\n01040350=string/fingerprint_error_no_fingerprints\n01040351=string/fingerprint_error_no_space\n01040352=string/fingerprint_error_security_update_required\n01040353=string/fingerprint_error_timeout\n01040354=string/fingerprint_error_unable_to_process\n01040355=string/fingerprint_error_user_canceled\n01040356=string/fingerprint_name_template\n01040357=string/fingerprints\n01040358=string/floating_toolbar_close_overflow_description\n01040359=string/floating_toolbar_open_overflow_description\n0104035a=string/font_family_body_1_material\n0104035b=string/font_family_body_2_material\n0104035c=string/font_family_button_material\n0104035d=string/font_family_caption_material\n0104035e=string/font_family_display_1_material\n0104035f=string/font_family_display_2_material\n01040360=string/font_family_display_3_material\n01040361=string/font_family_display_4_material\n01040362=string/font_family_headline_material\n01040363=string/font_family_menu_material\n01040364=string/font_family_subhead_material\n01040365=string/font_family_title_material\n01040366=string/force_close\n01040367=string/foreground_service_app_in_background\n01040368=string/foreground_service_apps_in_background\n01040369=string/foreground_service_multiple_separator\n0104036a=string/foreground_service_tap_for_details\n0104036b=string/forward_intent_to_owner\n0104036c=string/forward_intent_to_work\n0104036d=string/gadget_host_error_inflating\n0104036e=string/gigabyteShort\n0104036f=string/global_action_assist\n01040370=string/global_action_bug_report\n01040371=string/global_action_emergency\n01040372=string/global_action_lock\n01040373=string/global_action_lockdown\n01040374=string/global_action_logout\n01040375=string/global_action_power_off\n01040376=string/global_action_power_options\n01040377=string/global_action_restart\n01040378=string/global_action_screenshot\n01040379=string/global_action_settings\n0104037a=string/global_action_silent_mode_off_status\n0104037b=string/global_action_silent_mode_on_status\n0104037c=string/global_action_toggle_silent_mode\n0104037d=string/global_action_voice_assist\n0104037e=string/global_actions\n0104037f=string/global_actions_airplane_mode_off_status\n01040380=string/global_actions_airplane_mode_on_status\n01040381=string/global_actions_toggle_airplane_mode\n01040382=string/gnss_nfw_notification_message_carrier\n01040383=string/gnss_nfw_notification_message_oem\n01040384=string/gnss_nfw_notification_title\n01040385=string/gpsNotifMessage\n01040386=string/gpsNotifTicker\n01040387=string/gpsNotifTitle\n01040388=string/gpsVerifNo\n01040389=string/gpsVerifYes\n0104038a=string/grant_credentials_permission_message_footer\n0104038b=string/grant_credentials_permission_message_header\n0104038c=string/grant_permissions_header_text\n0104038d=string/granularity_label_character\n0104038e=string/granularity_label_line\n0104038f=string/granularity_label_link\n01040390=string/granularity_label_word\n01040391=string/gsm_alphabet_default_charset\n01040392=string/hardware\n01040393=string/harmful_app_warning_open_anyway\n01040394=string/harmful_app_warning_title\n01040395=string/harmful_app_warning_uninstall\n01040396=string/heavy_weight_notification\n01040397=string/heavy_weight_notification_detail\n01040398=string/heavy_weight_switcher_text\n01040399=string/heavy_weight_switcher_title\n0104039a=string/hour\n0104039b=string/hour_picker_description\n0104039c=string/hours\n0104039d=string/httpError\n0104039e=string/httpErrorAuth\n0104039f=string/httpErrorConnect\n010403a0=string/httpErrorFailedSslHandshake\n010403a1=string/httpErrorFile\n010403a2=string/httpErrorFileNotFound\n010403a3=string/httpErrorIO\n010403a4=string/httpErrorLookup\n010403a5=string/httpErrorOk\n010403a6=string/httpErrorProxyAuth\n010403a7=string/httpErrorRedirectLoop\n010403a8=string/httpErrorTimeout\n010403a9=string/httpErrorTooManyRequests\n010403aa=string/httpErrorUnsupportedAuthScheme\n010403ab=string/icu_abbrev_wday_month_day_no_year\n010403ac=string/imProtocolAim\n010403ad=string/imProtocolCustom\n010403ae=string/imProtocolGoogleTalk\n010403af=string/imProtocolIcq\n010403b0=string/imProtocolJabber\n010403b1=string/imProtocolMsn\n010403b2=string/imProtocolNetMeeting\n010403b3=string/imProtocolQq\n010403b4=string/imProtocolSkype\n010403b5=string/imProtocolYahoo\n010403b6=string/imTypeCustom\n010403b7=string/imTypeHome\n010403b8=string/imTypeOther\n010403b9=string/imTypeWork\n010403ba=string/image_wallpaper_component\n010403bb=string/ime_action_default\n010403bc=string/ime_action_done\n010403bd=string/ime_action_go\n010403be=string/ime_action_next\n010403bf=string/ime_action_previous\n010403c0=string/ime_action_search\n010403c1=string/ime_action_send\n010403c2=string/imei\n010403c3=string/immersive_cling_description\n010403c4=string/immersive_cling_positive\n010403c5=string/immersive_cling_title\n010403c6=string/importance_from_person\n010403c7=string/importance_from_user\n010403c8=string/inputMethod\n010403c9=string/input_method_binding_label\n010403ca=string/install_carrier_app_notification_button\n010403cb=string/install_carrier_app_notification_text\n010403cc=string/install_carrier_app_notification_text_app_name\n010403cd=string/install_carrier_app_notification_title\n010403ce=string/invalidPin\n010403cf=string/invalidPuk\n010403d0=string/issued_by\n010403d1=string/issued_on\n010403d2=string/issued_to\n010403d3=string/js_dialog_before_unload\n010403d4=string/js_dialog_before_unload_negative_button\n010403d5=string/js_dialog_before_unload_positive_button\n010403d6=string/js_dialog_before_unload_title\n010403d7=string/js_dialog_title\n010403d8=string/js_dialog_title_default\n010403d9=string/keyboardview_keycode_alt\n010403da=string/keyboardview_keycode_cancel\n010403db=string/keyboardview_keycode_delete\n010403dc=string/keyboardview_keycode_done\n010403dd=string/keyboardview_keycode_enter\n010403de=string/keyboardview_keycode_mode_change\n010403df=string/keyboardview_keycode_shift\n010403e0=string/keygaurd_accessibility_media_controls\n010403e1=string/keyguard_accessibility_add_widget\n010403e2=string/keyguard_accessibility_camera\n010403e3=string/keyguard_accessibility_expand_lock_area\n010403e4=string/keyguard_accessibility_face_unlock\n010403e5=string/keyguard_accessibility_password_unlock\n010403e6=string/keyguard_accessibility_pattern_area\n010403e7=string/keyguard_accessibility_pattern_unlock\n010403e8=string/keyguard_accessibility_pin_unlock\n010403e9=string/keyguard_accessibility_sim_pin_unlock\n010403ea=string/keyguard_accessibility_sim_puk_unlock\n010403eb=string/keyguard_accessibility_slide_area\n010403ec=string/keyguard_accessibility_slide_unlock\n010403ed=string/keyguard_accessibility_status\n010403ee=string/keyguard_accessibility_unlock_area_collapsed\n010403ef=string/keyguard_accessibility_unlock_area_expanded\n010403f0=string/keyguard_accessibility_user_selector\n010403f1=string/keyguard_accessibility_widget\n010403f2=string/keyguard_accessibility_widget_changed\n010403f3=string/keyguard_accessibility_widget_deleted\n010403f4=string/keyguard_accessibility_widget_empty_slot\n010403f5=string/keyguard_accessibility_widget_reorder_end\n010403f6=string/keyguard_accessibility_widget_reorder_start\n010403f7=string/keyguard_label_text\n010403f8=string/keyguard_password_enter_password_code\n010403f9=string/keyguard_password_enter_pin_code\n010403fa=string/keyguard_password_enter_pin_password_code\n010403fb=string/keyguard_password_enter_pin_prompt\n010403fc=string/keyguard_password_enter_puk_code\n010403fd=string/keyguard_password_enter_puk_prompt\n010403fe=string/keyguard_password_entry_touch_hint\n010403ff=string/keyguard_password_wrong_pin_code\n01040400=string/kg_enter_confirm_pin_hint\n01040401=string/kg_failed_attempts_almost_at_login\n01040402=string/kg_failed_attempts_almost_at_wipe\n01040403=string/kg_failed_attempts_now_wiping\n01040404=string/kg_forgot_pattern_button_text\n01040405=string/kg_invalid_confirm_pin_hint\n01040406=string/kg_invalid_puk\n01040407=string/kg_invalid_sim_pin_hint\n01040408=string/kg_invalid_sim_puk_hint\n01040409=string/kg_login_account_recovery_hint\n0104040a=string/kg_login_checking_password\n0104040b=string/kg_login_instructions\n0104040c=string/kg_login_invalid_input\n0104040d=string/kg_login_password_hint\n0104040e=string/kg_login_submit_button\n0104040f=string/kg_login_too_many_attempts\n01040410=string/kg_login_username_hint\n01040411=string/kg_password_instructions\n01040412=string/kg_password_wrong_pin_code\n01040413=string/kg_pattern_instructions\n01040414=string/kg_pin_instructions\n01040415=string/kg_puk_enter_pin_hint\n01040416=string/kg_puk_enter_puk_hint\n01040417=string/kg_reordering_delete_drop_target_text\n01040418=string/kg_sim_pin_instructions\n01040419=string/kg_sim_unlock_progress_dialog_message\n0104041a=string/kg_text_message_separator\n0104041b=string/kg_too_many_failed_password_attempts_dialog_message\n0104041c=string/kg_too_many_failed_pattern_attempts_dialog_message\n0104041d=string/kg_too_many_failed_pin_attempts_dialog_message\n0104041e=string/kg_wrong_password\n0104041f=string/kg_wrong_pattern\n01040420=string/kg_wrong_pin\n01040421=string/kilobyteShort\n01040422=string/language_picker_section_all\n01040423=string/language_picker_section_suggested\n01040424=string/language_selection_title\n01040425=string/last_month\n01040426=string/launchBrowserDefault\n01040427=string/launch_warning_original\n01040428=string/launch_warning_replace\n01040429=string/launch_warning_title\n0104042a=string/leave_accessibility_shortcut_on\n0104042b=string/loading\n0104042c=string/locale_replacement\n0104042d=string/locale_search_menu\n0104042e=string/location_changed_notification_text\n0104042f=string/location_changed_notification_title\n01040430=string/location_service\n01040431=string/lock_pattern_view_aspect\n01040432=string/lock_to_app_unlock_password\n01040433=string/lock_to_app_unlock_pattern\n01040434=string/lock_to_app_unlock_pin\n01040435=string/lockscreen_access_pattern_area\n01040436=string/lockscreen_access_pattern_cell_added\n01040437=string/lockscreen_access_pattern_cell_added_verbose\n01040438=string/lockscreen_access_pattern_cleared\n01040439=string/lockscreen_access_pattern_detected\n0104043a=string/lockscreen_access_pattern_start\n0104043b=string/lockscreen_carrier_default\n0104043c=string/lockscreen_emergency_call\n0104043d=string/lockscreen_failed_attempts_almost_at_wipe\n0104043e=string/lockscreen_failed_attempts_almost_glogin\n0104043f=string/lockscreen_failed_attempts_now_wiping\n01040440=string/lockscreen_forgot_pattern_button_text\n01040441=string/lockscreen_glogin_account_recovery_hint\n01040442=string/lockscreen_glogin_checking_password\n01040443=string/lockscreen_glogin_forgot_pattern\n01040444=string/lockscreen_glogin_instructions\n01040445=string/lockscreen_glogin_invalid_input\n01040446=string/lockscreen_glogin_password_hint\n01040447=string/lockscreen_glogin_submit_button\n01040448=string/lockscreen_glogin_too_many_attempts\n01040449=string/lockscreen_glogin_username_hint\n0104044a=string/lockscreen_instructions_when_pattern_disabled\n0104044b=string/lockscreen_instructions_when_pattern_enabled\n0104044c=string/lockscreen_missing_sim_instructions\n0104044d=string/lockscreen_missing_sim_instructions_long\n0104044e=string/lockscreen_missing_sim_message\n0104044f=string/lockscreen_missing_sim_message_short\n01040450=string/lockscreen_network_locked_message\n01040451=string/lockscreen_password_wrong\n01040452=string/lockscreen_pattern_correct\n01040453=string/lockscreen_pattern_instructions\n01040454=string/lockscreen_pattern_wrong\n01040455=string/lockscreen_permanent_disabled_sim_instructions\n01040456=string/lockscreen_permanent_disabled_sim_message_short\n01040457=string/lockscreen_return_to_call\n01040458=string/lockscreen_screen_locked\n01040459=string/lockscreen_sim_locked_message\n0104045a=string/lockscreen_sim_puk_locked_instructions\n0104045b=string/lockscreen_sim_puk_locked_message\n0104045c=string/lockscreen_sim_unlock_progress_dialog_message\n0104045d=string/lockscreen_sound_off_label\n0104045e=string/lockscreen_sound_on_label\n0104045f=string/lockscreen_storage_locked\n01040460=string/lockscreen_too_many_failed_attempts_countdown\n01040461=string/lockscreen_too_many_failed_attempts_dialog_message\n01040462=string/lockscreen_too_many_failed_password_attempts_dialog_message\n01040463=string/lockscreen_too_many_failed_pin_attempts_dialog_message\n01040464=string/lockscreen_transport_ffw_description\n01040465=string/lockscreen_transport_next_description\n01040466=string/lockscreen_transport_pause_description\n01040467=string/lockscreen_transport_play_description\n01040468=string/lockscreen_transport_prev_description\n01040469=string/lockscreen_transport_rew_description\n0104046a=string/lockscreen_transport_stop_description\n0104046b=string/lockscreen_unlock_label\n0104046c=string/low_internal_storage_view_text\n0104046d=string/low_internal_storage_view_text_no_boot\n0104046e=string/low_internal_storage_view_title\n0104046f=string/low_memory\n01040470=string/managed_profile_label\n01040471=string/managed_profile_label_badge\n01040472=string/managed_profile_label_badge_2\n01040473=string/managed_profile_label_badge_3\n01040474=string/maximize_button_text\n01040475=string/me\n01040476=string/media_route_button_content_description\n01040477=string/media_route_chooser_extended_settings\n01040478=string/media_route_chooser_searching\n01040479=string/media_route_chooser_title\n0104047a=string/media_route_chooser_title_for_remote_display\n0104047b=string/media_route_controller_disconnect\n0104047c=string/media_route_status_available\n0104047d=string/media_route_status_connecting\n0104047e=string/media_route_status_in_use\n0104047f=string/media_route_status_not_available\n01040480=string/media_route_status_scanning\n01040481=string/mediasize_chinese_om_dai_pa_kai\n01040482=string/mediasize_chinese_om_jurro_ku_kai\n01040483=string/mediasize_chinese_om_pa_kai\n01040484=string/mediasize_chinese_prc_1\n01040485=string/mediasize_chinese_prc_10\n01040486=string/mediasize_chinese_prc_16k\n01040487=string/mediasize_chinese_prc_2\n01040488=string/mediasize_chinese_prc_3\n01040489=string/mediasize_chinese_prc_4\n0104048a=string/mediasize_chinese_prc_5\n0104048b=string/mediasize_chinese_prc_6\n0104048c=string/mediasize_chinese_prc_7\n0104048d=string/mediasize_chinese_prc_8\n0104048e=string/mediasize_chinese_prc_9\n0104048f=string/mediasize_chinese_roc_16k\n01040490=string/mediasize_chinese_roc_8k\n01040491=string/mediasize_iso_a0\n01040492=string/mediasize_iso_a1\n01040493=string/mediasize_iso_a10\n01040494=string/mediasize_iso_a2\n01040495=string/mediasize_iso_a3\n01040496=string/mediasize_iso_a4\n01040497=string/mediasize_iso_a5\n01040498=string/mediasize_iso_a6\n01040499=string/mediasize_iso_a7\n0104049a=string/mediasize_iso_a8\n0104049b=string/mediasize_iso_a9\n0104049c=string/mediasize_iso_b0\n0104049d=string/mediasize_iso_b1\n0104049e=string/mediasize_iso_b10\n0104049f=string/mediasize_iso_b2\n010404a0=string/mediasize_iso_b3\n010404a1=string/mediasize_iso_b4\n010404a2=string/mediasize_iso_b5\n010404a3=string/mediasize_iso_b6\n010404a4=string/mediasize_iso_b7\n010404a5=string/mediasize_iso_b8\n010404a6=string/mediasize_iso_b9\n010404a7=string/mediasize_iso_c0\n010404a8=string/mediasize_iso_c1\n010404a9=string/mediasize_iso_c10\n010404aa=string/mediasize_iso_c2\n010404ab=string/mediasize_iso_c3\n010404ac=string/mediasize_iso_c4\n010404ad=string/mediasize_iso_c5\n010404ae=string/mediasize_iso_c6\n010404af=string/mediasize_iso_c7\n010404b0=string/mediasize_iso_c8\n010404b1=string/mediasize_iso_c9\n010404b2=string/mediasize_japanese_chou2\n010404b3=string/mediasize_japanese_chou3\n010404b4=string/mediasize_japanese_chou4\n010404b5=string/mediasize_japanese_hagaki\n010404b6=string/mediasize_japanese_jis_b0\n010404b7=string/mediasize_japanese_jis_b1\n010404b8=string/mediasize_japanese_jis_b10\n010404b9=string/mediasize_japanese_jis_b2\n010404ba=string/mediasize_japanese_jis_b3\n010404bb=string/mediasize_japanese_jis_b4\n010404bc=string/mediasize_japanese_jis_b5\n010404bd=string/mediasize_japanese_jis_b6\n010404be=string/mediasize_japanese_jis_b7\n010404bf=string/mediasize_japanese_jis_b8\n010404c0=string/mediasize_japanese_jis_b9\n010404c1=string/mediasize_japanese_jis_exec\n010404c2=string/mediasize_japanese_kahu\n010404c3=string/mediasize_japanese_kaku2\n010404c4=string/mediasize_japanese_oufuku\n010404c5=string/mediasize_japanese_you4\n010404c6=string/mediasize_na_foolscap\n010404c7=string/mediasize_na_gvrnmt_letter\n010404c8=string/mediasize_na_index_3x5\n010404c9=string/mediasize_na_index_4x6\n010404ca=string/mediasize_na_index_5x8\n010404cb=string/mediasize_na_junior_legal\n010404cc=string/mediasize_na_ledger\n010404cd=string/mediasize_na_legal\n010404ce=string/mediasize_na_letter\n010404cf=string/mediasize_na_monarch\n010404d0=string/mediasize_na_quarto\n010404d1=string/mediasize_na_tabloid\n010404d2=string/mediasize_unknown_landscape\n010404d3=string/mediasize_unknown_portrait\n010404d4=string/megabyteShort\n010404d5=string/meid\n010404d6=string/menu_alt_shortcut_label\n010404d7=string/menu_ctrl_shortcut_label\n010404d8=string/menu_delete_shortcut_label\n010404d9=string/menu_enter_shortcut_label\n010404da=string/menu_function_shortcut_label\n010404db=string/menu_meta_shortcut_label\n010404dc=string/menu_shift_shortcut_label\n010404dd=string/menu_space_shortcut_label\n010404de=string/menu_sym_shortcut_label\n010404df=string/midnight\n010404e0=string/mime_type_apk\n010404e1=string/mime_type_audio\n010404e2=string/mime_type_audio_ext\n010404e3=string/mime_type_compressed\n010404e4=string/mime_type_compressed_ext\n010404e5=string/mime_type_document\n010404e6=string/mime_type_document_ext\n010404e7=string/mime_type_folder\n010404e8=string/mime_type_generic\n010404e9=string/mime_type_generic_ext\n010404ea=string/mime_type_image\n010404eb=string/mime_type_image_ext\n010404ec=string/mime_type_presentation\n010404ed=string/mime_type_presentation_ext\n010404ee=string/mime_type_spreadsheet\n010404ef=string/mime_type_spreadsheet_ext\n010404f0=string/mime_type_video\n010404f1=string/mime_type_video_ext\n010404f2=string/minute\n010404f3=string/minute_picker_description\n010404f4=string/minutes\n010404f5=string/mismatchPin\n010404f6=string/mmcc_authentication_reject\n010404f7=string/mmcc_authentication_reject_msim_template\n010404f8=string/mmcc_illegal_me\n010404f9=string/mmcc_illegal_me_msim_template\n010404fa=string/mmcc_illegal_ms\n010404fb=string/mmcc_illegal_ms_msim_template\n010404fc=string/mmcc_imsi_unknown_in_hlr\n010404fd=string/mmcc_imsi_unknown_in_hlr_msim_template\n010404fe=string/mmiComplete\n010404ff=string/mmiError\n01040500=string/mmiErrorWhileRoaming\n01040501=string/mmiFdnError\n01040502=string/mobile_no_internet\n01040503=string/mobile_provisioning_apn\n01040504=string/mobile_provisioning_url\n01040505=string/month_day_year\n01040506=string/more_item_label\n01040507=string/muted_by\n01040508=string/needPuk\n01040509=string/needPuk2\n0104050a=string/negative_duration\n0104050b=string/network_available_sign_in\n0104050c=string/network_available_sign_in_detailed\n0104050d=string/network_logging_notification_text\n0104050e=string/network_logging_notification_title\n0104050f=string/network_partial_connectivity\n01040510=string/network_partial_connectivity_detailed\n01040511=string/network_switch_metered\n01040512=string/network_switch_metered_detail\n01040513=string/network_switch_metered_toast\n01040514=string/network_switch_type_name_unknown\n01040515=string/new_app_action\n01040516=string/new_app_description\n01040517=string/new_sms_notification_content\n01040518=string/new_sms_notification_title\n01040519=string/next_button_label\n0104051a=string/noApplications\n0104051b=string/no_file_chosen\n0104051c=string/no_matches\n0104051d=string/no_permissions\n0104051e=string/no_recent_tasks\n0104051f=string/noon\n01040520=string/not_checked\n01040521=string/notification_alerted_content_description\n01040522=string/notification_app_name_settings\n01040523=string/notification_app_name_system\n01040524=string/notification_appops_camera_active\n01040525=string/notification_appops_microphone_active\n01040526=string/notification_appops_overlay_active\n01040527=string/notification_channel_account\n01040528=string/notification_channel_alerts\n01040529=string/notification_channel_call_forward\n0104052a=string/notification_channel_car_mode\n0104052b=string/notification_channel_developer\n0104052c=string/notification_channel_developer_important\n0104052d=string/notification_channel_device_admin\n0104052e=string/notification_channel_do_not_disturb\n0104052f=string/notification_channel_emergency_callback\n01040530=string/notification_channel_foreground_service\n01040531=string/notification_channel_heavy_weight_app\n01040532=string/notification_channel_mobile_data_status\n01040533=string/notification_channel_network_alert\n01040534=string/notification_channel_network_alerts\n01040535=string/notification_channel_network_available\n01040536=string/notification_channel_network_status\n01040537=string/notification_channel_physical_keyboard\n01040538=string/notification_channel_retail_mode\n01040539=string/notification_channel_security\n0104053a=string/notification_channel_sim\n0104053b=string/notification_channel_sim_high_prio\n0104053c=string/notification_channel_sms\n0104053d=string/notification_channel_system_changes\n0104053e=string/notification_channel_updates\n0104053f=string/notification_channel_usb\n01040540=string/notification_channel_virtual_keyboard\n01040541=string/notification_channel_voice_mail\n01040542=string/notification_channel_vpn\n01040543=string/notification_channel_wfc\n01040544=string/notification_header_divider_symbol\n01040545=string/notification_header_divider_symbol_with_spaces\n01040546=string/notification_hidden_text\n01040547=string/notification_history_title_placeholder\n01040548=string/notification_inbox_ellipsis\n01040549=string/notification_listener_binding_label\n0104054a=string/notification_messaging_title_template\n0104054b=string/notification_ranker_binding_label\n0104054c=string/notification_reply_button_accessibility\n0104054d=string/notification_title\n0104054e=string/notification_work_profile_content_description\n0104054f=string/now_string_shortest\n01040550=string/number_picker_decrement_button\n01040551=string/number_picker_increment_button\n01040552=string/number_picker_increment_scroll_action\n01040553=string/number_picker_increment_scroll_mode\n01040554=string/old_app_action\n01040555=string/older\n01040556=string/oneMonthDurationPast\n01040557=string/open_permission_deny\n01040558=string/orgTypeCustom\n01040559=string/orgTypeOther\n0104055a=string/orgTypeWork\n0104055b=string/org_name\n0104055c=string/org_unit\n0104055d=string/other_networks_no_internet\n0104055e=string/owner_name\n0104055f=string/package_deleted_device_owner\n01040560=string/package_installed_device_owner\n01040561=string/package_updated_device_owner\n01040562=string/passwordIncorrect\n01040563=string/password_keyboard_label_alpha_key\n01040564=string/password_keyboard_label_alt_key\n01040565=string/password_keyboard_label_symbol_key\n01040566=string/peerTtyModeFull\n01040567=string/peerTtyModeHco\n01040568=string/peerTtyModeOff\n01040569=string/peerTtyModeVco\n0104056a=string/perm_costs_money\n0104056b=string/permdesc_acceptHandovers\n0104056c=string/permdesc_accessBackgroundLocation\n0104056d=string/permdesc_accessCoarseLocation\n0104056e=string/permdesc_accessDrmCertificates\n0104056f=string/permdesc_accessFineLocation\n01040570=string/permdesc_accessImsCallService\n01040571=string/permdesc_accessLocationExtraCommands\n01040572=string/permdesc_accessNetworkConditions\n01040573=string/permdesc_accessNetworkState\n01040574=string/permdesc_accessNotifications\n01040575=string/permdesc_accessWifiState\n01040576=string/permdesc_accessWimaxState\n01040577=string/permdesc_access_notification_policy\n01040578=string/permdesc_activityRecognition\n01040579=string/permdesc_addVoicemail\n0104057a=string/permdesc_answerPhoneCalls\n0104057b=string/permdesc_audioWrite\n0104057c=string/permdesc_bindCarrierMessagingService\n0104057d=string/permdesc_bindCarrierServices\n0104057e=string/permdesc_bindCellBroadcastService\n0104057f=string/permdesc_bindConditionProviderService\n01040580=string/permdesc_bindDreamService\n01040581=string/permdesc_bindNotificationListenerService\n01040582=string/permdesc_bind_connection_service\n01040583=string/permdesc_bind_incall_service\n01040584=string/permdesc_bluetooth\n01040585=string/permdesc_bluetoothAdmin\n01040586=string/permdesc_bodySensors\n01040587=string/permdesc_broadcastSticky\n01040588=string/permdesc_callCompanionApp\n01040589=string/permdesc_callPhone\n0104058a=string/permdesc_camera\n0104058b=string/permdesc_cameraOpenCloseListener\n0104058c=string/permdesc_changeNetworkState\n0104058d=string/permdesc_changeTetherState\n0104058e=string/permdesc_changeWifiMulticastState\n0104058f=string/permdesc_changeWifiState\n01040590=string/permdesc_changeWimaxState\n01040591=string/permdesc_connection_manager\n01040592=string/permdesc_control_incall_experience\n01040593=string/permdesc_createNetworkSockets\n01040594=string/permdesc_disableKeyguard\n01040595=string/permdesc_enableCarMode\n01040596=string/permdesc_exemptFromAudioRecordRestrictions\n01040597=string/permdesc_expandStatusBar\n01040598=string/permdesc_foregroundService\n01040599=string/permdesc_getAccounts\n0104059a=string/permdesc_getPackageSize\n0104059b=string/permdesc_getTasks\n0104059c=string/permdesc_handoverStatus\n0104059d=string/permdesc_imagesWrite\n0104059e=string/permdesc_install_shortcut\n0104059f=string/permdesc_invokeCarrierSetup\n010405a0=string/permdesc_killBackgroundProcesses\n010405a1=string/permdesc_manageFace\n010405a2=string/permdesc_manageFingerprint\n010405a3=string/permdesc_manageNetworkPolicy\n010405a4=string/permdesc_manageOwnCalls\n010405a5=string/permdesc_manageProfileAndDeviceOwners\n010405a6=string/permdesc_mediaLocation\n010405a7=string/permdesc_modifyAudioSettings\n010405a8=string/permdesc_modifyNetworkAccounting\n010405a9=string/permdesc_nfc\n010405aa=string/permdesc_persistentActivity\n010405ab=string/permdesc_preferredPaymentInfo\n010405ac=string/permdesc_processOutgoingCalls\n010405ad=string/permdesc_readCalendar\n010405ae=string/permdesc_readCallLog\n010405af=string/permdesc_readCellBroadcasts\n010405b0=string/permdesc_readContacts\n010405b1=string/permdesc_readHistoryBookmarks\n010405b2=string/permdesc_readInstallSessions\n010405b3=string/permdesc_readNetworkUsageHistory\n010405b4=string/permdesc_readPhoneNumbers\n010405b5=string/permdesc_readPhoneState\n010405b6=string/permdesc_readSms\n010405b7=string/permdesc_readSyncSettings\n010405b8=string/permdesc_readSyncStats\n010405b9=string/permdesc_receiveBootCompleted\n010405ba=string/permdesc_receiveMms\n010405bb=string/permdesc_receiveSms\n010405bc=string/permdesc_receiveWapPush\n010405bd=string/permdesc_recordAudio\n010405be=string/permdesc_register_call_provider\n010405bf=string/permdesc_register_sim_subscription\n010405c0=string/permdesc_removeDrmCertificates\n010405c1=string/permdesc_reorderTasks\n010405c2=string/permdesc_requestDeletePackages\n010405c3=string/permdesc_requestIgnoreBatteryOptimizations\n010405c4=string/permdesc_requestInstallPackages\n010405c5=string/permdesc_requestPasswordComplexity\n010405c6=string/permdesc_route_media_output\n010405c7=string/permdesc_runInBackground\n010405c8=string/permdesc_sdcardRead\n010405c9=string/permdesc_sdcardWrite\n010405ca=string/permdesc_sendSms\n010405cb=string/permdesc_setAlarm\n010405cc=string/permdesc_setInputCalibration\n010405cd=string/permdesc_setTimeZone\n010405ce=string/permdesc_setWallpaper\n010405cf=string/permdesc_setWallpaperHints\n010405d0=string/permdesc_sim_communication\n010405d1=string/permdesc_startViewPermissionUsage\n010405d2=string/permdesc_statusBar\n010405d3=string/permdesc_statusBarService\n010405d4=string/permdesc_subscribedFeedsRead\n010405d5=string/permdesc_systemAlertWindow\n010405d6=string/permdesc_systemCamera\n010405d7=string/permdesc_transmitIr\n010405d8=string/permdesc_uninstall_shortcut\n010405d9=string/permdesc_useBiometric\n010405da=string/permdesc_useDataInBackground\n010405db=string/permdesc_useFaceAuthentication\n010405dc=string/permdesc_useFingerprint\n010405dd=string/permdesc_use_sip\n010405de=string/permdesc_vibrate\n010405df=string/permdesc_vibrator_state\n010405e0=string/permdesc_videoWrite\n010405e1=string/permdesc_wakeLock\n010405e2=string/permdesc_writeCalendar\n010405e3=string/permdesc_writeCallLog\n010405e4=string/permdesc_writeContacts\n010405e5=string/permdesc_writeGeolocationPermissions\n010405e6=string/permdesc_writeHistoryBookmarks\n010405e7=string/permdesc_writeSettings\n010405e8=string/permdesc_writeSyncSettings\n010405e9=string/permgroupdesc_activityRecognition\n010405ea=string/permgroupdesc_calendar\n010405eb=string/permgroupdesc_calllog\n010405ec=string/permgroupdesc_camera\n010405ed=string/permgroupdesc_contacts\n010405ee=string/permgroupdesc_location\n010405ef=string/permgroupdesc_microphone\n010405f0=string/permgroupdesc_phone\n010405f1=string/permgroupdesc_sensors\n010405f2=string/permgroupdesc_sms\n010405f3=string/permgroupdesc_storage\n010405f4=string/permgrouplab_activityRecognition\n010405f5=string/permgrouplab_calendar\n010405f6=string/permgrouplab_calllog\n010405f7=string/permgrouplab_camera\n010405f8=string/permgrouplab_contacts\n010405f9=string/permgrouplab_location\n010405fa=string/permgrouplab_microphone\n010405fb=string/permgrouplab_phone\n010405fc=string/permgrouplab_sensors\n010405fd=string/permgrouplab_sms\n010405fe=string/permgrouplab_storage\n010405ff=string/permission_request_notification_title\n01040600=string/permission_request_notification_with_subtitle\n01040601=string/permlab_acceptHandover\n01040602=string/permlab_accessBackgroundLocation\n01040603=string/permlab_accessCoarseLocation\n01040604=string/permlab_accessDrmCertificates\n01040605=string/permlab_accessFineLocation\n01040606=string/permlab_accessImsCallService\n01040607=string/permlab_accessLocationExtraCommands\n01040608=string/permlab_accessNetworkConditions\n01040609=string/permlab_accessNetworkState\n0104060a=string/permlab_accessNotifications\n0104060b=string/permlab_accessWifiState\n0104060c=string/permlab_accessWimaxState\n0104060d=string/permlab_access_notification_policy\n0104060e=string/permlab_activityRecognition\n0104060f=string/permlab_addVoicemail\n01040610=string/permlab_answerPhoneCalls\n01040611=string/permlab_audioWrite\n01040612=string/permlab_bindCarrierMessagingService\n01040613=string/permlab_bindCarrierServices\n01040614=string/permlab_bindCellBroadcastService\n01040615=string/permlab_bindConditionProviderService\n01040616=string/permlab_bindDreamService\n01040617=string/permlab_bindNotificationListenerService\n01040618=string/permlab_bind_connection_service\n01040619=string/permlab_bind_incall_service\n0104061a=string/permlab_bluetooth\n0104061b=string/permlab_bluetoothAdmin\n0104061c=string/permlab_bodySensors\n0104061d=string/permlab_broadcastSticky\n0104061e=string/permlab_callCompanionApp\n0104061f=string/permlab_callPhone\n01040620=string/permlab_camera\n01040621=string/permlab_cameraOpenCloseListener\n01040622=string/permlab_changeNetworkState\n01040623=string/permlab_changeTetherState\n01040624=string/permlab_changeWifiMulticastState\n01040625=string/permlab_changeWifiState\n01040626=string/permlab_changeWimaxState\n01040627=string/permlab_connection_manager\n01040628=string/permlab_control_incall_experience\n01040629=string/permlab_createNetworkSockets\n0104062a=string/permlab_disableKeyguard\n0104062b=string/permlab_enableCarMode\n0104062c=string/permlab_exemptFromAudioRecordRestrictions\n0104062d=string/permlab_expandStatusBar\n0104062e=string/permlab_foregroundService\n0104062f=string/permlab_getAccounts\n01040630=string/permlab_getPackageSize\n01040631=string/permlab_getTasks\n01040632=string/permlab_handoverStatus\n01040633=string/permlab_imagesWrite\n01040634=string/permlab_install_shortcut\n01040635=string/permlab_invokeCarrierSetup\n01040636=string/permlab_killBackgroundProcesses\n01040637=string/permlab_manageFace\n01040638=string/permlab_manageFingerprint\n01040639=string/permlab_manageNetworkPolicy\n0104063a=string/permlab_manageOwnCalls\n0104063b=string/permlab_manageProfileAndDeviceOwners\n0104063c=string/permlab_mediaLocation\n0104063d=string/permlab_modifyAudioSettings\n0104063e=string/permlab_modifyNetworkAccounting\n0104063f=string/permlab_nfc\n01040640=string/permlab_persistentActivity\n01040641=string/permlab_preferredPaymentInfo\n01040642=string/permlab_processOutgoingCalls\n01040643=string/permlab_readCalendar\n01040644=string/permlab_readCallLog\n01040645=string/permlab_readCellBroadcasts\n01040646=string/permlab_readContacts\n01040647=string/permlab_readHistoryBookmarks\n01040648=string/permlab_readInstallSessions\n01040649=string/permlab_readNetworkUsageHistory\n0104064a=string/permlab_readPhoneNumbers\n0104064b=string/permlab_readPhoneState\n0104064c=string/permlab_readSms\n0104064d=string/permlab_readSyncSettings\n0104064e=string/permlab_readSyncStats\n0104064f=string/permlab_receiveBootCompleted\n01040650=string/permlab_receiveMms\n01040651=string/permlab_receiveSms\n01040652=string/permlab_receiveWapPush\n01040653=string/permlab_recordAudio\n01040654=string/permlab_register_call_provider\n01040655=string/permlab_register_sim_subscription\n01040656=string/permlab_removeDrmCertificates\n01040657=string/permlab_reorderTasks\n01040658=string/permlab_requestDeletePackages\n01040659=string/permlab_requestIgnoreBatteryOptimizations\n0104065a=string/permlab_requestInstallPackages\n0104065b=string/permlab_requestPasswordComplexity\n0104065c=string/permlab_route_media_output\n0104065d=string/permlab_runInBackground\n0104065e=string/permlab_sdcardRead\n0104065f=string/permlab_sdcardWrite\n01040660=string/permlab_sendSms\n01040661=string/permlab_setAlarm\n01040662=string/permlab_setInputCalibration\n01040663=string/permlab_setTimeZone\n01040664=string/permlab_setWallpaper\n01040665=string/permlab_setWallpaperHints\n01040666=string/permlab_sim_communication\n01040667=string/permlab_startViewPermissionUsage\n01040668=string/permlab_statusBar\n01040669=string/permlab_statusBarService\n0104066a=string/permlab_subscribedFeedsRead\n0104066b=string/permlab_systemAlertWindow\n0104066c=string/permlab_systemCamera\n0104066d=string/permlab_transmitIr\n0104066e=string/permlab_uninstall_shortcut\n0104066f=string/permlab_useBiometric\n01040670=string/permlab_useDataInBackground\n01040671=string/permlab_useFaceAuthentication\n01040672=string/permlab_useFingerprint\n01040673=string/permlab_use_sip\n01040674=string/permlab_vibrate\n01040675=string/permlab_videoWrite\n01040676=string/permlab_wakeLock\n01040677=string/permlab_writeCalendar\n01040678=string/permlab_writeCallLog\n01040679=string/permlab_writeContacts\n0104067a=string/permlab_writeGeolocationPermissions\n0104067b=string/permlab_writeHistoryBookmarks\n0104067c=string/permlab_writeSettings\n0104067d=string/permlab_writeSyncSettings\n0104067e=string/perms_description_app\n0104067f=string/perms_new_perm_prefix\n01040680=string/personal_apps_suspended_turn_profile_on\n01040681=string/personal_apps_suspension_soon_text\n01040682=string/personal_apps_suspension_text\n01040683=string/personal_apps_suspension_title\n01040684=string/petabyteShort\n01040685=string/phoneTypeAssistant\n01040686=string/phoneTypeCallback\n01040687=string/phoneTypeCar\n01040688=string/phoneTypeCompanyMain\n01040689=string/phoneTypeCustom\n0104068a=string/phoneTypeFaxHome\n0104068b=string/phoneTypeFaxWork\n0104068c=string/phoneTypeHome\n0104068d=string/phoneTypeIsdn\n0104068e=string/phoneTypeMain\n0104068f=string/phoneTypeMms\n01040690=string/phoneTypeMobile\n01040691=string/phoneTypeOther\n01040692=string/phoneTypeOtherFax\n01040693=string/phoneTypePager\n01040694=string/phoneTypeRadio\n01040695=string/phoneTypeTelex\n01040696=string/phoneTypeTtyTdd\n01040697=string/phoneTypeWork\n01040698=string/phoneTypeWorkMobile\n01040699=string/phoneTypeWorkPager\n0104069a=string/pin_specific_target\n0104069b=string/pin_target\n0104069c=string/policydesc_disableCamera\n0104069d=string/policydesc_disableKeyguardFeatures\n0104069e=string/policydesc_encryptedStorage\n0104069f=string/policydesc_expirePassword\n010406a0=string/policydesc_forceLock\n010406a1=string/policydesc_limitPassword\n010406a2=string/policydesc_resetPassword\n010406a3=string/policydesc_setGlobalProxy\n010406a4=string/policydesc_watchLogin\n010406a5=string/policydesc_watchLogin_secondaryUser\n010406a6=string/policydesc_wipeData\n010406a7=string/policydesc_wipeData_secondaryUser\n010406a8=string/policylab_disableCamera\n010406a9=string/policylab_disableKeyguardFeatures\n010406aa=string/policylab_encryptedStorage\n010406ab=string/policylab_expirePassword\n010406ac=string/policylab_forceLock\n010406ad=string/policylab_limitPassword\n010406ae=string/policylab_resetPassword\n010406af=string/policylab_setGlobalProxy\n010406b0=string/policylab_watchLogin\n010406b1=string/policylab_wipeData\n010406b2=string/policylab_wipeData_secondaryUser\n010406b3=string/popup_window_default_title\n010406b4=string/postalTypeCustom\n010406b5=string/postalTypeHome\n010406b6=string/postalTypeOther\n010406b7=string/postalTypeWork\n010406b8=string/power_dialog\n010406b9=string/power_off\n010406ba=string/prepend_shortcut_label\n010406bb=string/preposition_for_date\n010406bc=string/preposition_for_time\n010406bd=string/preposition_for_year\n010406be=string/print_service_installed_message\n010406bf=string/print_service_installed_title\n010406c0=string/printing_disabled_by\n010406c1=string/private_dns_broken_detailed\n010406c2=string/profile_encrypted_detail\n010406c3=string/profile_encrypted_message\n010406c4=string/profile_encrypted_title\n010406c5=string/progress_erasing\n010406c6=string/prohibit_manual_network_selection_in_gobal_mode\n010406c7=string/quick_contacts_not_available\n010406c8=string/radial_numbers_typeface\n010406c9=string/reason_service_unavailable\n010406ca=string/reason_unknown\n010406cb=string/reboot_safemode_confirm\n010406cc=string/reboot_safemode_title\n010406cd=string/reboot_to_reset_message\n010406ce=string/reboot_to_reset_title\n010406cf=string/reboot_to_update_package\n010406d0=string/reboot_to_update_prepare\n010406d1=string/reboot_to_update_reboot\n010406d2=string/reboot_to_update_title\n010406d3=string/recent_tasks_title\n010406d4=string/redo\n010406d5=string/region_picker_section_all\n010406d6=string/relationTypeAssistant\n010406d7=string/relationTypeBrother\n010406d8=string/relationTypeChild\n010406d9=string/relationTypeCustom\n010406da=string/relationTypeDomesticPartner\n010406db=string/relationTypeFather\n010406dc=string/relationTypeFriend\n010406dd=string/relationTypeManager\n010406de=string/relationTypeMother\n010406df=string/relationTypeParent\n010406e0=string/relationTypePartner\n010406e1=string/relationTypeReferredBy\n010406e2=string/relationTypeRelative\n010406e3=string/relationTypeSister\n010406e4=string/relationTypeSpouse\n010406e5=string/relative_time\n010406e6=string/replace\n010406e7=string/report\n010406e8=string/reset\n010406e9=string/resolver_cant_access_personal_apps\n010406ea=string/resolver_cant_access_personal_apps_explanation\n010406eb=string/resolver_cant_access_work_apps\n010406ec=string/resolver_cant_access_work_apps_explanation\n010406ed=string/resolver_cant_share_with_personal_apps\n010406ee=string/resolver_cant_share_with_personal_apps_explanation\n010406ef=string/resolver_cant_share_with_work_apps\n010406f0=string/resolver_cant_share_with_work_apps_explanation\n010406f1=string/resolver_no_personal_apps_available_resolve\n010406f2=string/resolver_no_personal_apps_available_share\n010406f3=string/resolver_no_work_apps_available_resolve\n010406f4=string/resolver_no_work_apps_available_share\n010406f5=string/resolver_personal_tab\n010406f6=string/resolver_personal_tab_accessibility\n010406f7=string/resolver_switch_on_work\n010406f8=string/resolver_turn_on_work_apps\n010406f9=string/resolver_work_tab\n010406fa=string/resolver_work_tab_accessibility\n010406fb=string/restr_pin_confirm_pin\n010406fc=string/restr_pin_create_pin\n010406fd=string/restr_pin_enter_admin_pin\n010406fe=string/restr_pin_enter_new_pin\n010406ff=string/restr_pin_enter_old_pin\n01040700=string/restr_pin_enter_pin\n01040701=string/restr_pin_error_doesnt_match\n01040702=string/restr_pin_error_too_short\n01040703=string/restr_pin_incorrect\n01040704=string/restr_pin_try_later\n01040705=string/revoke\n01040706=string/ringtone_default\n01040707=string/ringtone_default_with_actual\n01040708=string/ringtone_picker_title\n01040709=string/ringtone_picker_title_alarm\n0104070a=string/ringtone_picker_title_notification\n0104070b=string/ringtone_silent\n0104070c=string/ringtone_unknown\n0104070d=string/roamingText0\n0104070e=string/roamingText1\n0104070f=string/roamingText10\n01040710=string/roamingText11\n01040711=string/roamingText12\n01040712=string/roamingText2\n01040713=string/roamingText3\n01040714=string/roamingText4\n01040715=string/roamingText5\n01040716=string/roamingText6\n01040717=string/roamingText7\n01040718=string/roamingText8\n01040719=string/roamingText9\n0104071a=string/roamingTextSearching\n0104071b=string/safeMode\n0104071c=string/safe_media_volume_warning\n0104071d=string/sans_serif\n0104071e=string/save_password_label\n0104071f=string/save_password_message\n01040720=string/save_password_never\n01040721=string/save_password_notnow\n01040722=string/save_password_remember\n01040723=string/screen_compat_mode_hint\n01040724=string/screen_compat_mode_scale\n01040725=string/screen_compat_mode_show\n01040726=string/screen_lock\n01040727=string/screenshot_edit\n01040728=string/search_hint\n01040729=string/search_language_hint\n0104072a=string/searchview_description_clear\n0104072b=string/searchview_description_query\n0104072c=string/searchview_description_search\n0104072d=string/searchview_description_submit\n0104072e=string/searchview_description_voice\n0104072f=string/second\n01040730=string/seconds\n01040731=string/select_character\n01040732=string/select_day\n01040733=string/select_hours\n01040734=string/select_input_method\n01040735=string/select_keyboard_layout_notification_message\n01040736=string/select_keyboard_layout_notification_title\n01040737=string/select_minutes\n01040738=string/select_year\n01040739=string/sendText\n0104073a=string/sending\n0104073b=string/sensor_notification_service\n0104073c=string/serial_number\n0104073d=string/serviceClassData\n0104073e=string/serviceClassDataAsync\n0104073f=string/serviceClassDataSync\n01040740=string/serviceClassFAX\n01040741=string/serviceClassPAD\n01040742=string/serviceClassPacket\n01040743=string/serviceClassSMS\n01040744=string/serviceClassVoice\n01040745=string/serviceDisabled\n01040746=string/serviceEnabled\n01040747=string/serviceEnabledFor\n01040748=string/serviceErased\n01040749=string/serviceNotProvisioned\n0104074a=string/serviceRegistered\n0104074b=string/setup_autofill\n0104074c=string/sha1_fingerprint\n0104074d=string/sha256_fingerprint\n0104074e=string/share\n0104074f=string/share_action_provider_share_with\n01040750=string/share_remote_bugreport_action\n01040751=string/share_remote_bugreport_notification_message_finished\n01040752=string/share_remote_bugreport_notification_title\n01040753=string/shareactionprovider_share_with\n01040754=string/shareactionprovider_share_with_application\n01040755=string/sharing_remote_bugreport_notification_title\n01040756=string/shortcut_disabled_reason_unknown\n01040757=string/shortcut_restore_not_supported\n01040758=string/shortcut_restore_signature_mismatch\n01040759=string/shortcut_restore_unknown_issue\n0104075a=string/shortcut_restored_on_lower_version\n0104075b=string/show_ime\n0104075c=string/shutdown_confirm\n0104075d=string/shutdown_confirm_question\n0104075e=string/shutdown_progress\n0104075f=string/silent_mode\n01040760=string/silent_mode_ring\n01040761=string/silent_mode_silent\n01040762=string/silent_mode_vibrate\n01040763=string/sim_added_message\n01040764=string/sim_added_title\n01040765=string/sim_done_button\n01040766=string/sim_removed_message\n01040767=string/sim_removed_title\n01040768=string/sim_restart_button\n01040769=string/sipAddressTypeCustom\n0104076a=string/sipAddressTypeHome\n0104076b=string/sipAddressTypeOther\n0104076c=string/sipAddressTypeWork\n0104076d=string/skip_button_label\n0104076e=string/slice_more_content\n0104076f=string/slices_permission_request\n01040770=string/sms_control_message\n01040771=string/sms_control_no\n01040772=string/sms_control_title\n01040773=string/sms_control_yes\n01040774=string/sms_premium_short_code_details\n01040775=string/sms_short_code_confirm_allow\n01040776=string/sms_short_code_confirm_always_allow\n01040777=string/sms_short_code_confirm_deny\n01040778=string/sms_short_code_confirm_message\n01040779=string/sms_short_code_confirm_never_allow\n0104077a=string/sms_short_code_details\n0104077b=string/sms_short_code_remember_choice\n0104077c=string/sms_short_code_remember_undo_instruction\n0104077d=string/smv_application\n0104077e=string/smv_process\n0104077f=string/ssl_ca_cert_noti_by_administrator\n01040780=string/ssl_ca_cert_noti_by_unknown\n01040781=string/ssl_ca_cert_noti_managed\n01040782=string/ssl_certificate\n01040783=string/ssl_certificate_is_valid\n01040784=string/status_bar_airplane\n01040785=string/status_bar_alarm_clock\n01040786=string/status_bar_battery\n01040787=string/status_bar_bluetooth\n01040788=string/status_bar_camera\n01040789=string/status_bar_cast\n0104078a=string/status_bar_cdma_eri\n0104078b=string/status_bar_clock\n0104078c=string/status_bar_data_connection\n0104078d=string/status_bar_data_saver\n0104078e=string/status_bar_ethernet\n0104078f=string/status_bar_headset\n01040790=string/status_bar_hotspot\n01040791=string/status_bar_ime\n01040792=string/status_bar_location\n01040793=string/status_bar_managed_profile\n01040794=string/status_bar_microphone\n01040795=string/status_bar_mobile\n01040796=string/status_bar_mute\n01040797=string/status_bar_nfc\n01040798=string/status_bar_phone_evdo_signal\n01040799=string/status_bar_phone_signal\n0104079a=string/status_bar_rotate\n0104079b=string/status_bar_screen_record\n0104079c=string/status_bar_secure\n0104079d=string/status_bar_sensors_off\n0104079e=string/status_bar_speakerphone\n0104079f=string/status_bar_sync_active\n010407a0=string/status_bar_sync_failing\n010407a1=string/status_bar_tty\n010407a2=string/status_bar_volume\n010407a3=string/status_bar_vpn\n010407a4=string/status_bar_wifi\n010407a5=string/status_bar_zen\n010407a6=string/stk_cc_ss_to_dial\n010407a7=string/stk_cc_ss_to_dial_video\n010407a8=string/stk_cc_ss_to_ss\n010407a9=string/stk_cc_ss_to_ussd\n010407aa=string/stk_cc_ussd_to_dial\n010407ab=string/stk_cc_ussd_to_dial_video\n010407ac=string/stk_cc_ussd_to_ss\n010407ad=string/stk_cc_ussd_to_ussd\n010407ae=string/storage_internal\n010407af=string/storage_sd_card\n010407b0=string/storage_sd_card_label\n010407b1=string/storage_usb\n010407b2=string/storage_usb_drive\n010407b3=string/storage_usb_drive_label\n010407b4=string/submit\n010407b5=string/suspended_widget_accessibility\n010407b6=string/sync_binding_label\n010407b7=string/sync_do_nothing\n010407b8=string/sync_really_delete\n010407b9=string/sync_too_many_deletes\n010407ba=string/sync_too_many_deletes_desc\n010407bb=string/sync_undo_deletes\n010407bc=string/system_error_manufacturer\n010407bd=string/system_error_wipe_data\n010407be=string/system_ui_date_pattern\n010407bf=string/taking_remote_bugreport_notification_title\n010407c0=string/terabyteShort\n010407c1=string/test_harness_mode_notification_message\n010407c2=string/test_harness_mode_notification_title\n010407c3=string/textSelectionCABTitle\n010407c4=string/text_copied\n010407c5=string/time_of_day\n010407c6=string/time_picker_decrement_hour_button\n010407c7=string/time_picker_decrement_minute_button\n010407c8=string/time_picker_decrement_set_am_button\n010407c9=string/time_picker_dialog_title\n010407ca=string/time_picker_header_text\n010407cb=string/time_picker_hour_label\n010407cc=string/time_picker_increment_hour_button\n010407cd=string/time_picker_increment_minute_button\n010407ce=string/time_picker_increment_set_pm_button\n010407cf=string/time_picker_input_error\n010407d0=string/time_picker_minute_label\n010407d1=string/time_picker_mode\n010407d2=string/time_picker_prompt_label\n010407d3=string/time_picker_radial_mode_description\n010407d4=string/time_picker_text_input_mode_description\n010407d5=string/time_placeholder\n010407d6=string/toolbar_collapse_description\n010407d7=string/tooltip_popup_title\n010407d8=string/turn_off_radio\n010407d9=string/turn_on_radio\n010407da=string/tutorial_double_tap_to_zoom_message_short\n010407db=string/twilight_service\n010407dc=string/undo\n010407dd=string/unpin_specific_target\n010407de=string/unpin_target\n010407df=string/unread_convo_overflow\n010407e0=string/unsupported_compile_sdk_check_update\n010407e1=string/unsupported_compile_sdk_message\n010407e2=string/unsupported_compile_sdk_show\n010407e3=string/unsupported_display_size_message\n010407e4=string/unsupported_display_size_show\n010407e5=string/upload_file\n010407e6=string/usb_accessory_notification_title\n010407e7=string/usb_charging_notification_title\n010407e8=string/usb_contaminant_detected_message\n010407e9=string/usb_contaminant_detected_title\n010407ea=string/usb_contaminant_not_detected_message\n010407eb=string/usb_contaminant_not_detected_title\n010407ec=string/usb_device_resolve_prompt_warn\n010407ed=string/usb_midi_notification_title\n010407ee=string/usb_midi_peripheral_manufacturer_name\n010407ef=string/usb_midi_peripheral_name\n010407f0=string/usb_midi_peripheral_product_name\n010407f1=string/usb_mtp_launch_notification_description\n010407f2=string/usb_mtp_launch_notification_title\n010407f3=string/usb_mtp_notification_title\n010407f4=string/usb_notification_message\n010407f5=string/usb_power_notification_message\n010407f6=string/usb_ptp_notification_title\n010407f7=string/usb_supplying_notification_title\n010407f8=string/usb_tether_notification_title\n010407f9=string/usb_unsupported_audio_accessory_message\n010407fa=string/usb_unsupported_audio_accessory_title\n010407fb=string/use_a_different_app\n010407fc=string/user_creation_account_exists\n010407fd=string/user_creation_adding\n010407fe=string/user_logging_out_message\n010407ff=string/user_owner_label\n01040800=string/user_switched\n01040801=string/user_switching_message\n01040802=string/validity_period\n01040803=string/volume_alarm\n01040804=string/volume_bluetooth_call\n01040805=string/volume_call\n01040806=string/volume_dialog_ringer_guidance_silent\n01040807=string/volume_dialog_ringer_guidance_vibrate\n01040808=string/volume_icon_description_bluetooth\n01040809=string/volume_icon_description_incall\n0104080a=string/volume_icon_description_media\n0104080b=string/volume_icon_description_notification\n0104080c=string/volume_icon_description_ringer\n0104080d=string/volume_music\n0104080e=string/volume_music_hint_playing_through_bluetooth\n0104080f=string/volume_music_hint_silent_ringtone_selected\n01040810=string/volume_notification\n01040811=string/volume_ringtone\n01040812=string/volume_unknown\n01040813=string/vpn_lockdown_config\n01040814=string/vpn_lockdown_connected\n01040815=string/vpn_lockdown_connecting\n01040816=string/vpn_lockdown_disconnected\n01040817=string/vpn_lockdown_error\n01040818=string/vpn_text\n01040819=string/vpn_text_long\n0104081a=string/vpn_title\n0104081b=string/vpn_title_long\n0104081c=string/vr_listener_binding_label\n0104081d=string/wait\n0104081e=string/wallpaper_binding_label\n0104081f=string/webpage_unresponsive\n01040820=string/websearch\n01040821=string/week\n01040822=string/weeks\n01040823=string/wfcRegErrorTitle\n01040824=string/wfcSpnFormat\n01040825=string/wfcSpnFormat_spn\n01040826=string/wfcSpnFormat_spn_vowifi\n01040827=string/wfcSpnFormat_spn_wifi\n01040828=string/wfcSpnFormat_spn_wifi_calling\n01040829=string/wfcSpnFormat_spn_wifi_calling_vo_hyphen\n0104082a=string/wfcSpnFormat_spn_wlan_call\n0104082b=string/wfcSpnFormat_vowifi\n0104082c=string/wfcSpnFormat_wifi\n0104082d=string/wfcSpnFormat_wifi_calling\n0104082e=string/wfcSpnFormat_wifi_calling_bar_spn\n0104082f=string/wfcSpnFormat_wifi_calling_wo_hyphen\n01040830=string/wfcSpnFormat_wlan_call\n01040831=string/wfc_mode_cellular_preferred_summary\n01040832=string/wfc_mode_wifi_only_summary\n01040833=string/wfc_mode_wifi_preferred_summary\n01040834=string/whichApplication\n01040835=string/whichApplicationLabel\n01040836=string/whichApplicationNamed\n01040837=string/whichEditApplication\n01040838=string/whichEditApplicationLabel\n01040839=string/whichEditApplicationNamed\n0104083a=string/whichGiveAccessToApplicationLabel\n0104083b=string/whichHomeApplication\n0104083c=string/whichHomeApplicationLabel\n0104083d=string/whichHomeApplicationNamed\n0104083e=string/whichImageCaptureApplication\n0104083f=string/whichImageCaptureApplicationLabel\n01040840=string/whichImageCaptureApplicationNamed\n01040841=string/whichOpenHostLinksWith\n01040842=string/whichOpenHostLinksWithApp\n01040843=string/whichOpenLinksWith\n01040844=string/whichOpenLinksWithApp\n01040845=string/whichSendApplication\n01040846=string/whichSendApplicationLabel\n01040847=string/whichSendApplicationNamed\n01040848=string/whichSendToApplication\n01040849=string/whichSendToApplicationLabel\n0104084a=string/whichSendToApplicationNamed\n0104084b=string/whichViewApplication\n0104084c=string/whichViewApplicationLabel\n0104084d=string/whichViewApplicationNamed\n0104084e=string/widget_default_class_name\n0104084f=string/widget_default_package_name\n01040850=string/wifi_available_sign_in\n01040851=string/wifi_calling_off_summary\n01040852=string/wifi_no_internet\n01040853=string/wifi_no_internet_detailed\n01040854=string/wireless_display_route_description\n01040855=string/work_mode_off_message\n01040856=string/work_mode_off_title\n01040857=string/work_mode_turn_on\n01040858=string/work_profile_deleted\n01040859=string/work_profile_deleted_description_dpm_wipe\n0104085a=string/work_profile_deleted_details\n0104085b=string/work_profile_deleted_reason_maximum_password_failure\n0104085c=string/write_fail_reason_cancelled\n0104085d=string/write_fail_reason_cannot_write\n0104085e=string/year\n0104085f=string/years\n01040860=string/zen_mode_alarm\n01040861=string/zen_mode_default_events_name\n01040862=string/zen_mode_default_every_night_name\n01040863=string/zen_mode_default_weekends_name\n01040864=string/zen_mode_default_weeknights_name\n01040865=string/zen_mode_downtime_feature_name\n01040866=string/zen_mode_feature_name\n01040867=string/zen_mode_forever\n01040868=string/zen_mode_forever_dnd\n01040869=string/zen_mode_rule_name_combination\n0104086a=string/zen_mode_until\n0104086b=string/zen_upgrade_notification_content\n0104086c=string/zen_upgrade_notification_title\n0104086d=string/zen_upgrade_notification_visd_content\n0104086e=string/zen_upgrade_notification_visd_title\n01050000=dimen/app_icon_size\n01050001=dimen/thumbnail_height\n01050002=dimen/thumbnail_width\n01050003=dimen/dialog_min_width_major\n01050004=dimen/dialog_min_width_minor\n01050005=dimen/notification_large_icon_width\n01050006=dimen/notification_large_icon_height\n01050007=dimen/config_restrictedIconSize\n01050008=dimen/accessibility_magnification_indicator_width\n01050009=dimen/accessibility_touch_slop\n0105000a=dimen/action_bar_button_margin\n0105000b=dimen/action_bar_button_max_width\n0105000c=dimen/action_bar_content_inset_material\n0105000d=dimen/action_bar_content_inset_with_nav\n0105000e=dimen/action_bar_default_height\n0105000f=dimen/action_bar_default_height_material\n01050010=dimen/action_bar_default_padding_end_material\n01050011=dimen/action_bar_default_padding_start_material\n01050012=dimen/action_bar_elevation_material\n01050013=dimen/action_bar_icon_vertical_padding\n01050014=dimen/action_bar_icon_vertical_padding_material\n01050015=dimen/action_bar_margin\n01050016=dimen/action_bar_margin_end\n01050017=dimen/action_bar_margin_start\n01050018=dimen/action_bar_overflow_padding_end_material\n01050019=dimen/action_bar_overflow_padding_start_material\n0105001a=dimen/action_bar_stacked_max_height\n0105001b=dimen/action_bar_stacked_tab_max_width\n0105001c=dimen/action_bar_subtitle_bottom_margin\n0105001d=dimen/action_bar_subtitle_bottom_margin_material\n0105001e=dimen/action_bar_subtitle_text_size\n0105001f=dimen/action_bar_subtitle_top_margin\n01050020=dimen/action_bar_subtitle_top_margin_material\n01050021=dimen/action_bar_title_text_size\n01050022=dimen/action_bar_toggle_internal_padding\n01050023=dimen/action_button_min_height_material\n01050024=dimen/action_button_min_width\n01050025=dimen/action_button_min_width_material\n01050026=dimen/action_button_min_width_overflow_material\n01050027=dimen/activity_chooser_popup_min_width\n01050028=dimen/aerr_padding_list_bottom\n01050029=dimen/aerr_padding_list_top\n0105002a=dimen/alert_dialog_button_bar_height\n0105002b=dimen/alert_dialog_button_bar_width\n0105002c=dimen/alert_dialog_round_padding\n0105002d=dimen/alert_dialog_title_height\n0105002e=dimen/ambient_shadow_alpha\n0105002f=dimen/app_header_height\n01050030=dimen/autofill_dataset_picker_max_height\n01050031=dimen/autofill_dataset_picker_max_width\n01050032=dimen/autofill_elevation\n01050033=dimen/autofill_save_button_bar_padding\n01050034=dimen/autofill_save_custom_subtitle_max_height\n01050035=dimen/autofill_save_icon_max_size\n01050036=dimen/autofill_save_icon_size\n01050037=dimen/autofill_save_inner_padding\n01050038=dimen/autofill_save_outer_top_margin\n01050039=dimen/autofill_save_outer_top_padding\n0105003a=dimen/autofill_save_scroll_view_top_margin\n0105003b=dimen/autofill_save_title_start_padding\n0105003c=dimen/bubble_gone_padding_end\n0105003d=dimen/bubble_visible_padding_end\n0105003e=dimen/button_bar_layout_end_padding\n0105003f=dimen/button_bar_layout_start_padding\n01050040=dimen/button_bar_layout_top_padding\n01050041=dimen/button_elevation_material\n01050042=dimen/button_end_margin\n01050043=dimen/button_inset_horizontal_material\n01050044=dimen/button_inset_vertical_material\n01050045=dimen/button_layout_height\n01050046=dimen/button_padding_horizontal_material\n01050047=dimen/button_padding_vertical_material\n01050048=dimen/button_pressed_z_material\n01050049=dimen/car_action1_size\n0105004a=dimen/car_action2_size\n0105004b=dimen/car_app_bar_height\n0105004c=dimen/car_body1_size\n0105004d=dimen/car_body2_size\n0105004e=dimen/car_body3_size\n0105004f=dimen/car_body4_size\n01050050=dimen/car_body5_size\n01050051=dimen/car_borderless_button_horizontal_padding\n01050052=dimen/car_button_height\n01050053=dimen/car_button_horizontal_padding\n01050054=dimen/car_button_min_width\n01050055=dimen/car_button_radius\n01050056=dimen/car_card_action_bar_height\n01050057=dimen/car_card_header_height\n01050058=dimen/car_dialog_action_bar_height\n01050059=dimen/car_double_line_list_item_height\n0105005a=dimen/car_headline1_size\n0105005b=dimen/car_headline2_size\n0105005c=dimen/car_headline3_size\n0105005d=dimen/car_headline4_size\n0105005e=dimen/car_icon_size\n0105005f=dimen/car_keyline_1\n01050060=dimen/car_keyline_1_keyline_3_diff\n01050061=dimen/car_keyline_2\n01050062=dimen/car_keyline_3\n01050063=dimen/car_keyline_4\n01050064=dimen/car_label1_size\n01050065=dimen/car_label2_size\n01050066=dimen/car_large_avatar_size\n01050067=dimen/car_list_divider_height\n01050068=dimen/car_margin\n01050069=dimen/car_padding_0\n0105006a=dimen/car_padding_1\n0105006b=dimen/car_padding_2\n0105006c=dimen/car_padding_3\n0105006d=dimen/car_padding_4\n0105006e=dimen/car_padding_5\n0105006f=dimen/car_padding_6\n01050070=dimen/car_pill_button_size\n01050071=dimen/car_preference_category_icon_size\n01050072=dimen/car_preference_icon_size\n01050073=dimen/car_preference_row_vertical_margin\n01050074=dimen/car_primary_icon_size\n01050075=dimen/car_progress_bar_height\n01050076=dimen/car_radius_1\n01050077=dimen/car_radius_2\n01050078=dimen/car_radius_3\n01050079=dimen/car_radius_5\n0105007a=dimen/car_secondary_icon_size\n0105007b=dimen/car_seekbar_height\n0105007c=dimen/car_seekbar_padding\n0105007d=dimen/car_seekbar_text_overlap\n0105007e=dimen/car_seekbar_thumb_size\n0105007f=dimen/car_seekbar_thumb_stroke\n01050080=dimen/car_single_line_list_item_height\n01050081=dimen/car_textview_fading_edge_length\n01050082=dimen/car_title2_size\n01050083=dimen/car_title_size\n01050084=dimen/car_touch_target_size\n01050085=dimen/cascading_menus_min_smallest_width\n01050086=dimen/chooser_action_button_icon_size\n01050087=dimen/chooser_badge_size\n01050088=dimen/chooser_corner_radius\n01050089=dimen/chooser_direct_share_label_placeholder_max_width\n0105008a=dimen/chooser_edge_margin_normal\n0105008b=dimen/chooser_edge_margin_thin\n0105008c=dimen/chooser_grid_padding\n0105008d=dimen/chooser_header_scroll_elevation\n0105008e=dimen/chooser_icon_size\n0105008f=dimen/chooser_max_collapsed_height\n01050090=dimen/chooser_preview_image_border\n01050091=dimen/chooser_preview_image_font_size\n01050092=dimen/chooser_preview_image_max_dimen\n01050093=dimen/chooser_preview_width\n01050094=dimen/chooser_row_text_option_translate\n01050095=dimen/chooser_service_spacing\n01050096=dimen/chooser_view_spacing\n01050097=dimen/circular_display_mask_thickness\n01050098=dimen/config_alertDialogSelectionScrollOffset\n01050099=dimen/config_ambiguousGestureMultiplier\n0105009a=dimen/config_appTransitionAnimationDurationScaleDefault\n0105009b=dimen/config_backGestureInset\n0105009c=dimen/config_bottomDialogCornerRadius\n0105009d=dimen/config_buttonCornerRadius\n0105009e=dimen/config_closeToSquareDisplayMaxAspectRatio\n0105009f=dimen/config_dialogCornerRadius\n010500a0=dimen/config_displayWhiteBalanceBrightnessFilterIntercept\n010500a1=dimen/config_displayWhiteBalanceColorTemperatureFilterIntercept\n010500a2=dimen/config_displayWhiteBalanceHighLightAmbientColorTemperature\n010500a3=dimen/config_displayWhiteBalanceLowLightAmbientColorTemperature\n010500a4=dimen/config_highResTaskSnapshotScale\n010500a5=dimen/config_horizontalScrollFactor\n010500a6=dimen/config_inCallNotificationVolume\n010500a7=dimen/config_lowResTaskSnapshotScale\n010500a8=dimen/config_mediaMetadataBitmapMaxSize\n010500a9=dimen/config_minScalingSpan\n010500aa=dimen/config_minScalingTouchMajor\n010500ab=dimen/config_minScrollbarTouchTarget\n010500ac=dimen/config_pictureInPictureAspectRatioLimitForMinSize\n010500ad=dimen/config_pictureInPictureDefaultAspectRatio\n010500ae=dimen/config_pictureInPictureDefaultSizePercent\n010500af=dimen/config_pictureInPictureMaxAspectRatio\n010500b0=dimen/config_pictureInPictureMinAspectRatio\n010500b1=dimen/config_prefDialogWidth\n010500b2=dimen/config_preferredHyphenationFrequency\n010500b3=dimen/config_progressBarCornerRadius\n010500b4=dimen/config_qsTileStrokeWidthActive\n010500b5=dimen/config_qsTileStrokeWidthInactive\n010500b6=dimen/config_screenBrightnessDimFloat\n010500b7=dimen/config_screenBrightnessDozeFloat\n010500b8=dimen/config_screenBrightnessSettingDefaultFloat\n010500b9=dimen/config_screenBrightnessSettingForVrDefaultFloat\n010500ba=dimen/config_screenBrightnessSettingForVrMaximumFloat\n010500bb=dimen/config_screenBrightnessSettingForVrMinimumFloat\n010500bc=dimen/config_screenBrightnessSettingMaximumFloat\n010500bd=dimen/config_screenBrightnessSettingMinimumFloat\n010500be=dimen/config_screen_magnification_scaling_threshold\n010500bf=dimen/config_scrollFactor\n010500c0=dimen/config_scrollbarSize\n010500c1=dimen/config_signalCutoutHeightFraction\n010500c2=dimen/config_signalCutoutWidthFraction\n010500c3=dimen/config_verticalScrollFactor\n010500c4=dimen/config_viewConfigurationHoverSlop\n010500c5=dimen/config_viewConfigurationTouchSlop\n010500c6=dimen/config_viewMaxFlingVelocity\n010500c7=dimen/config_viewMinFlingVelocity\n010500c8=dimen/config_wallpaperMaxScale\n010500c9=dimen/content_rect_bottom_clip_allowance\n010500ca=dimen/control_corner_material\n010500cb=dimen/control_inset_material\n010500cc=dimen/control_padding_material\n010500cd=dimen/conversation_avatar_size\n010500ce=dimen/conversation_avatar_size_group_expanded\n010500cf=dimen/conversation_badge_side_margin\n010500d0=dimen/conversation_badge_side_margin_group_expanded\n010500d1=dimen/conversation_badge_side_margin_group_expanded_face_pile\n010500d2=dimen/conversation_content_start\n010500d3=dimen/conversation_expand_button_size\n010500d4=dimen/conversation_expand_button_top_margin_expanded\n010500d5=dimen/conversation_face_pile_avatar_size\n010500d6=dimen/conversation_face_pile_avatar_size_group_expanded\n010500d7=dimen/conversation_face_pile_protection_width\n010500d8=dimen/conversation_face_pile_protection_width_expanded\n010500d9=dimen/conversation_header_expanded_padding_end\n010500da=dimen/conversation_icon_container_top_padding\n010500db=dimen/conversation_icon_container_top_padding_small_avatar\n010500dc=dimen/conversation_icon_size_badged\n010500dd=dimen/cross_profile_apps_thumbnail_size\n010500de=dimen/date_picker_date_label_size\n010500df=dimen/date_picker_day_height\n010500e0=dimen/date_picker_day_of_week_height\n010500e1=dimen/date_picker_day_of_week_text_size\n010500e2=dimen/date_picker_day_selector_radius\n010500e3=dimen/date_picker_day_text_size\n010500e4=dimen/date_picker_day_width\n010500e5=dimen/date_picker_month_height\n010500e6=dimen/date_picker_month_text_size\n010500e7=dimen/date_picker_year_label_size\n010500e8=dimen/datepicker_component_width\n010500e9=dimen/datepicker_dialog_width\n010500ea=dimen/datepicker_header_height\n010500eb=dimen/datepicker_header_text_size\n010500ec=dimen/datepicker_list_year_activated_label_size\n010500ed=dimen/datepicker_list_year_label_size\n010500ee=dimen/datepicker_selected_date_day_size\n010500ef=dimen/datepicker_selected_date_month_size\n010500f0=dimen/datepicker_selected_date_year_size\n010500f1=dimen/datepicker_view_animator_height\n010500f2=dimen/datepicker_year_label_height\n010500f3=dimen/day_picker_button_margin_top\n010500f4=dimen/day_picker_padding_horizontal\n010500f5=dimen/day_picker_padding_top\n010500f6=dimen/default_app_widget_padding_bottom\n010500f7=dimen/default_app_widget_padding_left\n010500f8=dimen/default_app_widget_padding_right\n010500f9=dimen/default_app_widget_padding_top\n010500fa=dimen/default_gap\n010500fb=dimen/default_magnifier_corner_radius\n010500fc=dimen/default_magnifier_elevation\n010500fd=dimen/default_magnifier_height\n010500fe=dimen/default_magnifier_horizontal_offset\n010500ff=dimen/default_magnifier_vertical_offset\n01050100=dimen/default_magnifier_width\n01050101=dimen/default_magnifier_zoom\n01050102=dimen/default_minimal_size_pip_resizable_task\n01050103=dimen/default_minimal_size_resizable_task\n01050104=dimen/dialog_corner_radius\n01050105=dimen/dialog_fixed_height_major\n01050106=dimen/dialog_fixed_height_minor\n01050107=dimen/dialog_fixed_width_major\n01050108=dimen/dialog_fixed_width_minor\n01050109=dimen/dialog_list_padding_bottom_no_buttons\n0105010a=dimen/dialog_list_padding_top_no_title\n0105010b=dimen/dialog_no_title_padding_top\n0105010c=dimen/dialog_padding\n0105010d=dimen/dialog_padding_material\n0105010e=dimen/dialog_padding_top_material\n0105010f=dimen/dialog_title_divider_material\n01050110=dimen/disabled_alpha_device_default\n01050111=dimen/disabled_alpha_leanback_formwizard\n01050112=dimen/disabled_alpha_material_dark\n01050113=dimen/disabled_alpha_material_light\n01050114=dimen/display_cutout_touchable_region_size\n01050115=dimen/docked_stack_divider_insets\n01050116=dimen/docked_stack_divider_thickness\n01050117=dimen/docked_stack_minimize_thickness\n01050118=dimen/dropdownitem_icon_width\n01050119=dimen/dropdownitem_text_padding_left\n0105011a=dimen/dropdownitem_text_padding_right\n0105011b=dimen/edit_text_inset_bottom_material\n0105011c=dimen/edit_text_inset_horizontal_material\n0105011d=dimen/edit_text_inset_top_material\n0105011e=dimen/emphasized_button_stroke_width\n0105011f=dimen/expanded_group_conversation_message_padding\n01050120=dimen/face_unlock_height\n01050121=dimen/fast_scroller_minimum_touch_target\n01050122=dimen/floating_toolbar_height\n01050123=dimen/floating_toolbar_horizontal_margin\n01050124=dimen/floating_toolbar_icon_text_spacing\n01050125=dimen/floating_toolbar_maximum_overflow_height\n01050126=dimen/floating_toolbar_menu_button_minimum_width\n01050127=dimen/floating_toolbar_menu_button_side_padding\n01050128=dimen/floating_toolbar_menu_image_button_vertical_padding\n01050129=dimen/floating_toolbar_menu_image_button_width\n0105012a=dimen/floating_toolbar_menu_image_width\n0105012b=dimen/floating_toolbar_minimum_overflow_height\n0105012c=dimen/floating_toolbar_overflow_image_button_width\n0105012d=dimen/floating_toolbar_overflow_side_padding\n0105012e=dimen/floating_toolbar_preferred_width\n0105012f=dimen/floating_toolbar_text_size\n01050130=dimen/floating_toolbar_vertical_margin\n01050131=dimen/floating_window_margin_bottom\n01050132=dimen/floating_window_margin_left\n01050133=dimen/floating_window_margin_right\n01050134=dimen/floating_window_margin_top\n01050135=dimen/floating_window_z\n01050136=dimen/glowpadview_target_placement_radius\n01050137=dimen/harmful_app_icon_name_padding\n01050138=dimen/harmful_app_icon_size\n01050139=dimen/harmful_app_message_line_spacing_modifier\n0105013a=dimen/harmful_app_message_padding_bottom\n0105013b=dimen/harmful_app_message_padding_left\n0105013c=dimen/harmful_app_message_padding_right\n0105013d=dimen/harmful_app_name_padding_bottom\n0105013e=dimen/harmful_app_name_padding_left\n0105013f=dimen/harmful_app_name_padding_right\n01050140=dimen/harmful_app_name_padding_top\n01050141=dimen/harmful_app_padding_top\n01050142=dimen/highlight_alpha_material_colored\n01050143=dimen/highlight_alpha_material_dark\n01050144=dimen/highlight_alpha_material_light\n01050145=dimen/hint_alpha_material_dark\n01050146=dimen/hint_alpha_material_light\n01050147=dimen/hint_pressed_alpha_material_dark\n01050148=dimen/hint_pressed_alpha_material_light\n01050149=dimen/image_margin_start\n0105014a=dimen/image_size\n0105014b=dimen/immersive_mode_cling_width\n0105014c=dimen/importance_ring_anim_max_stroke_width\n0105014d=dimen/importance_ring_size\n0105014e=dimen/importance_ring_stroke_width\n0105014f=dimen/input_extract_action_button_height\n01050150=dimen/input_extract_action_button_width\n01050151=dimen/input_extract_action_icon_padding\n01050152=dimen/item_touch_helper_max_drag_scroll_per_frame\n01050153=dimen/item_touch_helper_swipe_escape_max_velocity\n01050154=dimen/item_touch_helper_swipe_escape_velocity\n01050155=dimen/keyguard_avatar_frame_shadow_radius\n01050156=dimen/keyguard_avatar_frame_stroke_width\n01050157=dimen/keyguard_avatar_name_size\n01050158=dimen/keyguard_avatar_size\n01050159=dimen/keyguard_lockscreen_clock_font_size\n0105015a=dimen/keyguard_lockscreen_outerring_diameter\n0105015b=dimen/keyguard_lockscreen_pin_margin_left\n0105015c=dimen/keyguard_lockscreen_status_line_clockfont_bottom_margin\n0105015d=dimen/keyguard_lockscreen_status_line_clockfont_top_margin\n0105015e=dimen/keyguard_lockscreen_status_line_font_right_margin\n0105015f=dimen/keyguard_lockscreen_status_line_font_size\n01050160=dimen/keyguard_muliuser_selector_margin\n01050161=dimen/keyguard_pattern_unlock_clock_font_size\n01050162=dimen/keyguard_pattern_unlock_status_line_font_size\n01050163=dimen/kg_clock_top_margin\n01050164=dimen/kg_edge_swipe_region_size\n01050165=dimen/kg_emergency_button_shift\n01050166=dimen/kg_key_horizontal_gap\n01050167=dimen/kg_key_vertical_gap\n01050168=dimen/kg_pin_key_height\n01050169=dimen/kg_runway_lights_height\n0105016a=dimen/kg_runway_lights_top_margin\n0105016b=dimen/kg_runway_lights_vertical_padding\n0105016c=dimen/kg_secure_padding_height\n0105016d=dimen/kg_security_panel_height\n0105016e=dimen/kg_security_view_height\n0105016f=dimen/kg_small_widget_height\n01050170=dimen/kg_squashed_layout_threshold\n01050171=dimen/kg_status_clock_font_size\n01050172=dimen/kg_status_date_font_size\n01050173=dimen/kg_status_line_font_right_margin\n01050174=dimen/kg_status_line_font_size\n01050175=dimen/kg_widget_pager_bottom_padding\n01050176=dimen/kg_widget_pager_horizontal_padding\n01050177=dimen/kg_widget_pager_top_padding\n01050178=dimen/kg_widget_view_height\n01050179=dimen/kg_widget_view_width\n0105017a=dimen/leanback_alert_dialog_horizontal_margin\n0105017b=dimen/leanback_alert_dialog_vertical_margin\n0105017c=dimen/leanback_setup_alpha_activity_in_bkg_end\n0105017d=dimen/leanback_setup_alpha_activity_in_bkg_start\n0105017e=dimen/leanback_setup_alpha_activity_out_bkg_end\n0105017f=dimen/leanback_setup_alpha_activity_out_bkg_start\n01050180=dimen/leanback_setup_alpha_animiation_max_opacity\n01050181=dimen/leanback_setup_alpha_animiation_min_opacity\n01050182=dimen/leanback_setup_alpha_backward_in_content_end\n01050183=dimen/leanback_setup_alpha_backward_in_content_start\n01050184=dimen/leanback_setup_alpha_backward_out_content_end\n01050185=dimen/leanback_setup_alpha_backward_out_content_start\n01050186=dimen/leanback_setup_alpha_forward_in_content_end\n01050187=dimen/leanback_setup_alpha_forward_in_content_start\n01050188=dimen/leanback_setup_alpha_forward_out_content_end\n01050189=dimen/leanback_setup_alpha_forward_out_content_start\n0105018a=dimen/leanback_setup_translation_backward_out_content_end\n0105018b=dimen/leanback_setup_translation_backward_out_content_end_v4\n0105018c=dimen/leanback_setup_translation_backward_out_content_start\n0105018d=dimen/leanback_setup_translation_backward_out_content_start_v4\n0105018e=dimen/leanback_setup_translation_content_cliff\n0105018f=dimen/leanback_setup_translation_content_resting_point\n01050190=dimen/leanback_setup_translation_forward_in_content_end\n01050191=dimen/leanback_setup_translation_forward_in_content_end_v4\n01050192=dimen/leanback_setup_translation_forward_in_content_start\n01050193=dimen/leanback_setup_translation_forward_in_content_start_v4\n01050194=dimen/light_radius\n01050195=dimen/light_y\n01050196=dimen/light_z\n01050197=dimen/list_item_padding_end_material\n01050198=dimen/list_item_padding_horizontal_material\n01050199=dimen/list_item_padding_start_material\n0105019a=dimen/lock_pattern_dot_line_width\n0105019b=dimen/lock_pattern_dot_size\n0105019c=dimen/lock_pattern_dot_size_activated\n0105019d=dimen/media_notification_action_button_size\n0105019e=dimen/media_notification_actions_padding_bottom\n0105019f=dimen/media_notification_expanded_image_margin_bottom\n010501a0=dimen/media_notification_expanded_image_max_size\n010501a1=dimen/media_notification_header_height\n010501a2=dimen/messaging_avatar_size\n010501a3=dimen/messaging_group_sending_progress_size\n010501a4=dimen/messaging_group_singleline_sender_padding_end\n010501a5=dimen/messaging_image_extra_spacing\n010501a6=dimen/messaging_image_max_height\n010501a7=dimen/messaging_image_min_size\n010501a8=dimen/messaging_image_rounding\n010501a9=dimen/messaging_layout_margin_end\n010501aa=dimen/min_xlarge_screen_width\n010501ab=dimen/navigation_bar_frame_height\n010501ac=dimen/navigation_bar_frame_height_landscape\n010501ad=dimen/navigation_bar_gesture_height\n010501ae=dimen/navigation_bar_height\n010501af=dimen/navigation_bar_height_car_mode\n010501b0=dimen/navigation_bar_height_landscape\n010501b1=dimen/navigation_bar_height_landscape_car_mode\n010501b2=dimen/navigation_bar_height_portrait\n010501b3=dimen/navigation_bar_width\n010501b4=dimen/navigation_bar_width_car_mode\n010501b5=dimen/notification_action_disabled_alpha\n010501b6=dimen/notification_action_emphasized_height\n010501b7=dimen/notification_action_list_height\n010501b8=dimen/notification_action_list_margin_top\n010501b9=dimen/notification_alerted_size\n010501ba=dimen/notification_badge_size\n010501bb=dimen/notification_big_picture_max_height\n010501bc=dimen/notification_big_picture_max_height_low_ram\n010501bd=dimen/notification_big_picture_max_width\n010501be=dimen/notification_big_picture_max_width_low_ram\n010501bf=dimen/notification_content_image_margin_end\n010501c0=dimen/notification_content_margin\n010501c1=dimen/notification_content_margin_end\n010501c2=dimen/notification_content_margin_start\n010501c3=dimen/notification_content_margin_top\n010501c4=dimen/notification_conversation_header_separating_margin\n010501c5=dimen/notification_custom_view_max_image_height\n010501c6=dimen/notification_custom_view_max_image_height_low_ram\n010501c7=dimen/notification_custom_view_max_image_width\n010501c8=dimen/notification_custom_view_max_image_width_low_ram\n010501c9=dimen/notification_expand_button_padding_top\n010501ca=dimen/notification_header_app_name_margin_start\n010501cb=dimen/notification_header_background_height\n010501cc=dimen/notification_header_expand_icon_size\n010501cd=dimen/notification_header_height\n010501ce=dimen/notification_header_icon_margin_end\n010501cf=dimen/notification_header_icon_size\n010501d0=dimen/notification_header_icon_size_ambient\n010501d1=dimen/notification_header_margin_bottom\n010501d2=dimen/notification_header_padding_bottom\n010501d3=dimen/notification_header_padding_top\n010501d4=dimen/notification_header_separating_margin\n010501d5=dimen/notification_header_shrink_min_width\n010501d6=dimen/notification_inbox_item_top_padding\n010501d7=dimen/notification_large_icon_circle_padding\n010501d8=dimen/notification_media_image_margin_end\n010501d9=dimen/notification_media_image_max_height\n010501da=dimen/notification_media_image_max_height_low_ram\n010501db=dimen/notification_media_image_max_width\n010501dc=dimen/notification_media_image_max_width_low_ram\n010501dd=dimen/notification_messaging_spacing\n010501de=dimen/notification_min_content_height\n010501df=dimen/notification_min_height\n010501e0=dimen/notification_progress_bar_height\n010501e1=dimen/notification_progress_margin_top\n010501e2=dimen/notification_reply_inset\n010501e3=dimen/notification_right_icon_size\n010501e4=dimen/notification_right_icon_size_low_ram\n010501e5=dimen/notification_secondary_text_disabled_alpha\n010501e6=dimen/notification_subtext_size\n010501e7=dimen/notification_text_margin_top\n010501e8=dimen/notification_text_size\n010501e9=dimen/notification_title_text_size\n010501ea=dimen/notification_top_pad\n010501eb=dimen/notification_top_pad_large_text\n010501ec=dimen/notification_top_pad_large_text_narrow\n010501ed=dimen/notification_top_pad_narrow\n010501ee=dimen/password_keyboard_height\n010501ef=dimen/password_keyboard_horizontalGap\n010501f0=dimen/password_keyboard_key_height_alpha\n010501f1=dimen/password_keyboard_key_height_numeric\n010501f2=dimen/password_keyboard_spacebar_vertical_correction\n010501f3=dimen/password_keyboard_verticalGap\n010501f4=dimen/picker_bottom_margin\n010501f5=dimen/picker_top_margin\n010501f6=dimen/pip_minimized_visible_size\n010501f7=dimen/preference_breadcrumb_paddingLeft\n010501f8=dimen/preference_breadcrumb_paddingRight\n010501f9=dimen/preference_breadcrumbs_padding_end_material\n010501fa=dimen/preference_breadcrumbs_padding_start_material\n010501fb=dimen/preference_child_padding_side\n010501fc=dimen/preference_fragment_padding_bottom\n010501fd=dimen/preference_fragment_padding_side\n010501fe=dimen/preference_fragment_padding_side_material\n010501ff=dimen/preference_fragment_padding_vertical_material\n01050200=dimen/preference_icon_minWidth\n01050201=dimen/preference_item_padding_inner\n01050202=dimen/preference_item_padding_side\n01050203=dimen/preference_screen_bottom_margin\n01050204=dimen/preference_screen_header_padding_side\n01050205=dimen/preference_screen_header_padding_side_material\n01050206=dimen/preference_screen_header_vertical_padding\n01050207=dimen/preference_screen_header_vertical_padding_material\n01050208=dimen/preference_screen_side_margin\n01050209=dimen/preference_screen_side_margin_material\n0105020a=dimen/preference_screen_side_margin_negative\n0105020b=dimen/preference_screen_side_margin_negative_material\n0105020c=dimen/preference_screen_top_margin\n0105020d=dimen/preference_widget_width\n0105020e=dimen/primary_content_alpha_device_default\n0105020f=dimen/primary_content_alpha_material_dark\n01050210=dimen/primary_content_alpha_material_light\n01050211=dimen/progress_bar_corner_material\n01050212=dimen/progress_bar_height_material\n01050213=dimen/progress_bar_size_large\n01050214=dimen/progress_bar_size_medium\n01050215=dimen/progress_bar_size_small\n01050216=dimen/quick_qs_offset_height\n01050217=dimen/quick_qs_total_height\n01050218=dimen/quick_qs_total_height_with_media\n01050219=dimen/resize_shadow_size\n0105021a=dimen/resolver_badge_size\n0105021b=dimen/resolver_button_bar_spacing\n0105021c=dimen/resolver_edge_margin\n0105021d=dimen/resolver_elevation\n0105021e=dimen/resolver_empty_state_container_padding_bottom\n0105021f=dimen/resolver_empty_state_container_padding_top\n01050220=dimen/resolver_empty_state_height\n01050221=dimen/resolver_empty_state_height_with_tabs\n01050222=dimen/resolver_icon_margin\n01050223=dimen/resolver_icon_size\n01050224=dimen/resolver_max_collapsed_height\n01050225=dimen/resolver_max_collapsed_height_with_default\n01050226=dimen/resolver_max_collapsed_height_with_default_with_tabs\n01050227=dimen/resolver_max_collapsed_height_with_tabs\n01050228=dimen/resolver_max_width\n01050229=dimen/resolver_small_margin\n0105022a=dimen/resolver_tab_text_size\n0105022b=dimen/resolver_title_padding_bottom\n0105022c=dimen/restricted_icon_size_material\n0105022d=dimen/rounded_corner_radius\n0105022e=dimen/rounded_corner_radius_adjustment\n0105022f=dimen/rounded_corner_radius_bottom\n01050230=dimen/rounded_corner_radius_bottom_adjustment\n01050231=dimen/rounded_corner_radius_top\n01050232=dimen/rounded_corner_radius_top_adjustment\n01050233=dimen/screen_percentage_05\n01050234=dimen/screen_percentage_10\n01050235=dimen/screen_percentage_12\n01050236=dimen/screen_percentage_15\n01050237=dimen/search_view_preferred_height\n01050238=dimen/search_view_preferred_width\n01050239=dimen/secondary_content_alpha_device_default\n0105023a=dimen/secondary_content_alpha_material_dark\n0105023b=dimen/secondary_content_alpha_material_light\n0105023c=dimen/seekbar_thumb_exclusion_max_size\n0105023d=dimen/seekbar_track_background_height_material\n0105023e=dimen/seekbar_track_progress_height_material\n0105023f=dimen/select_dialog_drawable_padding_start_material\n01050240=dimen/select_dialog_padding_start_material\n01050241=dimen/slice_icon_size\n01050242=dimen/slice_padding\n01050243=dimen/slice_shortcut_size\n01050244=dimen/spot_shadow_alpha\n01050245=dimen/status_bar_content_number_size\n01050246=dimen/status_bar_edge_ignore\n01050247=dimen/status_bar_height\n01050248=dimen/status_bar_height_landscape\n01050249=dimen/status_bar_height_portrait\n0105024a=dimen/status_bar_icon_size\n0105024b=dimen/status_bar_system_icon_intrinsic_size\n0105024c=dimen/status_bar_system_icon_size\n0105024d=dimen/subtitle_corner_radius\n0105024e=dimen/subtitle_outline_width\n0105024f=dimen/subtitle_shadow_offset\n01050250=dimen/subtitle_shadow_radius\n01050251=dimen/task_height_of_minimized_mode\n01050252=dimen/text_edit_floating_toolbar_elevation\n01050253=dimen/text_edit_floating_toolbar_margin\n01050254=dimen/text_handle_min_size\n01050255=dimen/text_line_spacing_multiplier_material\n01050256=dimen/text_size_body_1_material\n01050257=dimen/text_size_body_2_material\n01050258=dimen/text_size_button_material\n01050259=dimen/text_size_caption_material\n0105025a=dimen/text_size_display_1_material\n0105025b=dimen/text_size_display_2_material\n0105025c=dimen/text_size_display_3_material\n0105025d=dimen/text_size_display_4_material\n0105025e=dimen/text_size_headline_material\n0105025f=dimen/text_size_large_material\n01050260=dimen/text_size_medium_material\n01050261=dimen/text_size_menu_header_material\n01050262=dimen/text_size_menu_material\n01050263=dimen/text_size_small_material\n01050264=dimen/text_size_subhead_material\n01050265=dimen/text_size_subtitle_material_toolbar\n01050266=dimen/text_size_title_material\n01050267=dimen/text_size_title_material_toolbar\n01050268=dimen/text_view_end_margin\n01050269=dimen/text_view_start_margin\n0105026a=dimen/textview_error_popup_default_width\n0105026b=dimen/timepicker_am_top_padding\n0105026c=dimen/timepicker_ampm_horizontal_padding\n0105026d=dimen/timepicker_ampm_label_size\n0105026e=dimen/timepicker_center_dot_radius\n0105026f=dimen/timepicker_edit_text_size\n01050270=dimen/timepicker_header_height\n01050271=dimen/timepicker_left_side_width\n01050272=dimen/timepicker_pm_top_padding\n01050273=dimen/timepicker_radial_picker_dimen\n01050274=dimen/timepicker_radial_picker_horizontal_margin\n01050275=dimen/timepicker_radial_picker_top_margin\n01050276=dimen/timepicker_selector_dot_radius\n01050277=dimen/timepicker_selector_radius\n01050278=dimen/timepicker_selector_stroke\n01050279=dimen/timepicker_separator_padding\n0105027a=dimen/timepicker_text_inset_inner\n0105027b=dimen/timepicker_text_inset_normal\n0105027c=dimen/timepicker_text_size_inner\n0105027d=dimen/timepicker_text_size_normal\n0105027e=dimen/timepicker_time_label_size\n0105027f=dimen/toast_y_offset\n01050280=dimen/tooltip_corner_radius\n01050281=dimen/tooltip_horizontal_padding\n01050282=dimen/tooltip_margin\n01050283=dimen/tooltip_precise_anchor_extra_offset\n01050284=dimen/tooltip_precise_anchor_threshold\n01050285=dimen/tooltip_vertical_padding\n01050286=dimen/tooltip_y_offset_non_touch\n01050287=dimen/tooltip_y_offset_touch\n01050288=dimen/waterfall_display_bottom_edge_size\n01050289=dimen/waterfall_display_left_edge_size\n0105028a=dimen/waterfall_display_right_edge_size\n0105028b=dimen/waterfall_display_top_edge_size\n01060000=color/darker_gray\n01060001=color/primary_text_dark\n01060002=color/primary_text_dark_nodisable\n01060003=color/primary_text_light\n01060004=color/primary_text_light_nodisable\n01060005=color/secondary_text_dark\n01060006=color/secondary_text_dark_nodisable\n01060007=color/secondary_text_light\n01060008=color/secondary_text_light_nodisable\n01060009=color/tab_indicator_text\n0106000a=color/widget_edittext_dark\n0106000b=color/white\n0106000c=color/black\n0106000d=color/transparent\n0106000e=color/background_dark\n0106000f=color/background_light\n01060010=color/tertiary_text_dark\n01060011=color/tertiary_text_light\n01060012=color/holo_blue_light\n01060013=color/holo_blue_dark\n01060014=color/holo_green_light\n01060015=color/holo_green_dark\n01060016=color/holo_red_light\n01060017=color/holo_red_dark\n01060018=color/holo_orange_light\n01060019=color/holo_orange_dark\n0106001a=color/holo_purple\n0106001b=color/holo_blue_bright\n0106001c=color/system_notification_accent_color\n0106001d=color/Blue_700\n0106001e=color/Blue_800\n0106001f=color/GM2_grey_800\n01060020=color/Indigo_700\n01060021=color/Indigo_800\n01060022=color/Pink_700\n01060023=color/Pink_800\n01060024=color/Purple_700\n01060025=color/Purple_800\n01060026=color/Red_700\n01060027=color/Red_800\n01060028=color/Teal_700\n01060029=color/Teal_800\n0106002a=color/accent_device_default\n0106002b=color/accent_device_default_50\n0106002c=color/accent_device_default_700\n0106002d=color/accent_device_default_dark\n0106002e=color/accent_device_default_dark_60_percent_opacity\n0106002f=color/accent_device_default_light\n01060030=color/accent_material_dark\n01060031=color/accent_material_light\n01060032=color/accessibility_focus_highlight\n01060033=color/autofill_background_material_dark\n01060034=color/autofill_background_material_light\n01060035=color/autofilled_highlight\n01060036=color/background_cache_hint_selector_device_default\n01060037=color/background_cache_hint_selector_holo_dark\n01060038=color/background_cache_hint_selector_holo_light\n01060039=color/background_cache_hint_selector_material_dark\n0106003a=color/background_cache_hint_selector_material_light\n0106003b=color/background_device_default_dark\n0106003c=color/background_device_default_light\n0106003d=color/background_floating_device_default_dark\n0106003e=color/background_floating_device_default_light\n0106003f=color/background_floating_material_dark\n01060040=color/background_floating_material_light\n01060041=color/background_holo_dark\n01060042=color/background_holo_light\n01060043=color/background_leanback_dark\n01060044=color/background_leanback_light\n01060045=color/background_material_dark\n01060046=color/background_material_light\n01060047=color/bright_foreground_dark\n01060048=color/bright_foreground_dark_disabled\n01060049=color/bright_foreground_dark_inverse\n0106004a=color/bright_foreground_disabled_holo_dark\n0106004b=color/bright_foreground_disabled_holo_light\n0106004c=color/bright_foreground_holo_dark\n0106004d=color/bright_foreground_holo_light\n0106004e=color/bright_foreground_inverse_holo_dark\n0106004f=color/bright_foreground_inverse_holo_light\n01060050=color/bright_foreground_light\n01060051=color/bright_foreground_light_disabled\n01060052=color/bright_foreground_light_inverse\n01060053=color/btn_colored_background_material\n01060054=color/btn_colored_borderless_text_material\n01060055=color/btn_colored_text_material\n01060056=color/btn_default_material_dark\n01060057=color/btn_default_material_light\n01060058=color/btn_watch_default_dark\n01060059=color/button_material_dark\n0106005a=color/button_material_light\n0106005b=color/button_normal_device_default_dark\n0106005c=color/car_accent\n0106005d=color/car_accent_dark\n0106005e=color/car_accent_light\n0106005f=color/car_action1\n01060060=color/car_action1_dark\n01060061=color/car_action1_light\n01060062=color/car_background\n01060063=color/car_blue_100\n01060064=color/car_blue_200\n01060065=color/car_blue_300\n01060066=color/car_blue_400\n01060067=color/car_blue_50\n01060068=color/car_blue_500\n01060069=color/car_blue_600\n0106006a=color/car_blue_700\n0106006b=color/car_blue_800\n0106006c=color/car_blue_900\n0106006d=color/car_blue_grey_800\n0106006e=color/car_blue_grey_900\n0106006f=color/car_body1\n01060070=color/car_body1_dark\n01060071=color/car_body1_light\n01060072=color/car_body2\n01060073=color/car_body2_dark\n01060074=color/car_body2_light\n01060075=color/car_body3\n01060076=color/car_body3_dark\n01060077=color/car_body3_light\n01060078=color/car_body4\n01060079=color/car_body4_dark\n0106007a=color/car_body4_light\n0106007b=color/car_borderless_button_text_color\n0106007c=color/car_button_text_color\n0106007d=color/car_card\n0106007e=color/car_card_dark\n0106007f=color/car_card_light\n01060080=color/car_card_ripple_background\n01060081=color/car_card_ripple_background_dark\n01060082=color/car_card_ripple_background_inverse\n01060083=color/car_card_ripple_background_light\n01060084=color/car_colorPrimary\n01060085=color/car_colorPrimaryDark\n01060086=color/car_colorSecondary\n01060087=color/car_cyan_100\n01060088=color/car_cyan_200\n01060089=color/car_cyan_300\n0106008a=color/car_cyan_400\n0106008b=color/car_cyan_50\n0106008c=color/car_cyan_500\n0106008d=color/car_cyan_600\n0106008e=color/car_cyan_700\n0106008f=color/car_cyan_800\n01060090=color/car_cyan_900\n01060091=color/car_dark_blue_grey_1000\n01060092=color/car_dark_blue_grey_600\n01060093=color/car_dark_blue_grey_700\n01060094=color/car_dark_blue_grey_800\n01060095=color/car_dark_blue_grey_900\n01060096=color/car_green_100\n01060097=color/car_green_200\n01060098=color/car_green_300\n01060099=color/car_green_400\n0106009a=color/car_green_50\n0106009b=color/car_green_500\n0106009c=color/car_green_600\n0106009d=color/car_green_700\n0106009e=color/car_green_800\n0106009f=color/car_green_900\n010600a0=color/car_grey_100\n010600a1=color/car_grey_1000\n010600a2=color/car_grey_200\n010600a3=color/car_grey_300\n010600a4=color/car_grey_400\n010600a5=color/car_grey_50\n010600a6=color/car_grey_500\n010600a7=color/car_grey_600\n010600a8=color/car_grey_700\n010600a9=color/car_grey_746\n010600aa=color/car_grey_772\n010600ab=color/car_grey_800\n010600ac=color/car_grey_846\n010600ad=color/car_grey_868\n010600ae=color/car_grey_900\n010600af=color/car_grey_928\n010600b0=color/car_grey_958\n010600b1=color/car_grey_972\n010600b2=color/car_headline1\n010600b3=color/car_headline1_dark\n010600b4=color/car_headline1_light\n010600b5=color/car_headline2\n010600b6=color/car_headline2_dark\n010600b7=color/car_headline2_light\n010600b8=color/car_headline3\n010600b9=color/car_headline3_dark\n010600ba=color/car_headline3_light\n010600bb=color/car_headline4\n010600bc=color/car_headline4_dark\n010600bd=color/car_headline4_light\n010600be=color/car_highlight\n010600bf=color/car_highlight_dark\n010600c0=color/car_highlight_light\n010600c1=color/car_keyboard_divider_line\n010600c2=color/car_keyboard_text_primary_color\n010600c3=color/car_keyboard_text_secondary_color\n010600c4=color/car_light_blue_300\n010600c5=color/car_light_blue_500\n010600c6=color/car_light_blue_600\n010600c7=color/car_light_blue_700\n010600c8=color/car_light_blue_800\n010600c9=color/car_light_blue_900\n010600ca=color/car_list_divider\n010600cb=color/car_list_divider_dark\n010600cc=color/car_list_divider_light\n010600cd=color/car_orange_100\n010600ce=color/car_orange_200\n010600cf=color/car_orange_300\n010600d0=color/car_orange_400\n010600d1=color/car_orange_50\n010600d2=color/car_orange_500\n010600d3=color/car_orange_600\n010600d4=color/car_orange_700\n010600d5=color/car_orange_800\n010600d6=color/car_orange_900\n010600d7=color/car_pink_100\n010600d8=color/car_pink_200\n010600d9=color/car_pink_300\n010600da=color/car_pink_400\n010600db=color/car_pink_50\n010600dc=color/car_pink_500\n010600dd=color/car_pink_600\n010600de=color/car_pink_700\n010600df=color/car_pink_800\n010600e0=color/car_pink_900\n010600e1=color/car_purple_100\n010600e2=color/car_purple_200\n010600e3=color/car_purple_300\n010600e4=color/car_purple_400\n010600e5=color/car_purple_50\n010600e6=color/car_purple_500\n010600e7=color/car_purple_600\n010600e8=color/car_purple_700\n010600e9=color/car_purple_800\n010600ea=color/car_purple_900\n010600eb=color/car_red_100\n010600ec=color/car_red_200\n010600ed=color/car_red_300\n010600ee=color/car_red_400\n010600ef=color/car_red_50\n010600f0=color/car_red_500\n010600f1=color/car_red_500a\n010600f2=color/car_red_600\n010600f3=color/car_red_700\n010600f4=color/car_red_800\n010600f5=color/car_red_900\n010600f6=color/car_red_a700\n010600f7=color/car_scrollbar_thumb\n010600f8=color/car_scrollbar_thumb_dark\n010600f9=color/car_scrollbar_thumb_inverse\n010600fa=color/car_scrollbar_thumb_light\n010600fb=color/car_seekbar_track_background\n010600fc=color/car_seekbar_track_background_dark\n010600fd=color/car_seekbar_track_background_light\n010600fe=color/car_seekbar_track_secondary_progress\n010600ff=color/car_switch\n01060100=color/car_teal_100\n01060101=color/car_teal_200\n01060102=color/car_teal_300\n01060103=color/car_teal_400\n01060104=color/car_teal_50\n01060105=color/car_teal_500\n01060106=color/car_teal_600\n01060107=color/car_teal_700\n01060108=color/car_teal_800\n01060109=color/car_teal_900\n0106010a=color/car_tint\n0106010b=color/car_tint_dark\n0106010c=color/car_tint_inverse\n0106010d=color/car_tint_light\n0106010e=color/car_title\n0106010f=color/car_title2\n01060110=color/car_title2_dark\n01060111=color/car_title2_light\n01060112=color/car_title_dark\n01060113=color/car_title_light\n01060114=color/car_toast_background\n01060115=color/car_user_switcher_user_image_bgcolor\n01060116=color/car_user_switcher_user_image_fgcolor\n01060117=color/car_white_1000\n01060118=color/car_yellow_100\n01060119=color/car_yellow_200\n0106011a=color/car_yellow_300\n0106011b=color/car_yellow_400\n0106011c=color/car_yellow_50\n0106011d=color/car_yellow_500\n0106011e=color/car_yellow_600\n0106011f=color/car_yellow_700\n01060120=color/car_yellow_800\n01060121=color/car_yellow_900\n01060122=color/chooser_gradient_background\n01060123=color/chooser_gradient_highlight\n01060124=color/chooser_row_divider\n01060125=color/config_defaultNotificationColor\n01060126=color/config_progress_background_tint\n01060127=color/control_checkable_material\n01060128=color/control_default_material\n01060129=color/control_highlight_material\n0106012a=color/conversation_important_highlight\n0106012b=color/datepicker_default_circle_background_color_material_dark\n0106012c=color/datepicker_default_circle_background_color_material_light\n0106012d=color/datepicker_default_disabled_text_color_material_dark\n0106012e=color/datepicker_default_disabled_text_color_material_light\n0106012f=color/datepicker_default_header_dayofweek_background_color_material_dark\n01060130=color/datepicker_default_header_dayofweek_background_color_material_light\n01060131=color/datepicker_default_header_selector_background_material_dark\n01060132=color/datepicker_default_header_selector_background_material_light\n01060133=color/datepicker_default_normal_text_color_material_dark\n01060134=color/datepicker_default_normal_text_color_material_light\n01060135=color/datepicker_default_pressed_text_color_material_dark\n01060136=color/datepicker_default_pressed_text_color_material_light\n01060137=color/datepicker_default_selected_text_color_material_dark\n01060138=color/datepicker_default_selected_text_color_material_light\n01060139=color/datepicker_default_view_animator_color_material_dark\n0106013a=color/datepicker_default_view_animator_color_material_light\n0106013b=color/decor_button_dark_color\n0106013c=color/decor_button_light_color\n0106013d=color/decor_view_status_guard\n0106013e=color/decor_view_status_guard_light\n0106013f=color/default_magnifier_color_overlay\n01060140=color/dim_foreground_dark\n01060141=color/dim_foreground_dark_disabled\n01060142=color/dim_foreground_dark_inverse\n01060143=color/dim_foreground_dark_inverse_disabled\n01060144=color/dim_foreground_disabled_holo_dark\n01060145=color/dim_foreground_disabled_holo_light\n01060146=color/dim_foreground_holo_dark\n01060147=color/dim_foreground_holo_light\n01060148=color/dim_foreground_inverse_disabled_holo_dark\n01060149=color/dim_foreground_inverse_disabled_holo_light\n0106014a=color/dim_foreground_inverse_holo_dark\n0106014b=color/dim_foreground_inverse_holo_light\n0106014c=color/dim_foreground_light\n0106014d=color/dim_foreground_light_disabled\n0106014e=color/dim_foreground_light_inverse\n0106014f=color/dim_foreground_light_inverse_disabled\n01060150=color/edge_effect_device_default_dark\n01060151=color/edge_effect_device_default_light\n01060152=color/error_color_device_default_dark\n01060153=color/error_color_device_default_light\n01060154=color/error_color_material_dark\n01060155=color/error_color_material_light\n01060156=color/facelock_spotlight_mask\n01060157=color/floating_popup_divider_dark\n01060158=color/floating_popup_divider_light\n01060159=color/foreground_device_default_dark\n0106015a=color/foreground_material_dark\n0106015b=color/foreground_material_light\n0106015c=color/group_button_dialog_focused_holo_dark\n0106015d=color/group_button_dialog_focused_holo_light\n0106015e=color/group_button_dialog_pressed_holo_dark\n0106015f=color/group_button_dialog_pressed_holo_light\n01060160=color/highlighted_text_dark\n01060161=color/highlighted_text_holo_dark\n01060162=color/highlighted_text_holo_light\n01060163=color/highlighted_text_light\n01060164=color/highlighted_text_material\n01060165=color/hint_foreground_dark\n01060166=color/hint_foreground_holo_dark\n01060167=color/hint_foreground_holo_light\n01060168=color/hint_foreground_light\n01060169=color/hint_foreground_material_dark\n0106016a=color/hint_foreground_material_light\n0106016b=color/holo_button_normal\n0106016c=color/holo_button_pressed\n0106016d=color/holo_control_activated\n0106016e=color/holo_control_normal\n0106016f=color/holo_gray_bright\n01060170=color/holo_gray_light\n01060171=color/holo_light_button_normal\n01060172=color/holo_light_button_pressed\n01060173=color/holo_light_control_activated\n01060174=color/holo_light_control_normal\n01060175=color/holo_light_primary\n01060176=color/holo_light_primary_dark\n01060177=color/holo_primary\n01060178=color/holo_primary_dark\n01060179=color/instant_app_badge\n0106017a=color/keyguard_avatar_frame_color\n0106017b=color/keyguard_avatar_frame_pressed_color\n0106017c=color/keyguard_avatar_frame_shadow_color\n0106017d=color/keyguard_avatar_nick_color\n0106017e=color/keyguard_text_color_decline\n0106017f=color/keyguard_text_color_normal\n01060180=color/keyguard_text_color_soundoff\n01060181=color/keyguard_text_color_soundon\n01060182=color/keyguard_text_color_unlock\n01060183=color/kg_multi_user_text_active\n01060184=color/kg_multi_user_text_inactive\n01060185=color/kg_widget_pager_gradient\n01060186=color/legacy_button_normal\n01060187=color/legacy_button_pressed\n01060188=color/legacy_control_activated\n01060189=color/legacy_control_normal\n0106018a=color/legacy_green\n0106018b=color/legacy_light_button_normal\n0106018c=color/legacy_light_button_pressed\n0106018d=color/legacy_light_control_activated\n0106018e=color/legacy_light_control_normal\n0106018f=color/legacy_light_primary\n01060190=color/legacy_light_primary_dark\n01060191=color/legacy_long_pressed_highlight\n01060192=color/legacy_orange\n01060193=color/legacy_pressed_highlight\n01060194=color/legacy_primary\n01060195=color/legacy_primary_dark\n01060196=color/legacy_selected_highlight\n01060197=color/lighter_gray\n01060198=color/link_text_dark\n01060199=color/link_text_holo_dark\n0106019a=color/link_text_holo_light\n0106019b=color/link_text_light\n0106019c=color/list_divider_color_dark\n0106019d=color/list_divider_color_light\n0106019e=color/list_divider_opacity_device_default_dark\n0106019f=color/list_divider_opacity_device_default_light\n010601a0=color/list_divider_opacity_material\n010601a1=color/list_highlight_material\n010601a2=color/loading_gradient_background_color_dark\n010601a3=color/loading_gradient_background_color_light\n010601a4=color/loading_gradient_highlight_color_dark\n010601a5=color/loading_gradient_highlight_color_light\n010601a6=color/lock_pattern_view_regular_color\n010601a7=color/lock_pattern_view_success_color\n010601a8=color/lockscreen_clock_am_pm\n010601a9=color/lockscreen_clock_background\n010601aa=color/lockscreen_clock_foreground\n010601ab=color/lockscreen_owner_info\n010601ac=color/material_blue_grey_200\n010601ad=color/material_blue_grey_700\n010601ae=color/material_blue_grey_800\n010601af=color/material_blue_grey_900\n010601b0=color/material_blue_grey_950\n010601b1=color/material_deep_teal_100\n010601b2=color/material_deep_teal_200\n010601b3=color/material_deep_teal_300\n010601b4=color/material_deep_teal_500\n010601b5=color/material_grey_100\n010601b6=color/material_grey_200\n010601b7=color/material_grey_300\n010601b8=color/material_grey_50\n010601b9=color/material_grey_600\n010601ba=color/material_grey_800\n010601bb=color/material_grey_850\n010601bc=color/material_grey_900\n010601bd=color/material_red_A100\n010601be=color/material_red_A700\n010601bf=color/micro_text_light\n010601c0=color/navigation_bar_divider_device_default_settings\n010601c1=color/notification_action_button_text_color\n010601c2=color/notification_action_list\n010601c3=color/notification_action_list_background_color\n010601c4=color/notification_default_color\n010601c5=color/notification_default_color_dark\n010601c6=color/notification_default_color_light\n010601c7=color/notification_material_background_color\n010601c8=color/notification_primary_text_color_dark\n010601c9=color/notification_primary_text_color_light\n010601ca=color/notification_progress_background_color\n010601cb=color/notification_secondary_text_color_dark\n010601cc=color/notification_secondary_text_color_light\n010601cd=color/perms_dangerous_grp_color\n010601ce=color/perms_dangerous_perm_color\n010601cf=color/personal_apps_suspension_notification_color\n010601d0=color/primary_dark_device_default_dark\n010601d1=color/primary_dark_device_default_light\n010601d2=color/primary_dark_device_default_settings\n010601d3=color/primary_dark_device_default_settings_light\n010601d4=color/primary_dark_material_dark\n010601d5=color/primary_dark_material_light\n010601d6=color/primary_dark_material_light_light_status_bar\n010601d7=color/primary_dark_material_settings\n010601d8=color/primary_dark_material_settings_light\n010601d9=color/primary_device_default_dark\n010601da=color/primary_device_default_light\n010601db=color/primary_device_default_settings\n010601dc=color/primary_device_default_settings_light\n010601dd=color/primary_material_dark\n010601de=color/primary_material_light\n010601df=color/primary_material_settings\n010601e0=color/primary_material_settings_light\n010601e1=color/primary_text_dark_disable_only\n010601e2=color/primary_text_dark_focused\n010601e3=color/primary_text_default_material_dark\n010601e4=color/primary_text_default_material_light\n010601e5=color/primary_text_disable_only_holo_dark\n010601e6=color/primary_text_disable_only_holo_light\n010601e7=color/primary_text_disable_only_material_dark\n010601e8=color/primary_text_disable_only_material_light\n010601e9=color/primary_text_focused_holo_dark\n010601ea=color/primary_text_holo_dark\n010601eb=color/primary_text_holo_light\n010601ec=color/primary_text_inverse_when_activated_material\n010601ed=color/primary_text_leanback_dark\n010601ee=color/primary_text_leanback_formwizard_dark\n010601ef=color/primary_text_leanback_formwizard_default_dark\n010601f0=color/primary_text_leanback_light\n010601f1=color/primary_text_light_disable_only\n010601f2=color/primary_text_material_dark\n010601f3=color/primary_text_material_light\n010601f4=color/primary_text_nodisable_holo_dark\n010601f5=color/primary_text_nodisable_holo_light\n010601f6=color/primary_text_secondary_when_activated_material\n010601f7=color/primary_text_secondary_when_activated_material_inverse\n010601f8=color/profile_badge_1\n010601f9=color/profile_badge_1_dark\n010601fa=color/profile_badge_2\n010601fb=color/profile_badge_2_dark\n010601fc=color/profile_badge_3\n010601fd=color/profile_badge_3_dark\n010601fe=color/quaternary_device_default_settings\n010601ff=color/quaternary_material_settings\n01060200=color/ratingbar_background_material\n01060201=color/red\n01060202=color/resize_shadow_end_color\n01060203=color/resize_shadow_start_color\n01060204=color/resolver_empty_state_icon\n01060205=color/resolver_empty_state_text\n01060206=color/resolver_text_color_secondary_dark\n01060207=color/ripple_material_dark\n01060208=color/ripple_material_light\n01060209=color/safe_mode_text\n0106020a=color/search_url_text\n0106020b=color/search_url_text_holo\n0106020c=color/search_url_text_material_dark\n0106020d=color/search_url_text_material_light\n0106020e=color/search_url_text_normal\n0106020f=color/search_url_text_pressed\n01060210=color/search_url_text_selected\n01060211=color/search_widget_corpus_item_background\n01060212=color/secondary_device_default_settings\n01060213=color/secondary_device_default_settings_light\n01060214=color/secondary_material_settings\n01060215=color/secondary_material_settings_light\n01060216=color/secondary_text_default_material_dark\n01060217=color/secondary_text_default_material_light\n01060218=color/secondary_text_holo_dark\n01060219=color/secondary_text_holo_light\n0106021a=color/secondary_text_inverse_when_activated_material\n0106021b=color/secondary_text_leanback_dark\n0106021c=color/secondary_text_leanback_light\n0106021d=color/secondary_text_material_dark\n0106021e=color/secondary_text_material_light\n0106021f=color/secondary_text_nodisable_holo_dark\n01060220=color/secondary_text_nodisable_holo_light\n01060221=color/secondary_text_nofocus\n01060222=color/seekbar_track_progress_material\n01060223=color/shadow\n01060224=color/sliding_tab_text_color_active\n01060225=color/sliding_tab_text_color_shadow\n01060226=color/suggestion_highlight_text\n01060227=color/switch_thumb_disabled_material_dark\n01060228=color/switch_thumb_disabled_material_light\n01060229=color/switch_thumb_material_dark\n0106022a=color/switch_thumb_material_light\n0106022b=color/switch_thumb_normal_material_dark\n0106022c=color/switch_thumb_normal_material_light\n0106022d=color/switch_thumb_watch_default_dark\n0106022e=color/switch_track_material\n0106022f=color/switch_track_watch_default_dark\n01060230=color/system_bar_background_semi_transparent\n01060231=color/tab_highlight_material\n01060232=color/tab_indicator_material\n01060233=color/tab_indicator_text_material\n01060234=color/tab_indicator_text_v4\n01060235=color/tertiary_device_default_settings\n01060236=color/tertiary_material_settings\n01060237=color/tertiary_text_holo_dark\n01060238=color/tertiary_text_holo_light\n01060239=color/text_color_primary\n0106023a=color/text_color_secondary\n0106023b=color/timepicker_default_ampm_selected_background_color_material\n0106023c=color/timepicker_default_ampm_unselected_background_color_material\n0106023d=color/timepicker_default_background_material\n0106023e=color/timepicker_default_numbers_background_color_material\n0106023f=color/timepicker_default_selector_color_material\n01060240=color/timepicker_default_text_color_material\n01060241=color/tooltip_background_dark\n01060242=color/tooltip_background_light\n01060243=color/user_icon_1\n01060244=color/user_icon_2\n01060245=color/user_icon_3\n01060246=color/user_icon_4\n01060247=color/user_icon_5\n01060248=color/user_icon_6\n01060249=color/user_icon_7\n0106024a=color/user_icon_8\n0106024b=color/user_icon_default_gray\n0106024c=color/user_icon_default_white\n0106024d=color/white_disabled_material\n01070000=array/emailAddressTypes\n01070001=array/imProtocols\n01070002=array/organizationTypes\n01070003=array/phoneTypes\n01070004=array/postalAddressTypes\n01070005=array/config_keySystemUuidMapping\n01070006=array/carrier_properties\n01070007=array/common_nicknames\n01070008=array/config_allowedGlobalInstantAppSettings\n01070009=array/config_allowedSecureInstantAppSettings\n0107000a=array/config_allowedSystemInstantAppSettings\n0107000b=array/config_ambientBrighteningThresholds\n0107000c=array/config_ambientDarkeningThresholds\n0107000d=array/config_ambientThresholdLevels\n0107000e=array/config_ambientThresholdsOfPeakRefreshRate\n0107000f=array/config_apfEthTypeBlackList\n01070010=array/config_autoBrightnessButtonBacklightValues\n01070011=array/config_autoBrightnessDisplayValuesNits\n01070012=array/config_autoBrightnessKeyboardBacklightValues\n01070013=array/config_autoBrightnessLcdBacklightValues\n01070014=array/config_autoBrightnessLevels\n01070015=array/config_autoRotationTiltTolerance\n01070016=array/config_availableColorModes\n01070017=array/config_backGestureInsetScales\n01070018=array/config_batteryPackageTypeService\n01070019=array/config_batteryPackageTypeSystem\n0107001a=array/config_biometric_sensors\n0107001b=array/config_brightnessThresholdsOfPeakRefreshRate\n0107001c=array/config_calendarDateVibePattern\n0107001d=array/config_callBarringMMI\n0107001e=array/config_callBarringMMI_for_ims\n0107001f=array/config_cdma_dun_supported_types\n01070020=array/config_cdma_home_system\n01070021=array/config_cdma_international_roaming_indicators\n01070022=array/config_cell_retries_per_error_code\n01070023=array/config_clockTickVibePattern\n01070024=array/config_convert_to_emergency_number_map\n01070025=array/config_defaultFirstUserRestrictions\n01070026=array/config_defaultImperceptibleKillingExemptionPkgs\n01070027=array/config_defaultImperceptibleKillingExemptionProcStates\n01070028=array/config_defaultNotificationVibePattern\n01070029=array/config_defaultPinnerServiceFiles\n0107002a=array/config_default_vm_number\n0107002b=array/config_deviceSpecificSystemServices\n0107002c=array/config_disableApkUnlessMatchedSku_skus_list\n0107002d=array/config_disableApksUnlessMatchedSku_apk_list\n0107002e=array/config_disabledUntilUsedPreinstalledImes\n0107002f=array/config_displayCompositionColorModes\n01070030=array/config_displayCompositionColorSpaces\n01070031=array/config_displayWhiteBalanceAmbientColorTemperatures\n01070032=array/config_displayWhiteBalanceBaseThresholds\n01070033=array/config_displayWhiteBalanceDecreaseThresholds\n01070034=array/config_displayWhiteBalanceDisplayColorTemperatures\n01070035=array/config_displayWhiteBalanceDisplayNominalWhite\n01070036=array/config_displayWhiteBalanceDisplayPrimaries\n01070037=array/config_displayWhiteBalanceHighLightAmbientBiases\n01070038=array/config_displayWhiteBalanceHighLightAmbientBrightnesses\n01070039=array/config_displayWhiteBalanceIncreaseThresholds\n0107003a=array/config_displayWhiteBalanceLowLightAmbientBiases\n0107003b=array/config_displayWhiteBalanceLowLightAmbientBrightnesses\n0107003c=array/config_display_no_service_when_sim_unready\n0107003d=array/config_dropboxLowPriorityTags\n0107003e=array/config_emergency_iso_country_codes\n0107003f=array/config_ephemeralResolverPackage\n01070040=array/config_ethernet_interfaces\n01070041=array/config_face_acquire_biometricprompt_ignorelist\n01070042=array/config_face_acquire_enroll_ignorelist\n01070043=array/config_face_acquire_keyguard_ignorelist\n01070044=array/config_face_acquire_vendor_biometricprompt_ignorelist\n01070045=array/config_face_acquire_vendor_enroll_ignorelist\n01070046=array/config_face_acquire_vendor_keyguard_ignorelist\n01070047=array/config_forceQueryablePackages\n01070048=array/config_globalActionsList\n01070049=array/config_hideWhenDisabled_packageNames\n0107004a=array/config_highRefreshRateBlacklist\n0107004b=array/config_integrityRuleProviderPackages\n0107004c=array/config_jitzygoteBootImagePinnerServiceFiles\n0107004d=array/config_keyboardTapVibePattern\n0107004e=array/config_localPrivateDisplayPorts\n0107004f=array/config_locationExtraPackageNames\n01070050=array/config_locationProviderPackageNames\n01070051=array/config_longPressVibePattern\n01070052=array/config_lteDbmThresholds\n01070053=array/config_minimumBrightnessCurveLux\n01070054=array/config_minimumBrightnessCurveNits\n01070055=array/config_mobile_hotspot_provision_app\n01070056=array/config_mobile_tcp_buffers\n01070057=array/config_networkNotifySwitches\n01070058=array/config_networkSupportedKeepaliveCount\n01070059=array/config_nightDisplayColorTemperatureCoefficients\n0107005a=array/config_nightDisplayColorTemperatureCoefficientsNative\n0107005b=array/config_nonBlockableNotificationPackages\n0107005c=array/config_notificationFallbackVibePattern\n0107005d=array/config_notificationMsgPkgsAllowedAsConvos\n0107005e=array/config_notificationSignalExtractors\n0107005f=array/config_oemUsbModeOverride\n01070060=array/config_packagesExemptFromSuspension\n01070061=array/config_priorityOnlyDndExemptPackages\n01070062=array/config_protectedNetworks\n01070063=array/config_restrictedImagesServices\n01070064=array/config_ringtoneEffectUris\n01070065=array/config_safeModeEnabledVibePattern\n01070066=array/config_screenBrighteningThresholds\n01070067=array/config_screenBrightnessBacklight\n01070068=array/config_screenBrightnessNits\n01070069=array/config_screenDarkeningThresholds\n0107006a=array/config_screenThresholdLevels\n0107006b=array/config_scrollBarrierVibePattern\n0107006c=array/config_serialPorts\n0107006d=array/config_sms_enabled_locking_shift_tables\n0107006e=array/config_sms_enabled_single_shift_tables\n0107006f=array/config_statusBarIcons\n01070070=array/config_system_condition_providers\n01070071=array/config_telephonyEuiccDeviceCapabilities\n01070072=array/config_telephonyHardware\n01070073=array/config_testLocationProviders\n01070074=array/config_tether_bluetooth_regexs\n01070075=array/config_tether_dhcp_range\n01070076=array/config_tether_upstream_types\n01070077=array/config_tether_usb_regexs\n01070078=array/config_tether_wifi_p2p_regexs\n01070079=array/config_tether_wifi_regexs\n0107007a=array/config_tether_wimax_regexs\n0107007b=array/config_toastCrossUserPackages\n0107007c=array/config_twoDigitNumberPattern\n0107007d=array/config_usbHostBlacklist\n0107007e=array/config_virtualKeyVibePattern\n0107007f=array/config_vvmSmsFilterRegexes\n01070080=array/config_wakeonlan_supported_interfaces\n01070081=array/config_wearActivityModeRadios\n01070082=array/cross_profile_apps\n01070083=array/dial_string_replace\n01070084=array/disallowed_apps_managed_device\n01070085=array/disallowed_apps_managed_profile\n01070086=array/disallowed_apps_managed_user\n01070087=array/face_acquired_vendor\n01070088=array/face_error_vendor\n01070089=array/fingerprint_acquired_vendor\n0107008a=array/fingerprint_error_vendor\n0107008b=array/imAddressTypes\n0107008c=array/maps_starting_lat_lng\n0107008d=array/maps_starting_zoom\n0107008e=array/networkAttributes\n0107008f=array/network_switch_type_name\n01070090=array/networks_not_clear_data\n01070091=array/no_ems_support_sim_operators\n01070092=array/non_removable_euicc_slots\n01070093=array/preloaded_color_state_lists\n01070094=array/preloaded_drawables\n01070095=array/preloaded_freeform_multi_window_drawables\n01070096=array/radioAttributes\n01070097=array/required_apps_managed_device\n01070098=array/required_apps_managed_profile\n01070099=array/required_apps_managed_user\n0107009a=array/resolver_target_actions_pin\n0107009b=array/resolver_target_actions_unpin\n0107009c=array/sim_colors\n0107009d=array/special_locale_codes\n0107009e=array/special_locale_names\n0107009f=array/supported_locales\n010700a0=array/unloggable_phone_numbers\n010700a1=array/vendor_allowed_personal_apps_org_owned_device\n010700a2=array/vendor_cross_profile_apps\n010700a3=array/vendor_disallowed_apps_managed_device\n010700a4=array/vendor_disallowed_apps_managed_profile\n010700a5=array/vendor_disallowed_apps_managed_user\n010700a6=array/vendor_required_apps_managed_device\n010700a7=array/vendor_required_apps_managed_profile\n010700a8=array/vendor_required_apps_managed_user\n010700a9=array/wfcOperatorErrorAlertMessages\n010700aa=array/wfcOperatorErrorNotificationMessages\n010700ab=array/wfcSpnFormats\n01080000=drawable/alert_dark_frame\n01080001=drawable/alert_light_frame\n01080002=drawable/arrow_down_float\n01080003=drawable/arrow_up_float\n01080004=drawable/btn_default\n01080005=drawable/btn_default_small\n01080006=drawable/btn_dropdown\n01080007=drawable/btn_minus\n01080008=drawable/btn_plus\n01080009=drawable/btn_radio\n0108000a=drawable/btn_star\n0108000b=drawable/btn_star_big_off\n0108000c=drawable/btn_star_big_on\n0108000d=drawable/button_onoff_indicator_on\n0108000e=drawable/button_onoff_indicator_off\n0108000f=drawable/checkbox_off_background\n01080010=drawable/checkbox_on_background\n01080011=drawable/dialog_frame\n01080012=drawable/divider_horizontal_bright\n01080013=drawable/divider_horizontal_textfield\n01080014=drawable/divider_horizontal_dark\n01080015=drawable/divider_horizontal_dim_dark\n01080016=drawable/edit_text\n01080017=drawable/btn_dialog\n01080018=drawable/editbox_background\n01080019=drawable/editbox_background_normal\n0108001a=drawable/editbox_dropdown_dark_frame\n0108001b=drawable/editbox_dropdown_light_frame\n0108001c=drawable/gallery_thumb\n0108001d=drawable/ic_delete\n0108001e=drawable/ic_lock_idle_charging\n0108001f=drawable/ic_lock_idle_lock\n01080020=drawable/ic_lock_idle_low_battery\n01080021=drawable/ic_media_ff\n01080022=drawable/ic_media_next\n01080023=drawable/ic_media_pause\n01080024=drawable/ic_media_play\n01080025=drawable/ic_media_previous\n01080026=drawable/ic_media_rew\n01080027=drawable/ic_dialog_alert\n01080028=drawable/ic_dialog_dialer\n01080029=drawable/ic_dialog_email\n0108002a=drawable/ic_dialog_map\n0108002b=drawable/ic_input_add\n0108002c=drawable/ic_input_delete\n0108002d=drawable/ic_input_get\n0108002e=drawable/ic_lock_idle_alarm\n0108002f=drawable/ic_lock_lock\n01080030=drawable/ic_lock_power_off\n01080031=drawable/ic_lock_silent_mode\n01080032=drawable/ic_lock_silent_mode_off\n01080033=drawable/ic_menu_add\n01080034=drawable/ic_menu_agenda\n01080035=drawable/ic_menu_always_landscape_portrait\n01080036=drawable/ic_menu_call\n01080037=drawable/ic_menu_camera\n01080038=drawable/ic_menu_close_clear_cancel\n01080039=drawable/ic_menu_compass\n0108003a=drawable/ic_menu_crop\n0108003b=drawable/ic_menu_day\n0108003c=drawable/ic_menu_delete\n0108003d=drawable/ic_menu_directions\n0108003e=drawable/ic_menu_edit\n0108003f=drawable/ic_menu_gallery\n01080040=drawable/ic_menu_help\n01080041=drawable/ic_menu_info_details\n01080042=drawable/ic_menu_manage\n01080043=drawable/ic_menu_mapmode\n01080044=drawable/ic_menu_month\n01080045=drawable/ic_menu_more\n01080046=drawable/ic_menu_my_calendar\n01080047=drawable/ic_menu_mylocation\n01080048=drawable/ic_menu_myplaces\n01080049=drawable/ic_menu_preferences\n0108004a=drawable/ic_menu_recent_history\n0108004b=drawable/ic_menu_report_image\n0108004c=drawable/ic_menu_revert\n0108004d=drawable/ic_menu_rotate\n0108004e=drawable/ic_menu_save\n0108004f=drawable/ic_menu_search\n01080050=drawable/ic_menu_send\n01080051=drawable/ic_menu_set_as\n01080052=drawable/ic_menu_share\n01080053=drawable/ic_menu_slideshow\n01080054=drawable/ic_menu_today\n01080055=drawable/ic_menu_upload\n01080056=drawable/ic_menu_upload_you_tube\n01080057=drawable/ic_menu_view\n01080058=drawable/ic_menu_week\n01080059=drawable/ic_menu_zoom\n0108005a=drawable/ic_notification_clear_all\n0108005b=drawable/ic_notification_overlay\n0108005c=drawable/ic_partial_secure\n0108005d=drawable/ic_popup_disk_full\n0108005e=drawable/ic_popup_reminder\n0108005f=drawable/ic_popup_sync\n01080060=drawable/ic_search_category_default\n01080061=drawable/ic_secure\n01080062=drawable/list_selector_background\n01080063=drawable/menu_frame\n01080064=drawable/menu_full_frame\n01080065=drawable/menuitem_background\n01080066=drawable/picture_frame\n01080067=drawable/presence_away\n01080068=drawable/presence_busy\n01080069=drawable/presence_invisible\n0108006a=drawable/presence_offline\n0108006b=drawable/presence_online\n0108006c=drawable/progress_horizontal\n0108006d=drawable/progress_indeterminate_horizontal\n0108006e=drawable/radiobutton_off_background\n0108006f=drawable/radiobutton_on_background\n01080070=drawable/spinner_background\n01080071=drawable/spinner_dropdown_background\n01080072=drawable/star_big_on\n01080073=drawable/star_big_off\n01080074=drawable/star_on\n01080075=drawable/star_off\n01080076=drawable/stat_notify_call_mute\n01080077=drawable/stat_notify_chat\n01080078=drawable/stat_notify_error\n01080079=drawable/stat_notify_more\n0108007a=drawable/stat_notify_sdcard\n0108007b=drawable/stat_notify_sdcard_usb\n0108007c=drawable/stat_notify_sync\n0108007d=drawable/stat_notify_sync_noanim\n0108007e=drawable/stat_notify_voicemail\n0108007f=drawable/stat_notify_missed_call\n01080080=drawable/stat_sys_data_bluetooth\n01080081=drawable/stat_sys_download\n01080082=drawable/stat_sys_download_done\n01080083=drawable/stat_sys_headset\n01080084=drawable/stat_sys_phone_call\n01080085=drawable/stat_sys_phone_call_forward\n01080086=drawable/stat_sys_phone_call_on_hold\n01080087=drawable/stat_sys_speakerphone\n01080088=drawable/stat_sys_upload\n01080089=drawable/stat_sys_upload_done\n0108008a=drawable/stat_sys_warning\n0108008b=drawable/status_bar_item_app_background\n0108008c=drawable/status_bar_item_background\n0108008d=drawable/sym_action_call\n0108008e=drawable/sym_action_chat\n0108008f=drawable/sym_action_email\n01080090=drawable/sym_call_incoming\n01080091=drawable/sym_call_missed\n01080092=drawable/sym_call_outgoing\n01080093=drawable/sym_def_app_icon\n01080094=drawable/sym_contact_card\n01080095=drawable/title_bar\n01080096=drawable/toast_frame\n01080097=drawable/zoom_plate\n01080098=drawable/screen_background_dark\n01080099=drawable/screen_background_light\n0108009a=drawable/bottom_bar\n0108009b=drawable/ic_dialog_info\n0108009c=drawable/ic_menu_sort_alphabetically\n0108009d=drawable/ic_menu_sort_by_size\n0108009e=drawable/$$chooser_direct_share_icon_placeholder__0__0\n0108009f=drawable/$$chooser_direct_share_icon_placeholder__1__0\n010800a0=drawable/$chooser_direct_share_icon_placeholder__0\n010800a1=drawable/$chooser_direct_share_icon_placeholder__1\n010800a2=drawable/$ic_accessibility_color_correction__0\n010800a3=drawable/$ic_accessibility_color_correction__1\n010800a4=drawable/ic_btn_speak_now\n010800a5=drawable/dark_header\n010800a6=drawable/title_bar_tall\n010800a7=drawable/stat_sys_vp_phone_call\n010800a8=drawable/stat_sys_vp_phone_call_on_hold\n010800a9=drawable/screen_background_dark_transparent\n010800aa=drawable/screen_background_light_transparent\n010800ab=drawable/stat_notify_sdcard_prepare\n010800ac=drawable/presence_video_away\n010800ad=drawable/presence_video_busy\n010800ae=drawable/presence_video_online\n010800af=drawable/presence_audio_away\n010800b0=drawable/presence_audio_busy\n010800b1=drawable/presence_audio_online\n010800b2=drawable/dialog_holo_dark_frame\n010800b3=drawable/dialog_holo_light_frame\n010800b4=drawable/ic_info\n010800b5=drawable/$ic_accessibility_color_inversion__0\n010800b6=drawable/$ic_accessibility_color_inversion__1\n010800b7=drawable/$ic_accessibility_color_inversion__2\n010800b8=drawable/$ic_accessibility_magnification__0\n010800b9=drawable/$ic_accessibility_magnification__1\n010800ba=drawable/$ic_accessibility_magnification__2\n010800bb=drawable/ab_bottom_solid_dark_holo\n010800bc=drawable/ab_bottom_solid_inverse_holo\n010800bd=drawable/ab_bottom_solid_light_holo\n010800be=drawable/ab_bottom_transparent_dark_holo\n010800bf=drawable/ab_bottom_transparent_light_holo\n010800c0=drawable/ab_share_pack_holo_dark\n010800c1=drawable/ab_share_pack_holo_light\n010800c2=drawable/ab_share_pack_material\n010800c3=drawable/ab_share_pack_mtrl_alpha\n010800c4=drawable/ab_solid_dark_holo\n010800c5=drawable/ab_solid_light_holo\n010800c6=drawable/ab_solid_shadow_holo\n010800c7=drawable/ab_solid_shadow_material\n010800c8=drawable/ab_solid_shadow_mtrl_alpha\n010800c9=drawable/ab_stacked_solid_dark_holo\n010800ca=drawable/ab_stacked_solid_inverse_holo\n010800cb=drawable/ab_stacked_solid_light_holo\n010800cc=drawable/ab_stacked_transparent_dark_holo\n010800cd=drawable/ab_stacked_transparent_light_holo\n010800ce=drawable/ab_transparent_dark_holo\n010800cf=drawable/ab_transparent_light_holo\n010800d0=drawable/action_bar_background\n010800d1=drawable/action_bar_divider\n010800d2=drawable/action_bar_item_background_material\n010800d3=drawable/activated_background\n010800d4=drawable/activated_background_holo_dark\n010800d5=drawable/activated_background_holo_light\n010800d6=drawable/activated_background_light\n010800d7=drawable/activated_background_material\n010800d8=drawable/activity_title_bar\n010800d9=drawable/alert_window_layer\n010800da=drawable/android_logotype\n010800db=drawable/app_icon_background\n010800dc=drawable/autofill_dataset_picker_background\n010800dd=drawable/autofilled_highlight\n010800de=drawable/background_holo_dark\n010800df=drawable/background_holo_light\n010800e0=drawable/background_leanback_setup\n010800e1=drawable/battery_charge_background\n010800e2=drawable/blank_tile\n010800e3=drawable/bottomsheet_background\n010800e4=drawable/box\n010800e5=drawable/btn_borderless_material\n010800e6=drawable/btn_borderless_rect\n010800e7=drawable/btn_browser_zoom_fit_page\n010800e8=drawable/btn_browser_zoom_page_overview\n010800e9=drawable/btn_cab_done_default_holo_dark\n010800ea=drawable/btn_cab_done_default_holo_light\n010800eb=drawable/btn_cab_done_focused_holo_dark\n010800ec=drawable/btn_cab_done_focused_holo_light\n010800ed=drawable/btn_cab_done_holo_dark\n010800ee=drawable/btn_cab_done_holo_light\n010800ef=drawable/btn_cab_done_pressed_holo_dark\n010800f0=drawable/btn_cab_done_pressed_holo_light\n010800f1=drawable/btn_check\n010800f2=drawable/btn_check_buttonless_off\n010800f3=drawable/btn_check_buttonless_on\n010800f4=drawable/btn_check_holo_dark\n010800f5=drawable/btn_check_holo_light\n010800f6=drawable/btn_check_label_background\n010800f7=drawable/btn_check_material_anim\n010800f8=drawable/btn_check_off\n010800f9=drawable/btn_check_off_disable\n010800fa=drawable/btn_check_off_disable_focused\n010800fb=drawable/btn_check_off_disable_focused_holo_dark\n010800fc=drawable/btn_check_off_disable_focused_holo_light\n010800fd=drawable/btn_check_off_disable_holo_dark\n010800fe=drawable/btn_check_off_disable_holo_light\n010800ff=drawable/btn_check_off_disabled_focused_holo_dark\n01080100=drawable/btn_check_off_disabled_focused_holo_light\n01080101=drawable/btn_check_off_disabled_holo_dark\n01080102=drawable/btn_check_off_disabled_holo_light\n01080103=drawable/btn_check_off_focused_holo_dark\n01080104=drawable/btn_check_off_focused_holo_light\n01080105=drawable/btn_check_off_holo\n01080106=drawable/btn_check_off_holo_dark\n01080107=drawable/btn_check_off_holo_light\n01080108=drawable/btn_check_off_normal_holo_dark\n01080109=drawable/btn_check_off_normal_holo_light\n0108010a=drawable/btn_check_off_pressed\n0108010b=drawable/btn_check_off_pressed_holo_dark\n0108010c=drawable/btn_check_off_pressed_holo_light\n0108010d=drawable/btn_check_off_selected\n0108010e=drawable/btn_check_on\n0108010f=drawable/btn_check_on_disable\n01080110=drawable/btn_check_on_disable_focused\n01080111=drawable/btn_check_on_disable_focused_holo_light\n01080112=drawable/btn_check_on_disable_holo_dark\n01080113=drawable/btn_check_on_disable_holo_light\n01080114=drawable/btn_check_on_disabled_focused_holo_dark\n01080115=drawable/btn_check_on_disabled_focused_holo_light\n01080116=drawable/btn_check_on_disabled_holo_dark\n01080117=drawable/btn_check_on_disabled_holo_light\n01080118=drawable/btn_check_on_focused_holo_dark\n01080119=drawable/btn_check_on_focused_holo_light\n0108011a=drawable/btn_check_on_holo\n0108011b=drawable/btn_check_on_holo_dark\n0108011c=drawable/btn_check_on_holo_light\n0108011d=drawable/btn_check_on_pressed\n0108011e=drawable/btn_check_on_pressed_holo_dark\n0108011f=drawable/btn_check_on_pressed_holo_light\n01080120=drawable/btn_check_on_selected\n01080121=drawable/btn_checkbox_checked_mtrl\n01080122=drawable/btn_checkbox_checked_to_unchecked_mtrl_animation\n01080123=drawable/btn_checkbox_unchecked_mtrl\n01080124=drawable/btn_checkbox_unchecked_to_checked_mtrl_animation\n01080125=drawable/btn_circle\n01080126=drawable/btn_circle_disable\n01080127=drawable/btn_circle_disable_focused\n01080128=drawable/btn_circle_normal\n01080129=drawable/btn_circle_pressed\n0108012a=drawable/btn_circle_selected\n0108012b=drawable/btn_clock_material\n0108012c=drawable/btn_close\n0108012d=drawable/btn_close_normal\n0108012e=drawable/btn_close_pressed\n0108012f=drawable/btn_close_selected\n01080130=drawable/btn_colored_material\n01080131=drawable/btn_default_disabled_focused_holo_dark\n01080132=drawable/btn_default_disabled_focused_holo_light\n01080133=drawable/btn_default_disabled_holo\n01080134=drawable/btn_default_disabled_holo_dark\n01080135=drawable/btn_default_disabled_holo_light\n01080136=drawable/btn_default_focused_holo\n01080137=drawable/btn_default_focused_holo_dark\n01080138=drawable/btn_default_focused_holo_light\n01080139=drawable/btn_default_holo_dark\n0108013a=drawable/btn_default_holo_light\n0108013b=drawable/btn_default_material\n0108013c=drawable/btn_default_mtrl_shape\n0108013d=drawable/btn_default_normal\n0108013e=drawable/btn_default_normal_disable\n0108013f=drawable/btn_default_normal_disable_focused\n01080140=drawable/btn_default_normal_holo\n01080141=drawable/btn_default_normal_holo_dark\n01080142=drawable/btn_default_normal_holo_light\n01080143=drawable/btn_default_pressed\n01080144=drawable/btn_default_pressed_holo\n01080145=drawable/btn_default_pressed_holo_dark\n01080146=drawable/btn_default_pressed_holo_light\n01080147=drawable/btn_default_selected\n01080148=drawable/btn_default_small_normal\n01080149=drawable/btn_default_small_normal_disable\n0108014a=drawable/btn_default_small_normal_disable_focused\n0108014b=drawable/btn_default_small_pressed\n0108014c=drawable/btn_default_small_selected\n0108014d=drawable/btn_default_transparent\n0108014e=drawable/btn_default_transparent_normal\n0108014f=drawable/btn_dialog_disable\n01080150=drawable/btn_dialog_normal\n01080151=drawable/btn_dialog_pressed\n01080152=drawable/btn_dialog_selected\n01080153=drawable/btn_dropdown_disabled\n01080154=drawable/btn_dropdown_disabled_focused\n01080155=drawable/btn_dropdown_normal\n01080156=drawable/btn_dropdown_pressed\n01080157=drawable/btn_dropdown_selected\n01080158=drawable/btn_erase_default\n01080159=drawable/btn_erase_pressed\n0108015a=drawable/btn_erase_selected\n0108015b=drawable/btn_global_search\n0108015c=drawable/btn_global_search_normal\n0108015d=drawable/btn_group_disabled_holo_dark\n0108015e=drawable/btn_group_disabled_holo_light\n0108015f=drawable/btn_group_focused_holo_dark\n01080160=drawable/btn_group_focused_holo_light\n01080161=drawable/btn_group_holo_dark\n01080162=drawable/btn_group_holo_light\n01080163=drawable/btn_group_normal_holo_dark\n01080164=drawable/btn_group_normal_holo_light\n01080165=drawable/btn_group_pressed_holo_dark\n01080166=drawable/btn_group_pressed_holo_light\n01080167=drawable/btn_keyboard_key\n01080168=drawable/btn_keyboard_key_dark_normal_holo\n01080169=drawable/btn_keyboard_key_dark_normal_off_holo\n0108016a=drawable/btn_keyboard_key_dark_normal_on_holo\n0108016b=drawable/btn_keyboard_key_dark_pressed_holo\n0108016c=drawable/btn_keyboard_key_dark_pressed_off_holo\n0108016d=drawable/btn_keyboard_key_dark_pressed_on_holo\n0108016e=drawable/btn_keyboard_key_fulltrans\n0108016f=drawable/btn_keyboard_key_fulltrans_normal\n01080170=drawable/btn_keyboard_key_fulltrans_normal_off\n01080171=drawable/btn_keyboard_key_fulltrans_normal_on\n01080172=drawable/btn_keyboard_key_fulltrans_pressed\n01080173=drawable/btn_keyboard_key_fulltrans_pressed_off\n01080174=drawable/btn_keyboard_key_fulltrans_pressed_on\n01080175=drawable/btn_keyboard_key_ics\n01080176=drawable/btn_keyboard_key_light_normal_holo\n01080177=drawable/btn_keyboard_key_light_pressed_holo\n01080178=drawable/btn_keyboard_key_material\n01080179=drawable/btn_keyboard_key_normal\n0108017a=drawable/btn_keyboard_key_normal_off\n0108017b=drawable/btn_keyboard_key_normal_on\n0108017c=drawable/btn_keyboard_key_pressed\n0108017d=drawable/btn_keyboard_key_pressed_off\n0108017e=drawable/btn_keyboard_key_pressed_on\n0108017f=drawable/btn_keyboard_key_trans\n01080180=drawable/btn_keyboard_key_trans_normal\n01080181=drawable/btn_keyboard_key_trans_normal_off\n01080182=drawable/btn_keyboard_key_trans_normal_on\n01080183=drawable/btn_keyboard_key_trans_pressed\n01080184=drawable/btn_keyboard_key_trans_pressed_off\n01080185=drawable/btn_keyboard_key_trans_pressed_on\n01080186=drawable/btn_keyboard_key_trans_selected\n01080187=drawable/btn_lock_normal\n01080188=drawable/btn_media_player\n01080189=drawable/btn_media_player_disabled\n0108018a=drawable/btn_media_player_disabled_selected\n0108018b=drawable/btn_media_player_pressed\n0108018c=drawable/btn_media_player_selected\n0108018d=drawable/btn_minus_default\n0108018e=drawable/btn_minus_disable\n0108018f=drawable/btn_minus_disable_focused\n01080190=drawable/btn_minus_pressed\n01080191=drawable/btn_minus_selected\n01080192=drawable/btn_notification_emphasized\n01080193=drawable/btn_plus_default\n01080194=drawable/btn_plus_disable\n01080195=drawable/btn_plus_disable_focused\n01080196=drawable/btn_plus_pressed\n01080197=drawable/btn_plus_selected\n01080198=drawable/btn_radio_holo_dark\n01080199=drawable/btn_radio_holo_light\n0108019a=drawable/btn_radio_label_background\n0108019b=drawable/btn_radio_material_anim\n0108019c=drawable/btn_radio_off\n0108019d=drawable/btn_radio_off_disabled_focused_holo_dark\n0108019e=drawable/btn_radio_off_disabled_focused_holo_light\n0108019f=drawable/btn_radio_off_disabled_holo_dark\n010801a0=drawable/btn_radio_off_disabled_holo_light\n010801a1=drawable/btn_radio_off_focused_holo_dark\n010801a2=drawable/btn_radio_off_focused_holo_light\n010801a3=drawable/btn_radio_off_holo\n010801a4=drawable/btn_radio_off_holo_dark\n010801a5=drawable/btn_radio_off_holo_light\n010801a6=drawable/btn_radio_off_mtrl\n010801a7=drawable/btn_radio_off_pressed\n010801a8=drawable/btn_radio_off_pressed_holo_dark\n010801a9=drawable/btn_radio_off_pressed_holo_light\n010801aa=drawable/btn_radio_off_selected\n010801ab=drawable/btn_radio_off_to_on_mtrl_animation\n010801ac=drawable/btn_radio_on\n010801ad=drawable/btn_radio_on_disabled_focused_holo_dark\n010801ae=drawable/btn_radio_on_disabled_focused_holo_light\n010801af=drawable/btn_radio_on_disabled_holo_dark\n010801b0=drawable/btn_radio_on_disabled_holo_light\n010801b1=drawable/btn_radio_on_focused_holo_dark\n010801b2=drawable/btn_radio_on_focused_holo_light\n010801b3=drawable/btn_radio_on_holo\n010801b4=drawable/btn_radio_on_holo_dark\n010801b5=drawable/btn_radio_on_holo_light\n010801b6=drawable/btn_radio_on_mtrl\n010801b7=drawable/btn_radio_on_mtrl_alpha\n010801b8=drawable/btn_radio_on_pressed\n010801b9=drawable/btn_radio_on_pressed_holo_dark\n010801ba=drawable/btn_radio_on_pressed_holo_light\n010801bb=drawable/btn_radio_on_pressed_mtrl_alpha\n010801bc=drawable/btn_radio_on_selected\n010801bd=drawable/btn_radio_on_to_off_mtrl_animation\n010801be=drawable/btn_rating_star_off_disabled_focused_holo_dark\n010801bf=drawable/btn_rating_star_off_disabled_focused_holo_light\n010801c0=drawable/btn_rating_star_off_disabled_holo_dark\n010801c1=drawable/btn_rating_star_off_disabled_holo_light\n010801c2=drawable/btn_rating_star_off_focused_holo_dark\n010801c3=drawable/btn_rating_star_off_focused_holo_light\n010801c4=drawable/btn_rating_star_off_mtrl_alpha\n010801c5=drawable/btn_rating_star_off_normal\n010801c6=drawable/btn_rating_star_off_normal_holo_dark\n010801c7=drawable/btn_rating_star_off_normal_holo_light\n010801c8=drawable/btn_rating_star_off_pressed\n010801c9=drawable/btn_rating_star_off_pressed_holo_dark\n010801ca=drawable/btn_rating_star_off_pressed_holo_light\n010801cb=drawable/btn_rating_star_off_selected\n010801cc=drawable/btn_rating_star_on_disabled_focused_holo_dark\n010801cd=drawable/btn_rating_star_on_disabled_focused_holo_light\n010801ce=drawable/btn_rating_star_on_disabled_holo_dark\n010801cf=drawable/btn_rating_star_on_disabled_holo_light\n010801d0=drawable/btn_rating_star_on_focused_holo_dark\n010801d1=drawable/btn_rating_star_on_focused_holo_light\n010801d2=drawable/btn_rating_star_on_mtrl_alpha\n010801d3=drawable/btn_rating_star_on_normal\n010801d4=drawable/btn_rating_star_on_normal_holo_dark\n010801d5=drawable/btn_rating_star_on_normal_holo_light\n010801d6=drawable/btn_rating_star_on_pressed\n010801d7=drawable/btn_rating_star_on_pressed_holo_dark\n010801d8=drawable/btn_rating_star_on_pressed_holo_light\n010801d9=drawable/btn_rating_star_on_selected\n010801da=drawable/btn_search_dialog\n010801db=drawable/btn_search_dialog_default\n010801dc=drawable/btn_search_dialog_pressed\n010801dd=drawable/btn_search_dialog_selected\n010801de=drawable/btn_search_dialog_voice\n010801df=drawable/btn_search_dialog_voice_default\n010801e0=drawable/btn_search_dialog_voice_pressed\n010801e1=drawable/btn_search_dialog_voice_selected\n010801e2=drawable/btn_square_overlay\n010801e3=drawable/btn_square_overlay_disabled\n010801e4=drawable/btn_square_overlay_disabled_focused\n010801e5=drawable/btn_square_overlay_normal\n010801e6=drawable/btn_square_overlay_pressed\n010801e7=drawable/btn_square_overlay_selected\n010801e8=drawable/btn_star_big_off_disable\n010801e9=drawable/btn_star_big_off_disable_focused\n010801ea=drawable/btn_star_big_off_pressed\n010801eb=drawable/btn_star_big_off_selected\n010801ec=drawable/btn_star_big_on_disable\n010801ed=drawable/btn_star_big_on_disable_focused\n010801ee=drawable/btn_star_big_on_pressed\n010801ef=drawable/btn_star_big_on_selected\n010801f0=drawable/btn_star_holo_dark\n010801f1=drawable/btn_star_holo_light\n010801f2=drawable/btn_star_label_background\n010801f3=drawable/btn_star_material\n010801f4=drawable/btn_star_mtrl_alpha\n010801f5=drawable/btn_star_off_disabled_focused_holo_dark\n010801f6=drawable/btn_star_off_disabled_focused_holo_light\n010801f7=drawable/btn_star_off_disabled_holo_dark\n010801f8=drawable/btn_star_off_disabled_holo_light\n010801f9=drawable/btn_star_off_focused_holo_dark\n010801fa=drawable/btn_star_off_focused_holo_light\n010801fb=drawable/btn_star_off_normal_holo_dark\n010801fc=drawable/btn_star_off_normal_holo_light\n010801fd=drawable/btn_star_off_pressed_holo_dark\n010801fe=drawable/btn_star_off_pressed_holo_light\n010801ff=drawable/btn_star_on_disabled_focused_holo_dark\n01080200=drawable/btn_star_on_disabled_focused_holo_light\n01080201=drawable/btn_star_on_disabled_holo_dark\n01080202=drawable/btn_star_on_disabled_holo_light\n01080203=drawable/btn_star_on_focused_holo_dark\n01080204=drawable/btn_star_on_focused_holo_light\n01080205=drawable/btn_star_on_normal_holo_dark\n01080206=drawable/btn_star_on_normal_holo_light\n01080207=drawable/btn_star_on_pressed_holo_dark\n01080208=drawable/btn_star_on_pressed_holo_light\n01080209=drawable/btn_switch_to_off_mtrl_00001\n0108020a=drawable/btn_switch_to_off_mtrl_00002\n0108020b=drawable/btn_switch_to_off_mtrl_00003\n0108020c=drawable/btn_switch_to_off_mtrl_00004\n0108020d=drawable/btn_switch_to_off_mtrl_00005\n0108020e=drawable/btn_switch_to_off_mtrl_00006\n0108020f=drawable/btn_switch_to_off_mtrl_00007\n01080210=drawable/btn_switch_to_off_mtrl_00008\n01080211=drawable/btn_switch_to_off_mtrl_00009\n01080212=drawable/btn_switch_to_off_mtrl_00010\n01080213=drawable/btn_switch_to_off_mtrl_00011\n01080214=drawable/btn_switch_to_off_mtrl_00012\n01080215=drawable/btn_switch_to_on_mtrl_00001\n01080216=drawable/btn_switch_to_on_mtrl_00002\n01080217=drawable/btn_switch_to_on_mtrl_00003\n01080218=drawable/btn_switch_to_on_mtrl_00004\n01080219=drawable/btn_switch_to_on_mtrl_00005\n0108021a=drawable/btn_switch_to_on_mtrl_00006\n0108021b=drawable/btn_switch_to_on_mtrl_00007\n0108021c=drawable/btn_switch_to_on_mtrl_00008\n0108021d=drawable/btn_switch_to_on_mtrl_00009\n0108021e=drawable/btn_switch_to_on_mtrl_00010\n0108021f=drawable/btn_switch_to_on_mtrl_00011\n01080220=drawable/btn_switch_to_on_mtrl_00012\n01080221=drawable/btn_toggle\n01080222=drawable/btn_toggle_bg\n01080223=drawable/btn_toggle_holo_dark\n01080224=drawable/btn_toggle_holo_light\n01080225=drawable/btn_toggle_material\n01080226=drawable/btn_toggle_off\n01080227=drawable/btn_toggle_off_disabled_focused_holo_dark\n01080228=drawable/btn_toggle_off_disabled_focused_holo_light\n01080229=drawable/btn_toggle_off_disabled_holo_dark\n0108022a=drawable/btn_toggle_off_disabled_holo_light\n0108022b=drawable/btn_toggle_off_focused_holo_dark\n0108022c=drawable/btn_toggle_off_focused_holo_light\n0108022d=drawable/btn_toggle_off_normal_holo_dark\n0108022e=drawable/btn_toggle_off_normal_holo_light\n0108022f=drawable/btn_toggle_off_pressed_holo_dark\n01080230=drawable/btn_toggle_off_pressed_holo_light\n01080231=drawable/btn_toggle_on\n01080232=drawable/btn_toggle_on_disabled_focused_holo_dark\n01080233=drawable/btn_toggle_on_disabled_focused_holo_light\n01080234=drawable/btn_toggle_on_disabled_holo_dark\n01080235=drawable/btn_toggle_on_disabled_holo_light\n01080236=drawable/btn_toggle_on_focused_holo_dark\n01080237=drawable/btn_toggle_on_focused_holo_light\n01080238=drawable/btn_toggle_on_normal_holo_dark\n01080239=drawable/btn_toggle_on_normal_holo_light\n0108023a=drawable/btn_toggle_on_pressed_holo_dark\n0108023b=drawable/btn_toggle_on_pressed_holo_light\n0108023c=drawable/btn_zoom_down\n0108023d=drawable/btn_zoom_down_disabled\n0108023e=drawable/btn_zoom_down_disabled_focused\n0108023f=drawable/btn_zoom_down_normal\n01080240=drawable/btn_zoom_down_pressed\n01080241=drawable/btn_zoom_down_selected\n01080242=drawable/btn_zoom_page\n01080243=drawable/btn_zoom_page_normal\n01080244=drawable/btn_zoom_page_press\n01080245=drawable/btn_zoom_up\n01080246=drawable/btn_zoom_up_disabled\n01080247=drawable/btn_zoom_up_disabled_focused\n01080248=drawable/btn_zoom_up_normal\n01080249=drawable/btn_zoom_up_pressed\n0108024a=drawable/btn_zoom_up_selected\n0108024b=drawable/button_inset\n0108024c=drawable/cab_background_bottom_holo_dark\n0108024d=drawable/cab_background_bottom_holo_light\n0108024e=drawable/cab_background_bottom_material\n0108024f=drawable/cab_background_bottom_mtrl_alpha\n01080250=drawable/cab_background_top_holo_dark\n01080251=drawable/cab_background_top_holo_light\n01080252=drawable/cab_background_top_material\n01080253=drawable/cab_background_top_mtrl_alpha\n01080254=drawable/call_contact\n01080255=drawable/car_button_background\n01080256=drawable/car_checkbox\n01080257=drawable/car_dialog_button_background\n01080258=drawable/car_seekbar_thumb\n01080259=drawable/car_seekbar_thumb_dark\n0108025a=drawable/car_seekbar_thumb_light\n0108025b=drawable/car_seekbar_track\n0108025c=drawable/car_seekbar_track_dark\n0108025d=drawable/car_seekbar_track_light\n0108025e=drawable/car_switch_thumb\n0108025f=drawable/chooser_action_button_bg\n01080260=drawable/chooser_content_preview_rounded\n01080261=drawable/chooser_dialog_background\n01080262=drawable/chooser_direct_share_icon_placeholder\n01080263=drawable/chooser_direct_share_label_placeholder\n01080264=drawable/chooser_file_generic\n01080265=drawable/chooser_group_background\n01080266=drawable/chooser_pinned_background\n01080267=drawable/chooser_row_layer_list\n01080268=drawable/cling_arrow_up\n01080269=drawable/cling_bg\n0108026a=drawable/cling_button\n0108026b=drawable/cling_button_normal\n0108026c=drawable/cling_button_pressed\n0108026d=drawable/clock_dial\n0108026e=drawable/clock_hand_hour\n0108026f=drawable/clock_hand_minute\n01080270=drawable/code_lock_bottom\n01080271=drawable/code_lock_left\n01080272=drawable/code_lock_top\n01080273=drawable/combobox_disabled\n01080274=drawable/combobox_nohighlight\n01080275=drawable/compass_arrow\n01080276=drawable/compass_base\n01080277=drawable/config_scrollbarThumbVertical\n01080278=drawable/config_scrollbarTrackVertical\n01080279=drawable/contact_header_bg\n0108027a=drawable/control_background_32dp_material\n0108027b=drawable/control_background_40dp_material\n0108027c=drawable/conversation_badge_background\n0108027d=drawable/conversation_badge_ring\n0108027e=drawable/conversation_unread_bg\n0108027f=drawable/create_contact\n01080280=drawable/dark_header_dither\n01080281=drawable/day_picker_week_view_dayline_holo\n01080282=drawable/decor_caption_title\n01080283=drawable/decor_caption_title_focused\n01080284=drawable/decor_caption_title_unfocused\n01080285=drawable/decor_close_button_dark\n01080286=drawable/decor_close_button_light\n01080287=drawable/decor_maximize_button_dark\n01080288=drawable/decor_maximize_button_light\n01080289=drawable/default_lock_wallpaper\n0108028a=drawable/default_wallpaper\n0108028b=drawable/dialog_background_material\n0108028c=drawable/dialog_bottom_holo_dark\n0108028d=drawable/dialog_bottom_holo_light\n0108028e=drawable/dialog_divider_horizontal_holo_dark\n0108028f=drawable/dialog_divider_horizontal_holo_light\n01080290=drawable/dialog_divider_horizontal_light\n01080291=drawable/dialog_full_holo_dark\n01080292=drawable/dialog_full_holo_light\n01080293=drawable/dialog_ic_close_focused_holo_dark\n01080294=drawable/dialog_ic_close_focused_holo_light\n01080295=drawable/dialog_ic_close_normal_holo_dark\n01080296=drawable/dialog_ic_close_normal_holo_light\n01080297=drawable/dialog_ic_close_pressed_holo_dark\n01080298=drawable/dialog_ic_close_pressed_holo_light\n01080299=drawable/dialog_middle_holo\n0108029a=drawable/dialog_middle_holo_dark\n0108029b=drawable/dialog_middle_holo_light\n0108029c=drawable/dialog_top_holo_dark\n0108029d=drawable/dialog_top_holo_light\n0108029e=drawable/divider_horizontal_bright_opaque\n0108029f=drawable/divider_horizontal_dark_opaque\n010802a0=drawable/divider_horizontal_holo_dark\n010802a1=drawable/divider_horizontal_holo_light\n010802a2=drawable/divider_strong_holo\n010802a3=drawable/divider_vertical_bright\n010802a4=drawable/divider_vertical_bright_opaque\n010802a5=drawable/divider_vertical_dark\n010802a6=drawable/divider_vertical_dark_opaque\n010802a7=drawable/divider_vertical_holo_dark\n010802a8=drawable/divider_vertical_holo_light\n010802a9=drawable/dropdown_disabled_focused_holo_dark\n010802aa=drawable/dropdown_disabled_focused_holo_light\n010802ab=drawable/dropdown_disabled_holo_dark\n010802ac=drawable/dropdown_disabled_holo_light\n010802ad=drawable/dropdown_focused_holo_dark\n010802ae=drawable/dropdown_focused_holo_light\n010802af=drawable/dropdown_ic_arrow_disabled_focused_holo_dark\n010802b0=drawable/dropdown_ic_arrow_disabled_focused_holo_light\n010802b1=drawable/dropdown_ic_arrow_disabled_holo_dark\n010802b2=drawable/dropdown_ic_arrow_disabled_holo_light\n010802b3=drawable/dropdown_ic_arrow_focused_holo_dark\n010802b4=drawable/dropdown_ic_arrow_focused_holo_light\n010802b5=drawable/dropdown_ic_arrow_normal_holo_dark\n010802b6=drawable/dropdown_ic_arrow_normal_holo_light\n010802b7=drawable/dropdown_ic_arrow_pressed_holo_dark\n010802b8=drawable/dropdown_ic_arrow_pressed_holo_light\n010802b9=drawable/dropdown_normal_holo_dark\n010802ba=drawable/dropdown_normal_holo_light\n010802bb=drawable/dropdown_pressed_holo_dark\n010802bc=drawable/dropdown_pressed_holo_light\n010802bd=drawable/edit_query\n010802be=drawable/edit_query_background\n010802bf=drawable/edit_query_background_normal\n010802c0=drawable/edit_query_background_pressed\n010802c1=drawable/edit_query_background_selected\n010802c2=drawable/edit_text_holo_dark\n010802c3=drawable/edit_text_holo_light\n010802c4=drawable/edit_text_material\n010802c5=drawable/editbox_background_focus_yellow\n010802c6=drawable/editbox_dropdown_background\n010802c7=drawable/editbox_dropdown_background_dark\n010802c8=drawable/emergency_icon\n010802c9=drawable/emo_im_angel\n010802ca=drawable/emo_im_cool\n010802cb=drawable/emo_im_crying\n010802cc=drawable/emo_im_embarrassed\n010802cd=drawable/emo_im_foot_in_mouth\n010802ce=drawable/emo_im_happy\n010802cf=drawable/emo_im_kissing\n010802d0=drawable/emo_im_laughing\n010802d1=drawable/emo_im_lips_are_sealed\n010802d2=drawable/emo_im_money_mouth\n010802d3=drawable/emo_im_sad\n010802d4=drawable/emo_im_surprised\n010802d5=drawable/emo_im_tongue_sticking_out\n010802d6=drawable/emo_im_undecided\n010802d7=drawable/emo_im_winking\n010802d8=drawable/emo_im_wtf\n010802d9=drawable/emo_im_yelling\n010802da=drawable/emulator_circular_window_overlay\n010802db=drawable/expander_close_holo_dark\n010802dc=drawable/expander_close_holo_light\n010802dd=drawable/expander_close_mtrl_alpha\n010802de=drawable/expander_group\n010802df=drawable/expander_group_holo_dark\n010802e0=drawable/expander_group_holo_light\n010802e1=drawable/expander_group_material\n010802e2=drawable/expander_ic_maximized\n010802e3=drawable/expander_ic_minimized\n010802e4=drawable/expander_open_holo_dark\n010802e5=drawable/expander_open_holo_light\n010802e6=drawable/expander_open_mtrl_alpha\n010802e7=drawable/fastscroll_label_left_holo_dark\n010802e8=drawable/fastscroll_label_left_holo_light\n010802e9=drawable/fastscroll_label_left_material\n010802ea=drawable/fastscroll_label_right_holo_dark\n010802eb=drawable/fastscroll_label_right_holo_light\n010802ec=drawable/fastscroll_label_right_material\n010802ed=drawable/fastscroll_thumb_default_holo\n010802ee=drawable/fastscroll_thumb_holo\n010802ef=drawable/fastscroll_thumb_material\n010802f0=drawable/fastscroll_thumb_pressed_holo\n010802f1=drawable/fastscroll_track_default_holo_dark\n010802f2=drawable/fastscroll_track_default_holo_light\n010802f3=drawable/fastscroll_track_holo_dark\n010802f4=drawable/fastscroll_track_holo_light\n010802f5=drawable/fastscroll_track_material\n010802f6=drawable/fastscroll_track_pressed_holo_dark\n010802f7=drawable/fastscroll_track_pressed_holo_light\n010802f8=drawable/floating_popup_background_dark\n010802f9=drawable/floating_popup_background_light\n010802fa=drawable/focused_application_background_static\n010802fb=drawable/frame_gallery_thumb\n010802fc=drawable/frame_gallery_thumb_pressed\n010802fd=drawable/frame_gallery_thumb_selected\n010802fe=drawable/ft_avd_toarrow\n010802ff=drawable/ft_avd_toarrow_animation\n01080300=drawable/ft_avd_tooverflow\n01080301=drawable/ft_avd_tooverflow_animation\n01080302=drawable/gallery_item_background\n01080303=drawable/gallery_selected_default\n01080304=drawable/gallery_selected_focused\n01080305=drawable/gallery_selected_pressed\n01080306=drawable/gallery_unselected_default\n01080307=drawable/gallery_unselected_pressed\n01080308=drawable/global_action_icon_background\n01080309=drawable/grid_selector_background\n0108030a=drawable/grid_selector_background_focus\n0108030b=drawable/grid_selector_background_pressed\n0108030c=drawable/highlight_disabled\n0108030d=drawable/highlight_pressed\n0108030e=drawable/highlight_selected\n0108030f=drawable/ic_ab_back_holo_dark\n01080310=drawable/ic_ab_back_holo_dark_am\n01080311=drawable/ic_ab_back_holo_light\n01080312=drawable/ic_ab_back_holo_light_am\n01080313=drawable/ic_ab_back_material\n01080314=drawable/ic_ab_back_material_dark\n01080315=drawable/ic_ab_back_material_light\n01080316=drawable/ic_ab_back_material_settings\n01080317=drawable/ic_accessibility_color_correction\n01080318=drawable/ic_accessibility_color_inversion\n01080319=drawable/ic_accessibility_magnification\n0108031a=drawable/ic_account_circle\n0108031b=drawable/ic_action_assist_focused\n0108031c=drawable/ic_action_open\n0108031d=drawable/ic_aggregated\n0108031e=drawable/ic_alert_window_layer\n0108031f=drawable/ic_arrow_drop_right_black_24dp\n01080320=drawable/ic_arrow_forward\n01080321=drawable/ic_audio_alarm\n01080322=drawable/ic_audio_alarm_mute\n01080323=drawable/ic_audio_media\n01080324=drawable/ic_audio_media_mute\n01080325=drawable/ic_audio_notification\n01080326=drawable/ic_audio_notification_am_alpha\n01080327=drawable/ic_audio_notification_mute\n01080328=drawable/ic_audio_notification_mute_am_alpha\n01080329=drawable/ic_audio_ring_notif\n0108032a=drawable/ic_audio_ring_notif_mute\n0108032b=drawable/ic_audio_ring_notif_vibrate\n0108032c=drawable/ic_audio_vol\n0108032d=drawable/ic_audio_vol_mute\n0108032e=drawable/ic_battery\n0108032f=drawable/ic_battery_80_24dp\n01080330=drawable/ic_bluetooth_share_icon\n01080331=drawable/ic_bluetooth_transient_animation\n01080332=drawable/ic_bluetooth_transient_animation_drawable\n01080333=drawable/ic_bt_headphones_a2dp\n01080334=drawable/ic_bt_headset_hfp\n01080335=drawable/ic_bt_hearing_aid\n01080336=drawable/ic_bt_laptop\n01080337=drawable/ic_bt_misc_hid\n01080338=drawable/ic_bt_network_pan\n01080339=drawable/ic_bt_pointing_hid\n0108033a=drawable/ic_btn_round_more\n0108033b=drawable/ic_btn_round_more_disabled\n0108033c=drawable/ic_btn_round_more_normal\n0108033d=drawable/ic_btn_search_go\n0108033e=drawable/ic_btn_square_browser_zoom_fit_page\n0108033f=drawable/ic_btn_square_browser_zoom_fit_page_disabled\n01080340=drawable/ic_btn_square_browser_zoom_fit_page_normal\n01080341=drawable/ic_btn_square_browser_zoom_page_overview\n01080342=drawable/ic_btn_square_browser_zoom_page_overview_disabled\n01080343=drawable/ic_btn_square_browser_zoom_page_overview_normal\n01080344=drawable/ic_bullet_key_permission\n01080345=drawable/ic_cab_done_holo\n01080346=drawable/ic_cab_done_holo_dark\n01080347=drawable/ic_cab_done_holo_light\n01080348=drawable/ic_cab_done_mtrl_alpha\n01080349=drawable/ic_camera\n0108034a=drawable/ic_check_circle_24px\n0108034b=drawable/ic_checkmark_holo_light\n0108034c=drawable/ic_chevron_end\n0108034d=drawable/ic_chevron_start\n0108034e=drawable/ic_chooser_group_arrow\n0108034f=drawable/ic_chooser_pin\n01080350=drawable/ic_chooser_pin_dialog\n01080351=drawable/ic_clear\n01080352=drawable/ic_clear_disabled\n01080353=drawable/ic_clear_holo_dark\n01080354=drawable/ic_clear_holo_light\n01080355=drawable/ic_clear_material\n01080356=drawable/ic_clear_mtrl_alpha\n01080357=drawable/ic_clear_normal\n01080358=drawable/ic_clear_search_api_disabled_holo_dark\n01080359=drawable/ic_clear_search_api_disabled_holo_light\n0108035a=drawable/ic_clear_search_api_holo_dark\n0108035b=drawable/ic_clear_search_api_holo_light\n0108035c=drawable/ic_close\n0108035d=drawable/ic_coins_l\n0108035e=drawable/ic_coins_s\n0108035f=drawable/ic_collapse_bundle\n01080360=drawable/ic_collapse_notification\n01080361=drawable/ic_commit\n01080362=drawable/ic_commit_search_api_holo_dark\n01080363=drawable/ic_commit_search_api_holo_light\n01080364=drawable/ic_commit_search_api_material\n01080365=drawable/ic_commit_search_api_mtrl_alpha\n01080366=drawable/ic_contact_picture\n01080367=drawable/ic_contact_picture_180_holo_dark\n01080368=drawable/ic_contact_picture_180_holo_light\n01080369=drawable/ic_contact_picture_2\n0108036a=drawable/ic_contact_picture_3\n0108036b=drawable/ic_contact_picture_holo_dark\n0108036c=drawable/ic_contact_picture_holo_light\n0108036d=drawable/ic_corp_badge\n0108036e=drawable/ic_corp_badge_case\n0108036f=drawable/ic_corp_badge_color\n01080370=drawable/ic_corp_badge_no_background\n01080371=drawable/ic_corp_badge_off\n01080372=drawable/ic_corp_icon\n01080373=drawable/ic_corp_icon_badge_case\n01080374=drawable/ic_corp_icon_badge_color\n01080375=drawable/ic_corp_icon_badge_shadow\n01080376=drawable/ic_corp_statusbar_icon\n01080377=drawable/ic_corp_user_badge\n01080378=drawable/ic_dialog_alert_holo_dark\n01080379=drawable/ic_dialog_alert_holo_light\n0108037a=drawable/ic_dialog_alert_material\n0108037b=drawable/ic_dialog_close_normal_holo\n0108037c=drawable/ic_dialog_close_pressed_holo\n0108037d=drawable/ic_dialog_focused_holo\n0108037e=drawable/ic_dialog_time\n0108037f=drawable/ic_dialog_usb\n01080380=drawable/ic_dnd_block_notifications\n01080381=drawable/ic_doc_apk\n01080382=drawable/ic_doc_audio\n01080383=drawable/ic_doc_certificate\n01080384=drawable/ic_doc_codes\n01080385=drawable/ic_doc_compressed\n01080386=drawable/ic_doc_contact\n01080387=drawable/ic_doc_document\n01080388=drawable/ic_doc_event\n01080389=drawable/ic_doc_excel\n0108038a=drawable/ic_doc_folder\n0108038b=drawable/ic_doc_font\n0108038c=drawable/ic_doc_generic\n0108038d=drawable/ic_doc_image\n0108038e=drawable/ic_doc_pdf\n0108038f=drawable/ic_doc_powerpoint\n01080390=drawable/ic_doc_presentation\n01080391=drawable/ic_doc_spreadsheet\n01080392=drawable/ic_doc_text\n01080393=drawable/ic_doc_video\n01080394=drawable/ic_doc_word\n01080395=drawable/ic_drag_handle\n01080396=drawable/ic_eject_24dp\n01080397=drawable/ic_emergency\n01080398=drawable/ic_expand_bundle\n01080399=drawable/ic_expand_more\n0108039a=drawable/ic_expand_more_48dp\n0108039b=drawable/ic_expand_notification\n0108039c=drawable/ic_faster_emergency\n0108039d=drawable/ic_feedback\n0108039e=drawable/ic_file_copy\n0108039f=drawable/ic_find_next_holo_dark\n010803a0=drawable/ic_find_next_holo_light\n010803a1=drawable/ic_find_next_material\n010803a2=drawable/ic_find_next_mtrl_alpha\n010803a3=drawable/ic_find_previous_holo_dark\n010803a4=drawable/ic_find_previous_holo_light\n010803a5=drawable/ic_find_previous_material\n010803a6=drawable/ic_find_previous_mtrl_alpha\n010803a7=drawable/ic_fingerprint\n010803a8=drawable/ic_folder_24dp\n010803a9=drawable/ic_go\n010803aa=drawable/ic_go_search_api_holo_dark\n010803ab=drawable/ic_go_search_api_holo_light\n010803ac=drawable/ic_go_search_api_material\n010803ad=drawable/ic_hotspot_transient_animation\n010803ae=drawable/ic_hotspot_transient_animation_drawable\n010803af=drawable/ic_info_outline\n010803b0=drawable/ic_info_outline_24\n010803b1=drawable/ic_input_extract_action_done\n010803b2=drawable/ic_input_extract_action_go\n010803b3=drawable/ic_input_extract_action_next\n010803b4=drawable/ic_input_extract_action_previous\n010803b5=drawable/ic_input_extract_action_return\n010803b6=drawable/ic_input_extract_action_search\n010803b7=drawable/ic_input_extract_action_send\n010803b8=drawable/ic_instant_icon_badge_bolt\n010803b9=drawable/ic_jog_dial_answer\n010803ba=drawable/ic_jog_dial_answer_and_end\n010803bb=drawable/ic_jog_dial_answer_and_hold\n010803bc=drawable/ic_jog_dial_decline\n010803bd=drawable/ic_jog_dial_sound_off\n010803be=drawable/ic_jog_dial_sound_on\n010803bf=drawable/ic_jog_dial_unlock\n010803c0=drawable/ic_jog_dial_vibrate_on\n010803c1=drawable/ic_launcher_android\n010803c2=drawable/ic_lock\n010803c3=drawable/ic_lock_airplane_mode\n010803c4=drawable/ic_lock_airplane_mode_alpha\n010803c5=drawable/ic_lock_airplane_mode_off\n010803c6=drawable/ic_lock_airplane_mode_off_am_alpha\n010803c7=drawable/ic_lock_bugreport\n010803c8=drawable/ic_lock_idle_alarm_alpha\n010803c9=drawable/ic_lock_lock_alpha\n010803ca=drawable/ic_lock_lockdown\n010803cb=drawable/ic_lock_open\n010803cc=drawable/ic_lock_open_wht_24dp\n010803cd=drawable/ic_lock_outline_wht_24dp\n010803ce=drawable/ic_lock_power_off_alpha\n010803cf=drawable/ic_lock_ringer_off_alpha\n010803d0=drawable/ic_lock_ringer_on_alpha\n010803d1=drawable/ic_lock_silent_mode_vibrate\n010803d2=drawable/ic_lockscreen_alarm\n010803d3=drawable/ic_lockscreen_answer_active\n010803d4=drawable/ic_lockscreen_answer_focused\n010803d5=drawable/ic_lockscreen_answer_normal\n010803d6=drawable/ic_lockscreen_camera_activated\n010803d7=drawable/ic_lockscreen_camera_normal\n010803d8=drawable/ic_lockscreen_chevron_down\n010803d9=drawable/ic_lockscreen_chevron_left\n010803da=drawable/ic_lockscreen_chevron_right\n010803db=drawable/ic_lockscreen_chevron_up\n010803dc=drawable/ic_lockscreen_decline_activated\n010803dd=drawable/ic_lockscreen_decline_focused\n010803de=drawable/ic_lockscreen_decline_normal\n010803df=drawable/ic_lockscreen_emergencycall_normal\n010803e0=drawable/ic_lockscreen_emergencycall_pressed\n010803e1=drawable/ic_lockscreen_forgotpassword_normal\n010803e2=drawable/ic_lockscreen_forgotpassword_pressed\n010803e3=drawable/ic_lockscreen_google_activated\n010803e4=drawable/ic_lockscreen_google_focused\n010803e5=drawable/ic_lockscreen_google_normal\n010803e6=drawable/ic_lockscreen_handle_normal\n010803e7=drawable/ic_lockscreen_handle_pressed\n010803e8=drawable/ic_lockscreen_ime\n010803e9=drawable/ic_lockscreen_outerring\n010803ea=drawable/ic_lockscreen_player_background\n010803eb=drawable/ic_lockscreen_puk\n010803ec=drawable/ic_lockscreen_silent_activated\n010803ed=drawable/ic_lockscreen_silent_focused\n010803ee=drawable/ic_lockscreen_silent_normal\n010803ef=drawable/ic_lockscreen_sim\n010803f0=drawable/ic_lockscreen_soundon_activated\n010803f1=drawable/ic_lockscreen_soundon_focused\n010803f2=drawable/ic_lockscreen_soundon_normal\n010803f3=drawable/ic_lockscreen_text_activated\n010803f4=drawable/ic_lockscreen_text_focusde\n010803f5=drawable/ic_lockscreen_text_normal\n010803f6=drawable/ic_lockscreen_unlock_activated\n010803f7=drawable/ic_lockscreen_unlock_normal\n010803f8=drawable/ic_lockscreens_now_button\n010803f9=drawable/ic_logout\n010803fa=drawable/ic_maps_indicator_current_position\n010803fb=drawable/ic_maps_indicator_current_position_anim\n010803fc=drawable/ic_maps_indicator_current_position_anim1\n010803fd=drawable/ic_maps_indicator_current_position_anim2\n010803fe=drawable/ic_maps_indicator_current_position_anim3\n010803ff=drawable/ic_media_embed_play\n01080400=drawable/ic_media_fullscreen\n01080401=drawable/ic_media_route_connected_dark_00_mtrl\n01080402=drawable/ic_media_route_connected_dark_01_mtrl\n01080403=drawable/ic_media_route_connected_dark_02_mtrl\n01080404=drawable/ic_media_route_connected_dark_03_mtrl\n01080405=drawable/ic_media_route_connected_dark_04_mtrl\n01080406=drawable/ic_media_route_connected_dark_05_mtrl\n01080407=drawable/ic_media_route_connected_dark_06_mtrl\n01080408=drawable/ic_media_route_connected_dark_07_mtrl\n01080409=drawable/ic_media_route_connected_dark_08_mtrl\n0108040a=drawable/ic_media_route_connected_dark_09_mtrl\n0108040b=drawable/ic_media_route_connected_dark_10_mtrl\n0108040c=drawable/ic_media_route_connected_dark_11_mtrl\n0108040d=drawable/ic_media_route_connected_dark_12_mtrl\n0108040e=drawable/ic_media_route_connected_dark_13_mtrl\n0108040f=drawable/ic_media_route_connected_dark_14_mtrl\n01080410=drawable/ic_media_route_connected_dark_15_mtrl\n01080411=drawable/ic_media_route_connected_dark_16_mtrl\n01080412=drawable/ic_media_route_connected_dark_17_mtrl\n01080413=drawable/ic_media_route_connected_dark_18_mtrl\n01080414=drawable/ic_media_route_connected_dark_19_mtrl\n01080415=drawable/ic_media_route_connected_dark_20_mtrl\n01080416=drawable/ic_media_route_connected_dark_21_mtrl\n01080417=drawable/ic_media_route_connected_dark_22_mtrl\n01080418=drawable/ic_media_route_connected_dark_23_mtrl\n01080419=drawable/ic_media_route_connected_dark_24_mtrl\n0108041a=drawable/ic_media_route_connected_dark_25_mtrl\n0108041b=drawable/ic_media_route_connected_dark_26_mtrl\n0108041c=drawable/ic_media_route_connected_dark_27_mtrl\n0108041d=drawable/ic_media_route_connected_dark_28_mtrl\n0108041e=drawable/ic_media_route_connected_dark_29_mtrl\n0108041f=drawable/ic_media_route_connected_dark_30_mtrl\n01080420=drawable/ic_media_route_connected_dark_material\n01080421=drawable/ic_media_route_connected_light_00_mtrl\n01080422=drawable/ic_media_route_connected_light_01_mtrl\n01080423=drawable/ic_media_route_connected_light_02_mtrl\n01080424=drawable/ic_media_route_connected_light_03_mtrl\n01080425=drawable/ic_media_route_connected_light_04_mtrl\n01080426=drawable/ic_media_route_connected_light_05_mtrl\n01080427=drawable/ic_media_route_connected_light_06_mtrl\n01080428=drawable/ic_media_route_connected_light_07_mtrl\n01080429=drawable/ic_media_route_connected_light_08_mtrl\n0108042a=drawable/ic_media_route_connected_light_09_mtrl\n0108042b=drawable/ic_media_route_connected_light_10_mtrl\n0108042c=drawable/ic_media_route_connected_light_11_mtrl\n0108042d=drawable/ic_media_route_connected_light_12_mtrl\n0108042e=drawable/ic_media_route_connected_light_13_mtrl\n0108042f=drawable/ic_media_route_connected_light_14_mtrl\n01080430=drawable/ic_media_route_connected_light_15_mtrl\n01080431=drawable/ic_media_route_connected_light_16_mtrl\n01080432=drawable/ic_media_route_connected_light_17_mtrl\n01080433=drawable/ic_media_route_connected_light_18_mtrl\n01080434=drawable/ic_media_route_connected_light_19_mtrl\n01080435=drawable/ic_media_route_connected_light_20_mtrl\n01080436=drawable/ic_media_route_connected_light_21_mtrl\n01080437=drawable/ic_media_route_connected_light_22_mtrl\n01080438=drawable/ic_media_route_connected_light_23_mtrl\n01080439=drawable/ic_media_route_connected_light_24_mtrl\n0108043a=drawable/ic_media_route_connected_light_25_mtrl\n0108043b=drawable/ic_media_route_connected_light_26_mtrl\n0108043c=drawable/ic_media_route_connected_light_27_mtrl\n0108043d=drawable/ic_media_route_connected_light_28_mtrl\n0108043e=drawable/ic_media_route_connected_light_29_mtrl\n0108043f=drawable/ic_media_route_connected_light_30_mtrl\n01080440=drawable/ic_media_route_connected_light_material\n01080441=drawable/ic_media_route_connecting_dark_00_mtrl\n01080442=drawable/ic_media_route_connecting_dark_01_mtrl\n01080443=drawable/ic_media_route_connecting_dark_02_mtrl\n01080444=drawable/ic_media_route_connecting_dark_03_mtrl\n01080445=drawable/ic_media_route_connecting_dark_04_mtrl\n01080446=drawable/ic_media_route_connecting_dark_05_mtrl\n01080447=drawable/ic_media_route_connecting_dark_06_mtrl\n01080448=drawable/ic_media_route_connecting_dark_07_mtrl\n01080449=drawable/ic_media_route_connecting_dark_08_mtrl\n0108044a=drawable/ic_media_route_connecting_dark_09_mtrl\n0108044b=drawable/ic_media_route_connecting_dark_10_mtrl\n0108044c=drawable/ic_media_route_connecting_dark_11_mtrl\n0108044d=drawable/ic_media_route_connecting_dark_12_mtrl\n0108044e=drawable/ic_media_route_connecting_dark_13_mtrl\n0108044f=drawable/ic_media_route_connecting_dark_14_mtrl\n01080450=drawable/ic_media_route_connecting_dark_15_mtrl\n01080451=drawable/ic_media_route_connecting_dark_16_mtrl\n01080452=drawable/ic_media_route_connecting_dark_17_mtrl\n01080453=drawable/ic_media_route_connecting_dark_18_mtrl\n01080454=drawable/ic_media_route_connecting_dark_19_mtrl\n01080455=drawable/ic_media_route_connecting_dark_20_mtrl\n01080456=drawable/ic_media_route_connecting_dark_21_mtrl\n01080457=drawable/ic_media_route_connecting_dark_22_mtrl\n01080458=drawable/ic_media_route_connecting_dark_23_mtrl\n01080459=drawable/ic_media_route_connecting_dark_24_mtrl\n0108045a=drawable/ic_media_route_connecting_dark_25_mtrl\n0108045b=drawable/ic_media_route_connecting_dark_26_mtrl\n0108045c=drawable/ic_media_route_connecting_dark_27_mtrl\n0108045d=drawable/ic_media_route_connecting_dark_28_mtrl\n0108045e=drawable/ic_media_route_connecting_dark_29_mtrl\n0108045f=drawable/ic_media_route_connecting_dark_30_mtrl\n01080460=drawable/ic_media_route_connecting_dark_material\n01080461=drawable/ic_media_route_connecting_holo_dark\n01080462=drawable/ic_media_route_connecting_holo_light\n01080463=drawable/ic_media_route_connecting_light_00_mtrl\n01080464=drawable/ic_media_route_connecting_light_01_mtrl\n01080465=drawable/ic_media_route_connecting_light_02_mtrl\n01080466=drawable/ic_media_route_connecting_light_03_mtrl\n01080467=drawable/ic_media_route_connecting_light_04_mtrl\n01080468=drawable/ic_media_route_connecting_light_05_mtrl\n01080469=drawable/ic_media_route_connecting_light_06_mtrl\n0108046a=drawable/ic_media_route_connecting_light_07_mtrl\n0108046b=drawable/ic_media_route_connecting_light_08_mtrl\n0108046c=drawable/ic_media_route_connecting_light_09_mtrl\n0108046d=drawable/ic_media_route_connecting_light_10_mtrl\n0108046e=drawable/ic_media_route_connecting_light_11_mtrl\n0108046f=drawable/ic_media_route_connecting_light_12_mtrl\n01080470=drawable/ic_media_route_connecting_light_13_mtrl\n01080471=drawable/ic_media_route_connecting_light_14_mtrl\n01080472=drawable/ic_media_route_connecting_light_15_mtrl\n01080473=drawable/ic_media_route_connecting_light_16_mtrl\n01080474=drawable/ic_media_route_connecting_light_17_mtrl\n01080475=drawable/ic_media_route_connecting_light_18_mtrl\n01080476=drawable/ic_media_route_connecting_light_19_mtrl\n01080477=drawable/ic_media_route_connecting_light_20_mtrl\n01080478=drawable/ic_media_route_connecting_light_21_mtrl\n01080479=drawable/ic_media_route_connecting_light_22_mtrl\n0108047a=drawable/ic_media_route_connecting_light_23_mtrl\n0108047b=drawable/ic_media_route_connecting_light_24_mtrl\n0108047c=drawable/ic_media_route_connecting_light_25_mtrl\n0108047d=drawable/ic_media_route_connecting_light_26_mtrl\n0108047e=drawable/ic_media_route_connecting_light_27_mtrl\n0108047f=drawable/ic_media_route_connecting_light_28_mtrl\n01080480=drawable/ic_media_route_connecting_light_29_mtrl\n01080481=drawable/ic_media_route_connecting_light_30_mtrl\n01080482=drawable/ic_media_route_connecting_light_material\n01080483=drawable/ic_media_route_dark_material\n01080484=drawable/ic_media_route_disabled_holo_dark\n01080485=drawable/ic_media_route_disabled_holo_light\n01080486=drawable/ic_media_route_disabled_mtrl_alpha\n01080487=drawable/ic_media_route_holo_dark\n01080488=drawable/ic_media_route_holo_light\n01080489=drawable/ic_media_route_light_material\n0108048a=drawable/ic_media_route_off_dark_mtrl\n0108048b=drawable/ic_media_route_off_holo_dark\n0108048c=drawable/ic_media_route_off_holo_light\n0108048d=drawable/ic_media_route_off_light_mtrl\n0108048e=drawable/ic_media_route_on_0_holo_dark\n0108048f=drawable/ic_media_route_on_0_holo_light\n01080490=drawable/ic_media_route_on_1_holo_dark\n01080491=drawable/ic_media_route_on_1_holo_light\n01080492=drawable/ic_media_route_on_2_holo_dark\n01080493=drawable/ic_media_route_on_2_holo_light\n01080494=drawable/ic_media_route_on_holo_dark\n01080495=drawable/ic_media_route_on_holo_light\n01080496=drawable/ic_media_seamless\n01080497=drawable/ic_media_stop\n01080498=drawable/ic_media_video_poster\n01080499=drawable/ic_menu\n0108049a=drawable/ic_menu_account_list\n0108049b=drawable/ic_menu_allfriends\n0108049c=drawable/ic_menu_archive\n0108049d=drawable/ic_menu_attachment\n0108049e=drawable/ic_menu_back\n0108049f=drawable/ic_menu_block\n010804a0=drawable/ic_menu_blocked_user\n010804a1=drawable/ic_menu_btn_add\n010804a2=drawable/ic_menu_cc\n010804a3=drawable/ic_menu_cc_am\n010804a4=drawable/ic_menu_chat_dashboard\n010804a5=drawable/ic_menu_clear_playlist\n010804a6=drawable/ic_menu_compose\n010804a7=drawable/ic_menu_copy\n010804a8=drawable/ic_menu_copy_holo_dark\n010804a9=drawable/ic_menu_copy_holo_light\n010804aa=drawable/ic_menu_copy_material\n010804ab=drawable/ic_menu_cut\n010804ac=drawable/ic_menu_cut_holo_dark\n010804ad=drawable/ic_menu_cut_holo_light\n010804ae=drawable/ic_menu_cut_material\n010804af=drawable/ic_menu_emoticons\n010804b0=drawable/ic_menu_end_conversation\n010804b1=drawable/ic_menu_find\n010804b2=drawable/ic_menu_find_holo_dark\n010804b3=drawable/ic_menu_find_holo_light\n010804b4=drawable/ic_menu_find_material\n010804b5=drawable/ic_menu_find_mtrl_alpha\n010804b6=drawable/ic_menu_forward\n010804b7=drawable/ic_menu_friendslist\n010804b8=drawable/ic_menu_goto\n010804b9=drawable/ic_menu_help_holo_light\n010804ba=drawable/ic_menu_home\n010804bb=drawable/ic_menu_invite\n010804bc=drawable/ic_menu_login\n010804bd=drawable/ic_menu_mark\n010804be=drawable/ic_menu_moreoverflow\n010804bf=drawable/ic_menu_moreoverflow_focused_holo_dark\n010804c0=drawable/ic_menu_moreoverflow_focused_holo_light\n010804c1=drawable/ic_menu_moreoverflow_holo_dark\n010804c2=drawable/ic_menu_moreoverflow_holo_light\n010804c3=drawable/ic_menu_moreoverflow_material\n010804c4=drawable/ic_menu_moreoverflow_material_dark\n010804c5=drawable/ic_menu_moreoverflow_material_light\n010804c6=drawable/ic_menu_moreoverflow_normal_holo_dark\n010804c7=drawable/ic_menu_moreoverflow_normal_holo_light\n010804c8=drawable/ic_menu_notifications\n010804c9=drawable/ic_menu_paste\n010804ca=drawable/ic_menu_paste_holo_dark\n010804cb=drawable/ic_menu_paste_holo_light\n010804cc=drawable/ic_menu_paste_material\n010804cd=drawable/ic_menu_play_clip\n010804ce=drawable/ic_menu_refresh\n010804cf=drawable/ic_menu_search_holo_dark\n010804d0=drawable/ic_menu_search_holo_light\n010804d1=drawable/ic_menu_search_material\n010804d2=drawable/ic_menu_search_mtrl_alpha\n010804d3=drawable/ic_menu_selectall_holo_dark\n010804d4=drawable/ic_menu_selectall_holo_light\n010804d5=drawable/ic_menu_selectall_material\n010804d6=drawable/ic_menu_settings_holo_light\n010804d7=drawable/ic_menu_share_holo_dark\n010804d8=drawable/ic_menu_share_holo_light\n010804d9=drawable/ic_menu_share_material\n010804da=drawable/ic_menu_star\n010804db=drawable/ic_menu_start_conversation\n010804dc=drawable/ic_menu_stop\n010804dd=drawable/ic_mic\n010804de=drawable/ic_minus\n010804df=drawable/ic_mode_edit\n010804e0=drawable/ic_more_items\n010804e1=drawable/ic_no_apps\n010804e2=drawable/ic_notification_alert\n010804e3=drawable/ic_notification_block\n010804e4=drawable/ic_notification_cast_0\n010804e5=drawable/ic_notification_cast_1\n010804e6=drawable/ic_notification_cast_2\n010804e7=drawable/ic_notification_ime_default\n010804e8=drawable/ic_notification_media_route\n010804e9=drawable/ic_notifications_alerted\n010804ea=drawable/ic_pan_tool\n010804eb=drawable/ic_perm_device_info\n010804ec=drawable/ic_perm_group_app_info\n010804ed=drawable/ic_perm_group_audio_settings\n010804ee=drawable/ic_perm_group_bluetooth\n010804ef=drawable/ic_perm_group_bookmarks\n010804f0=drawable/ic_perm_group_calendar\n010804f1=drawable/ic_perm_group_camera\n010804f2=drawable/ic_perm_group_device_alarms\n010804f3=drawable/ic_perm_group_display\n010804f4=drawable/ic_perm_group_effects_battery\n010804f5=drawable/ic_perm_group_location\n010804f6=drawable/ic_perm_group_messages\n010804f7=drawable/ic_perm_group_microphone\n010804f8=drawable/ic_perm_group_network\n010804f9=drawable/ic_perm_group_personal_info\n010804fa=drawable/ic_perm_group_phone_calls\n010804fb=drawable/ic_perm_group_screenlock\n010804fc=drawable/ic_perm_group_shortrange_network\n010804fd=drawable/ic_perm_group_social_info\n010804fe=drawable/ic_perm_group_status_bar\n010804ff=drawable/ic_perm_group_sync_settings\n01080500=drawable/ic_perm_group_system_clock\n01080501=drawable/ic_perm_group_system_tools\n01080502=drawable/ic_perm_group_voicemail\n01080503=drawable/ic_perm_group_wallpapewr\n01080504=drawable/ic_permission\n01080505=drawable/ic_phone\n01080506=drawable/ic_plus\n01080507=drawable/ic_popup_sync_1\n01080508=drawable/ic_popup_sync_2\n01080509=drawable/ic_popup_sync_3\n0108050a=drawable/ic_popup_sync_4\n0108050b=drawable/ic_popup_sync_5\n0108050c=drawable/ic_popup_sync_6\n0108050d=drawable/ic_print\n0108050e=drawable/ic_print_error\n0108050f=drawable/ic_qs_airplane\n01080510=drawable/ic_qs_auto_rotate\n01080511=drawable/ic_qs_battery_saver\n01080512=drawable/ic_qs_bluetooth\n01080513=drawable/ic_qs_dnd\n01080514=drawable/ic_qs_flashlight\n01080515=drawable/ic_qs_night_display_on\n01080516=drawable/ic_qs_ui_mode_night\n01080517=drawable/ic_refresh\n01080518=drawable/ic_reply_notification\n01080519=drawable/ic_restart\n0108051a=drawable/ic_schedule\n0108051b=drawable/ic_screenshot\n0108051c=drawable/ic_sd_card_48dp\n0108051d=drawable/ic_search\n0108051e=drawable/ic_search_api_holo_dark\n0108051f=drawable/ic_search_api_holo_light\n01080520=drawable/ic_search_api_material\n01080521=drawable/ic_settings\n01080522=drawable/ic_settings_24dp\n01080523=drawable/ic_settings_bluetooth\n01080524=drawable/ic_settings_language\n01080525=drawable/ic_settings_print\n01080526=drawable/ic_sharing_disabled\n01080527=drawable/ic_signal_cellular\n01080528=drawable/ic_signal_cellular_0_4_bar\n01080529=drawable/ic_signal_cellular_0_5_bar\n0108052a=drawable/ic_signal_cellular_1_4_bar\n0108052b=drawable/ic_signal_cellular_1_5_bar\n0108052c=drawable/ic_signal_cellular_2_4_bar\n0108052d=drawable/ic_signal_cellular_2_5_bar\n0108052e=drawable/ic_signal_cellular_3_4_bar\n0108052f=drawable/ic_signal_cellular_3_5_bar\n01080530=drawable/ic_signal_cellular_4_4_bar\n01080531=drawable/ic_signal_cellular_4_5_bar\n01080532=drawable/ic_signal_cellular_5_5_bar\n01080533=drawable/ic_signal_cellular_alt_24px\n01080534=drawable/ic_signal_location\n01080535=drawable/ic_signal_wifi_transient_animation\n01080536=drawable/ic_signal_wifi_transient_animation_drawable\n01080537=drawable/ic_sim_card_multi_24px_clr\n01080538=drawable/ic_sim_card_multi_48px_clr\n01080539=drawable/ic_slice_send\n0108053a=drawable/ic_spinner_caret\n0108053b=drawable/ic_star_black_16dp\n0108053c=drawable/ic_star_black_36dp\n0108053d=drawable/ic_star_black_48dp\n0108053e=drawable/ic_star_half_black_16dp\n0108053f=drawable/ic_star_half_black_36dp\n01080540=drawable/ic_star_half_black_48dp\n01080541=drawable/ic_storage_48dp\n01080542=drawable/ic_suggestions_add\n01080543=drawable/ic_suggestions_delete\n01080544=drawable/ic_sysbar_quicksettings\n01080545=drawable/ic_text_dot\n01080546=drawable/ic_usb_48dp\n01080547=drawable/ic_user_secure\n01080548=drawable/ic_vibrate\n01080549=drawable/ic_vibrate_small\n0108054a=drawable/ic_visibility\n0108054b=drawable/ic_voice_search\n0108054c=drawable/ic_voice_search_api_holo_dark\n0108054d=drawable/ic_voice_search_api_holo_light\n0108054e=drawable/ic_voice_search_api_material\n0108054f=drawable/ic_volume\n01080550=drawable/ic_volume_bluetooth_ad2p\n01080551=drawable/ic_volume_bluetooth_in_call\n01080552=drawable/ic_volume_off\n01080553=drawable/ic_volume_off_small\n01080554=drawable/ic_volume_small\n01080555=drawable/ic_wifi_signal_0\n01080556=drawable/ic_wifi_signal_1\n01080557=drawable/ic_wifi_signal_2\n01080558=drawable/ic_wifi_signal_3\n01080559=drawable/ic_wifi_signal_4\n0108055a=drawable/ic_work_apps_off\n0108055b=drawable/ic_zen_24dp\n0108055c=drawable/icon_highlight_rectangle\n0108055d=drawable/icon_highlight_square\n0108055e=drawable/iconfactory_adaptive_icon_drawable_wrapper\n0108055f=drawable/ime_qwerty\n01080560=drawable/immersive_cling_bg_circ\n01080561=drawable/immersive_cling_light_bg_circ\n01080562=drawable/indicator_check_mark_dark\n01080563=drawable/indicator_check_mark_light\n01080564=drawable/indicator_input_error\n01080565=drawable/input_extract_action_bg_material_dark\n01080566=drawable/input_extract_action_bg_normal_material_dark\n01080567=drawable/input_extract_action_bg_pressed_material_dark\n01080568=drawable/input_method_fullscreen_background\n01080569=drawable/input_method_fullscreen_background_holo\n0108056a=drawable/item_background\n0108056b=drawable/item_background_activated_holo_dark\n0108056c=drawable/item_background_borderless_material\n0108056d=drawable/item_background_borderless_material_dark\n0108056e=drawable/item_background_borderless_material_light\n0108056f=drawable/item_background_holo_dark\n01080570=drawable/item_background_holo_light\n01080571=drawable/item_background_material\n01080572=drawable/item_background_material_dark\n01080573=drawable/item_background_material_light\n01080574=drawable/jog_dial_arrow_long_left_green\n01080575=drawable/jog_dial_arrow_long_left_yellow\n01080576=drawable/jog_dial_arrow_long_middle_yellow\n01080577=drawable/jog_dial_arrow_long_right_red\n01080578=drawable/jog_dial_arrow_long_right_yellow\n01080579=drawable/jog_dial_arrow_short_left\n0108057a=drawable/jog_dial_arrow_short_left_and_right\n0108057b=drawable/jog_dial_arrow_short_right\n0108057c=drawable/jog_dial_bg\n0108057d=drawable/jog_dial_dimple\n0108057e=drawable/jog_dial_dimple_dim\n0108057f=drawable/jog_tab_bar_left_answer\n01080580=drawable/jog_tab_bar_left_end_confirm_gray\n01080581=drawable/jog_tab_bar_left_end_confirm_green\n01080582=drawable/jog_tab_bar_left_end_confirm_red\n01080583=drawable/jog_tab_bar_left_end_confirm_yellow\n01080584=drawable/jog_tab_bar_left_end_normal\n01080585=drawable/jog_tab_bar_left_end_pressed\n01080586=drawable/jog_tab_bar_left_generic\n01080587=drawable/jog_tab_bar_left_unlock\n01080588=drawable/jog_tab_bar_right_decline\n01080589=drawable/jog_tab_bar_right_end_confirm_gray\n0108058a=drawable/jog_tab_bar_right_end_confirm_green\n0108058b=drawable/jog_tab_bar_right_end_confirm_red\n0108058c=drawable/jog_tab_bar_right_end_confirm_yellow\n0108058d=drawable/jog_tab_bar_right_end_normal\n0108058e=drawable/jog_tab_bar_right_end_pressed\n0108058f=drawable/jog_tab_bar_right_generic\n01080590=drawable/jog_tab_bar_right_sound_off\n01080591=drawable/jog_tab_bar_right_sound_on\n01080592=drawable/jog_tab_left_answer\n01080593=drawable/jog_tab_left_confirm_gray\n01080594=drawable/jog_tab_left_confirm_green\n01080595=drawable/jog_tab_left_confirm_red\n01080596=drawable/jog_tab_left_confirm_yellow\n01080597=drawable/jog_tab_left_generic\n01080598=drawable/jog_tab_left_normal\n01080599=drawable/jog_tab_left_pressed\n0108059a=drawable/jog_tab_left_unlock\n0108059b=drawable/jog_tab_right_confirm_gray\n0108059c=drawable/jog_tab_right_confirm_green\n0108059d=drawable/jog_tab_right_confirm_red\n0108059e=drawable/jog_tab_right_confirm_yellow\n0108059f=drawable/jog_tab_right_decline\n010805a0=drawable/jog_tab_right_generic\n010805a1=drawable/jog_tab_right_normal\n010805a2=drawable/jog_tab_right_pressed\n010805a3=drawable/jog_tab_right_sound_off\n010805a4=drawable/jog_tab_right_sound_on\n010805a5=drawable/jog_tab_target_gray\n010805a6=drawable/jog_tab_target_green\n010805a7=drawable/jog_tab_target_red\n010805a8=drawable/jog_tab_target_yellow\n010805a9=drawable/keyboard_accessory_bg_landscape\n010805aa=drawable/keyboard_background\n010805ab=drawable/keyboard_key_feedback\n010805ac=drawable/keyboard_key_feedback_background\n010805ad=drawable/keyboard_key_feedback_more_background\n010805ae=drawable/keyboard_popup_panel_background\n010805af=drawable/keyboard_popup_panel_trans_background\n010805b0=drawable/light_header\n010805b1=drawable/light_header_dither\n010805b2=drawable/list_activated_holo\n010805b3=drawable/list_choice_background_material\n010805b4=drawable/list_divider_holo_dark\n010805b5=drawable/list_divider_holo_light\n010805b6=drawable/list_divider_horizontal_holo_dark\n010805b7=drawable/list_divider_material\n010805b8=drawable/list_focused_holo\n010805b9=drawable/list_highlight\n010805ba=drawable/list_highlight_active\n010805bb=drawable/list_highlight_inactive\n010805bc=drawable/list_longpressed_holo\n010805bd=drawable/list_longpressed_holo_dark\n010805be=drawable/list_longpressed_holo_light\n010805bf=drawable/list_pressed_holo_dark\n010805c0=drawable/list_pressed_holo_light\n010805c1=drawable/list_section_divider_holo_dark\n010805c2=drawable/list_section_divider_holo_light\n010805c3=drawable/list_section_divider_material\n010805c4=drawable/list_section_divider_mtrl_alpha\n010805c5=drawable/list_section_header_holo_dark\n010805c6=drawable/list_section_header_holo_light\n010805c7=drawable/list_selected_background\n010805c8=drawable/list_selected_background_light\n010805c9=drawable/list_selected_holo_dark\n010805ca=drawable/list_selected_holo_light\n010805cb=drawable/list_selector_activated_holo_dark\n010805cc=drawable/list_selector_activated_holo_light\n010805cd=drawable/list_selector_background_default\n010805ce=drawable/list_selector_background_default_light\n010805cf=drawable/list_selector_background_disabled\n010805d0=drawable/list_selector_background_disabled_light\n010805d1=drawable/list_selector_background_focus\n010805d2=drawable/list_selector_background_focused\n010805d3=drawable/list_selector_background_focused_light\n010805d4=drawable/list_selector_background_focused_selected\n010805d5=drawable/list_selector_background_light\n010805d6=drawable/list_selector_background_longpress\n010805d7=drawable/list_selector_background_longpress_light\n010805d8=drawable/list_selector_background_pressed\n010805d9=drawable/list_selector_background_pressed_light\n010805da=drawable/list_selector_background_selected\n010805db=drawable/list_selector_background_selected_light\n010805dc=drawable/list_selector_background_transition\n010805dd=drawable/list_selector_background_transition_holo_dark\n010805de=drawable/list_selector_background_transition_holo_light\n010805df=drawable/list_selector_background_transition_light\n010805e0=drawable/list_selector_disabled_holo_dark\n010805e1=drawable/list_selector_disabled_holo_light\n010805e2=drawable/list_selector_focused_holo_dark\n010805e3=drawable/list_selector_focused_holo_light\n010805e4=drawable/list_selector_holo_dark\n010805e5=drawable/list_selector_holo_light\n010805e6=drawable/list_selector_multiselect_holo_dark\n010805e7=drawable/list_selector_multiselect_holo_light\n010805e8=drawable/list_selector_pressed_holo_dark\n010805e9=drawable/list_selector_pressed_holo_light\n010805ea=drawable/load_average_background\n010805eb=drawable/loading_tile\n010805ec=drawable/loading_tile_android\n010805ed=drawable/lockscreen_notselected\n010805ee=drawable/lockscreen_protection_pattern\n010805ef=drawable/lockscreen_selected\n010805f0=drawable/magnified_region_frame\n010805f1=drawable/maps_google_logo\n010805f2=drawable/media_button_background\n010805f3=drawable/media_seamless_background\n010805f4=drawable/menu_background\n010805f5=drawable/menu_background_fill_parent_width\n010805f6=drawable/menu_dropdown_panel_holo_dark\n010805f7=drawable/menu_dropdown_panel_holo_light\n010805f8=drawable/menu_hardkey_panel_holo_dark\n010805f9=drawable/menu_hardkey_panel_holo_light\n010805fa=drawable/menu_panel_holo_dark\n010805fb=drawable/menu_panel_holo_light\n010805fc=drawable/menu_popup_panel_holo_dark\n010805fd=drawable/menu_popup_panel_holo_light\n010805fe=drawable/menu_selector\n010805ff=drawable/menu_separator\n01080600=drawable/menu_submenu_background\n01080601=drawable/menuitem_background_focus\n01080602=drawable/menuitem_background_pressed\n01080603=drawable/menuitem_background_solid\n01080604=drawable/menuitem_background_solid_focused\n01080605=drawable/menuitem_background_solid_pressed\n01080606=drawable/menuitem_checkbox\n01080607=drawable/menuitem_checkbox_on\n01080608=drawable/messaging_user\n01080609=drawable/minitab_lt\n0108060a=drawable/minitab_lt_focus\n0108060b=drawable/minitab_lt_press\n0108060c=drawable/minitab_lt_selected\n0108060d=drawable/minitab_lt_unselected\n0108060e=drawable/minitab_lt_unselected_press\n0108060f=drawable/no_tile_128\n01080610=drawable/no_tile_256\n01080611=drawable/notification_material_action_background\n01080612=drawable/notification_material_media_action_background\n01080613=drawable/notification_template_divider\n01080614=drawable/notification_template_divider_media\n01080615=drawable/notification_template_icon_bg\n01080616=drawable/notification_template_icon_low_bg\n01080617=drawable/number_picker_divider_material\n01080618=drawable/numberpicker_down_btn\n01080619=drawable/numberpicker_down_disabled\n0108061a=drawable/numberpicker_down_disabled_focused\n0108061b=drawable/numberpicker_down_disabled_focused_holo_dark\n0108061c=drawable/numberpicker_down_disabled_focused_holo_light\n0108061d=drawable/numberpicker_down_disabled_holo_dark\n0108061e=drawable/numberpicker_down_disabled_holo_light\n0108061f=drawable/numberpicker_down_focused_holo_dark\n01080620=drawable/numberpicker_down_focused_holo_light\n01080621=drawable/numberpicker_down_longpressed_holo_dark\n01080622=drawable/numberpicker_down_longpressed_holo_light\n01080623=drawable/numberpicker_down_normal\n01080624=drawable/numberpicker_down_normal_holo_dark\n01080625=drawable/numberpicker_down_normal_holo_light\n01080626=drawable/numberpicker_down_pressed\n01080627=drawable/numberpicker_down_pressed_holo_dark\n01080628=drawable/numberpicker_down_pressed_holo_light\n01080629=drawable/numberpicker_down_selected\n0108062a=drawable/numberpicker_input\n0108062b=drawable/numberpicker_input_disabled\n0108062c=drawable/numberpicker_input_normal\n0108062d=drawable/numberpicker_input_pressed\n0108062e=drawable/numberpicker_input_selected\n0108062f=drawable/numberpicker_selection_divider\n01080630=drawable/numberpicker_up_btn\n01080631=drawable/numberpicker_up_disabled\n01080632=drawable/numberpicker_up_disabled_focused\n01080633=drawable/numberpicker_up_disabled_focused_holo_dark\n01080634=drawable/numberpicker_up_disabled_focused_holo_light\n01080635=drawable/numberpicker_up_disabled_holo_dark\n01080636=drawable/numberpicker_up_disabled_holo_light\n01080637=drawable/numberpicker_up_focused_holo_dark\n01080638=drawable/numberpicker_up_focused_holo_light\n01080639=drawable/numberpicker_up_longpressed_holo_dark\n0108063a=drawable/numberpicker_up_longpressed_holo_light\n0108063b=drawable/numberpicker_up_normal\n0108063c=drawable/numberpicker_up_normal_holo_dark\n0108063d=drawable/numberpicker_up_normal_holo_light\n0108063e=drawable/numberpicker_up_pressed\n0108063f=drawable/numberpicker_up_pressed_holo_dark\n01080640=drawable/numberpicker_up_pressed_holo_light\n01080641=drawable/numberpicker_up_selected\n01080642=drawable/panel_background\n01080643=drawable/panel_bg_holo_dark\n01080644=drawable/panel_bg_holo_light\n01080645=drawable/panel_picture_frame_background\n01080646=drawable/panel_picture_frame_bg_focus_blue\n01080647=drawable/panel_picture_frame_bg_normal\n01080648=drawable/panel_picture_frame_bg_pressed_blue\n01080649=drawable/password_field_default\n0108064a=drawable/password_keyboard_background_holo\n0108064b=drawable/perm_group_accessibility_features\n0108064c=drawable/perm_group_activity_recognition\n0108064d=drawable/perm_group_affects_battery\n0108064e=drawable/perm_group_app_info\n0108064f=drawable/perm_group_audio_settings\n01080650=drawable/perm_group_aural\n01080651=drawable/perm_group_bluetooth\n01080652=drawable/perm_group_bookmarks\n01080653=drawable/perm_group_calendar\n01080654=drawable/perm_group_call_log\n01080655=drawable/perm_group_camera\n01080656=drawable/perm_group_contacts\n01080657=drawable/perm_group_device_alarms\n01080658=drawable/perm_group_display\n01080659=drawable/perm_group_location\n0108065a=drawable/perm_group_microphone\n0108065b=drawable/perm_group_network\n0108065c=drawable/perm_group_personal_info\n0108065d=drawable/perm_group_phone_calls\n0108065e=drawable/perm_group_screenlock\n0108065f=drawable/perm_group_sensors\n01080660=drawable/perm_group_shortrange_network\n01080661=drawable/perm_group_sms\n01080662=drawable/perm_group_status_bar\n01080663=drawable/perm_group_storage\n01080664=drawable/perm_group_sync_settings\n01080665=drawable/perm_group_system_clock\n01080666=drawable/perm_group_system_tools\n01080667=drawable/perm_group_visual\n01080668=drawable/perm_group_voicemail\n01080669=drawable/perm_group_wallpaper\n0108066a=drawable/picture_emergency\n0108066b=drawable/platlogo\n0108066c=drawable/platlogo_m\n0108066d=drawable/pointer_alias\n0108066e=drawable/pointer_alias_icon\n0108066f=drawable/pointer_alias_large\n01080670=drawable/pointer_alias_large_icon\n01080671=drawable/pointer_all_scroll\n01080672=drawable/pointer_all_scroll_icon\n01080673=drawable/pointer_all_scroll_large\n01080674=drawable/pointer_all_scroll_large_icon\n01080675=drawable/pointer_arrow\n01080676=drawable/pointer_arrow_icon\n01080677=drawable/pointer_arrow_large\n01080678=drawable/pointer_arrow_large_icon\n01080679=drawable/pointer_cell\n0108067a=drawable/pointer_cell_icon\n0108067b=drawable/pointer_cell_large\n0108067c=drawable/pointer_cell_large_icon\n0108067d=drawable/pointer_context_menu\n0108067e=drawable/pointer_context_menu_icon\n0108067f=drawable/pointer_context_menu_large\n01080680=drawable/pointer_context_menu_large_icon\n01080681=drawable/pointer_copy\n01080682=drawable/pointer_copy_icon\n01080683=drawable/pointer_copy_large\n01080684=drawable/pointer_copy_large_icon\n01080685=drawable/pointer_crosshair\n01080686=drawable/pointer_crosshair_icon\n01080687=drawable/pointer_crosshair_large\n01080688=drawable/pointer_crosshair_large_icon\n01080689=drawable/pointer_grab\n0108068a=drawable/pointer_grab_icon\n0108068b=drawable/pointer_grab_large\n0108068c=drawable/pointer_grab_large_icon\n0108068d=drawable/pointer_grabbing\n0108068e=drawable/pointer_grabbing_icon\n0108068f=drawable/pointer_grabbing_large\n01080690=drawable/pointer_grabbing_large_icon\n01080691=drawable/pointer_hand\n01080692=drawable/pointer_hand_icon\n01080693=drawable/pointer_hand_large\n01080694=drawable/pointer_hand_large_icon\n01080695=drawable/pointer_help\n01080696=drawable/pointer_help_icon\n01080697=drawable/pointer_help_large\n01080698=drawable/pointer_help_large_icon\n01080699=drawable/pointer_horizontal_double_arrow\n0108069a=drawable/pointer_horizontal_double_arrow_icon\n0108069b=drawable/pointer_horizontal_double_arrow_large\n0108069c=drawable/pointer_horizontal_double_arrow_large_icon\n0108069d=drawable/pointer_nodrop\n0108069e=drawable/pointer_nodrop_icon\n0108069f=drawable/pointer_nodrop_large\n010806a0=drawable/pointer_nodrop_large_icon\n010806a1=drawable/pointer_spot_anchor\n010806a2=drawable/pointer_spot_anchor_icon\n010806a3=drawable/pointer_spot_hover\n010806a4=drawable/pointer_spot_hover_icon\n010806a5=drawable/pointer_spot_touch\n010806a6=drawable/pointer_spot_touch_icon\n010806a7=drawable/pointer_text\n010806a8=drawable/pointer_text_icon\n010806a9=drawable/pointer_text_large\n010806aa=drawable/pointer_text_large_icon\n010806ab=drawable/pointer_top_left_diagonal_double_arrow\n010806ac=drawable/pointer_top_left_diagonal_double_arrow_icon\n010806ad=drawable/pointer_top_left_diagonal_double_arrow_large\n010806ae=drawable/pointer_top_left_diagonal_double_arrow_large_icon\n010806af=drawable/pointer_top_right_diagonal_double_arrow\n010806b0=drawable/pointer_top_right_diagonal_double_arrow_icon\n010806b1=drawable/pointer_top_right_diagonal_double_arrow_large\n010806b2=drawable/pointer_top_right_diagonal_double_arrow_large_icon\n010806b3=drawable/pointer_vertical_double_arrow\n010806b4=drawable/pointer_vertical_double_arrow_icon\n010806b5=drawable/pointer_vertical_double_arrow_large\n010806b6=drawable/pointer_vertical_double_arrow_large_icon\n010806b7=drawable/pointer_vertical_text\n010806b8=drawable/pointer_vertical_text_icon\n010806b9=drawable/pointer_vertical_text_large\n010806ba=drawable/pointer_vertical_text_large_icon\n010806bb=drawable/pointer_wait\n010806bc=drawable/pointer_wait_0\n010806bd=drawable/pointer_wait_1\n010806be=drawable/pointer_wait_10\n010806bf=drawable/pointer_wait_11\n010806c0=drawable/pointer_wait_12\n010806c1=drawable/pointer_wait_13\n010806c2=drawable/pointer_wait_14\n010806c3=drawable/pointer_wait_15\n010806c4=drawable/pointer_wait_16\n010806c5=drawable/pointer_wait_17\n010806c6=drawable/pointer_wait_18\n010806c7=drawable/pointer_wait_19\n010806c8=drawable/pointer_wait_2\n010806c9=drawable/pointer_wait_20\n010806ca=drawable/pointer_wait_21\n010806cb=drawable/pointer_wait_22\n010806cc=drawable/pointer_wait_23\n010806cd=drawable/pointer_wait_24\n010806ce=drawable/pointer_wait_25\n010806cf=drawable/pointer_wait_26\n010806d0=drawable/pointer_wait_27\n010806d1=drawable/pointer_wait_28\n010806d2=drawable/pointer_wait_29\n010806d3=drawable/pointer_wait_3\n010806d4=drawable/pointer_wait_30\n010806d5=drawable/pointer_wait_31\n010806d6=drawable/pointer_wait_32\n010806d7=drawable/pointer_wait_33\n010806d8=drawable/pointer_wait_34\n010806d9=drawable/pointer_wait_35\n010806da=drawable/pointer_wait_4\n010806db=drawable/pointer_wait_5\n010806dc=drawable/pointer_wait_6\n010806dd=drawable/pointer_wait_7\n010806de=drawable/pointer_wait_8\n010806df=drawable/pointer_wait_9\n010806e0=drawable/pointer_wait_icon\n010806e1=drawable/pointer_zoom_in\n010806e2=drawable/pointer_zoom_in_icon\n010806e3=drawable/pointer_zoom_in_large\n010806e4=drawable/pointer_zoom_in_large_icon\n010806e5=drawable/pointer_zoom_out\n010806e6=drawable/pointer_zoom_out_icon\n010806e7=drawable/pointer_zoom_out_large\n010806e8=drawable/pointer_zoom_out_large_icon\n010806e9=drawable/popup_background_material\n010806ea=drawable/popup_background_mtrl_mult\n010806eb=drawable/popup_bottom_bright\n010806ec=drawable/popup_bottom_dark\n010806ed=drawable/popup_bottom_medium\n010806ee=drawable/popup_center_bright\n010806ef=drawable/popup_center_dark\n010806f0=drawable/popup_center_medium\n010806f1=drawable/popup_full_bright\n010806f2=drawable/popup_full_dark\n010806f3=drawable/popup_inline_error\n010806f4=drawable/popup_inline_error_above\n010806f5=drawable/popup_inline_error_above_am\n010806f6=drawable/popup_inline_error_above_holo_dark\n010806f7=drawable/popup_inline_error_above_holo_dark_am\n010806f8=drawable/popup_inline_error_above_holo_light\n010806f9=drawable/popup_inline_error_above_holo_light_am\n010806fa=drawable/popup_inline_error_am\n010806fb=drawable/popup_inline_error_holo_dark\n010806fc=drawable/popup_inline_error_holo_dark_am\n010806fd=drawable/popup_inline_error_holo_light\n010806fe=drawable/popup_inline_error_holo_light_am\n010806ff=drawable/popup_top_bright\n01080700=drawable/popup_top_dark\n01080701=drawable/pressed_application_background_static\n01080702=drawable/progress_bg_holo_dark\n01080703=drawable/progress_bg_holo_light\n01080704=drawable/progress_horizontal_holo_dark\n01080705=drawable/progress_horizontal_holo_light\n01080706=drawable/progress_horizontal_material\n01080707=drawable/progress_indeterminate_anim_large_material\n01080708=drawable/progress_indeterminate_anim_medium_material\n01080709=drawable/progress_indeterminate_horizontal_holo\n0108070a=drawable/progress_indeterminate_horizontal_material\n0108070b=drawable/progress_large\n0108070c=drawable/progress_large_holo\n0108070d=drawable/progress_large_material\n0108070e=drawable/progress_large_white\n0108070f=drawable/progress_medium\n01080710=drawable/progress_medium_holo\n01080711=drawable/progress_medium_material\n01080712=drawable/progress_medium_white\n01080713=drawable/progress_primary_holo_dark\n01080714=drawable/progress_primary_holo_light\n01080715=drawable/progress_secondary_holo_dark\n01080716=drawable/progress_secondary_holo_light\n01080717=drawable/progress_small\n01080718=drawable/progress_small_holo\n01080719=drawable/progress_small_material\n0108071a=drawable/progress_small_titlebar\n0108071b=drawable/progress_small_white\n0108071c=drawable/progress_static_material\n0108071d=drawable/progressbar_indeterminate1\n0108071e=drawable/progressbar_indeterminate2\n0108071f=drawable/progressbar_indeterminate3\n01080720=drawable/progressbar_indeterminate_holo1\n01080721=drawable/progressbar_indeterminate_holo2\n01080722=drawable/progressbar_indeterminate_holo3\n01080723=drawable/progressbar_indeterminate_holo4\n01080724=drawable/progressbar_indeterminate_holo5\n01080725=drawable/progressbar_indeterminate_holo6\n01080726=drawable/progressbar_indeterminate_holo7\n01080727=drawable/progressbar_indeterminate_holo8\n01080728=drawable/quickactions_arrowdown_left_holo_dark\n01080729=drawable/quickactions_arrowdown_left_holo_light\n0108072a=drawable/quickactions_arrowdown_right_holo_dark\n0108072b=drawable/quickactions_arrowdown_right_holo_light\n0108072c=drawable/quickactions_arrowup_left_holo_dark\n0108072d=drawable/quickactions_arrowup_left_holo_light\n0108072e=drawable/quickactions_arrowup_left_right_holo_dark\n0108072f=drawable/quickactions_arrowup_right_holo_light\n01080730=drawable/quickcontact_badge_overlay_dark\n01080731=drawable/quickcontact_badge_overlay_focused_dark\n01080732=drawable/quickcontact_badge_overlay_focused_dark_am\n01080733=drawable/quickcontact_badge_overlay_focused_light\n01080734=drawable/quickcontact_badge_overlay_focused_light_am\n01080735=drawable/quickcontact_badge_overlay_light\n01080736=drawable/quickcontact_badge_overlay_normal_dark\n01080737=drawable/quickcontact_badge_overlay_normal_dark_am\n01080738=drawable/quickcontact_badge_overlay_normal_light\n01080739=drawable/quickcontact_badge_overlay_normal_light_am\n0108073a=drawable/quickcontact_badge_overlay_pressed_dark\n0108073b=drawable/quickcontact_badge_overlay_pressed_dark_am\n0108073c=drawable/quickcontact_badge_overlay_pressed_light\n0108073d=drawable/quickcontact_badge_overlay_pressed_light_am\n0108073e=drawable/rate_star_big_half\n0108073f=drawable/rate_star_big_half_holo_dark\n01080740=drawable/rate_star_big_half_holo_light\n01080741=drawable/rate_star_big_off\n01080742=drawable/rate_star_big_off_holo_dark\n01080743=drawable/rate_star_big_off_holo_light\n01080744=drawable/rate_star_big_on\n01080745=drawable/rate_star_big_on_holo_dark\n01080746=drawable/rate_star_big_on_holo_light\n01080747=drawable/rate_star_med_half\n01080748=drawable/rate_star_med_half_holo_dark\n01080749=drawable/rate_star_med_half_holo_light\n0108074a=drawable/rate_star_med_off\n0108074b=drawable/rate_star_med_off_holo_dark\n0108074c=drawable/rate_star_med_off_holo_light\n0108074d=drawable/rate_star_med_on\n0108074e=drawable/rate_star_med_on_holo_dark\n0108074f=drawable/rate_star_med_on_holo_light\n01080750=drawable/rate_star_small_half\n01080751=drawable/rate_star_small_half_holo_dark\n01080752=drawable/rate_star_small_half_holo_light\n01080753=drawable/rate_star_small_off\n01080754=drawable/rate_star_small_off_holo_dark\n01080755=drawable/rate_star_small_off_holo_light\n01080756=drawable/rate_star_small_on\n01080757=drawable/rate_star_small_on_holo_dark\n01080758=drawable/rate_star_small_on_holo_light\n01080759=drawable/ratingbar\n0108075a=drawable/ratingbar_full\n0108075b=drawable/ratingbar_full_empty\n0108075c=drawable/ratingbar_full_empty_holo_dark\n0108075d=drawable/ratingbar_full_empty_holo_light\n0108075e=drawable/ratingbar_full_empty_material\n0108075f=drawable/ratingbar_full_filled\n01080760=drawable/ratingbar_full_filled_holo_dark\n01080761=drawable/ratingbar_full_filled_holo_light\n01080762=drawable/ratingbar_full_filled_material\n01080763=drawable/ratingbar_full_half_material\n01080764=drawable/ratingbar_full_holo_dark\n01080765=drawable/ratingbar_full_holo_light\n01080766=drawable/ratingbar_holo_dark\n01080767=drawable/ratingbar_holo_light\n01080768=drawable/ratingbar_indicator_material\n01080769=drawable/ratingbar_material\n0108076a=drawable/ratingbar_small\n0108076b=drawable/ratingbar_small_holo_dark\n0108076c=drawable/ratingbar_small_holo_light\n0108076d=drawable/ratingbar_small_material\n0108076e=drawable/recent_dialog_background\n0108076f=drawable/red_shield\n01080770=drawable/resolver_icon_placeholder\n01080771=drawable/resolver_turn_on_work_button_ripple_background\n01080772=drawable/reticle\n01080773=drawable/safe_mode_background\n01080774=drawable/screen_background_holo_dark\n01080775=drawable/screen_background_holo_light\n01080776=drawable/screen_background_selector_dark\n01080777=drawable/screen_background_selector_light\n01080778=drawable/scroll_indicator_material\n01080779=drawable/scrollbar_handle_accelerated_anim2\n0108077a=drawable/scrollbar_handle_holo_dark\n0108077b=drawable/scrollbar_handle_holo_light\n0108077c=drawable/scrollbar_handle_horizontal\n0108077d=drawable/scrollbar_handle_material\n0108077e=drawable/scrollbar_handle_vertical\n0108077f=drawable/scrollbar_vertical_thumb\n01080780=drawable/scrollbar_vertical_track\n01080781=drawable/scrubber_control_disabled_holo\n01080782=drawable/scrubber_control_focused_holo\n01080783=drawable/scrubber_control_normal_holo\n01080784=drawable/scrubber_control_on_mtrl_alpha\n01080785=drawable/scrubber_control_on_pressed_mtrl_alpha\n01080786=drawable/scrubber_control_pressed_holo\n01080787=drawable/scrubber_control_selector_holo\n01080788=drawable/scrubber_primary_holo\n01080789=drawable/scrubber_primary_mtrl_alpha\n0108078a=drawable/scrubber_progress_horizontal_holo_dark\n0108078b=drawable/scrubber_progress_horizontal_holo_light\n0108078c=drawable/scrubber_secondary_holo\n0108078d=drawable/scrubber_track_holo_dark\n0108078e=drawable/scrubber_track_holo_light\n0108078f=drawable/scrubber_track_mtrl_alpha\n01080790=drawable/search_bar_default_color\n01080791=drawable/search_dropdown_background\n01080792=drawable/search_dropdown_dark\n01080793=drawable/search_dropdown_light\n01080794=drawable/search_plate\n01080795=drawable/search_plate_global\n01080796=drawable/search_spinner\n01080797=drawable/seek_thumb\n01080798=drawable/seek_thumb_normal\n01080799=drawable/seek_thumb_pressed\n0108079a=drawable/seek_thumb_selected\n0108079b=drawable/seekbar_thumb_material_anim\n0108079c=drawable/seekbar_thumb_pressed_to_unpressed\n0108079d=drawable/seekbar_thumb_pressed_to_unpressed_animation\n0108079e=drawable/seekbar_thumb_unpressed_to_pressed\n0108079f=drawable/seekbar_thumb_unpressed_to_pressed_animation\n010807a0=drawable/seekbar_tick_mark_material\n010807a1=drawable/seekbar_track_material\n010807a2=drawable/selected_day_background\n010807a3=drawable/settings_header\n010807a4=drawable/settings_header_raw\n010807a5=drawable/silent_mode_indicator\n010807a6=drawable/sim_dark_blue\n010807a7=drawable/sim_dark_green\n010807a8=drawable/sim_dark_orange\n010807a9=drawable/sim_dark_purple\n010807aa=drawable/sim_light_blue\n010807ab=drawable/sim_light_green\n010807ac=drawable/sim_light_orange\n010807ad=drawable/sim_light_purple\n010807ae=drawable/slice_remote_input_bg\n010807af=drawable/slice_ripple_drawable\n010807b0=drawable/spinner_16_inner_holo\n010807b1=drawable/spinner_16_outer_holo\n010807b2=drawable/spinner_48_inner_holo\n010807b3=drawable/spinner_48_outer_holo\n010807b4=drawable/spinner_76_inner_holo\n010807b5=drawable/spinner_76_outer_holo\n010807b6=drawable/spinner_ab_activated_holo_dark\n010807b7=drawable/spinner_ab_activated_holo_light\n010807b8=drawable/spinner_ab_default_holo_dark\n010807b9=drawable/spinner_ab_default_holo_dark_am\n010807ba=drawable/spinner_ab_default_holo_light\n010807bb=drawable/spinner_ab_default_holo_light_am\n010807bc=drawable/spinner_ab_disabled_holo_dark\n010807bd=drawable/spinner_ab_disabled_holo_dark_am\n010807be=drawable/spinner_ab_disabled_holo_light\n010807bf=drawable/spinner_ab_disabled_holo_light_am\n010807c0=drawable/spinner_ab_focused_holo_dark\n010807c1=drawable/spinner_ab_focused_holo_dark_am\n010807c2=drawable/spinner_ab_focused_holo_light\n010807c3=drawable/spinner_ab_focused_holo_light_am\n010807c4=drawable/spinner_ab_holo_dark\n010807c5=drawable/spinner_ab_holo_light\n010807c6=drawable/spinner_ab_pressed_holo_dark\n010807c7=drawable/spinner_ab_pressed_holo_dark_am\n010807c8=drawable/spinner_ab_pressed_holo_light\n010807c9=drawable/spinner_ab_pressed_holo_light_am\n010807ca=drawable/spinner_activated_holo_dark\n010807cb=drawable/spinner_activated_holo_light\n010807cc=drawable/spinner_background_holo_dark\n010807cd=drawable/spinner_background_holo_light\n010807ce=drawable/spinner_background_material\n010807cf=drawable/spinner_black_16\n010807d0=drawable/spinner_black_20\n010807d1=drawable/spinner_black_48\n010807d2=drawable/spinner_black_76\n010807d3=drawable/spinner_default_holo_dark\n010807d4=drawable/spinner_default_holo_dark_am\n010807d5=drawable/spinner_default_holo_light\n010807d6=drawable/spinner_default_holo_light_am\n010807d7=drawable/spinner_disabled_holo\n010807d8=drawable/spinner_disabled_holo_dark\n010807d9=drawable/spinner_disabled_holo_dark_am\n010807da=drawable/spinner_disabled_holo_light\n010807db=drawable/spinner_disabled_holo_light_am\n010807dc=drawable/spinner_dropdown_background_down\n010807dd=drawable/spinner_dropdown_background_up\n010807de=drawable/spinner_focused_holo_dark\n010807df=drawable/spinner_focused_holo_dark_am\n010807e0=drawable/spinner_focused_holo_light\n010807e1=drawable/spinner_focused_holo_light_am\n010807e2=drawable/spinner_normal\n010807e3=drawable/spinner_normal_holo\n010807e4=drawable/spinner_press\n010807e5=drawable/spinner_pressed_holo_dark\n010807e6=drawable/spinner_pressed_holo_dark_am\n010807e7=drawable/spinner_pressed_holo_light\n010807e8=drawable/spinner_pressed_holo_light_am\n010807e9=drawable/spinner_select\n010807ea=drawable/spinner_textfield_background_material\n010807eb=drawable/spinner_white_16\n010807ec=drawable/spinner_white_48\n010807ed=drawable/spinner_white_76\n010807ee=drawable/stat_ecb_mode\n010807ef=drawable/stat_notify_car_mode\n010807f0=drawable/stat_notify_disabled_data\n010807f1=drawable/stat_notify_disk_full\n010807f2=drawable/stat_notify_email_generic\n010807f3=drawable/stat_notify_gmail\n010807f4=drawable/stat_notify_mmcc_indication_icn\n010807f5=drawable/stat_notify_rssi_in_range\n010807f6=drawable/stat_notify_sim_toolkit\n010807f7=drawable/stat_notify_sync_anim0\n010807f8=drawable/stat_notify_sync_error\n010807f9=drawable/stat_notify_wifi_in_range\n010807fa=drawable/stat_sys_adb\n010807fb=drawable/stat_sys_battery\n010807fc=drawable/stat_sys_battery_0\n010807fd=drawable/stat_sys_battery_10\n010807fe=drawable/stat_sys_battery_100\n010807ff=drawable/stat_sys_battery_15\n01080800=drawable/stat_sys_battery_20\n01080801=drawable/stat_sys_battery_28\n01080802=drawable/stat_sys_battery_40\n01080803=drawable/stat_sys_battery_43\n01080804=drawable/stat_sys_battery_57\n01080805=drawable/stat_sys_battery_60\n01080806=drawable/stat_sys_battery_71\n01080807=drawable/stat_sys_battery_80\n01080808=drawable/stat_sys_battery_85\n01080809=drawable/stat_sys_battery_charge\n0108080a=drawable/stat_sys_battery_charge_anim0\n0108080b=drawable/stat_sys_battery_charge_anim1\n0108080c=drawable/stat_sys_battery_charge_anim100\n0108080d=drawable/stat_sys_battery_charge_anim15\n0108080e=drawable/stat_sys_battery_charge_anim2\n0108080f=drawable/stat_sys_battery_charge_anim28\n01080810=drawable/stat_sys_battery_charge_anim3\n01080811=drawable/stat_sys_battery_charge_anim4\n01080812=drawable/stat_sys_battery_charge_anim43\n01080813=drawable/stat_sys_battery_charge_anim5\n01080814=drawable/stat_sys_battery_charge_anim57\n01080815=drawable/stat_sys_battery_charge_anim71\n01080816=drawable/stat_sys_battery_charge_anim85\n01080817=drawable/stat_sys_battery_unknown\n01080818=drawable/stat_sys_certificate_info\n01080819=drawable/stat_sys_data_usb\n0108081a=drawable/stat_sys_data_wimax_signal_3_fully\n0108081b=drawable/stat_sys_data_wimax_signal_disconnected\n0108081c=drawable/stat_sys_download_anim0\n0108081d=drawable/stat_sys_download_anim1\n0108081e=drawable/stat_sys_download_anim2\n0108081f=drawable/stat_sys_download_anim3\n01080820=drawable/stat_sys_download_anim4\n01080821=drawable/stat_sys_download_anim5\n01080822=drawable/stat_sys_download_done_static\n01080823=drawable/stat_sys_gps_on\n01080824=drawable/stat_sys_r_signal_0_cdma\n01080825=drawable/stat_sys_r_signal_1_cdma\n01080826=drawable/stat_sys_r_signal_2_cdma\n01080827=drawable/stat_sys_r_signal_3_cdma\n01080828=drawable/stat_sys_r_signal_4_cdma\n01080829=drawable/stat_sys_ra_signal_0_cdma\n0108082a=drawable/stat_sys_ra_signal_1_cdma\n0108082b=drawable/stat_sys_ra_signal_2_cdma\n0108082c=drawable/stat_sys_ra_signal_3_cdma\n0108082d=drawable/stat_sys_ra_signal_4_cdma\n0108082e=drawable/stat_sys_signal_0_cdma\n0108082f=drawable/stat_sys_signal_1_cdma\n01080830=drawable/stat_sys_signal_2_cdma\n01080831=drawable/stat_sys_signal_3_cdma\n01080832=drawable/stat_sys_signal_4_cdma\n01080833=drawable/stat_sys_signal_evdo_0\n01080834=drawable/stat_sys_signal_evdo_1\n01080835=drawable/stat_sys_signal_evdo_2\n01080836=drawable/stat_sys_signal_evdo_3\n01080837=drawable/stat_sys_signal_evdo_4\n01080838=drawable/stat_sys_tether_wifi\n01080839=drawable/stat_sys_throttled\n0108083a=drawable/stat_sys_upload_anim0\n0108083b=drawable/stat_sys_upload_anim1\n0108083c=drawable/stat_sys_upload_anim2\n0108083d=drawable/stat_sys_upload_anim3\n0108083e=drawable/stat_sys_upload_anim4\n0108083f=drawable/stat_sys_upload_anim5\n01080840=drawable/stat_sys_vitals\n01080841=drawable/status_bar_background\n01080842=drawable/status_bar_closed_default_background\n01080843=drawable/status_bar_header_background\n01080844=drawable/status_bar_item_app_background_normal\n01080845=drawable/status_bar_item_background_focus\n01080846=drawable/status_bar_item_background_normal\n01080847=drawable/status_bar_item_background_pressed\n01080848=drawable/status_bar_opened_default_background\n01080849=drawable/statusbar_background\n0108084a=drawable/submenu_arrow\n0108084b=drawable/submenu_arrow_nofocus\n0108084c=drawable/switch_bg_disabled_holo_dark\n0108084d=drawable/switch_bg_disabled_holo_light\n0108084e=drawable/switch_bg_focused_holo_dark\n0108084f=drawable/switch_bg_focused_holo_light\n01080850=drawable/switch_bg_holo_dark\n01080851=drawable/switch_bg_holo_light\n01080852=drawable/switch_inner_holo_dark\n01080853=drawable/switch_inner_holo_light\n01080854=drawable/switch_thumb_activated_holo_dark\n01080855=drawable/switch_thumb_activated_holo_light\n01080856=drawable/switch_thumb_disabled_holo_dark\n01080857=drawable/switch_thumb_disabled_holo_light\n01080858=drawable/switch_thumb_holo_dark\n01080859=drawable/switch_thumb_holo_light\n0108085a=drawable/switch_thumb_holo_light_v2\n0108085b=drawable/switch_thumb_material_anim\n0108085c=drawable/switch_thumb_pressed_holo_dark\n0108085d=drawable/switch_thumb_pressed_holo_light\n0108085e=drawable/switch_thumb_watch_default_dark_anim\n0108085f=drawable/switch_track_holo_dark\n01080860=drawable/switch_track_holo_light\n01080861=drawable/switch_track_material\n01080862=drawable/sym_action_add\n01080863=drawable/sym_app_on_sd_unavailable_icon\n01080864=drawable/sym_def_app_icon_background\n01080865=drawable/sym_keyboard_delete\n01080866=drawable/sym_keyboard_delete_dim\n01080867=drawable/sym_keyboard_delete_holo\n01080868=drawable/sym_keyboard_enter\n01080869=drawable/sym_keyboard_feedback_delete\n0108086a=drawable/sym_keyboard_feedback_ok\n0108086b=drawable/sym_keyboard_feedback_return\n0108086c=drawable/sym_keyboard_feedback_shift\n0108086d=drawable/sym_keyboard_feedback_shift_locked\n0108086e=drawable/sym_keyboard_feedback_space\n0108086f=drawable/sym_keyboard_num0_no_plus\n01080870=drawable/sym_keyboard_num1\n01080871=drawable/sym_keyboard_num2\n01080872=drawable/sym_keyboard_num3\n01080873=drawable/sym_keyboard_num4\n01080874=drawable/sym_keyboard_num5\n01080875=drawable/sym_keyboard_num6\n01080876=drawable/sym_keyboard_num7\n01080877=drawable/sym_keyboard_num8\n01080878=drawable/sym_keyboard_num9\n01080879=drawable/sym_keyboard_ok\n0108087a=drawable/sym_keyboard_ok_dim\n0108087b=drawable/sym_keyboard_return\n0108087c=drawable/sym_keyboard_return_holo\n0108087d=drawable/sym_keyboard_shift\n0108087e=drawable/sym_keyboard_shift_locked\n0108087f=drawable/sym_keyboard_space\n01080880=drawable/tab_bottom_holo\n01080881=drawable/tab_bottom_left\n01080882=drawable/tab_bottom_left_v4\n01080883=drawable/tab_bottom_right\n01080884=drawable/tab_bottom_right_v4\n01080885=drawable/tab_focus\n01080886=drawable/tab_focus_bar_left\n01080887=drawable/tab_focus_bar_right\n01080888=drawable/tab_indicator\n01080889=drawable/tab_indicator_ab_holo\n0108088a=drawable/tab_indicator_holo\n0108088b=drawable/tab_indicator_material\n0108088c=drawable/tab_indicator_mtrl_alpha\n0108088d=drawable/tab_indicator_resolver\n0108088e=drawable/tab_indicator_v4\n0108088f=drawable/tab_press\n01080890=drawable/tab_press_bar_left\n01080891=drawable/tab_press_bar_right\n01080892=drawable/tab_pressed_holo\n01080893=drawable/tab_selected\n01080894=drawable/tab_selected_bar_left\n01080895=drawable/tab_selected_bar_left_v4\n01080896=drawable/tab_selected_bar_right\n01080897=drawable/tab_selected_bar_right_v4\n01080898=drawable/tab_selected_focused_holo\n01080899=drawable/tab_selected_holo\n0108089a=drawable/tab_selected_pressed_holo\n0108089b=drawable/tab_selected_v4\n0108089c=drawable/tab_unselected\n0108089d=drawable/tab_unselected_focused_holo\n0108089e=drawable/tab_unselected_holo\n0108089f=drawable/tab_unselected_pressed_holo\n010808a0=drawable/tab_unselected_v4\n010808a1=drawable/text_cursor_holo_dark\n010808a2=drawable/text_cursor_holo_light\n010808a3=drawable/text_cursor_material\n010808a4=drawable/text_edit_paste_window\n010808a5=drawable/text_edit_side_paste_window\n010808a6=drawable/text_edit_suggestions_window\n010808a7=drawable/text_select_handle_left_material\n010808a8=drawable/text_select_handle_left_mtrl_alpha\n010808a9=drawable/text_select_handle_middle_material\n010808aa=drawable/text_select_handle_middle_mtrl_alpha\n010808ab=drawable/text_select_handle_right_material\n010808ac=drawable/text_select_handle_right_mtrl_alpha\n010808ad=drawable/textfield_activated_holo_dark\n010808ae=drawable/textfield_activated_holo_light\n010808af=drawable/textfield_activated_mtrl_alpha\n010808b0=drawable/textfield_bg_activated_holo_dark\n010808b1=drawable/textfield_bg_default_holo_dark\n010808b2=drawable/textfield_bg_disabled_focused_holo_dark\n010808b3=drawable/textfield_bg_disabled_holo_dark\n010808b4=drawable/textfield_bg_focused_holo_dark\n010808b5=drawable/textfield_default\n010808b6=drawable/textfield_default_holo_dark\n010808b7=drawable/textfield_default_holo_light\n010808b8=drawable/textfield_default_mtrl_alpha\n010808b9=drawable/textfield_disabled\n010808ba=drawable/textfield_disabled_focused_holo_dark\n010808bb=drawable/textfield_disabled_focused_holo_light\n010808bc=drawable/textfield_disabled_holo_dark\n010808bd=drawable/textfield_disabled_holo_light\n010808be=drawable/textfield_disabled_selected\n010808bf=drawable/textfield_focused_holo_dark\n010808c0=drawable/textfield_focused_holo_light\n010808c1=drawable/textfield_longpress_holo\n010808c2=drawable/textfield_multiline_activated_holo_dark\n010808c3=drawable/textfield_multiline_activated_holo_light\n010808c4=drawable/textfield_multiline_default_holo_dark\n010808c5=drawable/textfield_multiline_default_holo_light\n010808c6=drawable/textfield_multiline_disabled_focused_holo_dark\n010808c7=drawable/textfield_multiline_disabled_focused_holo_light\n010808c8=drawable/textfield_multiline_disabled_holo_dark\n010808c9=drawable/textfield_multiline_disabled_holo_light\n010808ca=drawable/textfield_multiline_focused_holo_dark\n010808cb=drawable/textfield_multiline_focused_holo_light\n010808cc=drawable/textfield_pressed_holo\n010808cd=drawable/textfield_search\n010808ce=drawable/textfield_search_activated_mtrl_alpha\n010808cf=drawable/textfield_search_default\n010808d0=drawable/textfield_search_default_holo_dark\n010808d1=drawable/textfield_search_default_holo_light\n010808d2=drawable/textfield_search_default_mtrl_alpha\n010808d3=drawable/textfield_search_empty\n010808d4=drawable/textfield_search_empty_default\n010808d5=drawable/textfield_search_empty_pressed\n010808d6=drawable/textfield_search_empty_selected\n010808d7=drawable/textfield_search_material\n010808d8=drawable/textfield_search_pressed\n010808d9=drawable/textfield_search_right_default_holo_dark\n010808da=drawable/textfield_search_right_default_holo_light\n010808db=drawable/textfield_search_right_selected_holo_dark\n010808dc=drawable/textfield_search_right_selected_holo_light\n010808dd=drawable/textfield_search_selected\n010808de=drawable/textfield_search_selected_holo_dark\n010808df=drawable/textfield_search_selected_holo_light\n010808e0=drawable/textfield_searchview_holo_dark\n010808e1=drawable/textfield_searchview_holo_light\n010808e2=drawable/textfield_searchview_right_holo_dark\n010808e3=drawable/textfield_searchview_right_holo_light\n010808e4=drawable/textfield_selected\n010808e5=drawable/time_picker_editable_background\n010808e6=drawable/title_bar_medium\n010808e7=drawable/title_bar_portrait\n010808e8=drawable/title_bar_shadow\n010808e9=drawable/tooltip_frame\n010808ea=drawable/transportcontrol_bg\n010808eb=drawable/unknown_image\n010808ec=drawable/unlock_default\n010808ed=drawable/unlock_halo\n010808ee=drawable/unlock_ring\n010808ef=drawable/unlock_wave\n010808f0=drawable/vector_drawable_progress_bar_large\n010808f1=drawable/vector_drawable_progress_bar_medium\n010808f2=drawable/vector_drawable_progress_bar_small\n010808f3=drawable/vector_drawable_progress_indeterminate_horizontal\n010808f4=drawable/view_accessibility_focused\n010808f5=drawable/vpn_connected\n010808f6=drawable/vpn_disconnected\n010808f7=drawable/watch_switch_thumb_mtrl_14w\n010808f8=drawable/watch_switch_thumb_mtrl_15w\n010808f9=drawable/watch_switch_thumb_mtrl_16w\n010808fa=drawable/watch_switch_thumb_mtrl_17w\n010808fb=drawable/watch_switch_thumb_mtrl_18w\n010808fc=drawable/watch_switch_track_mtrl\n01090000=layout/activity_list_item\n01090001=layout/expandable_list_content\n01090002=layout/preference_category\n01090003=layout/simple_list_item_1\n01090004=layout/simple_list_item_2\n01090005=layout/simple_list_item_checked\n01090006=layout/simple_expandable_list_item_1\n01090007=layout/simple_expandable_list_item_2\n01090008=layout/simple_spinner_item\n01090009=layout/simple_spinner_dropdown_item\n0109000a=layout/simple_dropdown_item_1line\n0109000b=layout/simple_gallery_item\n0109000c=layout/test_list_item\n0109000d=layout/two_line_list_item\n0109000e=layout/browser_link_context_header\n0109000f=layout/simple_list_item_single_choice\n01090010=layout/simple_list_item_multiple_choice\n01090011=layout/select_dialog_item\n01090012=layout/select_dialog_singlechoice\n01090013=layout/select_dialog_multichoice\n01090014=layout/list_content\n01090015=layout/simple_selectable_list_item\n01090016=layout/simple_list_item_activated_1\n01090017=layout/simple_list_item_activated_2\n01090018=layout/accessibility_button_chooser\n01090019=layout/accessibility_button_chooser_item\n0109001a=layout/accessibility_enable_service_encryption_warning\n0109001b=layout/accessibility_shortcut_chooser_item\n0109001c=layout/action_bar_home\n0109001d=layout/action_bar_home_material\n0109001e=layout/action_bar_title_item\n0109001f=layout/action_bar_up_container\n01090020=layout/action_menu_item_layout\n01090021=layout/action_menu_layout\n01090022=layout/action_mode_bar\n01090023=layout/action_mode_close_item\n01090024=layout/action_mode_close_item_material\n01090025=layout/activity_chooser_view\n01090026=layout/activity_chooser_view_list_item\n01090027=layout/activity_list\n01090028=layout/activity_list_item_2\n01090029=layout/adaptive_notification_wrapper\n0109002a=layout/alert_dialog\n0109002b=layout/alert_dialog_button_bar_material\n0109002c=layout/alert_dialog_holo\n0109002d=layout/alert_dialog_leanback\n0109002e=layout/alert_dialog_leanback_button_panel_side\n0109002f=layout/alert_dialog_material\n01090030=layout/alert_dialog_progress\n01090031=layout/alert_dialog_progress_holo\n01090032=layout/alert_dialog_progress_material\n01090033=layout/alert_dialog_title_material\n01090034=layout/always_use_checkbox\n01090035=layout/am_compat_mode_dialog\n01090036=layout/app_anr_dialog\n01090037=layout/app_error_dialog\n01090038=layout/app_not_authorized\n01090039=layout/app_permission_item\n0109003a=layout/app_permission_item_money\n0109003b=layout/app_permission_item_old\n0109003c=layout/app_perms_summary\n0109003d=layout/auto_complete_list\n0109003e=layout/autofill_dataset_picker\n0109003f=layout/autofill_dataset_picker_fullscreen\n01090040=layout/autofill_dataset_picker_header_footer\n01090041=layout/autofill_save\n01090042=layout/breadcrumbs_in_fragment\n01090043=layout/breadcrumbs_in_fragment_material\n01090044=layout/calendar_view\n01090045=layout/car_preference\n01090046=layout/car_preference_category\n01090047=layout/car_resolver_list\n01090048=layout/car_resolver_list_with_default\n01090049=layout/cascading_menu_item_layout\n0109004a=layout/character_picker\n0109004b=layout/character_picker_button\n0109004c=layout/choose_account\n0109004d=layout/choose_account_row\n0109004e=layout/choose_account_type\n0109004f=layout/choose_type_and_account\n01090050=layout/chooser_action_button\n01090051=layout/chooser_action_row\n01090052=layout/chooser_az_label_row\n01090053=layout/chooser_dialog\n01090054=layout/chooser_dialog_item\n01090055=layout/chooser_grid\n01090056=layout/chooser_grid_preview_file\n01090057=layout/chooser_grid_preview_image\n01090058=layout/chooser_grid_preview_text\n01090059=layout/chooser_list_per_profile\n0109005a=layout/chooser_profile_row\n0109005b=layout/chooser_row\n0109005c=layout/chooser_row_direct_share\n0109005d=layout/common_tab_settings\n0109005e=layout/conversation_face_pile_layout\n0109005f=layout/date_picker_dialog\n01090060=layout/date_picker_header_material\n01090061=layout/date_picker_legacy\n01090062=layout/date_picker_legacy_holo\n01090063=layout/date_picker_material\n01090064=layout/date_picker_month_item_material\n01090065=layout/date_picker_view_animator_material\n01090066=layout/day_picker_content_material\n01090067=layout/decor_caption\n01090068=layout/default_navigation\n01090069=layout/dialog_custom_title\n0109006a=layout/dialog_custom_title_holo\n0109006b=layout/dialog_custom_title_material\n0109006c=layout/dialog_title\n0109006d=layout/dialog_title_holo\n0109006e=layout/dialog_title_icons\n0109006f=layout/dialog_title_icons_holo\n01090070=layout/dialog_title_icons_material\n01090071=layout/dialog_title_material\n01090072=layout/expanded_menu_layout\n01090073=layout/floating_popup_close_overflow_button\n01090074=layout/floating_popup_container\n01090075=layout/floating_popup_menu_button\n01090076=layout/floating_popup_open_overflow_button\n01090077=layout/floating_popup_overflow_button\n01090078=layout/fragment_bread_crumb_item\n01090079=layout/fragment_bread_crumb_item_material\n0109007a=layout/fragment_bread_crumbs\n0109007b=layout/global_actions\n0109007c=layout/global_actions_item\n0109007d=layout/global_actions_silent_mode\n0109007e=layout/grant_credentials_permission\n0109007f=layout/harmful_app_warning_dialog\n01090080=layout/heavy_weight_switcher\n01090081=layout/icon_menu_item_layout\n01090082=layout/icon_menu_layout\n01090083=layout/immersive_mode_cling\n01090084=layout/input_method\n01090085=layout/input_method_extract_view\n01090086=layout/input_method_switch_dialog_title\n01090087=layout/input_method_switch_item\n01090088=layout/js_prompt\n01090089=layout/keyboard_key_preview\n0109008a=layout/keyboard_popup_keyboard\n0109008b=layout/keyguard\n0109008c=layout/language_picker_item\n0109008d=layout/language_picker_section_header\n0109008e=layout/launch_warning\n0109008f=layout/list_content_simple\n01090090=layout/list_gestures_overlay\n01090091=layout/list_menu_item_checkbox\n01090092=layout/list_menu_item_icon\n01090093=layout/list_menu_item_layout\n01090094=layout/list_menu_item_radio\n01090095=layout/locale_picker_item\n01090096=layout/media_controller\n01090097=layout/media_route_chooser_dialog\n01090098=layout/media_route_controller_dialog\n01090099=layout/media_route_list_item\n0109009a=layout/menu_item\n0109009b=layout/notification_intruder_content\n0109009c=layout/notification_material_action\n0109009d=layout/notification_material_action_emphasized\n0109009e=layout/notification_material_action_list\n0109009f=layout/notification_material_action_tombstone\n010900a0=layout/notification_material_media_action\n010900a1=layout/notification_material_media_seekbar\n010900a2=layout/notification_material_media_transfer_action\n010900a3=layout/notification_material_reply_text\n010900a4=layout/notification_template_header\n010900a5=layout/notification_template_material_base\n010900a6=layout/notification_template_material_big_base\n010900a7=layout/notification_template_material_big_media\n010900a8=layout/notification_template_material_big_picture\n010900a9=layout/notification_template_material_big_text\n010900aa=layout/notification_template_material_conversation\n010900ab=layout/notification_template_material_inbox\n010900ac=layout/notification_template_material_media\n010900ad=layout/notification_template_material_messaging\n010900ae=layout/notification_template_messaging_group\n010900af=layout/notification_template_messaging_image_message\n010900b0=layout/notification_template_messaging_text_message\n010900b1=layout/notification_template_part_chronometer\n010900b2=layout/notification_template_part_line1\n010900b3=layout/notification_template_progress\n010900b4=layout/notification_template_progressbar\n010900b5=layout/notification_template_right_icon\n010900b6=layout/notification_template_smart_reply_container\n010900b7=layout/notification_template_text\n010900b8=layout/number_picker\n010900b9=layout/number_picker_material\n010900ba=layout/number_picker_with_selector_wheel\n010900bb=layout/overlay_display_window\n010900bc=layout/permissions_account_and_authtokentype\n010900bd=layout/permissions_package_list_item\n010900be=layout/platlogo_layout\n010900bf=layout/popup_menu_header_item_layout\n010900c0=layout/popup_menu_item_layout\n010900c1=layout/power_dialog\n010900c2=layout/preference\n010900c3=layout/preference_category_holo\n010900c4=layout/preference_category_material\n010900c5=layout/preference_child\n010900c6=layout/preference_child_holo\n010900c7=layout/preference_child_material\n010900c8=layout/preference_dialog_edittext\n010900c9=layout/preference_dialog_edittext_material\n010900ca=layout/preference_dialog_seekbar\n010900cb=layout/preference_dialog_seekbar_material\n010900cc=layout/preference_header_item\n010900cd=layout/preference_header_item_material\n010900ce=layout/preference_holo\n010900cf=layout/preference_information\n010900d0=layout/preference_information_holo\n010900d1=layout/preference_information_material\n010900d2=layout/preference_list_content\n010900d3=layout/preference_list_content_material\n010900d4=layout/preference_list_content_single\n010900d5=layout/preference_list_fragment\n010900d6=layout/preference_list_fragment_material\n010900d7=layout/preference_material\n010900d8=layout/preference_widget_checkbox\n010900d9=layout/preference_widget_seekbar\n010900da=layout/preference_widget_seekbar_material\n010900db=layout/preference_widget_switch\n010900dc=layout/preferences\n010900dd=layout/progress_dialog\n010900de=layout/progress_dialog_holo\n010900df=layout/progress_dialog_material\n010900e0=layout/recent_apps_dialog\n010900e1=layout/recent_apps_icon\n010900e2=layout/remote_views_adapter_default_loading_view\n010900e3=layout/resolve_grid_item\n010900e4=layout/resolve_list_item\n010900e5=layout/resolver_different_item_header\n010900e6=layout/resolver_empty_states\n010900e7=layout/resolver_list\n010900e8=layout/resolver_list_per_profile\n010900e9=layout/resolver_list_with_default\n010900ea=layout/restrictions_pin_challenge\n010900eb=layout/restrictions_pin_setup\n010900ec=layout/safe_mode\n010900ed=layout/screen\n010900ee=layout/screen_action_bar\n010900ef=layout/screen_custom_title\n010900f0=layout/screen_progress\n010900f1=layout/screen_simple\n010900f2=layout/screen_simple_overlay_action_mode\n010900f3=layout/screen_title\n010900f4=layout/screen_title_icons\n010900f5=layout/screen_toolbar\n010900f6=layout/search_bar\n010900f7=layout/search_dropdown_item_icons_2line\n010900f8=layout/search_view\n010900f9=layout/select_dialog\n010900fa=layout/select_dialog_holo\n010900fb=layout/select_dialog_item_holo\n010900fc=layout/select_dialog_item_material\n010900fd=layout/select_dialog_material\n010900fe=layout/select_dialog_multichoice_holo\n010900ff=layout/select_dialog_multichoice_material\n01090100=layout/select_dialog_singlechoice_holo\n01090101=layout/select_dialog_singlechoice_material\n01090102=layout/shutdown_dialog\n01090103=layout/simple_account_item\n01090104=layout/simple_dropdown_hint\n01090105=layout/simple_dropdown_item_2line\n01090106=layout/simple_list_item_2_single_choice\n01090107=layout/slice_grid\n01090108=layout/slice_message\n01090109=layout/slice_message_local\n0109010a=layout/slice_remote_input\n0109010b=layout/slice_secondary_text\n0109010c=layout/slice_small_template\n0109010d=layout/slice_title\n0109010e=layout/sms_short_code_confirmation_dialog\n0109010f=layout/ssl_certificate\n01090110=layout/status_bar_latest_event_content\n01090111=layout/subscription_item_layout\n01090112=layout/system_user_home\n01090113=layout/tab_content\n01090114=layout/tab_indicator\n01090115=layout/tab_indicator_holo\n01090116=layout/tab_indicator_material\n01090117=layout/tab_indicator_resolver\n01090118=layout/text_drag_thumbnail\n01090119=layout/text_edit_action_popup_text\n0109011a=layout/text_edit_no_paste_window\n0109011b=layout/text_edit_paste_window\n0109011c=layout/text_edit_side_no_paste_window\n0109011d=layout/text_edit_side_paste_window\n0109011e=layout/text_edit_suggestion_container\n0109011f=layout/text_edit_suggestion_container_material\n01090120=layout/text_edit_suggestion_item\n01090121=layout/text_edit_suggestion_item_material\n01090122=layout/text_edit_suggestions_window\n01090123=layout/textview_hint\n01090124=layout/time_picker_dialog\n01090125=layout/time_picker_header_material\n01090126=layout/time_picker_legacy\n01090127=layout/time_picker_legacy_material\n01090128=layout/time_picker_material\n01090129=layout/time_picker_text_input_material\n0109012a=layout/tooltip\n0109012b=layout/transient_notification\n0109012c=layout/twelve_key_entry\n0109012d=layout/typing_filter\n0109012e=layout/unsupported_compile_sdk_dialog_content\n0109012f=layout/unsupported_display_size_dialog_content\n01090130=layout/user_switching_dialog\n01090131=layout/voice_interaction_session\n01090132=layout/web_runtime\n01090133=layout/web_text_view_dropdown\n01090134=layout/webview_find\n01090135=layout/webview_select_singlechoice\n01090136=layout/work_widget_mask_view\n01090137=layout/year_label_text_view\n01090138=layout/zoom_browser_accessory_buttons\n01090139=layout/zoom_container\n0109013a=layout/zoom_controls\n0109013b=layout/zoom_magnify\n010a0000=anim/fade_in\n010a0001=anim/fade_out\n010a0002=anim/slide_in_left\n010a0003=anim/slide_out_right\n010a0004=anim/accelerate_decelerate_interpolator\n010a0005=anim/accelerate_interpolator\n010a0006=anim/decelerate_interpolator\n010a0007=anim/anticipate_interpolator\n010a0008=anim/overshoot_interpolator\n010a0009=anim/anticipate_overshoot_interpolator\n010a000a=anim/bounce_interpolator\n010a000b=anim/linear_interpolator\n010a000c=anim/cycle_interpolator\n010a000d=anim/activity_close_enter\n010a000e=anim/activity_close_exit\n010a000f=anim/activity_open_enter\n010a0010=anim/activity_open_exit\n010a0011=anim/activity_translucent_close_exit\n010a0012=anim/activity_translucent_open_enter\n010a0013=anim/app_starting_exit\n010a0014=anim/btn_checkbox_to_checked_box_inner_merged_animation\n010a0015=anim/btn_checkbox_to_checked_box_outer_merged_animation\n010a0016=anim/btn_checkbox_to_checked_icon_null_animation\n010a0017=anim/btn_checkbox_to_unchecked_box_inner_merged_animation\n010a0018=anim/btn_checkbox_to_unchecked_check_path_merged_animation\n010a0019=anim/btn_checkbox_to_unchecked_icon_null_animation\n010a001a=anim/btn_radio_to_off_mtrl_dot_group_animation\n010a001b=anim/btn_radio_to_off_mtrl_ring_outer_animation\n010a001c=anim/btn_radio_to_off_mtrl_ring_outer_path_animation\n010a001d=anim/btn_radio_to_on_mtrl_dot_group_animation\n010a001e=anim/btn_radio_to_on_mtrl_ring_outer_animation\n010a001f=anim/btn_radio_to_on_mtrl_ring_outer_path_animation\n010a0020=anim/button_state_list_anim_material\n010a0021=anim/cross_profile_apps_thumbnail_enter\n010a0022=anim/date_picker_fade_in_material\n010a0023=anim/date_picker_fade_out_material\n010a0024=anim/dialog_enter\n010a0025=anim/dialog_exit\n010a0026=anim/dock_bottom_enter\n010a0027=anim/dock_bottom_exit\n010a0028=anim/dock_bottom_exit_keyguard\n010a0029=anim/dock_left_enter\n010a002a=anim/dock_left_exit\n010a002b=anim/dock_right_enter\n010a002c=anim/dock_right_exit\n010a002d=anim/dock_top_enter\n010a002e=anim/dock_top_exit\n010a002f=anim/dream_activity_close_exit\n010a0030=anim/dream_activity_open_enter\n010a0031=anim/dream_activity_open_exit\n010a0032=anim/fast_fade_in\n010a0033=anim/fast_fade_out\n010a0034=anim/flat_button_state_list_anim_material\n010a0035=anim/ft_avd_toarrow_rectangle_1_animation\n010a0036=anim/ft_avd_toarrow_rectangle_1_pivot_0_animation\n010a0037=anim/ft_avd_toarrow_rectangle_1_pivot_animation\n010a0038=anim/ft_avd_toarrow_rectangle_2_animation\n010a0039=anim/ft_avd_toarrow_rectangle_2_pivot_0_animation\n010a003a=anim/ft_avd_toarrow_rectangle_2_pivot_animation\n010a003b=anim/ft_avd_toarrow_rectangle_3_animation\n010a003c=anim/ft_avd_toarrow_rectangle_3_pivot_0_animation\n010a003d=anim/ft_avd_toarrow_rectangle_3_pivot_animation\n010a003e=anim/ft_avd_toarrow_rectangle_4_animation\n010a003f=anim/ft_avd_toarrow_rectangle_5_animation\n010a0040=anim/ft_avd_toarrow_rectangle_6_animation\n010a0041=anim/ft_avd_toarrow_rectangle_path_1_animation\n010a0042=anim/ft_avd_toarrow_rectangle_path_2_animation\n010a0043=anim/ft_avd_toarrow_rectangle_path_3_animation\n010a0044=anim/ft_avd_toarrow_rectangle_path_4_animation\n010a0045=anim/ft_avd_toarrow_rectangle_path_5_animation\n010a0046=anim/ft_avd_toarrow_rectangle_path_6_animation\n010a0047=anim/ft_avd_tooverflow_rectangle_1_animation\n010a0048=anim/ft_avd_tooverflow_rectangle_1_pivot_animation\n010a0049=anim/ft_avd_tooverflow_rectangle_2_animation\n010a004a=anim/ft_avd_tooverflow_rectangle_2_pivot_animation\n010a004b=anim/ft_avd_tooverflow_rectangle_3_animation\n010a004c=anim/ft_avd_tooverflow_rectangle_3_pivot_animation\n010a004d=anim/ft_avd_tooverflow_rectangle_path_1_animation\n010a004e=anim/ft_avd_tooverflow_rectangle_path_2_animation\n010a004f=anim/ft_avd_tooverflow_rectangle_path_3_animation\n010a0050=anim/grow_fade_in\n010a0051=anim/grow_fade_in_center\n010a0052=anim/grow_fade_in_from_bottom\n010a0053=anim/ic_bluetooth_transient_animation_0\n010a0054=anim/ic_bluetooth_transient_animation_1\n010a0055=anim/ic_bluetooth_transient_animation_2\n010a0056=anim/ic_hotspot_transient_animation_0\n010a0057=anim/ic_hotspot_transient_animation_1\n010a0058=anim/ic_hotspot_transient_animation_2\n010a0059=anim/ic_hotspot_transient_animation_3\n010a005a=anim/ic_signal_wifi_transient_animation_0\n010a005b=anim/ic_signal_wifi_transient_animation_1\n010a005c=anim/ic_signal_wifi_transient_animation_2\n010a005d=anim/ic_signal_wifi_transient_animation_3\n010a005e=anim/ic_signal_wifi_transient_animation_4\n010a005f=anim/ic_signal_wifi_transient_animation_5\n010a0060=anim/ic_signal_wifi_transient_animation_6\n010a0061=anim/ic_signal_wifi_transient_animation_7\n010a0062=anim/ic_signal_wifi_transient_animation_8\n010a0063=anim/input_method_enter\n010a0064=anim/input_method_exit\n010a0065=anim/input_method_extract_enter\n010a0066=anim/input_method_extract_exit\n010a0067=anim/input_method_fancy_enter\n010a0068=anim/input_method_fancy_exit\n010a0069=anim/launch_task_behind_source\n010a006a=anim/launch_task_behind_target\n010a006b=anim/lock_screen_behind_enter\n010a006c=anim/lock_screen_behind_enter_fade_in\n010a006d=anim/lock_screen_behind_enter_subtle\n010a006e=anim/lock_screen_behind_enter_wallpaper\n010a006f=anim/lock_screen_enter\n010a0070=anim/lock_screen_exit\n010a0071=anim/lock_screen_wallpaper_exit\n010a0072=anim/options_panel_enter\n010a0073=anim/options_panel_exit\n010a0074=anim/popup_enter_material\n010a0075=anim/popup_exit_material\n010a0076=anim/progress_indeterminate_horizontal_rect1\n010a0077=anim/progress_indeterminate_horizontal_rect2\n010a0078=anim/progress_indeterminate_material\n010a0079=anim/progress_indeterminate_rotation_material\n010a007a=anim/push_down_in\n010a007b=anim/push_down_in_no_alpha\n010a007c=anim/push_down_out\n010a007d=anim/push_down_out_no_alpha\n010a007e=anim/push_up_in\n010a007f=anim/push_up_out\n010a0080=anim/recent_enter\n010a0081=anim/recent_exit\n010a0082=anim/recents_fade_in\n010a0083=anim/recents_fade_out\n010a0084=anim/resolver_close_anim\n010a0085=anim/resolver_launch_anim\n010a0086=anim/rotation_animation_enter\n010a0087=anim/rotation_animation_jump_exit\n010a0088=anim/rotation_animation_xfade_exit\n010a0089=anim/screen_rotate_0_enter\n010a008a=anim/screen_rotate_0_exit\n010a008b=anim/screen_rotate_180_enter\n010a008c=anim/screen_rotate_180_exit\n010a008d=anim/screen_rotate_180_frame\n010a008e=anim/screen_rotate_alpha\n010a008f=anim/screen_rotate_finish_enter\n010a0090=anim/screen_rotate_finish_exit\n010a0091=anim/screen_rotate_finish_frame\n010a0092=anim/screen_rotate_minus_90_enter\n010a0093=anim/screen_rotate_minus_90_exit\n010a0094=anim/screen_rotate_plus_90_enter\n010a0095=anim/screen_rotate_plus_90_exit\n010a0096=anim/screen_rotate_start_enter\n010a0097=anim/screen_rotate_start_exit\n010a0098=anim/screen_rotate_start_frame\n010a0099=anim/screen_user_enter\n010a009a=anim/screen_user_exit\n010a009b=anim/search_bar_enter\n010a009c=anim/search_bar_exit\n010a009d=anim/seekbar_thumb_pressed_to_unpressed_thumb_animation\n010a009e=anim/seekbar_thumb_unpressed_to_pressed_thumb_0_animation\n010a009f=anim/shrink_fade_out\n010a00a0=anim/shrink_fade_out_center\n010a00a1=anim/shrink_fade_out_from_bottom\n010a00a2=anim/slide_in_child_bottom\n010a00a3=anim/slide_in_enter_micro\n010a00a4=anim/slide_in_exit_micro\n010a00a5=anim/slide_in_right\n010a00a6=anim/slide_in_up\n010a00a7=anim/slide_out_down\n010a00a8=anim/slide_out_left\n010a00a9=anim/slide_out_micro\n010a00aa=anim/slow_fade_in\n010a00ab=anim/submenu_enter\n010a00ac=anim/submenu_exit\n010a00ad=anim/swipe_window_enter\n010a00ae=anim/swipe_window_exit\n010a00af=anim/task_close_enter\n010a00b0=anim/task_close_exit\n010a00b1=anim/task_open_enter\n010a00b2=anim/task_open_enter_cross_profile_apps\n010a00b3=anim/task_open_exit\n010a00b4=anim/toast_enter\n010a00b5=anim/toast_exit\n010a00b6=anim/tooltip_enter\n010a00b7=anim/tooltip_exit\n010a00b8=anim/translucent_enter\n010a00b9=anim/translucent_exit\n010a00ba=anim/voice_activity_close_enter\n010a00bb=anim/voice_activity_close_exit\n010a00bc=anim/voice_activity_open_enter\n010a00bd=anim/voice_activity_open_exit\n010a00be=anim/voice_layer_enter\n010a00bf=anim/voice_layer_exit\n010a00c0=anim/wallpaper_close_enter\n010a00c1=anim/wallpaper_close_exit\n010a00c2=anim/wallpaper_enter\n010a00c3=anim/wallpaper_exit\n010a00c4=anim/wallpaper_intra_close_enter\n010a00c5=anim/wallpaper_intra_close_exit\n010a00c6=anim/wallpaper_intra_open_enter\n010a00c7=anim/wallpaper_intra_open_exit\n010a00c8=anim/wallpaper_open_enter\n010a00c9=anim/wallpaper_open_exit\n010a00ca=anim/window_move_from_decor\n010a00cb=anim/ft_avd_tooverflow_rectangle_1_animation\n010a00cc=anim/ft_avd_tooverflow_rectangle_1_pivot_animation\n010a00cd=anim/ft_avd_tooverflow_rectangle_2_animation\n010a00ce=anim/ft_avd_tooverflow_rectangle_2_pivot_animation\n010a00cf=anim/ft_avd_tooverflow_rectangle_3_animation\n010a00d0=anim/ft_avd_tooverflow_rectangle_3_pivot_animation\n010a00d1=anim/ft_avd_tooverflow_rectangle_path_1_animation\n010a00d2=anim/ft_avd_tooverflow_rectangle_path_2_animation\n010a00d3=anim/ft_avd_tooverflow_rectangle_path_3_animation\n010a00d4=anim/grow_fade_in\n010a00d5=anim/grow_fade_in_center\n010a00d6=anim/grow_fade_in_from_bottom\n010a00d7=anim/ic_bluetooth_transient_animation_0\n010a00d8=anim/ic_bluetooth_transient_animation_1\n010a00d9=anim/ic_bluetooth_transient_animation_2\n010a00da=anim/ic_hotspot_transient_animation_0\n010a00db=anim/ic_hotspot_transient_animation_1\n010a00dc=anim/ic_hotspot_transient_animation_2\n010a00dd=anim/ic_hotspot_transient_animation_3\n010a00de=anim/ic_signal_wifi_transient_animation_0\n010a00df=anim/ic_signal_wifi_transient_animation_1\n010a00e0=anim/ic_signal_wifi_transient_animation_2\n010a00e1=anim/ic_signal_wifi_transient_animation_3\n010a00e2=anim/ic_signal_wifi_transient_animation_4\n010a00e3=anim/ic_signal_wifi_transient_animation_5\n010a00e4=anim/ic_signal_wifi_transient_animation_6\n010a00e5=anim/ic_signal_wifi_transient_animation_7\n010a00e6=anim/ic_signal_wifi_transient_animation_8\n010a00e7=anim/input_method_enter\n010a00e8=anim/input_method_exit\n010a00e9=anim/input_method_extract_enter\n010a00ea=anim/input_method_extract_exit\n010a00eb=anim/input_method_fancy_enter\n010a00ec=anim/input_method_fancy_exit\n010a00ed=anim/launch_task_behind_source\n010a00ee=anim/launch_task_behind_target\n010a00ef=anim/lock_in\n010a00f0=anim/lock_lock\n010a00f1=anim/lock_scanning\n010a00f2=anim/lock_screen_behind_enter\n010a00f3=anim/lock_screen_behind_enter_fade_in\n010a00f4=anim/lock_screen_behind_enter_wallpaper\n010a00f5=anim/lock_screen_enter\n010a00f6=anim/lock_screen_exit\n010a00f7=anim/lock_screen_wallpaper_exit\n010a00f8=anim/lock_to_error\n010a00f9=anim/lock_unlock\n010a00fa=anim/options_panel_enter\n010a00fb=anim/options_panel_exit\n010a00fc=anim/popup_enter_material\n010a00fd=anim/popup_exit_material\n010a00fe=anim/progress_indeterminate_horizontal_rect1\n010a00ff=anim/progress_indeterminate_horizontal_rect2\n010a0100=anim/progress_indeterminate_material\n010a0101=anim/progress_indeterminate_rotation_material\n010a0102=anim/push_down_in\n010a0103=anim/push_down_in_no_alpha\n010a0104=anim/push_down_out\n010a0105=anim/push_down_out_no_alpha\n010a0106=anim/push_up_in\n010a0107=anim/push_up_out\n010a0108=anim/recent_enter\n010a0109=anim/recent_exit\n010a010a=anim/recents_fade_in\n010a010b=anim/recents_fade_out\n010a010c=anim/resolver_close_anim\n010a010d=anim/resolver_launch_anim\n010a010e=anim/rotation_animation_enter\n010a010f=anim/rotation_animation_jump_exit\n010a0110=anim/rotation_animation_xfade_exit\n010a0111=anim/screen_rotate_0_enter\n010a0112=anim/screen_rotate_0_exit\n010a0113=anim/screen_rotate_0_frame\n010a0114=anim/screen_rotate_180_enter\n010a0115=anim/screen_rotate_180_exit\n010a0116=anim/screen_rotate_180_frame\n010a0117=anim/screen_rotate_finish_enter\n010a0118=anim/screen_rotate_finish_exit\n010a0119=anim/screen_rotate_finish_frame\n010a011a=anim/screen_rotate_minus_90_enter\n010a011b=anim/screen_rotate_minus_90_exit\n010a011c=anim/screen_rotate_minus_90_frame\n010a011d=anim/screen_rotate_plus_90_enter\n010a011e=anim/screen_rotate_plus_90_exit\n010a011f=anim/screen_rotate_plus_90_frame\n010a0120=anim/screen_rotate_start_enter\n010a0121=anim/screen_rotate_start_exit\n010a0122=anim/screen_rotate_start_frame\n010a0123=anim/screen_user_enter\n010a0124=anim/screen_user_exit\n010a0125=anim/search_bar_enter\n010a0126=anim/search_bar_exit\n010a0127=anim/seekbar_thumb_pressed_to_unpressed_thumb_animation\n010a0128=anim/seekbar_thumb_unpressed_to_pressed_thumb_0_animation\n010a0129=anim/shrink_fade_out\n010a012a=anim/shrink_fade_out_center\n010a012b=anim/shrink_fade_out_from_bottom\n010a012c=anim/slide_in_child_bottom\n010a012d=anim/slide_in_enter_micro\n010a012e=anim/slide_in_exit_micro\n010a012f=anim/slide_in_right\n010a0130=anim/slide_in_up\n010a0131=anim/slide_out_down\n010a0132=anim/slide_out_left\n010a0133=anim/slide_out_micro\n010a0134=anim/slow_fade_in\n010a0135=anim/submenu_enter\n010a0136=anim/submenu_exit\n010a0137=anim/swipe_window_enter\n010a0138=anim/swipe_window_exit\n010a0139=anim/task_close_enter\n010a013a=anim/task_close_exit\n010a013b=anim/task_open_enter\n010a013c=anim/task_open_enter_cross_profile_apps\n010a013d=anim/task_open_exit\n010a013e=anim/toast_enter\n010a013f=anim/toast_exit\n010a0140=anim/tooltip_enter\n010a0141=anim/tooltip_exit\n010a0142=anim/translucent_enter\n010a0143=anim/translucent_exit\n010a0144=anim/voice_activity_close_enter\n010a0145=anim/voice_activity_close_exit\n010a0146=anim/voice_activity_open_enter\n010a0147=anim/voice_activity_open_exit\n010a0148=anim/voice_layer_enter\n010a0149=anim/voice_layer_exit\n010a014a=anim/wallpaper_close_enter\n010a014b=anim/wallpaper_close_exit\n010a014c=anim/wallpaper_enter\n010a014d=anim/wallpaper_exit\n010a014e=anim/wallpaper_intra_close_enter\n010a014f=anim/wallpaper_intra_close_exit\n010a0150=anim/wallpaper_intra_open_enter\n010a0151=anim/wallpaper_intra_open_exit\n010a0152=anim/wallpaper_open_enter\n010a0153=anim/wallpaper_open_exit\n010a0154=anim/window_move_from_decor\n010b0000=animator/fade_in\n010b0001=animator/fade_out\n010b0002=animator/fragment_close_enter\n010b0003=animator/fragment_close_exit\n010b0004=animator/fragment_fade_enter\n010b0005=animator/fragment_fade_exit\n010b0006=animator/fragment_open_enter\n010b0007=animator/fragment_open_exit\n010b0008=animator/leanback_setup_fragment_close_enter\n010b0009=animator/leanback_setup_fragment_close_exit\n010b000a=animator/leanback_setup_fragment_open_enter\n010b000b=animator/leanback_setup_fragment_open_exit\n010b000c=xml/time_zones_by_country\n010c0000=interpolator/accelerate_quad\n010c0001=interpolator/decelerate_quad\n010c0002=interpolator/accelerate_cubic\n010c0003=interpolator/decelerate_cubic\n010c0004=interpolator/accelerate_quint\n010c0005=interpolator/decelerate_quint\n010c0006=interpolator/accelerate_decelerate\n010c0007=interpolator/anticipate\n010c0008=interpolator/overshoot\n010c0009=interpolator/anticipate_overshoot\n010c000a=interpolator/bounce\n010c000b=interpolator/linear\n010c000c=interpolator/cycle\n010c000d=interpolator/fast_out_slow_in\n010c000e=interpolator/linear_out_slow_in\n010c000f=interpolator/fast_out_linear_in\n010c0010=interpolator/accelerate_quart\n010c0011=interpolator/activity_close_dim\n010c0012=interpolator/aggressive_ease\n010c0013=interpolator/btn_checkbox_checked_mtrl_animation_interpolator_0\n010c0014=interpolator/btn_checkbox_checked_mtrl_animation_interpolator_1\n010c0015=interpolator/btn_checkbox_unchecked_mtrl_animation_interpolator_0\n010c0016=interpolator/btn_checkbox_unchecked_mtrl_animation_interpolator_1\n010c0017=interpolator/btn_radio_to_off_mtrl_animation_interpolator_0\n010c0018=interpolator/btn_radio_to_on_mtrl_animation_interpolator_0\n010c0019=interpolator/decelerate_quart\n010c001a=interpolator/fast_out_extra_slow_in\n010c001b=interpolator/ft_avd_toarrow_animation_interpolator_0\n010c001c=interpolator/ft_avd_toarrow_animation_interpolator_1\n010c001d=interpolator/ft_avd_toarrow_animation_interpolator_2\n010c001e=interpolator/ft_avd_toarrow_animation_interpolator_3\n010c001f=interpolator/ft_avd_toarrow_animation_interpolator_4\n010c0020=interpolator/ft_avd_toarrow_animation_interpolator_5\n010c0021=interpolator/ft_avd_toarrow_animation_interpolator_6\n010c0022=interpolator/launch_task_behind_source_scale_1\n010c0023=interpolator/launch_task_behind_source_scale_2\n010c0024=interpolator/launch_task_behind_target_ydelta\n010c0025=interpolator/launch_task_micro_alpha\n010c0026=interpolator/launch_task_micro_ydelta\n010c0027=interpolator/progress_indeterminate_horizontal_rect1_scalex\n010c0028=interpolator/progress_indeterminate_horizontal_rect1_translatex\n010c0029=interpolator/progress_indeterminate_horizontal_rect2_scalex\n010c002a=interpolator/progress_indeterminate_horizontal_rect2_translatex\n010c002b=interpolator/progress_indeterminate_rotation_interpolator\n010c002c=interpolator/screen_rotation_alpha_in\n010c002d=interpolator/screen_rotation_alpha_out\n010c002e=interpolator/transient_interpolator\n010c002f=interpolator/trim_end_interpolator\n010c0030=interpolator/trim_offset_interpolator\n010c0031=interpolator/trim_start_interpolator\n010d0000=mipmap/sym_def_app_icon\n010d0001=mipmap/sym_def_app_icon_foreground\n010d0002=mipmap/sym_def_app_icon_maskable\n010d0003=mipmap/sym_def_app_icon_maskable\n010d0004=bool/config_bypass_keyguard_if_slider_open\n010d0005=bool/config_automatic_brightness_available\n010d0006=bool/config_annoy_dianne\n010d0007=bool/config_unplugTurnsOnScreen\n010d0008=bool/config_animateScreenLights\n010d0009=bool/config_deskDockEnablesAccelerometer\n010d000a=bool/config_carDockEnablesAccelerometer\n010d000b=bool/config_batterySdCardAccessibility\n010d000c=bool/config_use_strict_phone_number_comparation\n010d000d=bool/config_disableMenuKeyInLockScreen\n010d000e=bool/config_swipeDisambiguation\n010d000f=bool/config_filterTouchEvents\n010d0010=bool/config_filterJumpyTouchEvents\n010d0011=bool/config_bluetooth_sco_off_call\n010d0012=bool/config_sip_wifi_only\n010d0013=bool/skip_restoring_network_selection\n010d0014=bool/lockscreen_isPortrait\n010e0000=integer/config_shortAnimTime\n010e0001=integer/config_mediumAnimTime\n010e0002=integer/config_longAnimTime\n010e0003=integer/status_bar_notification_info_maxnum\n010e0004=integer/autofill_max_visible_datasets\n010e0005=integer/button_pressed_animation_delay\n010e0006=integer/button_pressed_animation_duration\n010e0007=integer/config_MaxConcurrentDownloadsAllowed\n010e0008=integer/config_accessibilityColorMode\n010e0009=integer/config_activeTaskDurationHours\n010e000a=integer/config_activityDefaultDur\n010e000b=integer/config_activityShortDur\n010e000c=integer/config_alertDialogController\n010e000d=integer/config_allowedUnprivilegedKeepalivePerUid\n010e000e=integer/config_app_exit_info_history_list_size\n010e000f=integer/config_attentionMaximumExtension\n010e0010=integer/config_attentiveTimeout\n010e0011=integer/config_attentiveWarningDuration\n010e0012=integer/config_autoBrightnessBrighteningLightDebounce\n010e0013=integer/config_autoBrightnessDarkeningLightDebounce\n010e0014=integer/config_autoBrightnessInitialLightSensorRate\n010e0015=integer/config_autoBrightnessLightSensorRate\n010e0016=integer/config_autoBrightnessShortTermModelTimeout\n010e0017=integer/config_autoGroupAtCount\n010e0018=integer/config_autoPowerModeAnyMotionSensor\n010e0019=integer/config_autoPowerModeThresholdAngle\n010e001a=integer/config_bluetooth_idle_cur_ma\n010e001b=integer/config_bluetooth_max_advertisers\n010e001c=integer/config_bluetooth_max_connected_audio_devices\n010e001d=integer/config_bluetooth_max_scan_filters\n010e001e=integer/config_bluetooth_operating_voltage_mv\n010e001f=integer/config_bluetooth_rx_cur_ma\n010e0020=integer/config_bluetooth_tx_cur_ma\n010e0021=integer/config_brightness_ramp_rate_fast\n010e0022=integer/config_brightness_ramp_rate_slow\n010e0023=integer/config_burnInProtectionMaxHorizontalOffset\n010e0024=integer/config_burnInProtectionMaxRadius\n010e0025=integer/config_burnInProtectionMaxVerticalOffset\n010e0026=integer/config_burnInProtectionMinHorizontalOffset\n010e0027=integer/config_burnInProtectionMinVerticalOffset\n010e0028=integer/config_cameraLaunchGestureSensorType\n010e0029=integer/config_cameraLiftTriggerSensorType\n010e002a=integer/config_carDockKeepsScreenOn\n010e002b=integer/config_carDockRotation\n010e002c=integer/config_cdma_3waycall_flash_delay\n010e002d=integer/config_criticalBatteryWarningLevel\n010e002e=integer/config_cursorWindowSize\n010e002f=integer/config_datause_notification_type\n010e0030=integer/config_datause_polling_period_sec\n010e0031=integer/config_datause_threshold_bytes\n010e0032=integer/config_datause_throttle_kbitsps\n010e0033=integer/config_debugSystemServerPssThresholdBytes\n010e0034=integer/config_defaultDisplayDefaultColorMode\n010e0035=integer/config_defaultHapticFeedbackIntensity\n010e0036=integer/config_defaultNightDisplayAutoMode\n010e0037=integer/config_defaultNightDisplayCustomEndTime\n010e0038=integer/config_defaultNightDisplayCustomStartTime\n010e0039=integer/config_defaultNightMode\n010e003a=integer/config_defaultNotificationLedOff\n010e003b=integer/config_defaultNotificationLedOn\n010e003c=integer/config_defaultNotificationVibrationIntensity\n010e003d=integer/config_defaultPeakRefreshRate\n010e003e=integer/config_defaultPictureInPictureGravity\n010e003f=integer/config_defaultRefreshRate\n010e0040=integer/config_defaultRefreshRateInZone\n010e0041=integer/config_defaultRingVibrationIntensity\n010e0042=integer/config_defaultUiModeType\n010e0043=integer/config_defaultVibrationAmplitude\n010e0044=integer/config_deskDockKeepsScreenOn\n010e0045=integer/config_deskDockRotation\n010e0046=integer/config_displayWhiteBalanceBrightnessFilterHorizon\n010e0047=integer/config_displayWhiteBalanceBrightnessSensorRate\n010e0048=integer/config_displayWhiteBalanceColorTemperatureDefault\n010e0049=integer/config_displayWhiteBalanceColorTemperatureFilterHorizon\n010e004a=integer/config_displayWhiteBalanceColorTemperatureMax\n010e004b=integer/config_displayWhiteBalanceColorTemperatureMin\n010e004c=integer/config_displayWhiteBalanceColorTemperatureSensorRate\n010e004d=integer/config_displayWhiteBalanceDecreaseDebounce\n010e004e=integer/config_displayWhiteBalanceIncreaseDebounce\n010e004f=integer/config_dockedStackDividerSnapMode\n010e0050=integer/config_doublePressOnPowerBehavior\n010e0051=integer/config_doubleTapOnHomeBehavior\n010e0052=integer/config_downloadDataDirLowSpaceThreshold\n010e0053=integer/config_downloadDataDirSize\n010e0054=integer/config_dozeWakeLockScreenDebounce\n010e0055=integer/config_drawLockTimeoutMillis\n010e0056=integer/config_dreamsBatteryLevelDrainCutoff\n010e0057=integer/config_dreamsBatteryLevelMinimumWhenNotPowered\n010e0058=integer/config_dreamsBatteryLevelMinimumWhenPowered\n010e0059=integer/config_dropboxLowPriorityBroadcastRateLimitPeriod\n010e005a=integer/config_dynamicPowerSavingsDefaultDisableThreshold\n010e005b=integer/config_extraFreeKbytesAbsolute\n010e005c=integer/config_extraFreeKbytesAdjust\n010e005d=integer/config_faceMaxTemplatesPerUser\n010e005e=integer/config_fingerprintMaxTemplatesPerUser\n010e005f=integer/config_globalActionsKeyTimeout\n010e0060=integer/config_immersive_mode_confirmation_panic\n010e0061=integer/config_jobSchedulerIdleWindowSlop\n010e0062=integer/config_jobSchedulerInactivityIdleThreshold\n010e0063=integer/config_keepPreloadsMinDays\n010e0064=integer/config_lidKeyboardAccessibility\n010e0065=integer/config_lidNavigationAccessibility\n010e0066=integer/config_lidOpenRotation\n010e0067=integer/config_lightSensorWarmupTime\n010e0068=integer/config_lockSoundVolumeDb\n010e0069=integer/config_longPressOnBackBehavior\n010e006a=integer/config_longPressOnHomeBehavior\n010e006b=integer/config_longPressOnPowerBehavior\n010e006c=integer/config_lowBatteryAutoTriggerDefaultLevel\n010e006d=integer/config_lowBatteryCloseWarningBump\n010e006e=integer/config_lowBatteryWarningLevel\n010e006f=integer/config_lowMemoryKillerMinFreeKbytesAbsolute\n010e0070=integer/config_lowMemoryKillerMinFreeKbytesAdjust\n010e0071=integer/config_maxNumVisibleRecentTasks\n010e0072=integer/config_maxNumVisibleRecentTasks_grid\n010e0073=integer/config_maxNumVisibleRecentTasks_lowRam\n010e0074=integer/config_maxResolverActivityColumns\n010e0075=integer/config_maxShortcutTargetsPerApp\n010e0076=integer/config_maxUiWidth\n010e0077=integer/config_max_pan_devices\n010e0078=integer/config_maximumScreenDimDuration\n010e0079=integer/config_mdc_initial_max_retry\n010e007a=integer/config_minNumVisibleRecentTasks\n010e007b=integer/config_minNumVisibleRecentTasks_grid\n010e007c=integer/config_minNumVisibleRecentTasks_lowRam\n010e007d=integer/config_minimumScreenOffTimeout\n010e007e=integer/config_mobile_hotspot_provision_check_period\n010e007f=integer/config_mobile_mtu\n010e0080=integer/config_multiuserMaxRunningUsers\n010e0081=integer/config_multiuserMaximumUsers\n010e0082=integer/config_navBarInteractionMode\n010e0083=integer/config_navBarOpacityMode\n010e0084=integer/config_networkAvoidBadWifi\n010e0085=integer/config_networkDefaultDailyMultipathQuotaBytes\n010e0086=integer/config_networkMeteredMultipathPreference\n010e0087=integer/config_networkNotifySwitchType\n010e0088=integer/config_networkPolicyDefaultWarning\n010e0089=integer/config_networkTransitionTimeout\n010e008a=integer/config_networkWakeupPacketMark\n010e008b=integer/config_networkWakeupPacketMask\n010e008c=integer/config_nightDisplayColorTemperatureDefault\n010e008d=integer/config_nightDisplayColorTemperatureMax\n010e008e=integer/config_nightDisplayColorTemperatureMin\n010e008f=integer/config_notificationServiceArchiveSize\n010e0090=integer/config_notificationStripRemoteViewSizeBytes\n010e0091=integer/config_notificationWarnRemoteViewSizeBytes\n010e0092=integer/config_notificationsBatteryFullARGB\n010e0093=integer/config_notificationsBatteryLedOff\n010e0094=integer/config_notificationsBatteryLedOn\n010e0095=integer/config_notificationsBatteryLowARGB\n010e0096=integer/config_notificationsBatteryMediumARGB\n010e0097=integer/config_ntpPollingInterval\n010e0098=integer/config_ntpPollingIntervalShorter\n010e0099=integer/config_ntpRetry\n010e009a=integer/config_ntpTimeout\n010e009b=integer/config_num_physical_slots\n010e009c=integer/config_overrideHasPermanentMenuKey\n010e009d=integer/config_pdp_reject_retry_delay_ms\n010e009e=integer/config_phonenumber_compare_min_match\n010e009f=integer/config_previousVibrationsDumpLimit\n010e00a0=integer/config_radioScanningTimeout\n010e00a1=integer/config_reservedPrivilegedKeepaliveSlots\n010e00a2=integer/config_safe_media_volume_index\n010e00a3=integer/config_safe_media_volume_usb_mB\n010e00a4=integer/config_screenBrightnessDark\n010e00a5=integer/config_screenBrightnessDim\n010e00a6=integer/config_screenBrightnessDoze\n010e00a7=integer/config_screenBrightnessForVrSettingDefault\n010e00a8=integer/config_screenBrightnessForVrSettingMaximum\n010e00a9=integer/config_screenBrightnessForVrSettingMinimum\n010e00aa=integer/config_screenBrightnessSettingDefault\n010e00ab=integer/config_screenBrightnessSettingMaximum\n010e00ac=integer/config_screenBrightnessSettingMinimum\n010e00ad=integer/config_screen_magnification_multi_tap_adjustment\n010e00ae=integer/config_screen_rotation_color_transition\n010e00af=integer/config_screen_rotation_fade_in\n010e00b0=integer/config_screen_rotation_fade_in_delay\n010e00b1=integer/config_screen_rotation_fade_out\n010e00b2=integer/config_screen_rotation_total_180\n010e00b3=integer/config_screen_rotation_total_90\n010e00b4=integer/config_screenshotChordKeyTimeout\n010e00b5=integer/config_shortPressOnPowerBehavior\n010e00b6=integer/config_shortPressOnSleepBehavior\n010e00b7=integer/config_shutdownBatteryTemperature\n010e00b8=integer/config_soundEffectVolumeDb\n010e00b9=integer/config_stableDeviceDisplayHeight\n010e00ba=integer/config_stableDeviceDisplayWidth\n010e00bb=integer/config_storageManagerDaystoRetainDefault\n010e00bc=integer/config_timeZoneRulesCheckRetryCount\n010e00bd=integer/config_timeZoneRulesCheckTimeMillisAllowed\n010e00be=integer/config_toastDefaultGravity\n010e00bf=integer/config_tooltipAnimTime\n010e00c0=integer/config_triplePressOnPowerBehavior\n010e00c1=integer/config_undockedHdmiRotation\n010e00c2=integer/config_userTypePackageWhitelistMode\n010e00c3=integer/config_valid_wappush_index\n010e00c4=integer/config_veryLongPressOnPowerBehavior\n010e00c5=integer/config_veryLongPressTimeout\n010e00c6=integer/config_virtualKeyQuietTimeMillis\n010e00c7=integer/config_volte_replacement_rat\n010e00c8=integer/config_wakeUpDelayDoze\n010e00c9=integer/config_windowOutsetBottom\n010e00ca=integer/config_zen_repeat_callers_threshold\n010e00cb=integer/date_picker_header_max_lines_material\n010e00cc=integer/date_picker_mode\n010e00cd=integer/date_picker_mode_material\n010e00ce=integer/db_connection_pool_size\n010e00cf=integer/db_default_idle_connection_timeout\n010e00d0=integer/db_journal_size_limit\n010e00d1=integer/db_wal_autocheckpoint\n010e00d2=integer/db_wal_truncate_size\n010e00d3=integer/default_data_warning_level_mb\n010e00d4=integer/default_reserved_data_coding_scheme\n010e00d5=integer/disabled_alpha_animation_duration\n010e00d6=integer/dock_enter_exit_duration\n010e00d7=integer/kg_carousel_angle\n010e00d8=integer/kg_glowpad_rotation_offset\n010e00d9=integer/kg_security_flipper_weight\n010e00da=integer/kg_selector_gravity\n010e00db=integer/kg_widget_region_weight\n010e00dc=integer/leanback_setup_alpha_activity_in_bkg_delay\n010e00dd=integer/leanback_setup_alpha_activity_in_bkg_duration\n010e00de=integer/leanback_setup_alpha_activity_out_bkg_delay\n010e00df=integer/leanback_setup_alpha_activity_out_bkg_duration\n010e00e0=integer/leanback_setup_alpha_backward_in_content_delay\n010e00e1=integer/leanback_setup_alpha_backward_in_content_duration\n010e00e2=integer/leanback_setup_alpha_backward_out_content_delay\n010e00e3=integer/leanback_setup_alpha_backward_out_content_duration\n010e00e4=integer/leanback_setup_alpha_forward_in_content_delay\n010e00e5=integer/leanback_setup_alpha_forward_in_content_duration\n010e00e6=integer/leanback_setup_alpha_forward_out_content_delay\n010e00e7=integer/leanback_setup_alpha_forward_out_content_duration\n010e00e8=integer/leanback_setup_base_animation_duration\n010e00e9=integer/leanback_setup_translation_backward_out_content_delay\n010e00ea=integer/leanback_setup_translation_backward_out_content_duration\n010e00eb=integer/leanback_setup_translation_content_cliff_v4\n010e00ec=integer/leanback_setup_translation_content_resting_point_v4\n010e00ed=integer/leanback_setup_translation_forward_in_content_delay\n010e00ee=integer/leanback_setup_translation_forward_in_content_duration\n010e00ef=integer/preference_fragment_scrollbarStyle\n010e00f0=integer/preference_screen_header_scrollbarStyle\n010e00f1=integer/preferences_left_pane_weight\n010e00f2=integer/preferences_right_pane_weight\n010e00f3=integer/thumbnail_width_tv\n010e00f4=integer/time_picker_mode\n010e00f5=integer/time_picker_mode_material\n010e00f6=integer/timepicker_title_visibility\n010e00f7=integer/default_data_warning_level_mb\n010e00f8=integer/disabled_alpha_animation_duration\n010e00f9=integer/dock_enter_exit_duration\n010e00fa=integer/kg_carousel_angle\n010e00fb=integer/kg_glowpad_rotation_offset\n010e00fc=integer/kg_security_flipper_weight\n010e00fd=integer/kg_selector_gravity\n010e00fe=integer/kg_widget_region_weight\n010e00ff=integer/leanback_setup_alpha_activity_in_bkg_delay\n010e0100=integer/leanback_setup_alpha_activity_in_bkg_duration\n010e0101=integer/leanback_setup_alpha_activity_out_bkg_delay\n010e0102=integer/leanback_setup_alpha_activity_out_bkg_duration\n010e0103=integer/leanback_setup_alpha_backward_in_content_delay\n010e0104=integer/leanback_setup_alpha_backward_in_content_duration\n010e0105=integer/leanback_setup_alpha_backward_out_content_delay\n010e0106=integer/leanback_setup_alpha_backward_out_content_duration\n010e0107=integer/leanback_setup_alpha_forward_in_content_delay\n010e0108=integer/leanback_setup_alpha_forward_in_content_duration\n010e0109=integer/leanback_setup_alpha_forward_out_content_delay\n010e010a=integer/leanback_setup_alpha_forward_out_content_duration\n010e010b=integer/leanback_setup_base_animation_duration\n010e010c=integer/leanback_setup_translation_backward_out_content_delay\n010e010d=integer/leanback_setup_translation_backward_out_content_duration\n010e010e=integer/leanback_setup_translation_content_cliff_v4\n010e010f=integer/leanback_setup_translation_content_resting_point_v4\n010e0110=integer/leanback_setup_translation_forward_in_content_delay\n010e0111=integer/leanback_setup_translation_forward_in_content_duration\n010e0112=integer/preference_fragment_scrollbarStyle\n010e0113=integer/preference_screen_header_scrollbarStyle\n010e0114=integer/preferences_left_pane_weight\n010e0115=integer/preferences_right_pane_weight\n010e0116=integer/thumbnail_width_tv\n010e0117=integer/time_picker_mode\n010e0118=integer/time_picker_mode_material\n010e0119=integer/timepicker_title_visibility\n010f0000=transition/no_transition\n010f0001=transition/move\n010f0002=transition/fade\n010f0003=transition/explode\n010f0004=transition/slide_bottom\n010f0005=transition/slide_top\n010f0006=transition/slide_right\n010f0007=transition/slide_left\n010f0008=transition/popup_window_enter\n010f0009=transition/popup_window_exit\n010f000a=xml/password_kbd_qwerty_shifted\n010f000b=xml/password_kbd_symbols\n010f000c=xml/password_kbd_symbols_shift\n010f000d=xml/power_profile\n010f000e=xml/preferred_time_zones\n010f000f=xml/sms_short_codes\n010f0010=xml/storage_list\n010f0011=xml/time_zones_by_country\n010f0012=plurals/wifi_available_detailed\n01100000=raw/loaderror\n01100001=raw/nodomain\n01100002=raw/color_fade_frag\n01100003=raw/color_fade_vert\n01100004=raw/fallback_categories\n01100005=raw/fallbackring\n01100006=raw/incognito_mode_start_page\n01110000=bool/config_sendPackageName\n01110001=bool/config_showDefaultAssistant\n01110002=bool/config_showDefaultEmergency\n01110003=bool/config_showDefaultHome\n01110004=bool/config_perDisplayFocusEnabled\n01110005=bool/ImsConnectedDefaultValue\n01110006=bool/action_bar_embed_tabs\n01110007=bool/action_bar_expanded_action_views_exclusive\n01110008=bool/config_LTE_eri_for_network_name\n01110009=bool/config_actionMenuItemAllCaps\n0111000a=bool/config_adaptive_sleep_available\n0111000b=bool/config_allow3rdPartyAppOnInternal\n0111000c=bool/config_allowAllRotations\n0111000d=bool/config_allowAnimationsInLowPowerMode\n0111000e=bool/config_allowAutoBrightnessWhileDozing\n0111000f=bool/config_allowDisablingAssistDisclosure\n01110010=bool/config_allowEscrowTokenForTrustAgent\n01110011=bool/config_allowPriorityVibrationsInLowPowerMode\n01110012=bool/config_allowSeamlessRotationDespiteNavBarMoving\n01110013=bool/config_allowStartActivityForLongPressOnPowerInSetup\n01110014=bool/config_allowTheaterModeWakeFromCameraLens\n01110015=bool/config_allowTheaterModeWakeFromDock\n01110016=bool/config_allowTheaterModeWakeFromGesture\n01110017=bool/config_allowTheaterModeWakeFromKey\n01110018=bool/config_allowTheaterModeWakeFromLidSwitch\n01110019=bool/config_allowTheaterModeWakeFromMotion\n0111001a=bool/config_allowTheaterModeWakeFromMotionWhenNotDreaming\n0111001b=bool/config_allowTheaterModeWakeFromPowerKey\n0111001c=bool/config_allowTheaterModeWakeFromUnplug\n0111001d=bool/config_allowTheaterModeWakeFromWindowLayout\n0111001e=bool/config_allow_ussd_over_ims\n0111001f=bool/config_alwaysUseCdmaRssi\n01110020=bool/config_animateScreenLights\n01110021=bool/config_annoy_dianne\n01110022=bool/config_apfDrop802_3Frames\n01110023=bool/config_assistantOnTopOfDream\n01110024=bool/config_autoBrightnessResetAmbientLuxAfterWarmUp\n01110025=bool/config_autoPowerModePreferWristTilt\n01110026=bool/config_autoPowerModePrefetchLocation\n01110027=bool/config_autoPowerModeUseMotionSensor\n01110028=bool/config_auto_attach_data_on_creation\n01110029=bool/config_automatic_brightness_available\n0111002a=bool/config_automotiveHideNavBarForKeyboard\n0111002b=bool/config_avoidGfxAccel\n0111002c=bool/config_awareSettingAvailable\n0111002d=bool/config_batterySaverStickyBehaviourDisabled\n0111002e=bool/config_batterySdCardAccessibility\n0111002f=bool/config_battery_percentage_setting_available\n01110030=bool/config_batterymeterDualTone\n01110031=bool/config_bluetooth_address_validation\n01110032=bool/config_bluetooth_default_profiles\n01110033=bool/config_bluetooth_hfp_inband_ringing_support\n01110034=bool/config_bluetooth_le_peripheral_mode_supported\n01110035=bool/config_bluetooth_pan_enable_autoconnect\n01110036=bool/config_bluetooth_reload_supported_profiles_when_enabled\n01110037=bool/config_bluetooth_sco_off_call\n01110038=bool/config_bluetooth_wide_band_speech\n01110039=bool/config_bugReportHandlerEnabled\n0111003a=bool/config_built_in_sip_phone\n0111003b=bool/config_buttonTextAllCaps\n0111003c=bool/config_cameraDoubleTapPowerGestureEnabled\n0111003d=bool/config_camera_sound_forced\n0111003e=bool/config_carDockEnablesAccelerometer\n0111003f=bool/config_carrier_volte_available\n01110040=bool/config_carrier_volte_tty_supported\n01110041=bool/config_carrier_vt_available\n01110042=bool/config_carrier_wfc_ims_available\n01110043=bool/config_cbrs_supported\n01110044=bool/config_cellBroadcastAppLinks\n01110045=bool/config_checkWallpaperAtBoot\n01110046=bool/config_closeDialogWhenTouchOutside\n01110047=bool/config_customBugreport\n01110048=bool/config_customUserSwitchUi\n01110049=bool/config_debugEnableAutomaticSystemServerHeapDumps\n0111004a=bool/config_defaultInTouchMode\n0111004b=bool/config_defaultRingtonePickerEnabled\n0111004c=bool/config_defaultWindowFeatureContextMenu\n0111004d=bool/config_defaultWindowFeatureOptionsPanel\n0111004e=bool/config_deskDockEnablesAccelerometer\n0111004f=bool/config_device_respects_hold_carrier_config\n01110050=bool/config_device_volte_available\n01110051=bool/config_device_vt_available\n01110052=bool/config_device_wfc_ims_available\n01110053=bool/config_disableLockscreenByDefault\n01110054=bool/config_disableMenuKeyInLockScreen\n01110055=bool/config_disableTransitionAnimation\n01110056=bool/config_disableUsbPermissionDialogs\n01110057=bool/config_displayBlanksAfterDoze\n01110058=bool/config_displayBrightnessBucketsInDoze\n01110059=bool/config_displayWhiteBalanceAvailable\n0111005a=bool/config_displayWhiteBalanceEnabledDefault\n0111005b=bool/config_dockedStackDividerFreeSnapMode\n0111005c=bool/config_dontPreferApn\n0111005d=bool/config_dozeAfterScreenOffByDefault\n0111005e=bool/config_dozeAlwaysOnDisplayAvailable\n0111005f=bool/config_dozeAlwaysOnEnabled\n01110060=bool/config_dozePulsePickup\n01110061=bool/config_dozeSupportsAodWallpaper\n01110062=bool/config_dozeWakeLockScreenSensorAvailable\n01110063=bool/config_dreamsActivatedOnDockByDefault\n01110064=bool/config_dreamsActivatedOnSleepByDefault\n01110065=bool/config_dreamsEnabledByDefault\n01110066=bool/config_dreamsEnabledOnBattery\n01110067=bool/config_dreamsSupported\n01110068=bool/config_duplicate_port_omadm_wappush\n01110069=bool/config_eap_sim_based_auth_supported\n0111006a=bool/config_enableActivityRecognitionHardwareOverlay\n0111006b=bool/config_enableAppWidgetService\n0111006c=bool/config_enableAutoPowerModes\n0111006d=bool/config_enableBurnInProtection\n0111006e=bool/config_enableCarDockHomeLaunch\n0111006f=bool/config_enableCredentialFactoryResetProtection\n01110070=bool/config_enableFusedLocationOverlay\n01110071=bool/config_enableGeocoderOverlay\n01110072=bool/config_enableGeofenceOverlay\n01110073=bool/config_enableHapticTextHandle\n01110074=bool/config_enableLockBeforeUnlockScreen\n01110075=bool/config_enableLockScreenRotation\n01110076=bool/config_enableMultiUserUI\n01110077=bool/config_enableNetworkLocationOverlay\n01110078=bool/config_enableNewAutoSelectNetworkUI\n01110079=bool/config_enableNightMode\n0111007a=bool/config_enableScreenshotChord\n0111007b=bool/config_enableServerNotificationEffectsForAutomotive\n0111007c=bool/config_enableUpdateableTimeZoneRules\n0111007d=bool/config_enableWallpaperService\n0111007e=bool/config_enableWcgMode\n0111007f=bool/config_enableWifiDisplay\n01110080=bool/config_enable_emergency_call_while_sim_locked\n01110081=bool/config_enable_puk_unlock_screen\n01110082=bool/config_expandLockScreenUserSwitcher\n01110083=bool/config_faceAuthDismissesKeyguard\n01110084=bool/config_fillMainBuiltInDisplayCutout\n01110085=bool/config_fingerprintSupportsGestures\n01110086=bool/config_focusScrollContainersInTouchMode\n01110087=bool/config_forceShowSystemBars\n01110088=bool/config_forceSystemPackagesQueryable\n01110089=bool/config_forceWindowDrawsStatusBarBackground\n0111008a=bool/config_freeformWindowManagement\n0111008b=bool/config_goToSleepOnButtonPressTheaterMode\n0111008c=bool/config_guestUserEphemeral\n0111008d=bool/config_handleVolumeKeysInWindowManager\n0111008e=bool/config_hasPermanentDpad\n0111008f=bool/config_hasRecents\n01110090=bool/config_hearing_aid_profile_supported\n01110091=bool/config_hotswapCapable\n01110092=bool/config_inflateSignalStrength\n01110093=bool/config_intrusiveNotificationLed\n01110094=bool/config_keepRestrictedProfilesInBackground\n01110095=bool/config_keyguardUserSwitcher\n01110096=bool/config_lidControlsDisplayFold\n01110097=bool/config_lidControlsScreenLock\n01110098=bool/config_lidControlsSleep\n01110099=bool/config_localDisplaysMirrorContent\n0111009a=bool/config_lockDayNightMode\n0111009b=bool/config_lockUiMode\n0111009c=bool/config_mainBuiltInDisplayIsRound\n0111009d=bool/config_maskMainBuiltInDisplayCutout\n0111009e=bool/config_mms_content_disposition_support\n0111009f=bool/config_mobile_data_capable\n011100a0=bool/config_multiuserDelayUserDataLocking\n011100a1=bool/config_navBarAlwaysShowOnSideEdgeGesture\n011100a2=bool/config_navBarCanMove\n011100a3=bool/config_navBarNeedsScrim\n011100a4=bool/config_navBarTapThrough\n011100a5=bool/config_networkSamplingWakesDevice\n011100a6=bool/config_nightDisplayAvailable\n011100a7=bool/config_noHomeScreen\n011100a8=bool/config_notificationBadging\n011100a9=bool/config_notificationHeaderClickableForExpand\n011100aa=bool/config_overrideRemoteViewsActivityTransition\n011100ab=bool/config_pdp_reject_enable_retry\n011100ac=bool/config_permissionsIndividuallyControlled\n011100ad=bool/config_pinnerAssistantApp\n011100ae=bool/config_pinnerCameraApp\n011100af=bool/config_pinnerHomeApp\n011100b0=bool/config_powerDecoupleAutoSuspendModeFromDisplay\n011100b1=bool/config_powerDecoupleInteractiveModeFromDisplay\n011100b2=bool/config_preferenceFragmentClipToPadding\n011100b3=bool/config_quickSettingsSupported\n011100b4=bool/config_requireCallCapableAccountForHandle\n011100b5=bool/config_requireRadioPowerOffOnSimRefreshReset\n011100b6=bool/config_restartRadioAfterProvisioning\n011100b7=bool/config_restart_radio_on_pdp_fail_regular_deactivation\n011100b8=bool/config_reverseDefaultRotation\n011100b9=bool/config_safe_media_disable_on_volume_up\n011100ba=bool/config_safe_media_volume_enabled\n011100bb=bool/config_sendAudioBecomingNoisy\n011100bc=bool/config_setColorTransformAccelerated\n011100bd=bool/config_setColorTransformAcceleratedPerLayer\n011100be=bool/config_sf_limitedAlpha\n011100bf=bool/config_sf_slowBlur\n011100c0=bool/config_showAreaUpdateInfoSettings\n011100c1=bool/config_showBuiltinWirelessChargingAnim\n011100c2=bool/config_showGesturalNavigationHints\n011100c3=bool/config_showMenuShortcutsWhenKeyboardPresent\n011100c4=bool/config_showNavigationBar\n011100c5=bool/config_showSysuiShutdown\n011100c6=bool/config_showUserSwitcherByDefault\n011100c7=bool/config_silenceSensorAvailable\n011100c8=bool/config_single_volume\n011100c9=bool/config_sip_wifi_only\n011100ca=bool/config_skipScreenOnBrightnessRamp\n011100cb=bool/config_skipSensorAvailable\n011100cc=bool/config_smart_battery_available\n011100cd=bool/config_sms_capable\n011100ce=bool/config_sms_decode_gsm_8bit_data\n011100cf=bool/config_sms_force_7bit_encoding\n011100d0=bool/config_sms_utf8_support\n011100d1=bool/config_speed_up_audio_on_mt_calls\n011100d2=bool/config_stkNoAlphaUsrCnf\n011100d3=bool/config_strongAuthRequiredOnBoot\n011100d4=bool/config_supportAudioSourceUnprocessed\n011100d5=bool/config_supportAutoRotation\n011100d6=bool/config_supportBluetoothPersistedState\n011100d7=bool/config_supportDoubleTapWake\n011100d8=bool/config_supportLongPressPowerWhenNonInteractive\n011100d9=bool/config_supportMicNearUltrasound\n011100da=bool/config_supportPreRebootSecurityLogs\n011100db=bool/config_supportSpeakerNearUltrasound\n011100dc=bool/config_supportSystemNavigationKeys\n011100dd=bool/config_supportsInsecureLockScreen\n011100de=bool/config_supportsMultiDisplay\n011100df=bool/config_supportsMultiWindow\n011100e0=bool/config_supportsRoundedCornersOnWindows\n011100e1=bool/config_supportsSplitScreenMultiWindow\n011100e2=bool/config_supportsSystemDecorsOnSecondaryDisplays\n011100e3=bool/config_suspendWhenScreenOffDueToProximity\n011100e4=bool/config_sustainedPerformanceModeSupported\n011100e5=bool/config_swipeDisambiguation\n011100e6=bool/config_swipe_up_gesture_setting_available\n011100e7=bool/config_switch_phone_on_voice_reg_state_change\n011100e8=bool/config_syncstorageengine_masterSyncAutomatically\n011100e9=bool/config_tether_upstream_automatic\n011100ea=bool/config_timeZoneRulesUpdateTrackingEnabled\n011100eb=bool/config_tintNotificationActionButtons\n011100ec=bool/config_ui_enableFadingMarquee\n011100ed=bool/config_unplugTurnsOnScreen\n011100ee=bool/config_usbChargingMessage\n011100ef=bool/config_use16BitTaskSnapshotPixelFormat\n011100f0=bool/config_useAssistantVolume\n011100f1=bool/config_useAttentionLight\n011100f2=bool/config_useDefaultFocusHighlight\n011100f3=bool/config_useDevInputEventForAudioJack\n011100f4=bool/config_useFixedVolume\n011100f5=bool/config_useRoundIcon\n011100f6=bool/config_useSmsAppService\n011100f7=bool/config_useSystemProvidedLauncherForSecondary\n011100f8=bool/config_useVideoPauseWorkaround\n011100f9=bool/config_useVolumeKeySounds\n011100fa=bool/config_useWebViewPacProcessor\n011100fb=bool/config_use_sim_language_file\n011100fc=bool/config_use_strict_phone_number_comparation\n011100fd=bool/config_use_strict_phone_number_comparation_for_kazakhstan\n011100fe=bool/config_use_strict_phone_number_comparation_for_russia\n011100ff=bool/config_use_voip_mode_for_ims\n01110100=bool/config_user_notification_of_restrictied_mobile_access\n01110101=bool/config_voice_capable\n01110102=bool/config_volumeHushGestureEnabled\n01110103=bool/config_wakeOnAssistKeyPress\n01110104=bool/config_wakeOnBackKeyPress\n01110105=bool/config_wakeOnDpadKeyPress\n01110106=bool/config_wifiDisplaySupportsProtectedBuffers\n01110107=bool/config_wimaxEnabled\n01110108=bool/config_windowActionBarSupported\n01110109=bool/config_windowEnableCircularEmulatorDisplayOverlay\n0111010a=bool/config_windowIsRound\n0111010b=bool/config_windowNoTitleDefault\n0111010c=bool/config_windowOverscanByDefault\n0111010d=bool/config_windowShowCircularMask\n0111010e=bool/config_windowSwipeToDismiss\n0111010f=bool/config_wirelessConsentRequired\n01110110=bool/config_zramWriteback\n01110111=bool/editable_voicemailnumber\n01110112=bool/imsServiceAllowTurnOff\n01110113=bool/kg_center_small_widgets_vertically\n01110114=bool/kg_enable_camera_default_widget\n01110115=bool/kg_share_status_area\n01110116=bool/kg_sim_puk_account_full_screen\n01110117=bool/kg_top_align_page_shrink_on_bouncer_visible\n01110118=bool/lockscreen_isPortrait\n01110119=bool/preferences_prefer_dual_pane\n0111011a=bool/reset_geo_fencing_check_after_boot_or_apm\n0111011b=bool/resolver_landscape_phone\n0111011c=bool/show_ongoing_ime_switcher\n0111011d=bool/skipHoldBeforeMerge\n0111011e=bool/skip_restoring_network_selection\n0111011f=bool/split_action_bar_is_narrow\n01110120=bool/use_lock_pattern_drawable\n01110121=bool/target_honeycomb_needs_options_menu\n01110122=bool/use_lock_pattern_drawable\n01120000=^attr-private/__removed0\n01120001=^attr-private/__removed1\n01120002=^attr-private/__removed2\n01120003=^attr-private/__removed3\n01120004=^attr-private/__removed4\n01120005=^attr-private/__removed5\n01120006=^attr-private/__removed6\n01120007=^attr-private/accessibilityFocusedDrawable\n01120008=^attr-private/actionModePopupWindowStyle\n01120009=^attr-private/activityChooserViewStyle\n0112000a=^attr-private/activityOpenRemoteViewsEnterAnimation\n0112000b=^attr-private/adjustable\n0112000c=^attr-private/alertDialogButtonGroupStyle\n0112000d=^attr-private/alertDialogCenterButtons\n0112000e=^attr-private/allowAutoRevokePermissionsExemption\n0112000f=^attr-private/allowMassStorage\n01120010=^attr-private/allowStacking\n01120011=^attr-private/alwaysFocusable\n01120012=^attr-private/aspect\n01120013=^attr-private/autofillDatasetPickerMaxHeight\n01120014=^attr-private/autofillDatasetPickerMaxWidth\n01120015=^attr-private/autofillSaveCustomSubtitleMaxHeight\n01120016=^attr-private/backgroundLeft\n01120017=^attr-private/backgroundPermission\n01120018=^attr-private/backgroundRequest\n01120019=^attr-private/backgroundRequestDetail\n0112001a=^attr-private/backgroundRight\n0112001b=^attr-private/borderBottom\n0112001c=^attr-private/borderLeft\n0112001d=^attr-private/borderRight\n0112001e=^attr-private/borderTop\n0112001f=^attr-private/buttonPanelSideLayout\n01120020=^attr-private/calendarViewMode\n01120021=^attr-private/checkMarkGravity\n01120022=^attr-private/clickColor\n01120023=^attr-private/closeItemLayout\n01120024=^attr-private/colorListDivider\n01120025=^attr-private/colorPopupBackground\n01120026=^attr-private/colorProgressBackgroundNormal\n01120027=^attr-private/colorSwitchThumbNormal\n01120028=^attr-private/controllerType\n01120029=^attr-private/dayHighlightColor\n0112002a=^attr-private/daySelectorColor\n0112002b=^attr-private/defaultQueryHint\n0112002c=^attr-private/dialogCustomTitleDecorLayout\n0112002d=^attr-private/dialogMode\n0112002e=^attr-private/dialogTitleDecorLayout\n0112002f=^attr-private/dialogTitleIconsDecorLayout\n01120030=^attr-private/disableChildrenWhenDisabled\n01120031=^attr-private/dotSize\n01120032=^attr-private/drawableAlpha\n01120033=^attr-private/dropdownListPreferredItemHeight\n01120034=^attr-private/emulated\n01120035=^attr-private/enableControlView\n01120036=^attr-private/enableSubtitle\n01120037=^attr-private/errorColor\n01120038=^attr-private/errorMessageAboveBackground\n01120039=^attr-private/errorMessageBackground\n0112003a=^attr-private/expandActivityOverflowButtonDrawable\n0112003b=^attr-private/externalRouteEnabledDrawable\n0112003c=^attr-private/featureId\n0112003d=^attr-private/findOnPageNextDrawable\n0112003e=^attr-private/findOnPagePreviousDrawable\n0112003f=^attr-private/floatingToolbarCloseDrawable\n01120040=^attr-private/floatingToolbarDividerColor\n01120041=^attr-private/floatingToolbarForegroundColor\n01120042=^attr-private/floatingToolbarItemBackgroundBorderlessDrawable\n01120043=^attr-private/floatingToolbarItemBackgroundDrawable\n01120044=^attr-private/floatingToolbarOpenDrawable\n01120045=^attr-private/floatingToolbarPopupBackgroundDrawable\n01120046=^attr-private/foregroundInsidePadding\n01120047=^attr-private/fragmentBreadCrumbsStyle\n01120048=^attr-private/frameDuration\n01120049=^attr-private/framesCount\n0112004a=^attr-private/fromBottom\n0112004b=^attr-private/fromLeft\n0112004c=^attr-private/fromRight\n0112004d=^attr-private/fromTop\n0112004e=^attr-private/gestureOverlayViewStyle\n0112004f=^attr-private/glowDot\n01120050=^attr-private/hasRoundedCorners\n01120051=^attr-private/headerLayout\n01120052=^attr-private/headerRemoveIconIfEmpty\n01120053=^attr-private/headerTextColor\n01120054=^attr-private/hideWheelUntilFocused\n01120055=^attr-private/horizontalProgressLayout\n01120056=^attr-private/iconfactoryBadgeSize\n01120057=^attr-private/iconfactoryIconSize\n01120058=^attr-private/initialActivityCount\n01120059=^attr-private/internalLayout\n0112005a=^attr-private/internalMaxHeight\n0112005b=^attr-private/internalMaxWidth\n0112005c=^attr-private/internalMinHeight\n0112005d=^attr-private/internalMinWidth\n0112005e=^attr-private/interpolatorX\n0112005f=^attr-private/interpolatorY\n01120060=^attr-private/interpolatorZ\n01120061=^attr-private/itemColor\n01120062=^attr-private/itemLayout\n01120063=^attr-private/keyboardViewStyle\n01120064=^attr-private/layoutManager\n01120065=^attr-private/layout_alwaysShow\n01120066=^attr-private/layout_childType\n01120067=^attr-private/layout_hasNestedScrollIndicator\n01120068=^attr-private/layout_ignoreOffset\n01120069=^attr-private/layout_maxHeight\n0112006a=^attr-private/layout_removeBorders\n0112006b=^attr-private/leftToRight\n0112006c=^attr-private/legacyLayout\n0112006d=^attr-private/lightRadius\n0112006e=^attr-private/lightY\n0112006f=^attr-private/lightZ\n01120070=^attr-private/listItemLayout\n01120071=^attr-private/listLayout\n01120072=^attr-private/locale\n01120073=^attr-private/lockPatternStyle\n01120074=^attr-private/magnifierColorOverlay\n01120075=^attr-private/magnifierElevation\n01120076=^attr-private/magnifierHeight\n01120077=^attr-private/magnifierHorizontalOffset\n01120078=^attr-private/magnifierStyle\n01120079=^attr-private/magnifierVerticalOffset\n0112007a=^attr-private/magnifierWidth\n0112007b=^attr-private/magnifierZoom\n0112007c=^attr-private/majorWeightMax\n0112007d=^attr-private/majorWeightMin\n0112007e=^attr-private/maxCollapsedHeight\n0112007f=^attr-private/maxCollapsedHeightSmall\n01120080=^attr-private/maxFileSize\n01120081=^attr-private/maxItems\n01120082=^attr-private/minorWeightMax\n01120083=^attr-private/minorWeightMin\n01120084=^attr-private/monthTextAppearance\n01120085=^attr-private/mountPoint\n01120086=^attr-private/mtpReserve\n01120087=^attr-private/multiChoiceItemLayout\n01120088=^attr-private/navigationButtonStyle\n01120089=^attr-private/needsDefaultBackgrounds\n0112008a=^attr-private/notificationHeaderAppNameVisibility\n0112008b=^attr-private/notificationHeaderIconSize\n0112008c=^attr-private/notificationHeaderStyle\n0112008d=^attr-private/notificationHeaderTextAppearance\n0112008e=^attr-private/numDots\n0112008f=^attr-private/opacityListDivider\n01120090=^attr-private/paddingBottomNoButtons\n01120091=^attr-private/paddingTopNoTitle\n01120092=^attr-private/pageSpacing\n01120093=^attr-private/panelMenuIsCompact\n01120094=^attr-private/panelMenuListTheme\n01120095=^attr-private/panelMenuListWidth\n01120096=^attr-private/pathAdvancedPattern\n01120097=^attr-private/pathColor\n01120098=^attr-private/pointerIconAlias\n01120099=^attr-private/pointerIconAllScroll\n0112009a=^attr-private/pointerIconArrow\n0112009b=^attr-private/pointerIconCell\n0112009c=^attr-private/pointerIconContextMenu\n0112009d=^attr-private/pointerIconCopy\n0112009e=^attr-private/pointerIconCrosshair\n0112009f=^attr-private/pointerIconGrab\n011200a0=^attr-private/pointerIconGrabbing\n011200a1=^attr-private/pointerIconHand\n011200a2=^attr-private/pointerIconHelp\n011200a3=^attr-private/pointerIconHorizontalDoubleArrow\n011200a4=^attr-private/pointerIconNodrop\n011200a5=^attr-private/pointerIconSpotAnchor\n011200a6=^attr-private/pointerIconSpotHover\n011200a7=^attr-private/pointerIconSpotTouch\n011200a8=^attr-private/pointerIconText\n011200a9=^attr-private/pointerIconTopLeftDiagonalDoubleArrow\n011200aa=^attr-private/pointerIconTopRightDiagonalDoubleArrow\n011200ab=^attr-private/pointerIconVerticalDoubleArrow\n011200ac=^attr-private/pointerIconVerticalText\n011200ad=^attr-private/pointerIconWait\n011200ae=^attr-private/pointerIconZoomIn\n011200af=^attr-private/pointerIconZoomOut\n011200b0=^attr-private/popupPromptView\n011200b1=^attr-private/position\n011200b2=^attr-private/preferenceActivityStyle\n011200b3=^attr-private/preferenceFragmentListStyle\n011200b4=^attr-private/preferenceFragmentPaddingSide\n011200b5=^attr-private/preferenceFrameLayoutStyle\n011200b6=^attr-private/preferenceHeaderPanelStyle\n011200b7=^attr-private/preferenceListStyle\n011200b8=^attr-private/preferencePanelStyle\n011200b9=^attr-private/preserveIconSpacing\n011200ba=^attr-private/primary\n011200bb=^attr-private/productId\n011200bc=^attr-private/progressBarCornerRadius\n011200bd=^attr-private/progressLayout\n011200be=^attr-private/quickContactBadgeOverlay\n011200bf=^attr-private/quickContactWindowSize\n011200c0=^attr-private/regularColor\n011200c1=^attr-private/removable\n011200c2=^attr-private/removeBeforeMRelease\n011200c3=^attr-private/request\n011200c4=^attr-private/requestDetail\n011200c5=^attr-private/resOutColor\n011200c6=^attr-private/reverseLayout\n011200c7=^attr-private/screenLayout\n011200c8=^attr-private/scrollCaptureHint\n011200c9=^attr-private/scrollIndicatorPaddingLeft\n011200ca=^attr-private/scrollIndicatorPaddingRight\n011200cb=^attr-private/searchDialogTheme\n011200cc=^attr-private/searchResultListItemHeight\n011200cd=^attr-private/searchWidgetCorpusItemBackground\n011200ce=^attr-private/seekBarDialogPreferenceStyle\n011200cf=^attr-private/seekBarPreferenceStyle\n011200d0=^attr-private/selectionDivider\n011200d1=^attr-private/selectionDividersDistance\n011200d2=^attr-private/selectionScrollOffset\n011200d3=^attr-private/showAtTop\n011200d4=^attr-private/showRelative\n011200d5=^attr-private/showSeekBarValue\n011200d6=^attr-private/showTitle\n011200d7=^attr-private/showWallpaper\n011200d8=^attr-private/singleChoiceItemLayout\n011200d9=^attr-private/spanCount\n011200da=^attr-private/stackFromEnd\n011200db=^attr-private/state_accessibility_focused\n011200dc=^attr-private/storageDescription\n011200dd=^attr-private/successColor\n011200de=^attr-private/systemUserOnly\n011200df=^attr-private/tabLayout\n011200e0=^attr-private/textAppearanceAutoCorrectionSuggestion\n011200e1=^attr-private/textAppearanceEasyCorrectSuggestion\n011200e2=^attr-private/textAppearanceMisspelledSuggestion\n011200e3=^attr-private/textColorPrimaryActivated\n011200e4=^attr-private/textColorSearchUrl\n011200e5=^attr-private/textColorSecondaryActivated\n011200e6=^attr-private/textEditSuggestionContainerLayout\n011200e7=^attr-private/textEditSuggestionHighlightStyle\n011200e8=^attr-private/textUnderlineColor\n011200e9=^attr-private/textUnderlineThickness\n011200ea=^attr-private/thumbDrawable\n011200eb=^attr-private/thumbMinHeight\n011200ec=^attr-private/thumbMinWidth\n011200ed=^attr-private/toBottom\n011200ee=^attr-private/toLeft\n011200ef=^attr-private/toRight\n011200f0=^attr-private/toTop\n011200f1=^attr-private/toastFrameBackground\n011200f2=^attr-private/tooltipBackgroundColor\n011200f3=^attr-private/tooltipForegroundColor\n011200f4=^attr-private/tooltipFrameBackground\n011200f5=^attr-private/trackDrawable\n011200f6=^attr-private/unlockProfile\n011200f7=^attr-private/useDisabledAlpha\n011200f8=^attr-private/vendorId\n011200f9=^attr-private/viewType\n011200fa=^attr-private/virtualButtonPressedDrawable\n011200fb=^attr-private/windowActionBarFullscreenDecorLayout\n011200fc=^attr-private/windowFixedHeightMajor\n011200fd=^attr-private/windowFixedHeightMinor\n011200fe=^attr-private/windowFixedWidthMajor\n011200ff=^attr-private/windowFixedWidthMinor\n01120100=^attr-private/windowOutsetBottom\n01120101=^attr-private/yearListItemActivatedTextAppearance\n01130000=fraction/config_autoBrightnessAdjustmentMaxGamma\n01130001=fraction/config_dimBehindFadeDuration\n01130002=fraction/config_maximumScreenDimRatio\n01130003=fraction/config_prescaleAbsoluteVolume_index1\n01130004=fraction/config_prescaleAbsoluteVolume_index2\n01130005=fraction/config_prescaleAbsoluteVolume_index3\n01130006=fraction/config_screenAutoBrightnessDozeScaleFactor\n01130007=fraction/docked_stack_divider_fixed_ratio\n01130008=fraction/input_extract_action_margin_bottom\n01130009=fraction/input_extract_layout_height\n0113000a=fraction/input_extract_layout_padding_left\n0113000b=fraction/input_extract_layout_padding_left_no_action\n0113000c=fraction/input_extract_layout_padding_right\n0113000d=fraction/input_extract_text_margin_bottom\n0113000e=fraction/thumbnail_fullscreen_scale\n0113000f=plurals/abbrev_in_num_minutes\n01130010=plurals/abbrev_in_num_hours\n01130011=plurals/abbrev_in_num_days\n01130012=plurals/duration_seconds\n01130013=plurals/duration_minutes\n01130014=plurals/duration_hours\n01130015=plurals/wifi_available\n01130016=plurals/wifi_available_detailed\n01130017=plurals/matches_found\n01130018=plurals/restr_pin_countdown\n01140000=menu/language_selection_list\n01140001=menu/webview_copy\n01140002=menu/webview_find\n01140003=plurals/last_num_days\n01140004=plurals/duration_seconds\n01140005=plurals/duration_minutes\n01140006=plurals/duration_hours\n01140007=plurals/duration_minutes_shortest\n01140008=plurals/duration_hours_shortest\n01140009=plurals/duration_days_shortest\n0114000a=plurals/duration_years_shortest\n0114000b=plurals/duration_minutes_shortest_future\n0114000c=plurals/duration_hours_shortest_future\n0114000d=plurals/duration_days_shortest_future\n0114000e=plurals/duration_years_shortest_future\n0114000f=plurals/duration_minutes_relative\n01140010=plurals/duration_hours_relative\n01140011=plurals/duration_days_relative\n01140012=plurals/duration_years_relative\n01140013=plurals/duration_minutes_relative_future\n01140014=plurals/duration_hours_relative_future\n01140015=plurals/duration_days_relative_future\n01140016=plurals/duration_years_relative_future\n01140017=plurals/wifi_available\n01140018=plurals/wifi_available_detailed\n01140019=plurals/matches_found\n0114001a=plurals/restr_pin_countdown\n0114001b=plurals/zen_mode_duration_minutes_summary\n0114001c=plurals/zen_mode_duration_minutes_summary_short\n0114001d=plurals/zen_mode_duration_hours_summary\n0114001e=plurals/zen_mode_duration_hours_summary_short\n0114001f=plurals/zen_mode_duration_minutes\n01140020=plurals/zen_mode_duration_minutes_short\n01140021=plurals/zen_mode_duration_hours\n01140022=plurals/zen_mode_duration_hours_short\n01140023=plurals/selected_count\n01150000=plurals/autofill_picker_some_suggestions\n01150001=plurals/bugreport_countdown\n01150002=plurals/duration_days_relative\n01150003=plurals/duration_days_relative_future\n01150004=plurals/duration_days_shortest\n01150005=plurals/duration_days_shortest_future\n01150006=plurals/duration_hours_relative\n01150007=plurals/duration_hours_relative_future\n01150008=plurals/duration_hours_shortest\n01150009=plurals/duration_hours_shortest_future\n0115000a=plurals/duration_minutes_relative\n0115000b=plurals/duration_minutes_relative_future\n0115000c=plurals/duration_minutes_shortest\n0115000d=plurals/duration_minutes_shortest_future\n0115000e=plurals/duration_years_relative\n0115000f=plurals/duration_years_relative_future\n01150010=plurals/duration_years_shortest\n01150011=plurals/duration_years_shortest_future\n01150012=plurals/file_count\n01150013=plurals/kg_too_many_failed_attempts_countdown\n01150014=plurals/last_num_days\n01150015=plurals/matches_found\n01150016=plurals/pinpuk_attempts\n01150017=plurals/restr_pin_countdown\n01150018=plurals/selected_count\n01150019=plurals/ssl_ca_cert_warning\n0115001a=plurals/zen_mode_duration_hours\n0115001b=plurals/zen_mode_duration_hours_short\n0115001c=plurals/zen_mode_duration_hours_summary\n0115001d=plurals/zen_mode_duration_hours_summary_short\n0115001e=plurals/zen_mode_duration_minutes\n0115001f=plurals/zen_mode_duration_minutes_short\n01150020=plurals/zen_mode_duration_minutes_summary\n01150021=plurals/zen_mode_duration_minutes_summary_short\n01150022=plurals/zen_mode_duration_minutes_summary\n01150023=plurals/zen_mode_duration_minutes_summary_short\n01160000=^attr-private/isLightTheme\n01160001=^attr-private/textColorPrimaryActivated\n01160002=^attr-private/textColorSecondaryActivated\n01160003=^attr-private/textColorSearchUrl\n01160004=^attr-private/searchWidgetCorpusItemBackground\n01160005=^attr-private/textAppearanceEasyCorrectSuggestion\n01160006=^attr-private/textAppearanceMisspelledSuggestion\n01160007=^attr-private/textAppearanceAutoCorrectionSuggestion\n01160008=^attr-private/textUnderlineColor\n01160009=^attr-private/textUnderlineThickness\n0116000a=^attr-private/errorMessageBackground\n0116000b=^attr-private/errorMessageAboveBackground\n0116000c=^attr-private/searchResultListItemHeight\n0116000d=^attr-private/dropdownListPreferredItemHeight\n0116000e=^attr-private/windowActionBarFullscreenDecorLayout\n0116000f=^attr-private/floatingToolbarCloseDrawable\n01160010=^attr-private/floatingToolbarForegroundColor\n01160011=^attr-private/floatingToolbarItemBackgroundBorderlessDrawable\n01160012=^attr-private/floatingToolbarItemBackgroundDrawable\n01160013=^attr-private/floatingToolbarOpenDrawable\n01160014=^attr-private/floatingToolbarPopupBackgroundDrawable\n01160015=^attr-private/alertDialogButtonGroupStyle\n01160016=^attr-private/alertDialogCenterButtons\n01160017=^attr-private/panelMenuIsCompact\n01160018=^attr-private/panelMenuListWidth\n01160019=^attr-private/panelMenuListTheme\n0116001a=^attr-private/gestureOverlayViewStyle\n0116001b=^attr-private/quickContactBadgeOverlay\n0116001c=^attr-private/fragmentBreadCrumbsStyle\n0116001d=^attr-private/activityChooserViewStyle\n0116001e=^attr-private/actionModePopupWindowStyle\n0116001f=^attr-private/preferenceActivityStyle\n01160020=^attr-private/seekBarDialogPreferenceStyle\n01160021=^attr-private/preferencePanelStyle\n01160022=^attr-private/preferenceHeaderPanelStyle\n01160023=^attr-private/preferenceListStyle\n01160024=^attr-private/preferenceFragmentListStyle\n01160025=^attr-private/preferenceFragmentPaddingSide\n01160026=^attr-private/seekBarPreferenceStyle\n01160027=^attr-private/textEditSuggestionContainerLayout\n01160028=^attr-private/textEditSuggestionHighlightStyle\n01160029=^attr-private/dialogTitleIconsDecorLayout\n0116002a=^attr-private/dialogCustomTitleDecorLayout\n0116002b=^attr-private/dialogTitleDecorLayout\n0116002c=^attr-private/toastFrameBackground\n0116002d=^attr-private/searchDialogTheme\n0116002e=^attr-private/preferenceFrameLayoutStyle\n0116002f=^attr-private/accessibilityFocusedDrawable\n01160030=^attr-private/findOnPageNextDrawable\n01160031=^attr-private/findOnPagePreviousDrawable\n01160032=^attr-private/colorSwitchThumbNormal\n01160033=^attr-private/lightY\n01160034=^attr-private/lightZ\n01160035=^attr-private/lightRadius\n01160036=^attr-private/windowFixedWidthMajor\n01160037=^attr-private/windowFixedHeightMinor\n01160038=^attr-private/windowFixedWidthMinor\n01160039=^attr-private/windowFixedHeightMajor\n0116003a=^attr-private/windowOutsetBottom\n0116003b=^attr-private/buttonPanelSideLayout\n0116003c=^attr-private/listLayout\n0116003d=^attr-private/multiChoiceItemLayout\n0116003e=^attr-private/singleChoiceItemLayout\n0116003f=^attr-private/listItemLayout\n01160040=^attr-private/progressLayout\n01160041=^attr-private/horizontalProgressLayout\n01160042=^attr-private/showTitle\n01160043=^attr-private/needsDefaultBackgrounds\n01160044=^attr-private/controllerType\n01160045=^attr-private/allowStacking\n01160046=^attr-private/activityOpenRemoteViewsEnterAnimation\n01160047=^attr-private/foregroundInsidePadding\n01160048=^attr-private/paddingBottomNoButtons\n01160049=^attr-private/paddingTopNoTitle\n0116004a=^attr-private/checkMarkGravity\n0116004b=^attr-private/thumbDrawable\n0116004c=^attr-private/thumbMinWidth\n0116004d=^attr-private/thumbMinHeight\n0116004e=^attr-private/trackDrawable\n0116004f=^attr-private/backgroundRight\n01160050=^attr-private/backgroundLeft\n01160051=^attr-private/position\n01160052=^attr-private/drawableAlpha\n01160053=^attr-private/borderTop\n01160054=^attr-private/borderBottom\n01160055=^attr-private/borderLeft\n01160056=^attr-private/borderRight\n01160057=^attr-private/layout_removeBorders\n01160058=^attr-private/preserveIconSpacing\n01160059=^attr-private/maxItems\n0116005a=^attr-private/useDisabledAlpha\n0116005b=^attr-private/resOutColor\n0116005c=^attr-private/clickColor\n0116005d=^attr-private/tabLayout\n0116005e=^attr-private/popupPromptView\n0116005f=^attr-private/disableChildrenWhenDisabled\n01160060=^attr-private/internalLayout\n01160061=^attr-private/headerTextColor\n01160062=^attr-private/yearListItemActivatedTextAppearance\n01160063=^attr-private/dialogMode\n01160064=^attr-private/quickContactWindowSize\n01160065=^attr-private/majorWeightMin\n01160066=^attr-private/minorWeightMin\n01160067=^attr-private/majorWeightMax\n01160068=^attr-private/minorWeightMax\n01160069=^attr-private/monthTextAppearance\n0116006a=^attr-private/daySelectorColor\n0116006b=^attr-private/dayHighlightColor\n0116006c=^attr-private/calendarViewMode\n0116006d=^attr-private/selectionDivider\n0116006e=^attr-private/selectionDividerHeight\n0116006f=^attr-private/selectionDividersDistance\n01160070=^attr-private/internalMinHeight\n01160071=^attr-private/internalMaxHeight\n01160072=^attr-private/internalMinWidth\n01160073=^attr-private/internalMaxWidth\n01160074=^attr-private/virtualButtonPressedDrawable\n01160075=^attr-private/hideWheelUntilFocused\n01160076=^attr-private/legacyLayout\n01160077=^attr-private/frameDuration\n01160078=^attr-private/framesCount\n01160079=^attr-private/opticalInsetLeft\n0116007a=^attr-private/opticalInsetTop\n0116007b=^attr-private/opticalInsetRight\n0116007c=^attr-private/opticalInsetBottom\n0116007d=^attr-private/interpolatorX\n0116007e=^attr-private/interpolatorY\n0116007f=^attr-private/interpolatorZ\n01160080=^attr-private/removeBeforeMRelease\n01160081=^attr-private/state_accessibility_focused\n01160082=^attr-private/initialActivityCount\n01160083=^attr-private/expandActivityOverflowButtonDrawable\n01160084=^attr-private/keyboardViewStyle\n01160085=^attr-private/aspect\n01160086=^attr-private/pathColor\n01160087=^attr-private/regularColor\n01160088=^attr-private/errorColor\n01160089=^attr-private/successColor\n0116008a=^attr-private/closeItemLayout\n0116008b=^attr-private/defaultQueryHint\n0116008c=^attr-private/pointerIconArrow\n0116008d=^attr-private/pointerIconSpotHover\n0116008e=^attr-private/pointerIconSpotTouch\n0116008f=^attr-private/pointerIconSpotAnchor\n01160090=^attr-private/pointerIconContextMenu\n01160091=^attr-private/pointerIconHand\n01160092=^attr-private/pointerIconHelp\n01160093=^attr-private/pointerIconWait\n01160094=^attr-private/pointerIconCell\n01160095=^attr-private/pointerIconCrosshair\n01160096=^attr-private/pointerIconText\n01160097=^attr-private/pointerIconVerticalText\n01160098=^attr-private/pointerIconAlias\n01160099=^attr-private/pointerIconCopy\n0116009a=^attr-private/pointerIconNodrop\n0116009b=^attr-private/pointerIconAllScroll\n0116009c=^attr-private/pointerIconHorizontalDoubleArrow\n0116009d=^attr-private/pointerIconVerticalDoubleArrow\n0116009e=^attr-private/pointerIconTopRightDiagonalDoubleArrow\n0116009f=^attr-private/pointerIconTopLeftDiagonalDoubleArrow\n011600a0=^attr-private/pointerIconZoomIn\n011600a1=^attr-private/pointerIconZoomOut\n011600a2=^attr-private/pointerIconGrab\n011600a3=^attr-private/pointerIconGrabbing\n011600a4=^attr-private/mountPoint\n011600a5=^attr-private/storageDescription\n011600a6=^attr-private/primary\n011600a7=^attr-private/removable\n011600a8=^attr-private/emulated\n011600a9=^attr-private/mtpReserve\n011600aa=^attr-private/allowMassStorage\n011600ab=^attr-private/maxFileSize\n011600ac=^attr-private/screenLayout\n011600ad=^attr-private/headerLayout\n011600ae=^attr-private/headerRemoveIconIfEmpty\n011600af=^attr-private/locale\n011600b0=^attr-private/vendorId\n011600b1=^attr-private/productId\n011600b2=^attr-private/externalRouteEnabledDrawable\n011600b3=^attr-private/pageSpacing\n011600b4=^attr-private/scrollIndicatorPaddingLeft\n011600b5=^attr-private/scrollIndicatorPaddingRight\n011600b6=^attr-private/dotSize\n011600b7=^attr-private/numDots\n011600b8=^attr-private/glowDot\n011600b9=^attr-private/leftToRight\n011600ba=^attr-private/layout_childType\n011600bb=^attr-private/itemLayout\n011600bc=^attr-private/itemColor\n011600bd=^attr-private/navigationButtonStyle\n011600be=^attr-private/maxCollapsedHeight\n011600bf=^attr-private/maxCollapsedHeightSmall\n011600c0=^attr-private/showRelative\n011600c1=^attr-private/layout_alwaysShow\n011600c2=^attr-private/layout_ignoreOffset\n011600c3=^attr-private/layout_hasNestedScrollIndicator\n011600c4=^attr-private/cantSaveState\n011600c5=^attr-private/systemUserOnly\n011600c6=^attr-private/alwaysFocusable\n011600c7=^attr-private/minimalWidth\n011600c8=^attr-private/minimalHeight\n01170000=xml/apns\n01170001=xml/audio_assets\n01170002=xml/autofill_compat_accessibility_service\n01170003=xml/autotext\n01170004=xml/bookmarks\n01170005=xml/color_extraction\n01170006=xml/config_user_types\n01170007=xml/config_webview_packages\n01170008=xml/default_zen_mode_config\n01170009=xml/global_keys\n0117000a=xml/kg_password_kbd_numeric\n0117000b=xml/password_kbd_extension\n0117000c=xml/password_kbd_numeric\n0117000d=xml/password_kbd_popup_template\n0117000e=xml/password_kbd_qwerty\n0117000f=xml/password_kbd_qwerty_shifted\n01170010=xml/password_kbd_symbols\n01170011=xml/password_kbd_symbols_shift\n01170012=xml/power_profile\n01170013=xml/power_profile_test\n01170014=xml/sms_7bit_translation_table\n01170015=xml/sms_short_codes\n01170016=xml/storage_list\n"
  },
  {
    "path": "recaf-core/src/main/resources/logback.xml",
    "content": "<configuration>\n    <!-- This configuration only specifies writing to the console.\n         Writing to the file is defined only in the UI module.\n     -->\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <filter class=\"software.coley.recaf.analytics.logging.RecafLoggingFilter\" />\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} [%logger{0}/%thread] %-5level: %msg%n</pattern>\n        </encoder>\n    </appender>\n    <root level=\"trace\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n    <!-- Disable 3rd party loggers -->\n    <logger name=\"org.jboss.weld.Bootstrap\" level=\"OFF\"/>\n    <logger name=\"org.jboss.weld.Bean\" level=\"OFF\"/>\n    <logger name=\"org.jboss.weld.Reflection\" level=\"OFF\"/>\n    <logger name=\"org.jboss.weld.Event\" level=\"OFF\"/>\n    <logger name=\"org.jboss.weld.Context\" level=\"OFF\"/>\n    <logger name=\"org.jboss.weld.Resolution\" level=\"OFF\"/>\n    <logger name=\"org.jboss.weld.BeanManager\" level=\"OFF\"/>\n    <logger name=\"org.jboss.weld.Validator\" level=\"OFF\"/>\n</configuration>"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/BootstrapTest.java",
    "content": "package software.coley.recaf;\n\nimport jakarta.enterprise.inject.Instance;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.HelloWorld;\nimport software.coley.recaf.workspace.model.EmptyWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\n\nimport java.io.IOException;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link Bootstrap}\n */\nclass BootstrapTest extends TestBase {\n\t@BeforeEach\n\tvoid setup() {\n\t\tworkspaceManager.closeCurrent();\n\t}\n\n\t@AfterAll\n\tstatic void cleanup() {\n\t\tworkspaceManager.closeCurrent();\n\t}\n\n\t@Test\n\tvoid testGetApplicationScopedInstance() {\n\t\tassertNotNull(workspaceManager, \"Failed to get instance of workspace manager, which should be application-scoped\");\n\t}\n\n\t@Test\n\tvoid testGetWorkspaceInstance() throws IOException {\n\t\t// Create workspace with single class\n\t\tJvmClassBundle classes = TestClassUtils.fromClasses(HelloWorld.class);\n\t\tWorkspace workspace = TestClassUtils.fromBundle(classes);\n\n\t\t// Should be null since nothing is active in the workspace manager.\n\t\t// Thus, the supplier method should yield 'null'.\n\t\tInstance<Workspace> currentWorkspaceInstance = recaf.instance(Workspace.class);\n\t\tassertSame(EmptyWorkspace.get(), currentWorkspaceInstance.get(),\n\t\t\t\t\"No current workspace should be set, thus empty should be provided\");\n\n\t\t// Assign a workspace.\n\t\tworkspaceManager.setCurrent(workspace);\n\n\t\t// Should no longer be null.\n\t\tassertEquals(workspace, currentWorkspaceInstance.get(),\n\t\t\t\t\"Workspace manager should expose current workspace as dependent scoped bean\");\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/info/ClassInfoTest.java",
    "content": "package software.coley.recaf.info;\n\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.Opcodes;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.AccessibleFields;\nimport software.coley.recaf.test.dummy.AnnotationImpl;\nimport software.coley.recaf.test.dummy.ClassWithAnonymousInner;\nimport software.coley.recaf.test.dummy.ClassWithEmbeddedInners;\nimport software.coley.recaf.test.dummy.ClassWithInner;\nimport software.coley.recaf.test.dummy.MultipleInterfacesClass;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link ClassInfo}\n */\nclass ClassInfoTest {\n\tstatic JvmClassInfo arrayList;\n\tstatic JvmClassInfo accessibleFields;\n\tstatic JvmClassInfo multipleInterfacesClass;\n\tstatic JvmClassInfo annotationImpl;\n\tstatic JvmClassInfo classWithInner;\n\tstatic JvmClassInfo classWithInner$Inner;\n\tstatic JvmClassInfo classWithAnonymousInner;\n\tstatic JvmClassInfo classWithAnonymousInner$Inner;\n\tstatic JvmClassInfo classWithEmbeddedInners;\n\tstatic JvmClassInfo classWithEmbeddedInners$A;\n\tstatic JvmClassInfo classWithEmbeddedInners$B;\n\tstatic JvmClassInfo classWithEmbeddedInners$C;\n\tstatic JvmClassInfo classWithEmbeddedInners$D;\n\tstatic JvmClassInfo classWithEmbeddedInners$E;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\tarrayList = TestClassUtils.fromRuntimeClass(ArrayList.class);\n\t\taccessibleFields = TestClassUtils.fromRuntimeClass(AccessibleFields.class);\n\t\tmultipleInterfacesClass = TestClassUtils.fromRuntimeClass(MultipleInterfacesClass.class);\n\t\tannotationImpl = TestClassUtils.fromRuntimeClass(AnnotationImpl.class);\n\t\tclassWithInner = TestClassUtils.fromRuntimeClass(ClassWithInner.class);\n\t\tclassWithInner$Inner = TestClassUtils.fromRuntimeClass(ClassWithInner.TheInner.class);\n\t\tclassWithAnonymousInner = TestClassUtils.fromRuntimeClass(ClassWithAnonymousInner.class);\n\t\tclassWithAnonymousInner$Inner\n\t\t\t\t= new JvmClassInfoBuilder(new ClassReader(classWithAnonymousInner.getName() + \"$1\")).build();\n\t\tclassWithEmbeddedInners = TestClassUtils.fromRuntimeClass(ClassWithEmbeddedInners.class);\n\t\tclassWithEmbeddedInners$A = TestClassUtils.fromRuntimeClass(ClassWithEmbeddedInners.A.class);\n\t\tclassWithEmbeddedInners$B = TestClassUtils.fromRuntimeClass(ClassWithEmbeddedInners.A.B.class);\n\t\tclassWithEmbeddedInners$C = TestClassUtils.fromRuntimeClass(ClassWithEmbeddedInners.A.B.C.class);\n\t\tclassWithEmbeddedInners$D = TestClassUtils.fromRuntimeClass(ClassWithEmbeddedInners.A.B.C.D.class);\n\t\tclassWithEmbeddedInners$E = TestClassUtils.fromRuntimeClass(ClassWithEmbeddedInners.A.B.C.D.E.class);\n\t}\n\n\t@Test\n\tvoid getSourceFileName() {\n\t\t// Root class\n\t\tassertEquals(\"AccessibleFields.java\", accessibleFields.getSourceFileName(),\n\t\t\t\t\"Source file name missing, or incorrect\");\n\t\tassertEquals(\"ClassWithInner.java\", classWithInner.getSourceFileName(),\n\t\t\t\t\"Source file name missing, or incorrect\");\n\n\t\t// Inner classes should have their outer class's source name\n\t\tassertEquals(classWithInner.getSourceFileName(), classWithInner$Inner.getSourceFileName(),\n\t\t\t\t\"Inner source file should match outer class's\");\n\t}\n\n\t@Test\n\tvoid getInterfaces() {\n\t\tassertEquals(0, accessibleFields.getInterfaces().size(), \"Class should have 0 interfaces\");\n\n\t\t// Implicit annotation\n\t\tassertEquals(1, annotationImpl.getInterfaces().size(), \"Annotation should implement annotation type\");\n\t\tassertEquals(\"java/lang/annotation/Annotation\", annotationImpl.getInterfaces().getFirst(),\n\t\t\t\t\"Expected annotation interface\");\n\n\t\t// This one has 2 interfaces\n\t\tList<String> interfaces = multipleInterfacesClass.getInterfaces();\n\t\tassertEquals(2, interfaces.size(), \"Class should have 2 interfaces\");\n\t\tassertEquals(\"java/lang/AutoCloseable\", interfaces.get(0));\n\t\tassertEquals(\"java/util/Comparator\", interfaces.get(1));\n\t}\n\n\t@Test\n\tvoid getSuperName() {\n\t\tassertEquals(\"java/lang/Object\", accessibleFields.getSuperName(), \"Class should extend Object\");\n\t\tassertEquals(\"java/lang/Object\", multipleInterfacesClass.getSuperName(), \"Class should extend Object\");\n\n\t\t// Edge case, while ours does, the JVM technically allows you to not define one for annotations.\n\t\tassertEquals(\"java/lang/Object\", annotationImpl.getSuperName(), \"Annotation should extend Object\");\n\t}\n\n\t@Test\n\tvoid getPackageName() {\n\t\tString packageName = AccessibleFields.class.getPackageName().replace('.', '/');\n\t\tassertEquals(packageName, accessibleFields.getPackageName());\n\n\t\t// Should work for inner classes too\n\t\tassertEquals(packageName, classWithEmbeddedInners$E.getPackageName());\n\t}\n\n\t@Test\n\tvoid parentTypesStream() {\n\t\t// For basic class\n\t\tSet<String> fieldsParents = accessibleFields.parentTypesStream().collect(Collectors.toSet());\n\t\tassertEquals(Set.of(\"java/lang/Object\"), fieldsParents);\n\n\t\t// Multiple interface class has object, plus the two interfaces it implements\n\t\t// (of which do not have any further parents)\n\t\tSet<String> interfaceParents = multipleInterfacesClass.parentTypesStream().collect(Collectors.toSet());\n\t\tassertEquals(Set.of(\"java/lang/Object\", \"java/lang/AutoCloseable\", \"java/util/Comparator\"), interfaceParents);\n\t}\n\n\t@Test\n\tvoid getSignature() {\n\t\tassertNull(accessibleFields.getSignature(), \"Should have no signature, no generics defined\");\n\n\t\t// Multiple interfaces implements Comparable<String>\n\t\tassertEquals(\"Ljava/lang/Object;Ljava/lang/AutoCloseable;Ljava/util/Comparator<Ljava/lang/String;>;\",\n\t\t\t\tmultipleInterfacesClass.getSignature(),\n\t\t\t\t\"Usage of generic interfaces should yield a signature\");\n\n\t\t// ArrayList obviously has <T> pattern, but as with the above,\n\t\t// you get a lot of additional information from signatures.\n\t\tassertEquals(\"<E:Ljava/lang/Object;>Ljava/util/AbstractList<TE;>;Ljava/util/List<TE;>;\" +\n\t\t\t\t\t\t\"Ljava/util/RandomAccess;Ljava/lang/Cloneable;Ljava/io/Serializable;\",\n\t\t\t\tarrayList.getSignature(),\n\t\t\t\t\"ArrayList should have generic for <T> pattern\");\n\t}\n\n\t@Test\n\tvoid getOuterClassName() {\n\t\tassertNull(accessibleFields.getOuterClassName(), \"Should have no outer class\");\n\n\t\t// Test inner-outer relation (normal)\n\t\tassertNull(classWithInner.getOuterClassName(), \"Should have no outer class\");\n\t\tassertEquals(classWithInner.getName(), classWithInner$Inner.getOuterClassName(),\n\t\t\t\t\"Inner has wrong outer class name\");\n\n\t\t// Test inner-outer relation (anonymous)\n\t\tassertNull(classWithAnonymousInner.getOuterClassName(), \"Should have no outer class\");\n\t\tassertEquals(classWithAnonymousInner.getName(), classWithAnonymousInner$Inner.getOuterClassName(),\n\t\t\t\t\"Inner has wrong outer class name\");\n\n\t\t// Test chain\n\t\tassertEquals(classWithEmbeddedInners.getName(), classWithEmbeddedInners$A.getOuterClassName(), \"Expected Root -> A\");\n\t\tassertEquals(classWithEmbeddedInners$A.getName(), classWithEmbeddedInners$B.getOuterClassName(), \"Expected A -> B\");\n\t\tassertEquals(classWithEmbeddedInners$B.getName(), classWithEmbeddedInners$C.getOuterClassName(), \"Expected B -> C\");\n\t\tassertEquals(classWithEmbeddedInners$C.getName(), classWithEmbeddedInners$D.getOuterClassName(), \"Expected C -> D\");\n\t\tassertEquals(classWithEmbeddedInners$D.getName(), classWithEmbeddedInners$E.getOuterClassName(), \"Expected D -> E\");\n\t}\n\n\t@Test\n\tvoid getOuterMethodName() {\n\t\t// Classes without outer methods\n\t\tassertNull(accessibleFields.getOuterMethodName(), \"Should have no outer method\");\n\t\tassertNull(classWithInner.getOuterMethodName(), \"Should have no outer method\");\n\t\tassertNull(classWithAnonymousInner.getOuterMethodName(), \"Should have no outer method\");\n\t\tassertNull(classWithEmbeddedInners$A.getOuterMethodName(), \"Should have no outer method\");\n\t\tassertNull(classWithEmbeddedInners$E.getOuterMethodName(), \"Should have no outer method\");\n\n\t\t// Classes with outer class, not method\n\t\tassertNull(classWithInner$Inner.getOuterMethodName(), \"Should have no outer method\");\n\n\t\t// Classes with outer method\n\t\tassertEquals(\"foo\", classWithAnonymousInner$Inner.getOuterMethodName(),\n\t\t\t\t\"Should have an outer method of 'foo'\");\n\t}\n\n\t@Test\n\tvoid getOuterMethodDescriptor() {\n\t\t// Classes without outer methods\n\t\tassertNull(accessibleFields.getOuterMethodDescriptor(), \"Should have no outer method\");\n\t\tassertNull(classWithInner.getOuterMethodDescriptor(), \"Should have no outer method\");\n\t\tassertNull(classWithAnonymousInner.getOuterMethodDescriptor(), \"Should have no outer method\");\n\t\tassertNull(classWithEmbeddedInners$A.getOuterMethodName(), \"Should have no outer method\");\n\t\tassertNull(classWithEmbeddedInners$E.getOuterMethodName(), \"Should have no outer method\");\n\n\t\t// Classes with outer class, not method\n\t\tassertNull(classWithInner$Inner.getOuterMethodDescriptor(), \"Should have no outer method\");\n\n\t\t// Classes with outer method\n\t\tassertEquals(\"()V\", classWithAnonymousInner$Inner.getOuterMethodDescriptor(),\n\t\t\t\t\"Should have an outer method of 'void()'\");\n\t}\n\n\t@Test\n\tvoid getOuterClassBreadcrumbs() {\n\t\t// No outer\n\t\tassertEquals(Collections.emptyList(), accessibleFields.getOuterClassBreadcrumbs(),\n\t\t\t\t\"Should have no outer class, so no breadcrumbs\");\n\n\t\t// Class outer\n\t\tassertEquals(Collections.singletonList(classWithInner.getName()), classWithInner$Inner.getOuterClassBreadcrumbs(),\n\t\t\t\t\"Should have an outer class\");\n\n\t\t// Class outer, inner is anonymous\n\t\tassertEquals(Collections.singletonList(classWithAnonymousInner.getName()), classWithAnonymousInner$Inner.getOuterClassBreadcrumbs(),\n\t\t\t\t\"Should have an outer class\");\n\n\t\t// Chained\n\t\tassertEquals(Collections.singletonList(classWithEmbeddedInners.getName()), classWithEmbeddedInners$A.getOuterClassBreadcrumbs(),\n\t\t\t\t\"Expected chain [Root]\");\n\t\tassertEquals(Arrays.asList(\n\t\t\t\t\t\tclassWithEmbeddedInners.getName(),\n\t\t\t\t\t\tclassWithEmbeddedInners$A.getName()\n\t\t\t\t), classWithEmbeddedInners$B.getOuterClassBreadcrumbs(),\n\t\t\t\t\"Expected chain [Root, A]\");\n\t\tassertEquals(Arrays.asList(\n\t\t\t\t\t\tclassWithEmbeddedInners.getName(),\n\t\t\t\t\t\tclassWithEmbeddedInners$A.getName(),\n\t\t\t\t\t\tclassWithEmbeddedInners$B.getName()\n\t\t\t\t), classWithEmbeddedInners$C.getOuterClassBreadcrumbs(),\n\t\t\t\t\"Expected chain [Root, A,B]\");\n\t\tassertEquals(Arrays.asList(\n\t\t\t\t\t\tclassWithEmbeddedInners.getName(),\n\t\t\t\t\t\tclassWithEmbeddedInners$A.getName(),\n\t\t\t\t\t\tclassWithEmbeddedInners$B.getName(),\n\t\t\t\t\t\tclassWithEmbeddedInners$C.getName()\n\t\t\t\t), classWithEmbeddedInners$D.getOuterClassBreadcrumbs(),\n\t\t\t\t\"Expected chain [Root, A,B,C]\");\n\t\tassertEquals(Arrays.asList(\n\t\t\t\t\t\tclassWithEmbeddedInners.getName(),\n\t\t\t\t\t\tclassWithEmbeddedInners$A.getName(),\n\t\t\t\t\t\tclassWithEmbeddedInners$B.getName(),\n\t\t\t\t\t\tclassWithEmbeddedInners$C.getName(),\n\t\t\t\t\t\tclassWithEmbeddedInners$D.getName()\n\t\t\t\t), classWithEmbeddedInners$E.getOuterClassBreadcrumbs(),\n\t\t\t\t\"Expected chain [Root, A,B,C,D]\");\n\t}\n\n\t@Test\n\tvoid getInnerClasses() {\n\t\tassertEquals(5, classWithEmbeddedInners.getInnerClasses().size());\n\t\tassertEquals(0, accessibleFields.getInnerClasses().size(), \"Expected no inners\");\n\n\t\t// Normal inner\n\t\tassertEquals(1, classWithInner.getInnerClasses().size(), \"Expected 1 inner\");\n\t\tassertEquals(1, classWithInner$Inner.getInnerClasses().size(), \"Expected 1 inner, of self\");\n\n\t\t// Anonymous inner\n\t\tassertEquals(1, classWithAnonymousInner.getInnerClasses().size(), \"Expected 1 inner\");\n\t\tassertEquals(1, classWithAnonymousInner$Inner.getInnerClasses().size(), \"Expected 1 inner, of self\");\n\n\t\t// Get by name, and check for bad lookups\n\t\tList<InnerClassInfo> inners = List.of(\n\t\t\t\tObjects.requireNonNull(classWithEmbeddedInners.getInnerClassByInnerName(\"A\")),\n\t\t\t\tObjects.requireNonNull(classWithEmbeddedInners.getInnerClassByInnerName(\"B\")),\n\t\t\t\tObjects.requireNonNull(classWithEmbeddedInners.getInnerClassByInnerName(\"C\")),\n\t\t\t\tObjects.requireNonNull(classWithEmbeddedInners.getInnerClassByInnerName(\"D\")),\n\t\t\t\tObjects.requireNonNull(classWithEmbeddedInners.getInnerClassByInnerName(\"E\"))\n\t\t);\n\t\tassertNull(classWithEmbeddedInners.getInnerClassByInnerName(\"Bogus\"));\n\t\tassertEquals(inners, classWithEmbeddedInners.getInnerClasses());\n\n\t\t// Honestly, this is just to get code-coverage on the hashcode of inner class info\n\t\tinners.stream().collect(Collectors.toMap(InnerClassInfo::getInnerName, Function.identity())).forEach((name, c) -> {\n\t\t\tassertSame(c, classWithEmbeddedInners.getInnerClassByInnerName(name));\n\t\t});\n\t}\n\n\t@Test\n\tvoid isAnonymousInner() {\n\t\tassertFalse(classWithAnonymousInner.isAnonymousInnerClass());\n\t\tassertTrue(classWithAnonymousInner$Inner.isAnonymousInnerClass());\n\t}\n\n\t@Test\n\tvoid getFields() {\n\t\t// Normal case\n\t\tassertEquals(5, accessibleFields.getFields().size());\n\n\t\t// Non-static inner should have a reference to outer as synthetic field.\n\t\t// At least, this used to be the case before nest-host attributes and such.\n\t\t//  assertEquals(1, classWithInner$Inner.getFields().size());\n\n\t\t// Despite how the format looks, they're not fields\n\t\tassertEquals(0, annotationImpl.getFields().size());\n\t}\n\n\t@Test\n\tvoid getMethods() {\n\t\t// Single method 'foo()' plus the default constructor\n\t\tassertEquals(2, classWithAnonymousInner.getMethods().size());\n\n\t\t// No constructor, just the two annotation property methods.\n\t\tassertEquals(2, annotationImpl.getMethods().size());\n\t}\n\n\t@Test\n\tvoid fieldStream() {\n\t\t// Mirror results of prior tests\n\t\tassertEquals(5, accessibleFields.fieldStream().count());\n\t\t// See comment in getFields.\n\t\t// assertEquals(1, classWithInner$Inner.fieldStream().count());\n\t\tassertEquals(0, annotationImpl.fieldStream().count());\n\t}\n\n\t@Test\n\tvoid methodStream() {\n\t\t// Mirror results of prior tests\n\t\tassertEquals(2, classWithAnonymousInner.methodStream().count());\n\t\tassertEquals(2, annotationImpl.methodStream().count());\n\t}\n\n\t@Test\n\tvoid testClass() {\n\t\tassertTrue(accessibleFields.testClass(ClassInfo::isJvmClass));\n\t\tassertFalse(accessibleFields.testClass(ClassInfo::isAndroidClass));\n\t}\n\n\t@Test\n\tvoid mapClass() {\n\t\tassertDoesNotThrow(() -> {\n\t\t\tInnerClassInfo inner = classWithInner.mapClass(c -> c.getInnerClasses().getFirst());\n\t\t\tassertNotNull(inner);\n\t\t});\n\t}\n\n\t@Test\n\tvoid asFile() {\n\t\tassertThrows(Exception.class, () -> accessibleFields.asFile());\n\t}\n\n\t@Test\n\tvoid isClass() {\n\t\tassertTrue(accessibleFields.isClass());\n\t}\n\n\t@Test\n\tvoid isFile() {\n\t\tassertFalse(accessibleFields.isFile());\n\t}\n\n\t@Test\n\tvoid isJvmClass() {\n\t\tassertTrue(accessibleFields.isJvmClass());\n\t}\n\n\t@Test\n\tvoid isAndroidClass() {\n\t\tassertFalse(accessibleFields.isAndroidClass());\n\t}\n\n\t@Test\n\tvoid equals() {\n\t\t// Two different classes should be different/\n\t\tassertNotEquals(accessibleFields, classWithInner);\n\n\t\t// Builder copy with no changes should be equal.\n\t\tJvmClassInfo accessibleFieldCopy = accessibleFields.toJvmClassBuilder().build();\n\t\tassertEquals(accessibleFields, accessibleFieldCopy);\n\t\taccessibleFieldCopy = new JvmClassInfoBuilder().adaptFrom(accessibleFields.getBytecode()).build();\n\t\tassertEquals(accessibleFields, accessibleFieldCopy);\n\n\t\t// Even a small change like the version const will make the class copy no longer equal.\n\t\tbyte[] accessibleFieldsBytecodeCopy = ArrayUtils.clone(accessibleFields.getBytecode());\n\t\taccessibleFieldsBytecodeCopy[5] = Opcodes.V11; // Change the major version\n\t\taccessibleFieldCopy = new JvmClassInfoBuilder().adaptFrom(accessibleFieldsBytecodeCopy).build();\n\t\tassertNotEquals(accessibleFields, accessibleFieldCopy);\n\n\t\t// Edge case, we try to compare a jvm-class to a non-jvm class\n\t\tassertNotEquals(accessibleFields, new StubClassInfo(accessibleFields.getName()).asAndroidClass());\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/info/JvmClassInfoTest.java",
    "content": "package software.coley.recaf.info;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.Opcodes;\nimport software.coley.cafedude.classfile.VersionConstants;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.AccessibleFields;\nimport software.coley.recaf.util.ByteHeaderUtil;\n\nimport java.io.IOException;\nimport java.util.Arrays;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link JvmClassInfo}\n */\nclass JvmClassInfoTest {\n\tstatic JvmClassInfo accessibleFields;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\taccessibleFields = TestClassUtils.fromRuntimeClass(AccessibleFields.class);\n\t}\n\n\t@Test\n\tvoid getBytecode() {\n\t\tassertNotNull(accessibleFields.getBytecode(),\n\t\t\t\t\"Missing bytecode value\");\n\t\tassertTrue(ByteHeaderUtil.match(accessibleFields.getBytecode(), ByteHeaderUtil.CLASS),\n\t\t\t\t\"Bytecode header is not CAFEBABE\");\n\t}\n\n\t@Test\n\tvoid getVersion() {\n\t\tassertTrue(accessibleFields.getVersion() >= VersionConstants.JAVA11,\n\t\t\t\t\"Invalid class file version, should be >= Java 11\");\n\t}\n\n\t@Test\n\tvoid acceptIfJvmClass() {\n\t\tint[] usage = new int[1];\n\t\taccessibleFields.acceptIfJvmClass(cls -> usage[0]++);\n\t\tassertEquals(1, usage[0], \"Accept consumer was not used\");\n\t}\n\n\t@Test\n\tvoid acceptIfAndroidClass() {\n\t\tint[] usage = new int[1];\n\t\taccessibleFields.acceptIfAndroidClass(cls -> usage[0]++);\n\t\tassertEquals(0, usage[0], \"Accept consumer was used when it should not have been\");\n\t}\n\n\t@Test\n\tvoid testIfJvmClass() {\n\t\tassertTrue(accessibleFields.testIfJvmClass(cls -> true),\n\t\t\t\t\"Predicate<JVM> should have been called\");\n\t}\n\n\t@Test\n\tvoid testIfAndroidClass() {\n\t\tassertFalse(accessibleFields.testIfAndroidClass(cls -> true),\n\t\t\t\t\"Predicate<Android> should not have been called\");\n\t}\n\n\t@Test\n\tvoid asJvmClass() {\n\t\tassertDoesNotThrow(() -> {\n\t\t\tassertEquals(accessibleFields, accessibleFields.asJvmClass(), \"JVM.asJvm() should yield self\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid asAndroidClass() {\n\t\tassertThrows(Throwable.class, () -> accessibleFields.asAndroidClass(), \"JVM.asAndroid() should fail\");\n\t}\n\n\t@Test\n\tvoid isJvmClass() {\n\t\tassertTrue(accessibleFields.isJvmClass());\n\t}\n\n\t@Test\n\tvoid isAndroidClass() {\n\t\tassertFalse(accessibleFields.isAndroidClass());\n\t}\n\n\t@Test\n\tvoid toBuilder() {\n\t\t// Direct copy\n\t\tJvmClassInfo builderDirectCopy = accessibleFields.toJvmClassBuilder()\n\t\t\t\t.build();\n\t\tassertEquals(accessibleFields, builderDirectCopy,\n\t\t\t\t\"Direct copy via builder should have same class equality\");\n\n\t\t// With modification\n\t\tbyte[] modifiedBytecode = Arrays.copyOf(accessibleFields.getBytecode(), accessibleFields.getBytecode().length);\n\t\tmodifiedBytecode[5] = 1; // Change minor version to any non-zero value\n\t\tJvmClassInfo builderModifiedCopy = accessibleFields.toJvmClassBuilder()\n\t\t\t\t.withBytecode(modifiedBytecode)\n\t\t\t\t.build();\n\t\tassertNotEquals(accessibleFields, builderModifiedCopy,\n\t\t\t\t\"Direct copy via builder should have same class equality\");\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/info/annotation/AnnotatedTest.java",
    "content": "package software.coley.recaf.info.annotation;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.AnnotationImpl;\nimport software.coley.recaf.test.dummy.ClassWithAnnotation;\nimport software.coley.recaf.test.dummy.TypeAnnotationImpl;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests for {@link Annotated}\n */\nclass AnnotatedTest {\n\tstatic JvmClassInfo classWithAnnotation;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\tclassWithAnnotation = TestClassUtils.fromRuntimeClass(ClassWithAnnotation.class);\n\t}\n\n\t@Test\n\tvoid getAnnotations() {\n\t\tList<AnnotationInfo> annotations = classWithAnnotation.getAnnotations();\n\t\tassertEquals(1, annotations.size(), \"Should declare one standard annotation\");\n\t\tassertEquals(Type.getDescriptor(AnnotationImpl.class), annotations.get(0).getDescriptor());\n\t}\n\n\t@Test\n\tvoid getTypeAnnotations() {\n\t\tList<TypeAnnotationInfo> typeAnnotations = classWithAnnotation.getTypeAnnotations();\n\t\tassertEquals(1, typeAnnotations.size(), \"Should declare one type annotation\");\n\t\tassertEquals(Type.getDescriptor(TypeAnnotationImpl.class), typeAnnotations.get(0).getDescriptor());\n\t}\n\n\t@Test\n\tvoid annotationStream() {\n\t\t// Mirror results of prior test\n\t\tassertEquals(1, classWithAnnotation.annotationStream().count());\n\t}\n\n\t@Test\n\tvoid typeAnnotationStream() {\n\t\t// Mirror results of prior test\n\t\tassertEquals(1, classWithAnnotation.typeAnnotationStream().count());\n\t}\n\n\t@Test\n\tvoid allAnnotationsStream() {\n\t\t// Class has one standard annotation, one type annotation\n\t\tassertEquals(2, classWithAnnotation.allAnnotationsStream().count());\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/info/annotation/AnnotationInfoTest.java",
    "content": "package software.coley.recaf.info.annotation;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.AnnotationImpl;\nimport software.coley.recaf.test.dummy.ClassWithAnnotation;\nimport software.coley.recaf.test.dummy.ClassWithInvisAnnotation;\nimport software.coley.recaf.test.dummy.InvisAnnotationImpl;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link AnnotationInfo}\n */\nclass AnnotationInfoTest {\n\tstatic JvmClassInfo classWithAnnotation;\n\tstatic JvmClassInfo classWithInvisAnnotation;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\tclassWithAnnotation = TestClassUtils.fromRuntimeClass(ClassWithAnnotation.class);\n\t\tclassWithInvisAnnotation = TestClassUtils.fromRuntimeClass(ClassWithInvisAnnotation.class);\n\t}\n\n\t@Test\n\tvoid isVisible() {\n\t\tassertTrue(classWithAnnotation.getAnnotations().get(0).isVisible());\n\t\tassertFalse(classWithInvisAnnotation.getAnnotations().get(0).isVisible());\n\t}\n\n\t@Test\n\tvoid getDescriptor() {\n\t\tassertEquals(Type.getDescriptor(AnnotationImpl.class),\n\t\t\t\tclassWithAnnotation.getAnnotations().get(0).getDescriptor());\n\t\tassertEquals(Type.getDescriptor(InvisAnnotationImpl.class),\n\t\t\t\tclassWithInvisAnnotation.getAnnotations().get(0).getDescriptor());\n\t}\n\n\t@Test\n\tvoid getElements() {\n\t\t// Empty\n\t\tassertEquals(Collections.emptyMap(),\n\t\t\t\tclassWithInvisAnnotation.getAnnotations().get(0).getElements());\n\n\t\t// Some values\n\t\tMap<String, AnnotationElement> elements = classWithAnnotation.getAnnotations().get(0).getElements();\n\t\tassertEquals(2, elements.size());\n\n\t\t// Basic string\n\t\tAnnotationElement valueElement = elements.get(\"value\");\n\t\tassertEquals(\"value\", valueElement.getElementName());\n\t\tassertEquals(\"Hello\", valueElement.getElementValue());\n\n\t\t// Another annotation\n\t\tAnnotationElement policyElement = elements.get(\"policy\");\n\t\tassertEquals(\"policy\", policyElement.getElementName());\n\t\tObject policyValue = policyElement.getElementValue();\n\t\tif (policyValue instanceof AnnotationInfo policyInfo) {\n\t\t\tassertEquals(1, policyInfo.getElements().size());\n\t\t} else {\n\t\t\tfail(\"Annotation element of another annotation should yield embedded 'AnnotationInfo'\");\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/info/annotation/TypeAnnotationInfoTest.java",
    "content": "package software.coley.recaf.info.annotation;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.TypeReference;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.ClassWithAnnotation;\n\nimport java.io.IOException;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\n/**\n * Tests for {@link TypeAnnotationInfo}\n */\nclass TypeAnnotationInfoTest {\n\tstatic JvmClassInfo classWithAnnotation;\n\tstatic TypeAnnotationInfo typeAnnoOnTypeArgument;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\tclassWithAnnotation = TestClassUtils.fromRuntimeClass(ClassWithAnnotation.class);\n\t\t// Has only one type-anno on <T> arg.\n\t\ttypeAnnoOnTypeArgument = classWithAnnotation.getTypeAnnotations().get(0);\n\t}\n\n\t@Test\n\tvoid getTypeRef() {\n\t\tassertEquals(TypeReference.CLASS_TYPE_PARAMETER, typeAnnoOnTypeArgument.getTypeRef());\n\t}\n\n\t@Test\n\tvoid getTypePath() {\n\t\tassertNull(typeAnnoOnTypeArgument.getTypePath());\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/info/member/FieldMemberTest.java",
    "content": "package software.coley.recaf.info.member;\n\nimport jakarta.annotation.Nullable;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.AccessibleFields;\nimport software.coley.recaf.test.dummy.ClassWithInner;\nimport software.coley.recaf.test.dummy.VariedModifierFields;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static java.lang.reflect.Modifier.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link FieldMember}\n */\nclass FieldMemberTest {\n\tstatic JvmClassInfo accessibleFields;\n\tstatic FieldMember acc_CONSTANT_FIELD;\n\tstatic FieldMember acc_privateFinalField;\n\tstatic FieldMember acc_protectedField;\n\tstatic FieldMember acc_publicField;\n\tstatic FieldMember acc_packageField;\n\tstatic List<FieldMember> acc_fields;\n\tstatic JvmClassInfo variousFields;\n\tstatic FieldMember var_staticField;\n\tstatic FieldMember var_volatileField;\n\tstatic FieldMember var_transientField;\n\tstatic JvmClassInfo classWithInner$Inner;\n\t@Nullable\n\tstatic FieldMember inner_outerRefField;\n\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\taccessibleFields = TestClassUtils.fromRuntimeClass(AccessibleFields.class);\n\t\tacc_CONSTANT_FIELD = accessibleFields.getDeclaredField(\"CONSTANT_FIELD\", \"I\");\n\t\tacc_privateFinalField = accessibleFields.getDeclaredField(\"privateFinalField\", \"I\");\n\t\tacc_protectedField = accessibleFields.getDeclaredField(\"protectedField\", \"I\");\n\t\tacc_publicField = accessibleFields.getDeclaredField(\"publicField\", \"I\");\n\t\tacc_packageField = accessibleFields.getDeclaredField(\"packageField\", \"I\");\n\t\tacc_fields = List.of(acc_CONSTANT_FIELD, acc_privateFinalField, acc_protectedField, acc_publicField, acc_packageField);\n\n\t\tvariousFields = TestClassUtils.fromRuntimeClass(VariedModifierFields.class);\n\t\tvar_staticField = variousFields.getDeclaredField(\"staticField\", \"I\");\n\t\tvar_volatileField = variousFields.getDeclaredField(\"volatileField\", \"I\");\n\t\tvar_transientField = variousFields.getDeclaredField(\"transientField\", \"I\");\n\n\t\tclassWithInner$Inner = TestClassUtils.fromRuntimeClass(ClassWithInner.TheInner.class);\n\t\tif (!classWithInner$Inner.getFields().isEmpty()) {\n\t\t\tinner_outerRefField = classWithInner$Inner.getFields().get(0);\n\t\t}\n\t}\n\n\t@Test\n\tvoid getDeclaringClass() {\n\t\t// When a class-info is created all of its fields should be made aware of the declaring class\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertEquals(accessibleFields, field.getDeclaringClass(), \"Field not linked with declaring class\");\n\t\t}\n\t}\n\n\t@Test\n\tvoid isDeclarationAware() {\n\t\t// When a class-info is created all of its fields should be made aware of the declaring class\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertTrue(field.isDeclarationAware(), \"Field not linked with declaring class\");\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasPublicModifier() {\n\t\tassertTrue(acc_CONSTANT_FIELD.hasPublicModifier());\n\t\tassertFalse(acc_privateFinalField.hasPublicModifier());\n\t\tassertFalse(acc_protectedField.hasPublicModifier());\n\t\tassertTrue(acc_publicField.hasPublicModifier());\n\t\tassertFalse(acc_packageField.hasPublicModifier());\n\t}\n\n\t@Test\n\tvoid hasProtectedModifier() {\n\t\tassertFalse(acc_CONSTANT_FIELD.hasProtectedModifier());\n\t\tassertFalse(acc_privateFinalField.hasProtectedModifier());\n\t\tassertTrue(acc_protectedField.hasProtectedModifier());\n\t\tassertFalse(acc_publicField.hasProtectedModifier());\n\t\tassertFalse(acc_packageField.hasProtectedModifier());\n\t}\n\n\t@Test\n\tvoid hasPrivateModifier() {\n\t\tassertFalse(acc_CONSTANT_FIELD.hasPrivateModifier());\n\t\tassertTrue(acc_privateFinalField.hasPrivateModifier());\n\t\tassertFalse(acc_protectedField.hasPrivateModifier());\n\t\tassertFalse(acc_publicField.hasPrivateModifier());\n\t\tassertFalse(acc_packageField.hasPrivateModifier());\n\t}\n\n\t@Test\n\tvoid hasPackagePrivateModifier() {\n\t\tassertFalse(acc_CONSTANT_FIELD.hasPackagePrivateModifier());\n\t\tassertFalse(acc_privateFinalField.hasPackagePrivateModifier());\n\t\tassertFalse(acc_protectedField.hasPackagePrivateModifier());\n\t\tassertFalse(acc_publicField.hasPackagePrivateModifier());\n\t\tassertTrue(acc_packageField.hasPackagePrivateModifier());\n\t}\n\n\t@Test\n\tvoid hasStaticModifier() {\n\t\tassertTrue(acc_CONSTANT_FIELD.hasStaticModifier());\n\t\tassertFalse(acc_privateFinalField.hasStaticModifier());\n\t\tassertFalse(acc_protectedField.hasStaticModifier());\n\t\tassertFalse(acc_publicField.hasStaticModifier());\n\t\tassertFalse(acc_packageField.hasStaticModifier());\n\t}\n\n\t@Test\n\tvoid hasFinalModifier() {\n\t\tassertTrue(acc_CONSTANT_FIELD.hasFinalModifier());\n\t\tassertTrue(acc_privateFinalField.hasFinalModifier());\n\t\tassertTrue(acc_protectedField.hasFinalModifier());\n\t\tassertTrue(acc_publicField.hasFinalModifier());\n\t\tassertTrue(acc_packageField.hasFinalModifier());\n\t}\n\n\t@Test\n\tvoid hasSynchronizedModifier() {\n\t\t// Not allowed on fields\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertFalse(field.hasSynchronizedModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasVolatileModifier() {\n\t\tassertFalse(var_staticField.hasVolatileModifier());\n\t\tassertTrue(var_volatileField.hasVolatileModifier());\n\t\tassertFalse(var_transientField.hasVolatileModifier());\n\t}\n\n\t@Test\n\tvoid hasTransientModifier() {\n\t\tassertFalse(var_staticField.hasTransientModifier());\n\t\tassertFalse(var_volatileField.hasTransientModifier());\n\t\tassertTrue(var_transientField.hasTransientModifier());\n\t}\n\n\t@Test\n\tvoid hasNativeModifier() {\n\t\t// Not allowed on fields\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertFalse(field.hasNativeModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasEnumModifier() {\n\t\t// Not allowed on fields\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertFalse(field.hasEnumModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasAnnotationModifier() {\n\t\t// Not allowed on fields\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertFalse(field.hasAnnotationModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasInterfaceModifier() {\n\t\t// Not allowed on fields\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertFalse(field.hasInterfaceModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasAbstractModifier() {\n\t\t// Not allowed on fields\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertFalse(field.hasAbstractModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasStrictFpModifier() {\n\t\t// Not allowed on fields\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertFalse(field.hasStrictFpModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasVarargsModifier() {\n\t\t// Not allowed on fields\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertFalse(field.hasVarargsModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasBridgeModifier() {\n\t\t// Bridge is for methods, only similar mod is synthetic, but they are not the same.\n\t\tif (inner_outerRefField != null) {\n\t\t\tassertFalse(inner_outerRefField.hasBridgeModifier());\n\t\t}\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertFalse(field.hasBridgeModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasSyntheticModifier() {\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertFalse(field.hasSyntheticModifier());\n\t\t}\n\t\tif (inner_outerRefField != null) {\n\t\t\tassertTrue(inner_outerRefField.hasSyntheticModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid isCompilerGenerated() {\n\t\t// Mirror prior test results\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertFalse(field.isCompilerGenerated());\n\t\t}\n\t\tif (inner_outerRefField != null) {\n\t\t\tassertTrue(inner_outerRefField.isCompilerGenerated());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasModifierMask() {\n\t\tassertFalse(acc_publicField.hasModifierMask(PRIVATE | PROTECTED));\n\t\tassertTrue(acc_publicField.hasModifierMask(PUBLIC));\n\t}\n\n\t@Test\n\tvoid hasAllModifiers() {\n\t\t// True with no-args\n\t\tassertTrue(acc_publicField.hasAllModifiers());\n\n\t\t// Field is both public/final\n\t\tassertTrue(acc_publicField.hasAllModifiers(FINAL));\n\t\tassertTrue(acc_publicField.hasAllModifiers(FINAL, PUBLIC));\n\t}\n\n\t@Test\n\tvoid hasAnyModifiers() {\n\t\t// False with no-args\n\t\tassertFalse(acc_publicField.hasAnyModifiers());\n\n\t\t// Field is both public/final\n\t\tassertTrue(acc_publicField.hasAnyModifiers(FINAL));\n\t\tassertTrue(acc_publicField.hasAnyModifiers(FINAL, PUBLIC));\n\t\tassertTrue(acc_publicField.hasAnyModifiers(PUBLIC));\n\t}\n\n\t@Test\n\tvoid hasNoneOfMask() {\n\t\t// Field is only public/final\n\t\tassertFalse(acc_publicField.hasNoneOfMask(FINAL));\n\t\tassertFalse(acc_publicField.hasNoneOfMask(FINAL | PUBLIC));\n\t\tassertFalse(acc_publicField.hasNoneOfMask(PUBLIC));\n\t\tassertTrue(acc_publicField.hasNoneOfMask(STATIC | ABSTRACT | VOLATILE));\n\t\tassertTrue(acc_publicField.hasNoneOfMask(STATIC));\n\t}\n\n\t@Test\n\tvoid hasNoneOfModifiers() {\n\t\t// True with no-args\n\t\tassertTrue(acc_publicField.hasNoneOfModifiers());\n\n\t\t// Mirror results of prior test\n\t\tassertFalse(acc_publicField.hasNoneOfModifiers(FINAL));\n\t\tassertFalse(acc_publicField.hasNoneOfModifiers(FINAL, PUBLIC));\n\t\tassertFalse(acc_publicField.hasNoneOfModifiers(PUBLIC));\n\t\tassertTrue(acc_publicField.hasNoneOfModifiers(STATIC, ABSTRACT, VOLATILE));\n\t\tassertTrue(acc_publicField.hasNoneOfModifiers(STATIC));\n\t}\n\n\t@Test\n\tvoid getDefaultValue() {\n\t\tassertEquals(16, acc_CONSTANT_FIELD.getDefaultValue());\n\t\tassertEquals(8, acc_privateFinalField.getDefaultValue());\n\t\tassertEquals(4, acc_protectedField.getDefaultValue());\n\t\tassertEquals(2, acc_publicField.getDefaultValue());\n\t\tassertEquals(1, acc_packageField.getDefaultValue());\n\t}\n\n\t@Test\n\tvoid isField() {\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertTrue(field.isField());\n\t\t}\n\t}\n\n\t@Test\n\tvoid isMethod() {\n\t\tfor (FieldMember field : acc_fields) {\n\t\t\tassertFalse(field.isMethod());\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/info/member/MethodMemberTest.java",
    "content": "package software.coley.recaf.info.member;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.*;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\n\nimport static java.lang.reflect.Modifier.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link MethodMember}\n */\nclass MethodMemberTest {\n\tstatic JvmClassInfo arrayList;\n\tstatic JvmClassInfo annotationImpl;\n\tstatic JvmClassInfo accessibleMethods;\n\tstatic MethodMember acc_publicMethod;\n\tstatic MethodMember acc_privateMethod;\n\tstatic MethodMember acc_protectedMethod;\n\tstatic MethodMember acc_packageMethod;\n\tstatic JvmClassInfo variedMethods;\n\tstatic MethodMember var_staticMethod;\n\tstatic MethodMember var_finalMethod;\n\tstatic MethodMember var_synchronizedMethod;\n\tstatic MethodMember var_nativeMethod;\n\tstatic MethodMember var_abstractMethod;\n\tstatic MethodMember var_varargsMethod;\n\tstatic JvmClassInfo classWithLambda;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\tarrayList = TestClassUtils.fromRuntimeClass(ArrayList.class);\n\t\tannotationImpl = TestClassUtils.fromRuntimeClass(AnnotationImpl.class);\n\t\tannotationImpl = TestClassUtils.fromRuntimeClass(ClassWithAnonymousInner.class);\n\n\t\taccessibleMethods = TestClassUtils.fromRuntimeClass(AccessibleMethods.class);\n\t\tacc_publicMethod = accessibleMethods.getDeclaredMethod(\"publicMethod\", \"()V\");\n\t\tacc_privateMethod = accessibleMethods.getDeclaredMethod(\"privateMethod\", \"()V\");\n\t\tacc_protectedMethod = accessibleMethods.getDeclaredMethod(\"protectedMethod\", \"()V\");\n\t\tacc_packageMethod = accessibleMethods.getDeclaredMethod(\"packageMethod\", \"()V\");\n\n\t\tvariedMethods = TestClassUtils.fromRuntimeClass(VariedModifierMethods.class);\n\t\tvar_staticMethod = variedMethods.getDeclaredMethod(\"staticMethod\", \"()V\");\n\t\tvar_finalMethod = variedMethods.getDeclaredMethod(\"finalMethod\", \"()V\");\n\t\tvar_synchronizedMethod = variedMethods.getDeclaredMethod(\"synchronizedMethod\", \"()V\");\n\t\tvar_nativeMethod = variedMethods.getDeclaredMethod(\"nativeMethod\", \"()V\");\n\t\tvar_abstractMethod = variedMethods.getDeclaredMethod(\"abstractMethod\", \"()V\");\n\t\tvar_varargsMethod = variedMethods.getDeclaredMethod(\"varargsMethod\", \"([Ljava/lang/String;)V\");\n\n\t\tclassWithLambda = TestClassUtils.fromRuntimeClass(ClassWithLambda.class);\n\t}\n\n\t@Test\n\tvoid getDeclaringClass() {\n\t\t// When a class-info is created all of its fields should be made aware of the declaring class\n\t\tfor (MethodMember method : arrayList.getMethods()) {\n\t\t\tassertEquals(arrayList, method.getDeclaringClass(), \"Method not linked with declaring class\");\n\t\t}\n\t}\n\n\t@Test\n\tvoid isDeclarationAware() {\n\t\t// When a class-info is created all of its fields should be made aware of the declaring class\n\t\tfor (MethodMember method : arrayList.getMethods()) {\n\t\t\tassertTrue(method.isDeclarationAware(), \"Method not linked with declaring class\");\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasPublicModifier() {\n\t\tassertTrue(acc_publicMethod.hasPublicModifier());\n\t\tassertFalse(acc_privateMethod.hasPublicModifier());\n\t\tassertFalse(acc_protectedMethod.hasPublicModifier());\n\t\tassertFalse(acc_packageMethod.hasPublicModifier());\n\t}\n\n\t@Test\n\tvoid hasProtectedModifier() {\n\t\tassertFalse(acc_publicMethod.hasProtectedModifier());\n\t\tassertFalse(acc_privateMethod.hasProtectedModifier());\n\t\tassertTrue(acc_protectedMethod.hasProtectedModifier());\n\t\tassertFalse(acc_packageMethod.hasProtectedModifier());\n\t}\n\n\t@Test\n\tvoid hasPrivateModifier() {\n\t\tassertFalse(acc_publicMethod.hasPrivateModifier());\n\t\tassertTrue(acc_privateMethod.hasPrivateModifier());\n\t\tassertFalse(acc_protectedMethod.hasPrivateModifier());\n\t\tassertFalse(acc_packageMethod.hasPrivateModifier());\n\t}\n\n\t@Test\n\tvoid hasPackagePrivateModifier() {\n\t\tassertFalse(acc_publicMethod.hasPackagePrivateModifier());\n\t\tassertFalse(acc_privateMethod.hasPackagePrivateModifier());\n\t\tassertFalse(acc_protectedMethod.hasPackagePrivateModifier());\n\t\tassertTrue(acc_packageMethod.hasPackagePrivateModifier());\n\t}\n\n\t@Test\n\tvoid hasStaticModifier() {\n\t\tassertTrue(var_staticMethod.hasStaticModifier());\n\t\tassertFalse(var_finalMethod.hasStaticModifier());\n\t\tassertFalse(var_synchronizedMethod.hasStaticModifier());\n\t\tassertFalse(var_nativeMethod.hasStaticModifier());\n\t\tassertFalse(var_abstractMethod.hasStaticModifier());\n\t\tassertFalse(var_varargsMethod.hasStaticModifier());\n\t}\n\n\t@Test\n\tvoid hasFinalModifier() {\n\t\tassertFalse(var_staticMethod.hasFinalModifier());\n\t\tassertTrue(var_finalMethod.hasFinalModifier());\n\t\tassertFalse(var_synchronizedMethod.hasFinalModifier());\n\t\tassertFalse(var_nativeMethod.hasFinalModifier());\n\t\tassertFalse(var_abstractMethod.hasFinalModifier());\n\t\tassertFalse(var_varargsMethod.hasFinalModifier());\n\t}\n\n\t@Test\n\tvoid hasSynchronizedModifier() {\n\t\tassertFalse(var_staticMethod.hasSynchronizedModifier());\n\t\tassertFalse(var_finalMethod.hasSynchronizedModifier());\n\t\tassertTrue(var_synchronizedMethod.hasSynchronizedModifier());\n\t\tassertFalse(var_nativeMethod.hasSynchronizedModifier());\n\t\tassertFalse(var_abstractMethod.hasSynchronizedModifier());\n\t\tassertFalse(var_varargsMethod.hasSynchronizedModifier());\n\t}\n\n\t@Test\n\tvoid hasVolatileModifier() {\n\t\t// Not allowed on methods\n\t\tfor (MethodMember method : arrayList.getMethods()) {\n\t\t\tassertFalse(method.hasVolatileModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasTransientModifier() {\n\t\t// Not allowed on methods\n\t\tfor (MethodMember method : arrayList.getMethods()) {\n\t\t\tassertFalse(method.hasTransientModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasNativeModifier() {\n\t\tassertFalse(var_staticMethod.hasNativeModifier());\n\t\tassertFalse(var_finalMethod.hasNativeModifier());\n\t\tassertFalse(var_synchronizedMethod.hasNativeModifier());\n\t\tassertTrue(var_nativeMethod.hasNativeModifier());\n\t\tassertFalse(var_abstractMethod.hasNativeModifier());\n\t\tassertFalse(var_varargsMethod.hasNativeModifier());\n\t}\n\n\t@Test\n\tvoid hasEnumModifier() {\n\t\t// Not allowed on methods\n\t\tfor (MethodMember method : arrayList.getMethods()) {\n\t\t\tassertFalse(method.hasEnumModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasAnnotationModifier() {\n\t\t// Not allowed on methods\n\t\tfor (MethodMember method : arrayList.getMethods()) {\n\t\t\tassertFalse(method.hasEnumModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasInterfaceModifier() {\n\t\t// Not allowed on methods\n\t\tfor (MethodMember method : arrayList.getMethods()) {\n\t\t\tassertFalse(method.hasEnumModifier());\n\t\t}\n\t}\n\n\t@Test\n\tvoid hasAbstractModifier() {\n\t\tassertFalse(var_staticMethod.hasAbstractModifier());\n\t\tassertFalse(var_finalMethod.hasAbstractModifier());\n\t\tassertFalse(var_synchronizedMethod.hasAbstractModifier());\n\t\tassertFalse(var_nativeMethod.hasAbstractModifier());\n\t\tassertTrue(var_abstractMethod.hasAbstractModifier());\n\t\tassertFalse(var_varargsMethod.hasAbstractModifier());\n\t}\n\n\t@Test\n\tvoid hasStrictFpModifier() {\n\t\t// There are no true asserts because the runtime class compiles with the project's Java version.\n\t\t// Since Java 17 'strictfp' is no longer emitted in bytecode since all operations are now strict.\n\t\tassertFalse(var_staticMethod.hasStrictFpModifier());\n\t\tassertFalse(var_finalMethod.hasStrictFpModifier());\n\t\tassertFalse(var_synchronizedMethod.hasStrictFpModifier());\n\t\tassertFalse(var_nativeMethod.hasStrictFpModifier());\n\t\tassertFalse(var_abstractMethod.hasStrictFpModifier());\n\t\tassertFalse(var_varargsMethod.hasStrictFpModifier());\n\t}\n\n\t@Test\n\tvoid hasVarargsModifier() {\n\t\tassertFalse(var_staticMethod.hasVarargsModifier());\n\t\tassertFalse(var_finalMethod.hasVarargsModifier());\n\t\tassertFalse(var_synchronizedMethod.hasVarargsModifier());\n\t\tassertFalse(var_nativeMethod.hasVarargsModifier());\n\t\tassertFalse(var_abstractMethod.hasVarargsModifier());\n\t\tassertTrue(var_varargsMethod.hasVarargsModifier());\n\t}\n\n\t@Test\n\t@Disabled(\"Bridge methods do not generate in Java 11+\")\n\tvoid hasBridgeModifier() {\n\t\t// Lambda generates synthetic methods, but they are not marked as bridge\n\t\tfor (MethodMember method : classWithLambda.getMethods()) {\n\t\t\tassertFalse(method.hasBridgeModifier());\n\t\t}\n\n\t\t// need to find an existing class with bridge example\n\t}\n\n\t@Test\n\tvoid hasSyntheticModifier() {\n\t\tint lambda = 0;\n\t\tfor (MethodMember method : classWithLambda.getMethods()) {\n\t\t\tif (method.getName().startsWith(\"lambda$\")) {\n\t\t\t\tlambda++;\n\t\t\t\tassertTrue(method.hasSyntheticModifier());\n\t\t\t} else {\n\t\t\t\tassertFalse(method.hasSyntheticModifier());\n\t\t\t}\n\t\t}\n\t\tassertEquals(3, lambda, \"ClassWithLambda defines 3 lambdas\");\n\t}\n\n\t@Test\n\tvoid isCompilerGenerated() {\n\t\tint lambda = 0;\n\t\tfor (MethodMember method : classWithLambda.getMethods()) {\n\t\t\tif (method.getName().startsWith(\"lambda$\")) {\n\t\t\t\tlambda++;\n\t\t\t\tassertTrue(method.isCompilerGenerated());\n\t\t\t} else {\n\t\t\t\tassertFalse(method.isCompilerGenerated());\n\t\t\t}\n\t\t}\n\t\tassertEquals(3, lambda, \"ClassWithLambda defines 3 lambdas\");\n\t}\n\n\t@Test\n\tvoid hasModifierMask() {\n\t\tassertTrue(var_staticMethod.hasModifierMask(STATIC));\n\t\tassertFalse(var_staticMethod.hasModifierMask(STATIC | ABSTRACT));\n\t}\n\n\t@Test\n\tvoid hasAllModifiers() {\n\t\t// True with no-args\n\t\tassertTrue(acc_publicMethod.hasAllModifiers());\n\n\t\t// Method is only public\n\t\tassertFalse(acc_publicMethod.hasAllModifiers(PRIVATE, STATIC));\n\t\tassertFalse(acc_publicMethod.hasAllModifiers(PRIVATE));\n\t\tassertTrue(acc_publicMethod.hasAllModifiers(PUBLIC));\n\t}\n\n\t@Test\n\tvoid hasAnyModifiers() {\n\t\t// False with no-args\n\t\tassertFalse(acc_publicMethod.hasAnyModifiers());\n\n\t\t// Method is only public\n\t\tassertFalse(acc_publicMethod.hasAnyModifiers(STATIC));\n\t\tassertTrue(acc_publicMethod.hasAnyModifiers(STATIC, PUBLIC));\n\t\tassertTrue(acc_publicMethod.hasAnyModifiers(PUBLIC));\n\t}\n\n\t@Test\n\tvoid hasNoneOfMask() {\n\t\t// Method is only public\n\t\tassertFalse(acc_publicMethod.hasNoneOfMask(FINAL | PUBLIC));\n\t\tassertFalse(acc_publicMethod.hasNoneOfMask(PUBLIC));\n\t\tassertTrue(acc_publicMethod.hasNoneOfMask(FINAL));\n\t\tassertTrue(acc_publicMethod.hasNoneOfMask(STATIC | ABSTRACT | VOLATILE));\n\t\tassertTrue(acc_publicMethod.hasNoneOfMask(STATIC));\n\t}\n\n\t@Test\n\tvoid hasNoneOfModifiers() {\n\t\t// True with no-args\n\t\tassertTrue(acc_publicMethod.hasNoneOfModifiers());\n\n\t\t// Method is only public\n\t\tassertFalse(acc_publicMethod.hasNoneOfModifiers(FINAL, PUBLIC));\n\t\tassertFalse(acc_publicMethod.hasNoneOfModifiers(PUBLIC));\n\t\tassertTrue(acc_publicMethod.hasNoneOfModifiers(FINAL));\n\t\tassertTrue(acc_publicMethod.hasNoneOfModifiers(STATIC, ABSTRACT, VOLATILE));\n\t\tassertTrue(acc_publicMethod.hasNoneOfModifiers(STATIC));\n\t}\n\n\t@Test\n\tvoid isField() {\n\t\tfor (MethodMember method : arrayList.getMethods()) {\n\t\t\tassertFalse(method.isField());\n\t\t}\n\t}\n\n\t@Test\n\tvoid isMethod() {\n\t\tfor (MethodMember method : arrayList.getMethods()) {\n\t\t\tassertTrue(method.isMethod());\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/path/PathNodeTest.java",
    "content": "package software.coley.recaf.path;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.info.Accessed;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.StubClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.builder.AndroidClassInfoBuilder;\nimport software.coley.recaf.info.builder.TextFileInfoBuilder;\nimport software.coley.recaf.test.dummy.ClassWithLambda;\nimport software.coley.recaf.test.dummy.StringConsumerUser;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicAndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicFileBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicVersionedJvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NavigableMap;\nimport java.util.Objects;\nimport java.util.TreeMap;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatComparable;\nimport static software.coley.recaf.test.TestClassUtils.*;\n\n/**\n * Tests for {@link PathNode}.\n */\nclass PathNodeTest {\n\tstatic Workspace workspace;\n\tstatic WorkspaceResource primaryResource;\n\tstatic WorkspaceResource secondaryResource;\n\tstatic JvmClassBundle primaryJvmBundle;\n\tstatic AndroidClassBundle primaryAndroidBundle;\n\tstatic JvmClassBundle secondaryJvmBundle;\n\tstatic FileInfo primaryFileInfo;\n\tstatic FileBundle primaryFileBundle;\n\tstatic JvmClassInfo primaryClassInfo;\n\tstatic JvmClassInfo secondaryClassInfo;\n\tstatic AndroidClassInfo primaryAndroidClassInfo;\n\t// paths\n\tstatic ClassMemberPathNode p1field;\n\tstatic ClassMemberPathNode p1method;\n\tstatic ClassPathNode p1;\n\tstatic FilePathNode pFile1;\n\tstatic FilePathNode pFile2;\n\tstatic DirectoryPathNode p2;\n\tstatic DirectoryPathNode p2parent;\n\tstatic BundlePathNode p3;\n\tstatic BundlePathNode p3and1;\n\tstatic BundlePathNode p3and2;\n\tstatic BundlePathNode p3file;\n\tstatic ResourcePathNode p4;\n\tstatic WorkspacePathNode p5;\n\tstatic ClassPathNode s1;\n\tstatic DirectoryPathNode s2;\n\tstatic BundlePathNode s3;\n\tstatic ResourcePathNode s4;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\tprimaryClassInfo = fromRuntimeClass(ClassWithLambda.class);\n\t\tprimaryJvmBundle = fromClasses(primaryClassInfo);\n\t\tprimaryFileBundle = new BasicFileBundle();\n\t\tprimaryFileInfo = new TextFileInfoBuilder().withName(\"foo.txt\").withRawContent(\"foo\".getBytes()).build();\n\t\tprimaryFileBundle.put(primaryFileInfo);\n\t\tprimaryAndroidClassInfo = new AndroidClassInfoBuilder().withName(\"foo/HelloWorld\").build();\n\t\tprimaryAndroidBundle = new BasicAndroidClassBundle();\n\t\tBasicAndroidClassBundle secondaryAndroidBundle = new BasicAndroidClassBundle();\n\t\tprimaryAndroidBundle.put(primaryAndroidClassInfo);\n\t\tMap<String, AndroidClassBundle> androidClassBundles = new HashMap<>();\n\t\tandroidClassBundles.put(\"classes.dex\", primaryAndroidBundle);\n\t\tandroidClassBundles.put(\"others.dex\", secondaryAndroidBundle);\n\t\tprimaryResource = new WorkspaceResourceBuilder()\n\t\t\t\t.withJvmClassBundle(primaryJvmBundle)\n\t\t\t\t.withFileBundle(primaryFileBundle)\n\t\t\t\t.withAndroidClassBundles(androidClassBundles)\n\t\t\t\t.build();\n\n\t\tFileInfo secondaryFileInfo = new TextFileInfoBuilder().withName(\"bar.txt\").withRawContent(\"bar\".getBytes()).build();\n\t\tsecondaryClassInfo = fromRuntimeClass(StringConsumerUser.class);\n\t\tsecondaryJvmBundle = fromClasses(secondaryClassInfo);\n\t\tsecondaryResource = new WorkspaceResourceBuilder()\n\t\t\t\t.withJvmClassBundle(secondaryJvmBundle)\n\t\t\t\t.build();\n\n\t\tworkspace = new BasicWorkspace(primaryResource, List.of(secondaryResource));\n\n\t\tString packageName = Objects.requireNonNull(primaryClassInfo.getPackageName());\n\t\tString parentPackageName = packageName.substring(0, packageName.lastIndexOf('/'));\n\n\t\tp5 = PathNodes.workspacePath(workspace);\n\t\tp4 = p5.child(primaryResource);\n\t\tp3 = p4.child(primaryJvmBundle);\n\t\tp3and1 = p4.child(primaryAndroidBundle);\n\t\tp3and2 = p4.child(secondaryAndroidBundle);\n\t\tp3file = p4.child(primaryFileBundle);\n\t\tpFile1 = p3file.child(null).child(primaryFileInfo);\n\t\tpFile2 = p3file.child(null).child(secondaryFileInfo);\n\t\tp2 = p3.child(packageName);\n\t\tp2parent = p3.child(parentPackageName);\n\t\tp1 = p2.child(primaryClassInfo);\n\t\tp1field = p1.child(Objects.requireNonNull(primaryClassInfo.getFirstDeclaredFieldByName(\"map\")));\n\t\tp1method = p1.child(Objects.requireNonNull(primaryClassInfo.getFirstDeclaredMethodByName(\"runnable\")));\n\n\t\ts4 = p5.child(secondaryResource);\n\t\ts3 = s4.child(secondaryJvmBundle);\n\t\ts2 = s3.child(packageName);\n\t\ts1 = s2.child(secondaryClassInfo);\n\t}\n\n\t@Nested\n\tclass Value {\n\t\t@Test\n\t\tvoid getValueOfTypeForParentTypes() {\n\t\t\t// We should be able to get the current value by any of its interfaces.\n\t\t\tClassInfo v1 = p1.getValueOfType(ClassInfo.class);\n\t\t\tAccessed v2 = p1.getValueOfType(Accessed.class);\n\t\t\tAnnotated v3 = p1.getValueOfType(Annotated.class);\n\t\t\tassertThat(v2).isSameAs(v1);\n\t\t\tassertThat(v3).isSameAs(v2);\n\t\t}\n\n\t\t@Test\n\t\tvoid getParentOfTypeForParentTypes() {\n\t\t\tClassMemberPathNode memberPath = p1.child(primaryClassInfo.getMethods().getFirst());\n\n\t\t\t// Member path is not a class-info, so this will be the parent.\n\t\t\tClassPathNode v1 = memberPath.getPathOfType(ClassInfo.class);\n\t\t\tassertThat(p1).isSameAs(v1);\n\n\t\t\t// The member itself is accessed/annotated\n\t\t\tClassMemberPathNode v2 = memberPath.getPathOfType(Accessed.class);\n\t\t\tClassMemberPathNode v3 = memberPath.getPathOfType(Annotated.class);\n\t\t\tassertThat(v2).isSameAs(memberPath);\n\t\t\tassertThat(v3).isSameAs(v2);\n\t\t}\n\t}\n\n\t@Nested\n\tclass Descendant {\n\t\t@Test\n\t\tvoid childDescendantOfParent() {\n\t\t\t// Descendant of parent\n\t\t\tassertThat(p1.isDescendantOf(p2)).isTrue();\n\t\t\tassertThat(p1.isDescendantOf(p2parent)).isTrue();\n\t\t\tassertThat(p1.isDescendantOf(p3)).isTrue();\n\t\t\tassertThat(p1.isDescendantOf(p4)).isTrue();\n\t\t\tassertThat(p1.isDescendantOf(p5)).isTrue();\n\t\t\tassertThat(p2.isDescendantOf(p2parent)).isTrue();\n\t\t\tassertThat(p2.isDescendantOf(p3)).isTrue();\n\t\t\tassertThat(p2.isDescendantOf(p4)).isTrue();\n\t\t\tassertThat(p2.isDescendantOf(p5)).isTrue();\n\t\t\tassertThat(p2parent.isDescendantOf(p3)).isTrue();\n\t\t\tassertThat(p2parent.isDescendantOf(p4)).isTrue();\n\t\t\tassertThat(p2parent.isDescendantOf(p5)).isTrue();\n\t\t\tassertThat(p3.isDescendantOf(p4)).isTrue();\n\t\t\tassertThat(p3.isDescendantOf(p5)).isTrue();\n\t\t\tassertThat(p4.isDescendantOf(p5)).isTrue();\n\t\t}\n\n\t\t@Test\n\t\tvoid descendantOfSelf() {\n\t\t\t// Descendant of self\n\t\t\tassertThat(p1.isDescendantOf(p1)).isTrue();\n\t\t\tassertThat(p2.isDescendantOf(p2)).isTrue();\n\t\t\tassertThat(p2parent.isDescendantOf(p2parent)).isTrue();\n\t\t\tassertThat(p3.isDescendantOf(p3)).isTrue();\n\t\t\tassertThat(p4.isDescendantOf(p4)).isTrue();\n\t\t\tassertThat(p5.isDescendantOf(p5)).isTrue();\n\t\t}\n\n\t\t@Test\n\t\tvoid parentNotDescendantOfChild() {\n\t\t\t// Parent is not descendant of child\n\t\t\tassertThat(p5.isDescendantOf(p4)).isFalse();\n\t\t\tassertThat(p5.isDescendantOf(p3)).isFalse();\n\t\t\tassertThat(p5.isDescendantOf(p2)).isFalse();\n\t\t\tassertThat(p5.isDescendantOf(p1)).isFalse();\n\t\t\tassertThat(p4.isDescendantOf(p3)).isFalse();\n\t\t\tassertThat(p4.isDescendantOf(p2)).isFalse();\n\t\t\tassertThat(p4.isDescendantOf(p1)).isFalse();\n\t\t\tassertThat(p3.isDescendantOf(p2)).isFalse();\n\t\t\tassertThat(p3.isDescendantOf(p1)).isFalse();\n\t\t\tassertThat(p2parent.isDescendantOf(p2)).isFalse();\n\t\t\tassertThat(p2parent.isDescendantOf(p1)).isFalse();\n\t\t\tassertThat(p2.isDescendantOf(p1)).isFalse();\n\t\t}\n\n\t\t@Test\n\t\tvoid sameDirectoryPathsFromDifferentParentAreNotDescendants() {\n\t\t\tassertThat(p3.isDescendantOf(s3)).isFalse();\n\t\t\tassertThat(p2.isDescendantOf(s2)).isFalse();\n\t\t\tassertThat(p1.isDescendantOf(s1)).isFalse();\n\t\t}\n\n\t\t@Test\n\t\tvoid directoryNodesCanValidateParentChildRelationsFromPathValues() {\n\t\t\tDirectoryPathNode dirA = new DirectoryPathNode(\"a\");\n\t\t\tDirectoryPathNode dirB = new DirectoryPathNode(\"b\");\n\t\t\tDirectoryPathNode dirAA = new DirectoryPathNode(\"a/a\");\n\t\t\tDirectoryPathNode dirAAA = new DirectoryPathNode(\"a/a/a\");\n\t\t\tDirectoryPathNode dirAAB = new DirectoryPathNode(\"a/a/b\");\n\t\t\tDirectoryPathNode dirAB = new DirectoryPathNode(\"a/b\");\n\t\t\tDirectoryPathNode dirABA = new DirectoryPathNode(\"a/b/a\");\n\t\t\tDirectoryPathNode dirABB = new DirectoryPathNode(\"a/b/b\");\n\n\t\t\t// A\n\t\t\tassertThat(dirA.isParentOf(dirB)).isFalse();\n\t\t\tassertThat(dirA.isParentOf(dirAA)).isTrue();\n\t\t\tassertThat(dirA.isParentOf(dirAAA)).isTrue();\n\t\t\tassertThat(dirA.isParentOf(dirAAB)).isTrue();\n\t\t\tassertThat(dirA.isParentOf(dirAB)).isTrue();\n\t\t\tassertThat(dirA.isParentOf(dirABA)).isTrue();\n\t\t\tassertThat(dirA.isParentOf(dirABB)).isTrue();\n\t\t\t// B\n\t\t\tassertThat(dirB.isParentOf(dirA)).isFalse();\n\t\t\tassertThat(dirB.isParentOf(dirAA)).isFalse();\n\t\t\tassertThat(dirB.isParentOf(dirAAA)).isFalse();\n\t\t\tassertThat(dirB.isParentOf(dirAAB)).isFalse();\n\t\t\tassertThat(dirB.isParentOf(dirAB)).isFalse();\n\t\t\tassertThat(dirB.isParentOf(dirABA)).isFalse();\n\t\t\tassertThat(dirB.isParentOf(dirABB)).isFalse();\n\t\t\t// AA\n\t\t\tassertThat(dirAA.isParentOf(dirB)).isFalse();\n\t\t\tassertThat(dirAA.isParentOf(dirA)).isFalse();\n\t\t\tassertThat(dirAA.isParentOf(dirAAA)).isTrue();\n\t\t\tassertThat(dirAA.isParentOf(dirAAB)).isTrue();\n\t\t\tassertThat(dirAA.isParentOf(dirAB)).isFalse();\n\t\t\tassertThat(dirAA.isParentOf(dirABA)).isFalse();\n\t\t\tassertThat(dirAA.isParentOf(dirABB)).isFalse();\n\t\t\t// AB\n\t\t\tassertThat(dirAB.isParentOf(dirB)).isFalse();\n\t\t\tassertThat(dirAB.isParentOf(dirAA)).isFalse();\n\t\t\tassertThat(dirAB.isParentOf(dirAAA)).isFalse();\n\t\t\tassertThat(dirAB.isParentOf(dirAAB)).isFalse();\n\t\t\tassertThat(dirAB.isParentOf(dirABA)).isTrue();\n\t\t\tassertThat(dirAB.isParentOf(dirABB)).isTrue();\n\n\t\t\t// Classes\n\t\t\t// A\n\t\t\tClassPathNode classA = dirA.child(createEmptyClass(dirA.getValue() + \"/TheClass\"));\n\t\t\tassertThat(classA.isDescendantOf(dirA)).isTrue();\n\t\t\tassertThat(classA.isDescendantOf(dirAA)).isFalse();\n\t\t\tassertThat(classA.isDescendantOf(dirAB)).isFalse();\n\t\t\tassertThat(classA.isDescendantOf(dirAAA)).isFalse();\n\t\t\tassertThat(classA.isDescendantOf(dirAAB)).isFalse();\n\t\t\tassertThat(classA.isDescendantOf(dirABA)).isFalse();\n\t\t\tassertThat(classA.isDescendantOf(dirABB)).isFalse();\n\t\t\tassertThat(classA.isDescendantOf(dirB)).isFalse();\n\n\t\t\t// AA\n\t\t\tClassPathNode classAA = dirAA.child(createEmptyClass(dirAA.getValue() + \"/TheClass\"));\n\t\t\tassertThat(classAA.isDescendantOf(dirA)).isTrue();\n\t\t\tassertThat(classAA.isDescendantOf(dirAA)).isTrue();\n\t\t\tassertThat(classAA.isDescendantOf(dirAB)).isFalse();\n\t\t\tassertThat(classAA.isDescendantOf(dirAAA)).isFalse();\n\t\t\tassertThat(classAA.isDescendantOf(dirAAB)).isFalse();\n\t\t\tassertThat(classAA.isDescendantOf(dirABA)).isFalse();\n\t\t\tassertThat(classAA.isDescendantOf(dirABB)).isFalse();\n\t\t\tassertThat(classAA.isDescendantOf(dirB)).isFalse();\n\n\t\t\t// AAA\n\t\t\tClassPathNode classAAA = dirAAA.child(createEmptyClass(dirAAA.getValue() + \"/TheClass\"));\n\t\t\tassertThat(classAAA.isDescendantOf(dirA)).isTrue();\n\t\t\tassertThat(classAAA.isDescendantOf(dirAA)).isTrue();\n\t\t\tassertThat(classAAA.isDescendantOf(dirAB)).isFalse();\n\t\t\tassertThat(classAAA.isDescendantOf(dirAAA)).isTrue();\n\t\t\tassertThat(classAAA.isDescendantOf(dirAAB)).isFalse();\n\t\t\tassertThat(classAAA.isDescendantOf(dirABA)).isFalse();\n\t\t\tassertThat(classAAA.isDescendantOf(dirABB)).isFalse();\n\t\t\tassertThat(classAAA.isDescendantOf(dirB)).isFalse();\n\n\t\t\t// B\n\t\t\tClassPathNode classB = dirB.child(createEmptyClass(dirB.getValue() + \"/TheClass\"));\n\t\t\tassertThat(classB.isDescendantOf(dirA)).isFalse();\n\t\t\tassertThat(classB.isDescendantOf(dirAA)).isFalse();\n\t\t\tassertThat(classB.isDescendantOf(dirAB)).isFalse();\n\t\t\tassertThat(classB.isDescendantOf(dirAAA)).isFalse();\n\t\t\tassertThat(classB.isDescendantOf(dirAAB)).isFalse();\n\t\t\tassertThat(classB.isDescendantOf(dirABA)).isFalse();\n\t\t\tassertThat(classB.isDescendantOf(dirABB)).isFalse();\n\t\t\tassertThat(classB.isDescendantOf(dirB)).isTrue();\n\n\t\t\t// Class to class\n\t\t\t// A\n\t\t\tassertThat(classA.isDescendantOf(classA)).isTrue();\n\t\t\tassertThat(classA.isDescendantOf(classAA)).isFalse();\n\t\t\tassertThat(classA.isDescendantOf(classAAA)).isFalse();\n\t\t\tassertThat(classA.isDescendantOf(classB)).isFalse();\n\n\t\t\t// AA\n\t\t\tassertThat(classAA.isDescendantOf(classA)).isFalse();\n\t\t\tassertThat(classAA.isDescendantOf(classAA)).isTrue();\n\t\t\tassertThat(classAA.isDescendantOf(classAAA)).isFalse();\n\t\t\tassertThat(classAA.isDescendantOf(classB)).isFalse();\n\n\t\t\t// AAA\n\t\t\tassertThat(classAAA.isDescendantOf(classA)).isFalse();\n\t\t\tassertThat(classAAA.isDescendantOf(classAA)).isFalse();\n\t\t\tassertThat(classAAA.isDescendantOf(classAAA)).isTrue();\n\t\t\tassertThat(classAAA.isDescendantOf(classB)).isFalse();\n\n\t\t\t// B\n\t\t\tassertThat(classB.isDescendantOf(classA)).isFalse();\n\t\t\tassertThat(classB.isDescendantOf(classAA)).isFalse();\n\t\t\tassertThat(classB.isDescendantOf(classAAA)).isFalse();\n\t\t\tassertThat(classB.isDescendantOf(classB)).isTrue();\n\t\t}\n\n\t\t/**\n\t\t * When classes are in different versioned bundles, they should not be considered descendants of each other,\n\t\t * even if they have the same package and class name. In this scenario all versioned bundles will also have\n\t\t * map equality which is another problem case that this test is designed to catch (with how paths handle\n\t\t * value equality checks by default, and how bundle paths override that behavior to address this problem).\n\t\t */\n\t\t@Test\n\t\tvoid descendantOfDifferentVersionedClasses() {\n\t\t\tint min = 9;\n\t\t\tint max = 16;\n\t\t\tNavigableMap<Integer, ClassPathNode> versionedClasses = new TreeMap<>();\n\t\t\tNavigableMap<Integer, BundlePathNode> versionedBundles = new TreeMap<>();\n\t\t\tfor (int i = min; i < max; i++) {\n\t\t\t\tBundlePathNode bundlePath = s4.child(new BasicVersionedJvmClassBundle(i));\n\t\t\t\tClassPathNode classPath = bundlePath.child(null).child(new StubClassInfo(\"Foo\"));\n\t\t\t\tversionedClasses.put(i, classPath);\n\t\t\t\tversionedBundles.put(i, bundlePath);\n\t\t\t}\n\n\t\t\t// Validate that only classes from the same version are considered descendants of each other.\n\t\t\tInteger previousVersion = null;\n\t\t\tfor (int a = min; a < max; a++) {\n\t\t\t\tfor (int b = min; b < max; b++) {\n\t\t\t\t\tClassPathNode classA = versionedClasses.get(a);\n\t\t\t\t\tClassPathNode classB = versionedClasses.get(b);\n\t\t\t\t\tBundlePathNode bundleA = versionedBundles.get(a);\n\t\t\t\t\tBundlePathNode bundleB = versionedBundles.get(b);\n\n\t\t\t\t\tif (a == b) {\n\t\t\t\t\t\t// Same version\n\t\t\t\t\t\tassertThat(classA.isDescendantOf(classB)).isTrue();\n\t\t\t\t\t\tassertThat(classA.isDescendantOf(bundleB)).isTrue();\n\t\t\t\t\t\tassertThat(bundleA.isDescendantOf(bundleB)).isTrue();\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Different version\n\t\t\t\t\t\tassertThat(classA.isDescendantOf(classB)).isFalse();\n\t\t\t\t\t\tassertThat(classA.isDescendantOf(bundleB)).isFalse();\n\t\t\t\t\t\tassertThat(bundleA.isDescendantOf(bundleB)).isFalse();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Nested\n\tclass Comparison {\n\t\t@Test\n\t\tvoid compareToSelfIsZero() {\n\t\t\t// Self comparison or equal items should always be 0\n\t\t\tassertThatComparable(p1).isEqualTo(p1);\n\t\t\tassertThatComparable(p2).isEqualTo(p2);\n\t\t\tassertThatComparable(p3).isEqualTo(p3);\n\t\t\tassertThatComparable(p4).isEqualTo(p4);\n\t\t\tassertThatComparable(p5).isEqualTo(p5);\n\t\t}\n\n\t\t@Test\n\t\tvoid compareToParentIsGreater() {\n\t\t\t// Children appear last (thus > 0)\n\t\t\tassertThatComparable(p1).isGreaterThan(p2);\n\t\t\tassertThatComparable(p2).isGreaterThan(p3);\n\t\t\tassertThatComparable(p3).isGreaterThan(p4);\n\t\t\tassertThatComparable(p4).isGreaterThan(p5);\n\t\t}\n\n\t\t@Test\n\t\tvoid compareToChildIsLess() {\n\t\t\t// Parents appear first (thus < 0)\n\t\t\tassertThatComparable(p5).isLessThan(p4);\n\t\t\tassertThatComparable(p5).isLessThan(p3);\n\t\t\tassertThatComparable(p5).isLessThan(p2);\n\t\t\tassertThatComparable(p5).isLessThan(p1);\n\t\t\tassertThatComparable(p4).isLessThan(p3);\n\t\t\tassertThatComparable(p4).isLessThan(p2);\n\t\t\tassertThatComparable(p4).isLessThan(p1);\n\t\t\tassertThatComparable(p3).isLessThan(p2);\n\t\t\tassertThatComparable(p3).isLessThan(p1);\n\t\t\tassertThatComparable(p2).isLessThan(p1);\n\t\t}\n\n\t\t@Test\n\t\tvoid compareToOtherBundleTypes() {\n\t\t\t// 1: JVM\n\t\t\t// 2: Versioned\n\t\t\t// 3: Android\n\t\t\t// 4: File\n\t\t\tassertThatComparable(p3).isLessThan(p3and1);\n\t\t\tassertThatComparable(p3).isLessThan(p3file);\n\t\t\tassertThatComparable(p3and1).isLessThan(p3file);\n\t\t\tassertThatComparable(p3and1).isLessThan(p3and2);\n\n\t\t\t// Inverse\n\t\t\tassertThatComparable(p3and1).isGreaterThan(p3);\n\t\t\tassertThatComparable(p3file).isGreaterThan(p3);\n\t\t\tassertThatComparable(p3file).isGreaterThan(p3and1);\n\t\t\tassertThatComparable(p3and2).isGreaterThan(p3and1);\n\t\t}\n\n\t\t@Test\n\t\tvoid compareToOtherResourceLocations() {\n\t\t\t// Primary resource should come first\n\t\t\tassertThatComparable(p4).isLessThan(s4);\n\t\t\tassertThatComparable(s4).isGreaterThan(p4);\n\n\t\t\t// Self comparison\n\t\t\tassertThat(p4.localCompare(p4)).isZero();\n\t\t\tassertThat(s4.localCompare(s4)).isZero();\n\t\t}\n\t}\n\n\t@Nested\n\tclass Misc {\n\t\t@Test\n\t\tvoid hasEqualOrChildValue() {\n\t\t\t// Comparison between members\n\t\t\tassertThat(p1field.hasEqualOrChildValue(p1method)).isFalse();\n\t\t\tassertThat(p1method.hasEqualOrChildValue(p1field)).isFalse();\n\t\t\tassertThat(p1method.hasEqualOrChildValue(p1method)).isTrue();\n\t\t\tassertThat(p1field.hasEqualOrChildValue(p1field)).isTrue();\n\n\t\t\t// Comparison between classes\n\t\t\tassertThat(p1.hasEqualOrChildValue(p1)).isTrue();\n\t\t\tassertThat(p1.hasEqualOrChildValue(s1)).isFalse();\n\t\t\tassertThat(s1.hasEqualOrChildValue(p1)).isFalse();\n\n\t\t\t// Comparison between files\n\t\t\tassertThat(pFile1.hasEqualOrChildValue(pFile1)).isTrue();\n\t\t\tassertThat(pFile1.hasEqualOrChildValue(pFile2)).isFalse();\n\n\t\t\t// Comparison between packages\n\t\t\t//  - p2       == 'com/example'\n\t\t\t//  - p2parent == 'com/'\n\t\t\t// The deeper package should be treated as a \"child value\" of the parent package\n\t\t\tassertThat(p2.hasEqualOrChildValue(p2parent)).isTrue();\n\t\t\tassertThat(p2parent.hasEqualOrChildValue(p2)).isFalse();\n\n\t\t\t// Comparison between other types\n\t\t\tassertThat(p1field.hasEqualOrChildValue(p1)).isFalse();\n\t\t\tassertThat(p1field.hasEqualOrChildValue(p2)).isFalse();\n\t\t\tassertThat(p1field.hasEqualOrChildValue(p3)).isFalse();\n\t\t\tassertThat(p1field.hasEqualOrChildValue(p4)).isFalse();\n\t\t\tassertThat(p1field.hasEqualOrChildValue(p5)).isFalse();\n\t\t}\n\n\t\t@Test\n\t\tvoid bundleInTarget() {\n\t\t\t// JVM bundle\n\t\t\tassertThat(p3.isInJvmBundle()).isTrue();\n\t\t\tassertThat(p3.isInVersionedJvmBundle()).isFalse();\n\t\t\tassertThat(p3.isInAndroidBundle()).isFalse();\n\t\t\tassertThat(p3.isInFileBundle()).isFalse();\n\n\t\t\t// Android bundle\n\t\t\tassertThat(p3and1.isInJvmBundle()).isFalse();\n\t\t\tassertThat(p3and1.isInVersionedJvmBundle()).isFalse();\n\t\t\tassertThat(p3and1.isInAndroidBundle()).isTrue();\n\t\t\tassertThat(p3and1.isInFileBundle()).isFalse();\n\n\t\t\t// File bundle\n\t\t\tassertThat(p3file.isInJvmBundle()).isFalse();\n\t\t\tassertThat(p3file.isInVersionedJvmBundle()).isFalse();\n\t\t\tassertThat(p3file.isInAndroidBundle()).isFalse();\n\t\t\tassertThat(p3file.isInFileBundle()).isTrue();\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/assembler/ExpressionCompilerTest.java",
    "content": "package software.coley.recaf.services.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.services.compile.CompilerDiagnostic;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.ClassWithFieldsAndMethods;\nimport software.coley.recaf.test.dummy.ClassWithInnerAndMembers;\nimport software.coley.recaf.test.dummy.ClassWithLambda;\nimport software.coley.recaf.test.dummy.ClassWithRequiredConstructor;\nimport software.coley.recaf.test.dummy.ClassWithToString;\nimport software.coley.recaf.test.dummy.DummyEnum;\nimport software.coley.recaf.test.dummy.DummyRecord;\nimport software.coley.recaf.test.dummy.SealedCircle;\nimport software.coley.recaf.test.dummy.SealedOtherShape;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.objectweb.asm.Opcodes.*;\n\n/**\n * Tests for {@link ExpressionCompiler}\n */\nclass ExpressionCompilerTest extends TestBase {\n\tstatic Workspace workspace;\n\tstatic JvmClassInfo targetClass;\n\tstatic JvmClassInfo targetCtorClass;\n\tstatic JvmClassInfo targetToStringClass;\n\tstatic JvmClassInfo targetEnum;\n\tstatic JvmClassInfo targetRecord;\n\tstatic JvmClassInfo targetOuterWithInner;\n\tstatic JvmClassInfo targetClassWithLambda;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\ttargetClass = TestClassUtils.fromRuntimeClass(ClassWithFieldsAndMethods.class);\n\t\ttargetCtorClass = TestClassUtils.fromRuntimeClass(ClassWithRequiredConstructor.class);\n\t\ttargetToStringClass = TestClassUtils.fromRuntimeClass(ClassWithToString.class);\n\t\ttargetEnum = TestClassUtils.fromRuntimeClass(DummyEnum.class);\n\t\ttargetRecord = TestClassUtils.fromRuntimeClass(DummyRecord.class);\n\t\ttargetOuterWithInner = TestClassUtils.fromRuntimeClass(ClassWithInnerAndMembers.class);\n\t\ttargetClassWithLambda = TestClassUtils.fromRuntimeClass(ClassWithLambda.class);\n\t\tworkspace = TestClassUtils.fromBundle(TestClassUtils.fromClasses(targetClass, targetCtorClass, targetEnum));\n\t\tworkspaceManager.setCurrent(workspace);\n\t}\n\n\t@Test\n\tvoid importSupport() {\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tExpressionResult result = compile(assembler, \"\"\"\n\t\t\t\timport java.util.Random;\n\t\t\t\t\n\t\t\t\ttry {\n\t\t\t\t\tRandom random = new Random();\n\t\t\t\t \tint a = random.nextInt(100);\n\t\t\t\t \tint b = random.nextInt(100);\n\t\t\t\t \tSystem.out.println(a + \" / \" + b + \" = \" + (a/b));\n\t\t\t\t} catch (Exception ex) {\n\t\t\t\t\tSystem.out.println(\"Fail: \" + ex);\n\t\t\t\t}\n\t\t\t\t\"\"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid classContext() {\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(targetClass);\n\t\tExpressionResult result = compile(assembler, \"\"\"\n\t\t\t\tint localConst = CONST_INT;\n\t\t\t\tint localField = finalInt;\n\t\t\t\tint localMethod = plusTwo();\n\t\t\t\tint add = localConst + localField + localMethod;\n\t\t\t\t\"\"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid classContextWithRequiredCtor() {\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(targetCtorClass);\n\t\tExpressionResult result = compile(assembler, \"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid enumContext() {\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(targetEnum);\n\t\tExpressionResult result = compile(assembler, \"\"\"\n\t\t\t\tint i1 = ONE.ordinal();\n\t\t\t\tint i2 = TWO.ordinal();\n\t\t\t\tint i3 = THREE.ordinal();\n\t\t\t\tint add = i1 + i2 + i3;\n\t\t\t\t\"\"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid recordContext() {\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(targetRecord);\n\t\tassembler.setMethodContext(targetRecord.getFirstDeclaredMethodByName(\"fooPlus\"));\n\t\tExpressionResult result = compile(assembler, \"\"\"\n\t\t\t\tint plus = foo + other;\n\t\t\t\tint mul = foo * other;\n\t\t\t\treturn String.valueOf(mul - plus);\n\t\t\t\t\"\"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid classAndMethodContextForParameters() {\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(targetClass);\n\t\tassembler.setMethodContext(targetClass.getFirstDeclaredMethodByName(\"methodWithParameters\"));\n\t\tExpressionResult result = compile(assembler, \"\"\"\n\t\t\t\tSystem.out.println(foo + \": \" +\n\t\t\t\t\t\tLong.toHexString(wide) +\n\t\t\t\t\t\t\"/\" +\n\t\t\t\t\t\tFloat.floatToIntBits(decimal) +\n\t\t\t\t\t\t\" s=\" + strings.get(0));\n\t\t\t\t\"\"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid classAndMethodContextForLocals() {\n\t\t// Tests that local variables are accessible to the expression compiler\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(targetClass);\n\t\tassembler.setMethodContext(targetClass.getFirstDeclaredMethodByName(\"methodWithLocalVariables\"));\n\t\tExpressionResult result = compile(assembler, \"\"\"\n\t\t\t\tout.println(message.contains(\"0\") ? \"Has zero\" : \"No zero found\");\n\t\t\t\t\"\"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid classAndMethodContextForConstructor() {\n\t\t// Tests that the assembler works for constructor method contexts\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(targetClass);\n\t\tassembler.setMethodContext(targetClass.getFirstDeclaredMethodByName(\"<init>\"));\n\t\tExpressionResult result = compile(assembler, \"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid classAndMethodContextForStaticInitializer() {\n\t\t// Tests that the assembler works for static initializer method contexts\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(targetEnum);\n\t\tassembler.setMethodContext(targetEnum.getFirstDeclaredMethodByName(\"<clinit>\"));\n\t\tExpressionResult result = compile(assembler, \"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid classWithInnerReferences() {\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(targetOuterWithInner);\n\t\tExpressionResult result = compile(assembler, \"\"\"\n\t\t\t\tTheInner inner = new TheInner();\n\t\t\t\tSystem.out.println(\"foo: \" + foo);\n\t\t\t\tSystem.out.println(\"bar: \" + inner.bar);\n\t\t\t\tinner.strings.add(\"something\");\n\t\t\t\tinner.innerToOuter();\n\t\t\t\t\"\"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid sealedChild() throws IOException {\n\t\t// Tests basic support for compiling within a child of a sealed type.\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(TestClassUtils.fromRuntimeClass(SealedOtherShape.class));\n\t\tExpressionResult result = compile(assembler, \"\"\"\n\t\t\t\tSystem.out.println(\"area: \" + area());\n\t\t\t\t\"\"\");\n\t\tassertSuccess(result);\n\n\t\t// Same test but in a record child.\n\t\tassembler.setClassContext(TestClassUtils.fromRuntimeClass(SealedCircle.class));\n\t\t  result = compile(assembler, \"\"\"\n\t\t\t\tSystem.out.println(\"area: \" + area());\n\t\t\t\t\"\"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid overrideLibraryMethodDoesNotFail() {\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(targetToStringClass);\n\t\tassembler.setMethodContext(targetToStringClass.getFirstDeclaredMethodByName(\"toString\"));\n\t\tExpressionResult result = compile(assembler, \"\"\"\n\t\t\t\treturn \"string\";\n\t\t\t\t\"\"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid ignoreTooOldTargetVersion() {\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setVersionTarget(1);\n\t\tExpressionResult result = compile(assembler, \"\"\"\n\t\t\t\tSystem.out.println(\"We do not compile against Java 1\");\n\t\t\t\t\"\"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid ignoreNonExistingTypeForFields() {\n\t\tClassWriter cw = new ClassWriter(0);\n\t\tcw.visit(V1_8, ACC_PUBLIC, \"ExampleClass\", null, \"java/lang/Object\", null);\n\t\tcw.visitField(ACC_PRIVATE, \"foo\", \"Lfoo/Bar;\", null, null); // Bogus field type\n\t\tcw.visitMethod(ACC_PRIVATE, \"methodName\", \"()V\", null, null);\n\t\tJvmClassInfo classInfo = new JvmClassInfoBuilder(cw.toByteArray()).build();\n\n\t\t// The expression compiler should skip the field since it uses a type not in the workspace.\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(classInfo);\n\t\tassembler.setMethodContext(classInfo.getFirstDeclaredMethodByName(\"methodName\"));\n\t\tExpressionResult result = compile(assembler, \"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid ignoreNonExistingTypeForMethodParams() {\n\t\tClassWriter cw = new ClassWriter(0);\n\t\tcw.visit(V1_8, ACC_PUBLIC, \"ExampleClass\", null, \"java/lang/Object\", null);\n\t\tcw.visitMethod(ACC_PRIVATE, \"foo\", \"(Lfoo/Bar;)V\", null, null); // Bogus parameter type\n\t\tcw.visitMethod(ACC_PRIVATE, \"methodName\", \"()V\", null, null);\n\t\tJvmClassInfo classInfo = new JvmClassInfoBuilder(cw.toByteArray()).build();\n\n\t\t// The expression compiler should skip the method since it uses a type not in the workspace.\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(classInfo);\n\t\tassembler.setMethodContext(classInfo.getFirstDeclaredMethodByName(\"methodName\"));\n\t\tExpressionResult result = compile(assembler, \"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid ignoreNonExistingTypeForMethodReturns() {\n\t\tClassWriter cw = new ClassWriter(0);\n\t\tcw.visit(V1_8, ACC_PUBLIC, \"ExampleClass\", null, \"java/lang/Object\", null);\n\t\tcw.visitMethod(ACC_PRIVATE, \"foo\", \"()Lfoo/Bar;\", null, null); // Bogus method parameter type\n\t\tcw.visitMethod(ACC_PRIVATE, \"methodName\", \"()V\", null, null);\n\t\tJvmClassInfo classInfo = new JvmClassInfoBuilder(cw.toByteArray()).build();\n\n\t\t// The expression compiler should skip the method since it uses a type not in the workspace.\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(classInfo);\n\t\tassembler.setMethodContext(classInfo.getFirstDeclaredMethodByName(\"methodName\"));\n\t\tExpressionResult result = compile(assembler, \"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid ignoreNonExistingTypeForMethodContext() {\n\t\tClassWriter cw = new ClassWriter(0);\n\t\tcw.visit(V1_8, ACC_PUBLIC, \"ExampleClass\", null, \"java/lang/Object\", null);\n\t\tcw.visitMethod(ACC_PRIVATE, \"methodName\", \"(Lfoo/Bar;)V\", null, null);\n\t\tJvmClassInfo classInfo = new JvmClassInfoBuilder(cw.toByteArray()).build();\n\n\t\t// The expression compiler should skip the method since it uses a type not in the workspace.\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(classInfo);\n\t\tassembler.setMethodContext(classInfo.getFirstDeclaredMethodByName(\"methodName\"));\n\t\tExpressionResult result = compile(assembler, \"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid dontStubBogusInnerLikeMethodHandlesLookup() {\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(targetClassWithLambda);\n\t\tExpressionResult result = compile(assembler, \"\"\"\n\t\t\t\tSystem.out.println(\"The stub should not reference the MethodHandles$Lookup synthetic inner class\");\n\t\t\t\t\"\"\");\n\t\tassertSuccess(result);\n\t}\n\n\t@Test\n\tvoid errorLineIsOffsetToInputExpressionLineNumber() {\n\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\tassembler.setClassContext(targetClass);\n\t\tassembler.setMethodContext(targetClass.getFirstDeclaredMethodByName(\"plusTwo\"));\n\t\tString expression = \"\"\"\n\t\t\t\treturn \"not-an-int\";\n\t\t\t\t\"\"\";\n\t\tExpressionResult result = compile(assembler, expression);\n\n\t\t// Should be a failure\n\t\tassertFalse(result.wasSuccess());\n\t\tassertNull(result.getException());\n\n\t\t// Should have an error on line 1 of our expression\n\t\tList<CompilerDiagnostic> diagnostics = result.getDiagnostics();\n\t\tassertEquals(1, diagnostics.size());\n\t\tCompilerDiagnostic error = diagnostics.getFirst();\n\t\tassertEquals(1, error.line());\n\t}\n\n\t@Nested\n\tclass ObfuscatedContexts {\n\t\t@ParameterizedTest\n\t\t@ValueSource(strings = {\"void\", \"null\", \"int\", \"private\", \"throws\", \"\", \"\\0\", \" \", \"-10\", \"100\", \"<lol>\"})\n\t\tvoid ignoreIllegalFieldName(String illegalFieldName) {\n\t\t\tClassWriter cw = new ClassWriter(0);\n\t\t\tcw.visit(V1_8, ACC_PUBLIC, \"ExampleClass\", null, \"java/lang/Object\", null);\n\t\t\tcw.visitField(ACC_PRIVATE, illegalFieldName, \"I\", null, null);\n\t\t\tcw.visitMethod(ACC_PRIVATE, \"methodName\", \"()V\", null, null);\n\t\t\tJvmClassInfo classInfo = new JvmClassInfoBuilder(cw.toByteArray()).build();\n\n\t\t\t// The expression compiler should skip the field since it has an illegal name.\n\t\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\t\tassembler.setClassContext(classInfo);\n\t\t\tassembler.setMethodContext(classInfo.getFirstDeclaredMethodByName(\"methodName\"));\n\t\t\tExpressionResult result = compile(assembler, \"\");\n\t\t\tassertSuccess(result);\n\t\t}\n\n\t\t@ParameterizedTest\n\t\t@ValueSource(strings = {\"void\", \"null\", \"int\", \"private\", \"throws\", \"\", \"\\0\", \" \", \"-10\", \"100\", \"<lol>\"})\n\t\tvoid ignoreIllegalMethodName(String illegalMethodName) {\n\t\t\tClassWriter cw = new ClassWriter(0);\n\t\t\tcw.visit(V1_8, ACC_PUBLIC, \"ExampleClass\", null, \"java/lang/Object\", null);\n\t\t\tcw.visitMethod(ACC_PRIVATE, illegalMethodName, \"()I\", null, null);\n\t\t\tcw.visitMethod(ACC_PRIVATE, \"methodName\", \"()V\", null, null);\n\t\t\tJvmClassInfo classInfo = new JvmClassInfoBuilder(cw.toByteArray()).build();\n\n\t\t\t// The expression compiler should skip the method since it has an illegal name.\n\t\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\t\tassembler.setClassContext(classInfo);\n\t\t\tassembler.setMethodContext(classInfo.getFirstDeclaredMethodByName(\"methodName\"));\n\t\t\tExpressionResult result = compile(assembler, \"\");\n\t\t\tassertSuccess(result);\n\t\t}\n\n\t\t@ParameterizedTest\n\t\t@ValueSource(strings = {\"void\", \"null\", \"int\", \"private\", \"throws\", \"\", \"\\0\", \" \", \"-10\", \"100\", \"<lol>\"})\n\t\tvoid ignoreIllegalMethodContextName(String illegalMethodName) {\n\t\t\tClassWriter cw = new ClassWriter(0);\n\t\t\tcw.visit(V1_8, ACC_PUBLIC, \"ExampleClass\", null, \"java/lang/Object\", null);\n\t\t\tLabel start = new Label();\n\t\t\tLabel end = new Label();\n\n\t\t\tMethodVisitor mv = cw.visitMethod(ACC_PRIVATE, illegalMethodName, \"(IIII)V\", null, null);\n\t\t\tmv.visitCode();\n\t\t\tmv.visitLabel(start);\n\t\t\tmv.visitInsn(ICONST_0);\n\t\t\tmv.visitInsn(IRETURN);\n\t\t\tmv.visitLabel(end);\n\t\t\tmv.visitEnd();\n\t\t\tmv.visitLocalVariable(\"one\", \"I\", null, start, end, 1);\n\t\t\tmv.visitLocalVariable(\"two\", \"I\", null, start, end, 2);\n\t\t\tmv.visitLocalVariable(\"three\", \"I\", null, start, end, 3);\n\t\t\tmv.visitLocalVariable(illegalMethodName, \"I\", null, start, end, 4); // Add an illegal named parameter\n\t\t\tJvmClassInfo classInfo = new JvmClassInfoBuilder(cw.toByteArray()).build();\n\n\t\t\t// The expression compiler should rename the obfuscated method specified as the context.\n\t\t\t// Variables passed in (that are not illegally named) and such should still be accessible.\n\t\t\tExpressionCompiler assembler = recaf.get(ExpressionCompiler.class);\n\t\t\tassembler.setClassContext(classInfo);\n\t\t\tassembler.setMethodContext(classInfo.getFirstDeclaredMethodByName(illegalMethodName));\n\t\t\tExpressionResult result = compile(assembler, \"int result = one + two + three;\");\n\t\t\tassertSuccess(result);\n\t\t}\n\t}\n\n\tprivate static void assertSuccess(@Nonnull ExpressionResult result) {\n\t\tList<CompilerDiagnostic> diagnostics = result.getDiagnostics();\n\t\tfor (CompilerDiagnostic diagnostic : diagnostics)\n\t\t\tSystem.err.println(diagnostic);\n\t\tassertNull(result.getException(), \"Exception thrown when compiling: \" + result.getException());\n\t\tassertTrue(diagnostics.isEmpty(), \"There were \" + diagnostics.size() + \" compiler messages\");\n\t\tassertTrue(result.wasSuccess(), \"Missing assembler output\");\n\t}\n\n\t@Nonnull\n\tprivate static ExpressionResult compile(@Nonnull ExpressionCompiler assembler, @Nonnull String expressionResult) {\n\t\tExpressionResult result = assembler.compile(expressionResult);\n\t\tList<CompilerDiagnostic> diagnostics = result.getDiagnostics();\n\t\tdiagnostics.forEach(System.out::println);\n\t\tExpressionCompileException exception = result.getException();\n\t\tif (exception != null)\n\t\t\tfail(exception);\n\t\tString assembly = result.getAssembly();\n\t\tif (assembly != null)\n\t\t\tSystem.out.println(assembly);\n\t\treturn result;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/callgraph/CallGraphTest.java",
    "content": "package software.coley.recaf.services.callgraph;\n\nimport jakarta.annotation.Nonnull;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.MethodVisitor;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.StubMethodMember;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.StringConsumer;\nimport software.coley.recaf.test.dummy.StringConsumerUser;\nimport software.coley.recaf.util.visitors.MemberFilteringVisitor;\nimport software.coley.recaf.util.visitors.MemberPredicate;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.IOException;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.objectweb.asm.Opcodes.*;\n\n/**\n * Tests for {@link CallGraph}\n */\nclass CallGraphTest {\n\tprivate static final byte[] fooBytes;\n\tprivate static final byte[] fooCallerBytes;\n\n\t@Test\n\t@Timeout(10)\n\tvoid testCalleeCallerRelation() throws IOException {\n\t\tWorkspace workspace = TestClassUtils.fromBundle(TestClassUtils.fromClasses(\n\t\t\t\tStringConsumer.class,\n\t\t\t\tStringConsumerUser.class\n\t\t));\n\n\t\tClassPathNode pathUser = workspace.findJvmClass(StringConsumerUser.class.getName().replace('.', '/'));\n\t\tClassPathNode pathFunc = workspace.findJvmClass(StringConsumer.class.getName().replace('.', '/'));\n\t\tassertNotNull(pathUser, \"Missing main class\");\n\t\tassertNotNull(pathFunc, \"Missing function class\");\n\t\tJvmClassInfo mainClass = pathUser.getValue().asJvmClass();\n\t\tJvmClassInfo functionClass = pathFunc.getValue().asJvmClass();\n\n\t\tCallGraph callGraph = newCallGraph(workspace);\n\n\t\tClassMethodsContainer containerMain = callGraph.getClassMethodsContainer(mainClass);\n\t\tClassMethodsContainer containerFunction = callGraph.getClassMethodsContainer(functionClass);\n\n\t\t// Get outbound calls for main. Should just be to 'new StringConsumer()' and 'StringConsumer.accept(String)'\n\t\tMethodVertex mainVertex = containerMain.getVertex(\"main\", \"([Ljava/lang/String;)V\");\n\t\tassertNotNull(mainVertex, \"Missing method vertex for 'main'\");\n\t\tassertEquals(2, mainVertex.getCalls().size());\n\n\t\t// Assert main calls 'accept'\n\t\tMethodVertex acceptVertex = containerFunction.getVertex(\"accept\", \"(Ljava/lang/String;)V\");\n\t\tassertNotNull(acceptVertex, \"Missing method vertex for 'accept'\");\n\t\tassertTrue(acceptVertex.getCallers().contains(mainVertex));\n\n\t\t// Assert main calls 'new StringConsumer()'\n\t\tMethodVertex newVertex = containerFunction.getVertex(\"<init>\", \"()V\");\n\t\tassertNotNull(newVertex, \"Missing method vertex for '<init>'\");\n\t\tassertTrue(newVertex.getCallers().contains(mainVertex));\n\t}\n\n\t@Test\n\t@Timeout(10)\n\tvoid testUnresolvedCall() {\n\t\tWorkspace workspace = TestClassUtils.fromBundle(TestClassUtils.fromClasses(\n\t\t\t\tnew JvmClassInfoBuilder(fooCallerBytes).build()\n\t\t));\n\n\t\tClassPathNode pathUser = workspace.findJvmClass(\"FooCaller\");\n\t\tassertNotNull(pathUser, \"Missing FooCaller class\");\n\t\tJvmClassInfo mainClass = pathUser.getValue().asJvmClass();\n\n\t\tCallGraph callGraph = newCallGraph(workspace);\n\n\t\t// Get outbound calls for call(Foo). Should just be to 'foo.bar()' which is unresolved\n\t\tClassMethodsContainer fooCaller = callGraph.getClassMethodsContainer(mainClass);\n\t\tMethodVertex callVertex = fooCaller.getVertex(\"call\", \"(LFoo;)V\");\n\t\tassertNotNull(callVertex, \"Missing method vertex for 'call'\");\n\t\tassertEquals(0, callVertex.getCalls().size(), \"Expected to have no resolved calls from call(Foo)\");\n\t\tassertEquals(1, callGraph.getUnresolvedDeclarations().get(\"Foo\").size(), \"Expected to have unresolved call to Foo.bar()\");\n\n\t\t// Add the missing Foo class to the workspace\n\t\tworkspace.getPrimaryResource().getJvmClassBundle().put(new JvmClassInfoBuilder(fooBytes).build());\n\n\t\t// The call to Foo.bar() should be resolved now\n\t\tassertEquals(1, callVertex.getCalls().size());\n\t\tassertEquals(0, callGraph.getUnresolvedDeclarations().size(), \"Expected to have resolved unresolved call to Foo.bar()\");\n\n\t\t// Updating the Foo class (with no real changes) should not cause the call to\n\t\t// become unresolved again, assuming the method's code is unchanged.\n\t\tworkspace.getPrimaryResource().getJvmClassBundle().put(new JvmClassInfoBuilder(fooBytes).build());\n\t\tassertEquals(1, callVertex.getCalls().size());\n\t\tassertEquals(0, callGraph.getUnresolvedDeclarations().size(), \"Expected to have resolved call to Foo.bar() after updating Foo class\");\n\n\t\t// Updating the FooCaller class (with no real changes) should also not cause the\n\t\t// call to become unresolved again, assuming the method's code is unchanged.\n\t\t// - Need to get new container/vertex references since the class is updated and the old ones will be stale.\n\t\t// - Need to also update 'mainClass' to use the modified class reference since the old one will be stale.\n\t\tworkspace.getPrimaryResource().getJvmClassBundle().put(mainClass = new JvmClassInfoBuilder(fooCallerBytes).build());\n\t\tfooCaller = callGraph.getClassMethodsContainer(mainClass);\n\t\tcallVertex = fooCaller.getVertex(\"call\", \"(LFoo;)V\");\n\t\tassertNotNull(callVertex, \"Missing method vertex for 'call' after updating FooCaller class\");\n\t\tassertEquals(1, callVertex.getCalls().size());\n\t\tassertEquals(0, callGraph.getUnresolvedDeclarations().size(), \"Expected to have resolved call to Foo.bar() after updating FooCaller class\");\n\n\t\t// If we change the Foo class to remove the bar() method, the call should become unresolved again\n\t\t// since the called method no longer exists in the class.\n\t\tClassWriter cw = new ClassWriter(0);\n\t\tnew ClassReader(fooBytes).accept(new MemberFilteringVisitor(cw, MemberPredicate.of(new StubMethodMember(\"\", \"\", 0))), 0);\n\t\tworkspace.getPrimaryResource().getJvmClassBundle().put(new JvmClassInfoBuilder(cw.toByteArray()).build());\n\t\tassertEquals(0, callVertex.getCalls().size());\n\t\tassertEquals(1, callGraph.getUnresolvedDeclarations().get(\"Foo\").size(), \"Expected to have unresolved call to Foo.bar() after removing bar() method from Foo class\");\n\n\t\t// Put it back to resolve the call again (mainly to set up for the next part of the test)\n\t\tworkspace.getPrimaryResource().getJvmClassBundle().put(new JvmClassInfoBuilder(fooBytes).build());\n\t\tassertEquals(1, callVertex.getCalls().size());\n\t\tassertEquals(0, callGraph.getUnresolvedDeclarations().size(), \"Expected to have resolved call to Foo.bar() after updating Foo class\");\n\n\t\t// Remove the Foo class again should cause the call to become unresolved again\n\t\tworkspace.getPrimaryResource().getJvmClassBundle().remove(\"Foo\");\n\t\tfooCaller = callGraph.getClassMethodsContainer(mainClass);\n\t\tcallVertex = fooCaller.getVertex(\"call\", \"(LFoo;)V\");\n\t\tassertNotNull(callVertex);\n\t\tassertEquals(0, callVertex.getCalls().size());\n\t\tassertEquals(1, callGraph.getUnresolvedDeclarations().get(\"Foo\").size(), \"Expected to have unresolved call to Foo.bar() after removing Foo class\");\n\t}\n\n\t@Nonnull\n\tstatic CallGraph newCallGraph(@Nonnull Workspace workspace) {\n\t\tCallGraph callGraph = new CallGraph(workspace);\n\t\tcallGraph.initialize();\n\n\t\t// Need to wait until async population of graph contents is done.\n\t\tObservableBoolean ready = callGraph.isReady();\n\t\tassertDoesNotThrow(() -> {\n\t\t\twhile (!ready.getValue()) {\n\t\t\t\tThread.sleep(100);\n\t\t\t}\n\t\t});\n\n\t\treturn callGraph;\n\t}\n\n\tstatic {\n\t\tClassWriter cv = new ClassWriter(0);\n\t\tcv.visit(V1_8, ACC_PUBLIC, \"Foo\", null, \"java/lang/Object\", null);\n\t\tMethodVisitor mv = cv.visitMethod(0, \"bar\", \"()V\", null, null);\n\t\tmv.visitCode();\n\t\tmv.visitInsn(RETURN);\n\t\tmv.visitMaxs(1, 1);\n\t\tmv.visitEnd();\n\t\tcv.visitEnd();\n\t\tfooBytes = cv.toByteArray();\n\n\t\tcv = new ClassWriter(0);\n\t\tcv.visit(V1_8, ACC_PUBLIC | ACC_STATIC, \"FooCaller\", null, \"java/lang/Object\", null);\n\t\tmv = cv.visitMethod(0, \"call\", \"(LFoo;)V\", null, null);\n\t\tmv.visitCode();\n\t\tmv.visitVarInsn(ALOAD, 0);\n\t\tmv.visitMethodInsn(INVOKEVIRTUAL, \"Foo\", \"bar\", \"()V\", false);\n\t\tmv.visitInsn(RETURN);\n\t\tmv.visitMaxs(2, 2);\n\t\tmv.visitEnd();\n\t\tcv.visitEnd();\n\t\tfooCallerBytes = cv.toByteArray();\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/comment/CommentManagerTest.java",
    "content": "package software.coley.recaf.services.comment;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.MethodOrderer;\nimport org.junit.jupiter.api.Order;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestMethodOrder;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.decompile.DecompileResult;\nimport software.coley.recaf.services.decompile.DecompilerManager;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.MappingApplierService;\nimport software.coley.recaf.services.mapping.MappingResults;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.ClassWithFieldsAndMethods;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.IOException;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link CommentManager}\n */\n@TestMethodOrder(MethodOrderer.OrderAnnotation.class)\nclass CommentManagerTest extends TestBase {\n\tstatic CommentManager commentManager;\n\tstatic CommentManagerConfig commentManagerConfig;\n\tstatic DecompilerManager decompilerManager;\n\tstatic MappingApplierService mappingApplierService;\n\tstatic JvmClassInfo classToDecompile;\n\tstatic Workspace workspace;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\t// Setup workspace\n\t\tclassToDecompile = TestClassUtils.fromRuntimeClass(ClassWithFieldsAndMethods.class);\n\t\tworkspace = TestClassUtils.fromBundle(TestClassUtils.fromClasses(classToDecompile));\n\t\tworkspaceManager.setCurrent(workspace);\n\n\t\t// Grab services\n\t\tcommentManager = recaf.get(CommentManager.class);\n\t\tcommentManagerConfig = recaf.get(CommentManagerConfig.class);\n\t\tdecompilerManager = recaf.get(DecompilerManager.class);\n\t\tmappingApplierService = recaf.get(MappingApplierService.class);\n\t}\n\n\t@Test\n\t@Order(1)\n\tvoid testCommentsInsertedIntoDecompilation() {\n\t\tClassPathNode path = workspace.findClass(classToDecompile.getName());\n\t\tassertNotNull(path, \"Failed to find class in workspace\");\n\n\t\tWorkspaceComments workspaceComments = commentManager.getOrCreateWorkspaceComments(workspace);\n\t\tClassComments classComments = workspaceComments.getOrCreateClassComments(path);\n\t\tclassComments.setClassComment(\"Class comment with too many words to be put on a single line, which should \" +\n\t\t\t\t\"trigger the automatic word wrapping so that the full contents of this message are legible \" +\n\t\t\t\t\"without having to scroll to the right, which is annoying\");\n\t\tclassComments.setFieldComment(\"CONST_INT\", \"I\", \"Field comment\\nThis is a constant value.\");\n\t\tclassComments.setMethodComment(\"methodWithLocalVariables\", \"()V\", \"Method comment\");\n\n\t\t// Validate that decompiling the class inserts the comments\n\t\ttry {\n\t\t\tcommentManagerConfig.getWordWrappingLimit().setValue(100);\n\t\t\tDecompileResult result = decompilerManager.decompile(workspace, classToDecompile).get();\n\t\t\tString text = result.getText();\n\t\t\tassertNotNull(text, \"Decompile failed\");\n\t\t\tassertTrue(text.contains(\"\"\"\n\t\t\t\t\t/**\n\t\t\t\t\t * Class comment with too many words to be put on a single line, which should trigger the automatic\n\t\t\t\t\t * word wrapping so that the full contents of this message are legible without having to scroll to the\n\t\t\t\t\t * right, which is annoying\n\t\t\t\t\t */\n\t\t\t\t\t\"\"\"), \"Expected class comment to exist and be line wrapped (100)\");\n\t\t\tassertTrue(text.contains(\"/** Method comment */\"), \"Expected single line method comment\");\n\t\t\tassertTrue(text.contains(\"\"\"\n\t\t\t\t\t    /**\n\t\t\t\t\t     * Field comment\n\t\t\t\t\t     * This is a constant value.\n\t\t\t\t\t     */\n\t\t\t\t\t\"\"\"), \"Expected multi-line indented field comment\");\n\t\t} catch (Exception ex) {\n\t\t\tfail(ex);\n\t\t}\n\t}\n\n\t@Test\n\t@Order(2)\n\tvoid testCommentsGetMigratedAfterRemapping() {\n\t\tClassPathNode preMappingPath = workspace.findJvmClass(classToDecompile.getName());\n\t\tassertNotNull(preMappingPath);\n\n\t\t// Generate some mappings for the documented class (applied in the first test)\n\t\tString mappedClassName = \"Foo\";\n\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\tmappings.addClass(classToDecompile.getName(), mappedClassName);\n\t\tmappings.addField(classToDecompile.getName(), \"I\", \"CONST_INT\", \"BAR\");\n\t\tmappings.addMethod(classToDecompile.getName(), \"()V\", \"methodWithLocalVariables\", \"fizz\");\n\n\t\t// Apply the mappings\n\t\tMappingResults results = mappingApplierService.inCurrentWorkspace().applyToPrimaryResource(mappings);\n\t\tClassPathNode postMappingPath = results.getPostMappingPath(classToDecompile.getName());\n\t\tassertNotNull(postMappingPath, \"Post-mapping path does not exist in mapping results\");\n\t\tresults.apply();\n\n\t\t// Validate the old mappings are migrated.\n\t\tWorkspaceComments workspaceComments = commentManager.getOrCreateWorkspaceComments(workspace);\n\t\tassertNull(workspaceComments.getClassComments(preMappingPath), \"Old comment container still exists\");\n\t\tClassComments newClassComments = workspaceComments.getClassComments(postMappingPath);\n\t\tassertNotNull(newClassComments, \"New comment container does not exist\");\n\t\tassertNotNull(newClassComments.getClassComment(), \"Missing class comment\");\n\t\tassertNotNull(newClassComments.getFieldComment(\"BAR\", \"I\"), \"Missing field comment\");\n\t\tassertNotNull(newClassComments.getMethodComment(\"fizz\", \"()V\"), \"Missing method comment\");\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/compile/JavacCompilerTest.java",
    "content": "package software.coley.recaf.services.compile;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.commons.ClassRemapper;\nimport org.objectweb.asm.commons.SimpleRemapper;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.StubFileInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.StringConsumer;\nimport software.coley.recaf.util.JavaVersion;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResourceBuilder;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder;\n\nimport java.io.IOException;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link JavacCompiler}\n */\npublic class JavacCompilerTest extends TestBase {\n\tstatic JavacCompiler javac;\n\n\t@BeforeAll\n\tstatic void setup() {\n\t\tassertTrue(JavacCompiler.isAvailable(), \"javac not available!\");\n\t\tjavac = recaf.get(JavacCompiler.class);\n\t}\n\n\t@Test\n\tvoid testJavacWithoutWorkspace() {\n\t\tJavacArguments arguments = new JavacArgumentsBuilder()\n\t\t\t\t.withClassName(\"HelloWorld\")\n\t\t\t\t.withClassSource(\"\"\"\n\t\t\t\t\t\tpublic class HelloWorld {\n\t\t\t\t\t\t\tpublic static void main(String[] args) {\n\t\t\t\t\t\t\t\tSystem.out.println(\"Hello world\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\"\"\")\n\t\t\t\t.build();\n\n\t\t// Run compiler\n\t\tCompilerResult result = javac.compile(arguments, null, null);\n\n\t\t// Assert no errors\n\t\tassertTrue(result.wasSuccess(), \"Result does not indicate success\");\n\t\tassertEquals(0, result.getDiagnostics().size(), \"There were unexpected diagnostic messages\");\n\t\tassertTrue(result.getCompilations().containsKey(\"HelloWorld\"), \"Class missing from compile map output\");\n\n\t\t// Assert class validity\n\t\tbyte[] classBytecode = result.getCompilations().get(\"HelloWorld\");\n\t\tJvmClassInfo classInfo = new JvmClassInfoBuilder(classBytecode).build();\n\t\tassertEquals(\"HelloWorld\", classInfo.getName(), \"Class name did not match expected value\");\n\t\tassertNotNull(classInfo.getDeclaredMethod(\"main\", \"([Ljava/lang/String;)V\"), \"Missing main method\");\n\t}\n\n\t@Test\n\tvoid testJavacDownsample() {\n\t\tJavacArguments arguments = new JavacArgumentsBuilder()\n\t\t\t\t.withDownsampleTarget(8)\n\t\t\t\t.withClassName(\"HelloWorld\")\n\t\t\t\t.withClassSource(\"\"\"\n\t\t\t\t\t\timport java.util.ArrayList;\n\t\t\t\t\t\timport java.util.List;\n\t\t\t\t\t\tpublic class HelloWorld {\n\t\t\t\t\t\t\tpublic static void main(String[] args) {\n\t\t\t\t\t\t\t\tif (args.length < 1)\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\tString amountProperty = System.getProperty(args[0]);\n\t\t\t\t\t\t\t\tString amount = amountProperty;\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t// List.of should be replaced since it was added in Java 9\n\t\t\t\t\t\t\t\tList<String> values = new ArrayList<>(switch (amount) {\n\t\t\t\t\t\t\t\t\tcase \"one\" -> List.of(\"one\");\n\t\t\t\t\t\t\t\t\tcase \"two\" -> List.of(\"one\", \"two\");\n\t\t\t\t\t\t\t\t\tcase \"three\" -> List.of(\"one\", \"two\", \"three\");\n\t\t\t\t\t\t\t\t\tdefault -> throw new IllegalStateException(\"Unexpected value: \" + amount);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\twhile (!values.isEmpty()) {\n\t\t\t\t\t\t\t\t\t// Should removeLast since it was added in Java 21\n\t\t\t\t\t\t\t\t\tString last = values.removeLast();\n\t\t\t\t\t\t\t\t\tSystem.out.println(last);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\"\"\")\n\t\t\t\t.build();\n\n\t\t// Run compiler\n\t\tCompilerResult result = javac.compile(arguments, null, null);\n\n\t\t// Assert no errors\n\t\tassertTrue(result.wasSuccess(), \"Result does not indicate success\");\n\t\tassertEquals(0, result.getDiagnostics().size(), \"There were unexpected diagnostic messages\");\n\t\tassertTrue(result.getCompilations().containsKey(\"HelloWorld\"), \"Class missing from compile map output\");\n\n\t\t// Assert class validity\n\t\tbyte[] classBytecode = result.getCompilations().get(\"HelloWorld\");\n\t\tJvmClassInfo classInfo = new JvmClassInfoBuilder(classBytecode).build();\n\t\tassertEquals(\"HelloWorld\", classInfo.getName(), \"Class name did not match expected value\");\n\t\tassertNotNull(classInfo.getDeclaredMethod(\"main\", \"([Ljava/lang/String;)V\"), \"Missing main method\");\n\n\t\t// Assert downgrade was a success\n\t\tassertEquals(8, classInfo.getVersion() - JavaVersion.VERSION_OFFSET, \"Class was not downgraded to Java 8\");\n\t\tclassInfo.getClassReader().accept(new ClassVisitor(RecafConstants.getAsmVersion()) {\n\t\t\t@Override\n\t\t\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\t\t\treturn new MethodVisitor(RecafConstants.getAsmVersion()) {\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {\n\t\t\t\t\t\tif (owner.equals(\"java/util/List\")) {\n\t\t\t\t\t\t\tif (name.equals(\"of\"))\n\t\t\t\t\t\t\t\tfail(\"Did not downgrade away: List.of\");\n\t\t\t\t\t\t\telse if (name.equals(\"removeLast\"))\n\t\t\t\t\t\t\t\tfail(\"Did not downgrade away: List.removeLast\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t}, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);\n\n\t\t// Assert the necessary stubs were added\n\t\tassertNotNull(result.getCompilations().get(\"xyz/wagyourtail/jvmdg/j9/stub/java_base/J_U_List\"), \"Missing stub for Java 9: List.of\");\n\t\tassertNotNull(result.getCompilations().get(\"xyz/wagyourtail/jvmdg/j21/stub/java_base/J_U_List\"), \"Missing stub for Java 21: List.removeLast\");\n\t}\n\n\t@Test\n\tvoid testJavacUsesVirtualClasspathFromWorkspace() throws IOException {\n\t\t// Create a HelloWorld that uses 'StringConsumer'\n\t\tJavacArguments arguments = new JavacArgumentsBuilder()\n\t\t\t\t.withClassName(\"HelloWorld\")\n\t\t\t\t.withClassSource(\"\"\"\n\t\t\t\t\t\timport dummy.StringConsumer;\n\t\t\t\t\t\t\n\t\t\t\t\t\tpublic class HelloWorld {\n\t\t\t\t\t\t\tpublic static void main(String[] args) {\n\t\t\t\t\t\t\t\tfor (String arg : args) {\n\t\t\t\t\t\t\t\t\tnew StringConsumer().accept(arg);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\"\"\")\n\t\t\t\t.build();\n\n\t\t// Run compiler, it should fail with no passed workspace due to the unknown 'StringConsumer'\n\t\tCompilerResult result = javac.compile(arguments, null, null);\n\t\tassertFalse(result.getDiagnostics().isEmpty(), \"Expected compilation failure\");\n\t\tassertFalse(result.getCompilations().containsKey(\"HelloWorld\"), \"Class should have failed compilation\");\n\n\t\t// First, create the class that was missing. We cannot use the existing StringConsumer because Javac will find\n\t\t// it on our class-path. So, we remap it to a different package.\n\t\tJvmClassInfo classInfo = TestClassUtils.fromRuntimeClass(StringConsumer.class);\n\t\tClassReader reader = classInfo.getClassReader();\n\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\tClassRemapper mapper = new ClassRemapper(writer, new SimpleRemapper(RecafConstants.getAsmVersion(), classInfo.getName(), \"dummy/StringConsumer\"));\n\t\treader.accept(mapper, 0);\n\t\tclassInfo = new JvmClassInfoBuilder(writer.toByteArray()).build();\n\n\t\t// Put it into a workspace and try again. Should work now that it can pull the missing class from the workspace.\n\t\tWorkspace workspace = TestClassUtils.fromBundle(TestClassUtils.fromClasses(classInfo));\n\t\tresult = javac.compile(arguments, workspace, null);\n\t\tassertEquals(0, result.getDiagnostics().size(), \"There were unexpected diagnostic messages\");\n\t\tassertTrue(result.getCompilations().containsKey(\"HelloWorld\"), \"Class missing from compile map output\");\n\n\t\t// Put it into a workspace but as an embedded resource and try again. Should still work.\n\t\tWorkspaceResource resource = new WorkspaceResourceBuilder()\n\t\t\t\t.withEmbeddedResources(Map.of(\"embed.jar\", new WorkspaceFileResourceBuilder()\n\t\t\t\t\t\t.withFileInfo(new StubFileInfo(\"embed.jar\"))\n\t\t\t\t\t\t.withJvmClassBundle(TestClassUtils.fromClasses(classInfo)).build()))\n\t\t\t\t.build();\n\t\tworkspace = new BasicWorkspace(resource);\n\t\tresult = javac.compile(arguments, workspace, null);\n\t\tassertEquals(0, result.getDiagnostics().size(), \"There were unexpected diagnostic messages\");\n\t\tassertTrue(result.getCompilations().containsKey(\"HelloWorld\"), \"Class missing from compile map output\");\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/decompile/DecompileManagerTest.java",
    "content": "package software.coley.recaf.services.decompile;\n\nimport jakarta.annotation.Nonnull;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.decompile.cfr.CfrDecompiler;\nimport software.coley.recaf.services.decompile.fallback.FallbackDecompiler;\nimport software.coley.recaf.services.decompile.filter.JvmBytecodeFilter;\nimport software.coley.recaf.services.decompile.filter.OutputTextFilter;\nimport software.coley.recaf.services.decompile.procyon.ProcyonDecompiler;\nimport software.coley.recaf.services.decompile.vineflower.VineflowerDecompiler;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.HelloWorld;\nimport software.coley.recaf.util.ReflectUtil;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.IOException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\n/**\n * Tests for {@link DecompilerManager}.\n */\npublic class DecompileManagerTest extends TestBase {\n\tprivate static final ObservableBoolean OB_TRUE = new ObservableBoolean(true);\n\tprivate static final ObservableBoolean OB_FALSE = new ObservableBoolean(false);\n\tstatic final TestJvmBytecodeFilter bytecodeFilter = new TestJvmBytecodeFilter();\n\tstatic final TestOutputTextFilter textFilter = new TestOutputTextFilter();\n\tstatic DecompilerManager decompilerManager;\n\tstatic DecompilerManagerConfig decompilerManagerConfig;\n\tstatic Workspace workspace;\n\tstatic JvmClassInfo classHelloWorld;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\tdecompilerManager = recaf.get(DecompilerManager.class);\n\n\t\t// Setup workspace to pull from\n\t\tclassHelloWorld = TestClassUtils.fromRuntimeClass(HelloWorld.class);\n\t\tworkspace = TestClassUtils.fromBundle(TestClassUtils.fromClasses(classHelloWorld));\n\t\tworkspaceManager.setCurrent(workspace);\n\t}\n\n\t@BeforeEach\n\tvoid setupEach() {\n\t\t// We don't want to cache decompilations for this test, but we also\n\t\t// do not want to edit the shared config in tests.\n\t\t// Thus, we will make new config instances each test run so there's no cross-test pollution.\n\t\tdecompilerManagerConfig = new DecompilerManagerConfig();\n\t\tdecompilerManagerConfig.getCacheDecompilations().setValue(false);\n\t\tassertDoesNotThrow(() -> ReflectUtil.quietSet(unwrapProxy(decompilerManager),\n\t\t\t\tDecompilerManager.class.getDeclaredField(\"config\"),\n\t\t\t\tdecompilerManagerConfig));\n\t}\n\n\t@Test\n\tvoid testCfr() {\n\t\tJvmDecompiler decompiler = decompilerManager.getJvmDecompiler(CfrDecompiler.NAME);\n\t\tassertNotNull(decompiler, \"CFR decompiler was never registered with manager\");\n\t\trunJvmDecompilation(decompiler);\n\t}\n\n\t@Test\n\tvoid testProcyon() {\n\t\tJvmDecompiler decompiler = decompilerManager.getJvmDecompiler(ProcyonDecompiler.NAME);\n\t\tassertNotNull(decompiler, \"Procyon decompiler was never registered with manager\");\n\t\trunJvmDecompilation(decompiler);\n\t}\n\n\t@Test\n\tvoid testVineflower() {\n\t\tJvmDecompiler decompiler = decompilerManager.getJvmDecompiler(VineflowerDecompiler.NAME);\n\t\tassertNotNull(decompiler, \"Vineflower decompiler was never registered with manager\");\n\t\trunJvmDecompilation(decompiler);\n\t}\n\n\t@Test\n\tvoid testFallback() {\n\t\tJvmDecompiler decompiler = decompilerManager.getJvmDecompiler(FallbackDecompiler.NAME);\n\t\tassertNotNull(decompiler, \"Fallback decompiler was never registered with manager\");\n\t\trunJvmDecompilation(decompiler);\n\t}\n\n\t@Test\n\tvoid testFiltersUsed() {\n\t\tJvmDecompiler decompiler = decompilerManager.getTargetJvmDecompiler();\n\t\tTestJvmBytecodeFilter bytecodeFilterSpy = spy(bytecodeFilter);\n\t\tTestOutputTextFilter textFilterSpy = spy(textFilter);\n\t\ttry {\n\t\t\t// Add input/output filters\n\t\t\tdecompilerManager.addJvmBytecodeFilter(bytecodeFilterSpy);\n\t\t\tdecompilerManager.addOutputTextFilter(textFilterSpy);\n\n\t\t\t// Decompile\n\t\t\tdecompilerManager.decompile(decompiler, workspace, classHelloWorld).get();\n\n\t\t\t// Verify each filter was called once\n\t\t\tverify(bytecodeFilterSpy, times(1)).filter(any(), any(), any());\n\t\t\tverify(textFilterSpy, times(1)).filter(any(), any(), anyString());\n\t\t} catch (Exception ex) {\n\t\t\tfail(ex);\n\t\t} finally {\n\t\t\tdecompilerManager.removeJvmBytecodeFilter(bytecodeFilterSpy);\n\t\t\tdecompilerManager.removeOutputTextFilter(textFilterSpy);\n\t\t}\n\t}\n\n\t@Test\n\tvoid testCaching() {\n\t\tdecompilerManagerConfig.getCacheDecompilations().setValue(true);\n\t\tJvmDecompiler decompiler = decompilerManager.getJvmDecompiler(CfrDecompiler.NAME);\n\t\tDecompileResult firstResult = assertDoesNotThrow(() -> decompilerManager.decompile(decompiler, workspace, classHelloWorld).get(1, TimeUnit.DAYS));\n\n\t\t// Assert that repeated decompiles use the same result (caching, should be handled by abstract base)\n\t\t// Only the manager will cache results. Using decompilers direcrly will not cache.\n\t\tassertTrue(decompilerManagerConfig.getCacheDecompilations().getValue(), \"Cache config not 'true'\");\n\t\tDecompileResult newResult = assertDoesNotThrow(() -> decompilerManager.decompile(decompiler, workspace, classHelloWorld).get(1, TimeUnit.SECONDS));\n\t\tassertSame(firstResult, newResult, \"Decompiler did not cache results\");\n\n\t\t// Change the decompiler hash. The decompiler result should change.\n\t\tdecompiler.getConfig().setHash(-1);\n\t\tnewResult = assertDoesNotThrow(() -> decompilerManager.decompile(decompiler, workspace, classHelloWorld).get(1, TimeUnit.SECONDS));\n\t\tassertNotSame(firstResult, newResult, \"Decompiler used cached result even though config hash changed\");\n\n\t\t// Verify direct decompiler usage does not cache\n\t\tDecompileResult direct1 = decompiler.decompile(workspace, classHelloWorld);\n\t\tDecompileResult direct2 = decompiler.decompile(workspace, classHelloWorld);\n\t\tassertNotSame(direct1, direct2, \"Direct decompiler use cached results unexpectedly\");\n\t}\n\n\t@Test\n\tvoid testFilterHollow() {\n\t\tString decompilationBefore = assertDoesNotThrow(() -> decompilerManager.decompile(workspace, classHelloWorld).get().getText());\n\t\tassertTrue(decompilationBefore.contains(\"\\\"Hello world\\\"\"));\n\n\t\tdecompilerManagerConfig.getFilterHollow().setValue(true);\n\n\t\t// Hollowing will remove method bodies, so the 'println' call should no longer exist in the output\n\t\tString decompilationAfter = assertDoesNotThrow(() -> decompilerManager.decompile(workspace, classHelloWorld).get().getText());\n\t\tassertFalse(decompilationAfter.contains(\"\\\"Hello world\\\"\"));\n\t}\n\n\t@Test\n\tvoid testDisplay() {\n\t\tfor (JvmDecompiler decompiler : decompilerManager.getJvmDecompilers()) {\n\t\t\tassertTrue(decompiler.toString().contains(decompiler.getName()));\n\t\t\tassertTrue(decompiler.toString().contains(decompiler.getVersion()));\n\t\t}\n\t}\n\n\t@Test\n\tvoid testComparison() {\n\t\tJvmDecompiler cfr = decompilerManager.getJvmDecompiler(CfrDecompiler.NAME);\n\t\tJvmDecompiler pro = decompilerManager.getJvmDecompiler(ProcyonDecompiler.NAME);\n\t\tassertNotNull(cfr);\n\t\tassertNotNull(pro);\n\t\tassertNotEquals(cfr, pro);\n\t\tassertNotEquals(cfr.hashCode(), pro.hashCode());\n\t}\n\n\tprivate static void runJvmDecompilation(@Nonnull JvmDecompiler decompiler) {\n\t\ttry {\n\t\t\t// Generally, you'd handle results like this, with a when-complete.\n\t\t\t// The blocking 'get' at the end is just so our test works.\n\t\t\tDecompileResult firstResult = decompilerManager.decompile(decompiler, workspace, classHelloWorld)\n\t\t\t\t\t.whenComplete((result, throwable) -> {\n\t\t\t\t\t\tassertNull(throwable);\n\n\t\t\t\t\t\t// Throwable thrown when unhandled exception occurs.\n\t\t\t\t\t\tassertEquals(DecompileResult.ResultType.SUCCESS, result.getType(), \"Decompile result was not successful\");\n\t\t\t\t\t\tassertNotNull(result.getText(), \"Decompile result missing text\");\n\t\t\t\t\t\tassertTrue(result.getText().contains(\"\\\"Hello world\\\"\"), \"Decompilation seems to be wrong\");\n\t\t\t\t\t}) // Block on this thread until we have the value.\n\t\t\t\t\t.get(5, TimeUnit.SECONDS);\n\n\t\t\t// Verify direct decompiler usage does not cache\n\t\t\tDecompileResult result = decompiler.decompile(workspace, classHelloWorld);\n\t\t\tassertNull(result.getException(), \"No exceptions should be reported during decompilation\");\n\t\t\tassertNotNull(result.getText(), \"Missing decompilation output\");\n\t\t} catch (InterruptedException e) {\n\t\t\tfail(\"Decompile was interrupted\", e);\n\t\t} catch (ExecutionException e) {\n\t\t\tfail(\"Decompile was encountered exception\", e.getCause());\n\t\t} catch (TimeoutException e) {\n\t\t\tfail(\"Decompile timed out\", e);\n\t\t}\n\t}\n\n\tstatic class TestJvmBytecodeFilter implements JvmBytecodeFilter {\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic byte[] filter(@Nonnull Workspace workspace, @Nonnull JvmClassInfo initialClassInfo, @Nonnull byte[] bytecode) {\n\t\t\treturn bytecode;\n\t\t}\n\t}\n\n\tstatic class TestOutputTextFilter implements OutputTextFilter {\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String filter(@Nonnull Workspace workspace, @Nonnull ClassInfo classInfo, @Nonnull String code) {\n\t\t\treturn code;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/decompile/FallbackDecompilerTest.java",
    "content": "package software.coley.recaf.services.decompile;\n\n\nimport jakarta.annotation.Nonnull;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.services.decompile.fallback.FallbackDecompiler;\nimport software.coley.recaf.services.decompile.fallback.print.ClassPrinter;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.AccessibleFields;\nimport software.coley.recaf.test.dummy.ClassWithAnnotation;\nimport software.coley.recaf.test.dummy.ClassWithExceptions;\nimport software.coley.recaf.test.dummy.ClassWithStaticInit;\nimport software.coley.recaf.test.dummy.DummyEmptyMap;\nimport software.coley.recaf.test.dummy.DummyEnum;\nimport software.coley.recaf.test.dummy.InvisAnnotationImpl;\nimport software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle;\n\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n * Tests for {@link FallbackDecompiler}\n */\nclass FallbackDecompilerTest {\n\tstatic final TextFormatConfig textConfig = new TextFormatConfig();\n\n\t@Test\n\tvoid fieldModifiers() {\n\t\tString decompile = decompile(AccessibleFields.class);\n\t\tassertTrue(decompile.contains(\"public static final int CONSTANT_FIELD = 16;\"));\n\t\tassertTrue(decompile.contains(\"private final int privateFinalField = 8;\"));\n\t\tassertTrue(decompile.contains(\"protected final int protectedField = 4;\"));\n\t\tassertTrue(decompile.contains(\"public final int publicField = 2;\"));\n\t\tassertTrue(decompile.contains(\"final int packageField = 1;\"));\n\t}\n\n\t@Test\n\tvoid classAnnotation() {\n\t\tString decompile = decompile(ClassWithAnnotation.class);\n\t\tassertTrue(decompile.contains(\"@AnnotationImpl(value = \\\"Hello\\\", policy = @Retention(RetentionPolicy.CLASS))\"));\n\t}\n\n\t@Test\n\tvoid annotationClass() {\n\t\tString decompile = decompile(InvisAnnotationImpl.class);\n\t\tassertTrue(decompile.contains(\"\"\"\n\t\t\t\t@Retention(RetentionPolicy.CLASS)\n\t\t\t\t@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })\n\t\t\t\tpublic @interface InvisAnnotationImpl\n\t\t\t\t\"\"\"));\n\t}\n\n\t@Test\n\tvoid throwsException() {\n\t\tString decompile = decompile(ClassWithExceptions.class);\n\t\tassertTrue(decompile.contains(\"static int readInt(Object input) throws NumberFormatException\"));\n\t}\n\n\t@Test\n\tvoid clinit() {\n\t\tString decompile = decompile(ClassWithStaticInit.class);\n\t\tassertTrue(decompile.contains(\"\\n    static {\\n\"));\n\t}\n\n\t@Test\n\tvoid enumFields() {\n\t\tString decompile = decompile(DummyEnum.class);\n\t\tassertTrue(decompile.contains(\" ONE,\"));\n\t\tassertTrue(decompile.contains(\" TWO,\"));\n\t\tassertTrue(decompile.contains(\" THREE;\"));\n\t\tassertTrue(decompile.contains(\"private static final /* synthetic */ DummyEnum[] $VALUES;\"));\n\t}\n\n\n\t@Test\n\t@Disabled(\"Need to implement signature parsing in the fallback decompiler\")\n\tvoid genericClassArgs() {\n\t\tString decompile = decompile(DummyEmptyMap.class);\n\t\tassertTrue(decompile.contains(\"class DummyEmptyMap<K, V> implements Map<K, V> {\"));\n\t}\n\n\t@Nonnull\n\tprivate static String decompile(@Nonnull Class<?> cls) {\n\t\treturn assertDoesNotThrow(() -> {\n\t\t\tBasicJvmClassBundle bundle = TestClassUtils.fromClasses(cls);\n\t\t\treturn new ClassPrinter(textConfig, bundle.get(cls.getName().replace('.', '/'))).print();\n\t\t});\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/deobfuscation/BaseDeobfuscationTest.java",
    "content": "package software.coley.recaf.services.deobfuscation;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.compile.JavaClassRepresentation;\nimport me.darknet.assembler.compile.visitor.JavaCompileResult;\nimport me.darknet.assembler.error.Error;\nimport me.darknet.assembler.error.Result;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.StubClassInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.services.assembler.AssemblerPipelineManager;\nimport software.coley.recaf.services.assembler.JvmAssemblerPipeline;\nimport software.coley.recaf.services.compile.CompilerResult;\nimport software.coley.recaf.services.compile.JavacArguments;\nimport software.coley.recaf.services.compile.JavacArgumentsBuilder;\nimport software.coley.recaf.services.compile.JavacCompiler;\nimport software.coley.recaf.services.decompile.DecompileResult;\nimport software.coley.recaf.services.decompile.JvmDecompiler;\nimport software.coley.recaf.services.decompile.cfr.CfrConfig;\nimport software.coley.recaf.services.decompile.cfr.CfrDecompiler;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformResult;\nimport software.coley.recaf.services.transform.TransformationApplier;\nimport software.coley.recaf.services.transform.TransformationApplierService;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\nimport software.coley.recaf.workspace.model.EmptyWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.BasicWorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Common setup for deobfuscation tests.\n */\npublic abstract class BaseDeobfuscationTest extends TestBase {\n\tprivate static final boolean PRINT_BEFORE_AFTER = true;\n\tprotected static final String CLASS_NAME = \"Example\";\n\tprotected static final String EXCEPTION_NAME = \"BogusException\";\n\tprivate static JvmAssemblerPipeline assembler;\n\tprivate static TransformationApplierService transformationApplierService;\n\tprivate static JavacCompiler javac;\n\tprivate static JvmDecompiler decompiler;\n\tprivate static Workspace workspace;\n\n\t@BeforeAll\n\tstatic void setupServices() {\n\t\ttransformationApplierService = recaf.get(TransformationApplierService.class);\n\t\tjavac = recaf.get(JavacCompiler.class);\n\t\tdecompiler = new CfrDecompiler(recaf.get(WorkspaceManager.class), new CfrConfig());\n\t}\n\n\t@BeforeEach\n\tvoid setupWorkspace() {\n\t\tworkspace = new BasicWorkspace(new WorkspaceResourceBuilder().build());\n\t\tworkspace.getPrimaryResource().getJvmClassBundle().put(TestClassUtils.createClass(EXCEPTION_NAME, n -> n.superName = \"java/lang/Exception\"));\n\t\tworkspaceManager.setCurrentIgnoringConditions(workspace);\n\t\tassembler = recaf.get(AssemblerPipelineManager.class).newJvmAssemblerPipeline(workspace);\n\t}\n\n\tprotected void validateNoTransformation(@Nonnull String assembly, @Nonnull List<Class<? extends JvmClassTransformer>> transformers) {\n\t\tboolean isFullBody = assembly.contains(\".class\");\n\t\tJvmClassInfo cls = assemble(assembly, isFullBody);\n\n\t\t// Transforming should not actually result in any changes\n\t\tJvmTransformResult result = assertDoesNotThrow(() -> newApplier().transformJvm(transformers));\n\t\tassertTrue(result.getTransformerFailures().isEmpty(), \"There were transformation failures\");\n\t\tif (!result.getTransformedClasses().isEmpty()) {\n\t\t\tString transformedDisassembly = disassembleTransformed(result, isFullBody);\n\t\t\tfail(\"There were unexpected transformations applied:\\n\\n\" + transformedDisassembly);\n\t\t}\n\t}\n\n\tprotected void validateBeforeAfterDecompile(@Nonnull String assembly, @Nonnull List<Class<? extends JvmClassTransformer>> transformers,\n\t                                            @Nonnull String expectedBefore, @Nullable String expectedAfter) {\n\t\tboolean isFullBody = assembly.contains(\".class\");\n\t\tJvmClassInfo cls = assemble(assembly, isFullBody);\n\n\t\t// Before transformation, check that the expected before-state is matched\n\t\tString initialDecompile = decompile(cls);\n\t\tif (PRINT_BEFORE_AFTER) System.out.println(\"======== BEFORE ========\\n\" + initialDecompile);\n\t\tassertTrue(initialDecompile.contains(expectedBefore));\n\n\t\t// Run the transformer\n\t\tJvmTransformResult result = assertDoesNotThrow(() -> newApplier().transformJvm(transformers));\n\t\tassertTrue(result.getTransformerFailures().isEmpty(), \"There were transformation failures\");\n\t\tassertEquals(1, result.getTransformedClasses().size(), \"Expected transformation to be applied\");\n\n\t\t// Validate output has been transformed to match the expected after-state.\n\t\tString transformedDecompile = decompileTransformed(result);\n\t\tif (PRINT_BEFORE_AFTER) System.out.println(\"========= AFTER ========\\n\" + transformedDecompile);\n\t\tif (expectedAfter == null)\n\t\t\t// If the 'after' is null, we should just check if the 'before' no longer exists\n\t\t\tassertFalse(transformedDecompile.contains(expectedBefore));\n\t\telse\n\t\t\t// Otherwise, check if the 'after' exists\n\t\t\tassertTrue(transformedDecompile.contains(expectedAfter));\n\t}\n\n\tprotected void validateAfterAssembly(@Nonnull String assembly, @Nonnull List<Class<? extends JvmClassTransformer>> transformers,\n\t                                     @Nonnull Consumer<String> assertionChecker) {\n\t\tif (PRINT_BEFORE_AFTER) System.out.println(\"======== BEFORE ========\\n\" + assembly);\n\t\tboolean isFullBody = assembly.contains(\".class\");\n\n\t\t// Run the transformer.\n\t\tJvmClassInfo cls = assemble(assembly, isFullBody);\n\t\tJvmTransformResult result = assertDoesNotThrow(() -> newApplier().transformJvm(transformers));\n\t\tassertTrue(result.getTransformerFailures().isEmpty(), \"There were transformation failures\");\n\t\tassertEquals(1, result.getTransformedClasses().size(), \"Expected transformation to be applied\");\n\n\t\t// Validate output has been transformed to match the expected after-state.\n\t\tString transformedDisassembly = disassembleTransformed(result, isFullBody);\n\t\tif (PRINT_BEFORE_AFTER) System.out.println(\"========= AFTER ========\\n\" + transformedDisassembly);\n\t\tassertionChecker.accept(transformedDisassembly);\n\t}\n\n\tprotected void validateAfterRepeatedAssembly(@Nonnull String assembly, @Nonnull List<Class<? extends JvmClassTransformer>> transformers,\n\t                                             @Nonnull Consumer<String> assertionChecker) {\n\t\tif (PRINT_BEFORE_AFTER) System.out.println(\"======== BEFORE ========\\n\" + assembly);\n\t\tboolean isFullBody = assembly.contains(\".class\");\n\n\t\t// Run the transformer until no changes are observed.\n\t\tJvmClassInfo cls = assemble(assembly, isFullBody);\n\t\tJvmTransformResult result = assertDoesNotThrow(() -> newApplier(10).transformJvm(transformers));\n\n\t\t// No transform step should fail.\n\t\tresult.getTransformerFailures().forEach((path, failureMap) -> {\n\t\t\tSystem.err.println(path.getValue().getName());\n\t\t\tfailureMap.forEach((transformer, error) -> {\n\t\t\t\tSystem.err.println(transformer.getSimpleName());\n\t\t\t\terror.printStackTrace(System.err);\n\t\t\t\tSystem.err.println();\n\t\t\t});\n\t\t});\n\n\t\t// Validate output has been transformed without any errors.\n\t\tassertTrue(result.getTransformerFailures().isEmpty(), \"There were transformation failures\");\n\t\tassertEquals(1, result.getTransformedClasses().size(), \"Expected transformation to be applied\");\n\n\t\t// Update the assembly to hold the transformed output.\n\t\t// Validate the new disassembly matches our assertion checker's assumptions.\n\t\tassembly = disassembleTransformed(result, isFullBody);\n\t\tif (PRINT_BEFORE_AFTER) System.out.println(\"========= AFTER ========\\n\" + assembly);\n\t\tassertionChecker.accept(assembly);\n\t}\n\n\tprotected void validateMappingAfterAssembly(@Nonnull String assembly, @Nonnull List<Class<? extends JvmClassTransformer>> transformers,\n\t                                            @Nonnull Consumer<String> assertionChecker) {\n\t\tif (PRINT_BEFORE_AFTER) System.out.println(\"======== BEFORE ========\\n\" + assembly);\n\t\tboolean isFullBody = assembly.contains(\".class\");\n\n\t\t// Run the transformer\n\t\tJvmClassInfo cls = assemble(assembly, isFullBody);\n\t\tJvmTransformResult result = assertDoesNotThrow(() -> newApplier().transformJvm(transformers));\n\t\tassertTrue(result.getTransformerFailures().isEmpty(), \"There were transformation failures\");\n\t\tassertFalse(result.getMappingsToApply().isEmpty(), \"Expected transformation to register mappings\");\n\n\t\t// Validate output has been transformed to match the expected after-state.\n\t\tString transformedDisassembly = disassembleTransformed(result, isFullBody);\n\t\tif (PRINT_BEFORE_AFTER) System.out.println(\"========= AFTER ========\\n\" + transformedDisassembly);\n\t\tassertionChecker.accept(transformedDisassembly);\n\t}\n\n\t@Nonnull\n\tprotected String disassembleTransformed(@Nonnull JvmTransformResult result, boolean isFullBody) {\n\t\tresult.apply();\n\t\tJvmClassBundle bundle = workspace.getPrimaryResource().getJvmClassBundle();\n\t\tWorkspaceResource resource = workspace.getPrimaryResource();\n\t\tJvmClassInfo cls = bundle.get(CLASS_NAME);\n\t\tResult<String> disassembly;\n\t\tif (isFullBody) {\n\t\t\tClassPathNode path = PathNodes.classPath(workspace, resource, bundle, cls);\n\t\t\tdisassembly = assembler.disassemble(path);\n\t\t} else {\n\t\t\tMethodMember method = cls.getFirstDeclaredMethodByName(\"example\");\n\t\t\tif (method == null)\n\t\t\t\tfail(\"Failed to find 'example' method, cannot disassemble\");\n\t\t\tClassMemberPathNode path = PathNodes.memberPath(workspace, resource, bundle, cls, method);\n\t\t\tdisassembly = assembler.disassemble(path);\n\t\t}\n\n\t\tif (disassembly.isOk())\n\t\t\treturn disassembly.get();\n\t\tfail(disassembly.errors().stream().map(Error::toString).collect(Collectors.joining(\"\\n\")));\n\t\treturn \"<error>\";\n\t}\n\n\t@Nonnull\n\tprotected String decompileTransformed(@Nonnull JvmTransformResult result) {\n\t\tresult.apply();\n\t\tJvmClassBundle bundle = workspace.getPrimaryResource().getJvmClassBundle();\n\t\tJvmClassInfo cls = bundle.get(CLASS_NAME);\n\t\treturn decompile(cls);\n\t}\n\n\t@Nonnull\n\tprotected String decompile(@Nonnull JvmClassInfo cls) {\n\t\tDecompileResult result = decompiler.decompile(workspace, cls);\n\t\tif (result.getText() == null)\n\t\t\tfail(\"Missing decompilation result\");\n\t\treturn result.getText();\n\t}\n\n\t@Nonnull\n\tprotected String compile(@Nonnull String src, Class<?>... importedType) {\n\t\tWorkspaceResource resource = new WorkspaceResourceBuilder().build();\n\t\tJvmClassBundle bundle = resource.getJvmClassBundle();\n\t\tWorkspace workspace = new BasicWorkspace(resource);\n\t\tJavacArguments args = new JavacArgumentsBuilder()\n\t\t\t\t.withClassName(CLASS_NAME)\n\t\t\t\t.withClassSource(\"%IMPORTS%\\nclass %NAME% {\\n%SRC%\\n}\"\n\t\t\t\t\t\t.replace(\"%IMPORTS%\", Arrays.stream(importedType).map(c -> \"import \" + c.getName() + \";\").collect(Collectors.joining(\"\\n\")))\n\t\t\t\t\t\t.replace(\"%NAME%\", CLASS_NAME)\n\t\t\t\t\t\t.replace(\"%SRC%\", src))\n\t\t\t\t.build();\n\t\tCompilerResult result = javac.compile(args, workspace, null);\n\t\tif (result.wasSuccess())\n\t\t\tresult.getCompilations().forEach((name, code) -> bundle.put(name, new JvmClassInfoBuilder(code).build()));\n\t\telse\n\t\t\tfail(\"Failed to compile test input\");\n\t\tJvmClassInfo cls = bundle.get(CLASS_NAME);\n\t\treturn assembler.disassemble(PathNodes.classPath(workspace, resource, bundle, cls)).get();\n\t}\n\n\t@Nonnull\n\tprotected JvmClassInfo assemble(@Nonnull String body, boolean isFullBody) {\n\t\tString assembly = isFullBody ? body : \"\"\"\n\t\t\t\t.super java/lang/Object\n\t\t\t\t.class public super %NAME% {\n\t\t\t\t%CODE%\n\t\t\t\t}\n\t\t\t\t\"\"\".replace(\"%NAME%\", CLASS_NAME).replace(\"%CODE%\", body);\n\t\tWorkspaceResource resource = workspace.getPrimaryResource();\n\t\tJvmClassBundle bundle = resource.getJvmClassBundle();\n\t\tClassPathNode path = PathNodes.classPath(workspace, resource, bundle, new StubClassInfo(CLASS_NAME).asJvmClass());\n\t\tResult<JavaCompileResult> result = assembler.tokenize(assembly, \"<assembly>\")\n\t\t\t\t.flatMap(assembler::roughParse)\n\t\t\t\t.flatMap(assembler::concreteParse)\n\t\t\t\t.flatMap(concreteAst -> assembler.assemble(concreteAst, path))\n\t\t\t\t.ifErr(errors -> fail(\"Errors assembling test input:\\n - \" + errors.stream().map(Error::toString).collect(Collectors.joining(\"\\n - \"))));\n\t\tJavaClassRepresentation representation = result.get().representation();\n\t\tif (representation == null) fail(\"No assembler output for test case\");\n\t\tJvmClassInfo cls = new JvmClassInfoBuilder(representation.classFile()).build();\n\t\tbundle.put(cls);\n\t\treturn cls;\n\t}\n\n\t@Nonnull\n\tprotected TransformationApplier newApplier() {\n\t\treturn newApplier(1);\n\t}\n\n\t@Nonnull\n\tprotected TransformationApplier newApplier(int passCount) {\n\t\tTransformationApplier applier = transformationApplierService.newApplierForCurrentWorkspace();\n\t\tassertNotNull(applier);\n\t\tapplier.setMaxPasses(passCount);\n\t\treturn applier;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/deobfuscation/CycleRemovingTest.java",
    "content": "package software.coley.recaf.services.deobfuscation;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.Opcodes;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.services.deobfuscation.transform.generic.CycleClassRemovingTransformer;\nimport software.coley.recaf.services.transform.JvmTransformResult;\nimport software.coley.recaf.services.transform.TransformationApplier;\nimport software.coley.recaf.services.transform.TransformationApplierService;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder;\n\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class CycleRemovingTest extends TestBase {\n\tprivate static TransformationApplierService transformationApplierService;\n\tprivate static TransformationApplier transformationApplier;\n\tprivate static Workspace workspace;\n\n\t@BeforeAll\n\tstatic void setupServices() {\n\t\ttransformationApplierService = recaf.get(TransformationApplierService.class);\n\t}\n\n\t@BeforeEach\n\tvoid setupWorkspace() {\n\t\tworkspace = new BasicWorkspace(new WorkspaceResourceBuilder().build());\n\t\tworkspaceManager.setCurrentIgnoringConditions(workspace);\n\t\ttransformationApplier = transformationApplierService.newApplierForCurrentWorkspace();\n\t}\n\n\t@Test\n\tvoid testCycleViaExtends() {\n\t\tClassWriter cw = new ClassWriter(0);\n\t\tcw.visit(Opcodes.V1_8, 0, \"Loop\", null, \"Loop\", null);\n\t\tcw.visitEnd();\n\t\tbyte[] bytes = cw.toByteArray();\n\n\t\tassertCycleRemoved(bytes);\n\t}\n\n\t@Test\n\tvoid testCycleViaImplements() {\n\t\tClassWriter cw = new ClassWriter(0);\n\t\tcw.visit(Opcodes.V1_8, Opcodes.ACC_INTERFACE, \"Loop\", null, \"java/lang/Object\", new String[]{\"Loop\"});\n\t\tcw.visitEnd();\n\t\tbyte[] bytes = cw.toByteArray();\n\n\t\tassertCycleRemoved(bytes);\n\t}\n\n\tprivate static void assertCycleRemoved(byte[] bytes) {\n\t\t// Initial workspace state\n\t\tJvmClassBundle bundle = workspace.getPrimaryResource().getJvmClassBundle();\n\t\tbundle.put(new JvmClassInfoBuilder(bytes).build());\n\t\tassertEquals(1, bundle.size());\n\n\t\t// Use cycle removing transformer and observe the class being removed from the workspace\n\t\tJvmTransformResult result = assertDoesNotThrow(() -> transformationApplier.transformJvm(List.of(CycleClassRemovingTransformer.class)));\n\t\tassertEquals(1, result.getClassesToRemove().size());\n\t\tassertEquals(1, bundle.size());\n\t\tresult.apply();\n\t\tassertEquals(0, bundle.size());\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/deobfuscation/EvaluatorTest.java",
    "content": "package software.coley.recaf.services.deobfuscation;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.transform.JvmTransformerContext;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.util.analysis.ReInterpreter;\nimport software.coley.recaf.util.analysis.eval.EvaluationFailureResult;\nimport software.coley.recaf.util.analysis.eval.EvaluationResult;\nimport software.coley.recaf.util.analysis.eval.EvaluationThrowsResult;\nimport software.coley.recaf.util.analysis.eval.EvaluationYieldResult;\nimport software.coley.recaf.util.analysis.eval.Evaluator;\nimport software.coley.recaf.util.analysis.eval.FieldCacheManager;\nimport software.coley.recaf.util.analysis.value.IntValue;\nimport software.coley.recaf.util.analysis.value.ObjectValue;\nimport software.coley.recaf.util.analysis.value.ReValue;\nimport software.coley.recaf.util.analysis.value.StringValue;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Random;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\n\npublic class EvaluatorTest extends BaseDeobfuscationTest {\n\t@Test\n\tvoid testSimpleCharArrayToString() {\n\t\tString src = \"\"\"\n\t\t\t\t.method static decrypt (I)Ljava/lang/String; {\n\t\t\t\t    parameters: { length },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iload length\n\t\t\t\t        newarray char\n\t\t\t\t        astore chars\n\t\t\t\t    B:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore i\n\t\t\t\t    C:\n\t\t\t\t        iload i\n\t\t\t\t        iload length\n\t\t\t\t        if_icmpge F\n\t\t\t\t    D:\n\t\t\t\t        line 7\n\t\t\t\t        aload chars\n\t\t\t\t        iload i\n\t\t\t\t        bipush 97 // 'a'\n\t\t\t\t        iload i\n\t\t\t\t        iadd\n\t\t\t\t        i2c\n\t\t\t\t        castore\n\t\t\t\t    E:\n\t\t\t\t        iinc i 1\n\t\t\t\t        goto C\n\t\t\t\t    F:\n\t\t\t\t        aload chars\n\t\t\t\t        invokestatic java/lang/String.valueOf ([C)Ljava/lang/String;\n\t\t\t\t        areturn\n\t\t\t\t    G:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tReValue retVal = evaluate(src, \"decrypt\", \"(I)Ljava/lang/String;\", null,\n\t\t\t\tList.of(IntValue.of(26)));\n\t\tif (retVal instanceof StringValue str)\n\t\t\tassertEquals(\"abcdefghijklmnopqrstuvwxyz\", str.getText().orElse(null));\n\t\telse\n\t\t\tfail(\"Evaluation failure\");\n\t}\n\n\t@Test\n\tvoid testXorString() {\n\t\tString src = \"\"\"\n\t\t\t\t.method static decrypt (Ljava/lang/String;I)Ljava/lang/String; {\n\t\t\t\t    parameters: { input, xor },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aload input\n\t\t\t\t        invokevirtual java/lang/String.length ()I\n\t\t\t\t        istore length\n\t\t\t\t    B:\n\t\t\t\t        iload length\n\t\t\t\t        newarray char\n\t\t\t\t        astore chars\n\t\t\t\t    C:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore i\n\t\t\t\t    D:\n\t\t\t\t        iload i\n\t\t\t\t        iload length\n\t\t\t\t        if_icmpge G\n\t\t\t\t    E:\n\t\t\t\t        aload chars\n\t\t\t\t        iload i\n\t\t\t\t        aload input\n\t\t\t\t        iload i\n\t\t\t\t        invokevirtual java/lang/String.charAt (I)C\n\t\t\t\t        iload xor\n\t\t\t\t        ixor\n\t\t\t\t        i2c\n\t\t\t\t        castore\n\t\t\t\t    F:\n\t\t\t\t        iinc i 1\n\t\t\t\t        goto D\n\t\t\t\t    G:\n\t\t\t\t        aload chars\n\t\t\t\t        invokestatic java/lang/String.valueOf ([C)Ljava/lang/String;\n\t\t\t\t        areturn\n\t\t\t\t    H:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tReValue retVal = evaluate(src, \"decrypt\", \"(Ljava/lang/String;I)Ljava/lang/String;\", null,\n\t\t\t\tList.of(ObjectValue.string(\"㘯㘂㘋㘋㘈㙇㘐㘈㘕㘋㘃\"), IntValue.of(0b11011001100111)));\n\t\tif (retVal instanceof StringValue str)\n\t\t\tassertEquals(\"Hello world\", str.getText().orElse(null));\n\t\telse\n\t\t\tfail(\"Evaluation failure, unexpected return value: \" + retVal);\n\t}\n\n\t@Test\n\tvoid testStringBuilder() {\n\t\tString compiled = compile(\"\"\"\n\t\t\t\tstatic String make() { return new StringBuilder().append('T').append(\"est\").toString(); }\n\t\t\t\tstatic String makeTwo() { return make().repeat(2); }\n\t\t\t\t\"\"\");\n\t\tReValue retVal = evaluate(compiled, \"makeTwo\", \"()Ljava/lang/String;\", null, List.of());\n\t\tif (retVal instanceof StringValue str)\n\t\t\tassertEquals(\"TestTest\", str.getText().orElse(null));\n\t\telse\n\t\t\tfail(\"Evaluation failure, unexpected return value: \" + retVal);\n\t}\n\n\t@Test\n\tvoid testRandom() {\n\t\tString compiled = compile(\"\"\"\n\t\t\t\tstatic int notSoRandom() { return new Random(1234).nextInt(1000); }\n\t\t\t\t\"\"\", Random.class);\n\t\tReValue retVal = evaluate(compiled, \"notSoRandom\", \"()I\", null, List.of());\n\t\tif (retVal instanceof IntValue str)\n\t\t\tassertEquals(new Random(1234).nextInt(1000), str.value().orElseThrow());\n\t\telse\n\t\t\tfail(\"Evaluation failure, unexpected return value: \" + retVal);\n\t}\n\n\t@Test\n\tvoid testArrayList() {\n\t\tString compiled = compile(\"\"\"\n\t\t\t\tString helloWorld() {\n\t\t\t\t    List<String> strings = new ArrayList<>();\n\t\t\t\t    strings.add(\"World\");\n\t\t\t\t    strings.add(0, \"Hello\");\n\t\t\t\t    CharSequence[] arr = new CharSequence[strings.size()];\n\t\t\t\t    for (int i = 0; i < strings.size(); i++) arr[i] = strings.get(i);\n\t\t\t\t    return String.join(\" \", arr);\n\t\t\t\t}\n\t\t\t\t\"\"\", List.class, ArrayList.class);\n\t\tReValue retVal = evaluate(compiled, \"helloWorld\", \"()Ljava/lang/String;\", null, List.of());\n\t\tif (retVal instanceof StringValue str)\n\t\t\tassertEquals(\"Hello World\", str.getText().orElseThrow());\n\t\telse\n\t\t\tfail(\"Evaluation failure, unexpected return value: \" + retVal);\n\t}\n\n\t@Nonnull\n\tprivate ReValue evaluate(@Nonnull String src, @Nonnull String name, @Nonnull String desc,\n\t                         @Nullable ReValue classInstance, @Nonnull List<ReValue> parameters) {\n\t\tJvmClassInfo assembled = assemble(src, src.contains(\".class\"));\n\t\tWorkspace workspace = TestClassUtils.fromBundle(TestClassUtils.fromClasses(assembled));\n\t\tJvmTransformerContext ctx = new JvmTransformerContext(workspace, workspace.getPrimaryResource(), Collections.emptyList());\n\t\tReInterpreter interpreter = ctx.newInterpreter(new InheritanceGraph(workspace));\n\t\tEvaluationResult result = new Evaluator(workspace, interpreter, new FieldCacheManager(), 1000, false)\n\t\t\t\t.evaluate(CLASS_NAME, name, desc, classInstance, parameters);\n\t\tswitch (result) {\n\t\t\tcase EvaluationYieldResult(ReValue value) -> {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t\tcase EvaluationFailureResult fail -> fail(\"Evaluation failed\", fail.cause());\n\t\t\tcase EvaluationThrowsResult(ReValue exception) ->\n\t\t\t\t\tfail(\"Evaluation yielded a thrown exception: \" + exception);\n\t\t\tdefault -> {}\n\t\t}\n\n\t\t// Won't reach here due to calls to 'fail()' above, but the compiler doesn't know that.\n\t\tthrow new IllegalStateException();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/deobfuscation/FoldingDeobfuscationTest.java",
    "content": "package software.coley.recaf.services.deobfuscation;\n\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.services.deobfuscation.transform.generic.CallResultInliningTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.DeadCodeRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.GotoInliningTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.OpaqueConstantFoldingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.OpaquePredicateFoldingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.VariableFoldingTransformer;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class FoldingDeobfuscationTest extends BaseDeobfuscationTest {\n\t@Test\n\tvoid foldIntegerMath() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_1\n\t\t\t\t        iconst_1\n\t\t\t\t        iadd\n\t\t\t\t        // 1 + 1  --> 2\n\t\t\t\t        ineg\n\t\t\t\t        // 2      --> -2\n\t\t\t\t        iconst_2\n\t\t\t\t        imul\n\t\t\t\t        // -2 x 2 --> -4\n\t\t\t\t        iconst_4\n\t\t\t\t        idiv\n\t\t\t\t        // -4 / 4 --> -1\n\t\t\t\t        iconst_m1\n\t\t\t\t        isub\n\t\t\t\t        // -1 --1 --> 0\n\t\t\t\t        iconst_1\n\t\t\t\t        ior\n\t\t\t\t        // 1 | 0 --> 1\n\t\t\t\t        iconst_1\n\t\t\t\t        ishl\n\t\t\t\t        // 1 << 1 --> 2\n\t\t\t\t        iconst_1\n\t\t\t\t        ishr\n\t\t\t\t        // 2 >> 1 --> 1\n\t\t\t\t        iconst_2\n\t\t\t\t        iand\n\t\t\t\t        // 1 & 2 --> 0\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_0\", dis), \"Expected to fold to 0\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldUnknownIntegerMath() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (I)I {\n\t\t\t\t\tparameters: { x },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        // x + 0 --> x\n\t\t\t\t        iload x\n\t\t\t\t        iconst_0\n\t\t\t\t        iadd\n\t\t\t\t\n\t\t\t\t        // x * 1 --> x\n\t\t\t\t        iconst_1\n\t\t\t\t        imul\n\t\t\t\t\n\t\t\t\t        // x / 1 --> x\n\t\t\t\t        iconst_1\n\t\t\t\t        idiv\n\t\t\t\t\n\t\t\t\t        // x | 0 --> x\n\t\t\t\t        iconst_0\n\t\t\t\t        ior\n\t\t\t\t\n\t\t\t\t        // x & -1 --> x\n\t\t\t\t        iconst_m1\n\t\t\t\t        iand\n\t\t\t\t\n\t\t\t\t        // x ^ 0 --> x\n\t\t\t\t        iconst_0\n\t\t\t\t        ixor\n\t\t\t\t\n\t\t\t\t        // (x << 0) --> x\n\t\t\t\t        iconst_0\n\t\t\t\t        ishl\n\t\t\t\t\n\t\t\t\t        // (x >> 0) --> x\n\t\t\t\t        iconst_0\n\t\t\t\t        ishr\n\t\t\t\t\n\t\t\t\t        // (x >>> 0) --> x\n\t\t\t\t        iconst_0\n\t\t\t\t        iushr\n\t\t\t\t\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\t// Should fold to just \"return x\"\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_0\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_1\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_m1\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"imul\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"idiv\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"ior\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"iand\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"ixor\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"ishl\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"ishr\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"iushr\", dis));\n\t\t\tassertEquals(1, StringUtil.count(\"iload x\", dis));\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldFloatMath() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()F {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        fconst_1\n\t\t\t\t        fconst_1\n\t\t\t\t        fadd\n\t\t\t\t        // 1 + 1  --> 2\n\t\t\t\t        fneg\n\t\t\t\t        // 2      --> -2\n\t\t\t\t        fconst_2\n\t\t\t\t        fmul\n\t\t\t\t        // -2 x 2 --> -4\n\t\t\t\t        ldc 4F\n\t\t\t\t        fdiv\n\t\t\t\t        // -4 / 4 --> -1\n\t\t\t\t        ldc -1F\n\t\t\t\t        fsub\n\t\t\t\t        // -1 --1 --> 0\n\t\t\t\t        freturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"fconst_0\", dis), \"Expected to fold to 0F\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldDoubleMath() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()D {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        dconst_1\n\t\t\t\t        dconst_1\n\t\t\t\t        dadd\n\t\t\t\t        // 1 + 1  --> 2\n\t\t\t\t        dneg\n\t\t\t\t        // 2      --> -2\n\t\t\t\t        ldc 2.0\n\t\t\t\t        dmul\n\t\t\t\t        // -2 x 2 --> -4\n\t\t\t\t        ldc 4.0\n\t\t\t\t        ddiv\n\t\t\t\t        // -4 / 4 --> -1\n\t\t\t\t        ldc -1.0\n\t\t\t\t        dsub\n\t\t\t\t        // -1 --1 --> 0\n\t\t\t\t        dreturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"dconst_0\", dis), \"Expected to fold to 0.0\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldLongMath() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()J {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        lconst_1\n\t\t\t\t        lconst_1\n\t\t\t\t        ladd\n\t\t\t\t        // 1 + 1  --> 2\n\t\t\t\t        lneg\n\t\t\t\t        // 2      --> -2\n\t\t\t\t        ldc 2L\n\t\t\t\t        lmul\n\t\t\t\t        // -2 x 2 --> -4\n\t\t\t\t        ldc 4L\n\t\t\t\t        ldiv\n\t\t\t\t        // -4 / 4 --> -1\n\t\t\t\t        ldc -1L\n\t\t\t\t        lsub\n\t\t\t\t        // -1 --1 --> 0\n\t\t\t\t        lreturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"lconst_0\", dis), \"Expected to fold to 0L\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldIntMathWithSwap() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_3\n\t\t\t\t        iconst_2\n\t\t\t\t        swap\n\t\t\t\t        ishl\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"swap\", dis), \"Expected to stack operation\");\n\t\t\tassertEquals(0, StringUtil.count(\"ishl\", dis), \"Expected to math operation\");\n\t\t\tassertEquals(1, StringUtil.count(\"bipush 16\", dis), \"Expected to fold to 16 from (2 << 3)\");\n\t\t});\n\n\t\t// Same thing but with two swaps\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_3\n\t\t\t\t        iconst_2\n\t\t\t\t        swap\n\t\t\t\t        swap\n\t\t\t\t        ishl\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"swap\", dis), \"Expected to stack operation\");\n\t\t\tassertEquals(0, StringUtil.count(\"ishl\", dis), \"Expected to math operation\");\n\t\t\tassertEquals(1, StringUtil.count(\"bipush 12\", dis), \"Expected to fold to 16 from (3 << 2)\");\n\t\t});\n\n\t\t// Same thing but with three swaps\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_3\n\t\t\t\t        iconst_2\n\t\t\t\t        swap\n\t\t\t\t        swap\n\t\t\t\t        swap\n\t\t\t\t        ishl\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"swap\", dis), \"Expected to stack operation\");\n\t\t\tassertEquals(0, StringUtil.count(\"ishl\", dis), \"Expected to math operation\");\n\t\t\tassertEquals(1, StringUtil.count(\"bipush 16\", dis), \"Expected to fold to 16 from (2 << 3)\");\n\t\t});\n\n\t\t// Similar again, but with more stack manipulation.\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_1 // [1]\n\t\t\t\t        iconst_2 // [1, 2]\n\t\t\t\t        swap     // [2, 1]\n\t\t\t\t        swap     // [1, 2]\n\t\t\t\t        swap     // [2, 1]\n\t\t\t\t        dup2     // [2, 1, 2, 1]\n\t\t\t\t        swap     // [2, 1, 1, 2]\n\t\t\t\t        pop2     // [2, 1]\n\t\t\t\t        swap     // [1, 2]\n\t\t\t\t        dup2     // [1, 2, 1, 2]\n\t\t\t\t        swap     // [1, 2, 2, 1]\n\t\t\t\t        dup2_x2  // [2, 1, 1, 2, 2, 1]\n\t\t\t\t        pop      // [2, 1, 1, 2, 2]\n\t\t\t\t        imul     // [2, 1, 1, 4]\n\t\t\t\t        swap     // [2, 1, 4, 1]\n\t\t\t\t        iconst_1 // [2, 1, 4, 1, 1]\n\t\t\t\t        iadd     // [2, 1, 4, 2]\n\t\t\t\t        swap     // [2, 1, 2, 4]\n\t\t\t\t        dup_x2   // [2, 4, 1, 2, 4]\n\t\t\t\t        pop2     // [2, 4, 1]\n\t\t\t\t        ishl     // [2, 8]\n\t\t\t\t        swap     // [8, 2]\n\t\t\t\t        imul     // [16]\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"swap\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"pop\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"imul\", dis));\n\t\t\tassertEquals(0, StringUtil.count(\"ishl\", dis));\n\t\t\tassertEquals(1, StringUtil.count(\"bipush 16\", dis), \"Expected to fold to 16\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldLongWithDup2NotConfusedByPrecedingIntOnStack() {\n\t\t// Ensures that the stack dup2 operation folds correctly:\n\t\t//  [int, long] --> [int, long, long]\n\t\t// The preceding int shouldn't confuse the fold logic.\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()J {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        // 1L + 1L = 2L\n\t\t\t\t        lconst_1\n\t\t\t\t        dup2\n\t\t\t\t        ladd\n\t\t\t\t        // Store var so that we can pop the int off the stack\n\t\t\t\t        lstore tmp\n\t\t\t\t        pop\n\t\t\t\t        lload tmp\n\t\t\t\t        lreturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"lconst_1\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup2\", dis), \"Expected to stack operation\");\n\t\t\tassertEquals(0, StringUtil.count(\"ladd\", dis), \"Expected to operation\");\n\t\t\tassertEquals(1, StringUtil.count(\"ldc 2L\", dis), \"Expected to fold stack operation to 2L\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldLongWithDup2X1NotConfusedByPrecedingIntOnStack() {\n\t\t// Ensures that the stack dup2_x1 operation folds correctly:\n\t\t//  [int, long] --> [long, int, long]\n\t\t// The preceding int shouldn't confuse the stack simplification logic.\n\t\t// This in turn will allow the value folding step to fold the whole method.\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()J {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        lconst_1\n\t\t\t\t        dup2_x1\n\t\t\t\t        // Store var so that we can pop the int off the stack\n\t\t\t\t        lstore tmp\n\t\t\t\t        pop\n\t\t\t\t        lconst_1\n\t\t\t\t        ladd\n\t\t\t\t        // The duplicated long below the int should be all that remains\n\t\t\t\t        lreturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class, VariableFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"lconst_1\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_0\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(1, StringUtil.count(\"ldc 2L\", dis), \"Expected to fold stack operation to 2L\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldPopsToNothing() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_3\n\t\t\t\t        iconst_2\n\t\t\t\t        iconst_1\n\t\t\t\t        pop2\n\t\t\t\t        pop\n\t\t\t\t        return\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to remove redundant popped values\");\n\t\t\tassertEquals(0, StringUtil.count(\"pop\", dis), \"Expected to remove pop operations\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldPopsWithDupX1ToNothing() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_2\n\t\t\t\t        iconst_1\n\t\t\t\t        dup_x1\n\t\t\t\t        pop2\n\t\t\t\t        pop\n\t\t\t\t        return\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to remove redundant popped values\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to remove redundant popped values\");\n\t\t\tassertEquals(0, StringUtil.count(\"pop\", dis), \"Expected to remove pop operations\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldPopsWithDupX2ToNothing() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_3\n\t\t\t\t        iconst_2\n\t\t\t\t        iconst_1\n\t\t\t\t        dup_x1\n\t\t\t\t        pop2\n\t\t\t\t        pop2\n\t\t\t\t        return\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to remove redundant popped values\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to remove redundant popped values\");\n\t\t\tassertEquals(0, StringUtil.count(\"pop\", dis), \"Expected to remove pop operations\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldPopsWithDup2X1ToNothing() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_3\n\t\t\t\t        iconst_2\n\t\t\t\t        iconst_1\n\t\t\t\t        dup2_x1\n\t\t\t\t        pop2\n\t\t\t\t        pop2\n\t\t\t\t        pop\n\t\t\t\t        return\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to remove redundant popped values\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to remove redundant popped values\");\n\t\t\tassertEquals(0, StringUtil.count(\"pop\", dis), \"Expected to remove pop operations\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldPopsWithDup2X2ToNothing() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_4\n\t\t\t\t        iconst_3\n\t\t\t\t        iconst_2\n\t\t\t\t        iconst_1\n\t\t\t\t        dup2_x2\n\t\t\t\t        pop2\n\t\t\t\t        pop2\n\t\t\t\t        pop2\n\t\t\t\t        return\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to remove redundant popped values\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to remove redundant popped values\");\n\t\t\tassertEquals(0, StringUtil.count(\"pop\", dis), \"Expected to remove pop operations\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldMathWithSwapPop() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t\t\ticonst_1\n\t\t\t\t\t\ticonst_4\n\t\t\t\t\t\tbipush 9\n\t\t\t\t\t\tswap\n\t\t\t\t\t\tpop\n\t\t\t\t\t\tiadd\n\t\t\t\t\t\tireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"swap\", dis), \"Expected to fold stack swapping\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold addition\");\n\t\t\tassertEquals(1, StringUtil.count(\"bipush 10\", dis), \"Expected to fold to 10 from 9+1\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldWithDup2() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_3\n\t\t\t\t        iconst_2\n\t\t\t\t        dup2\n\t\t\t\t        iadd\n\t\t\t\t        iadd\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to fold stack duplication\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold addition\");\n\t\t\tassertEquals(1, StringUtil.count(\"bipush 10\", dis), \"Expected to fold to 10 from (3+2)*2\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldWithDup2Pop() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_3\n\t\t\t\t        iconst_2\n\t\t\t\t        dup2\n\t\t\t\t        pop\n\t\t\t\t        iadd\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to fold stack duplication\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold addition\");\n\t\t\tassertEquals(1, StringUtil.count(\"bipush 8\", dis), \"Expected to fold to 8 from (3+2+3)\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldWithDup2X1() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_1\n\t\t\t\t        iconst_1\n\t\t\t\t        iconst_1\n\t\t\t\t        dup2_x1\n\t\t\t\t        iadd\n\t\t\t\t        iadd\n\t\t\t\t        iadd\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_1\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to fold stack duplication\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold addition\");\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_5\", dis), \"Expected to fold to 5 from 1+1+1+1+1\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldWithDup2X1Pop() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_3\n\t\t\t\t        iconst_2\n\t\t\t\t        iconst_1\n\t\t\t\t        dup2_x1\n\t\t\t\t        pop2\n\t\t\t\t        iadd\n\t\t\t\t        imul\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to fold stack duplication\");\n\t\t\tassertEquals(0, StringUtil.count(\"pop\", dis), \"Expected to fold stack popping\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold addition\");\n\t\t\tassertEquals(0, StringUtil.count(\"imul\", dis), \"Expected to fold multiplication\");\n\t\t\tassertEquals(1, StringUtil.count(\"bipush 8\", dis), \"Expected to fold to 8 from (3+1)*2\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid fold1Plus1() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_1\n\t\t\t\t        iconst_1\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_1\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_2\", dis), \"Expected to fold to 2 from 1+1\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldWithDup2X2Pop() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_4\n\t\t\t\t        iconst_3\n\t\t\t\t        iconst_2\n\t\t\t\t        iconst_1\n\t\t\t\t        dup2_x2\n\t\t\t\t        iadd\n\t\t\t\t        imul\n\t\t\t\t        swap\n\t\t\t\t        pop\n\t\t\t\t        iadd\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to fold stack duplication\");\n\t\t\tassertEquals(0, StringUtil.count(\"pop\", dis), \"Expected to fold stack popping\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold addition\");\n\t\t\tassertEquals(0, StringUtil.count(\"imul\", dis), \"Expected to fold multiplication\");\n\t\t\tassertEquals(1, StringUtil.count(\"bipush 12\", dis), \"Expected to fold to 12 from ((2+1)*3)+1+2\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldWithDupX1() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        bipush 10\n\t\t\t\t        iconst_5\n\t\t\t\t        dup_x1\n\t\t\t\t        imul\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to fold stack duplication\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold addition\");\n\t\t\tassertEquals(0, StringUtil.count(\"imul\", dis), \"Expected to fold multiplication\");\n\t\t\tassertEquals(1, StringUtil.count(\"bipush 55\", dis), \"Expected to fold to 55 from (10*5)+5\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldWithDupX2() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        bipush 15\n\t\t\t\t        bipush 8\n\t\t\t\t        iconst_2\n\t\t\t\t        dup_x2\n\t\t\t\t        iadd\n\t\t\t\t        iadd\n\t\t\t\t        imul\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to fold stack duplication\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold addition\");\n\t\t\tassertEquals(0, StringUtil.count(\"imul\", dis), \"Expected to fold multiplication\");\n\t\t\tassertEquals(1, StringUtil.count(\"bipush 50\", dis), \"Expected to fold to 50 from (2+8+15)*2\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldRedundantOperationOnUnknownValue() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (I)I {\n\t\t\t\t\tparameters: { a },\n\t\t\t\t\tcode: {\n\t\t\t\t    A:\n\t\t\t\t        iload a\n\t\t\t\t        iconst_0\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_0\", dis), \"Expected to fold redundant operation inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold redundant operation\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example (I)I {\n\t\t\t\t\tparameters: { a },\n\t\t\t\t\tcode: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        iload a\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_0\", dis), \"Expected to fold redundant operation inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold redundant operation\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example (I)I {\n\t\t\t\t\tparameters: { a },\n\t\t\t\t\tcode: {\n\t\t\t\t    A:\n\t\t\t\t        iload a\n\t\t\t\t        iconst_0\n\t\t\t\t        swap\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_0\", dis), \"Expected to fold redundant operation inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"swap\", dis), \"Expected to fold redundant input swapping\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold redundant operation\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example (I)I {\n\t\t\t\t\tparameters: { a },\n\t\t\t\t\tcode: {\n\t\t\t\t    A:\n\t\t\t\t        iload a\n\t\t\t\t        iconst_0\n\t\t\t\t        swap\n\t\t\t\t        swap\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_0\", dis), \"Expected to fold redundant operation inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"swap\", dis), \"Expected to fold redundant input swapping\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold redundant operation\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example (I)I {\n\t\t\t\t\tparameters: { a },\n\t\t\t\t\tcode: {\n\t\t\t\t    A:\n\t\t\t\t        iload a\n\t\t\t\t        iconst_0\n\t\t\t\t        swap\n\t\t\t\t        swap\n\t\t\t\t        swap\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_0\", dis), \"Expected to fold redundant operation inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"swap\", dis), \"Expected to fold redundant input swapping\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold redundant operation\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldWhenUnknownValueMultipliedByZero() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (I)I {\n\t\t\t\t\tparameters: { a },\n\t\t\t\t\tcode: {\n\t\t\t\t    A:\n\t\t\t\t        iload a\n\t\t\t\t        iconst_0\n\t\t\t\t        imul\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iload\", dis), \"Expected to fold redundant operation inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"imul\", dis), \"Expected to fold redundant operation\");\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_0\", dis), \"Expected to keep zero as output\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid opaqueConstantFoldingLongShift() {\n\t\t// We had a bug where shift instructions were not reporting the right stack consumption amount\n\t\t// but this has since been fixed. So these should cleanly be folded now.\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()J {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 1000000L\n\t\t\t\t        iconst_5\n\t\t\t\t        lushr\n\t\t\t\t        lreturn\n\t\t\t\t    H:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\t// Expected folded value\n\t\t\tassertTrue(dis.contains(\"31250L\"));\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()J {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 1000000L\n\t\t\t\t        iconst_5\n\t\t\t\t        lshr\n\t\t\t\t        lreturn\n\t\t\t\t    H:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\t// Expected folded value\n\t\t\tassertTrue(dis.contains(\"31250L\"));\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()J {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 1000000L\n\t\t\t\t        iconst_5\n\t\t\t\t        lshl\n\t\t\t\t        lreturn\n\t\t\t\t    H:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\t// Expected folded value\n\t\t\tassertTrue(dis.contains(\"32000000L\"));\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldDoubleDup2X1() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (I)I {\n\t\t\t\t\tparameters: { a },\n\t\t\t\t\tcode: {\n\t\t\t\t    A:\n\t\t\t\t        iload a\n\t\t\t\t        dconst_0\n\t\t\t\t        dup2_x1\n\t\t\t\t        pop2\n\t\t\t\t        i2d\n\t\t\t\t        dmul\n\t\t\t\t        d2i\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iload\", dis), \"Expected to fold redundant operation inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"imul\", dis), \"Expected to fold redundant operation\");\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_0\", dis), \"Expected to keep zero as output\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldDoubleDup2X2() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (D)I {\n\t\t\t\t\tparameters: { a },\n\t\t\t\t\tcode: {\n\t\t\t\t    A:\n\t\t\t\t        dload a\n\t\t\t\t        dconst_0\n\t\t\t\t        dup2_x2\n\t\t\t\t        pop2\n\t\t\t\t        dmul\n\t\t\t\t        d2i\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iload\", dis), \"Expected to fold redundant operation inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"imul\", dis), \"Expected to fold redundant operation\");\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_0\", dis), \"Expected to keep zero as output\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldWithMultipleDupsSwapsAndOperations() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc -2556828284053591649L\n\t\t\t\t        ldc 2556828284013841450L\n\t\t\t\t        lxor\n\t\t\t\t        l2i\n\t\t\t\t        ldc -415261744\n\t\t\t\t        i2l\n\t\t\t\t        ldc 1102084415\n\t\t\t\t        i2l\n\t\t\t\t        lxor\n\t\t\t\t        l2i\n\t\t\t\t        dup2\n\t\t\t\t        dup_x1\n\t\t\t\t        ineg\n\t\t\t\t        iconst_m1\n\t\t\t\t        iadd\n\t\t\t\t        swap\n\t\t\t\t        dup_x1\n\t\t\t\t        iconst_m1\n\t\t\t\t        ixor\n\t\t\t\t        ior\n\t\t\t\t        swap\n\t\t\t\t        iconst_m1\n\t\t\t\t        ixor\n\t\t\t\t        isub\n\t\t\t\t        iadd\n\t\t\t\t        dup_x2\n\t\t\t\t        pop\n\t\t\t\t        iconst_1\n\t\t\t\t        isub\n\t\t\t\t        iconst_m1\n\t\t\t\t        ixor\n\t\t\t\t        iconst_m1\n\t\t\t\t        iadd\n\t\t\t\t        swap\n\t\t\t\t        iconst_1\n\t\t\t\t        isub\n\t\t\t\t        iconst_m1\n\t\t\t\t        ixor\n\t\t\t\t        iconst_m1\n\t\t\t\t        iadd\n\t\t\t\t        dup_x1\n\t\t\t\t        ineg\n\t\t\t\t        iconst_m1\n\t\t\t\t        iadd\n\t\t\t\t        swap\n\t\t\t\t        dup_x1\n\t\t\t\t        iconst_m1\n\t\t\t\t        ixor\n\t\t\t\t        ior\n\t\t\t\t        swap\n\t\t\t\t        iconst_m1\n\t\t\t\t        ixor\n\t\t\t\t        isub\n\t\t\t\t        iadd\n\t\t\t\t        swap\n\t\t\t\t        dup_x1\n\t\t\t\t        ineg\n\t\t\t\t        iconst_m1\n\t\t\t\t        iadd\n\t\t\t\t        dup_x1\n\t\t\t\t        iconst_m1\n\t\t\t\t        ixor\n\t\t\t\t        iand\n\t\t\t\t        iadd\n\t\t\t\t        swap\n\t\t\t\t        ineg\n\t\t\t\t        iconst_m1\n\t\t\t\t        iadd\n\t\t\t\t        isub\n\t\t\t\t        i2l\n\t\t\t\t        ldc -6788200298164900932L\n\t\t\t\t        ldc -6788200298671981035L\n\t\t\t\t        lxor\n\t\t\t\t        l2i\n\t\t\t\t        ldc -1757344751\n\t\t\t\t        i2l\n\t\t\t\t        ldc -1441456414\n\t\t\t\t        i2l\n\t\t\t\t        lxor\n\t\t\t\t        l2i\n\t\t\t\t        dup2\n\t\t\t\t        dup_x1\n\t\t\t\t        ineg\n\t\t\t\t        iconst_m1\n\t\t\t\t        iadd\n\t\t\t\t        swap\n\t\t\t\t        dup_x1\n\t\t\t\t        iconst_m1\n\t\t\t\t        ixor\n\t\t\t\t        ior\n\t\t\t\t        swap\n\t\t\t\t        iconst_m1\n\t\t\t\t        ixor\n\t\t\t\t        isub\n\t\t\t\t        iadd\n\t\t\t\t        dup_x2\n\t\t\t\t        pop\n\t\t\t\t        swap\n\t\t\t\t        dup_x1\n\t\t\t\t        ineg\n\t\t\t\t        iconst_m1\n\t\t\t\t        iadd\n\t\t\t\t        dup_x1\n\t\t\t\t        iconst_m1\n\t\t\t\t        ixor\n\t\t\t\t        iand\n\t\t\t\t        iadd\n\t\t\t\t        swap\n\t\t\t\t        ineg\n\t\t\t\t        iconst_m1\n\t\t\t\t        iadd\n\t\t\t\t        isub\n\t\t\t\t        isub\n\t\t\t\t        i2l\n\t\t\t\t        lxor\n\t\t\t\t        l2i\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateBeforeAfterDecompile(asm, List.of(OpaqueConstantFoldingTransformer.class), \"return (\", \"return 0;\");\n\t}\n\n\t@Test\n\tvoid foldRedundantOperatorsOnParameterToParameter() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (I)I {\n\t\t\t\t\tparameters: { a },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iload a\n\t\t\t\t        iconst_0\n\t\t\t\t        dup2\n\t\t\t\t        swap\n\t\t\t\t        imul\n\t\t\t\t        iadd\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst\", dis), \"Expected to fold redundant inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to fold redundant operations\");\n\t\t\tassertEquals(0, StringUtil.count(\"imul\", dis), \"Expected to fold redundant operations\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to fold redundant operations\");\n\t\t});\n\t\tvalidateBeforeAfterDecompile(asm, List.of(OpaqueConstantFoldingTransformer.class), \"return \", \"return a;\");\n\t}\n\n\t@Test\n\tvoid foldDupSwappingUnknownValueMultipliedBy0To0() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (I)I {\n\t\t\t\t\tparameters: { a },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iload a\n\t\t\t\t        iconst_0\n\t\t\t\t        // [a, 0]\n\t\t\t\t        dup2\n\t\t\t\t        // [a, 0, a, 0]\n\t\t\t\t        swap\n\t\t\t\t        // [a, 0, a, 0]\n\t\t\t\t        imul\n\t\t\t\t        // [a, 0, 0]\n\t\t\t\t        imul\n\t\t\t\t        // [a, 0]\n\t\t\t\t        imul\n\t\t\t\t        // [0]\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iload\", dis), \"Expected to fold inputs\");\n\t\t\tassertEquals(0, StringUtil.count(\"imul\", dis), \"Expected to fold redundant operations\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to fold redundant operations\");\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_0\", dis), \"Expected to fold to 0\");\n\t\t});\n\t\tvalidateBeforeAfterDecompile(asm, List.of(OpaqueConstantFoldingTransformer.class), \"return \", \"return 0;\");\n\t}\n\n\t@Test\n\tvoid foldConvertInt2Float() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 10\n\t\t\t\t        i2f\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"i2f\", dis), \"Expected to fold conversion\");\n\t\t\tassertEquals(1, StringUtil.count(\"ldc 10F\", dis), \"Expected to fold to converted value\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldConvertLong2Float() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 100000000000000000L\n\t\t\t\t        l2f\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"l2f\", dis), \"Expected to fold conversion\");\n\t\t\tassertEquals(1, StringUtil.count(\"ldc \" + StringUtil.toString(100000000000000000F) + \"F\", dis),\n\t\t\t\t\t\"Expected to fold to converted value\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldConvertDouble2Float() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 5.125\n\t\t\t\t        d2f\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"d2f\", dis), \"Expected to fold conversion\");\n\t\t\tassertEquals(1, StringUtil.count(\"ldc 5.125F\", dis), \"Expected to fold to converted value\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldConvertLong2Int() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 100000000000000000L\n\t\t\t\t        l2i\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"l2i\", dis), \"Expected to fold conversion\");\n\t\t\tassertEquals(1, StringUtil.count(\"ldc \" + (int) 100000000000000000L, dis), \"Expected to fold to converted value\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldConvertFloat2Int() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 100000000000000000F\n\t\t\t\t        f2i\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"f2i\", dis), \"Expected to fold conversion\");\n\t\t\tassertEquals(1, StringUtil.count(\"ldc \" + (int) 100000000000000000F, dis), \"Expected to fold to converted value\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldConvertDouble2Int() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 10000000000000000000000000000000.0\n\t\t\t\t        d2i\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"d2i\", dis), \"Expected to fold conversion\");\n\t\t\tassertEquals(1, StringUtil.count(\"ldc \" + (int) 10000000000000000000000000000000.0, dis), \"Expected to fold to converted value\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 0.95\n\t\t\t\t        d2i\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"d2i\", dis), \"Expected to fold conversion\");\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_0\", dis), \"Expected to fold to converted value\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldLongComparison() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 123L\n\t\t\t\t        ldc 321L\n\t\t\t\t        lcmp\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_m1\", dis), \"Expected to fold to -1\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 321L\n\t\t\t\t        ldc 123L\n\t\t\t\t        lcmp\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_1\", dis), \"Expected to fold to 1\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 321L\n\t\t\t\t        dup2\n\t\t\t\t        lcmp\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_0\", dis), \"Expected to fold to 0\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldFloatComparison() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 123.0F\n\t\t\t\t        ldc 321.0F\n\t\t\t\t        fcmpl\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_m1\", dis), \"Expected to fold to -1\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t    \tldc 321.0F\n\t\t\t\t        ldc 123.0F\n\t\t\t\t        fcmpl\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_1\", dis), \"Expected to fold to 1\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t    \tldc 321.0F\n\t\t\t\t        ldc 321.0F\n\t\t\t\t        fcmpl\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_0\", dis), \"Expected to fold to 0\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldDoubleComparison() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 123.0\n\t\t\t\t        ldc 321.0\n\t\t\t\t        dcmpl\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_m1\", dis), \"Expected to fold to -1\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 321.0\n\t\t\t\t        ldc 123.0\n\t\t\t\t        dcmpl\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_1\", dis), \"Expected to fold to 1\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc 321.0\n\t\t\t\t        ldc 321.0\n\t\t\t\t        dcmpl\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_0\", dis), \"Expected to fold to 0\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldCommonStaticMethodCalls() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        getstatic java/lang/Byte.BYTES I\n\t\t\t\t        getstatic java/lang/Long.BYTES I\n\t\t\t\t        invokestatic java/lang/Math.min (II)I\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_1\", dis), \"Expected to fold to 1\");\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_5\", dis), \"Expected to fold argument\");\n\t\t\tassertEquals(0, StringUtil.count(\"Math.min\", dis), \"Expected to fold method call\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldStringInstanceMethodCalls() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc \"12345\"\n\t\t\t\t        invokevirtual java/lang/String.length ()I\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_5\", dis), \"Expected to fold to 5\");\n\t\t\tassertEquals(0, StringUtil.count(\"ldc\", dis), \"Expected to fold original string\");\n\t\t\tassertEquals(0, StringUtil.count(\"invoke\", dis), \"Expected to fold method call\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc \"0123456789\"\n\t\t\t\t        ldc \"5\"\n\t\t\t\t        invokevirtual java/lang/String.indexOf (Ljava/lang/String;)I\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_5\", dis), \"Expected to fold to 5\");\n\t\t\tassertEquals(0, StringUtil.count(\"ldc\", dis), \"Expected to fold original string\");\n\t\t\tassertEquals(0, StringUtil.count(\"invoke\", dis), \"Expected to fold method call\");\n\t\t});\n\n\t\t// Found in a random sample, the control flow was resulting in the code under label 'C' being revisited.\n\t\t// The merge code in our analysis for strings was wrong, which prevented this from being combined.\n\t\t// This test ensures that bad frame merge is no longer an issue.\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ([B)Ljava/lang/String; {\n\t\t\t\t\tparameters: { data },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore counter\n\t\t\t\t    B:\n\t\t\t\t        iload counter\n\t\t\t\t        aload data\n\t\t\t\t        arraylength\n\t\t\t\t        if_icmpge C\n\t\t\t\t        iinc counter 1\n\t\t\t\t        goto B\n\t\t\t\t    C:\n\t\t\t\t        ldc \"UTF-\"\n\t\t\t\t        ldc \"8\"\n\t\t\t\t        invokevirtual java/lang/String.concat (Ljava/lang/String;)Ljava/lang/String;\n\t\t\t\t    D:\n\t\t\t\t        areturn\n\t\t\t\t    E:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"ldc \\\"UTF-8\\\"\", dis), \"Expected to fold to single string\");\n\t\t\tassertEquals(0, StringUtil.count(\"invoke\", dis), \"Expected to remove method calls\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldCharArrayIntoStringFromValueOf() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()Ljava/lang/String; {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_2\n\t\t\t\t        newarray char\n\t\t\t\t        dup\n\t\t\t\t        iconst_0\n\t\t\t\t        ldc 'H'\n\t\t\t\t        castore\n\t\t\t\t        dup\n\t\t\t\t        iconst_1\n\t\t\t\t        ldc 'i'\n\t\t\t\t        castore\n\t\t\t\t        invokestatic java/lang/String.valueOf ([C)Ljava/lang/String;\n\t\t\t\t        areturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateBeforeAfterDecompile(asm, List.of(OpaqueConstantFoldingTransformer.class),\n\t\t\t\t\"return String.valueOf(new char[]{'H', 'i'});\", \"return \\\"Hi\\\";\");\n\t}\n\n\t@Test\n\tvoid foldStringToCharArrayFetch() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()C {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc \"abc\"\n\t\t\t\t        invokevirtual java/lang/String.toCharArray ()[C\n\t\t\t\t        iconst_0\n\t\t\t\t        caload\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateBeforeAfterDecompile(asm, List.of(OpaqueConstantFoldingTransformer.class),\n\t\t\t\t\"return \\\"abc\\\".toCharArray()[0]\", \"return 'a';\");\n\t}\n\n\t@Test\n\tvoid simulatingThrowingMethodDoesNotFold() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()[C {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc \"abc\"\n\t\t\t\t        invokevirtual java/lang/String.toCharArray ()[C\n\t\t\t\t        iconst_m1\n\t\t\t\t        iconst_m1\n\t\t\t\t        invokestatic java/util/Arrays.copyOfRange ([CII)[C\n\t\t\t\t        areturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(OpaqueConstantFoldingTransformer.class));\n\t}\n\n\t@Test\n\tvoid foldFormattedString() {\n\t\t// Has Object[] parameter with values that we want to fold\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()Ljava/lang/String; {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc \"Hello %s - Number %d\"\n\t\t\t\t        iconst_2\n\t\t\t\t        anewarray java/lang/Object\n\t\t\t\t        dup\n\t\t\t\t        iconst_0\n\t\t\t\t        ldc \"Name\"\n\t\t\t\t        aastore\n\t\t\t\t        dup\n\t\t\t\t        iconst_1\n\t\t\t\t        bipush 32\n\t\t\t\t        invokestatic java/lang/Integer.valueOf (I)Ljava/lang/Integer;\n\t\t\t\t        aastore\n\t\t\t\t        invokevirtual java/lang/String.formatted ([Ljava/lang/Object;)Ljava/lang/String;\n\t\t\t\t        areturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateBeforeAfterDecompile(asm, List.of(OpaqueConstantFoldingTransformer.class), \"\", \"\");\n\t}\n\n\t@Test\n\tvoid foldStaticCallToXorString() {\n\t\tString asm = \"\"\"\n\t\t\t\t.super java/lang/Object\n\t\t\t\t.class Example {\n\t\t\t\t\t.method static example ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t        getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t\t        ldc \"㘯㘂㘋㘋㘈㙇㘐㘈㘕㘋㘃\"\n\t\t\t\t\t        ldc 0b11011001100111\n\t\t\t\t\t        invokestatic Example.decrypt (Ljava/lang/String;I)Ljava/lang/String;\n\t\t\t\t\t        invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V\n\t\t\t\t\t    B:\n\t\t\t\t\t        return\n\t\t\t\t\t    C:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t.method static decrypt (Ljava/lang/String;I)Ljava/lang/String; {\n\t\t\t\t\t    parameters: { input, xor },\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t        aload input\n\t\t\t\t\t        invokevirtual java/lang/String.length ()I\n\t\t\t\t\t        istore length\n\t\t\t\t\t    B:\n\t\t\t\t\t        iload length\n\t\t\t\t\t        newarray char\n\t\t\t\t\t        astore chars\n\t\t\t\t\t    C:\n\t\t\t\t\t        iconst_0\n\t\t\t\t\t        istore i\n\t\t\t\t\t    D:\n\t\t\t\t\t        iload i\n\t\t\t\t\t        iload length\n\t\t\t\t\t        if_icmpge G\n\t\t\t\t\t    E:\n\t\t\t\t\t        aload chars\n\t\t\t\t\t        iload i\n\t\t\t\t\t        aload input\n\t\t\t\t\t        iload i\n\t\t\t\t\t        invokevirtual java/lang/String.charAt (I)C\n\t\t\t\t\t        iload xor\n\t\t\t\t\t        ixor\n\t\t\t\t\t        i2c\n\t\t\t\t\t        castore\n\t\t\t\t\t    F:\n\t\t\t\t\t        iinc i 1\n\t\t\t\t\t        goto D\n\t\t\t\t\t    G:\n\t\t\t\t\t        aload chars\n\t\t\t\t\t        invokestatic java/lang/String.valueOf ([C)Ljava/lang/String;\n\t\t\t\t\t        areturn\n\t\t\t\t\t    H:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateBeforeAfterDecompile(asm, List.of(CallResultInliningTransformer.class),\n\t\t\t\t\"System.out.println(Example.decrypt(\\\"\\\\u362f\\\\u3602\\\\u360b\\\\u360b\\\\u3608\\\\u3647\\\\u3610\\\\u3608\\\\u3615\\\\u360b\\\\u3603\\\", 13927));\",\n\t\t\t\t\"System.out.println(\\\"Hello world\\\");\");\n\t}\n\n\t@Test\n\tvoid foldRedundant1DIntArray() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        // return new int[] { 5 }[0];\n\t\t\t\t\t\ticonst_1\n\t\t\t\t\t\tnewarray int\n\t\t\t\t\t\tdup\n\t\t\t\t\t\ticonst_0\n\t\t\t\t\t\ticonst_5\n\t\t\t\t\t\tiastore\n\t\t\t\t\t\ticonst_0\n\t\t\t\t\t\tiaload\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"array\", dis), \"Expected to fold redundant array\");\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_5\", dis), \"Expected to fold to 5\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldRedundant2DIntArray() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        // int[][] array = new int[4][3];\n\t\t\t\t\t    iconst_4\n\t\t\t\t\t    iconst_3\n\t\t\t\t\t    multianewarray [[I 2\n\t\t\t\t\t    astore array\n\t\t\t\t\n\t\t\t\t\t    // array[1][2] = 5;\n\t\t\t\t\t    aload array\n\t\t\t\t\t    iconst_1\n\t\t\t\t\t    aaload\n\t\t\t\t\t    iconst_2\n\t\t\t\t\t    iconst_5\n\t\t\t\t\t    iastore\n\t\t\t\t\n\t\t\t\t\t    // return array[1][2];\n\t\t\t\t\t    aload array\n\t\t\t\t\t    iconst_1\n\t\t\t\t\t    aaload\n\t\t\t\t\t    iconst_2\n\t\t\t\t\t    iaload\n\t\t\t\t\t    ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class, VariableFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"array\", dis), \"Expected to fold redundant array\");\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_5\", dis), \"Expected to fold to 5\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfeq() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        ifeq C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"ifeq\", dis), \"Expected to remove ifeq\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace ifeq <target> with goto <target>\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIflt() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_m1\n\t\t\t\t        iflt C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iflt\", dis), \"Expected to remove iflt\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace iflt <target> with goto <target>\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfle() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        ifle C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iflt\", dis), \"Expected to remove ifle\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace ifle <target> with goto <target>\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfgt() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_1\n\t\t\t\t        ifgt C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iflt\", dis), \"Expected to remove ifgt\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace ifgt <target> with goto <target>\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfge() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        ifge C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"ifge\", dis), \"Expected to remove ifge\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace ifge <target> with goto <target>\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfnull() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aconst_null\n\t\t\t\t        ifnull C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"ifnull\", dis), \"Expected to remove ifnull\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace ifnull <target> with goto <target>\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfnonnull() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc \"\"\n\t\t\t\t        ifnonnull C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"ifnull\", dis), \"Expected to remove ifnonnull\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace ifnonnull <target> with goto <target>\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfIcmpeq() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        iconst_0\n\t\t\t\t        if_icmpeq C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"if_icmpeq\", dis), \"Expected to remove if_icmpeq\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace if_icmpeq <target> with goto <target>\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfIcmpne() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        iconst_1\n\t\t\t\t        if_icmpne C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"if_icmpne\", dis), \"Expected to remove if_icmpne\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace if_icmpne <target> with goto <target>\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfIcmplt() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        iconst_3\n\t\t\t\t        if_icmplt C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"if_icmplt\", dis), \"Expected to remove if_icmplt\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace if_icmplt <target> with goto <target>\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfIcmpge() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_3\n\t\t\t\t        iconst_3\n\t\t\t\t        if_icmpge C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"if_icmpge\", dis), \"Expected to remove if_icmpge\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace if_icmpge <target> with goto <target>\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfIcmpgt() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_5\n\t\t\t\t        iconst_3\n\t\t\t\t        if_icmpgt C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"if_icmpgt\", dis), \"Expected to remove if_icmpgt\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace if_icmpgt <target> with goto <target>\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfIcmple() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_3\n\t\t\t\t        iconst_3\n\t\t\t\t        if_icmple C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"if_icmple\", dis), \"Expected to remove if_icmple\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace if_icmple <target> with goto <target>\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfAcmpeq() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aconst_null\n\t\t\t\t        dup\n\t\t\t\t        if_acmpeq C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"if_acmpeq\", dis), \"Expected to remove if_acmpeq\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace if_acmpeq <target> with goto <target>\");\n\t\t});\n\n\t\t// Fall-through case\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aconst_null\n\t\t\t\t        ldc \"not_null\"\n\t\t\t\t        if_acmpeq C\n\t\t\t\t    B:\n\t\t\t\t        return\n\t\t\t\t    C:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"if_acmpeq\", dis), \"Expected to remove if_acmpeq\");\n\t\t\tassertEquals(0, StringUtil.count(\"aconst_null\", dis), \"Expected to optimize out all code\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueIfAcmpne() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aconst_null\n\t\t\t\t        ldc \"not null\"\n\t\t\t\t        if_acmpne C\n\t\t\t\t    B:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        return\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"if_acmpne\", dis), \"Expected to remove if_acmpne\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Expected to replace if_acmpne <target> with goto <target>\");\n\t\t});\n\n\t\t// Fall-through case\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aconst_null\n\t\t\t\t        dup\n\t\t\t\t        if_acmpne C\n\t\t\t\t    B:\n\t\t\t\t        return\n\t\t\t\t    C:\n\t\t\t\t        // Should be skipped over by transformer\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"if_acmpne\", dis), \"Expected to remove if_acmpne\");\n\t\t\tassertEquals(0, StringUtil.count(\"aconst_null\", dis), \"Expected to optimize out all code\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueTableSwitch() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_2\n\t\t\t\t        tableswitch {\n\t\t\t\t            min: 0,\n\t\t\t\t            max: 2,\n\t\t\t\t            cases: { B, C, D },\n\t\t\t\t            default: E\n\t\t\t\t        }\n\t\t\t\t    B:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    D:\n\t\t\t\t        return\n\t\t\t\t    E:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\t// Switch should be replaced with a single goto\n\t\t\tassertEquals(0, StringUtil.count(\"tableswitch\", dis), \"Expected to remove tableswitch\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto B\", dis), \"Expected to replace tableswitch <target> with goto <target>\");\n\n\t\t\t// Dead code should be removed\n\t\t\tassertEquals(0, StringUtil.count(\"aconst_null\", dis), \"Expected to remove dead aconst_null\");\n\t\t\tassertEquals(0, StringUtil.count(\"athrow\", dis), \"Expected to remove dead athrow\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaqueLookupSwitch() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_3\n\t\t\t\t        lookupswitch {\n\t\t\t\t            1: B,\n\t\t\t\t            2: C,\n\t\t\t\t            3: D,\n\t\t\t\t            default: E\n\t\t\t\t        }\n\t\t\t\t    B:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    D:\n\t\t\t\t        return\n\t\t\t\t    E:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\t// Switch should be replaced with a single goto\n\t\t\tassertEquals(0, StringUtil.count(\"lookupswitch\", dis), \"Expected to remove lookupswitch\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto B\", dis), \"Expected to replace lookupswitch <target> with goto <target>\");\n\n\t\t\t// Dead code should be removed\n\t\t\tassertEquals(0, StringUtil.count(\"aconst_null\", dis), \"Expected to remove dead aconst_null\");\n\t\t\tassertEquals(0, StringUtil.count(\"athrow\", dis), \"Expected to remove dead athrow\");\n\t\t});\n\t}\n\n\t/** @see #foldLookupSwitchOfUnknownParameterIfIsEffectiveGoto() */\n\t@Test\n\tvoid foldTableSwitchOfUnknownParameterIfIsEffectiveGoto() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (I)V {\n\t\t\t\t\tparameters: { key },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iload key\n\t\t\t\t        tableswitch {\n\t\t\t\t            min: 0,\n\t\t\t\t            max: 2,\n\t\t\t\t            cases: { D, D, D },\n\t\t\t\t            default: D\n\t\t\t\t        }\n\t\t\t\t    B:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    D:\n\t\t\t\t        return\n\t\t\t\t    E:\n\t\t\t\t        aconst_null\n\t\t\t\t        dup\n\t\t\t\t        pop\n\t\t\t\t        athrow\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\t// Switch should be replaced with a single goto\n\t\t\tassertEquals(0, StringUtil.count(\"iload key\", dis), \"Expected to remove tableswitch argument\");\n\t\t\tassertEquals(0, StringUtil.count(\"tableswitch\", dis), \"Expected to remove tableswitch\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto B\", dis), \"Expected to replace tableswitch <target> with goto <target>\");\n\n\t\t\t// Dead code should be removed\n\t\t\tassertEquals(0, StringUtil.count(\"aconst_null\", dis), \"Expected to remove dead aconst_null\");\n\t\t\tassertEquals(0, StringUtil.count(\"athrow\", dis), \"Expected to remove dead athrow\");\n\t\t\tassertEquals(0, StringUtil.count(\"pop\", dis), \"Expected to remove dead athrow\");\n\t\t});\n\t}\n\n\t/** @see #foldTableSwitchOfUnknownParameterIfIsEffectiveGoto() */\n\t@Test\n\tvoid foldLookupSwitchOfUnknownParameterIfIsEffectiveGoto() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (I)V {\n\t\t\t\t\tparameters: { key },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iload key\n\t\t\t\t        lookupswitch {\n\t\t\t\t            1: D,\n\t\t\t\t            2: D,\n\t\t\t\t            3: D,\n\t\t\t\t            default: D\n\t\t\t\t        }\n\t\t\t\t    B:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    D:\n\t\t\t\t        return\n\t\t\t\t    E:\n\t\t\t\t        aconst_null\n\t\t\t\t        dup\n\t\t\t\t        pop\n\t\t\t\t        athrow\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class), dis -> {\n\t\t\t// Switch should be replaced with a single goto\n\t\t\tassertEquals(0, StringUtil.count(\"iload key\", dis), \"Expected to remove lookupswitch argument\");\n\t\t\tassertEquals(0, StringUtil.count(\"lookupswitch\", dis), \"Expected to remove lookupswitch\");\n\t\t\tassertEquals(1, StringUtil.count(\"goto B\", dis), \"Expected to replace lookupswitch <target> with goto <target>\");\n\n\t\t\t// Dead code should be removed\n\t\t\tassertEquals(0, StringUtil.count(\"aconst_null\", dis), \"Expected to remove dead aconst_null\");\n\t\t\tassertEquals(0, StringUtil.count(\"athrow\", dis), \"Expected to remove dead athrow\");\n\t\t\tassertEquals(0, StringUtil.count(\"pop\", dis), \"Expected to remove dead athrow\");\n\t\t});\n\t}\n\n\t/** Showcase pairing of {@link VariableFoldingTransformer} with {@link OpaqueConstantFoldingTransformer} */\n\t@Test\n\tvoid foldVar() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore zero\n\t\t\t\t        bipush 50\n\t\t\t\t        istore unused0\n\t\t\t\t        bipush 51\n\t\t\t\t        istore unused1\n\t\t\t\t        bipush 52\n\t\t\t\t        istore unused2\n\t\t\t\t        bipush 53\n\t\t\t\t        istore unused3\n\t\t\t\t        bipush 54\n\t\t\t\t        istore unused4\n\t\t\t\t    B:\n\t\t\t\t        iload zero\n\t\t\t\t        ireturn\n\t\t\t\t    C:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(VariableFoldingTransformer.class, OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"istore unused\", dis), \"Expected to remove variable stores where no reads are used\");\n\t\t\tassertEquals(0, StringUtil.count(\"bipush\", dis), \"Expected to remove unused value pushes of variables\");\n\t\t\tassertEquals(0, StringUtil.count(\"zero\", dis), \"Expected to inline 'zero' variable\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldVarWithRedundantCopyVariable() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aload this\n\t\t\t\t        astore copy\n\t\t\t\t        aload copy\n\t\t\t\t        getfield Example.myInt I\n\t\t\t\t        ireturn\n\t\t\t\t    C:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(VariableFoldingTransformer.class, OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"copy\", dis), \"Expected to remove variable references to redundant 'copy' var\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aload this\n\t\t\t\t        astore copy\n\t\t\t\t        aload copy\n\t\t\t\t        dup\n\t\t\t\t        getfield Example.myInt I\n\t\t\t\t        swap\n\t\t\t\t        getfield Example.myInt I\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    C:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(VariableFoldingTransformer.class, OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"copy\", dis), \"Expected to remove variable references to redundant 'copy' var\");\n\t\t\tassertEquals(0, StringUtil.count(\"dup\", dis), \"Expected to remove 'dup'\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aload this\n\t\t\t\t        astore copy\n\t\t\t\t        aload copy\n\t\t\t\t        getfield Example.myInt I\n\t\t\t\t        aload copy\n\t\t\t\t        getfield Example.myInt I\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    C:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(VariableFoldingTransformer.class, OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"copy\", dis), \"Expected to remove variable references to redundant 'copy' var\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aload this\n\t\t\t\t        astore copy1\n\t\t\t\t        aload copy1\n\t\t\t\t        astore copy2\n\t\t\t\t\t\taload copy2\n\t\t\t\t\t\tastore copy3\n\t\t\t\t\t\taload copy3\n\t\t\t\t\t\tastore copy4\n\t\t\t\t\t\taload copy4\n\t\t\t\t        getfield Example.myInt I\n\t\t\t\t        aload copy4\n\t\t\t\t        getfield Example.myInt I\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    C:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(VariableFoldingTransformer.class, OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"copy\", dis), \"Expected to remove variable references to redundant 'copy' var\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldVarWithWideRedundantCopyVariable() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (J)I {\n\t\t\t\t\tparameters: { foo },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        lload foo\n\t\t\t\t        lstore copy2\n\t\t\t\t\n\t\t\t\t        lload copy2\n\t\t\t\t\t\tlstore copy3\n\t\t\t\t\n\t\t\t\t        lload copy3\n\t\t\t\t        l2i\n\t\t\t\t        ireturn\n\t\t\t\t    C:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterRepeatedAssembly(asm, List.of(VariableFoldingTransformer.class, OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"copy\", dis), \"Expected to remove variable references to redundant 'copy' var\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid doNotFoldVarUsedInComparisonIfOriginalPossiblyUpdates() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (I)I {\n\t\t\t\t\tparameters: { a },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iload a\n\t\t\t\t        istore b\n\t\t\t\t        iload a\n\t\t\t\t        invokestatic Example.lookup (I)I\n\t\t\t\t        istore a\n\t\t\t\t        iload a\n\t\t\t\t        iload b\n\t\t\t\t        if_icmpne B\n\t\t\t\t        iload a\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t        iload b\n\t\t\t\t        ireturn\n\t\t\t\t    C:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(VariableFoldingTransformer.class, OpaqueConstantFoldingTransformer.class));\n\t}\n\n\t@Test\n\tvoid foldVarWithSeriesOfRedundantBackToBackCycledWrites() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aload this\n\t\t\t\t        astore copy1\n\t\t\t\t\n\t\t\t\t        aload copy1\n\t\t\t\t        astore copy1\n\t\t\t\t\n\t\t\t\t        aload copy1\n\t\t\t\t        astore copy1\n\t\t\t\t\n\t\t\t\t        aload this\n\t\t\t\t        astore copy2\n\t\t\t\t\n\t\t\t\t        aload copy2\n\t\t\t\t        astore copy1\n\t\t\t\t\n\t\t\t\t        aload copy1\n\t\t\t\t        getfield Example.myInt I\n\t\t\t\t        aload copy2\n\t\t\t\t        getfield Example.myInt I\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    C:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterRepeatedAssembly(asm, List.of(VariableFoldingTransformer.class, OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"copy\", dis), \"Expected to remove variable references to redundant 'copy' var\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldVarRedundantInitialization() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (II)I {\n\t\t\t\t\tparameters: { a, b },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore c\n\t\t\t\t    B:\n\t\t\t\t        iload a\n\t\t\t\t        iload b\n\t\t\t\t        iadd\n\t\t\t\t        istore c\n\t\t\t\t    C:\n\t\t\t\t        sipush 1000\n\t\t\t\t        iload c\n\t\t\t\t        if_icmpge B\n\t\t\t\t    D:\n\t\t\t\t        iload c\n\t\t\t\t        ireturn\n\t\t\t\t    E:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(VariableFoldingTransformer.class, OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_0\", dis), \"Initial c = 0 should be removed\");\n\t\t\tassertEquals(2, StringUtil.count(\"iload c\", dis), \"Should retain both 'iload c' cases\");\n\t\t});\n\t}\n\n\t/** Chose case {@link VariableFoldingTransformer} along with other flow-based cleanup transformers. */\n\t@Test\n\tvoid foldVarAndFlow() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_m1\n\t\t\t\t        istore foo\n\t\t\t\t        iconst_0\n\t\t\t\t        istore foo\n\t\t\t\t    B:\n\t\t\t\t        iload foo\n\t\t\t\t        ifeq C\n\t\t\t\t        iload foo\n\t\t\t\t        ireturn\n\t\t\t\t    C:\n\t\t\t\t        iload foo\n\t\t\t\t        ireturn\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterRepeatedAssembly(asm, List.of(\n\t\t\t\tVariableFoldingTransformer.class,\n\t\t\t\tOpaquePredicateFoldingTransformer.class,\n\t\t\t\tOpaqueConstantFoldingTransformer.class,\n\t\t\t\tGotoInliningTransformer.class\n\t\t), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"iconst\", dis), \"Expected only one const\");\n\t\t\tassertEquals(1, StringUtil.count(\"ireturn\", dis), \"Expected only one return\");\n\t\t\tassertEquals(0, StringUtil.count(\"ifeq\", dis), \"Expected to fold opaque ifeq\");\n\t\t\tassertEquals(0, StringUtil.count(\"foo\", dis), \"Expected to fold redundant variable declaration\");\n\t\t});\n\t}\n\n\t/** Show {@link VariableFoldingTransformer} can inline when parameters are effectively unused/overwritten */\n\t@Test\n\tvoid foldVarsOfOverwrittenParameters() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (II)I {\n\t\t\t\t\tparameters: { a, b },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore a\n\t\t\t\t        iconst_0\n\t\t\t\t        istore b\n\t\t\t\t    B:\n\t\t\t\t        iload a\n\t\t\t\t        iload b\n\t\t\t\t        iadd\n\t\t\t\t        ireturn\n\t\t\t\t    C:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(VariableFoldingTransformer.class, OpaqueConstantFoldingTransformer.class,\n\t\t\t\tOpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"istore\", dis), \"Expected to remove redundant istore\");\n\t\t\tassertEquals(0, StringUtil.count(\"iload\", dis), \"Expected to inline redundant iload\");\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_0\", dis), \"Expected to have single iconst_0\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldUnusedIincWithVariableFolding() {\n\t\t// The variable folding transformer will not clean up the \"iconst_0, pop, nop\" sequence\n\t\t// as that is the role of the constant folding transformer, but when we decompile the\n\t\t// bytecode it should be smart enough to figure out the value has been folded to const 4.\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore a\n\t\t\t\t        iinc a 4\n\t\t\t\t\t\tiload a\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateBeforeAfterDecompile(asm, List.of(VariableFoldingTransformer.class), \"return a += 4;\", \"return 4;\");\n\t}\n\n\t@Test\n\tvoid foldUnusedIincWithConstantFolding() {\n\t\t// Constant folding will collapse all steps of this, but keep the perceived side-effects of variable writes.\n\t\t// Those residual variable writes will be cleaned up with the variable folding transformer.\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore a\n\t\t\t\t        iinc a 1\n\t\t\t\t        iload a\n\t\t\t\t        iconst_1\n\t\t\t\t        iadd\n\t\t\t\t        istore a\n\t\t\t\t        iinc a 2\n\t\t\t\t\t\tiload a\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class, VariableFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"iconst_0\", dis), \"Expected to remove initial 0 value\");\n\t\t\tassertEquals(0, StringUtil.count(\"istore\", dis), \"Expected to remove redundant istore\");\n\t\t\tassertEquals(0, StringUtil.count(\"iinc\", dis), \"Expected to remove redundant iinc\");\n\t\t\tassertEquals(0, StringUtil.count(\"iadd\", dis), \"Expected to remove redundant iadd\");\n\t\t\tassertEquals(0, StringUtil.count(\"iload\", dis), \"Expected to inline redundant iload\");\n\t\t\tassertEquals(1, StringUtil.count(\"iconst_4\", dis), \"Expected to have single iconst_4\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid repeatedSteps() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_1\n\t\t\t\t        istore a\n\t\t\t\t        iload a\n\t\t\t\t        istore b\n\t\t\t\t        iload b\n\t\t\t\t        istore c\n\t\t\t\t        iload c\n\t\t\t\t        istore d\n\t\t\t\t        iload d\n\t\t\t\t        istore e\n\t\t\t\t        iload a\n\t\t\t\t        iload b\n\t\t\t\t        iadd\n\t\t\t\t        istore c\n\t\t\t\t        iload c\n\t\t\t\t        iload d\n\t\t\t\t        iadd\n\t\t\t\t        istore e\n\t\t\t\t        return\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterRepeatedAssembly(asm, List.of(VariableFoldingTransformer.class, OpaqueConstantFoldingTransformer.class, OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"const\", dis), \"Expected to remove redundant iconst_0\");\n\t\t\tassertEquals(0, StringUtil.count(\"load\", dis), \"Expected to remove redundant iload\");\n\t\t\tassertEquals(0, StringUtil.count(\"store\", dis), \"Expected to remove redundant istore\");\n\t\t});\n\t}\n\n\t/** Show {@link VariableFoldingTransformer} isn't too aggressive */\n\t@Test\n\tvoid dontFoldVarsOfUsedButOverwrittenParameters() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (II)I {\n\t\t\t\t\tparameters: { a, b },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iload a\n\t\t\t\t        iload a\n\t\t\t\t        iadd\n\t\t\t\t        istore c\n\t\t\t\t    B:\n\t\t\t\t        // Must be retained\n\t\t\t\t        sipush 1000\n\t\t\t\t        iload c\n\t\t\t\t        if_icmpge A\n\t\t\t\t    C:\n\t\t\t\t        // Can be folded but not the end of the world if not\n\t\t\t\t        iconst_1\n\t\t\t\t        istore a\n\t\t\t\t        iconst_1\n\t\t\t\t        istore b\n\t\t\t\t    D:\n\t\t\t\t        // Must be retained\n\t\t\t\t        iload c\n\t\t\t\t        ireturn\n\t\t\t\t    E:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(VariableFoldingTransformer.class, OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertEquals(2, StringUtil.count(\"iload c\", dis), \"Should retain 'iload c'\");\n\t\t\tassertEquals(1, StringUtil.count(\"istore c\", dis), \"Should retain 'istore c'\");\n\t\t\tassertEquals(1, StringUtil.count(\"if_icmpge\", dis), \"Should retain 'if_icmpge' usage of var 'c'\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldOpaquePredicateAlsoRemovesTryCatchesThatAreNowDeadCode() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method example ()V {\n\t\t\t\t    exceptions: {\n\t\t\t\t        { B, C, C, Ljava/lang/Exception; },\n\t\t\t\t        { C, D, B, Ljava/lang/Exception; }\n\t\t\t\t     },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        // When this opaque predicate gets folded it will make the try-catch ranges into dead code.\n\t\t\t\t        // The dead code filter will remove the contents of that code range, and thus the\n\t\t\t\t        // try-catch declarations should also be removed from the method.\n\t\t\t\t        iconst_0\n\t\t\t\t        ifeq D\n\t\t\t\t        aconst_null\n\t\t\t\t    B:\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        athrow\n\t\t\t\t    D:\n\t\t\t\t        aload this\n\t\t\t\t        invokespecial java/lang/Object.<init> ()V\n\t\t\t\t        return\n\t\t\t\t    E:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class /* Dead code pass implicitly added */), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"exceptions:\", dis), \"Try-catch blocks should have been removed\");\n\t\t});\n\n\t\t// Jump to try end will remove it\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()Ljava/lang/String; {\n\t\t\t\t    exceptions: {\n\t\t\t\t        { B, D, E, Ljava/lang/Exception; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        goto D\n\t\t\t\t    B:\n\t\t\t\t        invokestatic Foo.b ()Ljava/lang/String;\n\t\t\t\t    C:\n\t\t\t\t        invokestatic Foo.c ()Ljava/lang/String;\n\t\t\t\t    D:\n\t\t\t\t        goto F\n\t\t\t\t    E:\n\t\t\t\t        pop\n\t\t\t\t    F:\n\t\t\t\t        aconst_null\n\t\t\t\t        areturn\n\t\t\t\t    G:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(DeadCodeRemovingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"exceptions:\", dis), \"Try-catch blocks should have been removed\");\n\t\t\tassertFalse(dis.contains(\"Foo.b\"), \"Expected to remove dead method call\");\n\t\t\tassertFalse(dis.contains(\"Foo.c\"), \"Expected to remove dead method call\");\n\t\t\tassertFalse(dis.contains(\"pop\"), \"Expected to remove dead handler pop\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid dontFoldTryCatchThatIsNotDeadCode() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()Ljava/lang/String; {\n\t\t\t\t    exceptions: {\n\t\t\t\t        { A, B, C, Ljava/lang/Exception; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        invokestatic Foo.get ()Ljava/lang/String;\n\t\t\t\t    B:\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        aconst_null\n\t\t\t\t        areturn\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(DeadCodeRemovingTransformer.class));\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()Ljava/lang/String; {\n\t\t\t\t    exceptions: {\n\t\t\t\t        { A, B, C, Ljava/lang/Exception; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        invokestatic Foo.get ()Ljava/lang/String;\n\t\t\t\t        areturn\n\t\t\t\t    B:\n\t\t\t\t        nop\n\t\t\t\t    C:\n\t\t\t\t        aconst_null\n\t\t\t\t        areturn\n\t\t\t\t    D:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(DeadCodeRemovingTransformer.class), dis -> {\n\t\t\tassertTrue(dis.contains(\"Ljava/lang/Exception;\"), \"Expected to keep exception\");\n\t\t\tassertTrue(dis.contains(\"Foo.get\"), \"Expected to keep method call\");\n\t\t\tassertTrue(dis.contains(\"aconst_null\"), \"Expected to keep handler\");\n\t\t\tassertFalse(dis.contains(\"nop\"), \"Expected to remove dead code\");\n\t\t});\n\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()Ljava/lang/String; {\n\t\t\t\t    exceptions: {\n\t\t\t\t        { B, D, E, Ljava/lang/Exception; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        goto C\n\t\t\t\t    B:\n\t\t\t\t        invokestatic Foo.dead ()V\n\t\t\t\t    C:\n\t\t\t\t        invokestatic Foo.get ()Ljava/lang/String;\n\t\t\t\t    D:\n\t\t\t\t        goto F\n\t\t\t\t    E:\n\t\t\t\t        pop\n\t\t\t\t        aconst_null\n\t\t\t\t    F:\n\t\t\t\t        areturn\n\t\t\t\t    G:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(DeadCodeRemovingTransformer.class), dis -> {\n\t\t\tassertTrue(dis.contains(\"Ljava/lang/Exception;\"), \"Expected to keep exception\");\n\t\t\tassertFalse(dis.contains(\"Foo.dead\"), \"Expected to remove dead method call\");\n\t\t\tassertTrue(dis.contains(\"Foo.get\"), \"Expected to keep method call\");\n\t\t\tassertTrue(dis.contains(\"pop\"), \"Expected to keep handler pop\");\n\t\t});\n\t}\n\n\t/** Simple case used to cover base case in transformer impl */\n\t@Test\n\tvoid foldImmediateGotoNext() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        goto B\n\t\t\t\t    B:\n\t\t\t\t        return\n\t\t\t\t    C:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(GotoInliningTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"goto\", dis), \"Expected to replace all goto <target> with inlining\");\n\t\t\tassertEquals(0, StringUtil.count(\"C:\", dis), \"Expected to simplify out 'C' label, only two labels needed in this example after inlining\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid foldUselessGoto() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        goto D\n\t\t\t\t    B:\n\t\t\t\t        goto C\n\t\t\t\t    C:\n\t\t\t\t        goto G\n\t\t\t\t    D:\n\t\t\t\t        goto H\n\t\t\t\t    E:\n\t\t\t\t        goto L\n\t\t\t\t    F:\n\t\t\t\t        goto N\n\t\t\t\t    G:\n\t\t\t\t        goto E\n\t\t\t\t    H:\n\t\t\t\t        goto M\n\t\t\t\t    I:\n\t\t\t\t        goto J\n\t\t\t\t    J:\n\t\t\t\t        getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t        ldc \"Hello world\"\n\t\t\t\t        invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V\n\t\t\t\t        return\n\t\t\t\t    K:\n\t\t\t\t        goto I\n\t\t\t\t    L:\n\t\t\t\t        goto K\n\t\t\t\t    M:\n\t\t\t\t        goto F\n\t\t\t\t    N:\n\t\t\t\t        goto B\n\t\t\t\t    O:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(GotoInliningTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"goto\", dis), \"Expected to replace all goto <target> with inlining\");\n\t\t});\n\t}\n\n\t/** Showcase pairing of {@link OpaquePredicateFoldingTransformer} with {@link GotoInliningTransformer} */\n\t@Test\n\tvoid foldOpaquePredicatesIntoGotosThenInlineTheGotos() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        ifeq D\n\t\t\t\t        goto E\n\t\t\t\t    B:\n\t\t\t\t        iconst_1\n\t\t\t\t        ifne C\n\t\t\t\t        goto E\n\t\t\t\t    C:\n\t\t\t\t        iconst_0\n\t\t\t\t        ifne E\n\t\t\t\t        goto G\n\t\t\t\t    D:\n\t\t\t\t        iconst_0\n\t\t\t\t        ifeq H\n\t\t\t\t        goto N\n\t\t\t\t    E:\n\t\t\t\t        iconst_2\n\t\t\t\t        ifeq N\n\t\t\t\t        goto L\n\t\t\t\t    F:\n\t\t\t\t        iconst_2\n\t\t\t\t        ifne N\n\t\t\t\t        iconst_0\n\t\t\t\t        ifeq A\n\t\t\t\t        goto L\n\t\t\t\t    G:\n\t\t\t\t        iconst_0\n\t\t\t\t        ifeq E\n\t\t\t\t        goto N\n\t\t\t\t    H:\n\t\t\t\t        iconst_1\n\t\t\t\t        ifeq L\n\t\t\t\t        goto M\n\t\t\t\t    I:\n\t\t\t\t        iconst_4\n\t\t\t\t        ifeq A\n\t\t\t\t    J:\n\t\t\t\t        getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t        ldc \"Hello world\"\n\t\t\t\t        invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V\n\t\t\t\t        return\n\t\t\t\t    K:\n\t\t\t\t        iconst_5\n\t\t\t\t        ifne I\n\t\t\t\t        goto L\n\t\t\t\t    L:\n\t\t\t\t        iconst_0\n\t\t\t\t        ifne J\n\t\t\t\t        goto K\n\t\t\t\t    M:\n\t\t\t\t        iconst_2\n\t\t\t\t        ifne F\n\t\t\t\t        goto N\n\t\t\t\t    N:\n\t\t\t\t        goto B\n\t\t\t\t    O:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterRepeatedAssembly(asm, List.of(OpaquePredicateFoldingTransformer.class, GotoInliningTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"goto\", dis), \"Expected to replace all goto <target> with inlining\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid doNotFoldGotoCycle() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        goto A\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(GotoInliningTransformer.class));\n\t}\n\n\t@Test\n\tvoid doNotFoldGotoOfTransitionBlockCycle() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_1\n\t\t\t\t        pop\n\t\t\t\t        // block \"A\" naturally flows into block \"B\"\n\t\t\t\t    B:\n\t\t\t\t        iconst_2\n\t\t\t\t        pop\n\t\t\t\t        // block \"B\" naturally flows into block \"C\"\n\t\t\t\t    C:\n\t\t\t\t        iconst_3\n\t\t\t\t        ifne D\n\t\t\t\t        // We should not inline block \"B\" here because \"A\" --> \"B\" would be broken if we did that.\n\t\t\t\t        goto B\n\t\t\t\t    D:\n\t\t\t\t        return\n\t\t\t\t    E:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(GotoInliningTransformer.class));\n\t}\n\n\t@Test\n\tvoid doNotFoldGotoInsideTryRangeWithCodeOutsideOfTryRange() {\n\t\t// Because this would technically be a behavior change (unless we do lots of more analysis)\n\t\t// we do not inline goto instructions that span the boundaries of try-catch.\n\t\t//\n\t\t// You would first need to apply a transformer to remove the try-catch if it is not actually useful.\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  E0,  E1,  EH, Ljava/lang/Throwable; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    E0:\n\t\t\t\t        goto X\n\t\t\t\t    E1:\n\t\t\t\t    EH:\n\t\t\t\t        athrow\n\t\t\t\t    X:\n\t\t\t\t        getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t        ldc \"Hello world\"\n\t\t\t\t        invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V\n\t\t\t\t        return\n\t\t\t\t    Z:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(GotoInliningTransformer.class));\n\t}\n\n\t@Test\n\tvoid doNotFoldGotoOutsideTryRangeWithCodeInsideOfTryRange() {\n\t\t// Because this would technically be a behavior change (unless we do lots of more analysis)\n\t\t// we do not inline goto instructions that span the boundaries of try-catch.\n\t\t//\n\t\t// You would first need to apply a transformer to remove the try-catch if it is not actually useful.\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  E0,  E1,  EH, Ljava/lang/Throwable; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        goto E0\n\t\t\t\t    E0:\n\t\t\t\t        getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t        ldc \"Hello world\"\n\t\t\t\t        invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V\n\t\t\t\t        return\n\t\t\t\t    E1:\n\t\t\t\t    EH:\n\t\t\t\t        athrow\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(GotoInliningTransformer.class));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/deobfuscation/IllegalAttributeDeobfuscationTest.java",
    "content": "package software.coley.recaf.services.deobfuscation;\n\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.services.deobfuscation.transform.generic.IllegalAnnotationRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.IllegalSignatureRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.IllegalVarargsRemovingTransformer;\n\nimport java.util.List;\n\n/**\n * Tests {@link IllegalVarargsRemovingTransformer} / {@link IllegalSignatureRemovingTransformer} / {@link IllegalAnnotationRemovingTransformer}.\n */\npublic class IllegalAttributeDeobfuscationTest extends BaseDeobfuscationTest {\n\t@Test\n\tvoid illegalSignatureRemoving() {\n\t\t// An int primitive is an invalid argument for a field signature (Valhalla when Brian?).\n\t\t// Most other invalid signatures aren't visible via decompilation so this suffices to show the transformer works.\n\t\t// Most of it delegates to code that is tested elsewhere.\n\t\tString asm = \"\"\"\n\t\t\t\t.signature \"Ljava/util/List<I>;\"\n\t\t\t\t.field private static foo Ljava/lang/List;\n\t\t\t\t\"\"\";\n\t\tvalidateBeforeAfterDecompile(asm, List.of(IllegalSignatureRemovingTransformer.class), \"List<int> foo\", \"List foo\");\n\t}\n\n\t@Test\n\tvoid illegalVarargsRemoving() {\n\t\t// CFR actually checks for illegal varargs use and emits a nice warning for us.\n\t\t// So we'll just check if that goes away.\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static varargs example ([I[II)V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        return\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateBeforeAfterDecompile(asm, List.of(IllegalVarargsRemovingTransformer.class), \"/* corrupt varargs signature?! */\", null);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/deobfuscation/MiscDeobfuscationTest.java",
    "content": "package software.coley.recaf.services.deobfuscation;\n\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.services.deobfuscation.transform.generic.EnumNameRestorationTransformer;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests for various deobfuscation transformers that don't fit in other test classes.\n */\nclass MiscDeobfuscationTest extends BaseDeobfuscationTest {\n\t@Test\n\tvoid enumNameRestoration() {\n\t\tString asm = \"\"\"\n\t\t\t\t.super java/lang/Enum\n\t\t\t\t.class public final enum Example {\n\t\t\t\t    .field public static final enum a LExample;\n\t\t\t\t    .field public static final enum b LExample;\n\t\t\t\t    .field public static final enum c LExample;\n\t\t\t\t    .field public static final enum d LExample;\n\t\t\t\t    .field private static final x [LExample;\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t    .method static <clinit> ()V {\n\t\t\t\t        code: {\n\t\t\t\t        A:\n\t\t\t\t            new Example\n\t\t\t\t            dup\n\t\t\t\t            ldc \"STATIC\"\n\t\t\t\t            iconst_0\n\t\t\t\t            invokespecial Example.<init> (Ljava/lang/String;I)V\n\t\t\t\t            putstatic Example.a LExample;\n\t\t\t\t        B:\n\t\t\t\t            new Example\n\t\t\t\t            dup\n\t\t\t\t            ldc \"WORLDGEN\"\n\t\t\t\t            iconst_1\n\t\t\t\t            invokespecial Example.<init> (Ljava/lang/String;I)V\n\t\t\t\t            putstatic Example.b LExample;\n\t\t\t\t        C:\n\t\t\t\t            new Example\n\t\t\t\t            dup\n\t\t\t\t            ldc \"DIMENSIONS\"\n\t\t\t\t            iconst_2\n\t\t\t\t            invokespecial Example.<init> (Ljava/lang/String;I)V\n\t\t\t\t            putstatic Example.c LExample;\n\t\t\t\t        D:\n\t\t\t\t            new Example\n\t\t\t\t            dup\n\t\t\t\t            ldc \"RELOADABLE\"\n\t\t\t\t            iconst_3\n\t\t\t\t            invokespecial Example.<init> (Ljava/lang/String;I)V\n\t\t\t\t            putstatic Example.d LExample;\n\t\t\t\t        E:\n\t\t\t\t            invokestatic Example.$values ()[LExample;\n\t\t\t\t            putstatic Example.x [LExample;\n\t\t\t\t        F:\n\t\t\t\t            return\n\t\t\t\t        G:\n\t\t\t\t        }\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateMappingAfterAssembly(asm, List.of(EnumNameRestorationTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"enum STATIC LExample;\", dis), \"Missing enum const mapping\");\n\t\t\tassertEquals(1, StringUtil.count(\"enum WORLDGEN LExample;\", dis), \"Missing enum const mapping\");\n\t\t\tassertEquals(1, StringUtil.count(\"enum DIMENSIONS LExample;\", dis), \"Missing enum const mapping\");\n\t\t\tassertEquals(1, StringUtil.count(\"enum RELOADABLE LExample;\", dis), \"Missing enum const mapping\");\n\t\t\tassertEquals(1, StringUtil.count(\"final $values [LExample;\", dis), \"Missing enum $values array mapping\");\n\t\t});\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/deobfuscation/RegressionDeobfuscationTest.java",
    "content": "package software.coley.recaf.services.deobfuscation;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport software.coley.recaf.services.deobfuscation.transform.generic.CallResultInliningTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.DeadCodeRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.GotoInliningTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.OpaqueConstantFoldingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.OpaquePredicateFoldingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.RedundantTryCatchRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.VariableFoldingTransformer;\nimport software.coley.recaf.util.AsmInsnUtil;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.analysis.ReInterpreter;\nimport software.coley.recaf.util.analysis.value.ReValue;\n\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * An assortment of deobfuscation tests, specifically to ensure weird bugs that occurred at one point do not come back.\n */\npublic class RegressionDeobfuscationTest extends BaseDeobfuscationTest {\n\t@Test\n\tvoid gotoInlining1a() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method static example ([I)V {\n\t\t\t\t    parameters: { array },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        goto P\n\t\t\t\t    B:\n\t\t\t\t        goto D\n\t\t\t\t    C:\n\t\t\t\t        goto Q\n\t\t\t\t    D:\n\t\t\t\t        goto F\n\t\t\t\t    E:\n\t\t\t\t        goto R\n\t\t\t\t    F:\n\t\t\t\t        goto H\n\t\t\t\t    G:\n\t\t\t\t        goto S\n\t\t\t\t    H:\n\t\t\t\t        goto J\n\t\t\t\t    I:\n\t\t\t\t        goto T\n\t\t\t\t    J:\n\t\t\t\t        goto L\n\t\t\t\t    K:\n\t\t\t\t        goto U\n\t\t\t\t    L:\n\t\t\t\t        aload array\n\t\t\t\t        iload i\n\t\t\t\t        iaload\n\t\t\t\t        invokevirtual java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;\n\t\t\t\t        ldc \" \"\n\t\t\t\t        invokevirtual java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;\n\t\t\t\t        invokevirtual java/lang/StringBuilder.toString ()Ljava/lang/String;\n\t\t\t\t        invokevirtual java/io/PrintStream.print (Ljava/lang/String;)V\n\t\t\t\t    M:\n\t\t\t\t        iinc i 1\n\t\t\t\t        goto W\n\t\t\t\t    N:\n\t\t\t\t        getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t        invokevirtual java/io/PrintStream.println ()V\n\t\t\t\t    O:\n\t\t\t\t        return\n\t\t\t\t    P:\n\t\t\t\t        goto C\n\t\t\t\t    Q:\n\t\t\t\t        goto E\n\t\t\t\t    R:\n\t\t\t\t        goto G\n\t\t\t\t    S:\n\t\t\t\t        goto I\n\t\t\t\t    T:\n\t\t\t\t        goto K\n\t\t\t\t    U:\n\t\t\t\t        aload array\n\t\t\t\t        arraylength\n\t\t\t\t        istore length\n\t\t\t\t    V:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore i\n\t\t\t\t    W:\n\t\t\t\t        iload i\n\t\t\t\t        iload length\n\t\t\t\t        if_icmpge N\n\t\t\t\t    X:\n\t\t\t\t        getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t        new java/lang/StringBuilder\n\t\t\t\t        dup\n\t\t\t\t        invokespecial java/lang/StringBuilder.<init> ()V\n\t\t\t\t        goto B\n\t\t\t\t    Y:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(GotoInliningTransformer.class, DeadCodeRemovingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Only one goto should remain (the while loop handler)\");\n\t\t});\n\t}\n\n\t/** The same as {@link #gotoInlining1a()} but starting from an easier step. */\n\t@Test\n\tvoid gotoInlining1b() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method static example ([I)V {\n\t\t\t\t    parameters: { array },\n\t\t\t\t    code: {\n\t\t\t\t\t\t  A:\n\t\t\t\t              goto C\n\t\t\t\t          B:\n\t\t\t\t              getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t              invokevirtual java/io/PrintStream.println ()V\n\t\t\t\t              return\n\t\t\t\t          C:\n\t\t\t\t              aload array\n\t\t\t\t              arraylength\n\t\t\t\t              istore length\n\t\t\t\t              iconst_0\n\t\t\t\t              istore i\n\t\t\t\t          D:\n\t\t\t\t              iload i1\n\t\t\t\t              iload i2\n\t\t\t\t              if_icmpge B\n\t\t\t\t              getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t              new java/lang/StringBuilder\n\t\t\t\t              dup\n\t\t\t\t              invokespecial java/lang/StringBuilder.<init> ()V\n\t\t\t\t              aload array\n\t\t\t\t              iload i1\n\t\t\t\t              iaload\n\t\t\t\t              invokevirtual java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;\n\t\t\t\t              ldc \" \"\n\t\t\t\t              invokevirtual java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;\n\t\t\t\t              invokevirtual java/lang/StringBuilder.toString ()Ljava/lang/String;\n\t\t\t\t              invokevirtual java/io/PrintStream.print (Ljava/lang/String;)V\n\t\t\t\t              iinc i1 1\n\t\t\t\t              goto D\n\t\t\t\t          E:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(GotoInliningTransformer.class, DeadCodeRemovingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"goto\", dis), \"Only one goto should remain (the while loop handler)\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid gotoInlining2() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ([Ljava/lang/String;)V {\n\t\t\t\t    parameters: { args },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        getstatic sample/math/BinarySearch.l Z\n\t\t\t\t        istore i6\n\t\t\t\t        goto C\n\t\t\t\t    B:\n\t\t\t\t        return\n\t\t\t\t    C:\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t    D:\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t        new sample/math/BinarySearch\n\t\t\t\t        dup\n\t\t\t\t        invokespecial sample/math/BinarySearch.<init> ()V\n\t\t\t\t        astore ob\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t    E:\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t        iconst_5\n\t\t\t\t        newarray int\n\t\t\t\t        dup\n\t\t\t\t        iconst_0\n\t\t\t\t        iconst_2\n\t\t\t\t        iastore\n\t\t\t\t        dup\n\t\t\t\t        iconst_1\n\t\t\t\t        iconst_3\n\t\t\t\t        iastore\n\t\t\t\t        dup\n\t\t\t\t        iconst_2\n\t\t\t\t        iconst_4\n\t\t\t\t        iastore\n\t\t\t\t        dup\n\t\t\t\t        iconst_3\n\t\t\t\t        bipush 10\n\t\t\t\t        iastore\n\t\t\t\t        dup\n\t\t\t\t        iconst_4\n\t\t\t\t        bipush 40\n\t\t\t\t        iastore\n\t\t\t\t        astore arr\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t    F:\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t        aload arr\n\t\t\t\t        arraylength\n\t\t\t\t        istore n\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t    G:\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t        bipush 10\n\t\t\t\t        istore x\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t    H:\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t        aload ob\n\t\t\t\t        aload arr\n\t\t\t\t        iconst_0\n\t\t\t\t        iload n\n\t\t\t\t        iconst_1\n\t\t\t\t        isub\n\t\t\t\t        iload x\n\t\t\t\t        invokevirtual sample/math/BinarySearch.binarySearch ([IIII)I\n\t\t\t\t        istore result\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t    I:\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t        iload result\n\t\t\t\t        iconst_m1\n\t\t\t\t        if_icmpne J\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t        getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t        ldc \"Element not present\"\n\t\t\t\t        invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t        goto K\n\t\t\t\t    J:\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t        getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t        new java/lang/StringBuilder\n\t\t\t\t        dup\n\t\t\t\t        invokespecial java/lang/StringBuilder.<init> ()V\n\t\t\t\t        ldc \"Element found at index \"\n\t\t\t\t        invokevirtual java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;\n\t\t\t\t        iload result\n\t\t\t\t        invokevirtual java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;\n\t\t\t\t        invokevirtual java/lang/StringBuilder.toString ()Ljava/lang/String;\n\t\t\t\t        invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t    K:\n\t\t\t\t        // This block should NEVER be inlined because if it were, then we'd have no\n\t\t\t\t        // terminating instruction at the end of the method.\n\t\t\t\t        iload i6\n\t\t\t\t        ifne B\n\t\t\t\t        return\n\t\t\t\t        // This dead code can be found as a result of some obfuscators.\n\t\t\t\t        // It can fool with our analysis of \"can we remove this block and not have dangling code?\"\n\t\t\t\t        // so having it here to make sure this still passes is important.\n\t\t\t\t        // See the internal comments in the goto-inlining transformer for more details.\n\t\t\t\t        nop\n\t\t\t\t        athrow\n\t\t\t\t    L:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(GotoInliningTransformer.class, DeadCodeRemovingTransformer.class), dis -> {\n\t\t\t// Just needs to pass, dead-code remover's analysis process\n\t\t\t// failing would indicate this has failed with an exception\n\t\t});\n\t}\n\n\t/**\n\t * Bug from improper label visitation tracking in {@link GotoInliningTransformer} due to improper catch block\n\t * tracking.\n\t */\n\t@Test\n\tvoid gotoInlining3() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public execute (Lorg/apache/http/HttpHost;Lorg/apache/http/HttpRequest;Lorg/apache/http/client/ResponseHandler;Lorg/apache/http/protocol/HttpContext;)Ljava/lang/Object; {\n\t\t\t\t    parameters: { this, host, request, handler, context },\n\t\t\t\t    exceptions: {\n\t\t\t\t        { B, C, D, Lorg/apache/http/client/ClientProtocolException; },\n\t\t\t\t        { E, F, G, Ljava/lang/Exception; },\n\t\t\t\t        { B, C, I, * },\n\t\t\t\t        { D, J, I, * }\n\t\t\t\t     },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aload this\n\t\t\t\t        aload host\n\t\t\t\t        aload request\n\t\t\t\t        aload context\n\t\t\t\t        invokevirtual org/apache/http/impl/client/CloseableHttpClient.execute (Lorg/apache/http/HttpHost;Lorg/apache/http/HttpRequest;Lorg/apache/http/protocol/HttpContext;)Lorg/apache/http/client/methods/CloseableHttpResponse;\n\t\t\t\t        astore response\n\t\t\t\t    B:\n\t\t\t\t        // try-start:   range=[B-C] handler=D:org/apache/http/client/ClientProtocolException\n\t\t\t\t        // try-start:   range=[B-C] handler=I:*\n\t\t\t\t        aload handler\n\t\t\t\t        aload response\n\t\t\t\t        invokeinterface org/apache/http/client/ResponseHandler.handleResponse (Lorg/apache/http/HttpResponse;)Ljava/lang/Object;\n\t\t\t\t        astore protoError\n\t\t\t\t        aload response\n\t\t\t\t        invokeinterface org/apache/http/client/methods/CloseableHttpResponse.getEntity ()Lorg/apache/http/HttpEntity;\n\t\t\t\t        astore entity\n\t\t\t\t        aload entity\n\t\t\t\t        invokestatic org/apache/http/util/EntityUtils.consume (Lorg/apache/http/HttpEntity;)V\n\t\t\t\t        aload protoError\n\t\t\t\t        astore ex\n\t\t\t\t    C:\n\t\t\t\t        // try-end:     range=[B-C] handler=D:org/apache/http/client/ClientProtocolException\n\t\t\t\t        // try-end:     range=[B-C] handler=I:*\n\t\t\t\t        aload response\n\t\t\t\t        invokeinterface org/apache/http/client/methods/CloseableHttpResponse.close ()V\n\t\t\t\t        aload ex\n\t\t\t\t        areturn\n\t\t\t\t    D:\n\t\t\t\t        // try-handler: range=[B-C] handler=D:org/apache/http/client/ClientProtocolException\n\t\t\t\t        // try-start:   range=[D-J] handler=I:*\n\t\t\t\t        astore protoError\n\t\t\t\t        aload response\n\t\t\t\t        invokeinterface org/apache/http/client/methods/CloseableHttpResponse.getEntity ()Lorg/apache/http/HttpEntity;\n\t\t\t\t        astore entity\n\t\t\t\t    E:\n\t\t\t\t        // try-start:   range=[E-F] handler=G:java/lang/Exception\n\t\t\t\t        aload entity\n\t\t\t\t        invokestatic org/apache/http/util/EntityUtils.consume (Lorg/apache/http/HttpEntity;)V\n\t\t\t\t    F:\n\t\t\t\t        // try-end:     range=[E-F] handler=G:java/lang/Exception\n\t\t\t\t        goto H\n\t\t\t\t    G:\n\t\t\t\t        // try-handler: range=[E-F] handler=G:java/lang/Exception\n\t\t\t\t        astore ex\n\t\t\t\t    H:\n\t\t\t\t        aload protoError\n\t\t\t\t        athrow\n\t\t\t\t    I:\n\t\t\t\t        // try-handler: range=[B-C] handler=I:*\n\t\t\t\t        // try-handler: range=[D-J] handler=I:*\n\t\t\t\t        astore t\n\t\t\t\t    J:\n\t\t\t\t        // try-end:     range=[D-J] handler=I:*\n\t\t\t\t        aload t\n\t\t\t\t        athrow\n\t\t\t\t    K:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(GotoInliningTransformer.class));\n\t}\n\n\t/**\n\t * Bug from {@link VariableFoldingTransformer} where {@code IINC} on a variable initially assigned to {@code 0}\n\t * wouldn't properly change the tracked state of the variable at the given slot. It usually indicates something\n\t * like a for loop counter. We would want to change the state to \"unknown\" since a loop counter can have more than\n\t * a single value during its lifespan in the relevant bytecode ranges.\n\t * <br>\n\t * If we properly track {@code IINC} handling, this should see no transformations.\n\t */\n\t@Test\n\tvoid variableFoldingWithIinc() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public equals (Ljava/lang/Object;)Z {\n\t\t\t\t    parameters: { this, object },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aload object\n\t\t\t\t        aload this\n\t\t\t\t        if_acmpne D\n\t\t\t\t    B:\n\t\t\t\t        iconst_1\n\t\t\t\t        ireturn\n\t\t\t\t    D:\n\t\t\t\t        aload object\n\t\t\t\t        checkcast Example\n\t\t\t\t        astore that\n\t\t\t\t    E:\n\t\t\t\t        aload this\n\t\t\t\t        invokevirtual Example.size ()I\n\t\t\t\t        istore size\n\t\t\t\t    F:\n\t\t\t\t        aload that\n\t\t\t\t        invokevirtual Example.size ()I\n\t\t\t\t        iload size\n\t\t\t\t        if_icmpeq H\n\t\t\t\t    G:\n\t\t\t\t        iconst_0\n\t\t\t\t        ireturn\n\t\t\t\t    H:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore i\n\t\t\t\t    I:\n\t\t\t\t        iload i\n\t\t\t\t        iload size\n\t\t\t\t        if_icmpge M\n\t\t\t\t    J:\n\t\t\t\t        aload this\n\t\t\t\t        getfield Example.array [I\n\t\t\t\t        aload this\n\t\t\t\t        getfield Example.start I\n\t\t\t\t        iload i\n\t\t\t\t        iadd\n\t\t\t\t        iaload\n\t\t\t\t        aload that\n\t\t\t\t        getfield Example.array [I\n\t\t\t\t        aload that\n\t\t\t\t        getfield Example.start I\n\t\t\t\t        iload i\n\t\t\t\t        iadd\n\t\t\t\t        iaload\n\t\t\t\t        if_icmpeq L\n\t\t\t\t    K:\n\t\t\t\t        iconst_0\n\t\t\t\t        ireturn\n\t\t\t\t    L:\n\t\t\t\t        iinc i 1\n\t\t\t\t        goto I\n\t\t\t\t    M:\n\t\t\t\t        iconst_1\n\t\t\t\t        ireturn\n\t\t\t\t    N:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(VariableFoldingTransformer.class));\n\t}\n\n\t@Test\n\tvoid callInliningOfUnhandledCoreJdkClassesDoesNotFail() {\n\t\t// Unknown values like 'Thread.currentThread()' shouldn't throw exceptions.\n\t\t// Instead, nothing should happen at all here.\n\t\tString asm = \"\"\"\n\t\t\t\t.method public test ()I {\n\t\t\t\t    parameters: { this },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        invokestatic java/lang/Thread.currentThread ()Ljava/lang/Thread;\n\t\t\t\t        invokevirtual java/lang/Object.hashCode ()I\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(CallResultInliningTransformer.class));\n\t}\n\n\t/**\n\t * This case has \"xyz\" always be \"1\" when read, so it should fold.\n\t */\n\t@Test\n\tvoid variableFoldingWithCatchThatHasNoThrowableContents() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public example ()I {\n\t\t\t\t    parameters: { this },\n\t\t\t\t    exceptions: { { B, C, D, Ljava/lang/Throwable; } },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        // Redundant first assignment\n\t\t\t\t        iconst_0\n\t\t\t\t        istore xyz\n\t\t\t\t    B:\n\t\t\t\t        // Will always overwrite, throwing behavior impossible\n\t\t\t\t        iconst_1\n\t\t\t\t        istore xyz\n\t\t\t\t    C:\n\t\t\t\t        goto E\n\t\t\t\t    D:\n\t\t\t\t        // Handler, not clear what should be folded here since the try block can't throw anything\n\t\t\t\t        // making it impossible to actually end up here.\n\t\t\t\t        pop\n\t\t\t\t        iload xyz\n\t\t\t\t        ireturn\n\t\t\t\t    E:\n\t\t\t\t        // Should always be \"1\" here since the try block can't throw anything\n\t\t\t\t        iload xyz\n\t\t\t\t        ireturn\n\t\t\t\t    F:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterRepeatedAssembly(asm, List.of(VariableFoldingTransformer.class), dis -> {\n\t\t\tassertFalse(RegexUtil.matchesAny(\"iconst_0\\\\s+istore\", dis), \"Redundant var store should be folded\");\n\t\t\tassertTrue(RegexUtil.matchesAny(\"iconst_1\\\\s+istore\", dis), \"Final var store should be kept\");\n\t\t\tassertTrue(RegexUtil.matchesAny(\"E:\\\\s+iconst_1\\\\s+ireturn\", dis), \"E block should fold to iconst_1\");\n\t\t\tassertTrue(RegexUtil.matchesAny(\"D:\\\\s+//.+\\\\s+pop\\\\s+iload xyz\\\\s+ireturn\", dis), \"D block shouldn't fold since it's technically possible to end up here, even if it can't actually happen in practice\");\n\t\t});\n\t}\n\n\t/**\n\t * This case has \"xyz\" change inside a \"try\" block, with a possibly throwing\n\t * method before the new value is assigned. Depending on if the called method\n\t * throws or not, the \"xyz\" value will be changed.\n\t */\n\t@Test\n\tvoid variableFoldingWithCatchThatCanThrow() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public example ()I {\n\t\t\t\t    parameters: { this },\n\t\t\t\t    exceptions: { { B, C, D, Ljava/lang/Throwable; } },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore xyz\n\t\t\t\t    B:\n\t\t\t\t        // Method can throw, so \"xyz\" later on can be either \"0\" or \"1\"\n\t\t\t\t        invokestatic Example.throwError ()V\n\t\t\t\t        iconst_1\n\t\t\t\t        istore xyz\n\t\t\t\t    C:\n\t\t\t\t        goto E\n\t\t\t\t    D:\n\t\t\t\t        // handler, pop exception off stack\n\t\t\t\t        pop\n\t\t\t\t        // In this basic case, this should always be \"0\"\n\t\t\t\t        iload xyz\n\t\t\t\t        ireturn\n\t\t\t\t    E:\n\t\t\t\t        // In this basic case, this should always be \"1\"\n\t\t\t\t        iload xyz\n\t\t\t\t        ireturn\n\t\t\t\t    F:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterRepeatedAssembly(asm, List.of(VariableFoldingTransformer.class), dis -> {\n\t\t\tassertTrue(RegexUtil.matchesAny(\"iconst_0\\\\s+istore\", dis), \"Both var stores should be kept\");\n\t\t\tassertTrue(RegexUtil.matchesAny(\"iconst_1\\\\s+istore\", dis), \"Both var stores should be kept\");\n\t\t\tassertTrue(RegexUtil.matchesAny(\"E:\\\\s+iconst_1\\\\s+ireturn\", dis), \"E block should fold to iconst_1\");\n\t\t\tassertTrue(RegexUtil.matchesAny(\"D:\\\\s+//.+\\\\s+pop\\\\s+iload xyz\\\\s+ireturn\", dis), \"D block shouldn't fold since it can be either 0 or 1\");\n\t\t});\n\t}\n\n\t/**\n\t * Advanced case of {@link #variableFoldingWithCatchThatCanThrow()}.\n\t * <br>\n\t * Multiple calls to throwing methods, with variable assignments after.\n\t */\n\t@Test\n\tvoid variableFoldingWithCatchThatCanThrowAdvanced() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public example ()I {\n\t\t\t\t    parameters: { this },\n\t\t\t\t    exceptions: { { B, C, D, Ljava/lang/Throwable; } },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore xyz\n\t\t\t\t    B:\n\t\t\t\t        // Method can throw, so \"xyz\" later on can be any \"0-3\" value.\n\t\t\t\t        invokestatic Example.throwError1 ()V\n\t\t\t\t        iconst_1\n\t\t\t\t        istore xyz\n\t\t\t\t        invokestatic Example.throwError2 ()V\n\t\t\t\t        iconst_2\n\t\t\t\t        istore xyz\n\t\t\t\t        invokestatic Example.throwError3 ()V\n\t\t\t\t        iconst_3\n\t\t\t\t        istore xyz\n\t\t\t\t    C:\n\t\t\t\t        goto E\n\t\t\t\t    D:\n\t\t\t\t        // handler, pop exception off stack\n\t\t\t\t        pop\n\t\t\t\t        // In this advanced case, this should be \"0\", \"1\", or \"2\"\n\t\t\t\t        iload xyz\n\t\t\t\t        ireturn\n\t\t\t\t    E:\n\t\t\t\t        // In this advanced case, this should always be \"3\"\n\t\t\t\t        iload xyz\n\t\t\t\t        ireturn\n\t\t\t\t    F:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterRepeatedAssembly(asm, List.of(VariableFoldingTransformer.class), dis -> {\n\t\t\tassertTrue(RegexUtil.matchesAny(\"iconst_0\\\\s+istore\", dis), \"All var stores should be kept\");\n\t\t\tassertTrue(RegexUtil.matchesAny(\"iconst_1\\\\s+istore\", dis), \"All var stores should be kept\");\n\t\t\tassertTrue(RegexUtil.matchesAny(\"iconst_2\\\\s+istore\", dis), \"All var stores should be kept\");\n\t\t\tassertTrue(RegexUtil.matchesAny(\"iconst_3\\\\s+istore\", dis), \"All var stores should be kept\");\n\t\t\tassertTrue(RegexUtil.matchesAny(\"E:\\\\s+iconst_3\\\\s+ireturn\", dis), \"E block should fold to iconst_3\");\n\t\t\tassertTrue(RegexUtil.matchesAny(\"D:\\\\s+//.+\\\\s+pop\\\\s+iload xyz\\\\s+ireturn\", dis), \"D block shouldn't fold since it can be 0, 1, or 2\");\n\t\t});\n\t}\n\n\t/**\n\t * If {@link ReInterpreter#merge(ReValue, ReValue)} is implemented incorrectly, some transformers relying on\n\t * frame analysis with {@link ReValue} contents may incorrectly assume they should make optimizations.\n\t * <p>\n\t * This example shows an if-else control flow based on some value stored in a local variable. That switch key\n\t * is unknown at the start, so any branch taken can modify the local variable state. This means we should\n\t * end up at the if-else control flow with an unknown value <i>(Or at the very least, not a single known value)</i>.\n\t * Because of this, no specific path in this if-else should be optimized away.\n\t * <p>\n\t * If we see any transforms take place here, something is broken.\n\t */\n\t@Test\n\tvoid frameMergeIncorrectlyLeadsToImproperOptimization() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        getstatic Example.key I\n\t\t\t\t        lookupswitch {\n\t\t\t\t\t\t    1: B,\n\t\t\t\t\t\t    2: C,\n\t\t\t\t\t\t    3: D,\n\t\t\t\t\t\t    default: E\n\t\t\t\t\t\t}\n\t\t\t\t    B:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore foo\n\t\t\t\t        goto F\n\t\t\t\t    C:\n\t\t\t\t        iconst_1\n\t\t\t\t        istore foo\n\t\t\t\t        goto F\n\t\t\t\t    D:\n\t\t\t\t        iconst_2\n\t\t\t\t        istore foo\n\t\t\t\t        goto F\n\t\t\t\t    E:\n\t\t\t\t        iconst_3\n\t\t\t\t        istore foo\n\t\t\t\t        goto F\n\t\t\t\t    F:\n\t\t\t\t        iload foo\n\t\t\t\t        ifeq G\n\t\t\t\t        invokestatic Example.nonzero ()V\n\t\t\t\t        return\n\t\t\t\t    G:\n\t\t\t\t        invokestatic Example.zero ()V\n\t\t\t\t        return\n\t\t\t\t    Z:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(VariableFoldingTransformer.class, OpaqueConstantFoldingTransformer.class, OpaquePredicateFoldingTransformer.class));\n\t}\n\n\t@Test\n\tvoid multiStepInteractionsOfVariousFoldingTransformers() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public example ()Ljava/lang/String; {\n\t\t\t\t    parameters: { this },\n\t\t\t\t    exceptions: {\n\t\t\t\t        { E, F, G, Ljava/lang/RuntimeException; },\n\t\t\t\t        { G, H, G, Ljava/lang/RuntimeException; }\n\t\t\t\t     },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        sipush 6197\n\t\t\t\t        sipush 1729\n\t\t\t\t        iand\n\t\t\t\t        tableswitch {\n\t\t\t\t            min: 0,\n\t\t\t\t            max: 3,\n\t\t\t\t            cases: { C, D, B },\n\t\t\t\t            default: B\n\t\t\t\t        }\n\t\t\t\t    B:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    D:\n\t\t\t\t        goto E\n\t\t\t\t    E:\n\t\t\t\t        ldc 191946085\n\t\t\t\t        ldc 191943797\n\t\t\t\t        iand\n\t\t\t\t        aconst_null\n\t\t\t\t        ifnull I\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    F:\n\t\t\t\t        nop\n\t\t\t\t        nop\n\t\t\t\t        athrow\n\t\t\t\t    G:\n\t\t\t\t        dup\n\t\t\t\t        invokevirtual java/lang/RuntimeException.printStackTrace ()V\n\t\t\t\t        checkcast java/lang/Throwable\n\t\t\t\t        athrow\n\t\t\t\t    H:\n\t\t\t\t        nop\n\t\t\t\t        nop\n\t\t\t\t        athrow\n\t\t\t\t    I:\n\t\t\t\t        lookupswitch {\n\t\t\t\t            191943781: K,\n\t\t\t\t            1241362125: J,\n\t\t\t\t            default: J\n\t\t\t\t        }\n\t\t\t\t    J:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    K:\n\t\t\t\t        goto L\n\t\t\t\t    L:\n\t\t\t\t        aload this\n\t\t\t\t        getfield Foo.value Ljava/lang/String;\n\t\t\t\t        areturn\n\t\t\t\t    M:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\t// This sample does not work in a single pass, it needs multiple to properly fold everything.\n\t\t// Any breakage in one transformer will affect the others.\n\t\tvalidateAfterRepeatedAssembly(asm, List.of(\n\t\t\t\tRedundantTryCatchRemovingTransformer.class,\n\t\t\t\tOpaqueConstantFoldingTransformer.class,\n\t\t\t\tGotoInliningTransformer.class,\n\t\t\t\tOpaquePredicateFoldingTransformer.class\n\t\t), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"goto\", dis), \"All goto instructions should be inlined\");\n\t\t\tassertEquals(0, StringUtil.count(\"switch\", dis), \"All switch instructions should be inlined\");\n\t\t\tassertEquals(0, StringUtil.count(\"athrow\", dis), \"All athrow instructions should removed\");\n\t\t\tassertEquals(0, StringUtil.count(\"aconst_null\", dis), \"All athrow instructions should removed\");\n\t\t\tassertEquals(0, StringUtil.count(\"ldc\", dis), \"All ldc instructions should removed\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid singleStackNoLocalsDoesNotAIOOBEConstantFolding() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()Ljava/lang/Object; {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        getstatic Foo.foo I\n\t\t\t\t        newarray int\n\t\t\t\t        areturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(OpaqueConstantFoldingTransformer.class));\n\t}\n\n\t/**\n\t * The first pass of the constant folding transformer should let us know what the final value is.\n\t * Everything else should facilitate cleanup. The arrays are referenced a lot due to how arrays are\n\t * populated in Java. It's not like Dalvik where you can just push a fully constructed array in one go.\n\t * Because of this, we need to detect that these reads/writes are not actually useful and clean up where\n\t * necessary.\n\t *\n\t * @see #constFoldingAndVariableFoldingWillNotAggressivelyRemoveArrayConstructionsForArraysUsedElsewhere()\n\t */\n\t@Test\n\tvoid constFoldingAndVariableFoldingWillEventuallyCleanUpNoLongerUsedArrays() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t     code: {\n\t\t\t\t     A:\n\t\t\t\t         iconst_5\n\t\t\t\t         newarray int\n\t\t\t\t         astore a\n\t\t\t\t     B:\n\t\t\t\t         aload a\n\t\t\t\t         iconst_0\n\t\t\t\t         iconst_1\n\t\t\t\t         iastore\n\t\t\t\t     C:\n\t\t\t\t         aload a\n\t\t\t\t         iconst_1\n\t\t\t\t         bipush 10\n\t\t\t\t         iastore\n\t\t\t\t     D:\n\t\t\t\t         aload a\n\t\t\t\t         iconst_2\n\t\t\t\t         bipush 100\n\t\t\t\t         iastore\n\t\t\t\t     E:\n\t\t\t\t         aload a\n\t\t\t\t         iconst_3\n\t\t\t\t         sipush 1000\n\t\t\t\t         iastore\n\t\t\t\t     F:\n\t\t\t\t         aload a\n\t\t\t\t         iconst_4\n\t\t\t\t         sipush 10000\n\t\t\t\t         iastore\n\t\t\t\t     G:\n\t\t\t\t         iconst_5\n\t\t\t\t         newarray int\n\t\t\t\t         astore b\n\t\t\t\t     H:\n\t\t\t\t         iconst_2\n\t\t\t\t         aload a\n\t\t\t\t         aload b\n\t\t\t\t         arraylength\n\t\t\t\t         iconst_2\n\t\t\t\t         iushr\n\t\t\t\t         iaload\n\t\t\t\t         aload a\n\t\t\t\t         arraylength\n\t\t\t\t         idiv\n\t\t\t\t         imul\n\t\t\t\t         newarray int\n\t\t\t\t         astore c\n\t\t\t\t     I:\n\t\t\t\t         aload c\n\t\t\t\t         arraylength\n\t\t\t\t         iconst_1\n\t\t\t\t         iushr\n\t\t\t\t         newarray int\n\t\t\t\t         astore b\n\t\t\t\t     J:\n\t\t\t\t         aload b\n\t\t\t\t         aload c\n\t\t\t\t         iconst_0\n\t\t\t\t         iaload\n\t\t\t\t         aload a\n\t\t\t\t         iconst_3\n\t\t\t\t         iaload\n\t\t\t\t         aload c\n\t\t\t\t         arraylength\n\t\t\t\t         iushr\n\t\t\t\t         iastore\n\t\t\t\t     K:\n\t\t\t\t         aload b\n\t\t\t\t         aload a\n\t\t\t\t         iconst_0\n\t\t\t\t         iaload\n\t\t\t\t         aload b\n\t\t\t\t         arraylength\n\t\t\t\t         iconst_2\n\t\t\t\t         ishl\n\t\t\t\t         aload b\n\t\t\t\t         aload a\n\t\t\t\t         iconst_0\n\t\t\t\t         iaload\n\t\t\t\t         iconst_1\n\t\t\t\t         iushr\n\t\t\t\t         iaload\n\t\t\t\t         iadd\n\t\t\t\t         aload c\n\t\t\t\t         arraylength\n\t\t\t\t         ishl\n\t\t\t\t         iconst_2\n\t\t\t\t         ishr\n\t\t\t\t         iastore\n\t\t\t\t     L:\n\t\t\t\t         aload c\n\t\t\t\t         aload a\n\t\t\t\t         iconst_1\n\t\t\t\t         iaload\n\t\t\t\t         bipush 10\n\t\t\t\t         newarray int\n\t\t\t\t         arraylength\n\t\t\t\t         idiv\n\t\t\t\t         aload a\n\t\t\t\t         iconst_4\n\t\t\t\t         iaload\n\t\t\t\t         iastore\n\t\t\t\t     M:\n\t\t\t\t         aload c\n\t\t\t\t         iconst_0\n\t\t\t\t         aload c\n\t\t\t\t         iconst_1\n\t\t\t\t         iaload\n\t\t\t\t         iconst_4\n\t\t\t\t         ishr\n\t\t\t\t         iastore\n\t\t\t\t     N:\n\t\t\t\t         aload c\n\t\t\t\t         iconst_3\n\t\t\t\t         aload a\n\t\t\t\t         iconst_2\n\t\t\t\t         newarray int\n\t\t\t\t         arraylength\n\t\t\t\t         iaload\n\t\t\t\t         bipush 10\n\t\t\t\t         imul\n\t\t\t\t         iastore\n\t\t\t\t     O:\n\t\t\t\t         aload c\n\t\t\t\t         iconst_2\n\t\t\t\t         aload c\n\t\t\t\t         iconst_3\n\t\t\t\t         iaload\n\t\t\t\t         iconst_1\n\t\t\t\t         ishr\n\t\t\t\t         iastore\n\t\t\t\t     P:\n\t\t\t\t         aload a\n\t\t\t\t         iconst_0\n\t\t\t\t         iaload\n\t\t\t\t         istore a0\n\t\t\t\t     Q:\n\t\t\t\t         aload a\n\t\t\t\t         iconst_1\n\t\t\t\t         iaload\n\t\t\t\t         istore a1\n\t\t\t\t     R:\n\t\t\t\t         aload a\n\t\t\t\t         iconst_2\n\t\t\t\t         iaload\n\t\t\t\t         istore a2\n\t\t\t\t     S:\n\t\t\t\t         aload a\n\t\t\t\t         iconst_3\n\t\t\t\t         iaload\n\t\t\t\t         istore a3\n\t\t\t\t     T:\n\t\t\t\t         aload a\n\t\t\t\t         iconst_4\n\t\t\t\t         iaload\n\t\t\t\t         istore a4\n\t\t\t\t     U:\n\t\t\t\t         aload b\n\t\t\t\t         iconst_0\n\t\t\t\t         iaload\n\t\t\t\t         istore b0\n\t\t\t\t     V:\n\t\t\t\t         aload b\n\t\t\t\t         iconst_1\n\t\t\t\t         iaload\n\t\t\t\t         istore b1\n\t\t\t\t     W:\n\t\t\t\t         aload c\n\t\t\t\t         iconst_0\n\t\t\t\t         iaload\n\t\t\t\t         istore c0\n\t\t\t\t     X:\n\t\t\t\t         aload c\n\t\t\t\t         iconst_1\n\t\t\t\t         iaload\n\t\t\t\t         istore c1\n\t\t\t\t     Y:\n\t\t\t\t         aload c\n\t\t\t\t         iconst_2\n\t\t\t\t         iaload\n\t\t\t\t         istore c2\n\t\t\t\t     Z:\n\t\t\t\t         aload c\n\t\t\t\t         iconst_3\n\t\t\t\t         iaload\n\t\t\t\t         istore c3\n\t\t\t\t     AA:\n\t\t\t\t         bipush 11\n\t\t\t\t         newarray int\n\t\t\t\t         dup\n\t\t\t\t         iconst_0\n\t\t\t\t         iload a0\n\t\t\t\t         iastore\n\t\t\t\t         dup\n\t\t\t\t         iconst_1\n\t\t\t\t         iload a1\n\t\t\t\t         iastore\n\t\t\t\t         dup\n\t\t\t\t         iconst_2\n\t\t\t\t         iload a2\n\t\t\t\t         iastore\n\t\t\t\t         dup\n\t\t\t\t         iconst_3\n\t\t\t\t         iload a3\n\t\t\t\t         iastore\n\t\t\t\t         dup\n\t\t\t\t         iconst_4\n\t\t\t\t         iload a4\n\t\t\t\t         iastore\n\t\t\t\t         dup\n\t\t\t\t         iconst_5\n\t\t\t\t         iload b0\n\t\t\t\t         iastore\n\t\t\t\t         dup\n\t\t\t\t         bipush 6\n\t\t\t\t         iload b1\n\t\t\t\t         iastore\n\t\t\t\t         dup\n\t\t\t\t         bipush 7\n\t\t\t\t         iload c0\n\t\t\t\t         iastore\n\t\t\t\t         dup\n\t\t\t\t         bipush 8\n\t\t\t\t         iload c1\n\t\t\t\t         iastore\n\t\t\t\t         dup\n\t\t\t\t         bipush 9\n\t\t\t\t         iload c2\n\t\t\t\t         iastore\n\t\t\t\t         dup\n\t\t\t\t         bipush 10\n\t\t\t\t         iload c3\n\t\t\t\t         iastore\n\t\t\t\t         astore d\n\t\t\t\t     AB:\n\t\t\t\t         aload d\n\t\t\t\t         invokestatic java/util/Arrays.hashCode ([I)I\n\t\t\t\t         istore h\n\t\t\t\t     AC:\n\t\t\t\t         iload h\n\t\t\t\t     AD:\n\t\t\t\t         // -1239995153\n\t\t\t\t         ireturn\n\t\t\t\t     AE:\n\t\t\t\t     }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterRepeatedAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class, VariableFoldingTransformer.class), dis -> {\n\t\t\t// Final result\n\t\t\tassertTrue(dis.contains(\"ldc -1239995153\"), \"Failed to fold sequence\");\n\t\t\t// All we should have is just the final value and a return\n\t\t\tassertFalse(dis.contains(\"iload\"), \"Failed to fold sequence\");\n\t\t\tassertFalse(dis.contains(\"istore\"), \"Failed to fold sequence\");\n\t\t\tassertFalse(dis.contains(\"iastore\"), \"Failed to fold sequence\");\n\t\t\tassertFalse(dis.contains(\"iaload\"), \"Failed to fold sequence\");\n\t\t\tassertFalse(dis.contains(\"invoke\"), \"Failed to fold sequence\");\n\t\t\tassertFalse(dis.contains(\"dup\"), \"Failed to fold sequence\");\n\t\t\tassertFalse(dis.contains(\"pop\"), \"Failed to fold sequence\");\n\t\t});\n\t}\n\n\t/**\n\t * Unlike {@link #constFoldingAndVariableFoldingWillEventuallyCleanUpNoLongerUsedArrays()} the array built here\n\t * is passed to some other method, so we need to keep the whole thing around.\n\t */\n\t@Test\n\tvoid constFoldingAndVariableFoldingWillNotAggressivelyRemoveArrayConstructionsForArraysUsedElsewhere() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t     code: {\n\t\t\t\t     A:\n\t\t\t\t         iconst_2\n\t\t\t\t         newarray int\n\t\t\t\t         astore a\n\t\t\t\t     B:\n\t\t\t\t         aload a\n\t\t\t\t         iconst_0\n\t\t\t\t         iconst_5\n\t\t\t\t         iastore\n\t\t\t\t     C:\n\t\t\t\t         aload a\n\t\t\t\t         iconst_1\n\t\t\t\t         iconst_5\n\t\t\t\t         iastore\n\t\t\t\t     D:\n\t\t\t\t         // This should be folded to 10\n\t\t\t\t         aload a\n\t\t\t\t         dup\n\t\t\t\t         iconst_0\n\t\t\t\t         iaload\n\t\t\t\t         aload a\n\t\t\t\t         iconst_1\n\t\t\t\t         iaload\n\t\t\t\t         iadd\n\t\t\t\t     E:\n\t\t\t\t         // This takes '10' and 'int[] a'\n\t\t\t\t         // The array should not be deleted since it is used as a parameter here.\n\t\t\t\t         invokestatic Example.consume (I[I)I\n\t\t\t\t         ireturn\n\t\t\t\t     F:\n\t\t\t\t     }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterRepeatedAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class, VariableFoldingTransformer.class), dis -> {\n\t\t\t// The foldable part\n\t\t\tassertTrue(dis.contains(\"bipush 10\"), \"Failed to fold sequence\");\n\t\t\tassertFalse(dis.contains(\"iaload\"), \"Failed to fold sequence\");\n\n\t\t\t// Keep the array construction parts\n\t\t\tassertTrue(dis.contains(\"iconst_5\"), \"Should keep array value assignments\");\n\t\t\tassertTrue(dis.contains(\"iastore\"), \"Should keep array value assignments\");\n\t\t});\n\t}\n\n\t/**\n\t * This got addressed by not using {@link AsmInsnUtil#getSizeConsumed(AbstractInsnNode)} and\n\t * {@link AsmInsnUtil#getSizeProduced(AbstractInsnNode)} by default for computing foldable sequences\n\t * in {@link OpaqueConstantFoldingTransformer}. Instead, we do a simple 'this' and 'next' stack size\n\t * diff via {@link org.objectweb.asm.tree.analysis.Frame#getStackSize()}.\n\t */\n\t@Test\n\tvoid i2lConfusesConstantFoldingStackBalanceAndSkipsFoldableSequence() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()I {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        invokestatic Example.foo ()V\n\t\t\t\t        iconst_0\n\t\t\t\t        // Begin foldable\n\t\t\t\t        ldc -114812231\n\t\t\t\t        i2l\n\t\t\t\t        ldc -8456834448885618340L\n\t\t\t\t        lxor\n\t\t\t\t        // End foldable\n\t\t\t\t        invokestatic Example.bar (IJ)I\n\t\t\t\t        ireturn\n\t\t\t\t    B:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertFalse(dis.contains(\"ldc -114812231\"), \"Failed to fold sequence\");\n\t\t\tassertFalse(dis.contains(\"i2l\"), \"Failed to fold sequence\");\n\t\t\tassertFalse(dis.contains(\"ldc -8456834448885618340L\"), \"Failed to fold sequence\");\n\t\t\tassertFalse(dis.contains(\"lxor\"), \"Failed to fold sequence\");\n\t\t\tassertTrue(dis.contains(\"ldc 8456834448930567141L\"), \"Failed to fold sequence into expected value\");\n\t\t});\n\t}\n\n\t/**\n\t * This example shows how <i>\"Get the value from the next frame\"</i> can fail.\n\t * This was fixed by using a fallback computation when the next frame's value is unknown for a given\n\t * sequence of foldable instructions.\n\t */\n\t@Test\n\tvoid backwardsJumpConfusesConstantFoldingKnownStackReplacement() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        bipush 50\n\t\t\t\t        goto C\n\t\t\t\t    B:\n\t\t\t\t        // Begin foldable --> 30\n\t\t\t\t        bipush 15\n\t\t\t\t        bipush 15\n\t\t\t\t        iadd\n\t\t\t\t        // End foldable\n\t\t\t\t    C:\n\t\t\t\t        // Stack top is either 50 or 30 depending on where we came from\n\t\t\t\t        //\n\t\t\t\t        // Begin foldable --> 10\n\t\t\t\t        iconst_5\n\t\t\t\t        iconst_5\n\t\t\t\t        iadd\n\t\t\t\t        iadd\n\t\t\t\t        // End foldable\n\t\t\t\t        //\n\t\t\t\t        // This backwards jump to C creates a scenario where we revisit C.\n\t\t\t\t        // - A will jump to C with 50 on the stack.\n\t\t\t\t        // - B will naturally flow into C with 30 on the stack.\n\t\t\t\t        // - This jumps back to B so the control flow over C has two possible stack top values\n\t\t\t\t        lookupswitch {\n\t\t\t\t\t\t    55: B,\n\t\t\t\t\t\t    default: D\n\t\t\t\t\t\t}\n\t\t\t\t\tD:\n\t\t\t\t        return\n\t\t\t\t    E:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(OpaqueConstantFoldingTransformer.class), dis -> {\n\t\t\tassertFalse(dis.contains(\"iconst_5\"), \"Failed to fold easy sequence\"); // The easy case\n\t\t\tassertFalse(dis.contains(\"bipush 15\"), \"Failed to fold edge sequence\"); // The edge case\n\t\t\tassertTrue(dis.contains(\"bipush 10\"), \"Failed to fold easy-case sequence into expected value\");\n\t\t\tassertTrue(dis.contains(\"bipush 30\"), \"Failed to fold edge-case sequence into expected value\");\n\t\t});\n\t}\n\n\t/**\n\t * A snippet of DashO obfuscation. Includes a series of opaque jumps with some loop-backs in dead code.\n\t * There's also sequences that set up the stack before those jumps which also have variable state side effects.\n\t * These two issues have caused issues with opaque folding transformers in the past.\n\t */\n\t@Test\n\tvoid dashFlow() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method private static example ()Ljava/lang/String; {\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        ldc \"example-text-to-decrypt\"\n\t\t\t\t        bipush 15\n\t\t\t\t        goto P\n\t\t\t\t    B:\n\t\t\t\t        goto C\n\t\t\t\t        athrow\n\t\t\t\t        nop\n\t\t\t\t        nop\n\t\t\t\t        athrow\n\t\t\t\t        athrow\n\t\t\t\t    C:\n\t\t\t\t        iadd\n\t\t\t\t        idiv\n\t\t\t\t        iinc i0 14\n\t\t\t\t        ldc \"22\"\n\t\t\t\t        astore v1\n\t\t\t\t        goto AB\n\t\t\t\t    D:\n\t\t\t\t        goto E\n\t\t\t\t        athrow\n\t\t\t\t        nop\n\t\t\t\t        nop\n\t\t\t\t        athrow\n\t\t\t\t        athrow\n\t\t\t\t    E:\n\t\t\t\t        iconst_4\n\t\t\t\t        dup\n\t\t\t\t        iconst_0\n\t\t\t\t        istore i0\n\t\t\t\t        ldc \"0\"\n\t\t\t\t        astore v1\n\t\t\t\t        goto M\n\t\t\t\t    F:\n\t\t\t\t        goto G\n\t\t\t\t        athrow\n\t\t\t\t        nop\n\t\t\t\t        nop\n\t\t\t\t        athrow\n\t\t\t\t        athrow\n\t\t\t\t    G:\n\t\t\t\t        iinc i0 6\n\t\t\t\t        iconst_0\n\t\t\t\t        iconst_0\n\t\t\t\t        goto T\n\t\t\t\t    H:\n\t\t\t\t        goto I\n\t\t\t\t        athrow\n\t\t\t\t        nop\n\t\t\t\t        nop\n\t\t\t\t        athrow\n\t\t\t\t        athrow\n\t\t\t\t    I:\n\t\t\t\t        iinc i0 15\n\t\t\t\t        pop\n\t\t\t\t        iconst_1\n\t\t\t\t        goto R\n\t\t\t\t    J:\n\t\t\t\t        goto K\n\t\t\t\t        athrow\n\t\t\t\t        nop\n\t\t\t\t        nop\n\t\t\t\t        athrow\n\t\t\t\t        athrow\n\t\t\t\t    K:\n\t\t\t\t        iadd\n\t\t\t\t        iadd\n\t\t\t\t        iinc i0 8\n\t\t\t\t        ldc \"22\"\n\t\t\t\t        astore v1\n\t\t\t\t        goto S\n\t\t\t\t    L:\n\t\t\t\t        aload v1\n\t\t\t\t        invokestatic java/lang/Integer.parseInt (Ljava/lang/String;)I\n\t\t\t\t        tableswitch {\n\t\t\t\t            min: 0,\n\t\t\t\t            max: 1,\n\t\t\t\t            cases: { U },\n\t\t\t\t            default: H\n\t\t\t\t        }\n\t\t\t\t    M:\n\t\t\t\t        aload v1\n\t\t\t\t        invokestatic java/lang/Integer.parseInt (Ljava/lang/String;)I\n\t\t\t\t        tableswitch {\n\t\t\t\t            min: 0,\n\t\t\t\t            max: 1,\n\t\t\t\t            cases: { J },\n\t\t\t\t            default: AA\n\t\t\t\t        }\n\t\t\t\t    N:\n\t\t\t\t        goto O\n\t\t\t\t        athrow\n\t\t\t\t        nop\n\t\t\t\t        nop\n\t\t\t\t        athrow\n\t\t\t\t        athrow\n\t\t\t\t    O:\n\t\t\t\t        bipush 15\n\t\t\t\t        dup\n\t\t\t\t        iconst_0\n\t\t\t\t        istore i0\n\t\t\t\t        ldc \"0\"\n\t\t\t\t        astore v1\n\t\t\t\t        goto T\n\t\t\t\t    P:\n\t\t\t\t        goto Q\n\t\t\t\t        athrow\n\t\t\t\t        nop\n\t\t\t\t        nop\n\t\t\t\t        athrow\n\t\t\t\t        athrow\n\t\t\t\t    Q:\n\t\t\t\t        iconst_m1\n\t\t\t\t        istore i0\n\t\t\t\t        ldc \"0\"\n\t\t\t\t        iinc i0 1\n\t\t\t\t        astore v1\n\t\t\t\t        goto L\n\t\t\t\t    R:\n\t\t\t\t        iload i0\n\t\t\t\t        tableswitch {\n\t\t\t\t            min: 0,\n\t\t\t\t            max: 1,\n\t\t\t\t            cases: { W },\n\t\t\t\t            default: D\n\t\t\t\t        }\n\t\t\t\t    S:\n\t\t\t\t        iload i0\n\t\t\t\t        tableswitch {\n\t\t\t\t            min: 0,\n\t\t\t\t            max: 1,\n\t\t\t\t            cases: { F },\n\t\t\t\t            default: N\n\t\t\t\t        }\n\t\t\t\t    T:\n\t\t\t\t        aload v1\n\t\t\t\t        invokestatic java/lang/Integer.parseInt (Ljava/lang/String;)I\n\t\t\t\t        tableswitch {\n\t\t\t\t            min: 0,\n\t\t\t\t            max: 1,\n\t\t\t\t            cases: { B },\n\t\t\t\t            default: Y\n\t\t\t\t        }\n\t\t\t\t    U:\n\t\t\t\t        goto V\n\t\t\t\t        athrow\n\t\t\t\t        nop\n\t\t\t\t        nop\n\t\t\t\t        athrow\n\t\t\t\t        athrow\n\t\t\t\t    V:\n\t\t\t\t        bipush 11\n\t\t\t\t        imul\n\t\t\t\t        iinc i0 5\n\t\t\t\t        ldc \"22\"\n\t\t\t\t        astore v1\n\t\t\t\t        goto R\n\t\t\t\t    W:\n\t\t\t\t        goto X\n\t\t\t\t        athrow\n\t\t\t\t        nop\n\t\t\t\t        nop\n\t\t\t\t        athrow\n\t\t\t\t        athrow\n\t\t\t\t    X:\n\t\t\t\t        iinc i0 9\n\t\t\t\t        iconst_1\n\t\t\t\t        iconst_1\n\t\t\t\t        goto M\n\t\t\t\t    Y:\n\t\t\t\t        goto Z\n\t\t\t\t        athrow\n\t\t\t\t        nop\n\t\t\t\t        nop\n\t\t\t\t        athrow\n\t\t\t\t        athrow\n\t\t\t\t    Z:\n\t\t\t\t        iinc i0 13\n\t\t\t\t        pop\n\t\t\t\t        pop\n\t\t\t\t        goto AB\n\t\t\t\t    AA:\n\t\t\t\t        iinc i0 8\n\t\t\t\t        pop\n\t\t\t\t        pop\n\t\t\t\t        goto S\n\t\t\t\t    AB:\n\t\t\t\t        invokestatic Example.decrypt (Ljava/lang/String;I)Ljava/lang/String;\n\t\t\t\t        nop\n\t\t\t\t        areturn\n\t\t\t\t    AC:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterRepeatedAssembly(asm, List.of(\n\t\t\t\tGotoInliningTransformer.class,\n\t\t\t\tVariableFoldingTransformer.class,\n\t\t\t\tOpaqueConstantFoldingTransformer.class,\n\t\t\t\tOpaquePredicateFoldingTransformer.class\n\t\t), dis -> {\n\t\t\t// Flow should be removed\n\t\t\tassertFalse(dis.contains(\"goto\"));\n\t\t\tassertFalse(dis.contains(\"switch\"));\n\t\t\tassertFalse(dis.contains(\"if\"));\n\n\t\t\t// Variable reads/writes should be removed\n\t\t\tassertFalse(dis.contains(\"iinc\"));\n\t\t\tassertFalse(dis.contains(\"store\"));\n\t\t\tassertFalse(dis.contains(\"load\"));\n\n\t\t\t// It should be just:\n\t\t\t//   return decrypt(const-text, const-key);\n\t\t\tassertFalse(dis.contains(\"C:\"), \"Method should be simplified enough to fit between two labels [A-B]\");\n\t\t});\n\t}\n\n\t/**\n\t * A mocked up sample that has been hand-crafted to mimic DashO.\n\t * The block of code in 'A' before the switch modifies state that is required later for proper folding.\n\t * This test ensures transformers don't overly optimize earlier code that can prevent later sections\n\t * from being properly folded.\n\t */\n\t@Test\n\tvoid mockDashFlow() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method private static example (I)Ljava/lang/String; {\n\t\t\t\t\tparameters: { foo },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        istore i\n\t\t\t\t        ldc \"0\"\n\t\t\t\t        iconst_1\n\t\t\t\t        istore foo\n\t\t\t\t        iinc i 1\n\t\t\t\t        astore s\n\t\t\t\t        aload s\n\t\t\t\t        invokestatic java/lang/Integer.parseInt (Ljava/lang/String;)I\n\t\t\t\t        tableswitch {\n\t\t\t\t            min: 0,\n\t\t\t\t            max: 1,\n\t\t\t\t            cases: { B },\n\t\t\t\t            default: X\n\t\t\t\t        }\n\t\t\t\t    B:\n\t\t\t\t        iload foo\n\t\t\t\t        ifeq X\n\t\t\t\t    C:\n\t\t\t\t        iload i\n\t\t\t\t        tableswitch {\n\t\t\t\t            min: 0,\n\t\t\t\t            max: 1,\n\t\t\t\t            cases: { X },\n\t\t\t\t            default: D\n\t\t\t\t        }\n\t\t\t\t    D:\n\t\t\t\t        ldc \"win\"\n\t\t\t\t        areturn\n\t\t\t\t    X:\n\t\t\t\t        iinc foo 1\n\t\t\t\t        iload foo\n\t\t\t\t        ifne B\n\t\t\t\t        ldc \"fail\"\n\t\t\t\t        areturn\n\t\t\t\t    Z:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterRepeatedAssembly(asm, List.of(\n\t\t\t\tGotoInliningTransformer.class,\n\t\t\t\tVariableFoldingTransformer.class,\n\t\t\t\tOpaqueConstantFoldingTransformer.class,\n\t\t\t\tOpaquePredicateFoldingTransformer.class\n\t\t), dis -> {\n\t\t\tassertFalse(dis.contains(\"switch\"));\n\t\t\tassertFalse(dis.contains(\"iinc\"));\n\t\t});\n\t}\n\n\t/**\n\t * Ensures the const-folder doesn't take too long to run.\n\t */\n\t@Test\n\t@Timeout(value = 1)\n\tvoid constFolderDoesNotCatestrophicallyBacktrack() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (I)I {\n\t\t\t\t\tparameters: { foo },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iload foo\n\t\t\t\t\\0\n\t\t\t\t        ireturn\n\t\t\t\t    E:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\".replace(\"\\0\", \"        iconst_1\\n        iadd\\n\".repeat(20_000));\n\t\tvalidateNoTransformation(asm, List.of(OpaqueConstantFoldingTransformer.class));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/deobfuscation/StaticValueInliningTest.java",
    "content": "package software.coley.recaf.services.deobfuscation;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.services.deobfuscation.transform.generic.StaticValueCollectionTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.StaticValueInliningTransformer;\n\nimport java.util.List;\n\n/**\n * Tests for {@link StaticValueCollectionTransformer} / {@link StaticValueInliningTransformer}.\n */\npublic class StaticValueInliningTest extends BaseDeobfuscationTest{\n\t@Test\n\tvoid effectiveFinalAssignmentInClinit() {\n\t\tString asm = \"\"\"\n\t\t\t\t\t.field private static foo I\n\t\t\t\t\t    \n\t\t\t\t\t.method public static example ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t\t    getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t\t\t    getstatic Example.foo I\n\t\t\t\t\t\t    invokevirtual java/io/PrintStream.println (I)V\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t.method static <clinit> ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t        iconst_5\n\t\t\t\t\t        putstatic Example.foo I\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\tvalidateInlining(asm, \"println(foo);\", \"println(5);\");\n\n\t\t// With strings\n\t\tasm = \"\"\"\n\t\t\t\t\t.field private static foo Ljava/lang/String;\n\t\t\t\t\t    \n\t\t\t\t\t.method public static example ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t\t    getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t\t\t    getstatic Example.foo Ljava/lang/String;\n\t\t\t\t\t\t    invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t.method static <clinit> ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t        ldc \"Hello\"\n\t\t\t\t\t        putstatic Example.foo Ljava/lang/String;\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\tvalidateInlining(asm, \"println(foo);\", \"println(\\\"Hello\\\");\");\n\t}\n\n\t@Test\n\tvoid effectiveFinalAssignmentDisqualified() {\n\t\tString asm = \"\"\"\n\t\t\t\t\t.field private static foo I\n\t\t\t\t\t    \n\t\t\t\t\t.method public static example ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t\t    getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t\t\t    getstatic Example.foo I\n\t\t\t\t\t\t    invokevirtual java/io/PrintStream.println (I)V\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t.method static disqualification ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t        iconst_1\n\t\t\t\t\t        putstatic Example.foo I\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t.method static <clinit> ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t        iconst_5\n\t\t\t\t\t        putstatic Example.foo I\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\tvalidateNoInlining(asm);\n\t}\n\n\t@Test\n\tvoid constAssignmentInClinit() {\n\t\tString asm = \"\"\"\n\t\t\t\t\t.field private static final foo I\n\t\t\t\t\t    \n\t\t\t\t\t.method public static example ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t\t    getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t\t\t    getstatic Example.foo I\n\t\t\t\t\t\t    invokevirtual java/io/PrintStream.println (I)V\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t.method static <clinit> ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t        iconst_5\n\t\t\t\t\t        putstatic Example.foo I\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\tvalidateInlining(asm, \"println(foo);\", \"println(5);\");\n\t}\n\n\t@Test\n\tvoid constAssignmentInField() {\n\t\tString asm = \"\"\"\n\t\t\t\t\t.field private static final foo I { value: 5 }\n\t\t\t\t\t    \n\t\t\t\t\t.method public static example ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t\t    getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t\t\t    getstatic Example.foo I\n\t\t\t\t\t\t    invokevirtual java/io/PrintStream.println (I)V\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\tvalidateInlining(asm, \"println(foo);\", \"println(5);\");\n\t}\n\n\t@Test\n\tvoid simpleMathComputedAssignment() {\n\t\tString asm = \"\"\"\n\t\t\t\t\t.field private static final foo I\n\t\t\t\t\t    \n\t\t\t\t\t.method public static example ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t\t    getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t\t\t    getstatic Example.foo I\n\t\t\t\t\t\t    invokevirtual java/io/PrintStream.println (I)V\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t.method static <clinit> ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t        // 50 * 5 = 250\n\t\t\t\t\t        bipush 50\n\t\t\t\t\t        bipush 5\n\t\t\t\t\t        imul\n\t\t\t\t\t        // 250 / 10 = 25\n\t\t\t\t\t        bipush 10\n\t\t\t\t\t        idiv\n\t\t\t\t\t        putstatic Example.foo I\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\tvalidateInlining(asm, \"println(foo);\", \"println(25);\");\n\t}\n\n\t@Test\n\t@Disabled(\"Requires array content tracking & tracking value into the creation of a 'new String(T)'\")\n\tvoid stringBase64Decode() {\n\t\tString asm = \"\"\"\n\t\t\t\t\t.field private static final foo Ljava/lang/String;\n\t\t\t\t\t    \n\t\t\t\t\t.method public static example ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t\t    getstatic java/lang/System.out Ljava/io/PrintStream;\n\t\t\t\t\t\t    getstatic Example.foo Ljava/lang/String;\n\t\t\t\t\t\t    invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t.method static <clinit> ()V {\n\t\t\t\t\t    code: {\n\t\t\t\t\t    A:\n\t\t\t\t\t        new java/lang/String\n\t\t\t\t\t        dup\n\t\t\t\t\t        ldc \"SGVsbG8=\"\n\t\t\t\t\t        invokestatic java/util/Base64.getDecoder ()Ljava/util/Base64$Decoder;\n\t\t\t\t\t        invokevirtual java/util/Base64$Decoder.decode (Ljava/lang/String;)[B\n\t\t\t\t\t        invokespecial java/lang/String.<init> ([B)V\n\t\t\t\t\t        putstatic Example.foo Ljava/lang/String;\n\t\t\t\t\t        return\n\t\t\t\t\t    B:\n\t\t\t\t\t    }\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\tvalidateInlining(asm, \"println(foo);\", \"println(\\\"Hello\\\");\");\n\t}\n\n\tprivate void validateNoInlining(@Nonnull String assembly) {\n\t\tvalidateNoTransformation(assembly, List.of(StaticValueInliningTransformer.class));\n\t}\n\n\n\tprivate void validateInlining(@Nonnull String assembly, @Nonnull String expectedBefore, @Nullable String expectedAfter) {\n\t\tvalidateBeforeAfterDecompile(assembly, List.of(StaticValueInliningTransformer.class), expectedBefore, expectedAfter);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/deobfuscation/TryCatchDeobfuscationTest.java",
    "content": "package software.coley.recaf.services.deobfuscation;\n\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.services.deobfuscation.transform.generic.DuplicateCatchMergingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.RedundantTryCatchRemovingTransformer;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests {@link DuplicateCatchMergingTransformer} / {@link RedundantTryCatchRemovingTransformer}.\n */\npublic class TryCatchDeobfuscationTest extends BaseDeobfuscationTest {\n\t/** Remove duplicate code among handler blocks where possible. */\n\t@Test\n\tvoid duplicateCatchHandlers() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/Throwable0; },\n\t\t\t\t       {  A,  B,  D, Ljava/lang/Throwable1; },\n\t\t\t\t       {  A,  B,  E, Ljava/lang/Throwable2; },\n\t\t\t\t       {  A,  B,  F, Ljava/lang/Throwable3; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        invokestatic Example.throwing ()V\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        invokestatic  com/example/MyApp.logFailure ()V\n\t\t\t\t        goto END\n\t\t\t\t    D:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        invokestatic  com/example/MyApp.logFailure ()V\n\t\t\t\t        goto END\n\t\t\t\t    E:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        invokestatic  com/example/MyApp.logFailure ()V\n\t\t\t\t        goto END\n\t\t\t\t    F:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        invokestatic  com/example/MyApp.logFailure ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(DuplicateCatchMergingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"printStackTrace\", dis), \"Catch block contents were not merged\");\n\t\t\tassertEquals(5, StringUtil.count(\"goto\", dis), \"Expected 5 goto instructions after merging\");\n\t\t});\n\t}\n\n\t/** Remove a try-catch that will be impossible to occur. */\n\t@Test\n\tvoid redundantTryCatch() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/Throwable; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aconst_null\n\t\t\t\t        pop\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        astore ex\n\t\t\t\t        aload ex\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(RedundantTryCatchRemovingTransformer.class), dis -> {\n\t\t\tassertEquals(0, StringUtil.count(\"exceptions:\", dis), \"Should have removed exception ranges\");\n\t\t\tassertEquals(0, StringUtil.count(\"printStackTrace\", dis), \"Should have removed catch block contents\");\n\t\t\tassertEquals(0, StringUtil.count(\"astore\", dis), \"Should have removed catch block contents\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid mergeAdjacentRangesWithSameHandler() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t        { A, B, D, * },\n\t\t\t\t        { B, C, D, * },\n\t\t\t\t        { C, D, D, * },\n\t\t\t\t        { E, F, D, * },\n\t\t\t\t        { F, G, D, * },\n\t\t\t\t        { G, END, D, * },\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        invokestatic Foo.bar ()V\n\t\t\t\t    B:\n\t\t\t\t        invokestatic Foo.bar ()V\n\t\t\t\t    C:\n\t\t\t\t        invokestatic Foo.bar ()V\n\t\t\t\t        goto E\n\t\t\t\t    D:\n\t\t\t\t        pop\n\t\t\t\t        goto END\n\t\t\t\t    E:\n\t\t\t\t        invokestatic Foo.bar ()V\n\t\t\t\t    F:\n\t\t\t\t        invokestatic Foo.bar ()V\n\t\t\t\t    G:\n\t\t\t\t        invokestatic Foo.bar ()V\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(RedundantTryCatchRemovingTransformer.class), dis -> {\n\t\t\t// Regex match to ensure there are only two ranges now instead of 6, and that they are properly merged together.\n\t\t\tassertTrue(RegexUtil.matchesAny(\"exceptions: \\\\{\\\\s+\\\\{\\\\s*\\\\w,\\\\s*\\\\w,\\\\s*\\\\w,\\\\s*\\\\*\\\\s*},\\\\s+\\\\{\\\\s*\\\\w,\\\\s*\\\\w,\\\\s*\\\\w,\\\\s*\\\\*\\\\s*}\\\\s+}\", dis));\n\n\t\t\t// Regex match to ensure the invokestatic instructions are grouped together in single blocks now.\n\t\t\tassertTrue(RegexUtil.matchesAny(\"(invokestatic Foo\\\\.bar \\\\(\\\\)V\\\\s+){3}[\\\\s\\\\S]+(invokestatic Foo\\\\.bar \\\\(\\\\)V\\\\s+){3}\", dis));\n\t\t});\n\t}\n\n\t@Test\n\tvoid removeCatchBlocksNotUsableAtRuntime() {\n\t\t// The JVM will use the first of \"duplicate\" blocks like this.\n\t\t// More details about this behavior can be found in the redundant catch removing transformer.\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (I[B)V {\n\t\t\t\t\tparameters: { index, array },\n\t\t\t\t\texceptions: {\n\t\t\t\t        { A, B, C, * },\n\t\t\t\t        { A, B, D, * },\n\t\t\t\t        { A, B, C, Ljava/lang/ArrayIndexOutOfBoundsException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aload array\n\t\t\t\t        iload index\n\t\t\t\t        baload\n\t\t\t\t        pop\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        pop\n\t\t\t\t        goto END\n\t\t\t\t    D:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(RedundantTryCatchRemovingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"{ A, B, C, * }\", dis), \"Expected to keep first try-catch\");\n\t\t\tassertEquals(0, StringUtil.count(\"{ A, B, D, * }\", dis), \"Expected to drop second try-catch\");\n\t\t\tassertEquals(0, StringUtil.count(\"ArrayIndexOutOfBoundsException\", dis), \"Expected to drop third try-catch\");\n\t\t\tassertEquals(0, StringUtil.count(\"printStackTrace\", dis), \"Expected to prune dead code of removed D handler\");\n\t\t});\n\t}\n\n\t@Test\n\tvoid removeSafeArrayStoreException() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t        { A, B, C, * }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_5\n\t\t\t\t        newarray char\n\t\t\t\t\t\tastore chars\n\t\t\t\t        aload chars\n\t\t\t\t        iconst_0\n\t\t\t\t        bipush 65\n\t\t\t\t        castore\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        pop\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\t// Any exception in the code above will never be thrown at runtime, so our transformer\n\t\t// should remove the exception block and cleanup any dead code as a result.\n\t\tfor (String ex : List.of(\"*\",\n\t\t\t\t\"Ljava/lang/ArrayStoreException;\",\n\t\t\t\t\"Ljava/lang/ArrayIndexOutOfBoundsException;\",\n\t\t\t\t\"Ljava/lang/NullPointerException;\")) {\n\t\t\tString asmVariation = asm.replace(\"*\", ex);\n\t\t\tvalidateAfterAssembly(asmVariation, List.of(RedundantTryCatchRemovingTransformer.class), dis -> {\n\t\t\t\tassertEquals(0, StringUtil.count(\"exceptions:\", dis), \"Expected to remove exception block\");\n\t\t\t\tassertEquals(0, StringUtil.count(\"pop\", dis), \"Expected to remove catch block contents\");\n\t\t\t\tassertEquals(1, StringUtil.count(\"castore\", dis), \"Expected to keep array store\");\n\t\t\t});\n\t\t}\n\n\t\t// If the array index would be out of bounds then no transformation should occur.\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t        { A, B, C, * }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_1\n\t\t\t\t        newarray char\n\t\t\t\t\t\tastore chars\n\t\t\t\t        aload chars\n\t\t\t\t        iconst_5\n\t\t\t\t        bipush 65\n\t\t\t\t        castore\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        pop\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\n\t\t// If the array is null then no transformation should occur since it would throw a NPE.\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t        { A, B, C, * }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aconst_null\n\t\t\t\t\t\tastore chars\n\t\t\t\t        aload chars\n\t\t\t\t        iconst_0\n\t\t\t\t        bipush 65\n\t\t\t\t        castore\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        pop\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\t}\n\n\t@Test\n\tvoid removeSameTypeCheckcastCastException() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/ClassCastException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        new Foo\n\t\t\t\t        checkcast Foo\n\t\t\t\t        pop\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        pop\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(RedundantTryCatchRemovingTransformer.class), dis ->{\n\t\t\tassertEquals(0, StringUtil.count(\"exceptions:\", dis), \"Expected to remove exceptions\");\n\n\t\t\t// Keep pop after cast, but remove at handler block\n\t\t\tassertEquals(1, StringUtil.count(\"pop\", dis), \"Expected to remove catch block pop contents\");\n\t\t});\n\t}\n\n\t@Test\n\t@Disabled\n\tvoid convertAlwaysThrowIntoDirectControlFlow() {\n\t\t// TODO: Implement redundant transformer pass for this\n\t\t//  - This is rather complex because code may utilize the exception object in the catch block, so we can't just convert the throw into a goto.\n\t\t//  - But even if we did convert it to a goto, what would we do with the 'prepop' method call before the exception gets popped?\n\t\t//    - We cant just remove the method due to side effects\n\t\t//    - We want to get rid of the 'pop' here since the exception is unused\n\t\t//  - Maybe we only allow this extra pass if the exception is not used in the catch block?\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/Exception; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    B:\n\t\t\t\t        invokestatic Foo.skipped ()V\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokestatic Foo.prepop ()V\n\t\t\t\t        pop\n\t\t\t\t        invokestatic Foo.postpop ()V\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(RedundantTryCatchRemovingTransformer.class), dis -> {});\n\t}\n\n\t@Test\n\tvoid redundantCatchOfTypeNeverThrownInWorkspace() {\n\t\t// If we observe 'BogusException' defined in the workspace and know it is never actually constructed\n\t\t// then any exception block with it is ALSO never going to be handled. These can be removed.\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    exceptions: {\n\t\t\t\t        { A, B, C, L\\0; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t\tA:\n\t\t\t\t\t    invokestatic Foo.foo ()V\n\t\t\t\t\tB:\n\t\t\t\t\t    return\n\t\t\t\t\tC:\n\t\t\t\t\t    pop\n\t\t\t\t\t    return\n\t\t\t\t\tD:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\".replace(\"\\0\", EXCEPTION_NAME);\n\t\tvalidateAfterAssembly(asm, List.of(RedundantTryCatchRemovingTransformer.class), dis -> {\n\t\t\tassertFalse(dis.contains(\"exceptions:\"), \"Expected removal of exceptions\");\n\t\t\tassertFalse(dis.contains(\"pop\"), \"Expected removal of handler block\");\n\t\t\tassertEquals(1, StringUtil.count(\"return\", dis), \"Expected dead code to collapse into single return\");\n\t\t\tassertTrue(dis.contains(\"Foo.foo\"), \"Expected to keep method call\");\n\t\t});\n\n\t\t// However, if we see an exception type that is not explicitly defined in our workspace's primary resource,\n\t\t// such as a library, then we cannot safely assume it cannot be thrown without expensive call-graph walking.\n\t\t// So we won't bother with these cases.\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    exceptions: {\n\t\t\t\t        { A, B, C, Lcom/example/Foo; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t\tA:\n\t\t\t\t\t    invokestatic Foo.foo ()V\n\t\t\t\t\tB:\n\t\t\t\t\t\treturn\n\t\t\t\t\tC:\n\t\t\t\t\t    pop\n\t\t\t\t\t    return\n\t\t\t\t\tD:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\n\t\t// If we have BOTH exceptions then we should remove the 'BogusException' entry but keep the\n\t\t// handler block as-is since it is still utilized by the third party 'Foo' exception.\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t    exceptions: {\n\t\t\t\t        { A, B, C, Lcom/example/Foo; },\n\t\t\t\t        { A, B, C, L\\0; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t\tA:\n\t\t\t\t\t    invokestatic Foo.foo ()V\n\t\t\t\t\tB:\n\t\t\t\t\t\treturn\n\t\t\t\t\tC:\n\t\t\t\t\t    pop\n\t\t\t\t\t    return\n\t\t\t\t\tD:\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\".replace(\"\\0\", EXCEPTION_NAME);\n\t\tvalidateAfterAssembly(asm, List.of(RedundantTryCatchRemovingTransformer.class), dis -> {\n\t\t\tassertTrue(dis.contains(\"{ A, B, C, Lcom/example/Foo; }\"), \"Expected to keep 'Foo' exception\");\n\t\t\tassertFalse(dis.contains(EXCEPTION_NAME), \"Expected to remove '\" + EXCEPTION_NAME + \"' exception\");\n\t\t\tassertTrue(dis.contains(\"pop\"), \"Expected keep handler block for 'Foo' use\");\n\t\t\tassertEquals(2, StringUtil.count(\"return\", dis), \"Expected to keep both returns\");\n\t\t\tassertTrue(dis.contains(\"Foo.foo\"), \"Expected to keep method call\");\n\t\t});\n\t}\n\n\t/** Ensures {@link RedundantTryCatchRemovingTransformer} keeps at least one try-catch if multiple identical ones are found. */\n\t@Test\n\tvoid keepOneTryCatchIfIdenticalCopyFound() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/Exception; },\n\t\t\t\t       {  A,  B,  C, Ljava/lang/Exception; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        iconst_0\n\t\t\t\t        idiv\n\t\t\t\t        pop\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        astore ex\n\t\t\t\t        aload ex\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(RedundantTryCatchRemovingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"Ljava/lang/Exception;\", dis), \"Should have one exception block\");\n\t\t});\n\t}\n\n\t/** Ensures {@link RedundantTryCatchRemovingTransformer} is not too aggressive. */\n\t@Test\n\tvoid oneRedundantOneRelevantTryCatch() {\n\t\t// The ClassNotFoundException is not relevant and should be removed\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/ClassNotFoundException; },\n\t\t\t\t       {  A,  B,  C, Ljava/lang/ArithmeticException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        iconst_0\n\t\t\t\t        idiv\n\t\t\t\t        pop\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        astore ex\n\t\t\t\t        aload ex\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(RedundantTryCatchRemovingTransformer.class), dis -> {\n\t\t\tassertEquals(1, StringUtil.count(\"Ljava/lang/ArithmeticException;\", dis), \"Should not have removed ArithmeticException\");\n\t\t\tassertEquals(0, StringUtil.count(\"Ljava/lang/ClassNotFoundException;\", dis), \"Should have removed ClassNotFoundException\");\n\t\t});\n\t}\n\n\t/** Ensures {@link RedundantTryCatchRemovingTransformer} is not too aggressive. */\n\t@Test\n\tvoid keepPotentialThrowingMethodTryCatch() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/Throwable; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        invokestatic Example.throwing ()V\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\t}\n\n\t/** Ensures {@link RedundantTryCatchRemovingTransformer} is not too aggressive. */\n\t@Test\n\tvoid keepThrowingNpe() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/NullPointerException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        aconst_null\n\t\t\t\t        athrow\n\t\t\t\t    B:\n\t\t\t\t        // Dead code space, block 'A' always throws so no need to put a jump to end here.\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\t}\n\n\t@Test\n\tvoid keepThrowingCheckcast() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/ClassCastException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        // Cannot assume this cast will succeed since we don't know type hierarchy of 'Foo' and 'Bar'\n\t\t\t\t        new Foo\n\t\t\t\t        // Normally you would call the constructor here\n\t\t\t\t        // but we skip this to limit the test case to just the 'checkcast' instruction which is what we care about.\n\t\t\t\t        checkcast Bar\n\t\t\t\t        pop\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        pop\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\t}\n\n\t/** Ensures {@link RedundantTryCatchRemovingTransformer} is not too aggressive. */\n\t@Test\n\tvoid keepDivideByZeroExceptions() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/ArithmeticException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        iconst_0\n\t\t\t\t        idiv\n\t\t\t\t        pop\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/ArithmeticException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        iconst_0\n\t\t\t\t        iconst_0\n\t\t\t\t        irem\n\t\t\t\t        pop\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/ArithmeticException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        lconst_0\n\t\t\t\t        lconst_0\n\t\t\t\t        ldiv\n\t\t\t\t        pop2\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/ArithmeticException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        lconst_0\n\t\t\t\t        lconst_0\n\t\t\t\t        lrem\n\t\t\t\t        pop2\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\t}\n\n\t/**\n\t * For {@link #keepDivideByZeroExceptions()} we want to keep division by zero for int/long since it throws an exception\n\t * but for float/double it does not throw an exception and instead produces NaN or Infinity.\n\t */\n\t@Test\n\tvoid dropFloatingDivideByZero() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/ArithmeticException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        fconst_0\n\t\t\t\t        fconst_0\n\t\t\t\t        fdiv\n\t\t\t\t        pop\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(RedundantTryCatchRemovingTransformer.class), dis -> {\n\t\t\tassertFalse(dis.contains(\"exceptions:\"), \"Expected to remove exception block\");\n\t\t\tassertFalse(dis.contains(\"printStackTrace\"), \"Expected to remove catch block contents\");\n\t\t});\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/ArithmeticException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        fconst_0\n\t\t\t\t        fconst_0\n\t\t\t\t        frem\n\t\t\t\t        pop\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(RedundantTryCatchRemovingTransformer.class), dis -> {\n\t\t\tassertFalse(dis.contains(\"exceptions:\"), \"Expected to remove exception block\");\n\t\t\tassertFalse(dis.contains(\"printStackTrace\"), \"Expected to remove catch block contents\");\n\t\t});\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/ArithmeticException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        dconst_0\n\t\t\t\t        dconst_0\n\t\t\t\t        ddiv\n\t\t\t\t        pop2\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(RedundantTryCatchRemovingTransformer.class), dis -> {\n\t\t\tassertFalse(dis.contains(\"exceptions:\"), \"Expected to remove exception block\");\n\t\t\tassertFalse(dis.contains(\"printStackTrace\"), \"Expected to remove catch block contents\");\n\t\t});\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/ArithmeticException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        dconst_0\n\t\t\t\t        dconst_0\n\t\t\t\t        drem\n\t\t\t\t        pop2\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateAfterAssembly(asm, List.of(RedundantTryCatchRemovingTransformer.class), dis -> {\n\t\t\tassertFalse(dis.contains(\"exceptions:\"), \"Expected to remove exception block\");\n\t\t\tassertFalse(dis.contains(\"printStackTrace\"), \"Expected to remove catch block contents\");\n\t\t});\n\t}\n\n\t/** Ensures {@link RedundantTryCatchRemovingTransformer} is not too aggressive. */\n\t@Test\n\tvoid keepObviousNpeOnFieldAccess() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/NullPointerException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        // field owner context is null\n\t\t\t\t        aconst_null\n\t\t\t\t        iconst_0\n\t\t\t\t        putfield Owner.intField I\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example ()V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/NullPointerException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        // field owner context is null\n\t\t\t\t        aconst_null\n\t\t\t\t        getfield Owner.intField I\n\t\t\t\t        pop\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\t}\n\n\t/** Ensures {@link RedundantTryCatchRemovingTransformer} is not too aggressive. */\n\t@Test\n\tvoid keepAmbiguousNpeOnFieldAccess() {\n\t\tString asm = \"\"\"\n\t\t\t\t.method public static example (LOwner;)V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/NullPointerException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        // field owner context is a parameter, and thus can be null or not-null\n\t\t\t\t        aload 0\n\t\t\t\t        iconst_0\n\t\t\t\t        putfield Owner.intField I\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\t\tasm = \"\"\"\n\t\t\t\t.method public static example (LOwner;)V {\n\t\t\t\t\texceptions: {\n\t\t\t\t       {  A,  B,  C, Ljava/lang/NullPointerException; }\n\t\t\t\t    },\n\t\t\t\t    code: {\n\t\t\t\t    A:\n\t\t\t\t        // field owner context is a parameter, and thus can be null or not-null\n\t\t\t\t        aload 0\n\t\t\t\t        getfield Owner.intField I\n\t\t\t\t        pop\n\t\t\t\t    B:\n\t\t\t\t        goto END\n\t\t\t\t    C:\n\t\t\t\t        invokevirtual java/lang/Throwable.printStackTrace ()V\n\t\t\t\t        goto END\n\t\t\t\t    END:\n\t\t\t\t        return\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tvalidateNoTransformation(asm, List.of(RedundantTryCatchRemovingTransformer.class));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/inheritance/InheritanceAndRenamingTest.java",
    "content": "package software.coley.recaf.services.inheritance;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.ClassWriter;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.MappingApplier;\nimport software.coley.recaf.services.mapping.MappingApplierService;\nimport software.coley.recaf.services.mapping.MappingListeners;\nimport software.coley.recaf.services.mapping.MappingResults;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle;\n\nimport java.util.Set;\nimport java.util.stream.IntStream;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.objectweb.asm.Opcodes.ACC_INTERFACE;\nimport static org.objectweb.asm.Opcodes.V1_8;\n\n/**\n * Tests for {@link InheritanceGraph} and interactions with {@link MappingApplier}.\n */\nclass InheritanceAndRenamingTest extends TestBase {\n\tprivate static final int CLASS_COUNT = 10;\n\tstatic MappingApplierService mappingApplierService;\n\tstatic InheritanceGraphService inheritanceGraphService;\n\tstatic MappingListeners mappingListeners;\n\n\t@BeforeAll\n\tstatic void setup() {\n\t\tinheritanceGraphService = recaf.get(InheritanceGraphService.class);\n\t\tmappingApplierService = recaf.get(MappingApplierService.class);\n\t\tmappingListeners = recaf.get(MappingListeners.class);\n\t}\n\n\t@Test\n\tvoid verifyLinearInterfaces() {\n\t\tJvmClassInfo[] generatedClasses = IntStream.rangeClosed(1, CLASS_COUNT).mapToObj(i -> {\n\t\t\tString[] interfaces = i == 1 ? null : new String[]{\"I\" + (i - 1)};\n\t\t\tClassWriter cw = new ClassWriter(0);\n\t\t\tcw.visit(V1_8, ACC_INTERFACE, \"I\" + i, null, \"java/lang/Object\", interfaces);\n\t\t\treturn new JvmClassInfoBuilder(cw.toByteArray()).build();\n\t\t}).toList().toArray(JvmClassInfo[]::new);\n\n\t\t// Create workspace + graph with the generated interfaces\n\t\tBasicJvmClassBundle classes = TestClassUtils.fromClasses(generatedClasses);\n\t\tWorkspace workspace = TestClassUtils.fromBundle(classes);\n\t\tInheritanceGraph inheritanceGraph = inheritanceGraphService.newInheritanceGraph(workspace);\n\t\tinheritanceGraph.installMappingListener(mappingListeners);\n\n\t\t// Verify initial state\n\t\tfor (int i = 1; i <= CLASS_COUNT; i++) {\n\t\t\tString name = \"I\" + i;\n\t\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(name);\n\t\t\tassertNotNull(vertex, \"Graph missing '\" + name + \"'\");\n\t\t}\n\n\t\t// Remap classes\n\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\tfor (int i = 1; i <= CLASS_COUNT; i++)\n\t\t\tmappings.addClass(\"I\" + i, \"R\" + i);\n\t\tMappingResults results = mappingApplierService.inWorkspace(workspace).applyToPrimaryResource(mappings);\n\t\tresults.apply();\n\n\t\t// Verify old classes are removed from the graph\n\t\tfor (int i = 1; i <= CLASS_COUNT; i++) {\n\t\t\tString name = \"I\" + i;\n\t\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(name);\n\t\t\tassertNull(vertex, \"Graph contains pre-mapped '\" + name + \"'\");\n\t\t}\n\n\t\t// Verify the new classes are added to the graph\n\t\tInheritanceVertex objectVertex = inheritanceGraph.getVertex(\"java/lang/Object\");\n\t\tfor (int i = 1; i <= CLASS_COUNT; i++) {\n\t\t\tString name = \"R\" + i;\n\t\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(name);\n\t\t\tassertNotNull(vertex, \"Graph missing post-mapped '\" + name + \"'\");\n\t\t\tif (i > 1) {\n\t\t\t\tSet<InheritanceVertex> parents = vertex.getParents();\n\t\t\t\tSet<InheritanceVertex> allParents = vertex.getAllParents();\n\t\t\t\tint directParentCount = parents.size();\n\t\t\t\tint allParentCount = allParents.size();\n\t\t\t\tassertEquals(2, directParentCount, \"Vertex R\" + i + \" should have 2 direct parents (extended interface R\" + (i - 1) + \" + java/lang/Object)\");\n\t\t\t\tassertEquals(i, allParentCount, \"Vertex R\" + i + \" should have \" + i + \" total (direct + transitive) parents\");\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/inheritance/InheritanceGraphTest.java",
    "content": "package software.coley.recaf.services.inheritance;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.Opcodes;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.Inheritance;\nimport software.coley.recaf.test.dummy.StringConsumer;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link InheritanceGraph}\n */\nclass InheritanceGraphTest extends TestBase {\n\tstatic Workspace workspace;\n\tstatic InheritanceGraph inheritanceGraph;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\tInheritanceGraphService inheritanceGraphService = recaf.get(InheritanceGraphService.class);\n\t\tassertNotNull(inheritanceGraphService.toString()); // Bogus call to initialize the service\n\n\t\t// Create workspace with the inheritance classes\n\t\tBasicJvmClassBundle classes = TestClassUtils.fromClasses(Inheritance.class.getClasses());\n\t\tclasses.initialPut(TestClassUtils.fromRuntimeClass(StringConsumer.class));\n\t\tassertEquals(7, classes.size(), \"Expecting 7 classes\");\n\t\tworkspace = TestClassUtils.fromBundle(classes);\n\t\tworkspaceManager.setCurrent(workspace);\n\n\t\t// Get graph\n\t\tinheritanceGraph = inheritanceGraphService.getCurrentWorkspaceInheritanceGraph();\n\t}\n\n\t@Test\n\tvoid getVertex() {\n\t\tString appleName = Inheritance.Apple.class.getName().replace('.', '/');\n\n\t\t// Check vertex\n\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(appleName);\n\t\tassertNotNull(vertex, \"Could not get Apple vertex from workspace\");\n\t\tassertEquals(appleName, vertex.getName(), \"Vertex should have same name as lookup\");\n\n\t\t// Check children\n\t\tSet<InheritanceVertex> children = vertex.getChildren();\n\t\tassertEquals(1, children.size(), \"Expecting 1 child for Apple (apple with worm)\");\n\t\tassertTrue(children.stream()\n\t\t\t\t.map(InheritanceVertex::getName)\n\t\t\t\t.anyMatch(name -> name.equals(appleName + \"WithWorm\")));\n\n\t\t// Check parents\n\t\tSet<InheritanceVertex> parents = vertex.getParents();\n\t\tassertEquals(3, parents.size(), \"Expecting 3 parents for Apple (interfaces, super is object and is ignored)\");\n\t\tassertTrue(parents.stream()\n\t\t\t\t\t\t.map(InheritanceVertex::getName)\n\t\t\t\t\t\t.anyMatch(name -> name.equals(appleName.replace(\"Apple\", \"Red\"))),\n\t\t\t\t\"Apple missing parent: Red\");\n\t\tassertTrue(parents.stream()\n\t\t\t\t\t\t.map(InheritanceVertex::getName)\n\t\t\t\t\t\t.anyMatch(name -> name.equals(appleName.replace(\"Apple\", \"Edible\"))),\n\t\t\t\t\"Apple missing parent: Edible\");\n\n\t\t// Check awareness of library methods\n\t\tvertex = inheritanceGraph.getVertex(StringConsumer.class.getName().replace('.', '/'));\n\t\tassertNotNull(vertex);\n\t\tassertTrue(vertex.hasMethod(\"accept\", \"(Ljava/lang/Object;)V\"));\n\t\tassertTrue(vertex.hasMethod(\"accept\", \"(Ljava/lang/String;)V\")); // Redirects to Object method\n\t\tassertTrue(vertex.isLibraryMethod(\"accept\", \"(Ljava/lang/Object;)V\")); // From Consumer<T>\n\t\tassertFalse(vertex.isLibraryMethod(\"accept\", \"(Ljava/lang/String;)V\")); // Local, doesn't matter if renamed.\n\t}\n\n\t@Test\n\tvoid getVertexFamily() {\n\t\tString appleName = Inheritance.Apple.class.getName().replace('.', '/');\n\t\tSet<String> names = Set.of(appleName,\n\t\t\t\tappleName + \"WithWorm\", // child of apple\n\t\t\t\tappleName.replace(\"Apple\", \"Red\"), // parent of apple\n\t\t\t\tappleName.replace(\"Apple\", \"Edible\"), // parent of apple\n\t\t\t\tappleName.replace(\"Apple\", \"Grape\") // shared parent edible\n\t\t);\n\t\tSet<InheritanceVertex> family = inheritanceGraph.getVertexFamily(appleName, false);\n\t\tassertEquals(5, family.size());\n\t\tassertEquals(names, family.stream().map(InheritanceVertex::getName).collect(Collectors.toSet()));\n\t}\n\n\t@Test\n\tvoid isLibraryMethod() {\n\t\tString stringConsumerName = StringConsumer.class.getName().replace('.', '/');\n\n\t\t// The bridge method defined in StringConsumer can technically be renamed since it is defined locally\n\t\t// and is not an override of the Consumer<T> method which takes Object.\n\t\tassertFalse(inheritanceGraph.isLibraryMethod(stringConsumerName, \"accept\", \"(Ljava/lang/String;)V\"),\n\t\t\t\t\"StringConsumer.accept(String) should not be a library method\");\n\n\t\t// Inherited method from java.util.function.Consumer so it should be marked as a library method.\n\t\tassertTrue(inheritanceGraph.isLibraryMethod(stringConsumerName, \"accept\", \"(Ljava/lang/Object;)V\"),\n\t\t\t\t\"StringConsumer.accept(Object) should be a library method\");\n\t}\n\n\t@Test\n\tvoid getCommon() {\n\t\tString edibleName = Inheritance.Edible.class.getName().replace('.', '/');\n\t\tString appleName = Inheritance.Apple.class.getName().replace('.', '/');\n\t\tString grapeName = Inheritance.Grape.class.getName().replace('.', '/');\n\n\t\t// Compare obvious case --> edible\n\t\tString commonType = inheritanceGraph.getCommon(appleName, grapeName);\n\t\tassertEquals(edibleName, commonType, \"Common type of Apple/Grape should be Edible\");\n\n\t\t// Compare with bogus --> object\n\t\tcommonType = inheritanceGraph.getCommon(appleName, UUID.randomUUID().toString());\n\t\tassertEquals(Types.OBJECT_TYPE.getInternalName(), commonType,\n\t\t\t\t\"Common type of two unrelated classes should be Object\");\n\t}\n\n\t@Test\n\tvoid isAssignableFrom() {\n\t\tString edibleName = Inheritance.Edible.class.getName().replace('.', '/');\n\t\tString appleName = Inheritance.Apple.class.getName().replace('.', '/');\n\t\tString grapeName = Inheritance.Grape.class.getName().replace('.', '/');\n\n\t\t// Edible.class.isAssignableFrom(Apple.class) --> true\n\t\tassertTrue(inheritanceGraph.isAssignableFrom(edibleName, appleName), \"Edible should be assignable from Apple\");\n\t\tassertTrue(inheritanceGraph.isAssignableFrom(edibleName, grapeName), \"Edible should be assignable from Grape\");\n\n\t\t// Apple.class.isAssignableFrom(Edible.class) --> false\n\t\tassertFalse(inheritanceGraph.isAssignableFrom(appleName, edibleName), \"Apple should not be assignable from Edible\");\n\t\tassertFalse(inheritanceGraph.isAssignableFrom(grapeName, edibleName), \"Grape should not be assignable from Edible\");\n\n\t\t// Core Java types\n\t\tassertTrue(inheritanceGraph.isAssignableFrom(\"java/lang/Throwable\", \"java/lang/Exception\"), \"Throwable should be assignable from Exception\");\n\t\tassertFalse(inheritanceGraph.isAssignableFrom(\"java/lang/Exception\", \"java/lang/Throwable\"), \"Exception should not be assignable from Throwable\");\n\t}\n\n\t@Test\n\tvoid getFamilyOfThrowable() {\n\t\tString notFoodExceptionName = Inheritance.NotFoodException.class.getName().replace('.', '/');\n\t\tClassPathNode classPath = workspace.findJvmClass(notFoodExceptionName);\n\t\tassertNotNull(classPath, \"Could not find class 'NotFoodException'\");\n\n\t\t// Assert that looking at child types of throwable finds NotFoodException.\n\t\t// Our class extends Exception, which extends Throwable. So there should be a vertex between Throwable and our type.\n\t\tJvmClassInfo notFoodException = classPath.getValue().asJvmClass();\n\t\tList<ClassInfo> exceptionClasses = inheritanceGraph.getVertex(\"java/lang/Exception\").getAllChildren().stream()\n\t\t\t\t.map(InheritanceVertex::getValue)\n\t\t\t\t.toList();\n\t\tassertTrue(exceptionClasses.contains(notFoodException), \"Subtypes of 'Exception' did not yield 'NotFoodException'\");\n\t\tList<ClassInfo> throwableClasses = inheritanceGraph.getVertex(\"java/lang/Throwable\").getAllChildren().stream()\n\t\t\t\t.map(InheritanceVertex::getValue)\n\t\t\t\t.toList();\n\t\tassertTrue(throwableClasses.contains(notFoodException), \"Subtypes of 'Throwable' did not yield 'NotFoodException'\");\n\t}\n\n\t@Test\n\tvoid vertexUpdatedWithClassModifications() {\n\t\tString appleName = Inheritance.Apple.class.getName().replace('.', '/');\n\n\t\t// Get 'Apple' class and vertex.\n\t\tJvmClassBundle bundle = workspace.getPrimaryResource().getJvmClassBundle();\n\t\tJvmClassInfo classInfo = bundle.get(appleName);\n\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(appleName);\n\n\t\t// Assert the vertex points to the same reference as the class info.\n\t\tassertNotNull(vertex);\n\t\tassertSame(classInfo, vertex.getValue(), \"Expected vertex to point to class\");\n\n\t\t// Modify the class a bit and put it back into the workspace.\n\t\t// We're only changing a single byte that doesn't matter so that it won't affect the other tests.\n\t\tbyte[] bytecodeCopy = Arrays.copyOf(classInfo.getBytecode(), classInfo.getBytecode().length);\n\t\tbytecodeCopy[4] = Opcodes.V17;\n\t\tJvmClassInfo classInfoUpdated = classInfo.toJvmClassBuilder().adaptFrom(bytecodeCopy).build();\n\t\tbundle.put(classInfoUpdated);\n\t\tassertNotSame(classInfo, classInfoUpdated);\n\n\t\t// Assert the vertex now points to the updated class info instance.\n\t\tInheritanceVertex vertexUpdated = inheritanceGraph.getVertex(appleName);\n\t\tassertNotNull(vertexUpdated);\n\t\tassertSame(classInfoUpdated, vertexUpdated.getValue(), \"Expected updated vertex to point to updated class\");\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/json/GsonProviderTest.java",
    "content": "package software.coley.recaf.services.json;\n\nimport com.google.gson.Gson;\nimport com.google.gson.JsonDeserializationContext;\nimport com.google.gson.JsonDeserializer;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonParseException;\nimport com.google.gson.JsonPrimitive;\nimport com.google.gson.JsonSerializationContext;\nimport com.google.gson.JsonSerializer;\nimport com.google.gson.TypeAdapter;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.lang.reflect.Type;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Tests for {@link GsonProvider}.\n */\nclass GsonProviderTest {\n\tprivate GsonProvider provider;\n\n\t@BeforeEach\n\tvoid setup() {\n\t\tprovider = new GsonProvider(new GsonProviderConfig());\n\t}\n\n\t@Test\n\tvoid addTypeSerializer() {\n\t\t// The default gson should match our default in the provider given instance.\n\t\tGson defaultGson = new Gson();\n\t\tGson baseline = provider.getGson();\n\t\tassertThat(baseline.getAdapter(Foo.class)).hasSameClassAs(defaultGson.getAdapter(Foo.class));\n\t\tassertThat(baseline.getAdapter(Fizz.class)).hasSameClassAs(defaultGson.getAdapter(Fizz.class));\n\n\t\t// If we add two type serializers, we should see them in our newly provided instance.\n\t\tDummyModelSerializer ourSerializer = new DummyModelSerializer();\n\t\tprovider.addTypeSerializer(Foo.class, ourSerializer);\n\t\tprovider.addTypeSerializer(Fizz.class, ourSerializer);\n\t\tGson afterSerialization = provider.getGson();\n\t\tTypeAdapter<Foo> adapterFoo = afterSerialization.getAdapter(Foo.class);\n\t\tTypeAdapter<Fizz> adapterFizz = afterSerialization.getAdapter(Fizz.class);\n\n\t\t// They should not be the same as our first assertion's type.\n\t\tassertThat(adapterFoo).doesNotHaveSameClassAs(defaultGson.getAdapter(Foo.class));\n\t\tassertThat(adapterFizz).doesNotHaveSameClassAs(defaultGson.getAdapter(Fizz.class));\n\t}\n\n\t@Test\n\tvoid addTypeSerializerAndDeserializer() {\n\t\tDummyModelSerializer ourSerializer = new DummyModelSerializer();\n\t\tDummyModelDeserializer ourDeserializer = new DummyModelDeserializer();\n\n\t\tprovider.addTypeSerializer(Foo.class, ourSerializer);\n\t\tprovider.addTypeDeserializer(Foo.class, ourDeserializer);\n\t\tGson gson = provider.getGson();\n\n\t\tString json = gson.toJson(new Foo(\"hello\"));\n\t\tFoo foo = gson.fromJson(json, Foo.class);\n\n\t\tassertThat(foo)\n\t\t\t\t.isInstanceOf(Foo.class)\n\t\t\t\t.matches(deserializedFoo -> deserializedFoo.display().equals(\"hello\"));\n\t}\n\n\tstatic class DummyModelSerializer implements JsonSerializer<DummyModel> {\n\t\t@Override\n\t\tpublic JsonElement serialize(DummyModel model, Type type, JsonSerializationContext context) {\n\t\t\treturn new JsonPrimitive(model.getClass().getSimpleName() + \":\" + model.display());\n\t\t}\n\t}\n\n\tstatic class DummyModelDeserializer implements JsonDeserializer<DummyModel> {\n\t\t@Override\n\t\tpublic DummyModel deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException {\n\t\t\tString string = element.getAsString();\n\t\t\tif (string.startsWith(\"Foo\"))\n\t\t\t\treturn new Foo(string.substring(4));\n\t\t\telse if (string.startsWith(\"Fizz\"))\n\t\t\t\treturn new Fizz(string.substring(5));\n\t\t\tthrow new JsonParseException(\"No type provided in encoded string: \" + string);\n\t\t}\n\t}\n\n\tinterface DummyModel {\n\t\tString display();\n\t}\n\n\trecord Foo(String bar) implements DummyModel {\n\t\t@Override\n\t\tpublic String display() {\n\t\t\treturn bar;\n\t\t}\n\t}\n\n\trecord Fizz(String buzz) implements DummyModel {\n\t\t@Override\n\t\tpublic String display() {\n\t\t\treturn buzz;\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/mapping/MappingApplierTest.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.annotation.AnnotationElement;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.info.properties.builtin.OriginalClassNameProperty;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.mapping.aggregate.AggregateMappingManager;\nimport software.coley.recaf.services.mapping.aggregate.AggregatedMappings;\nimport software.coley.recaf.services.mapping.gen.MappingGenerator;\nimport software.coley.recaf.services.mapping.gen.naming.NameGenerator;\nimport software.coley.recaf.services.mapping.gen.filter.NameGeneratorFilter;\nimport software.coley.recaf.services.mapping.gen.naming.AlphabetNameGenerator;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.*;\nimport software.coley.recaf.util.ClassDefiner;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.io.IOException;\nimport java.lang.reflect.Method;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link MappingApplier} with some edge case classes.\n */\nclass MappingApplierTest extends TestBase {\n\tstatic NameGenerator nameGenerator;\n\tMappingGenerator mappingGenerator;\n\tWorkspace workspace;\n\tWorkspaceResource resource;\n\tAggregateMappingManager aggregateMappingManager;\n\tInheritanceGraph inheritanceGraph;\n\tMappingApplierService mappingApplierService;\n\n\t@BeforeAll\n\tstatic void setupGenerator() {\n\t\tString alphabet = \"abcdefghijklmnopqrstuvwxyz\";\n\t\tnameGenerator = new AlphabetNameGenerator(alphabet, 3);\n\t}\n\n\t@BeforeEach\n\tvoid prepareWorkspace() throws IOException {\n\t\t// We want to reset the workspace before each test\n\t\tworkspace = TestClassUtils.fromBundle(TestClassUtils.fromClasses(\n\t\t\t\tAnonymousLambda.class,\n\t\t\t\tStringSupplier.class,\n\t\t\t\t//\n\t\t\t\tDummyEnum.class,\n\t\t\t\tDummyEnumPrinter.class,\n\t\t\t\t//\n\t\t\t\tDiamondA.class,\n\t\t\t\tDiamondB.class,\n\t\t\t\tDiamondC.class,\n\t\t\t\t//\n\t\t\t\tAnnotationImpl.class,\n\t\t\t\tClassWithAnnotation.class,\n\t\t\t\t//\n\t\t\t\tOverlapInterfaceA.class,\n\t\t\t\tOverlapInterfaceB.class,\n\t\t\t\tOverlapClassAB.class,\n\t\t\t\tOverlapCaller.class\n\t\t));\n\t\tresource = workspace.getPrimaryResource();\n\n\t\t// Get and initialize the aggregate mapping manager\n\t\taggregateMappingManager = recaf.get(AggregateMappingManager.class);\n\t\taggregateMappingManager.toString();\n\n\t\t// Get and initialize the inheritance graph service\n\t\tInheritanceGraphService graphService = recaf.get(InheritanceGraphService.class);\n\t\tgraphService.toString();\n\n\t\t// Get inherit graph for the\n\t\tmappingGenerator = recaf.get(MappingGenerator.class);\n\t\tmappingApplierService = recaf.get(MappingApplierService.class);\n\n\t\t// Set the workspace\n\t\tworkspaceManager.setCurrent(workspace);\n\t\tinheritanceGraph = graphService.getCurrentWorkspaceInheritanceGraph();\n\t}\n\n\t@Test\n\tvoid longName() {\n\t\tString alphabet = \"abcdefghijklmnopqrstuvwxyz\";\n\t\tAlphabetNameGenerator longNameGenerator = new AlphabetNameGenerator(alphabet, 2048);\n\t\tworkspace.getPrimaryResource().getJvmClassBundle().forEach(cls -> {\n\t\t\tassertDoesNotThrow(() -> longNameGenerator.mapClass(cls));\n\t\t});\n\t}\n\n\t@Test\n\tvoid applyAnonymousLambda() {\n\t\tString stringSupplierName = StringSupplier.class.getName().replace('.', '/');\n\t\tString anonymousLambdaName = AnonymousLambda.class.getName().replace('.', '/');\n\n\t\t// Create mappings for all classes but the runner 'AnonymousLambda'\n\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, new NameGeneratorFilter(null, true) {\n\t\t\t@Override\n\t\t\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\t\t\treturn !info.getName().equals(anonymousLambdaName);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\t\t\treturn shouldMapClass(owner);\n\t\t\t}\n\t\t});\n\n\t\t// Preview the mapping operation\n\t\tMappingResults results = mappingApplierService.inCurrentWorkspace().applyToPrimaryResource(mappings);\n\n\t\t// The supplier class we define should be remapped.\n\t\t// The runner class (AnonymousLambda) itself should not be remapped, but should be updated to point to\n\t\t// the new StringSupplier class name.\n\t\tString mappedStringSupplierName = mappings.getMappedClassName(stringSupplierName);\n\t\tassertNotNull(mappedStringSupplierName, \"StringSupplier should be remapped\");\n\t\tassertNull(mappings.getMappedClassName(anonymousLambdaName), \"AnonymousLambda should not be remapped\");\n\t\tassertTrue(results.wasMapped(stringSupplierName), \"StringSupplier should have updated\");\n\t\tassertTrue(results.wasMapped(anonymousLambdaName), \"AnonymousLambda should have updated\");\n\n\t\t// Verify that the original name is stored as a property.\n\t\tClassPathNode classPath = results.getPostMappingPath(stringSupplierName);\n\t\tassertNotNull(classPath, \"Could not find mapped StringSupplier in workspace\");\n\t\tJvmClassInfo mappedStringSupplier = classPath.getValue().asJvmClass();\n\t\tassertEquals(stringSupplierName, OriginalClassNameProperty.get(mappedStringSupplier),\n\t\t\t\t\"Did not record original name after applying mappings\");\n\n\t\t// Assert that the method is still runnable.\n\t\tString result = runMapped(AnonymousLambda.class, \"run\");\n\t\tassertTrue(result.contains(\"One: java.util.function.Supplier\"),\n\t\t\t\t\"JDK class reference should not be mapped\");\n\t\tassertFalse(result.contains(stringSupplierName),\n\t\t\t\t\"Class reference to '\" + stringSupplierName + \"' should have been remapped\");\n\n\t\t// Assert aggregate updated too.\n\t\t// We will validate this is only done AFTER 'results.apply()' is run.\n\t\t// For future tests we will skip this since if it works here, it works there.\n\t\tAggregatedMappings aggregatedMappings = aggregateMappingManager.getAggregatedMappings();\n\t\tassertNotNull(aggregatedMappings);\n\t\tassertNull(aggregatedMappings.getMappedClassName(stringSupplierName),\n\t\t\t\t\"StringSupplier should not yet be tracked in aggregate\");\n\t\tresults.apply();\n\t\tassertNotNull(aggregatedMappings.getMappedClassName(stringSupplierName),\n\t\t\t\t\"StringSupplier should be tracked in aggregate\");\n\t}\n\n\t@Test\n\tvoid applyDummyEnumPrinter() {\n\t\tString dummyEnumName = DummyEnum.class.getName().replace('.', '/');\n\t\tString dummyEnumPrinterName = DummyEnumPrinter.class.getName().replace('.', '/');\n\n\t\t// Create mappings for all classes but the runner 'DummyEnumPrinter'\n\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, new NameGeneratorFilter(null, true) {\n\t\t\t@Override\n\t\t\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\t\t\treturn !info.getName().equals(dummyEnumPrinterName);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\t\t\treturn shouldMapClass(owner);\n\t\t\t}\n\t\t});\n\n\t\t// Preview the mapping operation\n\t\tMappingResults results = mappingApplierService.inCurrentWorkspace().applyToPrimaryResource(mappings);\n\n\t\t// The enum class we define should be remapped.\n\t\t// The runner class (DummyEnumPrinter) itself should not be remapped, but should be updated to point to\n\t\t// the new DummyEnum class name.\n\t\tassertNotNull(mappings.getMappedClassName(dummyEnumName), \"DummyEnum should be remapped\");\n\t\tassertNull(mappings.getMappedClassName(dummyEnumPrinterName), \"DummyEnumPrinter should not be remapped\");\n\t\tassertNull(mappings.getMappedMethodName(dummyEnumName, \"values\", \"()[L\" + dummyEnumName + \";\"),\n\t\t\t\t\"DummyEnum#values() should not be remapped\");\n\t\tassertNull(mappings.getMappedMethodName(dummyEnumName, \"valueOf\", \"(Ljava/lang/String;)L\" + dummyEnumName + \";\"),\n\t\t\t\t\"DummyEnum#valueOf(String) should not be remapped\");\n\t\tassertTrue(results.wasMapped(dummyEnumName), \"DummyEnum should have updated\");\n\t\tassertTrue(results.wasMapped(dummyEnumPrinterName), \"DummyEnumPrinter should have updated\");\n\n\t\t// Assert aggregate updated too.\n\t\tresults.apply();\n\t\tAggregatedMappings aggregatedMappings = aggregateMappingManager.getAggregatedMappings();\n\t\tassertNotNull(aggregatedMappings);\n\t\tassertNotNull(aggregatedMappings.getMappedClassName(dummyEnumName),\n\t\t\t\t\"DummyEnum should be tracked in aggregate\");\n\n\t\t// Assert that the methods are still runnable.\n\t\trunMapped(DummyEnumPrinter.class, \"run1\");\n\t\trunMapped(DummyEnumPrinter.class, \"run2\");\n\t}\n\n\t@Test\n\tvoid applyClassWithAnnotation() {\n\t\tString annotationName = AnnotationImpl.class.getName().replace('.', '/');\n\t\tString classWithAnnotationName = ClassWithAnnotation.class.getName().replace('.', '/');\n\n\t\t// Create mappings for all classes but the target 'ClassWithAnnotation'\n\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, new NameGeneratorFilter(null, true) {\n\t\t\t@Override\n\t\t\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\t\t\treturn !info.getName().equals(classWithAnnotationName);\n\t\t\t}\n\t\t});\n\n\t\t// Preview the mapping operation\n\t\tMappingResults results = mappingApplierService.inCurrentWorkspace().applyToPrimaryResource(mappings);\n\n\t\t// The annotation class we define should be remapped.\n\t\t// The user class (ClassWithAnnotation) itself should not be remapped,\n\t\t// but its annotation usage should be updated.\n\t\tString mappedAnnotationName = mappings.getMappedClassName(annotationName);\n\t\tassertNotNull(mappedAnnotationName, \"AnnotationImpl should be remapped\");\n\t\tassertNull(mappings.getMappedClassName(classWithAnnotationName), \"ClassWithAnnotation should not be remapped\");\n\t\tassertTrue(results.wasMapped(annotationName), \"AnnotationImpl should have updated\");\n\t\tassertTrue(results.wasMapped(classWithAnnotationName), \"ClassWithAnnotation should have updated\");\n\n\t\t// Assert aggregate updated too.\n\t\tresults.apply();\n\t\tAggregatedMappings aggregatedMappings = aggregateMappingManager.getAggregatedMappings();\n\t\tassertNotNull(aggregatedMappings);\n\t\tassertNotNull(aggregatedMappings.getMappedClassName(annotationName),\n\t\t\t\t\"AnnotationImpl should be tracked in aggregate\");\n\n\t\t// Get the names of the annotation's mapped attribute methods\n\t\tString annoValueName = mappings.getMappedMethodName(annotationName, \"value\", \"()Ljava/lang/String;\");\n\t\tString annoPolicyName = mappings.getMappedMethodName(annotationName, \"policy\", \"()Ljava/lang/annotation/Retention;\");\n\n\t\t// Assert the user class has the correct new values\n\t\tClassPathNode classPath = results.getPostMappingPath(classWithAnnotationName);\n\t\tassertNotNull(classPath, \"Could not find: \" + classWithAnnotationName);\n\t\tJvmClassInfo classWithAnnotation = classPath.getValue().asJvmClass();\n\t\tAnnotationInfo annotationInfo = classWithAnnotation.getAnnotations().get(0);\n\t\tassertEquals(\"L\" + mappedAnnotationName + \";\", annotationInfo.getDescriptor(),\n\t\t\t\t\"AnnotationImpl not remapped in ClassWithAnnotation\");\n\t\tAnnotationElement valueElement = annotationInfo.getElements().get(annoValueName);\n\t\tAnnotationElement policyElement = annotationInfo.getElements().get(annoPolicyName);\n\t\tassertNotNull(valueElement, \"Missing mapped value element\");\n\t\tassertNotNull(policyElement, \"Missing mapped policy element\");\n\t}\n\n\t@Test\n\tvoid applyOverlapping() {\n\t\tString overlapInterfaceAName = OverlapInterfaceA.class.getName().replace('.', '/');\n\t\tString overlapInterfaceBName = OverlapInterfaceB.class.getName().replace('.', '/');\n\t\tString overlapClassABName = OverlapClassAB.class.getName().replace('.', '/');\n\t\tString overlapCallerName = OverlapCaller.class.getName().replace('.', '/');\n\n\t\t// Create mappings for all classes but the runner 'OverlapCaller'\n\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, new NameGeneratorFilter(null, true) {\n\t\t\t@Override\n\t\t\tpublic boolean shouldMapClass(@Nonnull ClassInfo info) {\n\t\t\t\treturn !info.getName().equals(overlapCallerName);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\t\t\treturn shouldMapClass(owner);\n\t\t\t}\n\t\t});\n\n\t\t// Preview the mapping operation\n\t\tMappingResults results = mappingApplierService.inCurrentWorkspace().applyToPrimaryResource(mappings);\n\n\t\tassertNotNull(mappings.getMappedClassName(overlapInterfaceAName), \"OverlapInterfaceA should be remapped\");\n\t\tassertNotNull(mappings.getMappedClassName(overlapInterfaceBName), \"OverlapInterfaceB should be remapped\");\n\t\tassertNotNull(mappings.getMappedClassName(overlapClassABName), \"OverlapClassAB should be remapped\");\n\t\tassertNull(mappings.getMappedClassName(overlapCallerName), \"OverlapCaller should not be remapped\");\n\t\tassertTrue(results.wasMapped(overlapInterfaceAName), \"OverlapInterfaceA should have updated\");\n\t\tassertTrue(results.wasMapped(overlapInterfaceBName), \"OverlapInterfaceB should have updated\");\n\t\tassertTrue(results.wasMapped(overlapClassABName), \"OverlapClassAB should have updated\");\n\t\tassertTrue(results.wasMapped(overlapCallerName), \"OverlapCaller should have updated\");\n\n\t\t// Assert aggregate updated too.\n\t\tresults.apply();\n\t\tAggregatedMappings aggregatedMappings = aggregateMappingManager.getAggregatedMappings();\n\t\tassertNotNull(aggregatedMappings);\n\t\tassertNotNull(aggregatedMappings.getMappedClassName(overlapInterfaceAName),\n\t\t\t\t\"OverlapInterfaceA should be tracked in aggregate\");\n\t\tassertNotNull(aggregatedMappings.getMappedClassName(overlapInterfaceBName),\n\t\t\t\t\"OverlapInterfaceB should be tracked in aggregate\");\n\t\tassertNotNull(aggregatedMappings.getMappedClassName(overlapClassABName),\n\t\t\t\t\"OverlapClassAB should be tracked in aggregate\");\n\t\tassertNull(aggregatedMappings.getMappedClassName(overlapCallerName),\n\t\t\t\t\"OverlapCaller should not be tracked in aggregate\");\n\n\t\t// Assert that the method is still runnable.\n\t\trunMapped(OverlapCaller.class, \"run\");\n\t}\n\n\t@Test\n\t@Disabled(\"Need to update hierarchy scanning in MappingsAdapter\")\n\tvoid applyDiamond() {\n\t\tString diamondA = DiamondA.class.getName().replace('.', '/');\n\t\tString diamondB = DiamondB.class.getName().replace('.', '/');\n\t\tString diamondC = DiamondC.class.getName().replace('.', '/');\n\n\t\t// Create mappings for all classes but the runner 'OverlapCaller'\n\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\tmappings.addMethod(diamondA, \"()V\", \"diamond\", \"foo\");\n\n\t\t// Preview the mapping operation\n\t\tMappingResults results = mappingApplierService.inCurrentWorkspace().applyToPrimaryResource(mappings);\n\n\t\t// The \"diamond()V\" in the interface, and disconnected super class DiamondB, should both be mapped\n\t\tClassInfo postA = results.getPostMappingClass(diamondA);\n\t\tClassInfo postB = results.getPostMappingClass(diamondB);\n\t\tClassInfo postC = results.getPostMappingClass(diamondC);\n\t\tassertNotNull(postA, \"DiamondA should be remapped\");\n\t\tassertNotNull(postB, \"DiamondB should be remapped\");\n\t\tassertNull(postC, \"DiamondA should not be remapped\");\n\t}\n\n\tprivate String runMapped(Class<?> cls, String methodName) {\n\t\tString className = cls.getName();\n\t\tClassDefiner definer = newDefinerFromWorkspace();\n\t\ttry {\n\t\t\tClass<?> runner = definer.findClass(className);\n\t\t\tMethod main = runner.getDeclaredMethod(methodName);\n\t\t\ttry {\n\t\t\t\treturn (String) main.invoke(null);\n\t\t\t} catch (ReflectiveOperationException ex) {\n\t\t\t\tfail(\"Failed to execute '\" + methodName + \"' method\", ex);\n\t\t\t}\n\t\t} catch (ClassNotFoundException | NoSuchMethodException ex) {\n\t\t\tfail(\"Class '\" + className + \"' or '\" + methodName + \"' method missing\", ex);\n\t\t}\n\t\tthrow new IllegalStateException();\n\t}\n\n\tprivate ClassDefiner newDefinerFromWorkspace() {\n\t\tMap<String, byte[]> map = resource.getJvmClassBundle().values().stream()\n\t\t\t\t.collect(Collectors.toMap(e -> e.getName().replace('/', '.'), JvmClassInfo::getBytecode));\n\t\treturn new ClassDefiner(map);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/mapping/aggregate/AggregateMappingManagerTest.java",
    "content": "package software.coley.recaf.services.mapping.aggregate;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.workspace.model.EmptyWorkspace;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\n/**\n * Tests for {@link AggregateMappingManager}\n *\n * @see AggregateMappingsTest\n */\npublic class AggregateMappingManagerTest extends TestBase {\n\tAggregateMappingManager aggregateMappingManager;\n\n\t@BeforeEach\n\tvoid setupPerTest() {\n\t\taggregateMappingManager = recaf.get(AggregateMappingManager.class);\n\t\tassertNotNull(aggregateMappingManager.toString()); // Bogus call to initialize the aggregate mapping manager\n\t\tworkspaceManager.setCurrent(EmptyWorkspace.get());\n\t}\n\n\t@AfterEach\n\tvoid cleanupPerTest() {\n\t\tworkspaceManager.closeCurrent();\n\t}\n\n\t@Test\n\tvoid testRefactorIntFieldWithManager() {\n\t\t// Setup renaming a class 3 times, and a simple int field\n\t\tIntermediateMappings mappings1 = new IntermediateMappings();\n\t\tIntermediateMappings mappings2 = new IntermediateMappings();\n\t\tIntermediateMappings mappings3 = new IntermediateMappings();\n\n\t\t// 'a' renamed to 'b'\n\t\tmappings1.addClass(\"a\", \"b\");\n\n\t\t// 'b' renamed to 'c', so 'a' is now 'c'\n\t\t// but also rename the field 'oldName' to 'newName'\n\t\tmappings2.addClass(\"b\", \"c\");\n\t\tmappings2.addField(\"b\", \"I\", \"oldName\", \"newName\");\n\n\t\t// 'c' renamed to 'd'\n\t\t// but also rename the field from before to 'brandNewName'\n\t\tmappings3.addClass(\"c\", \"d\");\n\t\tmappings3.addField(\"c\", \"I\", \"newName\", \"brandNewName\");\n\n\t\t// Get aggregate instance from manager\n\t\tAggregatedMappings aggregated = aggregateMappingManager.getAggregatedMappings();\n\t\tassertNotNull(aggregated);\n\n\t\t// Validate after first mapping pass\n\t\taggregateMappingManager.updateAggregateMappings(mappings1);\n\t\tassertEquals(\"b\", aggregated.getMappedClassName(\"a\"));\n\n\t\t// Validate after second mapping pass\n\t\taggregateMappingManager.updateAggregateMappings(mappings2);\n\t\tassertEquals(\"c\", aggregated.getMappedClassName(\"a\"));\n\t\tassertEquals(\"newName\", aggregated.getMappedFieldName(\"a\", \"oldName\", \"I\"));\n\n\t\t// Validate after third mapping pass\n\t\taggregateMappingManager.updateAggregateMappings(mappings3);\n\t\tassertEquals(\"d\", aggregated.getMappedClassName(\"a\"));\n\t\tassertEquals(\"brandNewName\", aggregated.getMappedFieldName(\"a\", \"oldName\", \"I\"));\n\t}\n\n\t@Test\n\tvoid testRefactorGetInstanceWithManager() {\n\t\t// Setup renaming a class 3 times, and a 'getInstance()' sort of method\n\t\tIntermediateMappings mappings1 = new IntermediateMappings();\n\t\tIntermediateMappings mappings2 = new IntermediateMappings();\n\t\tIntermediateMappings mappings3 = new IntermediateMappings();\n\n\t\t// 'a' renamed to 'b'\n\t\tmappings1.addClass(\"a\", \"b\");\n\n\t\t// 'b' renamed to 'c', so 'a' is now 'c'\n\t\t// but also rename the method 'obf' to 'factory'\n\t\tmappings2.addClass(\"b\", \"c\");\n\t\tmappings2.addMethod(\"b\", \"()Lb;\", \"obf\", \"factory\");\n\n\t\t// 'c' renamed to 'd'\n\t\t// but also rename the method from before to 'getInstance'\n\t\tmappings3.addClass(\"c\", \"d\");\n\t\tmappings3.addMethod(\"c\", \"()Lc;\", \"factory\", \"getInstance\");\n\n\t\t// Get aggregate instance from manager\n\t\tAggregatedMappings aggregated = aggregateMappingManager.getAggregatedMappings();\n\t\tassertNotNull(aggregated);\n\n\t\t// Validate after first mapping pass\n\t\taggregateMappingManager.updateAggregateMappings(mappings1);\n\t\tassertEquals(\"b\", aggregated.getMappedClassName(\"a\"));\n\n\t\t// Validate after second mapping pass\n\t\taggregateMappingManager.updateAggregateMappings(mappings2);\n\t\tassertEquals(\"c\", aggregated.getMappedClassName(\"a\"));\n\t\tassertEquals(\"factory\", aggregated.getMappedMethodName(\"a\", \"obf\", \"()La;\"));\n\n\t\t// Validate after third mapping pass\n\t\taggregateMappingManager.updateAggregateMappings(mappings3);\n\t\tassertEquals(\"d\", aggregated.getMappedClassName(\"a\"));\n\t\tassertEquals(\"getInstance\", aggregated.getMappedMethodName(\"a\", \"obf\", \"()La;\"));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/mapping/aggregate/AggregateMappingsTest.java",
    "content": "package software.coley.recaf.services.mapping.aggregate;\n\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.workspace.model.EmptyWorkspace;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests for {@link AggregatedMappings}\n *\n * @see AggregateMappingManagerTest\n */\npublic class AggregateMappingsTest {\n\t@Test\n\tvoid testRefactorIntField() {\n\t\t// Setup renaming a class 3 times, and a simple int field\n\t\tIntermediateMappings mappings1 = new IntermediateMappings();\n\t\tIntermediateMappings mappings2 = new IntermediateMappings();\n\t\tIntermediateMappings mappings3 = new IntermediateMappings();\n\n\t\t// 'a' renamed to 'b'\n\t\tmappings1.addClass(\"a\", \"b\");\n\t\t// 'b' renamed to 'c', so 'a' is now 'c'\n\t\t// but also rename the field 'oldName' to 'newName'\n\t\tmappings2.addClass(\"b\", \"c\");\n\t\tmappings2.addField(\"b\", \"I\", \"oldName\", \"newName\");\n\t\t// 'c' renamed to 'd'\n\t\t// but also rename the field from before to 'brandNewName'\n\t\tmappings3.addClass(\"c\", \"d\");\n\t\tmappings3.addField(\"c\", \"I\", \"newName\", \"brandNewName\");\n\n\t\t// Setup aggregation\n\t\tAggregatedMappings aggregated = new AggregatedMappings(EmptyWorkspace.get());\n\t\t// Validate after first mapping pass\n\t\taggregated.update(mappings1);\n\t\tassertEquals(\"b\", aggregated.getMappedClassName(\"a\"));\n\t\t// Validate after second mapping pass\n\t\taggregated.update(mappings2);\n\t\tassertEquals(\"c\", aggregated.getMappedClassName(\"a\"));\n\t\tassertEquals(\"newName\", aggregated.getMappedFieldName(\"a\", \"oldName\", \"I\"));\n\t\t// Validate after third mapping pass\n\t\taggregated.update(mappings3);\n\t\tassertEquals(\"d\", aggregated.getMappedClassName(\"a\"));\n\t\tassertEquals(\"brandNewName\", aggregated.getMappedFieldName(\"a\", \"oldName\", \"I\"));\n\t}\n\n\t@Test\n\tvoid testRefactorGetInstance() {\n\t\t// Setup renaming a class 3 times, and a 'getInstance()' sort of method\n\t\tIntermediateMappings mappings1 = new IntermediateMappings();\n\t\tIntermediateMappings mappings2 = new IntermediateMappings();\n\t\tIntermediateMappings mappings3 = new IntermediateMappings();\n\n\t\t// 'a' renamed to 'b'\n\t\tmappings1.addClass(\"a\", \"b\");\n\t\t// 'b' renamed to 'c', so 'a' is now 'c'\n\t\t// but also rename the method 'obf' to 'factory'\n\t\tmappings2.addClass(\"b\", \"c\");\n\t\tmappings2.addMethod(\"b\", \"()Lb;\", \"obf\", \"factory\");\n\t\t// 'c' renamed to 'd'\n\t\t// but also rename the method from before to 'getInstance'\n\t\tmappings3.addClass(\"c\", \"d\");\n\t\tmappings3.addMethod(\"c\", \"()Lc;\", \"factory\", \"getInstance\");\n\n\t\t// Setup aggregation\n\t\tAggregatedMappings aggregated = new AggregatedMappings(EmptyWorkspace.get());\n\t\t// Validate after first mapping pass\n\t\taggregated.update(mappings1);\n\t\tassertEquals(\"b\", aggregated.getMappedClassName(\"a\"));\n\t\t// Validate after second mapping pass\n\t\taggregated.update(mappings2);\n\t\tassertEquals(\"c\", aggregated.getMappedClassName(\"a\"));\n\t\tassertEquals(\"factory\", aggregated.getMappedMethodName(\"a\", \"obf\", \"()La;\"));\n\t\t// Validate after third mapping pass\n\t\taggregated.update(mappings3);\n\t\tassertEquals(\"d\", aggregated.getMappedClassName(\"a\"));\n\t\tassertEquals(\"getInstance\", aggregated.getMappedMethodName(\"a\", \"obf\", \"()La;\"));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/mapping/format/MappingImplementationTest.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests various {@link MappingFileFormat} implementation's ability to parse input texts.\n */\npublic class MappingImplementationTest {\n\t@Test\n\tvoid testTinyV1() {\n\t\tString mappingsText = \"\"\"\n\t\t\t\tv1\\tintermediary\\tnamed\n\t\t\t\tCLASS\\ttest/Greetings\\trename/Hello\n\t\t\t\tFIELD\\ttest/Greetings\\tLjava/lang/String;\\toldField\\tnewField\n\t\t\t\tMETHOD\\ttest/Greetings\\t()V\\tsay\\tspeak\"\"\";\n\t\tMappingFileFormat format = new TinyV1Mappings();\n\t\tIntermediateMappings mappings = assertDoesNotThrow(() -> format.parse(mappingsText));\n\t\tassertInheritMap(mappings);\n\t}\n\n\t@Test\n\tvoid testTinyV1WithTwoOutputs() {\n\t\tString mappingsText = \"\"\"\n\t\t\t\tv1\\tintermediary\\tobfuscated\\tnamed\n\t\t\t\tCLASS\\ttest/Greetings\\ta\\trename/Hello\n\t\t\t\tFIELD\\ttest/Greetings\\tLjava/lang/String;\\toldField\\tb\\tnewField\n\t\t\t\tMETHOD\\ttest/Greetings\\t()V\\tsay\\tc\\tspeak\"\"\";\n\t\tMappingFileFormat format = new TinyV1Mappings();\n\t\tIntermediateMappings mappings = assertDoesNotThrow(() -> format.parse(mappingsText));\n\t\tassertInheritMap(mappings);\n\n\t\t// Extra asserts for the intermediate 'obfuscated' column\n\t\tassertEquals(\"rename/Hello\", mappings.getMappedClassName(\"a\"));\n\t\tassertEquals(\"newField\", mappings.getMappedFieldName(\"a\", \"b\", \"Ljava/lang/String;\"));\n\t\tassertEquals(\"speak\", mappings.getMappedMethodName(\"a\", \"c\", \"()V\"));\n\t}\n\n\t@Test\n\tvoid testTinyV2() {\n\t\tString mappingsText = \"\"\"\n\t\t\t\ttiny\\t2\\t0\\tofficial\\tobfuscated\\tnamed\n\t\t\t\tc\\ttest/Greetings\\ta\\trename/Hello\n\t\t\t\t\\tf\\tLjava/lang/String;\\toldField\\tb\\tnewField\n\t\t\t\t\\tm\\t()V\\tsay\\tc\\tspeak\n\t\t\t\t\"\"\";\n\t\tMappingFileFormat format = new TinyV2Mappings();\n\t\tIntermediateMappings mappings = assertDoesNotThrow(() -> format.parse(mappingsText));\n\t\tassertInheritMap(mappings);\n\t}\n\n\t@Test\n\tvoid testSimple() {\n\t\tString mappingsText = \"\"\"\n\t\t\t\ttest/Greetings rename/Hello\n\t\t\t\ttest/Greetings.oldField Ljava/lang/String; newField\n\t\t\t\ttest/Greetings.say()V speak\"\"\";\n\t\tMappingFileFormat format = new SimpleMappings();\n\t\tIntermediateMappings mappings = assertDoesNotThrow(() -> format.parse(mappingsText));\n\t\tassertInheritMap(mappings);\n\t}\n\n\t@Test\n\tvoid testSrg() {\n\t\tString mappingsText = \"\"\"\n\t\t\t\tCL: test/Greetings rename/Hello\n\t\t\t\tFD: test/Greetings/oldField newField\n\t\t\t\tMD: test/Greetings/say ()V speak\"\"\";\n\t\tMappingFileFormat format = new SrgMappings();\n\t\tIntermediateMappings mappings = assertDoesNotThrow(() -> format.parse(mappingsText));\n\t\tassertInheritMap(mappings);\n\t}\n\n\t@Test\n\tvoid testSrgWithCleanName() {\n\t\tString mappingsText = \"\"\"\n\t\t\t\tCL: test/Greetings rename/Hello\n\t\t\t\tFD: test/Greetings/oldField rename/Hello/newField\n\t\t\t\tMD: test/Greetings/say ()V rename/Hello/speak\"\"\";\n\t\tMappingFileFormat format = new SrgMappings();\n\t\tIntermediateMappings mappings = assertDoesNotThrow(() -> format.parse(mappingsText));\n\t\tassertInheritMap(mappings);\n\t}\n\n\t@Test\n\tvoid testXSrg() {\n\t\tString mappingsText = \"\"\"\n\t\t\t\tCL: test/Greetings rename/Hello\n\t\t\t\tFD: test/Greetings/oldField Ljava/lang/String; rename/Hello/newField Ljava/lang/String;\n\t\t\t\tMD: test/Greetings/say ()V rename/Hello/speak\"\"\";\n\t\tMappingFileFormat format = new SrgMappings();\n\t\tIntermediateMappings mappings = assertDoesNotThrow(() -> format.parse(mappingsText));\n\t\tassertInheritMap(mappings);\n\t}\n\n\t@Test\n\tvoid testSrgPackageMapping() {\n\t\tString mappingsText = \"\"\"\n\t\t\t\tPK: test rename\n\t\t\t\t\"\"\";\n\t\tMappingFileFormat format = new SrgMappings();\n\t\tIntermediateMappings mappings = assertDoesNotThrow(() -> format.parse(mappingsText));\n\t\tassertEquals(\"rename/Greetings\", mappings.getMappedClassName(\"test/Greetings\"));\n\t}\n\n\t@Test\n\tvoid testProguard() {\n\t\tString mappingsText = \"\"\"\n\t\t\t\t# Backwards format because proguard mappings are intended to be undone, not applied\n\t\t\t\trename.Hello -> test.Greetings:\n\t\t\t\t    java.lang.String newField -> oldField\n\t\t\t\t    void speak() -> say\"\"\";\n\t\tMappingFileFormat format = new ProguardMappings();\n\t\tIntermediateMappings mappings = assertDoesNotThrow(() -> format.parse(mappingsText));\n\t\tassertInheritMap(mappings);\n\t}\n\n\t@Test\n\tvoid testEnigma() {\n\t\tString mappingsText = \"\"\"\n\t\t\t\tCOMMENT foo\n\t\t\t\tCLASS test/Greetings rename/Hello\n\t\t\t\t\\tFIELD oldField newField Ljava/lang/String;\n\t\t\t\t\\tMETHOD say speak ()V\n\t\t\t\t\\tCLASS Inner RenamedInner\"\"\";\n\t\tString mappingsTextWithTrailingNewline = mappingsText + \"\\n\";\n\n\t\t// The mapped names are optional, so we should be able to parse a sample with no\n\t\t// actual target names, and get an empty result.\n\t\tString mappingsTextWithNoDestinationNames = \"\"\"\n\t\t\t\tCLASS test/Greetings\n\t\t\t\t\\tFIELD oldField Ljava/lang/String;\n\t\t\t\t\\tMETHOD say ()V\"\"\";\n\t\tMappingFileFormat format = new EnigmaMappings();\n\t\tIntermediateMappings mappings = assertDoesNotThrow(() -> format.parse(mappingsTextWithNoDestinationNames));\n\t\tassertTrue(mappings.getClasses().isEmpty());\n\t\tassertTrue(mappings.getFields().isEmpty());\n\t\tassertTrue(mappings.getMethods().isEmpty());\n\n\t\t// The format spec says there should be a trailing newline, but we'll support both cases\n\t\tmappings = assertDoesNotThrow(() -> format.parse(mappingsText));\n\t\tassertInheritMap(mappings);\n\t\tmappings = assertDoesNotThrow(() -> format.parse(mappingsTextWithTrailingNewline));\n\t\tassertInheritMap(mappings);\n\t\tassertEquals(\"rename/Hello$RenamedInner\", mappings.getMappedClassName(\"test/Greetings$Inner\"));\n\n\t\t// This is an extreme edge case of comments and newlines spread out randomly\n\t\tString sampleWithCommentsAndNewlines = \"\"\"\n\t\t\t\tCOMMENT class comment # ignored\n\t\t\t\t# also ignored\n\t\t\t\t\t\t\t\\t\n\t\t\t\tCLASS test/Greetings rename/Hello\n\t\t\t\t\\t# field comment up next\n\t\t\t\t\t\t\t\\t\n\t\t\t\t# not indented but still ignored\n\t\t\t\t\\tCOMMENT This is a comment on the field\n\t\t\t\t\t\t\t\\t\n\t\t\t\t\\tFIELD oldField newField Ljava/lang/String;\n\t\t\t\t\t\t\t\\t\n\t\t\t\t\\tMETHOD say speak ()V\n\t\t\t\t\t\t\t\\t\n\t\t\t\t\\t\\t# There are no args\n\t\t\t\t\\t\\tARG noArgsHere\"\"\";\n\t\tmappings = assertDoesNotThrow(() -> format.parse(sampleWithCommentsAndNewlines));\n\t\tassertInheritMap(mappings);\n\t}\n\n\t@Test\n\tvoid testJadx() {\n\t\tString mappingsText = \"\"\"\n\t\t\t\tc test.Greetings = Hello\n\t\t\t\tf test.Greetings.oldField:Ljava/lang/String; = newField\n\t\t\t\tm test.Greetings.say()V = speak\"\"\";\n\t\tMappingFileFormat format = new JadxMappings();\n\t\tIntermediateMappings mappings = assertDoesNotThrow(() -> format.parse(mappingsText));\n\n\t\t// Cannot use same 'assertInheritMap(...)' because Jadx format doesn't allow package renaming\n\t\tassertEquals(\"test/Hello\", mappings.getMappedClassName(\"test/Greetings\"));\n\t\tassertEquals(\"newField\", mappings.getMappedFieldName(\"test/Greetings\", \"oldField\", \"Ljava/lang/String;\"));\n\t\tassertEquals(\"speak\", mappings.getMappedMethodName(\"test/Greetings\", \"say\", \"()V\"));\n\t}\n\n\t/**\n\t * @param mappings\n\t * \t\tMappings to check.\n\t */\n\tprivate void assertInheritMap(Mappings mappings) {\n\t\tassertEquals(\"rename/Hello\", mappings.getMappedClassName(\"test/Greetings\"));\n\t\tassertEquals(\"newField\", mappings.getMappedFieldName(\"test/Greetings\", \"oldField\", \"Ljava/lang/String;\"));\n\t\tassertEquals(\"speak\", mappings.getMappedMethodName(\"test/Greetings\", \"say\", \"()V\"));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/mapping/format/MappingIntermediateTest.java",
    "content": "package software.coley.recaf.services.mapping.format;\n\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.services.mapping.MappingsAdapter;\nimport software.coley.recaf.test.TestBase;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests {@link MappingFileFormat} implementation support for importing from intermediate mappings.\n */\npublic class MappingIntermediateTest extends TestBase {\n\t@Test\n\tvoid testMapFromIntermediate() {\n\t\t// Setup base mappings\n\t\tMappingsAdapter adapter = new MappingsAdapter(true, true);\n\t\tString oldClassName = \"Foo\";\n\t\tString newClassName = \"Bar\";\n\t\tString oldMethodName = \"say\";\n\t\tString newMethodName = \"speak\";\n\t\tString oldFieldName = \"syntax\";\n\t\tString newFieldName = \"pattern\";\n\t\tString methodDesc = \"(Ljava/lang/String;)V\";\n\t\tString fieldDesc = \"Ljava/lang/String;\";\n\t\tadapter.addClass(oldClassName, newClassName);\n\t\tadapter.addField(oldClassName, oldFieldName, fieldDesc, newFieldName);\n\t\tadapter.addMethod(oldClassName, oldMethodName, methodDesc, newMethodName);\n\n\t\t// Assert registered mapping types can import from the intermediate\n\t\tMappingFormatManager formatManager = recaf.get(MappingFormatManager.class);\n\t\tassertTrue(formatManager.getMappingFileFormats().size() > 1);\n\t\tfor (String formatName : formatManager.getMappingFileFormats()) {\n\t\t\tMappingFileFormat format = formatManager.createFormatInstance(formatName);\n\t\t\tassertNotNull(format, \"Could not get format: \" + formatName);\n\t\t\tSystem.out.println(\"Intermediate -> \" + format.implementationName());\n\n\t\t\t// Export and print\n\t\t\tif (format.supportsExportText())\n\t\t\t\tSystem.out.println(assertDoesNotThrow(() -> format.exportText(adapter)));\n\t\t\telse\n\t\t\t\tSystem.out.println(\"Mappings does not support text export: \" + format.implementationName() + \"\\n\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/mapping/gen/MappingGeneratorTest.java",
    "content": "package software.coley.recaf.services.mapping.gen;\n\nimport jakarta.annotation.Nonnull;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.Opcodes;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.StubClassInfo;\nimport software.coley.recaf.info.StubFieldMember;\nimport software.coley.recaf.info.StubMethodMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.mapping.aggregate.AggregatedMappings;\nimport software.coley.recaf.services.mapping.data.FieldMapping;\nimport software.coley.recaf.services.mapping.data.MethodMapping;\nimport software.coley.recaf.services.mapping.gen.filter.ExcludeClassesFilter;\nimport software.coley.recaf.services.mapping.gen.filter.ExcludeExistingMappedFilter;\nimport software.coley.recaf.services.mapping.gen.filter.ExcludeModifiersNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.ExcludeNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeClassesFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeKeywordNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeLongNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeModifiersNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeNonAsciiNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeNonJavaIdentifierNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeWhitespaceNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.NameGeneratorFilter;\nimport software.coley.recaf.services.mapping.gen.naming.NameGenerator;\nimport software.coley.recaf.services.search.match.StringPredicate;\nimport software.coley.recaf.services.search.match.StringPredicateProvider;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.AccessibleFields;\nimport software.coley.recaf.test.dummy.AccessibleMethods;\nimport software.coley.recaf.test.dummy.AccessibleMethodsChild;\nimport software.coley.recaf.test.dummy.StringConsumer;\nimport software.coley.recaf.test.dummy.StringConsumerUser;\nimport software.coley.recaf.util.EscapeUtil;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link MappingGenerator}\n */\npublic class MappingGeneratorTest extends TestBase {\n\tstatic Workspace workspace;\n\tstatic WorkspaceResource resource;\n\tstatic NameGenerator nameGenerator;\n\tstatic InheritanceGraph inheritanceGraph;\n\tstatic MappingGenerator mappingGenerator;\n\tstatic StringPredicateProvider strMatchProvider;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\tnameGenerator = new NameGenerator() {\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic String mapClass(@Nonnull ClassInfo info) {\n\t\t\t\treturn \"mapped/\" + info.getName();\n\t\t\t}\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic String mapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) {\n\t\t\t\treturn \"mapped\" + StringUtil.uppercaseFirstChar(field.getName());\n\t\t\t}\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic String mapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) {\n\t\t\t\treturn \"mapped\" + StringUtil.uppercaseFirstChar(method.getName());\n\t\t\t}\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic String mapVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) {\n\t\t\t\treturn \"mapped\" + StringUtil.uppercaseFirstChar(variable.getName());\n\t\t\t}\n\t\t};\n\t\tworkspace = TestClassUtils.fromBundle(TestClassUtils.fromClasses(\n\t\t\t\tAccessibleFields.class,\n\t\t\t\tAccessibleMethods.class,\n\t\t\t\tAccessibleMethodsChild.class,\n\t\t\t\tStringConsumer.class,\n\t\t\t\tStringConsumerUser.class\n\t\t));\n\t\tresource = workspace.getPrimaryResource();\n\t\tworkspaceManager.setCurrent(workspace);\n\t\tinheritanceGraph = recaf.get(InheritanceGraphService.class).getCurrentWorkspaceInheritanceGraph();\n\t\tmappingGenerator = recaf.get(MappingGenerator.class);\n\t\tstrMatchProvider = recaf.get(StringPredicateProvider.class);\n\t}\n\n\t@Test\n\tvoid testGeneral() {\n\t\t// Apply and assert no unexpected values exist\n\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, null);\n\n\t\t// Should not generate names for internal classes\n\t\tassertNull(mappings.getMappedClassName(\"java/lang/Object\"));\n\t\tassertNull(mappings.getMappedClassName(\"java/lang/enum\"));\n\n\t\t// Should not generate names for constructors/override/library methods\n\t\t//  - but still generate names for members\n\t\tString className = AccessibleFields.class.getName().replace('.', '/');\n\t\tassertNull(mappings.getMappedMethodName(className, \"hashCode\", \"()I\"));\n\t\tassertNull(mappings.getMappedMethodName(className, \"<init>>\", \"()V\"));\n\t\tassertNotNull(mappings.getMappedFieldName(className, \"CONSTANT_FIELD\", \"I\"));\n\t\tassertNotNull(mappings.getMappedFieldName(className, \"privateFinalField\", \"I\"));\n\t\tassertNotNull(mappings.getMappedFieldName(className, \"protectedField\", \"I\"));\n\t\tassertNotNull(mappings.getMappedFieldName(className, \"publicField\", \"I\"));\n\t\tassertNotNull(mappings.getMappedFieldName(className, \"packageField\", \"I\"));\n\t\tclassName = StringConsumerUser.class.getName().replace('.', '/');\n\t\tassertNotNull(mappings.getMappedVariableName(className, \"main\", \"([Ljava/lang/String;)V\", \"args\", \"[Ljava/lang/String;\", 0));\n\t}\n\n\t@Nested\n\tclass Filters {\n\t\t@Test\n\t\tvoid testDefaultMapAll() {\n\t\t\t// Empty filter with default to 'true' for mapping\n\t\t\t//  - All classes/fields/methods should be renamed (except <init>/<clinit> and library methods)\n\t\t\tNameGeneratorFilter filter = new NameGeneratorFilter(null, true) {\n\t\t\t\t// Empty\n\t\t\t};\n\n\t\t\t// Apply and assert all items are mapped\n\t\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, filter);\n\t\t\tfor (ClassInfo info : resource.getJvmClassBundle()) {\n\t\t\t\tassertNotNull(mappings.getMappedClassName(info.getName()));\n\t\t\t\tfor (FieldMember field : info.getFields())\n\t\t\t\t\tassertNotNull(mappings.getMappedFieldName(info.getName(), field.getName(), field.getDescriptor()),\n\t\t\t\t\t\t\t\"Field not mapped: \" + info.getName() + \".\" + field.getName());\n\t\t\t\tfor (MethodMember method : info.getMethods()) {\n\t\t\t\t\tString mappedMethodName =\n\t\t\t\t\t\t\tmappings.getMappedMethodName(info.getName(), method.getName(), method.getDescriptor());\n\t\t\t\t\tif (method.getName().startsWith(\"<\"))\n\t\t\t\t\t\tassertNull(mappedMethodName, \"Constructor/static-init had generated mappings\");\n\t\t\t\t\telse if (inheritanceGraph.getVertex(info.getName())\n\t\t\t\t\t\t\t.isLibraryMethod(method.getName(), method.getDescriptor()))\n\t\t\t\t\t\tassertNull(mappedMethodName, \"Library method had generated mappings\");\n\t\t\t\t\telse {\n\t\t\t\t\t\tassertNotNull(mappedMethodName, \"Method not mapped: \" + info.getName() + \".\" + method.getName());\n\t\t\t\t\t\tfor (LocalVariable variable : method.getLocalVariables()) {\n\t\t\t\t\t\t\t// Skip asserts for \"this\" local variable, we want to keep that one.\n\t\t\t\t\t\t\tif (variable.getIndex() == 0 && variable.getName().equals(\"this\"))\n\t\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\t\tString mappedVariableName = mappings.getMappedVariableName(info.getName(),\n\t\t\t\t\t\t\t\t\tmethod.getName(), method.getDescriptor(), variable.getName(),\n\t\t\t\t\t\t\t\t\tvariable.getDescriptor(), variable.getIndex());\n\t\t\t\t\t\t\tassertNotNull(mappedMethodName, \"Method variable not mapped: \" + info.getName() + \".\"\n\t\t\t\t\t\t\t\t\t+ method.getName() + \" - \" + variable.getName());\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Test\n\t\tvoid testDefaultMapNothing() {\n\t\t\t// Empty filter with default to 'false' for mapping\n\t\t\t//  - Nothing should generate\n\t\t\tNameGeneratorFilter filter = new NameGeneratorFilter(null, false) {\n\t\t\t\t// Empty\n\t\t\t};\n\n\t\t\t// Apply and assert nothing was generated\n\t\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, filter);\n\t\t\tIntermediateMappings intermediate = mappings.exportIntermediate();\n\t\t\tassertEquals(0, intermediate.getClasses().size());\n\t\t\tassertEquals(0, intermediate.getFields().size());\n\t\t\tassertEquals(0, intermediate.getMethods().size());\n\t\t\tassertEquals(0, intermediate.getVariables().size());\n\t\t}\n\n\t\t@Test\n\t\tvoid testExcludeClassNameFilterWithInheritance() {\n\t\t\t// Filter to exclude classes by name and by extension their declared members\n\t\t\t//  - Classes extending them with overridden methods, the overrides should not be remapped\n\t\t\t//\n\t\t\t// We make this filter so that it will target 'AccessibleMethods' which is the base type.\n\t\t\t// The child type 'AccessibleMethodsChild' will not be matched by this filter.\n\t\t\t// The default action of naming this not affect the methods declared in the child class since\n\t\t\t// those methods are overrides of an affected class.\n\t\t\tExcludeClassesFilter filter =\n\t\t\t\t\tnew ExcludeClassesFilter(null, strMatchProvider.newEndsWithPredicate(\"AccessibleMethods\"));\n\n\t\t\t// Apply and assert all items are mapped except the base classes types\n\t\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, filter);\n\t\t\tfor (ClassInfo info : resource.getJvmClassBundle()) {\n\t\t\t\tString mappedClass = mappings.getMappedClassName(info.getName());\n\n\t\t\t\t// Class should not be renamed if it matches the exclusion filter\n\t\t\t\tboolean isTargetExclude = info.getName().endsWith(\"AccessibleMethods\");\n\t\t\t\tboolean isSuperTargetExclude = info.getSuperName().endsWith(\"AccessibleMethods\");\n\t\t\t\tif (isTargetExclude)\n\t\t\t\t\tassertNull(mappedClass);\n\t\t\t\telse\n\t\t\t\t\tassertNotNull(mappedClass);\n\n\t\t\t\t// Fields in classes should be remapped if the class does not match the exclusion filter\n\t\t\t\tfor (FieldMember field : info.getFields()) {\n\t\t\t\t\tString mappedField = mappings.getMappedFieldName(info.getName(), field.getName(), field.getDescriptor());\n\t\t\t\t\tif (isTargetExclude)\n\t\t\t\t\t\tassertNull(mappedField,\n\t\t\t\t\t\t\t\t\"Excluded class has field mapped: \" + info.getName() + \".\" + field.getName());\n\t\t\t\t\telse\n\t\t\t\t\t\tassertNotNull(mappedField,\n\t\t\t\t\t\t\t\t\"Field not mapped: \" + info.getName() + \".\" + field.getName());\n\t\t\t\t}\n\n\t\t\t\t// Methods in classes should be remapped if the class does not match the exclusion filter\n\t\t\t\t//  - methods defined in these classes cannot be renamed in child classes even if the child class\n\t\t\t\t//    does not match the exclusion filter\n\t\t\t\tfor (MethodMember method : info.getMethods()) {\n\t\t\t\t\tString mappedMethod =\n\t\t\t\t\t\t\tmappings.getMappedMethodName(info.getName(), method.getName(), method.getDescriptor());\n\t\t\t\t\tif (method.getName().startsWith(\"<\"))\n\t\t\t\t\t\tassertNull(mappedMethod, \"Constructor/static-init had generated mappings\");\n\t\t\t\t\telse if (inheritanceGraph.getVertex(info.getName()).isLibraryMethod(method.getName(), method.getDescriptor()))\n\t\t\t\t\t\tassertNull(mappedMethod, \"Library method had generated mappings\");\n\t\t\t\t\telse if (isTargetExclude)\n\t\t\t\t\t\tassertNull(mappedMethod,\n\t\t\t\t\t\t\t\t\"Excluded class has method mapped: \" + info.getName() + \".\" + method.getName());\n\t\t\t\t\telse if (isSuperTargetExclude &&\n\t\t\t\t\t\t\tresource.getJvmClassBundle().get(info.getSuperName())\n\t\t\t\t\t\t\t\t\t.getDeclaredMethod(method.getName(), method.getDescriptor()) != null)\n\t\t\t\t\t\tassertNull(mappedMethod,\n\t\t\t\t\t\t\t\t\"Child of excluded class has method mapped: \" + info.getName() + \".\" + method.getName());\n\t\t\t\t\telse\n\t\t\t\t\t\tassertNotNull(mappedMethod, \"Method not mapped: \" + info.getName() + \".\" + method.getName());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Test\n\t\tvoid testExcludeModifiersOnAll() {\n\t\t\t// Filter to exclude anything private, protected, and public, leaving only package-private items\n\t\t\tExcludeModifiersNameFilter filter =\n\t\t\t\t\tnew ExcludeModifiersNameFilter(null, List.of(Opcodes.ACC_PRIVATE, Opcodes.ACC_PROTECTED, Opcodes.ACC_PUBLIC), true, true, true);\n\n\t\t\t// Generate mappings\n\t\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, filter);\n\n\t\t\t// There is 1 package-private field in our workspace\n\t\t\t//  - 'packageField' in 'AccessibleFields'\n\t\t\t// There are only 2 package-private method in our workspace:\n\t\t\t//  - 'packageMethod' in 'AccessibleMethods'\n\t\t\t//  - 'packageMethod' in 'AccessibleMethodsChild'\n\t\t\tIntermediateMappings intermediate = mappings.exportIntermediate();\n\t\t\tassertEquals(0, intermediate.getClasses().size());\n\t\t\tassertEquals(1, intermediate.getFields().size());\n\t\t\tassertEquals(2, intermediate.getMethods().size());\n\n\t\t\t// None of the methods included for mapping have any variables\n\t\t\tassertTrue(intermediate.getVariables().isEmpty());\n\t\t}\n\n\t\t@Test\n\t\tvoid testExcludeNotTargetingClassYieldsAllClassesMapped() {\n\t\t\t// Generic exception filter that targets fields/methods.\n\t\t\t//  - The classes are not targeted, and since this is an exclude this makes the default action map\n\t\t\t//  - Thus all classes should get mapped\n\t\t\tExcludeModifiersNameFilter filter =\n\t\t\t\t\tnew ExcludeModifiersNameFilter(null, List.of(Opcodes.ACC_PRIVATE), false, true, true);\n\n\t\t\t// Generate mappings and assert all classes were mapped.\n\t\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, filter);\n\t\t\tIntermediateMappings intermediate = mappings.exportIntermediate();\n\t\t\tassertEquals(5, intermediate.getClasses().size());\n\t\t}\n\n\t\t@Test\n\t\tvoid testExcludeNameFilter() {\n\t\t\t// Filter to exclude:\n\t\t\t// - Any class with 'Accessible' in the name\n\t\t\t// - Any field with 'Field' in the name\n\t\t\t// - Any method with 'Method' in the name\n\t\t\t// - Any variable with 'args' in the name\n\t\t\t// Each filter is independent, so if a class is named 'AccessibleThing' the class won't be renamed,\n\t\t\t// but its fields/methods/variables will still be renamed as long as their names do not contain the excluded text.\n\t\t\tExcludeNameFilter filter = new ExcludeNameFilter(null,\n\t\t\t\t\tstrMatchProvider.newContainsPredicate(\"Accessible\"), // Class filter\n\t\t\t\t\tstrMatchProvider.newContainsPredicate(\"Field\"), // Field filter\n\t\t\t\t\tstrMatchProvider.newContainsPredicate(\"Method\"), // Method filter\n\t\t\t\t\tstrMatchProvider.newContainsPredicate(\"args\") // Variable filter\n\t\t\t);\n\n\t\t\t// Generate mappings\n\t\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, filter);\n\t\t\tIntermediateMappings intermediate = mappings.exportIntermediate();\n\n\t\t\t// Only StringConsumer and StringConsumerUser should be mapped since they do not have 'Accessible' in their name.\n\t\t\tassertEquals(2, intermediate.getClasses().size());\n\n\t\t\t// Only AccessibleFields has fields, but only the constant field will match since it is 'FIELD' in capital letters.\n\t\t\tMap<String, List<FieldMapping>> classesWithMappedFields = intermediate.getFields();\n\t\t\tList<FieldMapping> fieldMappings = classesWithMappedFields.get(AccessibleFields.class.getName().replace('.', '/'));\n\t\t\tassertEquals(1, classesWithMappedFields.size());\n\t\t\tassertEquals(1, fieldMappings.size());\n\t\t\tassertEquals(\"CONSTANT_FIELD\", fieldMappings.getFirst().getOldName());\n\n\t\t\t// Only StringConsumer and StringConsumerUser will have mapped methods.\n\t\t\t// The AccessibleMethods and AccessibleMethodsChild classes only have methods with 'Method' in the name, so they will be excluded.\n\t\t\tMap<String, List<MethodMapping>> classesWithMappedMethods = intermediate.getMethods();\n\t\t\tassertEquals(2, classesWithMappedMethods.size());\n\t\t\tList<MethodMapping> consumerMethodMappings = classesWithMappedMethods.get(StringConsumer.class.getName().replace('.', '/'));\n\t\t\tList<MethodMapping> consumerUserMethodMappings = classesWithMappedMethods.get(StringConsumerUser.class.getName().replace('.', '/'));\n\t\t\tassertEquals(1, consumerMethodMappings.size());\n\t\t\tassertEquals(\"accept\", consumerMethodMappings.getFirst().getOldName());\n\t\t\tassertEquals(1, consumerUserMethodMappings.size());\n\t\t\tassertEquals(\"main\", consumerUserMethodMappings.getFirst().getOldName());\n\n\t\t\t// Nothing\n\t\t\tassertEquals(0, intermediate.getVariables().size());\n\t\t}\n\n\t\t@Test\n\t\t@SuppressWarnings(\"DataFlowIssue\")\n\t\tvoid testExcludeExistingMappedFilter() {\n\t\t\t// Setup dummy aggregate mappings with some existing mappings to test against.\n\t\t\t// Normally these would be filled with user mappings when using the UI.\n\t\t\tAggregatedMappings aggregate = new AggregatedMappings(workspace);\n\t\t\taggregate.addClass(\"com/example/AccessibleFields\", \"mapped/MappedFields\");\n\t\t\taggregate.addField(\"com/example/AccessibleFields\", \"I\", \"CONSTANT_FIELD\", \"MAPPED_FIELD\");\n\t\t\taggregate.addClass(\"com/example/AccessibleMethods\", \"mapped/MappedMethods\");\n\t\t\taggregate.addMethod(\"com/example/AccessibleMethods\", \"()V\", \"publicMethod\", \"mappedMethod\");\n\n\t\t\t// Now verify that if we feed it names of the mapped class and members, that the filter will exclude them from being mapped again.\n\t\t\tExcludeExistingMappedFilter filter = new ExcludeExistingMappedFilter(null, aggregate);\n\t\t\tStubClassInfo preFieldsClass = new StubClassInfo(\"com/example/AccessibleFields\",\n\t\t\t\t\tList.of(new StubFieldMember(\"CONSTANT_FIELD\", \"I\", 0)), List.of());\n\t\t\tStubClassInfo preMethodsClass = new StubClassInfo(\"com/example/AccessibleMethods\",\n\t\t\t\t\tList.of(), List.of(new StubMethodMember(\"publicMethod\", \"()V\", 0)));\n\t\t\tStubClassInfo postFieldsClass = new StubClassInfo(\"mapped/MappedFields\",\n\t\t\t\t\tList.of(new StubFieldMember(\"MAPPED_FIELD\", \"I\", 0)), List.of());\n\t\t\tStubClassInfo postMethodsClass = new StubClassInfo(\"mapped/MappedMethods\",\n\t\t\t\t\tList.of(), List.of(new StubMethodMember(\"mappedMethod\", \"()V\", 0)));\n\n\t\t\t// Original names can be mapped because they are not the resulting names in the aggregate.\n\t\t\tassertTrue(filter.shouldMapClass(preFieldsClass));\n\t\t\tassertTrue(filter.shouldMapClass(preMethodsClass));\n\n\t\t\t// Same idea for members, the original names can be mapped.\n\t\t\tassertTrue(filter.shouldMapField(preFieldsClass, preFieldsClass.getFirstDeclaredFieldByName(\"CONSTANT_FIELD\")));\n\t\t\tassertTrue(filter.shouldMapMethod(preMethodsClass, preMethodsClass.getFirstDeclaredMethodByName(\"publicMethod\")));\n\n\t\t\t// Classes that are clearly mapped as a result of a prior operation (thus appearing in the aggregate) should not be mapped again.\n\t\t\tassertFalse(filter.shouldMapClass(postFieldsClass));\n\t\t\tassertFalse(filter.shouldMapClass(postMethodsClass));\n\n\t\t\t// Same idea again for members, the mapped names should not be mapped again.\n\t\t\tassertFalse(filter.shouldMapField(postFieldsClass, postFieldsClass.getFirstDeclaredFieldByName(\"MAPPED_FIELD\")));\n\t\t\tassertFalse(filter.shouldMapMethod(preMethodsClass, postMethodsClass.getFirstDeclaredMethodByName(\"mappedMethod\")));\n\t\t}\n\n\t\t@Test\n\t\tvoid testIncludeModifiers() {\n\t\t\t// Filter to include only private methods\n\t\t\tIncludeModifiersNameFilter filter =\n\t\t\t\t\tnew IncludeModifiersNameFilter(null, List.of(Opcodes.ACC_PRIVATE), false, false, true);\n\n\t\t\t// Generate mappings\n\t\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, filter);\n\n\t\t\t// There is only 1 private method in our workspace:\n\t\t\t//  - 'privateMethod' in 'AccessibleMethods'\n\t\t\tIntermediateMappings intermediate = mappings.exportIntermediate();\n\t\t\tassertEquals(0, intermediate.getClasses().size());\n\t\t\tassertEquals(0, intermediate.getFields().size());\n\t\t\tassertEquals(1, intermediate.getMethods().size());\n\t\t\tList<MethodMapping> methodMappings = intermediate.getMethods().get(AccessibleMethods.class.getName().replace('.', '/'));\n\t\t\tassertEquals(1, methodMappings.size());\n\t\t\tassertEquals(\"privateMethod\", methodMappings.get(0).getOldName());\n\t\t}\n\n\t\t@Test\n\t\tvoid testIncludeNameFilter() {\n\t\t\tStringPredicate predicate = strMatchProvider.newContainsPredicate(\"AccessibleMethods\");\n\t\t\tIncludeNameFilter filter =\n\t\t\t\t\tnew IncludeNameFilter(null, predicate, predicate, predicate, predicate);\n\n\t\t\t// Generate mappings\n\t\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, filter);\n\n\t\t\t// There are only two classes that contain the given text, AccessibleMethods and AccessibleMethodsChild\n\t\t\tIntermediateMappings intermediate = mappings.exportIntermediate();\n\t\t\tassertEquals(2, intermediate.getClasses().size());\n\t\t\tassertEquals(0, intermediate.getFields().size());\n\t\t\tassertEquals(0, intermediate.getMethods().size());\n\t\t\tassertEquals(0, intermediate.getVariables().size());\n\t\t}\n\n\t\t/** Similar to {@link #testIncludeNameFilter()} but with a filter that includes members when a class is mapped */\n\t\t@Test\n\t\tvoid testIncludeClassesFilter() {\n\t\t\tIncludeClassesFilter filter =\n\t\t\t\t\tnew IncludeClassesFilter(null, strMatchProvider.newContainsPredicate(\"AccessibleMethods\"));\n\n\t\t\t// Generate mappings\n\t\t\tMappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, filter);\n\n\t\t\t// There are only two classes that contain the given text, AccessibleMethods and AccessibleMethodsChild\n\t\t\t// All of their methods should be remapped. They contain no fields.\n\t\t\tIntermediateMappings intermediate = mappings.exportIntermediate();\n\t\t\tassertEquals(2, intermediate.getClasses().size());\n\t\t\tassertEquals(0, intermediate.getFields().size());\n\t\t\tassertEquals(2, intermediate.getMethods().size());\n\t\t\tassertEquals(0, intermediate.getVariables().size()); // None of the methods have locals\n\t\t\tString key = AccessibleMethods.class.getName().replace('.', '/');\n\t\t\tString keyChild = AccessibleMethodsChild.class.getName().replace('.', '/');\n\t\t\tList<MethodMapping> methodMappings = intermediate.getMethods().get(key);\n\t\t\tList<MethodMapping> methodMappingsChild = intermediate.getMethods().get(keyChild);\n\t\t\tassertEquals(workspace.findClass(key).getValue().getMethods().size() - 1, methodMappings.size()); // -1 because <init>\n\t\t\tassertEquals(workspace.findClass(keyChild).getValue().getMethods().size() - 1, methodMappingsChild.size());\n\t\t}\n\n\t\t@Test\n\t\tvoid testIncludeKeywordNameFilter() {\n\t\t\tIncludeKeywordNameFilter filter = new IncludeKeywordNameFilter(null);\n\n\t\t\t// Classes with keywords in their name should be mapped, even if the keyword is not a standalone part of the name.\n\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(\"com/example/void\")));\n\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(\"com/void/Example\")));\n\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(\"void/example/Example\")));\n\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(\"void\")));\n\n\t\t\t// Edge case: 'package-info' and 'module-info' are not keywords, but they contain the keyword 'package' and 'module' respectively.\n\t\t\tassertFalse(filter.shouldMapClass(new StubClassInfo(\"package-info\")));\n\t\t\tassertFalse(filter.shouldMapClass(new StubClassInfo(\"module-info\")));\n\t\t}\n\n\t\t@Test\n\t\tvoid testIncludeWhitespaceNameFilter() {\n\t\t\tIncludeWhitespaceNameFilter filter = new IncludeWhitespaceNameFilter(null);\n\n\t\t\t// Classes with spaces in their name should be mapped, even if the spaces are not a standalone part of the name.\n\t\t\tSet<String> whitespaceStrings = EscapeUtil.getWhitespaceStrings();\n\t\t\tassertFalse(whitespaceStrings.isEmpty(), \"No whitespaces registered in EscapeUtil\");\n\t\t\tfor (String space : whitespaceStrings) {\n\t\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(\"com/example/_\".replace(\"_\", space))));\n\t\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(\"com/_/Example\".replace(\"_\", space))));\n\t\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(\"_/example/Example\".replace(\"_\", space))));\n\t\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(\"_\".replace(\"_\", space))));\n\t\t\t}\n\t\t}\n\n\t\t@Test\n\t\t@SuppressWarnings(\"UnnecessaryUnicodeEscape\")\n\t\tvoid testIncludeNonAsciiNameFilter() {\n\t\t\tIncludeNonAsciiNameFilter filter = new IncludeNonAsciiNameFilter(null);\n\n\t\t\t// Base case, a normal Java identifier should not be mapped.\n\t\t    assertFalse(filter.shouldMapClass(new StubClassInfo(\"com/example/Example\")));\n\n\t\t\t// A class with a non-ASCII character in the name should be mapped.\n\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(\"com/example/Exämple\")));\n\n\t\t\t// Big block of Unicode characters, should be mapped.\n\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(\"\\u062a\\u0634\\u064a\\u0632\\u0020\\u0628\\u0631\\u062c\\u0631\")));\n\n\t\t\t// Technically in the ASCII set, but still non-ASCII in the sense of Java identifiers, should be mapped.\n\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(\"\\0\")));\n\t\t}\n\n\t\t@Test\n\t\tvoid testIncludeNonJavaIdentifierNameFilter() {\n\t\t\tIncludeNonJavaIdentifierNameFilter filter = new IncludeNonJavaIdentifierNameFilter(null);\n\n\t\t\t// Base case, a normal Java identifier should not be mapped.\n\t\t\tassertFalse(filter.shouldMapClass(new StubClassInfo(\"com/example/Example\")));\n\n\t\t\t// Slight change, adding a number to the front is invalid.\n\t\t\t// A number at the end is fine, but not at the start.\n\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(\"com/example/1Example\")));\n\t\t\tassertFalse(filter.shouldMapClass(new StubClassInfo(\"com/example/Example1\")));\n\n\t\t\t// Zero-width package separator is not a valid Java identifier character, should be mapped.\n\t\t\tassertFalse(filter.shouldMapClass(new StubClassInfo(\"com//Example\")));\n\n\t\t\t// A package part that starts with a number is not valid, should be mapped.\n\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(\"com/1A/Example\")));\n\t\t\tassertFalse(filter.shouldMapClass(new StubClassInfo(\"com/A1/Example\")));\n\n\t\t\t// Edge case: 'package-info' and 'module-info' are not valid Java identifiers, but they are exempt\n\t\t\t// from this filter since they have special meaning in Java.\n\t\t\tassertFalse(filter.shouldMapClass(new StubClassInfo(\"package-info\")));\n\t\t\tassertFalse(filter.shouldMapClass(new StubClassInfo(\"module-info\")));\n\t\t}\n\n\t\t@Test\n\t\tvoid testIncludeLongNameFilter() {\n\t\t\t// Anything > 100 characters\n\t\t\tIncludeLongNameFilter filter = new IncludeLongNameFilter(null, 100, true, true, true, true);\n\n\t\t\t// 10 character name -> no\n\t\t\t// 100 character name -> no\n\t\t\t// 101 character name -> yes\n\t\t\t// 110 character name -> yes\n\t\t\tString name10 = \"0123456789\";\n\t\t\tassertFalse(filter.shouldMapClass(new StubClassInfo(name10)));\n\t\t\tassertFalse(filter.shouldMapClass(new StubClassInfo(name10.repeat(10))));\n\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(name10.repeat(10) + \"0\")));\n\t\t\tassertTrue(filter.shouldMapClass(new StubClassInfo(name10.repeat(11))));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/phantom/PhantomGeneratorTest.java",
    "content": "package software.coley.recaf.services.phantom;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.compile.CompilerResult;\nimport software.coley.recaf.services.compile.JavacArguments;\nimport software.coley.recaf.services.compile.JavacArgumentsBuilder;\nimport software.coley.recaf.services.compile.JavacCompiler;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.workspace.model.EmptyWorkspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link PhantomGenerator}.\n */\npublic class PhantomGeneratorTest extends TestBase implements Opcodes {\n\tprivate static PhantomGenerator generator;\n\tprivate static JavacCompiler compiler;\n\n\t@BeforeAll\n\tstatic void setup() {\n\t\tgenerator = recaf.get(PhantomGenerator.class);\n\t\tcompiler = recaf.get(JavacCompiler.class);\n\t}\n\n\t@Test\n\tvoid test() {\n\t\t// Make a dummy ctor to point to ClassDoesNotExist.<init> and a method in InterfaceDoesNotExist.\n\t\tClassNode node = new ClassNode();\n\t\tnode.visit(V11, ACC_PUBLIC | ACC_ABSTRACT, \"Example\", null, \"ClassDoesNotExist\", new String[]{\"InterfaceDoesNotExist\"});\n\t\tMethodNode constructor = new MethodNode(ACC_PUBLIC, \"<init>\", \"()V\", null, null);\n\t\tconstructor.visitVarInsn(ALOAD, 0);\n\t\tconstructor.visitMethodInsn(INVOKESPECIAL, \"ClassDoesNotExist\", \"<init>\", \"()V\", false);\n\t\tconstructor.visitVarInsn(ALOAD, 0);\n\t\tconstructor.visitMethodInsn(INVOKEINTERFACE, \"InterfaceDoesNotExist\", \"doSomething\", \"()V\", false);\n\t\tconstructor.visitInsn(RETURN);\n\t\tconstructor.visitMaxs(1, 1);\n\t\tnode.methods.add(constructor);\n\n\t\t// Create the class\n\t\tClassWriter writer = new ClassWriter(0);\n\t\tnode.accept(writer);\n\t\tbyte[] dummy = writer.toByteArray();\n\t\tJvmClassInfo dummyInfo = new JvmClassInfoBuilder(dummy).build();\n\t\tList<JvmClassInfo> dummyWrapped = Collections.singletonList(dummyInfo);\n\n\t\t// Generate phantoms\n\t\tEmptyWorkspace workspace = EmptyWorkspace.get();\n\t\tWorkspaceResource phantoms = assertDoesNotThrow(() -> generator.createPhantomsForClasses(workspace, dummyWrapped));\n\t\tJvmClassBundle phantomBundle = phantoms.getJvmClassBundle();\n\t\tJvmClassInfo cdnePhantom = phantomBundle.get(\"ClassDoesNotExist\");\n\t\tJvmClassInfo idnePhantom = phantomBundle.get(\"InterfaceDoesNotExist\");\n\t\tassertNotNull(cdnePhantom, \"Missing phantom: ClassDoesNotExist\");\n\t\tassertNotNull(idnePhantom, \"Missing phantom: InterfaceDoesNotExist\");\n\t\tassertFalse(cdnePhantom.hasInterfaceModifier());\n\t\tassertTrue(idnePhantom.hasInterfaceModifier());\n\t\tMethodMember cdneCtor = cdnePhantom.getDeclaredMethod(\"<init>\", \"()V\");\n\t\tMethodMember idneDoSomething = idnePhantom.getDeclaredMethod(\"doSomething\", \"()V\");\n\t\tassertNotNull(cdneCtor, \"Missing phantom: ClassDoesNotExist.<init>\");\n\t\tassertNotNull(idneDoSomething, \"Missing phantom: InterfaceDoesNotExist.doSomething\");\n\n\t\t// Show compiler integration works\n\t\tString exampleSrc = \"\"\"\n\t\t\t\tpublic abstract class Example extends ClassDoesNotExist implements InterfaceDoesNotExist {\n\t\t\t\t    public Example() {\n\t\t\t\t        super();\n\t\t\t\t        doSomething();\n\t\t\t\t    }\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tJavacArguments args = new JavacArgumentsBuilder()\n\t\t\t\t.withClassName(\"Example\")\n\t\t\t\t.withClassSource(exampleSrc)\n\t\t\t\t.withVersionTarget(11)\n\t\t\t\t.build();\n\t\tCompilerResult resultWithoutPhantoms = compiler.compile(args, null, null, null);\n\t\tCompilerResult resultWithPhantoms = compiler.compile(args, null, Collections.singletonList(phantoms), null);\n\t\tassertFalse(resultWithoutPhantoms.wasSuccess(), \"Class should not compile without phantoms\");\n\t\tassertTrue(resultWithPhantoms.wasSuccess(), \"Class should compile with phantoms\");\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/plugin/PluginManagerTest.java",
    "content": "package software.coley.recaf.services.plugin;\n\nimport net.bytebuddy.ByteBuddy;\nimport net.bytebuddy.dynamic.DynamicType;\nimport net.bytebuddy.implementation.FixedValue;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.AnnotationVisitor;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.Label;\nimport org.objectweb.asm.MethodVisitor;\nimport software.coley.recaf.plugin.*;\nimport software.coley.recaf.services.plugin.discovery.DiscoveredPluginSource;\nimport software.coley.recaf.services.plugin.discovery.PluginDiscoverer;\nimport software.coley.recaf.services.plugin.zip.ZipPluginLoader;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.util.ZipCreationUtils;\nimport software.coley.recaf.util.io.ByteSource;\nimport software.coley.recaf.util.io.ByteSources;\n\nimport java.io.IOException;\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Modifier;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.objectweb.asm.Opcodes.*;\n\n/**\n * Tests for {@link PluginManager}\n */\npublic class PluginManagerTest extends TestBase {\n\tstatic PluginManager pluginManager;\n\n\t@BeforeAll\n\tstatic void setup() {\n\t\tpluginManager = recaf.get(PluginManager.class);\n\t}\n\n\t@AfterEach\n\tvoid verifyCleanSlate() {\n\t\tassertEquals(0, pluginManager.getPlugins().size(), \"Plugins still loaded after test case\");\n\t}\n\n\t@Test\n\tvoid testSingleLoadAndUnload() throws IOException {\n\t\tString id = \"test-plugin\";\n\t\tString name = id;\n\t\tString version = \"test-version\";\n\t\tString author = \"test-author\";\n\t\tString description = \"test-description\";\n\t\tString className = \"test.PluginTest\";\n\t\tDynamicType.Unloaded<Plugin> unloaded = new ByteBuddy()\n\t\t\t\t.subclass(Plugin.class)\n\t\t\t\t.name(className)\n\t\t\t\t.defineMethod(\"onEnable\", void.class, Modifier.PUBLIC)\n\t\t\t\t.intercept(FixedValue.originType())\n\t\t\t\t.defineMethod(\"onDisable\", void.class, Modifier.PUBLIC)\n\t\t\t\t.intercept(FixedValue.originType())\n\t\t\t\t.annotateType(new PluginInformationRecord(id, name, version, author, new String[0], description)).make();\n\t\tbyte[] zip = ZipCreationUtils.createZip(Map.of(\n\t\t\t\t\"test/PluginTest.class\", unloaded.getBytes(),\n\t\t\t\tZipPluginLoader.SERVICE_PATH, className.getBytes(StandardCharsets.UTF_8)\n\t\t));\n\n\t\ttry {\n\t\t\t// Load the plugin\n\t\t\tByteSource pluginSource = ByteSources.wrap(zip);\n\t\t\tPluginDiscoverer discoverer = () -> List.of(() -> pluginSource);\n\t\t\tPluginContainer<?> container = pluginManager.loadPlugins(discoverer).iterator().next();\n\n\t\t\t// Assert the information stuck\n\t\t\tPluginInfo information = container.info();\n\t\t\tassertEquals(id, information.id());\n\t\t\tassertEquals(name, information.name());\n\t\t\tassertEquals(version, information.version());\n\t\t\tassertEquals(author, information.author());\n\t\t\tassertEquals(description, information.description());\n\n\t\t\t// Assert the plugin is active\n\t\t\tassertEquals(1, pluginManager.getPlugins().size());\n\t\t\tassertSame(container, pluginManager.getPlugin(id));\n\n\t\t\t// Assert that loading the same plugin twice throws an exception, and does\n\t\t\t// not actually register a 2nd instance of the plugin.\n\t\t\tassertThrows(PluginException.class, () -> pluginManager.loadPlugins(discoverer),\n\t\t\t\t\t\"Duplicate plugin loading should fail\");\n\t\t\tassertEquals(1, pluginManager.getPlugins().size());\n\t\t\tassertSame(container, pluginManager.getPlugin(id));\n\n\t\t\t// Now unload it\n\t\t\tpluginManager.unloaderFor(id).commit();\n\n\t\t\t// Assert the plugin is no longer active\n\t\t\tassertEquals(0, pluginManager.getPlugins().size());\n\t\t\tassertNull(pluginManager.getPlugin(id));\n\t\t} catch (PluginException ex) {\n\t\t\tfail(\"Failed to load plugin\", ex);\n\t\t}\n\t}\n\n\t@Test\n\tvoid testDependentChain() throws IOException {\n\t\tList<DiscoveredPluginSource> sources = new ArrayList<>(3);\n\t\tfor (int i = 0; i < 3; i++) {\n\t\t\tString id = \"test-plugin-\" + i;\n\t\t\tString name = id;\n\t\t\tString version = \"test-version\";\n\t\t\tString author = \"test-author\";\n\t\t\tString description = \"test-description\";\n\t\t\tString className = \"test.PluginTest\" + i;\n\t\t\tString[] dependencies = i > 0 ? new String[]{\"test-plugin-\" + (i - 1)} : new String[0];\n\t\t\tDynamicType.Unloaded<Plugin> unloaded = new ByteBuddy()\n\t\t\t\t\t.subclass(Plugin.class)\n\t\t\t\t\t.name(className)\n\t\t\t\t\t.defineMethod(\"onEnable\", void.class, Modifier.PUBLIC)\n\t\t\t\t\t.intercept(FixedValue.originType())\n\t\t\t\t\t.defineMethod(\"onDisable\", void.class, Modifier.PUBLIC)\n\t\t\t\t\t.intercept(FixedValue.originType())\n\t\t\t\t\t.annotateType(new PluginInformationRecord(id, name, version, author, dependencies, description)).make();\n\t\t\tbyte[] zip = ZipCreationUtils.createZip(Map.of(\n\t\t\t\t\t\"test/PluginTest\" + i + \".class\", unloaded.getBytes(),\n\t\t\t\t\tZipPluginLoader.SERVICE_PATH, className.getBytes(StandardCharsets.UTF_8)\n\t\t\t));\n\t\t\tsources.add(() -> ByteSources.wrap(zip));\n\t\t}\n\n\n\t\ttry {\n\t\t\t// Load the plugin\n\t\t\tPluginDiscoverer discoverer = () -> sources;\n\t\t\tpluginManager.loadPlugins(discoverer);\n\n\t\t\t// Assert the plugins are active\n\t\t\tassertEquals(3, pluginManager.getPlugins().size());\n\n\t\t\t// Now unload them\n\t\t\tpluginManager.unloaderFor(\"test-plugin-0\").commit();\n\n\t\t\t// Assert the plugins are no longer active\n\t\t\tassertEquals(0, pluginManager.getPlugins().size());\n\t\t\tfor (int i = 0; i < 3; i++) {\n\t\t\t\tString id = \"test-plugin-\" + i;\n\t\t\t\tassertNull(pluginManager.getPlugin(id));\n\t\t\t}\n\t\t} catch (PluginException ex) {\n\t\t\tfail(\"Failed to load plugins\", ex);\n\t\t}\n\t}\n\n\t@Test\n\tvoid testPluginWithResourceLoading() throws IOException {\n\t\tString className = \"DummyPluginBody\";\n\t\tbyte[] classBytes;\n\t\t{\n\t\t\t/*\n\t\t\t@PluginInformation(id = \"dummy\", name = \"dummy\", version = \"1.0\")\n\t\t\tpublic class DummyPluginBody implements Plugin {\n\t\t\t\t@Override\n\t\t\t\tpublic void onEnable() {\n\t\t\t\t\ttry (var s = getClass().getResourceAsStream(\"file.txt\")) {\n\t\t\t\t\t\tSystem.out.println(new String(s.readAllBytes()));\n\t\t\t\t\t} catch (Exception e) {\n\t\t\t\t\t\tthrow new RuntimeException(e);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t@Override\n\t\t\t\tpublic void onDisable() {}\n\t\t\t}\n\t\t\t */\n\n\t\t\tClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);\n\t\t\tcw.visit(V22, ACC_PUBLIC | ACC_SUPER, className, null, \"java/lang/Object\", new String[]{\"software/coley/recaf/plugin/Plugin\"});\n\t\t\tAnnotationVisitor av = cw.visitAnnotation(\"Lsoftware/coley/recaf/plugin/PluginInformation;\", true);\n\t\t\tav.visit(\"id\", \"dummy\");\n\t\t\tav.visit(\"name\", \"dummy\");\n\t\t\tav.visit(\"version\", \"1.0\");\n\t\t\tav.visitEnd();\n\t\t\tMethodVisitor mv;\n\t\t\t{\n\t\t\t\tmv = cw.visitMethod(ACC_PUBLIC, \"<init>\", \"()V\", null, null);\n\t\t\t\tmv.visitCode();\n\t\t\t\tmv.visitLabel(new Label());\n\t\t\t\tmv.visitVarInsn(ALOAD, 0);\n\t\t\t\tmv.visitMethodInsn(INVOKESPECIAL, \"java/lang/Object\", \"<init>\", \"()V\", false);\n\t\t\t\tmv.visitInsn(RETURN);\n\t\t\t\tmv.visitLabel(new Label());\n\t\t\t\tmv.visitMaxs(1, 1);\n\t\t\t\tmv.visitEnd();\n\t\t\t}\n\t\t\t{\n\t\t\t\tmv = cw.visitMethod(ACC_PUBLIC, \"onEnable\", \"()V\", null, null);\n\t\t\t\tmv.visitCode();\n\t\t\t\tLabel label0 = new Label();\n\t\t\t\tLabel label1 = new Label();\n\t\t\t\tLabel label2 = new Label();\n\t\t\t\tmv.visitTryCatchBlock(label0, label1, label2, \"java/lang/Throwable\");\n\t\t\t\tLabel label3 = new Label();\n\t\t\t\tLabel label4 = new Label();\n\t\t\t\tLabel label5 = new Label();\n\t\t\t\tmv.visitTryCatchBlock(label3, label4, label5, \"java/lang/Throwable\");\n\t\t\t\tLabel label6 = new Label();\n\t\t\t\tLabel label7 = new Label();\n\t\t\t\tLabel label8 = new Label();\n\t\t\t\tmv.visitTryCatchBlock(label6, label7, label8, \"java/lang/Exception\");\n\t\t\t\tmv.visitLabel(label6);\n\t\t\t\tmv.visitLineNumber(10, label6);\n\t\t\t\tmv.visitVarInsn(ALOAD, 0);\n\t\t\t\tmv.visitMethodInsn(INVOKEVIRTUAL, \"java/lang/Object\", \"getClass\", \"()Ljava/lang/Class;\", false);\n\t\t\t\tmv.visitLdcInsn(\"file.txt\");\n\t\t\t\tmv.visitMethodInsn(INVOKEVIRTUAL, \"java/lang/Class\", \"getResourceAsStream\", \"(Ljava/lang/String;)Ljava/io/InputStream;\", false);\n\t\t\t\tmv.visitVarInsn(ASTORE, 1);\n\t\t\t\tmv.visitLabel(label0);\n\t\t\t\tmv.visitLineNumber(11, label0);\n\t\t\t\tmv.visitFieldInsn(GETSTATIC, \"java/lang/System\", \"out\", \"Ljava/io/PrintStream;\");\n\t\t\t\tmv.visitTypeInsn(NEW, \"java/lang/String\");\n\t\t\t\tmv.visitInsn(DUP);\n\t\t\t\tmv.visitVarInsn(ALOAD, 1);\n\t\t\t\tmv.visitMethodInsn(INVOKEVIRTUAL, \"java/io/InputStream\", \"readAllBytes\", \"()[B\", false);\n\t\t\t\tmv.visitMethodInsn(INVOKESPECIAL, \"java/lang/String\", \"<init>\", \"([B)V\", false);\n\t\t\t\tmv.visitMethodInsn(INVOKEVIRTUAL, \"java/io/PrintStream\", \"println\", \"(Ljava/lang/String;)V\", false);\n\t\t\t\tmv.visitLabel(label1);\n\t\t\t\tmv.visitLineNumber(12, label1);\n\t\t\t\tmv.visitVarInsn(ALOAD, 1);\n\t\t\t\tmv.visitJumpInsn(IFNULL, label7);\n\t\t\t\tmv.visitVarInsn(ALOAD, 1);\n\t\t\t\tmv.visitMethodInsn(INVOKEVIRTUAL, \"java/io/InputStream\", \"close\", \"()V\", false);\n\t\t\t\tmv.visitJumpInsn(GOTO, label7);\n\t\t\t\tmv.visitLabel(label2);\n\t\t\t\tmv.visitLineNumber(10, label2);\n\t\t\t\tmv.visitVarInsn(ASTORE, 2);\n\t\t\t\tmv.visitVarInsn(ALOAD, 1);\n\t\t\t\tLabel label9 = new Label();\n\t\t\t\tmv.visitJumpInsn(IFNULL, label9);\n\t\t\t\tmv.visitLabel(label3);\n\t\t\t\tmv.visitVarInsn(ALOAD, 1);\n\t\t\t\tmv.visitMethodInsn(INVOKEVIRTUAL, \"java/io/InputStream\", \"close\", \"()V\", false);\n\t\t\t\tmv.visitLabel(label4);\n\t\t\t\tmv.visitJumpInsn(GOTO, label9);\n\t\t\t\tmv.visitLabel(label5);\n\t\t\t\tmv.visitVarInsn(ASTORE, 3);\n\t\t\t\tmv.visitVarInsn(ALOAD, 2);\n\t\t\t\tmv.visitVarInsn(ALOAD, 3);\n\t\t\t\tmv.visitMethodInsn(INVOKEVIRTUAL, \"java/lang/Throwable\", \"addSuppressed\", \"(Ljava/lang/Throwable;)V\", false);\n\t\t\t\tmv.visitLabel(label9);\n\t\t\t\tmv.visitVarInsn(ALOAD, 2);\n\t\t\t\tmv.visitInsn(ATHROW);\n\t\t\t\tmv.visitLabel(label7);\n\t\t\t\tmv.visitLineNumber(14, label7);\n\t\t\t\tLabel label10 = new Label();\n\t\t\t\tmv.visitJumpInsn(GOTO, label10);\n\t\t\t\tmv.visitLabel(label8);\n\t\t\t\tmv.visitLineNumber(12, label8);\n\t\t\t\tmv.visitVarInsn(ASTORE, 1);\n\t\t\t\tLabel label11 = new Label();\n\t\t\t\tmv.visitLabel(label11);\n\t\t\t\tmv.visitLineNumber(13, label11);\n\t\t\t\tmv.visitTypeInsn(NEW, \"java/lang/RuntimeException\");\n\t\t\t\tmv.visitInsn(DUP);\n\t\t\t\tmv.visitVarInsn(ALOAD, 1);\n\t\t\t\tmv.visitMethodInsn(INVOKESPECIAL, \"java/lang/RuntimeException\", \"<init>\", \"(Ljava/lang/Throwable;)V\", false);\n\t\t\t\tmv.visitInsn(ATHROW);\n\t\t\t\tmv.visitLabel(label10);\n\t\t\t\tmv.visitLineNumber(15, label10);\n\t\t\t\tmv.visitInsn(RETURN);\n\t\t\t\tLabel label12 = new Label();\n\t\t\t\tmv.visitLabel(label12);\n\t\t\t\tmv.visitLocalVariable(\"s\", \"Ljava/io/InputStream;\", null, label0, label7, 1);\n\t\t\t\tmv.visitLocalVariable(\"e\", \"Ljava/lang/Exception;\", null, label11, label10, 1);\n\t\t\t\tmv.visitLocalVariable(\"this\", \"Lsoftware/coley/recaf/services/plugin/DummyPluginBody;\", null, label6, label12, 0);\n\t\t\t\tmv.visitMaxs(4, 4);\n\t\t\t\tmv.visitEnd();\n\t\t\t}\n\t\t\t{\n\t\t\t\tmv = cw.visitMethod(ACC_PUBLIC, \"onDisable\", \"()V\", null, null);\n\t\t\t\tmv.visitCode();\n\t\t\t\tmv.visitLabel(new Label());\n\t\t\t\tmv.visitInsn(RETURN);\n\t\t\t\tmv.visitLabel(new Label());\n\t\t\t\tmv.visitMaxs(0, 1);\n\t\t\t\tmv.visitEnd();\n\t\t\t}\n\t\t\tcw.visitEnd();\n\t\t\tclassBytes = cw.toByteArray();\n\t\t}\n\t\tbyte[] zip = ZipCreationUtils.createZip(Map.of(\n\t\t\t\tclassName + \".class\", classBytes,\n\t\t\t\tZipPluginLoader.SERVICE_PATH, className.getBytes(StandardCharsets.UTF_8),\n\t\t\t\t\"file.txt\", \"Dummy plugin says 'hello world'!\".getBytes(StandardCharsets.UTF_8)\n\t\t));\n\n\t\ttry {\n\t\t\t// Load the plugin\n\t\t\tByteSource pluginSource = ByteSources.wrap(zip);\n\t\t\tPluginDiscoverer discoverer = () -> List.of(() -> pluginSource);\n\t\t\tPluginContainer<?> container = pluginManager.loadPlugins(discoverer).iterator().next();\n\n\t\t\t// Now unload it\n\t\t\tpluginManager.unloaderFor(\"dummy\").commit();\n\t\t} catch (PluginException ex) {\n\t\t\tfail(\"Failed to load plugin\", ex);\n\t\t}\n\t}\n\n\t@SuppressWarnings(\"all\")\n\tprivate record PluginInformationRecord(String id, String name, String version, String author, String[] dependencies,\n\t                                       String description) implements PluginInformation {\n\t\t@Override\n\t\tpublic Class<? extends Annotation> annotationType() {\n\t\t\treturn PluginInformation.class;\n\t\t}\n\n\t\t@Override\n\t\tpublic String[] softDependencies() {\n\t\t\treturn new String[0];\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/script/JavacScriptEngineTest.java",
    "content": "package software.coley.recaf.services.script;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.services.compile.CompilerDiagnostic;\nimport software.coley.recaf.test.TestBase;\n\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link JavacScriptEngine}\n */\npublic class JavacScriptEngineTest extends TestBase {\n\tstatic JavacScriptEngine engine;\n\n\t@BeforeAll\n\tstatic void setup() {\n\t\tengine = recaf.get(JavacScriptEngine.class);\n\t}\n\n\t@Nested\n\tclass Snippet {\n\t\t@Test\n\t\tvoid testHelloWorld() {\n\t\t\tassertSuccess(\"System.out.println(\\\"hello\\\");\");\n\t\t}\n\n\t\t@Test\n\t\tvoid repeatedDefinitions() {\n\t\t\tString propertyName = \"test-xyz\";\n\n\t\t\t// set = one\n\t\t\tassertSuccess(\"System.setProperty(\\\"\" + propertyName + \"\\\", \\\"one\\\");\");\n\t\t\tassertEquals(\"one\", System.getProperty(propertyName),\n\t\t\t\t\t\"Javac script engine failed to set property: \" + propertyName);\n\n\t\t\t// set = one\n\t\t\tassertSuccess(\"System.setProperty(\\\"\" + propertyName + \"\\\", \\\"two\\\");\");\n\t\t\tassertEquals(\"two\", System.getProperty(propertyName),\n\t\t\t\t\t\"Javac script engine failed to set property correctly after script modifications: \" + propertyName);\n\n\t\t\t// set = two\n\t\t\tassertSuccess(\"System.setProperty(\\\"\" + propertyName + \"\\\", \\\"three\\\");\");\n\t\t\tassertEquals(\"three\", System.getProperty(propertyName),\n\t\t\t\t\t\"Javac script engine failed to set property correctly after script modifications: \" + propertyName);\n\t\t}\n\t}\n\n\t@Nested\n\tclass Full {\n\t\t@Test\n\t\tvoid testConstructorInjection() {\n\t\t\tassertSuccess(\"\"\"\n\t\t\t\t\t@Dependent\n\t\t\t\t\tpublic class Test implements Runnable {\n\t\t\t\t\t\tprivate final JavacCompiler compiler;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t@Inject\n\t\t\t\t\t\tpublic Test(JavacCompiler compiler) {\n\t\t\t\t\t\t\tthis.compiler = compiler;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tpublic void run() {\n\t\t\t\t\t\t\tSystem.out.println(\"hello: \" + compiler);\n\t\t\t\t\t\t\tif (compiler == null) throw new IllegalStateException();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\");\n\t\t}\n\n\t\t@Test\n\t\tvoid testFieldInjection() {\n\t\t\tassertSuccess(\"\"\"\n\t\t\t\t\t@Dependent\n\t\t\t\t\tpublic class Test implements Runnable {\n\t\t\t\t\t\t@Inject\n\t\t\t\t\t\tJavacCompiler compiler;\n\t\t\t\t\t\t\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tpublic void run() {\n\t\t\t\t\t\t\tSystem.out.println(\"hello: \" + compiler);\n\t\t\t\t\t\t\tif (compiler == null) throw new IllegalStateException();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\");\n\t\t}\n\n\t\t@Test\n\t\tvoid testInjectionWithoutStatedScope() {\n\t\t\tassertSuccess(\"\"\"\n\t\t\t\t\tpublic class Test implements Runnable {\n\t\t\t\t\t\t@Inject\n\t\t\t\t\t\tJavacCompiler compiler;\n\t\t\t\t\t\t\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tpublic void run() {\n\t\t\t\t\t\t\tSystem.out.println(\"hello: \" + compiler);\n\t\t\t\t\t\t\tif (compiler == null) throw new IllegalStateException();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\");\n\t\t}\n\n\t\t@Test\n\t\tvoid repeatedDefinitions() {\n\t\t\tString propertyName = \"test-xyz\";\n\t\t\tString template = \"\"\"\n\t\t\t\t\tpublic class Test implements Runnable {\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tpublic void run() {\n\t\t\t\t\t\t\tSystem.setProperty(\"%s\", \"%s\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\n\t\t\t// set = one\n\t\t\tassertSuccess(template.formatted(propertyName, \"one\"));\n\t\t\tassertEquals(\"one\", System.getProperty(propertyName),\n\t\t\t\t\t\"Javac script engine failed to set property: \" + propertyName);\n\n\t\t\t// set = one\n\t\t\tassertSuccess(template.formatted(propertyName, \"two\"));\n\t\t\tassertEquals(\"two\", System.getProperty(propertyName),\n\t\t\t\t\t\"Javac script engine failed to set property correctly after script modifications: \" + propertyName);\n\n\t\t\t// set = two\n\t\t\tassertSuccess(template.formatted(propertyName, \"three\"));\n\t\t\tassertEquals(\"three\", System.getProperty(propertyName),\n\t\t\t\t\t\"Javac script engine failed to set property correctly after script modifications: \" + propertyName);\n\t\t}\n\t}\n\n\tstatic void assertSuccess(String code) {\n\t\ttry {\n\t\t\tengine.run(code).thenAccept(result -> {\n\t\t\t\tfor (CompilerDiagnostic diagnostic : result.getCompileDiagnostics())\n\t\t\t\t\tfail(\"Unexpected diagnostic: \" + diagnostic);\n\n\t\t\t\tThrowable thrown = result.getRuntimeThrowable();\n\t\t\t\tif (thrown != null)\n\t\t\t\t\tfail(\"Unexpected exception at runtime\", thrown);\n\n\t\t\t\tassertTrue(result.wasSuccess());\n\t\t\t\tassertFalse(result.wasCompileFailure());\n\t\t\t\tassertFalse(result.wasRuntimeError());\n\t\t\t}).get(5, TimeUnit.SECONDS);\n\t\t} catch (InterruptedException | ExecutionException | TimeoutException ex) {\n\t\t\tfail(ex);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/search/SearchServiceTest.java",
    "content": "package software.coley.recaf.services.search;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.info.BasicTextFileInfo;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.builder.TextFileInfoBuilder;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.path.AnnotationPathNode;\nimport software.coley.recaf.path.CatchPathNode;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.InstructionPathNode;\nimport software.coley.recaf.path.LocalVariablePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.path.ThrowsPathNode;\nimport software.coley.recaf.services.search.match.NumberPredicateProvider;\nimport software.coley.recaf.services.search.match.StringPredicateProvider;\nimport software.coley.recaf.services.search.query.DeclarationQuery;\nimport software.coley.recaf.services.search.query.InstructionQuery;\nimport software.coley.recaf.services.search.query.NumberQuery;\nimport software.coley.recaf.services.search.query.Query;\nimport software.coley.recaf.services.search.query.ReferenceQuery;\nimport software.coley.recaf.services.search.query.StringQuery;\nimport software.coley.recaf.services.search.result.Result;\nimport software.coley.recaf.services.search.result.Results;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.dummy.AccessibleFields;\nimport software.coley.recaf.test.dummy.AnnotationImpl;\nimport software.coley.recaf.test.dummy.ClassWithAnnotation;\nimport software.coley.recaf.test.dummy.ClassWithExceptions;\nimport software.coley.recaf.test.dummy.HelloWorld;\nimport software.coley.recaf.test.dummy.StringConsumer;\nimport software.coley.recaf.test.dummy.TypeAnnotationImpl;\nimport software.coley.recaf.workspace.model.EmptyWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.objectweb.asm.Opcodes.BIPUSH;\nimport static software.coley.recaf.test.TestClassUtils.*;\n\n/**\n * Tests for {@link SearchService}\n */\npublic class SearchServiceTest extends TestBase {\n\tstatic NumberPredicateProvider numMatchProvider;\n\tstatic StringPredicateProvider strMatchProvider;\n\tstatic SearchService searchService;\n\tstatic Workspace classesWorkspace;\n\tstatic Workspace filesWorkspace;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\tnumMatchProvider = recaf.get(NumberPredicateProvider.class);\n\t\tstrMatchProvider = recaf.get(StringPredicateProvider.class);\n\t\tsearchService = recaf.get(SearchService.class);\n\n\t\t// Make workspace with just some classes\n\t\tclassesWorkspace = fromBundle(fromClasses(\n\t\t\t\tfromRuntimeClass(AccessibleFields.class),\n\t\t\t\tfromRuntimeClass(HelloWorld.class),\n\t\t\t\tfromRuntimeClass(StringConsumer.class)\n\t\t));\n\n\t\t// Make workspace with just some files\n\t\tBasicTextFileInfo fileHello = new TextFileInfoBuilder()\n\t\t\t\t.withName(\"hello.txt\")\n\t\t\t\t.withRawContent(\"Hello world\".getBytes(StandardCharsets.UTF_8))\n\t\t\t\t.build();\n\t\tBasicTextFileInfo fileNumbers = new TextFileInfoBuilder()\n\t\t\t\t.withName(\"numbers.txt\")\n\t\t\t\t.withRawContent(\"1\\n-1\\n0xF\\n0\\n7\".getBytes(StandardCharsets.UTF_8))\n\t\t\t\t.build();\n\t\tfilesWorkspace = fromBundle(fromFiles(fileHello, fileNumbers));\n\t}\n\n\t@Test\n\tvoid testEmpty() {\n\t\tResults results = searchService.search(EmptyWorkspace.get(), new Query() {\n\t\t\t// empty query\n\t\t});\n\t\tassertTrue(results.isEmpty(), \"No results should be found in an empty workspace\");\n\t}\n\n\t@Nested\n\tclass Jvm {\n\t\t@Test\n\t\tvoid testClassNumbers() {\n\t\t\tResults results = searchService.search(classesWorkspace, new NumberQuery(numMatchProvider.newEqualsPredicate(4)));\n\t\t\tassertEquals(5, results.size());\n\n\t\t\tresults = searchService.search(classesWorkspace, new NumberQuery(numMatchProvider.newEqualsPredicate(32)));\n\t\t\tassertEquals(0, results.size());\n\n\t\t\tresults = searchService.search(classesWorkspace, new NumberQuery(numMatchProvider.newLessThanPredicate(1)));\n\t\t\tassertEquals(5, results.size());\n\n\t\t\tresults = searchService.search(classesWorkspace, new NumberQuery(numMatchProvider.newLessThanOrEqualPredicate(1)));\n\t\t\tassertEquals(12, results.size());\n\n\t\t\tresults = searchService.search(classesWorkspace, new NumberQuery(numMatchProvider.newGreaterThanPredicate(4)));\n\t\t\tassertEquals(9, results.size());\n\n\t\t\tresults = searchService.search(classesWorkspace, new NumberQuery(numMatchProvider.newGreaterThanOrEqualPredicate(4)));\n\t\t\tassertEquals(14, results.size());\n\n\t\t\tresults = searchService.search(classesWorkspace, new NumberQuery(numMatchProvider.newNotEqualsPredicate(4)));\n\t\t\tassertEquals(26, results.size());\n\t\t}\n\n\t\t@Test\n\t\tvoid testClassStrings() {\n\t\t\tResults results = searchService.search(classesWorkspace, new StringQuery(strMatchProvider.newEqualPredicate(\"Hello world\")));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\tresults = searchService.search(classesWorkspace, new StringQuery(strMatchProvider.newContainsPredicate(\":\")));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\tresults = searchService.search(classesWorkspace, new StringQuery(strMatchProvider.newStartsWithPredicate(\"Hello\")));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\tresults = searchService.search(classesWorkspace, new StringQuery(strMatchProvider.newEndsWithPredicate(\"world\")));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\tresults = searchService.search(classesWorkspace, new StringQuery(strMatchProvider.newPartialRegexPredicate(\"\\\\w+\\\\s\\\\w+\")));\n\t\t\tassertEquals(1, results.size());\n\t\t}\n\n\t\t@Test\n\t\tvoid testInsnSearch() {\n\t\t\tResults results = searchService.search(classesWorkspace, new InstructionQuery(List.of(\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"getstatic java/lang/System.out Ljava/io/PrintStream;\"),\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"ldc \\\"Hello world\\\"\"),\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V\")\n\t\t\t)));\n\t\t\tassertEquals(1, results.size());\n\t\t}\n\n\t\t@Test\n\t\tvoid testFieldPath() {\n\t\t\t// Used only in constant-value attribute for field 'CONSTANT_FIELD'\n\t\t\tResults results = searchService.search(classesWorkspace, new NumberQuery(numMatchProvider.newEqualsPredicate(16)));\n\t\t\tfor (Result<?> result : results) {\n\t\t\t\tClassMember declaredMember = result.getPath().getValueOfType(ClassMember.class);\n\t\t\t\tif (declaredMember != null) {\n\t\t\t\t\tassertTrue(declaredMember.isField());\n\t\t\t\t\tassertEquals(\"CONSTANT_FIELD\", declaredMember.getName());\n\t\t\t\t\tassertEquals(\"I\", declaredMember.getDescriptor());\n\t\t\t\t} else {\n\t\t\t\t\tfail(\"Value[16] found in not CONSTANT_FIELD path\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Test\n\t\tvoid testMethodPath() {\n\t\t\t// 31 is used in generated hashCode() implementations\n\t\t\tResults results = searchService.search(classesWorkspace, new NumberQuery(numMatchProvider.newEqualsPredicate(31)));\n\t\t\tfor (Result<?> result : results) {\n\t\t\t\tif (result.getPath() instanceof InstructionPathNode instructionPath) {\n\t\t\t\t\tassertEquals(BIPUSH, instructionPath.getValue().getOpcode());\n\n\t\t\t\t\tClassMemberPathNode memberPath = instructionPath.getPathOfType(ClassMember.class);\n\t\t\t\t\tassertNotNull(memberPath);\n\t\t\t\t\tClassMember declaredMember = memberPath.getValue();\n\t\t\t\t\tassertTrue(declaredMember.isMethod());\n\t\t\t\t\tassertEquals(\"hashCode\", declaredMember.getName());\n\t\t\t\t\tassertEquals(\"()I\", declaredMember.getDescriptor());\n\t\t\t\t} else {\n\t\t\t\t\tfail(\"Value[31] found in not hashCode() path\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Test\n\t\tvoid testAnnotationStrings() throws IOException {\n\t\t\tWorkspace workspace = fromBundle(fromClasses(\n\t\t\t\t\tfromRuntimeClass(ClassWithAnnotation.class)\n\t\t\t));\n\n\t\t\tResults results = searchService.search(workspace, new StringQuery(strMatchProvider.newEqualPredicate(\"Hello\")));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\tResult<?> result = results.iterator().next();\n\t\t\tPathNode<?> path = result.getPath();\n\t\t\tif (path instanceof AnnotationPathNode annotationPath) {\n\t\t\t\tAnnotationInfo declaredAnnotation = annotationPath.getValue();\n\t\t\t\tassertTrue(declaredAnnotation.isVisible());\n\t\t\t\tassertEquals(\"L\" + AnnotationImpl.class.getName().replace('.', '/') + \";\",\n\t\t\t\t\t\tdeclaredAnnotation.getDescriptor());\n\t\t\t} else {\n\t\t\t\tfail(\"Text[arg] found not in annotation usage\");\n\t\t\t}\n\t\t}\n\n\t\t@Test\n\t\tvoid testTypeAnnotationStrings() throws IOException {\n\t\t\tWorkspace workspace = fromBundle(fromClasses(\n\t\t\t\t\tfromRuntimeClass(ClassWithAnnotation.class)\n\t\t\t));\n\n\t\t\tResults results = searchService.search(workspace, new StringQuery(strMatchProvider.newEqualPredicate(\"arg\")));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\tResult<?> result = results.iterator().next();\n\t\t\tPathNode<?> path = result.getPath();\n\t\t\tif (path instanceof AnnotationPathNode annotationPath) {\n\t\t\t\tAnnotationInfo declaredAnnotation = annotationPath.getValue();\n\t\t\t\tassertTrue(declaredAnnotation.isVisible());\n\t\t\t\tassertEquals(\"L\" + TypeAnnotationImpl.class.getName().replace('.', '/') + \";\",\n\t\t\t\t\t\tdeclaredAnnotation.getDescriptor());\n\t\t\t} else {\n\t\t\t\tfail(\"Text[Hello] found not in annotation usage\");\n\t\t\t}\n\t\t}\n\n\t\t@Test\n\t\tvoid testMemberReferenceSearchSysOut() {\n\t\t\t// References to System.out\n\t\t\tResults results = searchService.search(classesWorkspace, new ReferenceQuery(\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"java/lang/System\"),\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"out\"),\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"Ljava/io/PrintStream;\")));\n\t\t\tassertEquals(2, results.size());\n\t\t\tResults baseline = results;\n\n\t\t\t// No other references should be of PrintStream as named 'out' in the workspace\n\t\t\t// so when we omit parameters the results should be the same.\n\t\t\tresults = searchService.search(classesWorkspace, new ReferenceQuery(\n\t\t\t\t\tnull,\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"out\"),\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"Ljava/io/PrintStream;\")\n\t\t\t));\n\t\t\tassertEquals(2, results.size());\n\n\t\t\tresults = searchService.search(classesWorkspace, new ReferenceQuery(\n\t\t\t\t\tnull,\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"out\"),\n\t\t\t\t\tnull\n\t\t\t));\n\t\t\tassertEquals(2, results.size());\n\n\t\t\tresults = searchService.search(classesWorkspace, new ReferenceQuery(\n\t\t\t\t\tnull,\n\t\t\t\t\tnull,\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"Ljava/io/PrintStream;\")\n\t\t\t));\n\t\t\tassertEquals(2, results.size());\n\n\t\t\tresults = searchService.search(classesWorkspace, new ReferenceQuery(\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"java/lang/System\"),\n\t\t\t\t\tnull, null\n\t\t\t));\n\t\t\tassertEquals(2, results.size());\n\t\t}\n\n\t\t@Test\n\t\tvoid testClassReferenceToNumberFormatException() throws IOException {\n\t\t\tWorkspace workspace = fromBundle(fromClasses(\n\t\t\t\t\tfromRuntimeClass(ClassWithExceptions.class)\n\t\t\t));\n\n\t\t\tResults results = searchService.search(workspace, new ReferenceQuery(\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"java/lang/NumberFormatException\")\n\t\t\t));\n\t\t\tassertEquals(4, results.size());\n\n\t\t\t// Thrown type on method declaration\n\t\t\tSet<Result<?>> throwsMatches = results.stream()\n\t\t\t\t\t.filter(r -> r.getPath() instanceof ThrowsPathNode)\n\t\t\t\t\t.collect(Collectors.toSet());\n\t\t\tassertEquals(1, throwsMatches.size());\n\n\t\t\t// NEW instruction is used to create a NFE\n\t\t\tSet<Result<?>> insnMatches = results.stream()\n\t\t\t\t\t.filter(r -> r.getPath() instanceof InstructionPathNode)\n\t\t\t\t\t.collect(Collectors.toSet());\n\t\t\tassertEquals(1, insnMatches.size());\n\n\t\t\t// Type of catch block\n\t\t\tSet<Result<?>> catchMatches = results.stream()\n\t\t\t\t\t.filter(r -> r.getPath() instanceof CatchPathNode)\n\t\t\t\t\t.collect(Collectors.toSet());\n\t\t\tassertEquals(1, catchMatches.size());\n\n\t\t\t// Catch block has local variable\n\t\t\tSet<Result<?>> varMatches = results.stream()\n\t\t\t\t\t.filter(r -> r.getPath() instanceof LocalVariablePathNode)\n\t\t\t\t\t.collect(Collectors.toSet());\n\t\t\tassertEquals(1, varMatches.size());\n\t\t}\n\n\t\t@Test\n\t\tvoid testMemberDeclarations() {\n\t\t\tResults results = searchService.search(classesWorkspace, new DeclarationQuery(\n\t\t\t\t\tstrMatchProvider.newEndsWithPredicate(\"AccessibleFields\"),\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"CONSTANT_FIELD\"),\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"I\")\n\t\t\t));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\t// Only 1 class in the provided workspace has an equals implemented\n\t\t\tresults = searchService.search(classesWorkspace, new DeclarationQuery(\n\t\t\t\t\tnull,\n\t\t\t\t\tstrMatchProvider.newEqualPredicate(\"equals\"),\n\t\t\t\t\tnull\n\t\t\t));\n\t\t\tassertEquals(1, results.size());\n\t\t}\n\t}\n\n\t@Nested\n\tclass File {\n\t\t@Test\n\t\tvoid testFileNumbers() {\n\t\t\tResults results = searchService.search(filesWorkspace, new NumberQuery(numMatchProvider.newEqualsPredicate(0)));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\tresults = searchService.search(filesWorkspace, new NumberQuery(numMatchProvider.newEqualsPredicate(0xF)));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\tresults = searchService.search(filesWorkspace, new NumberQuery(numMatchProvider.newAnyOfPredicate(0, 0xF)));\n\t\t\tassertEquals(2, results.size()); // combination of prior two\n\n\t\t\tresults = searchService.search(filesWorkspace, new NumberQuery(numMatchProvider.newRangePredicate(0, 10)));\n\t\t\tassertEquals(3, results.size()); // should find 0, 1, and 7\n\n\t\t\tresults = searchService.search(filesWorkspace, new NumberQuery(numMatchProvider.newLessThanPredicate(0)));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\tresults = searchService.search(filesWorkspace, new NumberQuery(numMatchProvider.newLessThanOrEqualPredicate(0)));\n\t\t\tassertEquals(2, results.size());\n\n\t\t\tresults = searchService.search(filesWorkspace, new NumberQuery(numMatchProvider.newGreaterThanPredicate(0)));\n\t\t\tassertEquals(3, results.size());\n\n\t\t\tresults = searchService.search(filesWorkspace, new NumberQuery(numMatchProvider.newGreaterThanOrEqualPredicate(0)));\n\t\t\tassertEquals(4, results.size());\n\n\t\t\tresults = searchService.search(filesWorkspace, new NumberQuery(numMatchProvider.newNotEqualsPredicate(0)));\n\t\t\tassertEquals(4, results.size());\n\t\t}\n\n\t\t@Test\n\t\tvoid testFileStrings() {\n\t\t\tResults results = searchService.search(filesWorkspace, new StringQuery(strMatchProvider.newEqualPredicate(\"Hello world\")));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\tresults = searchService.search(filesWorkspace, new StringQuery(strMatchProvider.newContainsPredicate(\"-\")));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\tresults = searchService.search(filesWorkspace, new StringQuery(strMatchProvider.newStartsWithPredicate(\"Hello\")));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\tresults = searchService.search(filesWorkspace, new StringQuery(strMatchProvider.newEndsWithPredicate(\"world\")));\n\t\t\tassertEquals(1, results.size());\n\n\t\t\tresults = searchService.search(filesWorkspace, new StringQuery(strMatchProvider.newPartialRegexPredicate(\"\\\\w+\\\\s\\\\w+\")));\n\t\t\tassertEquals(1, results.size());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/source/AstServiceTest.java",
    "content": "package software.coley.recaf.services.source;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.Type;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.DirectoryPathNode;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.AccessibleFields;\nimport software.coley.recaf.test.dummy.AnnotationImpl;\nimport software.coley.recaf.test.dummy.ClassWithConstructor;\nimport software.coley.recaf.test.dummy.ClassWithExceptions;\nimport software.coley.recaf.test.dummy.ClassWithMethodReference;\nimport software.coley.recaf.test.dummy.ClassWithMultipleMethods;\nimport software.coley.recaf.test.dummy.ClassWithStaticInit;\nimport software.coley.recaf.test.dummy.DummyEnum;\nimport software.coley.recaf.test.dummy.HelloWorld;\nimport software.coley.recaf.test.dummy.OverlapClassAB;\nimport software.coley.recaf.test.dummy.OverlapInterfaceA;\nimport software.coley.recaf.test.dummy.OverlapInterfaceB;\nimport software.coley.recaf.test.dummy.StringList;\nimport software.coley.recaf.test.dummy.StringListUser;\nimport software.coley.recaf.test.dummy.StringSupplier;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle;\nimport software.coley.sourcesolver.Parser;\nimport software.coley.sourcesolver.model.CompilationUnitModel;\n\nimport java.io.IOException;\nimport java.util.function.Consumer;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link AstService}\n */\n@SuppressWarnings(\"DataFlowIssue\")\npublic class AstServiceTest extends TestBase {\n\tstatic JvmClassInfo thisClass;\n\tstatic AstService service;\n\tstatic Parser parser;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\tthisClass = TestClassUtils.fromRuntimeClass(AstServiceTest.class);\n\t\tBasicJvmClassBundle bundle = TestClassUtils.fromClasses(\n\t\t\t\tDummyEnum.class,\n\t\t\t\tStringSupplier.class,\n\t\t\t\tClassWithConstructor.class,\n\t\t\t\tClassWithExceptions.class,\n\t\t\t\tClassWithStaticInit.class,\n\t\t\t\tClassWithMultipleMethods.class,\n\t\t\t\tClassWithMethodReference.class,\n\t\t\t\tAccessibleFields.class,\n\t\t\t\tOverlapClassAB.class,\n\t\t\t\tOverlapInterfaceA.class,\n\t\t\t\tOverlapInterfaceB.class,\n\t\t\t\tStringList.class,\n\t\t\t\tStringListUser.class,\n\t\t\t\tHelloWorld.class,\n\t\t\t\tTypes.class,\n\t\t\t\tType.class,\n\t\t\t\tAnnotationImpl.class\n\t\t);\n\t\tWorkspace workspace = TestClassUtils.fromBundle(bundle);\n\t\tworkspaceManager.setCurrent(workspace);\n\t\tservice = recaf.get(AstService.class);\n\t\tparser = service.getSharedJavaParser();\n\t}\n\n\t@Nested\n\tclass Resolving {\n\t\t@Test\n\t\tvoid testPackage() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\tenum DummyEnum {}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"package software.coley.recaf.test.dummy;\", DirectoryPathNode.class, packagePath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy\", packagePath.getValue());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testPackage_NoEndingSemicolon() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy\\t\n\t\t\t\t\tenum DummyEnum {}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"package software.coley.recaf.test.dummy\", DirectoryPathNode.class, packagePath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy\", packagePath.getValue());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testImport() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\timport java.io.File;\n\t\t\t\t\timport software.coley.recaf.test.dummy.DummyEnum;\n\t\t\t\t\tclass HelloWorld {}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"import java.io.File;\", IsDeclarationTarget.REFERENCE, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"java/io/File\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t\tvalidateRange(unit, source, \"import software.coley.recaf.test.dummy.DummyEnum;\", IsDeclarationTarget.REFERENCE, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/DummyEnum\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testImportStaticMember() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\timport static software.coley.recaf.test.dummy.DummyEnum.ONE;\n\t\t\t\t\tclass HelloWorld {}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\t// The static member import takes precedence across the whole import statement\n\t\t\t\tvalidateRange(unit, source, \"import static software.coley.recaf.test.dummy.DummyEnum.ONE;\", IsDeclarationTarget.REFERENCE, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/DummyEnum\",\n\t\t\t\t\t\t\tmemberPath.getValueOfType(ClassInfo.class).getName());\n\t\t\t\t\tassertEquals(\"ONE\", memberPath.getValue().getName());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testClassDeclaration() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\tclass HelloWorld {}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"class HelloWorld\", IsDeclarationTarget.DECLARATION, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/HelloWorld\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testInterfaceDeclaration() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\tinterface HelloWorld {}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"interface HelloWorld\", IsDeclarationTarget.DECLARATION, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/HelloWorld\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testResolveInterfaces() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\tpublic class OverlapClassAB implements OverlapInterfaceA, OverlapInterfaceB {\n\t\t\t\t\t\tpublic void methodA() {}\n\t\t\t\t\t\tpublic void methodB() {}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"OverlapInterfaceA\", IsDeclarationTarget.REFERENCE, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/OverlapInterfaceA\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t\tvalidateRange(unit, source, \"OverlapInterfaceB\", IsDeclarationTarget.REFERENCE, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/OverlapInterfaceB\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testEnumDeclaration() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\tenum HelloWorld {}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"enum HelloWorld\", IsDeclarationTarget.DECLARATION, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/HelloWorld\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testRecordDeclaration() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\trecord HelloWorld() {}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"record HelloWorld\", IsDeclarationTarget.DECLARATION, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/HelloWorld\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testFieldDeclaration_Normal() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.util;\n\t\t\t\t\timport org.objectweb.asm.Type;\n\t\t\t\t\tpublic class Types {\n\t\t\t\t\t  \tpublic static final Type OBJECT_TYPE = Type.getObjectType(\"java/lang/Object\");\n\t\t\t\t\t  \tpublic static final Type STRING_TYPE = new Type();\n\t\t\t\t\t  \tprivate static final Type[] PRIMITIVES = new Type[0];\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"OBJECT_TYPE\", IsDeclarationTarget.DECLARATION, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"OBJECT_TYPE\", member.getName());\n\t\t\t\t});\n\t\t\t\tvalidateRange(unit, source, \"STRING_TYPE\", IsDeclarationTarget.DECLARATION, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"STRING_TYPE\", member.getName());\n\t\t\t\t});\n\t\t\t\tvalidateRange(unit, source, \"PRIMITIVES\", IsDeclarationTarget.DECLARATION, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"PRIMITIVES\", member.getName());\n\t\t\t\t});\n\t\t\t\tvalidateRange(unit, source, \"getObjectType\", IsDeclarationTarget.REFERENCE, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"getObjectType\", member.getName());\n\t\t\t\t});\n\t\t\t\tint start = source.indexOf(\"new Type(\");\n\t\t\t\tint end = start + \"new Type\".length();\n\t\t\t\tvalidateRange(unit, start, end, source, IsDeclarationTarget.REFERENCE, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"org/objectweb/asm/Type\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t\tstart = source.indexOf(\"new Type[\");\n\t\t\t\tend = start + \"new Type\".length();\n\t\t\t\tvalidateRange(unit, start, end, source, IsDeclarationTarget.REFERENCE, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"org/objectweb/asm/Type\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testFieldDeclaration_EnumConstant() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\tpublic enum DummyEnum {\n\t\t\t\t\t \tONE,\n\t\t\t\t\t \tTWO,\n\t\t\t\t\t \tTHREE\n\t\t\t\t\t }\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"ONE\", IsDeclarationTarget.DECLARATION, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"ONE\", member.getName());\n\t\t\t\t});\n\t\t\t\tvalidateRange(unit, source, \"TWO\", IsDeclarationTarget.DECLARATION, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"TWO\", member.getName());\n\t\t\t\t});\n\t\t\t\tvalidateRange(unit, source, \"THREE\", IsDeclarationTarget.DECLARATION, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"THREE\", member.getName());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\t@Disabled(\"Fails since generic type is used as return type, getEnumConstants() = DummyEnum[] not base type Enum[]\")\n\t\tvoid testMethodReference_SyntheticEnumMethod() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t     \n\t\t\t\t\tpublic class DummyEnumPrinter {\n\t\t\t\t\t\tpublic static void main(String[] args) {\n\t\t\t\t\t\t\tfor (DummyEnum enumConstant : DummyEnum.class.getEnumConstants()) {\n\t\t\t\t\t\t\t\tString name = enumConstant.name();\n\t\t\t\t\t\t\t\tSystem.out.println(name);\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tSupplier<String> supplier = enumConstant::name;\n\t\t\t\t\t\t\t\tSystem.out.println(supplier.get());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\t// TODO: More tests validating generic methods being resolved to NOT the <T> type once this behavior is fixed\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"name()\", IsDeclarationTarget.REFERENCE, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"name\", member.getName());\n\t\t\t\t\tassertEquals(\"()Ljava/lang/String;\", member.getDescriptor());\n\t\t\t\t});\n\t\t\t\tvalidateRange(unit, source, \"enumConstant::name\", IsDeclarationTarget.REFERENCE, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"name\", member.getName());\n\t\t\t\t\tassertEquals(\"()Ljava/lang/String;\", member.getDescriptor());\n\t\t\t\t});\n\t\t\t\tvalidateRange(unit, source, \"getEnumConstants\", IsDeclarationTarget.REFERENCE, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/DummyEnum\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testStaticMethodCall() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t  \t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tstatic void main(String[] args) {\n\t\t\t\t\t\t\tClassWithExceptions.readInt(args[0]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"ClassWithExceptions\", IsDeclarationTarget.REFERENCE, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/ClassWithExceptions\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t\tvalidateRange(unit, source, \"readInt\", IsDeclarationTarget.REFERENCE, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"readInt\", member.getName());\n\t\t\t\t\tassertEquals(\"(Ljava/lang/Object;)I\", member.getDescriptor());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testStaticMethodCall_JavaLangSystem() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t  \t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tstatic void main(String[] args) {\n\t\t\t\t\t\t\tSystem.loadLibrary(\"nativelibname\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"loadLibrary\", IsDeclarationTarget.REFERENCE, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"loadLibrary\", member.getName());\n\t\t\t\t\tassertEquals(\"(Ljava/lang/String;)V\", member.getDescriptor());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testStaticFieldRef() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t  \t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tstatic void main(String[] args) {\n\t\t\t\t\t\t\tClassWithStaticInit.i = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"ClassWithStaticInit\", IsDeclarationTarget.REFERENCE, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/ClassWithStaticInit\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t\tvalidateRange(unit, source, \"i \", IsDeclarationTarget.REFERENCE, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/ClassWithStaticInit\", member.getDeclaringClass().getName());\n\t\t\t\t\tassertEquals(\"i\", member.getName());\n\t\t\t\t\tassertEquals(\"I\", member.getDescriptor());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testConstructorDeclaration() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\tpublic class ClassWithConstructor {\n\t\t\t\t\t  \tpublic ClassWithConstructor() {}\n\t\t\t\t\t  \tpublic ClassWithConstructor(int i) {}\n\t\t\t\t\t  \tpublic ClassWithConstructor(int i, int j) {}\n\t\t\t\t\t  \tpublic ClassWithConstructor(DummyEnum dummyEnum, StringSupplier supplier) {}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tint start = source.indexOf(\"ClassWithConstructor()\");\n\t\t\t\tint end = start + \"ClassWithConstructor\".length();\n\t\t\t\tvalidateRange(unit, start, end, source, IsDeclarationTarget.DECLARATION, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"<init>\", member.getName());\n\t\t\t\t\tassertEquals(\"()V\", member.getDescriptor());\n\t\t\t\t});\n\t\t\t\tstart = source.indexOf(\"ClassWithConstructor(int i)\");\n\t\t\t\tend = start + \"ClassWithConstructor\".length();\n\t\t\t\tvalidateRange(unit, start, end, source, IsDeclarationTarget.DECLARATION, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"<init>\", member.getName());\n\t\t\t\t\tassertEquals(\"(I)V\", member.getDescriptor());\n\t\t\t\t});\n\t\t\t\tstart = source.indexOf(\"ClassWithConstructor(int i, int j)\");\n\t\t\t\tend = start + \"ClassWithConstructor\".length();\n\t\t\t\tvalidateRange(unit, start, end, source, IsDeclarationTarget.DECLARATION, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"<init>\", member.getName());\n\t\t\t\t\tassertEquals(\"(II)V\", member.getDescriptor());\n\t\t\t\t});\n\t\t\t\tstart = source.indexOf(\"ClassWithConstructor(DummyEnum dummyEnum, StringSupplier supplier)\");\n\t\t\t\tend = start + \"ClassWithConstructor\".length();\n\t\t\t\tvalidateRange(unit, start, end, source, IsDeclarationTarget.DECLARATION, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"<init>\", member.getName());\n\t\t\t\t\tassertEquals(\"(Lsoftware/coley/recaf/test/dummy/DummyEnum;\" +\n\t\t\t\t\t\t\t\"Lsoftware/coley/recaf/test/dummy/StringSupplier;)V\", member.getDescriptor());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testStaticInitializer() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\tpublic class ClassWithStaticInit {\n\t\t\t\t\t \tpublic static int i;\n\t\t\t\t\t \tstatic { i = 42; }\n\t\t\t\t\t }\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tint staticBlockIndex = source.lastIndexOf(\"static {\");\n\t\t\t\tvalidateRange(unit, staticBlockIndex, staticBlockIndex + 6, source, IsDeclarationTarget.DECLARATION, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"<clinit>\", member.getName());\n\t\t\t\t\tassertEquals(\"()V\", member.getDescriptor());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testStringList() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\tpublic class StringListUser {\n\t\t\t\t\t  \tpublic static void main(String[] args) {\n\t\t\t\t\t  \t\tStringList list = StringList.of(\"foo\");\n\t\t\t\t\t  \t\tfor (String string : list.unique()) {\n\t\t\t\t\t  \t\t\tSystem.out.println(string);\n\t\t\t\t\t  \t\t}\n\t\t\t\t\t  \t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tvalidateRange(unit, source, \"of(\", IsDeclarationTarget.REFERENCE, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"of\", member.getName());\n\t\t\t\t});\n\t\t\t\tvalidateRange(unit, source, \"unique\", IsDeclarationTarget.REFERENCE, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"unique\", member.getName());\n\t\t\t\t});\n\t\t\t\tint start = source.indexOf(\"StringList list\");\n\t\t\t\tint end = start + \"StringList\".length();\n\t\t\t\tvalidateRange(unit, start, end, source, IsDeclarationTarget.REFERENCE, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/StringList\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t\tstart = source.indexOf(\"StringList.of\");\n\t\t\t\tend = start + \"StringList\".length();\n\t\t\t\tvalidateRange(unit, start, end, source, IsDeclarationTarget.REFERENCE, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/StringList\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testResolveThis() {\n\t\t\t// OpenRewrite can be funky about how it treats 'this'.\n\t\t\t// So we want to have a test to make sure it gets resolved correctly.\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\timport java.util.ArrayList;\n\t\t\t\t\timport java.util.Arrays;\n\t\t\t\t\timport java.util.LinkedHashSet;\n\t\t\t\t\timport java.util.Set;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tpublic class StringList extends ArrayList<String> {\n\t\t\t\t\t\tpublic Set<String> unique() {\n\t\t\t\t\t\t\tObject string = this.toString();\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\treturn new LinkedHashSet<>(this);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tint start = source.indexOf(\"this\");\n\t\t\t\tint end = start + \"this\".length();\n\t\t\t\tvalidateRange(unit, start, end, source, IsDeclarationTarget.REFERENCE, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/StringList\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t\tstart = source.lastIndexOf(\"this\");\n\t\t\t\tend = start + \"this\".length();\n\t\t\t\tvalidateRange(unit, start, end, source, IsDeclarationTarget.REFERENCE, ClassPathNode.class, classPath -> {\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/StringList\", classPath.getValue().getName());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t}\n\n\t@Nested\n\tclass Mapping {\n\t\t@Test\n\t\tvoid renameClass_ReplacesPackage() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tenum DummyEnum {\n\t\t\t\t\t\tONE, TWO, THREE\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addClass(DummyEnum.class.getName().replace('.', '/'), \"com/example/MyEnum\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.startsWith(\"package com.example;\"));\n\t\t\t\tassertTrue(modified.contains(\"enum MyEnum {\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameField_EnumConst() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tenum DummyEnum {\n\t\t\t\t\t\tONE, TWO, THREE\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tString owner = DummyEnum.class.getName().replace('.', '/');\n\t\t\t\tmappings.addField(owner, \"L\" + owner + \";\", \"ONE\", \"FIRST\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"FIRST,\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameClass_ReplacesCast() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tstatic void main(Object arg) {\n\t\t\t\t\t\t\tHelloWorld casted = (HelloWorld) arg;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addClass(HelloWorld.class.getName().replace('.', '/'), \"com/example/Howdy\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.startsWith(\"package com.example;\"));\n\t\t\t\tassertTrue(modified.contains(\"class Howdy {\"));\n\t\t\t\tassertTrue(modified.contains(\"Howdy casted = (Howdy) arg;\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameClass_ReplaceStaticCallContextInSamePackage() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tstatic void main(String[] args) {\n\t\t\t\t\t\t\tClassWithExceptions.readInt(args[0]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addClass(ClassWithExceptions.class.getName().replace('.', '/'), \"com/example/Call\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"Call.readInt(\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameClass_ReplacePackageImport() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\timport software.coley.recaf.util.*;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tstatic void main(String[] args) {\n\t\t\t\t\t\t\tSystem.out.println(StringUtil.getCommonSuffix(args[0], args[1]));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\t// We'll just map one class in the util package.\n\t\t\t\t// We should import the renamed class explicitly, but keep the\n\t\t\t\t// star import since other classes in the package were not renamed\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addClass(StringUtil.class.getName().replace('.', '/'), \"com/example/Strs\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"import software.coley.recaf.util.*;\"), \"Original star import should be kept\");\n\t\t\t\tassertTrue(modified.contains(\"import com.example.Strs\"), \"Missing explicit import of renamed class\");\n\t\t\t\tassertTrue(modified.contains(\"Strs.getCommonSuffix(\"), \"Reference to method in renamed class not updated\");\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameClass_ReplaceImportOfStaticCall() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\timport static software.coley.recaf.test.dummy.ClassWithExceptions.readInt;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tstatic void main(String[] args) {\n\t\t\t\t\t\t\treadInt(args[0]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addClass(ClassWithExceptions.class.getName().replace('.', '/'), \"com/example/Call\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"import static com.example.Call.readInt\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameClass_ArrayDecAndNew() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tstatic void main(String[] args) {\n\t\t\t\t\t\t\tClassWithExceptions[] bar = new ClassWithExceptions[0];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addClass(ClassWithExceptions.class.getName().replace('.', '/'), \"com/example/Foo\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"Foo[] bar = new Foo[0];\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameClass_FieldStaticQualifier() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tstatic void main(String[] args) {\n\t\t\t\t\t\t\tDummyEnum one = DummyEnum.ONE;\n\t\t\t\t\t\t\tString two = DummyEnum.valueOf(\"TWO\").name();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addClass(DummyEnum.class.getName().replace('.', '/'), \"com/example/Singleton\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"Singleton one = Singleton.ONE;\"));\n\t\t\t\tassertTrue(modified.contains(\"String two = Singleton.valueOf(\\\"TWO\\\").name();\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameClass_FieldAndVariableDeclarations() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tClassWithExceptions[] array;\n\t\t\t\t\t\tClassWithExceptions single;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\tstatic void main(String[] args) {\n\t\t\t\t\t\t\tClassWithExceptions[] local_array = null;\n\t\t\t\t\t\t\tClassWithExceptions local_single = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addClass(ClassWithExceptions.class.getName().replace('.', '/'), \"com/example/Foo\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"Foo[] array;\"));\n\t\t\t\tassertTrue(modified.contains(\"Foo single;\"));\n\t\t\t\tassertTrue(modified.contains(\"Foo[] local_array = null;\"));\n\t\t\t\tassertTrue(modified.contains(\"Foo local_single = null;\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameClass_MethodReturnAndArgs() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tstatic HelloWorld get() { return null; }\n\t\t\t\t\t\tvoid accept(HelloWorld arg) {}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addClass(HelloWorld.class.getName().replace('.', '/'), \"com/example/Howdy\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"static Howdy get()\"));\n\t\t\t\tassertTrue(modified.contains(\"void accept(Howdy arg)\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameClass_Constructor() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tprivate HelloWorld(String s) {}\n\t\t\t\t\t\tHelloWorld() {}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addClass(HelloWorld.class.getName().replace('.', '/'), \"com/example/Howdy\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"private Howdy(String s) {}\"));\n\t\t\t\tassertTrue(modified.contains(\"Howdy() {}\"));\n\t\t\t});\n\n\t\t\tsource = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\tpublic class ClassWithConstructor {\n\t\t\t\t\t  \tprivate ClassWithConstructor() {}\n\t\t\t\t\t  \tClassWithConstructor(int i) {}\n\t\t\t\t\t  \tprotected ClassWithConstructor(int i, int j) {}\n\t\t\t\t\t  \tpublic ClassWithConstructor(DummyEnum dummyEnum, StringSupplier supplier) {}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addClass(ClassWithConstructor.class.getName().replace('.', '/'), \"com/example/Howdy\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"Howdy() {}\"));\n\t\t\t\tassertTrue(modified.contains(\"Howdy(int i) {}\"));\n\t\t\t\tassertTrue(modified.contains(\"Howdy(int i, int j) {}\"));\n\t\t\t\tassertTrue(modified.contains(\"Howdy(DummyEnum dummyEnum, StringSupplier supplier) {}\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameClass_MethodReference() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\timport java.util.function.Supplier;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tstatic {\n\t\t\t\t\t\t\tSupplier<HelloWorld> worldSupplier = HelloWorld::new;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addClass(HelloWorld.class.getName().replace('.', '/'), \"com/example/Howdy\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"Supplier<Howdy> worldSupplier = Howdy::new\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\t@Disabled(\"Pending support in mapping visitor\")\n\t\tvoid renameClass_QualifiedNameReference() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tstatic {\n\t\t\t\t\t\t\tsoftware.coley.recaf.util.Types value = new software.coley.recaf.util.Types();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addClass(Types.class.getName().replace('.', '/'), \"com/example/TypeUtil\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tSystem.err.println(modified);\n\t\t\t\tassertTrue(modified.contains(\"com.example.TypeUtil value = new com.example.TypeUtil();\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameMember_FieldName() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass AccessibleFields {\n\t\t\t\t\t\tpublic static int CONSTANT_FIELD;\n\t\t\t\t\t \tprivate int privateFinalField = 8;\n\t\t\t\t\t \tprotected int protectedField;\n\t\t\t\t\t \tpublic int publicField;\n\t\t\t\t\t \tfinal int packageField;\n\t\t\t\t\t\t\n\t\t\t\t\t\tAccessibleFields() {\n\t\t\t\t\t\t\tthis.packageField = 3;\n\t\t\t\t\t\t\tpackageField = 4;\n\t\t\t\t\t\t\tprotectedField = packageField;\n\t\t\t\t\t\t\tpublicField = CONSTANT_FIELD;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\tstatic {\n\t\t\t\t\t\t\tAccessibleFields.CONSTANT_FIELD = 32;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addField(AccessibleFields.class.getName().replace('.', '/'), \"I\", \"CONSTANT_FIELD\", \"KON_FID\");\n\t\t\t\tmappings.addField(AccessibleFields.class.getName().replace('.', '/'), \"I\", \"privateFinalField\", \"priFid\");\n\t\t\t\tmappings.addField(AccessibleFields.class.getName().replace('.', '/'), \"I\", \"protectedField\", \"proFid\");\n\t\t\t\tmappings.addField(AccessibleFields.class.getName().replace('.', '/'), \"I\", \"publicField\", \"puFid\");\n\t\t\t\tmappings.addField(AccessibleFields.class.getName().replace('.', '/'), \"I\", \"packageField\", \"pkFid\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"int KON_FID;\"));\n\t\t\t\tassertTrue(modified.contains(\"int priFid = 8;\"));\n\t\t\t\tassertTrue(modified.contains(\"int proFid;\"));\n\t\t\t\tassertTrue(modified.contains(\"int puFid;\"));\n\t\t\t\tassertTrue(modified.contains(\"int pkFid;\"));\n\n\t\t\t\tassertTrue(modified.contains(\"this.pkFid = 3\"));\n\t\t\t\tassertTrue(modified.contains(\"pkFid = 4;\"));\n\t\t\t\tassertTrue(modified.contains(\"proFid = pkFid;\"));\n\t\t\t\tassertTrue(modified.contains(\"puFid = KON_FID;\"));\n\n\t\t\t\tassertTrue(modified.contains(\"AccessibleFields.KON_FID = 32;\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameMember_MethodName() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\timport java.util.function.Supplier;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass ClassWithMethodReference {\n\t\t\t\t\t\tstatic {\n\t\t\t\t\t\t\tSupplier<String> fooSupplier = ClassWithMethodReference::foo;\n\t\t\t\t\t\t\tfoo();\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\tstatic String foo() { return \"foo\"; }\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addMethod(ClassWithMethodReference.class.getName().replace('.', '/'), \"()Ljava/lang/String;\", \"foo\", \"bar\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"Supplier<String> fooSupplier = ClassWithMethodReference::bar;\"));\n\t\t\t\tassertTrue(modified.contains(\"bar();\"));\n\t\t\t\tassertTrue(modified.contains(\"static String bar() { return \\\"foo\\\"; }\"));\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid renameMember_MethodNameStaticallyImported() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\timport static software.coley.recaf.test.dummy.ClassWithExceptions.readInt;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tclass HelloWorld {\n\t\t\t\t\t\tstatic void main(String[] args) {\n\t\t\t\t\t\t\treadInt(args[0]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\tmappings.addMethod(ClassWithExceptions.class.getName().replace('.', '/'), \"(Ljava/lang/Object;)I\", \"readInt\", \"foo\");\n\n\t\t\t\tString modified = applyMappings(unit, mappings);\n\t\t\t\tassertTrue(modified.contains(\"import static software.coley.recaf.test.dummy.ClassWithExceptions.foo;\"));\n\t\t\t\tassertTrue(modified.contains(\"foo(args[0]);\"));\n\t\t\t});\n\t\t}\n\n\t\t@Nonnull\n\t\tprivate String applyMappings(@Nonnull CompilationUnitModel unit, @Nonnull Mappings mappings) {\n\t\t\treturn service.applyMappings(unit, service.newJavaResolver(unit), mappings);\n\t\t}\n\t}\n\n\t@Nested\n\tclass ErroneousInput {\n\t\t@Test\n\t\tvoid testResolveWithInvalidGoto() {\n\t\t\t// CFR can emit illegal code like this with patterns such as:\n\t\t\t//   ** GOTO lbl-1000\n\t\t\t//   else lbl-1000:\n\t\t\t// So in our AST resolving we simply check if the AST model's text and the input text are the same\n\t\t\t// for all given positions. When a mismatch is detected, its assumed the AST dropped tokens present\n\t\t\t// in the input text since they were invalid.\n\t\t\t//\n\t\t\t// With this assumption we scan forward until we get our next match.\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tenum DummyEnum {\n\t\t\t\t\t\tONE, TWO, THREE;\n\t\t\t\t\t\t\n\t\t\t\t\t\tvoid foo() {}\n\t\t\t\t\t\t\n\t\t\t\t\t\tvoid bar() {\n\t\t\t\t\t\t   ** GOTO lbl-1000\n\t\t\t\t\t\t   \n\t\t\t\t\t\t   ONE.foo();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tint firstOne = source.indexOf(\"ONE,\");\n\t\t\t\tvalidateRange(unit, firstOne, firstOne + 3, source, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassInfo owner = memberPath.getValueOfType(ClassInfo.class);\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/DummyEnum\", owner.getName());\n\t\t\t\t\tassertEquals(\"ONE\", member.getName());\n\t\t\t\t});\n\n\t\t\t\tint lastOne = source.lastIndexOf(\"ONE\");\n\t\t\t\tvalidateRange(unit, lastOne, lastOne + 3, source, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassInfo owner = memberPath.getValueOfType(ClassInfo.class);\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/DummyEnum\", owner.getName());\n\t\t\t\t\tassertEquals(\"ONE\", member.getName());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\tvoid testResolveWithMissingEndBraces() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tpublic class ClassWithMultipleMethods {\n\t\t\t\t\t\tpublic static String append(String one, String two) {\n\t\t\t\t\t\t\treturn one + two;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpublic static String append(String one, String two, String three) {\n\t\t\t\t\t\t\treturn append(append(one, two), three);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpublic static int add(int a, int b) {\n\t\t\t\t\t\t\treturn a + b;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpublic static int add(int a, int b, int c) {\n\t\t\t\t\t\t\treturn add(add(a, b), c);\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tint index = source.indexOf(\"append(one, two)\");\n\t\t\t\tvalidateRange(unit, index + 1, index + 4, source, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassInfo owner = memberPath.getValueOfType(ClassInfo.class);\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/ClassWithMultipleMethods\", owner.getName());\n\t\t\t\t\tassertEquals(\"append\", member.getName());\n\t\t\t\t\tassertEquals(\"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;\", member.getDescriptor());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\t@Disabled(\"u0000 in the variable name causes the following ', String two' to be treated as white-space\" +\n\t\t\t\t\"So the definition becomes (String)String, which is wrong. This arises from javac handling, so it\" +\n\t\t\t\t\"cannot be fixed\")\n\t\tvoid testResolveWithNullTerminatorParameterName() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tpublic class ClassWithMultipleMethods {\n\t\t\t\t\t\tpublic static String append(String \\0, String two) {\n\t\t\t\t\t\t\treturn \"one\" + two;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpublic static String append(String one, String two, String three) {\n\t\t\t\t\t\t\treturn append(append(one, two), three);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpublic static int add(int a, int b) {\n\t\t\t\t\t\t\treturn a + b;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpublic static int add(int a, int b, int c) {\n\t\t\t\t\t\t\treturn add(add(a, b), c);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tint index = source.indexOf(\"append(one, two)\");\n\t\t\t\tvalidateRange(unit, index + 1, index + 4, source, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassInfo owner = memberPath.getValueOfType(ClassInfo.class);\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/ClassWithMultipleMethods\", owner.getName());\n\t\t\t\t\tassertEquals(\"append\", member.getName());\n\t\t\t\t\tassertEquals(\"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;\", member.getDescriptor());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Test\n\t\t@Disabled(\"Reserved keywords break the parser in the same way as the test above with u0000\")\n\t\tvoid testResolveWithKeywordParameterName() {\n\t\t\tString source = \"\"\"\n\t\t\t\t\tpackage software.coley.recaf.test.dummy;\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tpublic class ClassWithMultipleMethods {\n\t\t\t\t\t\tpublic static String append(String static, String two) {\n\t\t\t\t\t\t\treturn \"one\" + two;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpublic static String append(String one, String two, String three) {\n\t\t\t\t\t\t\treturn append(append(one, two), three);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpublic static int add(int a, int b) {\n\t\t\t\t\t\t\treturn a + b;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpublic static int add(int a, int b, int c) {\n\t\t\t\t\t\t\treturn add(add(a, b), c);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\thandleUnit(source, unit -> {\n\t\t\t\tint index = source.indexOf(\"append(one, two)\");\n\t\t\t\tvalidateRange(unit, index + 1, index + 4, source, ClassMemberPathNode.class, memberPath -> {\n\t\t\t\t\tClassInfo owner = memberPath.getValueOfType(ClassInfo.class);\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tassertEquals(\"software/coley/recaf/test/dummy/ClassWithMultipleMethods\", owner.getName());\n\t\t\t\t\tassertEquals(\"append\", member.getName());\n\t\t\t\t\tassertEquals(\"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;\", member.getDescriptor());\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate static <T> void validateRange(@Nonnull CompilationUnitModel unit,\n\t                                      @Nonnull String source, @Nonnull String match,\n\t                                      @Nonnull Class<T> targetType,\n\t                                      @Nonnull Consumer<T> consumer) {\n\t\tint start = source.indexOf(match);\n\t\tint end = start + match.length();\n\t\tvalidateRange(unit, start, end, source, targetType, consumer);\n\t}\n\n\tprivate static <T> void validateRange(@Nonnull CompilationUnitModel unit,\n\t                                      @Nonnull String source, @Nonnull String match,\n\t                                      @Nonnull IsDeclarationTarget declarationTarget,\n\t                                      @Nonnull Class<T> targetType,\n\t                                      @Nonnull Consumer<T> consumer) {\n\t\tint start = source.indexOf(match);\n\t\tint end = start + match.length();\n\t\tvalidateRange(unit, start, end, source, declarationTarget, targetType, consumer);\n\t}\n\n\tprivate static <T> void validateRange(@Nonnull CompilationUnitModel unit,\n\t                                      int start, int end,\n\t                                      @Nonnull String source,\n\t                                      @Nonnull Class<T> targetType,\n\t                                      @Nonnull Consumer<T> consumer) {\n\t\tvalidateRange(unit, start, end, source, IsDeclarationTarget.DONT_CARE, targetType, consumer);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static <T> void validateRange(@Nonnull CompilationUnitModel unit,\n\t                                      int start, int end,\n\t                                      @Nonnull String source,\n\t                                      @Nonnull IsDeclarationTarget declarationTarget,\n\t                                      @Nonnull Class<T> targetType,\n\t                                      @Nonnull Consumer<T> consumer) {\n\t\tfor (int i = start; i < end; i++) {\n\t\t\tAstResolveResult result = service.newJavaResolver(unit).resolveThenAdapt(i);\n\t\t\tString suffix = \" at index: \" + i + \" in range [\" + start + \"-\" + end + \"]\";\n\t\t\tif (result == null) {\n\t\t\t\tfail(\"Failed to resolve target\" + suffix);\n\t\t\t} else if (!declarationTarget.isSatisfiedBy(result.isDeclaration())) {\n\t\t\t\tfail(\"Failed to clarify declaration vs reference relation\" + suffix);\n\t\t\t} else if (!targetType.isAssignableFrom(result.path().getClass())) {\n\t\t\t\tfail(\"Failed to resolve target as expected type '\" + targetType + \"'\" + suffix);\n\t\t\t} else {\n\t\t\t\tconsumer.accept((T) result.path());\n\t\t\t}\n\t\t}\n\t}\n\n\t@SuppressWarnings(\"LanguageMismatch\")\n\tprivate static void handleUnit(@Nonnull String source, @Nullable Consumer<CompilationUnitModel> consumer) {\n\t\tif (consumer != null)\n\t\t\tconsumer.accept(parser.parse(source));\n\t}\n\n\tprivate enum IsDeclarationTarget {\n\t\tDECLARATION, REFERENCE, DONT_CARE;\n\n\t\tpublic boolean isSatisfiedBy(boolean isDeclaration) {\n\t\t\treturn switch (this) {\n\t\t\t\tcase DONT_CARE -> true;\n\t\t\t\tcase REFERENCE -> !isDeclaration;\n\t\t\t\tcase DECLARATION -> isDeclaration;\n\t\t\t};\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/transform/TransformationApplierTest.java",
    "content": "package software.coley.recaf.services.transform;\n\nimport jakarta.annotation.Nonnull;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.mapping.MappingApplier;\nimport software.coley.recaf.services.mapping.MappingApplierService;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.HelloWorld;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Supplier;\n\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.*;\n\n/**\n * Tests for {@link TransformationApplier}\n */\nclass TransformationApplierTest extends TestBase {\n\tprivate static final TransformationApplierConfig config = new TransformationApplierConfig();\n\tprivate static final InheritanceGraph inheritanceGraph;\n\tprivate static final MappingApplier mappingApplier;\n\tprivate static final Workspace workspace;\n\n\tstatic {\n\t\t// Make a dummy workspace. We just need a single class (and any class will work)\n\t\ttry {\n\t\t\tworkspace = TestClassUtils.fromBundle(TestClassUtils.fromClasses(HelloWorld.class));\n\t\t\tinheritanceGraph = recaf.get(InheritanceGraphService.class).newInheritanceGraph(workspace);\n\t\t\tmappingApplier = recaf.get(MappingApplierService.class).inWorkspace(workspace);\n\t\t} catch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Failed to read input class for transformer test\", e);\n\t\t}\n\t}\n\n\t@Test\n\tvoid independentAB() {\n\t\tJvmTransformerA transformerA = spy(new JvmTransformerA());\n\t\tJvmTransformerB transformerB = spy(new JvmTransformerB());\n\n\t\t// Build transformer map with two items\n\t\t//  - A\n\t\t//  - B\n\t\tMap<Class<? extends JvmClassTransformer>, Supplier<JvmClassTransformer>> map = new IdentityHashMap<>();\n\t\tmap.put(JvmTransformerA.class, () -> transformerA);\n\t\tmap.put(JvmTransformerB.class, () -> transformerB);\n\n\t\t// If we transform with \"B\" we should observe that only \"B\" is called on sine the two hold no relation\n\t\tTransformationManager manager = new TransformationManager(map);\n\t\tTransformationApplier applier = new TransformationApplier(manager, config, inheritanceGraph, mappingApplier, workspace);\n\t\tassertDoesNotThrow(() -> applier.transformJvm(Collections.singletonList(JvmTransformerB.class)));\n\n\t\t// \"A\" not used\n\t\tverify(transformerA, never()).transform(any(), any(), any(), any(), any());\n\n\t\t// \"B\" used once\n\t\tverify(transformerB, times(1)).transform(any(), same(workspace), any(), any(), any());\n\t}\n\n\t@Test\n\tvoid dependentAB() {\n\t\tJvmTransformerA transformerA = spy(new JvmTransformerA());\n\t\tJvmTransformerDependingOnA transformerB = spy(new JvmTransformerDependingOnA());\n\n\t\t// Build transformer map with two items\n\t\t//  - A\n\t\t//  - B --> A\n\t\tMap<Class<? extends JvmClassTransformer>, Supplier<JvmClassTransformer>> map = new IdentityHashMap<>();\n\t\tmap.put(JvmTransformerA.class, () -> transformerA);\n\t\tmap.put(JvmTransformerDependingOnA.class, () -> transformerB);\n\n\t\t// If we transform with \"B\" we should observe that both \"B\" and \"A\" were called on.\n\t\tTransformationManager manager = new TransformationManager(map);\n\t\tTransformationApplier applier = new TransformationApplier(manager, config, inheritanceGraph, mappingApplier, workspace);\n\t\tassertDoesNotThrow(() -> applier.transformJvm(Collections.singletonList(JvmTransformerDependingOnA.class)));\n\t\tverify(transformerA, times(1)).transform(any(), same(workspace), any(), any(), any());\n\t\tverify(transformerB, times(1)).transform(any(), same(workspace), any(), any(), any());\n\t}\n\n\t@Test\n\tvoid cycleAB() {\n\t\tJvmCycleA transformerA = spy(new JvmCycleA());\n\t\tJvmCycleB transformerB = spy(new JvmCycleB());\n\n\t\t// Build transformer map with two items\n\t\t//  - A --> B\n\t\t//  - B --> A\n\t\tMap<Class<? extends JvmClassTransformer>, Supplier<JvmClassTransformer>> map = new IdentityHashMap<>();\n\t\tmap.put(JvmCycleA.class, () -> transformerA);\n\t\tmap.put(JvmCycleB.class, () -> transformerB);\n\n\t\t// If we transform with \"A\" or \"B\" we should observe an exception due to the detected cycle\n\t\tTransformationManager manager = new TransformationManager(map);\n\t\tTransformationApplier applier = new TransformationApplier(manager, config, inheritanceGraph, mappingApplier, workspace);\n\t\tassertThrows(TransformationException.class, () -> applier.transformJvm(Collections.singletonList(JvmCycleA.class)));\n\t\tassertThrows(TransformationException.class, () -> applier.transformJvm(Collections.singletonList(JvmCycleB.class)));\n\t\tverify(transformerA, never()).transform(any(), same(workspace), any(), any(), any());\n\t\tverify(transformerB, never()).transform(any(), same(workspace), any(), any(), any());\n\t}\n\n\t@Test\n\tvoid cycleSingle() {\n\t\tJvmCycleSingle transformer = spy(new JvmCycleSingle());\n\n\t\t// Build transformer map with one item\n\t\t//  - A --> A\n\t\tMap<Class<? extends JvmClassTransformer>, Supplier<JvmClassTransformer>> map = new IdentityHashMap<>();\n\t\tmap.put(JvmCycleSingle.class, () -> transformer);\n\n\t\t// If we transform with the single transformer we should observe an exception due to the detected cycle\n\t\tTransformationManager manager = new TransformationManager(map);\n\t\tTransformationApplier applier = new TransformationApplier(manager, config, inheritanceGraph, mappingApplier, workspace);\n\t\tassertThrows(TransformationException.class, () -> applier.transformJvm(Collections.singletonList(JvmCycleSingle.class)));\n\t\tverify(transformer, never()).transform(any(), same(workspace), any(), any(), any());\n\t}\n\n\t@Test\n\tvoid missingRegistration() {\n\t\t// If we transform with a transformer that is not registered in the manager, the transform should fail\n\t\tTransformationManager manager = new TransformationManager(Collections.emptyMap());\n\t\tTransformationApplier applier = new TransformationApplier(manager, config, inheritanceGraph, mappingApplier, workspace);\n\t\tassertThrows(TransformationException.class, () -> applier.transformJvm(Collections.singletonList(JvmCycleSingle.class)));\n\t}\n\n\tstatic class JvmTransformerA implements JvmClassTransformer {\n\n\t\t@Override\n\t\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t\t                      @Nonnull JvmClassInfo initialClassState) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String name() {\n\t\t\treturn \"jvm-a\";\n\t\t}\n\t}\n\n\tstatic class JvmTransformerB implements JvmClassTransformer {\n\n\t\t@Override\n\t\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t\t                      @Nonnull JvmClassInfo initialClassState) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String name() {\n\t\t\treturn \"jvm-b\";\n\t\t}\n\t}\n\n\tstatic class JvmTransformerDependingOnA implements JvmClassTransformer {\n\n\t\t@Override\n\t\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t\t                      @Nonnull JvmClassInfo initialClassState) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Set<Class<? extends ClassTransformer>> dependencies() {\n\t\t\treturn Collections.singleton(JvmTransformerA.class);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String name() {\n\t\t\treturn \"jvm-depending-on-a\";\n\t\t}\n\t}\n\n\tstatic class JvmCycleSingle implements JvmClassTransformer {\n\n\t\t@Override\n\t\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t\t                      @Nonnull JvmClassInfo initialClassState) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Set<Class<? extends ClassTransformer>> dependencies() {\n\t\t\treturn Collections.singleton(JvmCycleSingle.class);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String name() {\n\t\t\treturn \"jvm-cycle\";\n\t\t}\n\t}\n\n\tstatic class JvmCycleA implements JvmClassTransformer {\n\n\t\t@Override\n\t\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t\t                      @Nonnull JvmClassInfo initialClassState) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Set<Class<? extends ClassTransformer>> dependencies() {\n\t\t\treturn Collections.singleton(JvmCycleB.class);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String name() {\n\t\t\treturn \"jvm-cycle-a\";\n\t\t}\n\t}\n\n\tstatic class JvmCycleB implements JvmClassTransformer {\n\n\t\t@Override\n\t\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t\t                      @Nonnull JvmClassInfo initialClassState) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Set<Class<? extends ClassTransformer>> dependencies() {\n\t\t\treturn Collections.singleton(JvmCycleA.class);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String name() {\n\t\t\treturn \"jvm-cycle-b\";\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/transform/TransformationManagerTest.java",
    "content": "package software.coley.recaf.services.transform;\n\nimport jakarta.annotation.Nonnull;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.IdentityHashMap;\nimport java.util.Map;\nimport java.util.function.Supplier;\n\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\n/**\n * Tests for {@link TransformationManager}\n */\nclass TransformationManagerTest {\n\t@Test\n\tvoid single() {\n\t\t// Build transformer map with just one item\n\t\tMap<Class<? extends JvmClassTransformer>, Supplier<JvmClassTransformer>> map = new IdentityHashMap<>();\n\t\tmap.put(TransformerExample.class, TransformerExample::new);\n\n\t\t// If we ask for that single transformer, we should get it without issue\n\t\tTransformationManager manager = new TransformationManager(map);\n\t\tTransformerExample transformer = assertDoesNotThrow(() -> manager.newJvmTransformer(TransformerExample.class));\n\t\tassertNotNull(transformer);\n\t}\n\n\tstatic class TransformerExample implements JvmClassTransformer {\n\n\t\t@Override\n\t\tpublic void transform(@Nonnull JvmTransformerContext context, @Nonnull Workspace workspace,\n\t\t                      @Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle,\n\t\t                      @Nonnull JvmClassInfo initialClassState) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String name() {\n\t\t\treturn \"jvm-a\";\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/workspace/io/InfoImporterTest.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.info.*;\nimport software.coley.recaf.info.properties.builtin.ZipMarkerProperty;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.util.ZipCreationUtils;\nimport software.coley.recaf.util.io.ByteSource;\nimport software.coley.recaf.util.io.ByteSources;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\n/**\n * Tests for {@link InfoImporter}\n */\nclass InfoImporterTest {\n\tstatic final ClassPatcher mockedPatcher = mock(ClassPatcher.class);\n\tstatic InfoImporter importer;\n\n\t@BeforeAll\n\tstatic void setup() {\n\t\timporter = new BasicInfoImporter(new InfoImporterConfig(), new TextFormatConfig(), mockedPatcher);\n\t}\n\n\t@Test\n\tvoid testImportRuntimeClassesAsJvmClasses() throws IOException {\n\t\tfor (Class<?> type : Arrays.asList(Collection.class, List.class, ArrayList.class)) {\n\t\t\t// Fetch class from runtime.\n\t\t\tJvmClassInfo classInfo = TestClassUtils.fromRuntimeClass(type);\n\n\t\t\t// Pass the class's bytecode into the importer and see what we get back.\n\t\t\tByteSource bytecodeSource = ByteSources.wrap(classInfo.getBytecode());\n\t\t\tInfo read = importer.readInfo(classInfo.getName(), bytecodeSource);\n\n\t\t\t// Should be a JVM class\n\t\t\tassertTrue(read.isClass());\n\t\t\tClassInfo readClass = read.asClass();\n\t\t\tassertTrue(readClass.isJvmClass());\n\t\t\tJvmClassInfo readJvmClass = readClass.asJvmClass();\n\n\t\t\t// Should have equality with the original\n\t\t\tassertEquals(classInfo, readJvmClass, \"Class from importer does not match class from runtime\");\n\t\t}\n\t}\n\n\t@Test\n\tvoid testUsesClassPatcher() throws IOException {\n\t\tJvmClassInfo classInfo = TestClassUtils.fromRuntimeClass(ArrayList.class);\n\t\tByteSource invalidSource = ByteSources.wrap(classInfo.getBytecode(), 0, 20);\n\n\t\t// Cut off the class file so that it is 'invalid'.\n\t\t// We've passed in a no-op mock for the patcher, so it won't do anything currently.\n\t\tInfo read = importer.readInfo(\"\", invalidSource);\n\t\tassertFalse(read.isClass(), \"Should have failed to read class from cut off data\");\n\t\tassertTrue(read.isFile(), \"Should have read partial class as generic file\");\n\n\t\t// Mock it so that patcher 'fixes' the bytecode by returning the original.\n\t\twhen(mockedPatcher.patch(anyString(), any())).thenReturn(classInfo.getBytecode());\n\n\t\t// Now that patcher component is 'implemented' the class should be fixable by the importer.\n\t\tread = importer.readInfo(\"\", invalidSource);\n\t\tassertTrue(read.isClass(), \"Should have used patcher to fix invalid class bytecode\");\n\t\tassertTrue(read.asClass().isJvmClass(), \"Created wrong class type\");\n\t}\n\n\t@Test\n\tvoid testImportEmptyArrayAsGenericFile() throws IOException {\n\t\t// Importing something that cannot be identified, should use 'FileInfo' as catch-all.\n\t\tInfo read = importer.readInfo(\"\", ByteSources.wrap(new byte[0]));\n\t\tassertTrue(read.isFile());\n\n\t\t// Specifically, because we don't know anything about the file, we should not assign it to\n\t\t// anything more detailed than this.\n\t\tassertEquals(BasicFileInfo.class, read.getClass());\n\t}\n\n\t@Test\n\tvoid testImportZip() throws IOException {\n\t\t// Create virtual ZIP with single 'Hello.txt'\n\t\tbyte[] zipFileBytes = ZipCreationUtils.createSingleEntryZip(\"Hello.txt\", \"Hello world\".getBytes(StandardCharsets.UTF_8));\n\t\tByteSource zipSource = ByteSources.wrap(zipFileBytes);\n\n\t\t// We don't know the file name, so we can only assume it is a ZIP\n\t\tInfo read = importer.readInfo(\"\", zipSource);\n\t\tassertTrue(read.isFile());\n\t\tassertTrue(read.asFile().isZipFile());\n\t\tassertEquals(BasicZipFileInfo.class, read.getClass());\n\t\tZipFileInfo readZip = read.asFile().asZipFile();\n\t\tassertArrayEquals(zipFileBytes, readZip.getRawContent());\n\n\t\t// However, if we provide various extensions then we can use the file name to infer what kind of ZIP it is.\n\t\tread = importer.readInfo(\"data.jar\", zipSource);\n\t\tassertInstanceOf(JarFileInfo.class, read);\n\t\tread = importer.readInfo(\"data.war\", zipSource);\n\t\tassertInstanceOf(WarFileInfo.class, read);\n\t\tread = importer.readInfo(\"data.jmod\", zipSource);\n\t\tassertInstanceOf(JModFileInfo.class, read);\n\t\tread = importer.readInfo(\"data.apk\", zipSource);\n\t\tassertInstanceOf(ApkFileInfo.class, read);\n\t}\n\n\t/** @see ResourceImporterTest#testImportFileWithExeHeaderAsZipIfZipContentsAreValid() */\n\t@Test\n\tvoid testImportFileWithoutZipPrefixHasZipMarkerAssigned() throws IOException {\n\t\t// Create virtual ZIP with single 'Hello.txt' and suffix the file with a PE header.\n\t\tbyte[] zipFileBytes = ZipCreationUtils.createSingleEntryZip(\"Hello.txt\", \"Hello world\".getBytes(StandardCharsets.UTF_8));\n\t\tbyte[] inputBytes = new byte[4096];\n\t\tinputBytes[0] = 0x4D;\n\t\tinputBytes[1] = 0x5A;\n\t\tSystem.arraycopy(zipFileBytes, 0, inputBytes, inputBytes.length - zipFileBytes.length, zipFileBytes.length);\n\t\tByteSource exeSource = ByteSources.wrap(inputBytes);\n\n\t\t// We should import the info as an executable since the header matches\n\t\t// but note that it has the ZIP marker in its contents.\n\t\tInfo read = importer.readInfo(\"example.exe\", exeSource);\n\t\tassertTrue(read.isFile());\n\t\tassertTrue(read.asFile().isNativeLibraryFile());\n\t\tassertFalse(read.asFile().isZipFile());\n\t\tassertEquals(BasicNativeLibraryFileInfo.class, read.getClass());\n\n\t\t// The ZIP marker should exist.\n\t\tassertTrue(ZipMarkerProperty.get(read.asFile()));\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/workspace/io/ResourceImporterTest.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.RepeatedTest;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.info.BasicNativeLibraryFileInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.JarFileInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.properties.builtin.ZipAccessTimeProperty;\nimport software.coley.recaf.info.properties.builtin.ZipCommentProperty;\nimport software.coley.recaf.info.properties.builtin.ZipCreationTimeProperty;\nimport software.coley.recaf.info.properties.builtin.ZipMarkerProperty;\nimport software.coley.recaf.info.properties.builtin.ZipModificationTimeProperty;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.HelloWorld;\nimport software.coley.recaf.util.ZipCreationUtils;\nimport software.coley.recaf.util.io.ByteSource;\nimport software.coley.recaf.util.io.ByteSources;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.util.Arrays;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link ResourceImporter}\n */\nclass ResourceImporterTest {\n\tstatic ResourceImporter importer;\n\n\t@BeforeAll\n\tstatic void setup() {\n\t\timporter = new BasicResourceImporter(\n\t\t\t\tnew BasicInfoImporter(new InfoImporterConfig(), new TextFormatConfig(), new BasicClassPatcher()),\n\t\t\t\tnew ResourceImporterConfig()\n\t\t);\n\t}\n\n\t@Test\n\tvoid testImportSingleClass() throws IOException {\n\t\tJvmClassInfo helloWorldInfo = TestClassUtils.fromRuntimeClass(HelloWorld.class);\n\t\tByteSource classSource = ByteSources.wrap(helloWorldInfo.getBytecode());\n\t\tWorkspaceResource resource = importer.importResource(classSource);\n\n\t\t// Should be aware of the class file as its source\n\t\tassertInstanceOf(WorkspaceFileResource.class, resource,\n\t\t\t\t\"Resource didn't keep single-class as its associated input\");\n\n\t\t// Should have just ONE class in the JVM bundle\n\t\tassertEquals(1, resource.getJvmClassBundle().size());\n\t\tassertEquals(0, resource.getVersionedJvmClassBundles().size());\n\t\tassertEquals(0, resource.getAndroidClassBundles().size());\n\t\tassertEquals(0, resource.getFileBundle().size());\n\t\tassertEquals(0, resource.getEmbeddedResources().size());\n\t\tassertNull(resource.getContainingResource());\n\t\tassertFalse(resource.isEmbeddedResource());\n\t\tassertFalse(resource.isInternal());\n\n\t\t// Validate JVM class bundle content\n\t\tJvmClassInfo classInfo = resource.getJvmClassBundle().iterator().next();\n\t\tassertEquals(helloWorldInfo, classInfo, \"Missing data compared to baseline input class\");\n\t}\n\n\t@Test\n\tvoid testImportSingleFile() throws IOException {\n\t\tbyte[] helloBytes = \"Hello\".getBytes(StandardCharsets.UTF_8);\n\t\tByteSource classSource = ByteSources.wrap(helloBytes);\n\t\tWorkspaceResource resource = importer.importResource(classSource);\n\n\t\t// Should be aware of the class file as its source\n\t\tassertInstanceOf(WorkspaceFileResource.class, resource,\n\t\t\t\t\"Resource didn't keep single-file as its associated input\");\n\n\t\t// Should have just ONE file in the file bundle\n\t\tassertEquals(0, resource.getJvmClassBundle().size());\n\t\tassertEquals(0, resource.getVersionedJvmClassBundles().size());\n\t\tassertEquals(0, resource.getAndroidClassBundles().size());\n\t\tassertEquals(1, resource.getFileBundle().size());\n\t\tassertEquals(0, resource.getEmbeddedResources().size());\n\t\tassertNull(resource.getContainingResource());\n\t\tassertFalse(resource.isEmbeddedResource());\n\t\tassertFalse(resource.isInternal());\n\n\t\t// Validate file bundle content\n\t\tFileInfo fileInfo = resource.getFileBundle().iterator().next();\n\t\tassertArrayEquals(helloBytes, fileInfo.getRawContent(), \"Missing data compared to baseline input bytes\");\n\t}\n\n\t@Test\n\tvoid testImportMultiReleaseVersionedClasses() throws IOException {\n\t\tString helloWorldPath = HelloWorld.class.getName().replace(\".\", \"/\");\n\t\tbyte[] helloWorldBytes = TestClassUtils.fromRuntimeClass(HelloWorld.class).getBytecode();\n\n\t\t// Create JAR with 'HelloWorld' declared multiple times in different multi-release directories.\n\t\t// None should overlap.\n\t\tMap<String, byte[]> map = new LinkedHashMap<>();\n\t\tmap.put(helloWorldPath + \".class\", helloWorldBytes);\n\t\tmap.put(JarFileInfo.MULTI_RELEASE_PREFIX + \"9/\" + helloWorldPath + \".class\", helloWorldBytes);\n\t\tmap.put(JarFileInfo.MULTI_RELEASE_PREFIX + \"10/\" + helloWorldPath + \".class\", helloWorldBytes);\n\t\tmap.put(JarFileInfo.MULTI_RELEASE_PREFIX + \"11/\" + helloWorldPath + \".class\", helloWorldBytes);\n\t\tbyte[] zipBytes = ZipCreationUtils.createZip(map);\n\t\tByteSource zipSource = ByteSources.wrap(zipBytes);\n\t\tWorkspaceResource resource = importer.importResource(zipSource);\n\n\t\t// 1 in the JVM bundle, 3 in each version bundle\n\t\tassertEquals(1, resource.getJvmClassBundle().size());\n\t\tassertEquals(3, resource.getVersionedJvmClassBundles().size());\n\t\tassertEquals(0, resource.getAndroidClassBundles().size());\n\t\tassertEquals(0, resource.getFileBundle().size());\n\t\tassertEquals(0, resource.getEmbeddedResources().size());\n\n\t\t// Validate class bundle content\n\t\tassertArrayEquals(helloWorldBytes, resource.getJvmClassBundle().iterator().next().getBytecode());\n\t\tfor (JvmClassBundle versioned : resource.getVersionedJvmClassBundles().values()) {\n\t\t\tassertArrayEquals(helloWorldBytes, versioned.iterator().next().getBytecode());\n\t\t}\n\t}\n\n\t@Test\n\tvoid testImportVersionedClassOnlyWhenNameMatches() throws IOException {\n\t\tString helloWorldPath = HelloWorld.class.getName().replace(\".\", \"/\");\n\t\tbyte[] helloWorldBytes = TestClassUtils.fromRuntimeClass(HelloWorld.class).getBytecode();\n\n\t\t// Create JAR with 'HelloWorld' declared twice.\n\t\t// Once correctly as a Java 9 versioned class.\n\t\t// Once incorrectly as a Java 10 versioned class. This one will be added as a file instead.\n\t\tString validName = JarFileInfo.MULTI_RELEASE_PREFIX + \"9/\" + helloWorldPath + \".class\";\n\t\tString invalidName = JarFileInfo.MULTI_RELEASE_PREFIX + \"10/Bogus.class\";\n\t\tMap<String, byte[]> map = new LinkedHashMap<>();\n\t\tmap.put(validName, helloWorldBytes);\n\t\tmap.put(invalidName, helloWorldBytes);\n\t\tbyte[] zipBytes = ZipCreationUtils.createZip(map);\n\t\tByteSource zipSource = ByteSources.wrap(zipBytes);\n\t\tWorkspaceResource resource = importer.importResource(zipSource);\n\n\t\t// 1 in the JVM bundle, 3 in each version bundle\n\t\tassertEquals(0, resource.getJvmClassBundle().size());\n\t\tassertEquals(1, resource.getVersionedJvmClassBundles().size());\n\t\tassertEquals(0, resource.getAndroidClassBundles().size());\n\t\tassertEquals(1, resource.getFileBundle().size());\n\t\tassertEquals(0, resource.getEmbeddedResources().size());\n\n\t\t// Validate the added file is the invalid path item\n\t\tassertEquals(invalidName, resource.getFileBundle().iterator().next().getName());\n\t}\n\n\t@Test\n\tvoid testSupportClassFakeDirectory() throws IOException {\n\t\tString helloWorldPath = HelloWorld.class.getName().replace(\".\", \"/\");\n\t\tbyte[] helloWorldBytes = TestClassUtils.fromRuntimeClass(HelloWorld.class).getBytecode();\n\n\t\t// Create JAR with 'HelloWorld' declared but the class file has a trailing '/' in the entry name.\n\t\tMap<String, byte[]> map = new LinkedHashMap<>();\n\t\tmap.put(helloWorldPath + \".class/\", helloWorldBytes);\n\t\tbyte[] zipBytes = ZipCreationUtils.createZip(map);\n\t\tByteSource zipSource = ByteSources.wrap(zipBytes);\n\t\tWorkspaceResource resource = importer.importResource(zipSource);\n\n\t\t// 1 in the JVM bundle, 3 in each version bundle\n\t\tassertEquals(1, resource.getJvmClassBundle().size());\n\t\tassertEquals(0, resource.getVersionedJvmClassBundles().size());\n\t\tassertEquals(0, resource.getAndroidClassBundles().size());\n\t\tassertEquals(0, resource.getFileBundle().size());\n\t\tassertEquals(0, resource.getEmbeddedResources().size());\n\n\t\t// Validate JVM class bundle content\n\t\tassertArrayEquals(helloWorldBytes, resource.getJvmClassBundle().iterator().next().getBytecode());\n\t}\n\n\t@Test\n\tvoid testAlwaysUseLastClassEntry() throws IOException {\n\t\tString helloWorldPath = HelloWorld.class.getName().replace(\".\", \"/\");\n\t\tbyte[] helloWorldBytes = TestClassUtils.fromRuntimeClass(HelloWorld.class).getBytecode();\n\t\tbyte[] emptyBytes = new byte[0];\n\n\t\t// Create JAR with duplicate entries, with the last entry by the same name containing real data.\n\t\t// The first is a red herring and should be ignored, as the JVM does when it encounters repeats.\n\t\tbyte[] zipBytes = ZipCreationUtils.builder()\n\t\t\t\t.add(helloWorldPath + \".class\", emptyBytes)\n\t\t\t\t.add(helloWorldPath + \".class\", helloWorldBytes)\n\t\t\t\t.bytes();\n\t\tByteSource zipSource = ByteSources.wrap(zipBytes);\n\t\tWorkspaceResource resource = importer.importResource(zipSource);\n\n\t\t// Should only have the one JVM class, the duplicate gets added as a file.\n\t\tassertEquals(1, resource.getJvmClassBundle().size());\n\t\tassertEquals(0, resource.getVersionedJvmClassBundles().size());\n\t\tassertEquals(0, resource.getAndroidClassBundles().size());\n\t\tassertEquals(1, resource.getFileBundle().size());\n\t\tassertEquals(0, resource.getEmbeddedResources().size());\n\n\t\t// Validate the version chosen is the last one\n\t\tassertArrayEquals(helloWorldBytes, resource.getJvmClassBundle().iterator().next().getBytecode());\n\t\tassertArrayEquals(emptyBytes, resource.getFileBundle().iterator().next().getRawContent());\n\t}\n\n\t@Test\n\tvoid testAlwaysUseLastMultiReleaseClassEntry() throws IOException {\n\t\tString helloWorldPath = HelloWorld.class.getName().replace(\".\", \"/\");\n\t\tbyte[] helloWorldBytes = TestClassUtils.fromRuntimeClass(HelloWorld.class).getBytecode();\n\t\tbyte[] emptyBytes = new byte[0];\n\n\t\t// Create JAR with 'HelloWorld' declared as duplicate entries in multi-release directories.\n\t\tbyte[] zipBytes = ZipCreationUtils.builder()\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"9/\" + helloWorldPath + \".class\", emptyBytes)\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"9/\" + helloWorldPath + \".class\", helloWorldBytes)\n\t\t\t\t.bytes();\n\t\tByteSource zipSource = ByteSources.wrap(zipBytes);\n\t\tWorkspaceResource resource = importer.importResource(zipSource);\n\n\t\t// Should only have the one versioned bundle, the duplicate gets added as a file.\n\t\tassertEquals(0, resource.getJvmClassBundle().size());\n\t\tassertEquals(1, resource.getVersionedJvmClassBundles().size());\n\t\tassertEquals(0, resource.getAndroidClassBundles().size());\n\t\tassertEquals(1, resource.getFileBundle().size());\n\t\tassertEquals(0, resource.getEmbeddedResources().size());\n\n\t\t// Validate the version chosen is the last one\n\t\tassertArrayEquals(helloWorldBytes, resource.getVersionedJvmClassBundles().get(9).iterator().next().getBytecode());\n\t\tassertArrayEquals(emptyBytes, resource.getFileBundle().iterator().next().getRawContent());\n\t}\n\n\t/**\n\t * This test is repeated to verify it wasn't successful on a race condition <i>(since our IO is multithreaded by default)</i>.\n\t * I know, this isn't an exhaustive/perfect test, but it is better than a single test run.\n\t */\n\t@RepeatedTest(100)\n\tvoid testAlwaysUseLastFileEntry() throws IOException {\n\t\tString path = HelloWorld.class.getName().replace(\".\", \"/\");\n\t\tbyte[] bytes = new byte[]{1, 2, 3};\n\n\t\t// Create JAR with duplicate entries, with the last entry by the same name containing real data.\n\t\t// The first is a red herring and should be ignored, as the JVM does when it encounters repeats.\n\t\tbyte[] zipBytes = ZipCreationUtils.builder()\n\t\t\t\t.add(path + \".class\", new byte[0])\n\t\t\t\t.add(path + \".class\", bytes)\n\t\t\t\t.bytes();\n\t\tByteSource zipSource = ByteSources.wrap(zipBytes);\n\t\tWorkspaceResource resource = importer.importResource(zipSource);\n\n\t\t// Should only have the one file\n\t\tassertEquals(0, resource.getJvmClassBundle().size());\n\t\tassertEquals(0, resource.getVersionedJvmClassBundles().size());\n\t\tassertEquals(0, resource.getAndroidClassBundles().size());\n\t\tassertEquals(1, resource.getFileBundle().size());\n\t\tassertEquals(0, resource.getEmbeddedResources().size());\n\n\t\t// Validate the version chosen is the last one\n\t\tassertArrayEquals(bytes, resource.getFileBundle().iterator().next().getRawContent());\n\t}\n\n\t@RepeatedTest(100)\n\tvoid testDeduplicateClasses() throws IOException {\n\t\tString helloWorldPath = HelloWorld.class.getName().replace(\".\", \"/\");\n\t\tbyte[] helloWorldBytes = TestClassUtils.fromRuntimeClass(HelloWorld.class).getBytecode();\n\n\t\t// Create JAR with duplicate entries.\n\t\t//  - case 1: Class is first, followed by 'B.class'\n\t\t//  - case 2: Class is last, preceded by 'B.class'\n\t\tbyte[] zipClassFirst = ZipCreationUtils.builder()\n\t\t\t\t.add(helloWorldPath + \".class\", helloWorldBytes)\n\t\t\t\t.add(\"software/coley/B.class\", helloWorldBytes)\n\t\t\t\t.add(\"B.class\", helloWorldBytes)\n\t\t\t\t.bytes();\n\t\tbyte[] zipClassLast = ZipCreationUtils.builder()\n\t\t\t\t.add(\"software/coley/B.class\", helloWorldBytes)\n\t\t\t\t.add(\"B.class\", helloWorldBytes)\n\t\t\t\t.add(helloWorldPath + \".class\", helloWorldBytes)\n\t\t\t\t.bytes();\n\n\t\t// Both cases should have the same outcome\n\t\tfor (byte[] zipBytes : Arrays.asList(zipClassFirst, zipClassLast)) {\n\t\t\tByteSource zipSource = ByteSources.wrap(zipBytes);\n\t\t\tWorkspaceResource resource = importer.importResource(zipSource);\n\n\t\t\t// Should only have the one JVM class, the duplicate gets added as a fileInfo.\n\t\t\tassertEquals(1, resource.getJvmClassBundle().size());\n\t\t\tassertEquals(0, resource.getVersionedJvmClassBundles().size());\n\t\t\tassertEquals(0, resource.getAndroidClassBundles().size());\n\t\t\tassertEquals(2, resource.getFileBundle().size());\n\t\t\tassertEquals(0, resource.getEmbeddedResources().size());\n\n\t\t\t// Validate the contents of the file bundle.\n\t\t\tFileInfo fileInfo1 = resource.getFileBundle().get(\"B.class\");\n\t\t\tFileInfo fileInfo2 = resource.getFileBundle().get(\"software/coley/B.class\");\n\t\t\tassertNotNull(fileInfo1, \"Deduplication did not copy wrong path class to files bundle: B.class\");\n\t\t\tassertNotNull(fileInfo2, \"Deduplication did not copy wrong path class to files bundle: software/coley/B.class\");\n\t\t}\n\t}\n\n\t@Test\n\tvoid testDeduplicateVersionedClasses() throws IOException {\n\t\tString helloWorldPath = HelloWorld.class.getName().replace(\".\", \"/\");\n\t\tbyte[] helloWorldBytes = TestClassUtils.fromRuntimeClass(HelloWorld.class).getBytecode();\n\n\t\t// Create JAR with duplicate entries.\n\t\t//  - case 1: Class is first, followed by 'B.class'\n\t\t//  - case 2: Class is last, preceded by 'B.class'\n\t\tbyte[] zipClassFirst = ZipCreationUtils.builder()\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"9/\" + helloWorldPath + \".class\", helloWorldBytes)\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"9/B.class\", helloWorldBytes)\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"9/software/coley/B.class\", helloWorldBytes)\n\t\t\t\t.bytes();\n\t\tbyte[] zipClassLast = ZipCreationUtils.builder()\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"9/software/coley/B.class\", helloWorldBytes)\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"9/B.class\", helloWorldBytes)\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"9/\" + helloWorldPath + \".class\", helloWorldBytes)\n\t\t\t\t.bytes();\n\n\t\t// Both cases should have the same outcome\n\t\tfor (byte[] zipBytes : Arrays.asList(zipClassFirst, zipClassLast)) {\n\t\t\tByteSource zipSource = ByteSources.wrap(zipBytes);\n\t\t\tWorkspaceResource resource = importer.importResource(zipSource);\n\n\t\t\t// Should only have the one JVM class, the duplicate gets added as a fileInfo.\n\t\t\tassertEquals(0, resource.getJvmClassBundle().size());\n\t\t\tassertEquals(1, resource.getVersionedJvmClassBundles().size());\n\t\t\tassertEquals(0, resource.getAndroidClassBundles().size());\n\t\t\tassertEquals(2, resource.getFileBundle().size());\n\t\t\tassertEquals(0, resource.getEmbeddedResources().size());\n\n\t\t\t// Validate the contents of the file bundle.\n\t\t\tFileInfo fileInfo1 = resource.getFileBundle().get(JarFileInfo.MULTI_RELEASE_PREFIX + \"9/B.class\");\n\t\t\tFileInfo fileInfo2 = resource.getFileBundle().get(JarFileInfo.MULTI_RELEASE_PREFIX + \"9/software/coley/B.class\");\n\t\t\tassertNotNull(fileInfo1, \"Deduplication did not copy wrong path class to files bundle: B.class\");\n\t\t\tassertNotNull(fileInfo2, \"Deduplication did not copy wrong path class to files bundle: software/coley/B.class\");\n\t\t}\n\t}\n\n\t@Test\n\tvoid testImportZipInsideZip() throws IOException {\n\t\t// create a ZIP holding another ZIP\n\t\tString insideZipName = \"inner.zip\";\n\t\tString innerDataName = \"data\";\n\t\tbyte[] innerData = {1, 2, 3};\n\t\tbyte[] insideZipBytes = ZipCreationUtils.createSingleEntryZip(innerDataName, innerData);\n\t\tbyte[] outsideZipBytes = ZipCreationUtils.createSingleEntryZip(insideZipName, insideZipBytes);\n\t\tByteSource classSource = ByteSources.wrap(outsideZipBytes);\n\t\tWorkspaceResource resource = importer.importResource(classSource);\n\n\t\t// Should be aware of the ZIP file as its source\n\t\tassertInstanceOf(WorkspaceFileResource.class, resource,\n\t\t\t\t\"Resource didn't keep single-file as its associated input\");\n\n\t\t// Should create an embedded resource\n\t\tassertEquals(1, resource.getEmbeddedResources().size(), \"Should have 1 embedded resource\");\n\t\tWorkspaceFileResource insideZipResource = resource.getEmbeddedResources().get(insideZipName);\n\t\tassertNotNull(insideZipResource, \"Incorrect embedded resource path, expected: \" + insideZipName +\n\t\t\t\t\" got \" + insideZipResource.getFileInfo().getName());\n\n\t\t// Should have expected data\n\t\tFileInfo innerDotZip = insideZipResource.getFileBundle().get(innerDataName);\n\t\tassertNotNull(innerDotZip);\n\t\tassertEquals(innerDataName, innerDotZip.getName());\n\t\tassertArrayEquals(innerData, innerDotZip.getRawContent());\n\t}\n\n\t@Test\n\tvoid testImportsFromDifferentSourcesAreTheSame() throws IOException {\n\t\t// Create zip:\n\t\t//  - hello.txt\n\t\t//  - foo.zip (containing foo)\n\t\t//  - bla/bla/bla/HelloWorld.class\n\t\tMap<String, byte[]> map = new LinkedHashMap<>();\n\t\tmap.put(\"hello.txt\", \"Hello world\".getBytes(StandardCharsets.UTF_8));\n\t\tmap.put(HelloWorld.class.getName().replace(\".\", \"/\") + \".class\",\n\t\t\t\tTestClassUtils.fromRuntimeClass(HelloWorld.class).getBytecode());\n\t\tmap.put(\"data.zip\", ZipCreationUtils.createSingleEntryZip(\"foo\", new byte[]{1, 2, 3}));\n\t\tbyte[] zipBytes = ZipCreationUtils.createZip(map);\n\n\t\t// Write to disk temporarily for test duration\n\t\tFile tempFile = File.createTempFile(\"recaf\", \"test.zip\");\n\t\tFiles.write(tempFile.toPath(), zipBytes);\n\t\ttempFile.deleteOnExit();\n\n\t\t// Create workspace resources from each kind of input, all sourced from the same content.\n\t\t// They should all be equal.\n\t\tWorkspaceResource fromByteSource = importer.importResource(ByteSources.wrap(zipBytes));\n\t\tWorkspaceResource fromFile = importer.importResource(tempFile);\n\t\tWorkspaceResource fromPath = importer.importResource(tempFile.toPath());\n\t\tWorkspaceResource fromUri = importer.importResource(tempFile.toURI());\n\t\tWorkspaceResource fromUrl = importer.importResource(tempFile.toURI().toURL());\n\t\tassertEquals(fromByteSource, fromFile);\n\t\tassertEquals(fromByteSource, fromPath);\n\t\tassertEquals(fromByteSource, fromUri);\n\t\tassertEquals(fromByteSource, fromUrl);\n\t}\n\n\t@Test\n\tvoid testSkipDirectories() throws IOException {\n\t\tbyte[] empty = new byte[0];\n\t\tbyte[] fileBytes = \"data\".getBytes(StandardCharsets.UTF_8);\n\t\tbyte[] zipBytes = ZipCreationUtils.builder()\n\t\t\t\t.add(\"com/\", empty)\n\t\t\t\t.add(\"com/example/\", empty)\n\t\t\t\t.add(\"com/example/application/\", empty)\n\t\t\t\t.add(\"com/example/application/Config.txt\", fileBytes)\n\t\t\t\t.bytes();\n\t\tWorkspaceResource resource = importer.importResource(ByteSources.wrap(zipBytes));\n\n\t\t// Should have just ONE file in the file bundle\n\t\tassertEquals(0, resource.getJvmClassBundle().size());\n\t\tassertEquals(0, resource.getVersionedJvmClassBundles().size());\n\t\tassertEquals(0, resource.getAndroidClassBundles().size());\n\t\tassertEquals(1, resource.getFileBundle().size());\n\t\tassertEquals(0, resource.getEmbeddedResources().size());\n\t\tassertNull(resource.getContainingResource());\n\t\tassertFalse(resource.isEmbeddedResource());\n\t\tassertFalse(resource.isInternal());\n\n\t\t// Validate file bundle content\n\t\tFileInfo fileInfo = resource.getFileBundle().iterator().next();\n\t\tassertArrayEquals(fileBytes, fileInfo.getRawContent(), \"Missing data compared to baseline input bytes\");\n\t}\n\n\t@Test\n\tvoid testZipProperties() throws IOException {\n\t\t// Property declarations\n\t\tString comment = \"comment\";\n\t\tlong timeCreate = 1000000000000L;\n\t\tlong timeModify = 1200000000000L;\n\t\tlong timeAccess = 1400000000000L;\n\n\t\t// Create the file\n\t\tString name = \"com/example/application/Config.txt\";\n\t\tbyte[] data = new byte[]{1, 2, 3, 4, 5};\n\t\tbyte[] zipBytes = ZipCreationUtils.builder()\n\t\t\t\t.add(name, data, false, comment, timeCreate, timeModify, timeAccess)\n\t\t\t\t.bytes();\n\t\tWorkspaceResource resource = importer.importResource(ByteSources.wrap(zipBytes));\n\n\t\t// Should have just ONE file in the file bundle\n\t\tassertEquals(0, resource.getJvmClassBundle().size());\n\t\tassertEquals(0, resource.getVersionedJvmClassBundles().size());\n\t\tassertEquals(0, resource.getAndroidClassBundles().size());\n\t\tassertEquals(1, resource.getFileBundle().size());\n\t\tassertEquals(0, resource.getEmbeddedResources().size());\n\t\tassertNull(resource.getContainingResource());\n\t\tassertFalse(resource.isEmbeddedResource());\n\t\tassertFalse(resource.isInternal());\n\n\t\t// Validate file bundle content\n\t\tFileInfo fileInfo = resource.getFileBundle().iterator().next();\n\t\tassertArrayEquals(data, fileInfo.getRawContent(), \"Missing data compared to baseline input bytes\");\n\t\tassertEquals(comment, ZipCommentProperty.get(fileInfo), \"Missing comment\");\n\t\tassertEquals(timeCreate, ZipCreationTimeProperty.get(fileInfo), \"Missing creation time\");\n\t\tassertEquals(timeModify, ZipModificationTimeProperty.get(fileInfo), \"Missing modification time\");\n\t\tassertEquals(timeAccess, ZipAccessTimeProperty.get(fileInfo), \"Missing access time\");\n\t}\n\n\t/** @see InfoImporterTest#testImportFileWithoutZipPrefixHasZipMarkerAssigned() */\n\t@Test\n\tvoid testImportFileWithExeHeaderAsZipIfZipContentsAreValid() throws IOException {\n\t\t// Create virtual ZIP with single 'Hello.txt' and suffix the file with a PE header.\n\t\tbyte[] zipFileBytes = ZipCreationUtils.createSingleEntryZip(\"Hello.txt\", \"Hello world\".getBytes(StandardCharsets.UTF_8));\n\t\tbyte[] inputBytes = new byte[4096];\n\t\tinputBytes[0] = 0x4D;\n\t\tinputBytes[1] = 0x5A;\n\t\tSystem.arraycopy(zipFileBytes, 0, inputBytes, inputBytes.length - zipFileBytes.length, zipFileBytes.length);\n\t\tByteSource exeSource = ByteSources.wrap(inputBytes);\n\n\t\t// When we import the \"executable\" we should still load the ZIP file contents since it has a ZIP marker\n\t\t// and is able to be processed as a ZIP archive.\n\t\tWorkspaceResource resource = importer.importResource(exeSource);\n\t\tassertTrue(resource.getFileBundle().containsKey(\"Hello.txt\"));\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/workspace/io/WorkspaceExporterTest.java",
    "content": "package software.coley.recaf.services.workspace.io;\n\nimport com.google.common.primitives.Bytes;\nimport jakarta.annotation.Nonnull;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.info.JarFileInfo;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.HelloWorld;\nimport software.coley.recaf.util.ZipCreationUtils;\nimport software.coley.recaf.util.io.ByteSources;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.TreeSet;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link WorkspaceExporter}\n */\nclass WorkspaceExporterTest {\n\tstatic ResourceImporter importer;\n\n\t@BeforeAll\n\tstatic void setup() {\n\t\timporter = new BasicResourceImporter(\n\t\t\t\tnew BasicInfoImporter(new InfoImporterConfig(), new TextFormatConfig(), new BasicClassPatcher()),\n\t\t\t\tnew ResourceImporterConfig()\n\t\t);\n\t}\n\n\t@Test\n\tvoid testFileExportDoesNotTamperResourceModel() throws IOException {\n\t\ttestExportDoesNotTamperResourceModel(WorkspaceOutputType.FILE);\n\t}\n\n\t@Test\n\tvoid testDirectoryExportDoesNotTamperResourceModel() throws IOException {\n\t\ttestExportDoesNotTamperResourceModel(WorkspaceOutputType.DIRECTORY);\n\t}\n\n\tprivate static void testExportDoesNotTamperResourceModel(WorkspaceOutputType outputType) throws IOException {\n\t\t// Create test ZIP in memory\n\t\tbyte[] embeddedZipBytes = ZipCreationUtils.createSingleEntryZip(\"inside.txt\", new byte[0]);\n\t\tString helloWorldPath = HelloWorld.class.getName().replace(\".\", \"/\");\n\t\tbyte[] helloWorldBytes = TestClassUtils.fromRuntimeClass(HelloWorld.class).getBytecode();\n\t\tbyte[] targetZipBytes = ZipCreationUtils.builder()\n\t\t\t\t.add(helloWorldPath + \".class\", helloWorldBytes)\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"9/\" + helloWorldPath + \".class\", helloWorldBytes)\n\t\t\t\t.add(\"hello.txt\", \"hello world\".getBytes(StandardCharsets.UTF_8))\n\t\t\t\t.add(\"test.zip\", embeddedZipBytes)\n\t\t\t\t.bytes();\n\n\t\t// Workspace sourced from ZIP\n\t\tWorkspaceResource targetResource = importer.importResource(ByteSources.wrap(targetZipBytes));\n\t\tWorkspace workspace = new BasicWorkspace(targetResource);\n\n\t\t// Export the resource\n\t\tPath temp;\n\t\tswitch (outputType) {\n\t\t\tcase DIRECTORY:\n\t\t\t\ttemp = Files.createTempDirectory(\"recaf\");\n\t\t\t\ttemp.toFile().deleteOnExit();\n\t\t\t\tbreak;\n\t\t\tcase FILE:\n\t\t\tdefault:\n\t\t\t\ttemp = Files.createTempFile(\"recaf\", \"test.zip\");\n\t\t\t\ttemp.toFile().deleteOnExit();\n\t\t\t\tbreak;\n\t\t}\n\n\t\tWorkspaceExportOptions options = new WorkspaceExportOptions(outputType, new PathWorkspaceExportConsumer(temp));\n\t\toptions.setCreateZipDirEntries(true);\n\t\toptions.setBundleSupporting(false);\n\t\tWorkspaceExporter exporter = options.create();\n\t\texporter.export(workspace);\n\n\t\t// Assert it was written\n\t\tswitch (outputType) {\n\t\t\tcase DIRECTORY:\n\t\t\t\tassertTrue(Files.list(temp).findAny().isPresent(),\n\t\t\t\t\t\t\"Temp directory location for exported workspace not written to!\");\n\t\t\t\tbreak;\n\t\t\tcase FILE:\n\t\t\tdefault:\n\t\t\t\tassertTrue(Files.size(temp) > 0, \"\" +\n\t\t\t\t\t\t\"Temp zip location for exported workspace not written to!\");\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// Now read it back and ensure it is the same.\n\t\t// Because one is a file-backed resource and the other directory-backed, we need to compare the contents rather\n\t\t// than the whole resource.\n\t\tWorkspaceResource importedResource = importer.importResource(temp);\n\t\tassertEquals(targetResource.getJvmClassBundle(), importedResource.getJvmClassBundle());\n\t\tassertEquals(targetResource.getFileBundle(), importedResource.getFileBundle());\n\t\tassertEquals(targetResource.getEmbeddedResources(), importedResource.getEmbeddedResources());\n\t}\n\n\t/**\n\t * There's a lombok fabric mod which bundles some classes with tampered names. The file contents are normal classes.\n\t * When we re-export the workspace we need to ensure the classes are written back to where they originally came from.\n\t */\n\t@Test\n\tvoid testLombokClassPrefixSuffixExport() throws IOException {\n\t\tbyte[] inputZipBytes = Files.readAllBytes(Paths.get(\"src/testFixtures/resources/name-prefix-suffix.jar\"));\n\t\tWorkspaceResource resource = importer.importResource(ByteSources.wrap(inputZipBytes));\n\t\tassertNotNull(resource.getJvmClassBundle().get(\"org/objectweb/asm/Constants\"), \"Missing ASM classes\");\n\t\tBasicWorkspace workspace = new BasicWorkspace(resource);\n\n\t\t// Export the workspace\n\t\tSet<String> paths = new TreeSet<>();\n\t\tnew WorkspaceExportOptions(WorkspaceOutputType.DIRECTORY, new WorkspaceExportConsumer() {\n\t\t\t@Override\n\t\t\tpublic void write(@Nonnull byte[] bytes) {\n\t\t\t\tthrow new RuntimeException(\"Should not be invoked in directory output type\");\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void writeRelative(@Nonnull String relative, @Nonnull byte[] bytes) {\n\t\t\t\tpaths.add(relative);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void commit() {\n\t\t\t\t// no-op\n\t\t\t}\n\t\t}).create().export(workspace);\n\n\t\t// Verify the paths match the input names\n\t\tassertTrue(paths.contains(\"SCL.lombok/org/objectweb/asm/Constants.SCL.lombok\"),\n\t\t\t\t\"Lombok's bundled ASM classes not written to expected path\");\n\t}\n\n\t@Test\n\tvoid testNameDifferenceExport() throws IOException {\n\t\tbyte[] inputZipBytes = Files.readAllBytes(Paths.get(\"src/testFixtures/resources/name-difference.zip\"));\n\t\tWorkspaceResource resource = importer.importResource(ByteSources.wrap(inputZipBytes));\n\t\tassertNotNull(resource.getJvmClassBundle().get(\"org/objectweb/asm/Constants\"), \"Missing ASM classes\");\n\t\tBasicWorkspace workspace = new BasicWorkspace(resource);\n\n\t\t// Export the workspace\n\t\tSet<String> paths = new TreeSet<>();\n\t\tnew WorkspaceExportOptions(WorkspaceOutputType.DIRECTORY, new WorkspaceExportConsumer() {\n\t\t\t@Override\n\t\t\tpublic void write(@Nonnull byte[] bytes) {\n\t\t\t\tthrow new RuntimeException(\"Should not be invoked in directory output type\");\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void writeRelative(@Nonnull String relative, @Nonnull byte[] bytes) {\n\t\t\t\tpaths.add(relative);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void commit() {\n\t\t\t\t// no-op\n\t\t\t}\n\t\t}).create().export(workspace);\n\n\t\t// Verify the paths match the input names\n\t\tassertTrue(paths.contains(\"SomethingElse.bin\"), \"Class was not written back to expected name\");\n\t}\n\n\t@Test\n\tvoid testArbitraryHeaderDataIsKeptAfterExport() throws IOException {\n\t\t// Read a regular ZIP file and then pre-pend a lot of junk data to the front of it\n\t\tRandom random = new Random(2410L);\n\t\tbyte[] zipBytes = Files.readAllBytes(Paths.get(\"src/testFixtures/resources/name-difference.zip\"));\n\t\tbyte[] junkBytes = new byte[(int) Math.pow(2, 16)];\n\t\trandom.nextBytes(junkBytes);\n\t\tbyte[] concatJunkThenZip = Bytes.concat(junkBytes, zipBytes);\n\n\t\t// Import the data with the junk in the front\n\t\tWorkspaceResource resource = importer.importResource(ByteSources.wrap(concatJunkThenZip));\n\t\tBasicWorkspace workspace = new BasicWorkspace(resource);\n\n\t\t// Export it, and the junk should still be in the front, and the zip should still be in the back\n\t\tByteArrayWorkspaceExportConsumer bytesExport = new ByteArrayWorkspaceExportConsumer();\n\t\tnew WorkspaceExportOptions(WorkspaceOutputType.FILE, bytesExport).create().export(workspace);\n\t\tbyte[] output = bytesExport.getOutput();\n\n\t\t// Verify the junk is still in the front of the output.\n\t\tassertNotNull(output, \"Failed to export workspace to archive\");\n\t\tassertEquals(0, Bytes.indexOf(output, junkBytes), \"Failed to re-append prefix junk data to archive\");\n\n\t\t// Verify the ZIP contents appear after the junk\n\t\tbyte[] zipBytesHead = Arrays.copyOf(zipBytes, 0x30);\n\t\tbyte[] outBytesHead = Arrays.copyOfRange(output, junkBytes.length, junkBytes.length + 0x30);\n\t\tfor (int i = 0; i < 4; i++) {\n\t\t\tassertEquals(zipBytesHead[i], outBytesHead[i], \"Mismatch where normal ZIP is supposed to be appended\");\n\t\t}\n\t\tString zipHeadName = new String(zipBytesHead, StandardCharsets.ISO_8859_1).substring(30, 47);\n\t\tString outHeadName = new String(outBytesHead, StandardCharsets.ISO_8859_1).substring(30, 47);\n\t\tassertEquals(\"SomethingElse.bin\", outHeadName, \"Mismatch in outputs expected first ZIP entry name\");\n\t\tassertEquals(zipHeadName, outHeadName, \"Mismatch where normal ZIP entry for 'SomethingElse.bin' is supposed to be\");\n\t}\n\n\t@Test\n\tvoid testNonArchiveExportedAsIsAndNotBundledInAZipContainer() throws IOException {\n\t\t// Make a basic [0, 1, 2, 3... 100]\n\t\tbyte[] bytes = new byte[100];\n\t\tfor (int i = 0; i < bytes.length; i++)\n\t\t\tbytes[i] = (byte) i;\n\n\t\t// Import the data.\n\t\tWorkspaceResource resource = importer.importResource(ByteSources.wrap(bytes));\n\t\tBasicWorkspace workspace = new BasicWorkspace(resource);\n\n\t\t// Export it as a file.\n\t\tByteArrayWorkspaceExportConsumer bytesExport = new ByteArrayWorkspaceExportConsumer();\n\t\tnew WorkspaceExportOptions(WorkspaceOutputType.FILE, bytesExport).create().export(workspace);\n\t\tbyte[] output = bytesExport.getOutput();\n\n\t\t// Verify the contents are the exact same. Because it's not an archive we should not be\n\t\t// exporting it back bundled inside an archive.\n\t\tassertArrayEquals(bytes, output, \"Expected input and output to be exact match\");\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/services/workspace/patch/PatchingTest.java",
    "content": "package software.coley.recaf.services.workspace.patch;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.error.Error;\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.ClassWriter;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.StubClassInfo;\nimport software.coley.recaf.info.StubFileInfo;\nimport software.coley.recaf.info.TextFileInfo;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.workspace.patch.model.WorkspacePatch;\nimport software.coley.recaf.test.TestBase;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.HelloWorld;\nimport software.coley.recaf.util.visitors.MethodNoopingVisitor;\nimport software.coley.recaf.util.visitors.MethodPredicate;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link PatchProvider} and {@link PatchApplier}\n */\nclass PatchingTest extends TestBase {\n\tprivate static final PatchProvider patchProvider;\n\tprivate static final PatchApplier patchApplier;\n\n\tstatic {\n\t\tpatchProvider = recaf.get(PatchProvider.class);\n\t\tpatchApplier = recaf.get(PatchApplier.class);\n\t}\n\n\t@Test\n\tvoid testClass_methodNoop() throws Throwable {\n\t\tJvmClassInfo initialClass = TestClassUtils.fromRuntimeClass(HelloWorld.class);\n\t\tBasicJvmClassBundle classes = TestClassUtils.fromClasses(initialClass);\n\t\tWorkspace workspace = TestClassUtils.fromBundle(classes);\n\n\t\t// Modify the class. We'll just no-op a method to make things simple.\n\t\tClassWriter writer = new ClassWriter(0);\n\t\tMethodNoopingVisitor visitor = new MethodNoopingVisitor(writer, MethodPredicate.of(initialClass.getMethods().getLast()));\n\t\tinitialClass.getClassReader().accept(visitor, 0);\n\t\tJvmClassInfo modifiedClass = initialClass.toJvmClassBuilder().adaptFrom(writer.toByteArray()).build();\n\t\tclasses.put(modifiedClass);\n\n\t\t// Build the patch.\n\t\tWorkspacePatch patch = patchProvider.createPatch(workspace);\n\n\t\t// Assert serialization/deserialization doesn't result in breakage.\n\t\tString serialized = patchProvider.serializePatch(patch);\n\t\tWorkspacePatch deserializePatch = patchProvider.deserializePatch(workspace, serialized);\n\t\tassertEquals(patch, deserializePatch);\n\n\t\t// Undo the change.\n\t\tString classKey = initialClass.getName();\n\t\tclasses.decrementHistory(classKey);\n\t\tassertArrayEquals(initialClass.getBytecode(), classes.get(classKey).getBytecode(), \"Revert failed\");\n\n\t\t// Apply the patch\n\t\tassertTrue(patchApplier.apply(patch, failOnErrors()));\n\n\t\t// Validate the patch was applied\n\t\tJvmClassInfo patchedClassInfo = classes.get(classKey);\n\t\tassertNotSame(initialClass, patchedClassInfo, \"Class bundle post-patch yielded initial class state\");\n\t}\n\n\t@Test\n\tvoid testFile_textDiff() throws PatchGenerationException {\n\t\tTextFileInfo textFile = new StubFileInfo(\"foo.txt\").withText(\"\"\"\n\t\t\t\tone\n\t\t\t\ttwo\n\t\t\t\tthree\n\t\t\t\t\"\"\");\n\t\tTextFileInfo textFileAlt = new StubFileInfo(textFile.getName()).withText(\"\"\"\n\t\t\t\tone\n\t\t\t\tfive\n\t\t\t\tthree\n\t\t\t\t\"\"\");\n\t\tFileBundle fileInfos = TestClassUtils.fromFiles(textFile);\n\t\tWorkspace workspace = TestClassUtils.fromBundle(fileInfos);\n\n\t\t// Modify the file\n\t\tfileInfos.put(textFileAlt);\n\n\t\t// Build the patch.\n\t\tWorkspacePatch patch = patchProvider.createPatch(workspace);\n\n\t\t// Assert serialization/deserialization doesn't result in breakage.\n\t\tString serialized = patchProvider.serializePatch(patch);\n\t\tWorkspacePatch deserializePatch = patchProvider.deserializePatch(workspace, serialized);\n\t\tassertEquals(patch, deserializePatch);\n\n\t\t// Undo the change.\n\t\tString fileKey = textFile.getName();\n\t\tfileInfos.decrementHistory(fileKey);\n\t\tTextFileInfo revertedTextFile = fileInfos.get(fileKey).asTextFile();\n\t\tassertEquals(textFile.getText(), revertedTextFile.getText(), \"Revert failed\");\n\n\t\t// Apply the patch\n\t\tassertTrue(patchApplier.apply(patch, failOnErrors()));\n\n\t\t// Validate the patch was applied\n\t\tTextFileInfo patchedTextFile = fileInfos.get(fileKey).asTextFile();\n\t\tassertNotEquals(textFile, patchedTextFile, \"File bundle post-patch yielded initial file state\");\n\t\tassertEquals(textFileAlt, patchedTextFile, \"File bundle post-patch yielded unexpected state\");\n\t}\n\n\t@Test\n\tvoid testRemove_file() throws Throwable {\n\t\tStubFileInfo bar = new StubFileInfo(\"bar\");\n\t\tFileBundle fileBundle = TestClassUtils.fromFiles(new StubFileInfo(\"foo\"), bar, new StubFileInfo(\"fizz\"));\n\t\tWorkspace workspace = TestClassUtils.fromBundle(fileBundle);\n\n\t\t// Remove 'bar'\n\t\tfileBundle.remove(bar.getName());\n\n\t\t// Build the patch.\n\t\tWorkspacePatch patch = patchProvider.createPatch(workspace);\n\n\t\t// Assert the patch has the removal\n\t\tassertEquals(1, patch.removals().size(), \"Expected 1 file removal\");\n\n\t\t// Assert serialization/deserialization doesn't result in breakage.\n\t\tString serialized = patchProvider.serializePatch(patch);\n\t\tWorkspacePatch deserializePatch = patchProvider.deserializePatch(workspace, serialized);\n\t\tassertEquals(patch, deserializePatch);\n\n\t\t// Undo the change.\n\t\tfileBundle.put(bar);\n\n\t\t// Apply the patch\n\t\tassertTrue(patchApplier.apply(patch, failOnErrors()));\n\n\t\t// Validate the patch was applied\n\t\tassertNull(fileBundle.get(bar.getName()), \"File bundle post-patch did not remove 'bar'\");\n\t}\n\n\t@Test\n\tvoid testRemove_jvmClass() throws Throwable {\n\t\tJvmClassInfo bar = new StubClassInfo(\"bar\").asJvmClass();\n\t\tJvmClassBundle classBundle = TestClassUtils.fromClasses(new StubClassInfo(\"foo\").asJvmClass(), bar, new StubClassInfo(\"fizz\").asJvmClass());\n\t\tWorkspace workspace = TestClassUtils.fromBundle(classBundle);\n\n\t\t// Remove 'bar'\n\t\tclassBundle.remove(bar.getName());\n\n\t\t// Build the patch.\n\t\tWorkspacePatch patch = patchProvider.createPatch(workspace);\n\n\t\t// Assert the patch has the removal\n\t\tassertEquals(1, patch.removals().size(), \"Expected 1 class removal\");\n\n\t\t// Assert serialization/deserialization doesn't result in breakage.\n\t\tString serialized = patchProvider.serializePatch(patch);\n\t\tWorkspacePatch deserializePatch = patchProvider.deserializePatch(workspace, serialized);\n\t\tassertEquals(patch, deserializePatch);\n\n\t\t// Undo the change.\n\t\tclassBundle.put(bar);\n\n\t\t// Apply the patch\n\t\tassertTrue(patchApplier.apply(patch, failOnErrors()));\n\n\t\t// Validate the patch was applied\n\t\tassertNull(classBundle.get(bar.getName()), \"Class bundle post-patch did not remove 'bar'\");\n\t}\n\n\t@Nonnull\n\tprivate PatchFeedback failOnErrors() {\n\t\treturn new PatchFeedback() {\n\t\t\t@Override\n\t\t\tpublic void onAssemblerErrorsObserved(@Nonnull List<Error> errors) {\n\t\t\t\tfail(\"Errors encountered applying patch: \" + errors.stream().map(Error::getMessage).collect(Collectors.joining(\", \")));\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onIncompletePathObserved(@Nonnull PathNode<?> path) {\n\t\t\t\tfail(\"Incomplete path: \" + path);\n\t\t\t}\n\t\t};\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/util/AccessFlagTest.java",
    "content": "package software.coley.recaf.util;\n\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.Opcodes;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link AccessFlag}\n */\nclass AccessFlagTest {\n\t@Test\n\tvoid testGetFlag() {\n\t\tassertEquals(AccessFlag.ACC_STATIC, AccessFlag.getFlag(\"static\"));\n\t}\n\n\t@Test\n\tvoid testGetFlags() {\n\t\tassertEquals(Set.of(), AccessFlag.getFlags(0));\n\t\tassertEquals(Set.of(AccessFlag.ACC_PUBLIC), AccessFlag.getFlags(Opcodes.ACC_PUBLIC));\n\t\tassertEquals(Set.of(AccessFlag.ACC_STATIC), AccessFlag.getFlags(Opcodes.ACC_STATIC));\n\t\tassertEquals(List.of(AccessFlag.ACC_PUBLIC, AccessFlag.ACC_STATIC), AccessFlag.getFlags(\"public static\"));\n\t\tassertEquals(List.of(AccessFlag.ACC_STATIC, AccessFlag.ACC_STATIC), AccessFlag.getFlags(\"static static\"));\n\t}\n\n\t@Test\n\tvoid testToString() {\n\t\tassertEquals(\"public static final\", AccessFlag.toString(List.of(AccessFlag.ACC_PUBLIC, AccessFlag.ACC_STATIC, AccessFlag.ACC_FINAL)));\n\n\t\t// Super flag ignored\n\t\tassertEquals(\"\", AccessFlag.toString(List.of(AccessFlag.ACC_SUPER)));\n\t}\n\n\t@Test\n\tvoid testSortAndToString() {\n\t\tassertEquals(\"private static final\", AccessFlag.sortAndToString(AccessFlag.Type.FIELD,\n\t\t\t\tList.of(AccessFlag.ACC_FINAL, AccessFlag.ACC_PRIVATE, AccessFlag.ACC_STATIC)));\n\t\tassertEquals(\"private static final\", AccessFlag.sortAndToString(AccessFlag.Type.FIELD,\n\t\t\t\tOpcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC));\n\t}\n\n\t@Test\n\tvoid testGetCodeFriendlyName() {\n\t\tassertEquals(\"public\", AccessFlag.ACC_PUBLIC.getCodeFriendlyName());\n\t\tassertEquals(\"/* synthetic */\", AccessFlag.ACC_SYNTHETIC.getCodeFriendlyName());\n\t\tassertEquals(\"/* bridge */\", AccessFlag.ACC_BRIDGE.getCodeFriendlyName());\n\t}\n\n\t@Test\n\tvoid testGetApplicableFlags() {\n\t\t// All applicable flags\n\t\tassertEquals(Set.of(AccessFlag.ACC_PUBLIC, AccessFlag.ACC_PRIVATE, AccessFlag.ACC_PROTECTED,\n\t\t\t\t\t\tAccessFlag.ACC_STATIC, AccessFlag.ACC_FINAL, AccessFlag.ACC_VOLATILE,\n\t\t\t\t\t\tAccessFlag.ACC_TRANSIENT, AccessFlag.ACC_SYNTHETIC, AccessFlag.ACC_ENUM),\n\t\t\t\tAccessFlag.getApplicableFlags(AccessFlag.Type.FIELD));\n\n\t\t// Filter with mask\n\t\tassertEquals(Set.of(AccessFlag.ACC_PUBLIC, AccessFlag.ACC_STATIC),\n\t\t\t\tAccessFlag.getApplicableFlags(AccessFlag.Type.FIELD, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC));\n\t}\n\n\t@Test\n\tvoid testSort() {\n\t\tList<AccessFlag> correctOrder = List.of(AccessFlag.ACC_PRIVATE, AccessFlag.ACC_STATIC, AccessFlag.ACC_FINAL);\n\t\tassertEquals(correctOrder, AccessFlag.sort(AccessFlag.Type.FIELD, correctOrder));\n\t\tassertEquals(correctOrder, AccessFlag.sort(AccessFlag.Type.FIELD, List.of(AccessFlag.ACC_STATIC, AccessFlag.ACC_FINAL, AccessFlag.ACC_PRIVATE)));\n\t}\n\n\t@Test\n\tvoid testCreateAccess() {\n\t\tassertEquals(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, AccessFlag.createAccess(AccessFlag.ACC_STATIC, AccessFlag.ACC_PUBLIC));\n\t}\n\n\t@Test\n\tvoid testHasAll() {\n\t\tassertTrue(AccessFlag.hasAll(0, Collections.emptyList()));\n\t\tassertTrue(AccessFlag.hasAll(Opcodes.ACC_PUBLIC, List.of(AccessFlag.ACC_PUBLIC)));\n\t\tassertFalse(AccessFlag.hasAll(Opcodes.ACC_PUBLIC, List.of(AccessFlag.ACC_PUBLIC, AccessFlag.ACC_STATIC)));\n\n\t\t// var-args form\n\t\tassertTrue(AccessFlag.hasAll(0));\n\t\tassertTrue(AccessFlag.hasAll(Opcodes.ACC_PUBLIC, AccessFlag.ACC_PUBLIC));\n\t\tassertFalse(AccessFlag.hasAll(Opcodes.ACC_PUBLIC, AccessFlag.ACC_PUBLIC, AccessFlag.ACC_STATIC));\n\t}\n\n\t@Test\n\tvoid testHasNone() {\n\t\tassertTrue(AccessFlag.hasNone(0, Collections.emptyList()));\n\t\tassertTrue(AccessFlag.hasNone(Opcodes.ACC_PUBLIC, List.of(AccessFlag.ACC_STATIC)));\n\t\tassertFalse(AccessFlag.hasNone(Opcodes.ACC_PUBLIC, List.of(AccessFlag.ACC_PUBLIC, AccessFlag.ACC_STATIC)));\n\n\t\t// var-args form\n\t\tassertTrue(AccessFlag.hasNone(0));\n\t\tassertTrue(AccessFlag.hasNone(Opcodes.ACC_PUBLIC, AccessFlag.ACC_STATIC));\n\t\tassertFalse(AccessFlag.hasNone(Opcodes.ACC_PUBLIC, AccessFlag.ACC_PUBLIC, AccessFlag.ACC_STATIC));\n\t}\n\n\t@Test\n\tvoid testHasAny() {\n\t\tassertFalse(AccessFlag.hasAny(0, Collections.emptyList()));\n\t\tassertTrue(AccessFlag.hasAny(Opcodes.ACC_PUBLIC, List.of(AccessFlag.ACC_PUBLIC)));\n\t\tassertFalse(AccessFlag.hasAny(Opcodes.ACC_PUBLIC, List.of(AccessFlag.ACC_STATIC)));\n\t\tassertTrue(AccessFlag.hasAny(Opcodes.ACC_PUBLIC, List.of(AccessFlag.ACC_PUBLIC, AccessFlag.ACC_STATIC)));\n\n\t\t// var-args form\n\t\tassertFalse(AccessFlag.hasAny(0));\n\t\tassertTrue(AccessFlag.hasAny(Opcodes.ACC_PUBLIC, AccessFlag.ACC_PUBLIC));\n\t\tassertFalse(AccessFlag.hasAny(Opcodes.ACC_PUBLIC, AccessFlag.ACC_STATIC));\n\t\tassertTrue(AccessFlag.hasAny(Opcodes.ACC_PUBLIC, AccessFlag.ACC_PUBLIC, AccessFlag.ACC_STATIC));\n\t}\n\n\t@Test\n\tvoid testIsX() {\n\t\tassertTrue(AccessFlag.isPublic(Opcodes.ACC_PUBLIC));\n\t\tassertFalse(AccessFlag.isPublic(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isPrivate(Opcodes.ACC_PRIVATE));\n\t\tassertFalse(AccessFlag.isPrivate(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isProtected(Opcodes.ACC_PROTECTED));\n\t\tassertFalse(AccessFlag.isProtected(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isPackage(0));\n\t\tassertFalse(AccessFlag.isPackage(Opcodes.ACC_PRIVATE));\n\t\tassertFalse(AccessFlag.isPackage(Opcodes.ACC_PROTECTED));\n\t\tassertFalse(AccessFlag.isPackage(Opcodes.ACC_PUBLIC));\n\n\t\tassertTrue(AccessFlag.isStatic(Opcodes.ACC_STATIC));\n\t\tassertFalse(AccessFlag.isStatic(Opcodes.ACC_FINAL));\n\n\t\tassertTrue(AccessFlag.isFinal(Opcodes.ACC_FINAL));\n\t\tassertFalse(AccessFlag.isFinal(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isSynchronized(Opcodes.ACC_SYNCHRONIZED));\n\t\tassertFalse(AccessFlag.isSynchronized(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isSuper(Opcodes.ACC_SUPER));\n\t\tassertFalse(AccessFlag.isSuper(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isBridge(Opcodes.ACC_BRIDGE));\n\t\tassertFalse(AccessFlag.isBridge(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isVolatile(Opcodes.ACC_VOLATILE));\n\t\tassertFalse(AccessFlag.isVolatile(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isVarargs(Opcodes.ACC_VARARGS));\n\t\tassertFalse(AccessFlag.isVarargs(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isTransient(Opcodes.ACC_TRANSIENT));\n\t\tassertFalse(AccessFlag.isTransient(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isNative(Opcodes.ACC_NATIVE));\n\t\tassertFalse(AccessFlag.isNative(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isInterface(Opcodes.ACC_INTERFACE));\n\t\tassertFalse(AccessFlag.isInterface(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isAbstract(Opcodes.ACC_ABSTRACT));\n\t\tassertFalse(AccessFlag.isAbstract(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isStrict(Opcodes.ACC_STRICT));\n\t\tassertFalse(AccessFlag.isStrict(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isSynthetic(Opcodes.ACC_SYNTHETIC));\n\t\tassertFalse(AccessFlag.isSynthetic(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isAnnotation(Opcodes.ACC_ANNOTATION));\n\t\tassertFalse(AccessFlag.isAnnotation(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isEnum(Opcodes.ACC_ENUM));\n\t\tassertFalse(AccessFlag.isEnum(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isModule(Opcodes.ACC_MODULE));\n\t\tassertFalse(AccessFlag.isModule(Opcodes.ACC_STATIC));\n\n\t\tassertTrue(AccessFlag.isMandated(Opcodes.ACC_MANDATED));\n\t\tassertFalse(AccessFlag.isMandated(Opcodes.ACC_STATIC));\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/util/AsmInsnUtilTest.java",
    "content": "package software.coley.recaf.util;\n\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.Handle;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.objectweb.asm.tree.FieldInsnNode;\nimport org.objectweb.asm.tree.FrameNode;\nimport org.objectweb.asm.tree.InsnList;\nimport org.objectweb.asm.tree.InsnNode;\nimport org.objectweb.asm.tree.IntInsnNode;\nimport org.objectweb.asm.tree.InvokeDynamicInsnNode;\nimport org.objectweb.asm.tree.JumpInsnNode;\nimport org.objectweb.asm.tree.LabelNode;\nimport org.objectweb.asm.tree.LdcInsnNode;\nimport org.objectweb.asm.tree.LineNumberNode;\nimport org.objectweb.asm.tree.LocalVariableNode;\nimport org.objectweb.asm.tree.LookupSwitchInsnNode;\nimport org.objectweb.asm.tree.MethodInsnNode;\nimport org.objectweb.asm.tree.MethodNode;\nimport org.objectweb.asm.tree.MultiANewArrayInsnNode;\nimport org.objectweb.asm.tree.TableSwitchInsnNode;\nimport org.objectweb.asm.tree.TryCatchBlockNode;\nimport org.objectweb.asm.tree.VarInsnNode;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.ListIterator;\nimport java.util.Objects;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.objectweb.asm.Type.*;\nimport static software.coley.recaf.util.AsmInsnUtil.*;\n\n/**\n * Tests for {@link AsmInsnUtil}.\n */\nclass AsmInsnUtilTest {\n\t@Test\n\tvoid testOpcodeToTag() {\n\t\tassertEquals(H_INVOKESPECIAL, opcodeToTag(INVOKESPECIAL));\n\t\tassertEquals(H_INVOKEINTERFACE, opcodeToTag(INVOKEINTERFACE));\n\t\tassertEquals(H_INVOKEVIRTUAL, opcodeToTag(INVOKEVIRTUAL));\n\t\tassertEquals(H_INVOKESTATIC, opcodeToTag(INVOKESTATIC));\n\t\tassertEquals(H_GETFIELD, opcodeToTag(GETFIELD));\n\t\tassertEquals(H_GETSTATIC, opcodeToTag(GETSTATIC));\n\t\tassertEquals(H_PUTFIELD, opcodeToTag(PUTFIELD));\n\t\tassertEquals(H_PUTSTATIC, opcodeToTag(PUTSTATIC));\n\n\t\t// Other instructions don't have handle tags, so this is not allowed.\n\t\tassertThrows(IllegalStateException.class, () -> opcodeToTag(ICONST_0));\n\t}\n\n\t@Test\n\tvoid testTagToOpcode() {\n\t\tassertEquals(INVOKESPECIAL, tagToOpcode(H_INVOKESPECIAL));\n\t\tassertEquals(INVOKEINTERFACE, tagToOpcode(H_INVOKEINTERFACE));\n\t\tassertEquals(INVOKEVIRTUAL, tagToOpcode(H_INVOKEVIRTUAL));\n\t\tassertEquals(INVOKESTATIC, tagToOpcode(H_INVOKESTATIC));\n\t\tassertEquals(GETFIELD, tagToOpcode(H_GETFIELD));\n\t\tassertEquals(GETSTATIC, tagToOpcode(H_GETSTATIC));\n\t\tassertEquals(PUTFIELD, tagToOpcode(H_PUTFIELD));\n\t\tassertEquals(PUTSTATIC, tagToOpcode(H_PUTSTATIC));\n\n\t\t// Unknown tag values do not have respective opcodes, so this is not allowed.\n\t\tassertThrows(IllegalStateException.class, () -> tagToOpcode(-1));\n\t}\n\n\t@Test\n\tvoid testIndexOf() {\n\t\t// Items in the list should match the list index.\n\t\tInsnList list = new InsnList();\n\t\tfor (int i = 0; i < 25; i++)\n\t\t\tlist.add(new InsnNode(NOP));\n\t\tfor (int i = 0; i < 25; i++)\n\t\t\tassertEquals(i, indexOf(list.get(i)));\n\n\t\t// Items not in any list have no \"previous\" instruction, so they\n\t\t// always appear as if they are first.\n\t\tassertEquals(0, indexOf(new InsnNode(NOP)));\n\t}\n\n\t@Test\n\tvoid testGetTypeForVarInsn() {\n\t\t// int\n\t\tassertEquals(INT_TYPE, getTypeForVarInsn(new VarInsnNode(ILOAD, 0)));\n\t\tassertEquals(INT_TYPE, getTypeForVarInsn(new VarInsnNode(ISTORE, 0)));\n\n\t\t// float\n\t\tassertEquals(FLOAT_TYPE, getTypeForVarInsn(new VarInsnNode(FLOAD, 0)));\n\t\tassertEquals(FLOAT_TYPE, getTypeForVarInsn(new VarInsnNode(FSTORE, 0)));\n\n\t\t// long\n\t\tassertEquals(LONG_TYPE, getTypeForVarInsn(new VarInsnNode(LLOAD, 0)));\n\t\tassertEquals(LONG_TYPE, getTypeForVarInsn(new VarInsnNode(LSTORE, 0)));\n\n\t\t// double\n\t\tassertEquals(DOUBLE_TYPE, getTypeForVarInsn(new VarInsnNode(DLOAD, 0)));\n\t\tassertEquals(DOUBLE_TYPE, getTypeForVarInsn(new VarInsnNode(DSTORE, 0)));\n\n\t\t// object/array\n\t\tassertEquals(Types.OBJECT_TYPE, getTypeForVarInsn(new VarInsnNode(ALOAD, 0)));\n\t\tassertEquals(Types.OBJECT_TYPE, getTypeForVarInsn(new VarInsnNode(ASTORE, 0)));\n\t}\n\n\t@Test\n\tvoid testIsVarStore() {\n\t\tfor (int i = 0; i < 255; i++) {\n\t\t\tif (i == ISTORE || i == FSTORE || i == LSTORE || i == DSTORE || i == ASTORE) {\n\t\t\t\tassertTrue(isVarStore(i));\n\t\t\t} else {\n\t\t\t\tassertFalse(isVarStore(i));\n\t\t\t}\n\t\t}\n\t}\n\n\t@Test\n\tvoid testIsVarLoad() {\n\t\tfor (int i = 0; i < 255; i++) {\n\t\t\tif (i == ILOAD || i == FLOAD || i == LLOAD || i == DLOAD || i == ALOAD) {\n\t\t\t\tassertTrue(isVarLoad(i));\n\t\t\t} else {\n\t\t\t\tassertFalse(isVarLoad(i));\n\t\t\t}\n\t\t}\n\t}\n\n\t@Test\n\tvoid testCreateVarLoad() {\n\t\tType[] types = new Type[]{BOOLEAN_TYPE, CHAR_TYPE, SHORT_TYPE, INT_TYPE,\n\t\t\t\tFLOAT_TYPE, LONG_TYPE, DOUBLE_TYPE, Types.OBJECT_TYPE};\n\t\tfor (Type type : types) {\n\t\t\tType loadType = type;\n\t\t\tif (loadType.getSort() < INT)\n\t\t\t\tloadType = INT_TYPE;\n\t\t\tVarInsnNode varLoad = createVarLoad(0, type);\n\t\t\tassertTrue(isVarLoad(varLoad.getOpcode()));\n\t\t\tassertEquals(loadType, getTypeForVarInsn(varLoad));\n\t\t}\n\t}\n\n\t@Test\n\tvoid testCreateVarStore() {\n\t\tType[] types = new Type[]{BOOLEAN_TYPE, CHAR_TYPE, SHORT_TYPE, INT_TYPE,\n\t\t\t\tFLOAT_TYPE, LONG_TYPE, DOUBLE_TYPE, Types.OBJECT_TYPE};\n\t\tfor (Type type : types) {\n\t\t\tType storeType = type;\n\t\t\tif (storeType.getSort() < INT)\n\t\t\t\tstoreType = INT_TYPE;\n\t\t\tVarInsnNode varStore = createVarStore(0, type);\n\t\t\tassertTrue(isVarStore(varStore.getOpcode()));\n\t\t\tassertEquals(storeType, getTypeForVarInsn(varStore));\n\t\t}\n\t}\n\n\t@Test\n\tvoid testFixMissingVariableLabels() {\n\t\t// Create method with just \"return parameter[0]\"\n\t\tMethodNode method = new MethodNode(ACC_STATIC, \"foo\", \"(I)I\", null, null);\n\t\tmethod.instructions.add(new VarInsnNode(ILOAD, 0));\n\t\tmethod.instructions.add(new InsnNode(IRETURN));\n\n\t\t// Add variable to the method with labels that do not exist in the method.\n\t\tLabelNode varStart = new LabelNode();\n\t\tLabelNode varEnd = new LabelNode();\n\t\tLocalVariableNode variable = new LocalVariableNode(\"param\", \"I\", null, varStart, varEnd, 0);\n\t\tmethod.localVariables.add(variable);\n\n\t\t// They won't exist by default.\n\t\tassertFalse(method.instructions.contains(varStart));\n\t\tassertFalse(method.instructions.contains(varEnd));\n\n\t\t// We \"fix\" the method labels.\n\t\tfixMissingVariableLabels(method);\n\n\t\t// The labels should be re-assigned.\n\t\tassertNotSame(varStart, variable.start);\n\t\tassertNotSame(varEnd, variable.end);\n\n\t\t// The new labels should be in the method.\n\t\tassertSame(variable.start, method.instructions.getFirst());\n\t\tassertSame(variable.end, method.instructions.getLast());\n\t}\n\n\t@Test\n\tvoid testFixMissingVariableLabelsNoVars() {\n\t\t// Create method with just \"return parameter[0]\"\n\t\tMethodNode method = new MethodNode(ACC_STATIC, \"foo\", \"(I)I\", null, null);\n\t\tmethod.instructions.add(new VarInsnNode(ILOAD, 0));\n\t\tmethod.instructions.add(new InsnNode(IRETURN));\n\n\t\t// We \"fix\" the method labels, even though there are no variables.\n\t\t// This should do nothing.\n\t\tfixMissingVariableLabels(method);\n\n\t\t// No changes should be made. IE, no added labels.\n\t\tassertEquals(2, method.instructions.size());\n\t}\n\n\t@Test\n\tvoid testFixMissingVariableLabelsNoCode() {\n\t\t// Create method with just \"return parameter[0]\"\n\t\tMethodNode method = new MethodNode(ACC_STATIC | ACC_ABSTRACT, \"foo\", \"(I)I\", null, null);\n\n\t\t// We \"fix\" the method labels, even though there are no variables.\n\t\t// This should do nothing.\n\t\tfixMissingVariableLabels(method);\n\n\t\t// No changes should be made. IE, no added labels.\n\t\tassertEquals(0, method.instructions.size());\n\t}\n\n\t@Test\n\tvoid testIsConstValue() {\n\t\tint[] constValues = {\n\t\t\t\tACONST_NULL,\n\t\t\t\tICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5,\n\t\t\t\tLCONST_0, LCONST_1,\n\t\t\t\tFCONST_0, FCONST_1, FCONST_2,\n\t\t\t\tDCONST_0, DCONST_1,\n\t\t\t\tBIPUSH, SIPUSH,\n\t\t\t\tLDC,\n\t\t};\n\t\tfor (int constOp : constValues)\n\t\t\tassertTrue(isConstValue(constOp));\n\n\t\t// [ACONST_NULL, ... , LDC] is a range of instructions that all push constant values.\n\t\t// As of Java 25 there are no other instructions that push \"constant\" values outside this range.\n\t\tassertFalse(isConstValue(NOP));\n\t\tfor (int i = ILOAD; i < 200; i++)\n\t\t\tassertFalse(isConstValue(i));\n\t}\n\n\t@Test\n\tvoid testIsConstIntValue() {\n\t\t// Const-int values\n\t\tassertTrue(isConstIntValue(new InsnNode(ICONST_M1)));\n\t\tassertTrue(isConstIntValue(new InsnNode(ICONST_0)));\n\t\tassertTrue(isConstIntValue(new InsnNode(ICONST_1)));\n\t\tassertTrue(isConstIntValue(new InsnNode(ICONST_2)));\n\t\tassertTrue(isConstIntValue(new InsnNode(ICONST_3)));\n\t\tassertTrue(isConstIntValue(new InsnNode(ICONST_4)));\n\t\tassertTrue(isConstIntValue(new InsnNode(ICONST_5)));\n\t\tassertTrue(isConstIntValue(new IntInsnNode(SIPUSH, 0)));\n\t\tassertTrue(isConstIntValue(new IntInsnNode(BIPUSH, 0)));\n\t\tassertTrue(isConstIntValue(new LdcInsnNode(100000)));\n\n\t\t// Non const-int values\n\t\tassertFalse(isConstIntValue(new InsnNode(ACONST_NULL)));\n\t\tassertFalse(isConstIntValue(new InsnNode(FCONST_0)));\n\t\tassertFalse(isConstIntValue(new InsnNode(FCONST_1)));\n\t\tassertFalse(isConstIntValue(new InsnNode(FCONST_2)));\n\t\tassertFalse(isConstIntValue(new InsnNode(DCONST_0)));\n\t\tassertFalse(isConstIntValue(new InsnNode(DCONST_1)));\n\t\tassertFalse(isConstIntValue(new LdcInsnNode(\"\")));\n\t\tassertFalse(isConstIntValue(new LdcInsnNode(Math.PI)));\n\t\tassertFalse(isConstIntValue(new LdcInsnNode(Float.MAX_VALUE)));\n\t\tassertFalse(isConstIntValue(new LdcInsnNode(Long.MAX_VALUE)));\n\t\tassertFalse(isConstIntValue(new LdcInsnNode(Double.MAX_VALUE)));\n\t\tassertFalse(isConstIntValue(new LabelNode()));\n\t\tassertFalse(isConstIntValue(new VarInsnNode(ILOAD, 0)));\n\t}\n\n\t@Test\n\tvoid testGetDefaultValue() {\n\t\t// Types that widen to int\n\t\tType[] intTypes = new Type[]{BOOLEAN_TYPE, CHAR_TYPE, SHORT_TYPE, INT_TYPE};\n\t\tfor (Type type : intTypes)\n\t\t\tassertEquals(ICONST_0, getDefaultValue(type).getOpcode());\n\n\t\t// Other primitive types\n\t\tassertEquals(FCONST_0, getDefaultValue(FLOAT_TYPE).getOpcode());\n\t\tassertEquals(LCONST_0, getDefaultValue(LONG_TYPE).getOpcode());\n\t\tassertEquals(DCONST_0, getDefaultValue(DOUBLE_TYPE).getOpcode());\n\n\t\t// Types that widen to object\n\t\tassertEquals(ACONST_NULL, getDefaultValue(Types.OBJECT_TYPE).getOpcode());\n\t\tassertEquals(ACONST_NULL, getDefaultValue(Types.STRING_TYPE).getOpcode());\n\t\tassertEquals(ACONST_NULL, getDefaultValue(Types.ARRAY_1D_BYTE).getOpcode());\n\t}\n\n\t@Test\n\tvoid testIntToInsn() {\n\t\t// Special ICONST_X cases\n\t\tassertEquals(ICONST_M1, intToInsn(-1).getOpcode());\n\t\tassertEquals(ICONST_0, intToInsn(0).getOpcode());\n\t\tassertEquals(ICONST_1, intToInsn(1).getOpcode());\n\t\tassertEquals(ICONST_2, intToInsn(2).getOpcode());\n\t\tassertEquals(ICONST_3, intToInsn(3).getOpcode());\n\t\tassertEquals(ICONST_4, intToInsn(4).getOpcode());\n\t\tassertEquals(ICONST_5, intToInsn(5).getOpcode());\n\n\t\t// Byte range\n\t\tassertEquals(BIPUSH, intToInsn(-2).getOpcode());\n\t\tassertEquals(BIPUSH, intToInsn(6).getOpcode());\n\t\tassertEquals(BIPUSH, intToInsn(Byte.MIN_VALUE).getOpcode());\n\t\tassertEquals(BIPUSH, intToInsn(Byte.MAX_VALUE).getOpcode());\n\n\t\t// Short range\n\t\tassertEquals(SIPUSH, intToInsn(Short.MIN_VALUE).getOpcode());\n\t\tassertEquals(SIPUSH, intToInsn(Short.MAX_VALUE).getOpcode());\n\t\tassertEquals(SIPUSH, intToInsn(Short.MIN_VALUE / 2).getOpcode());\n\t\tassertEquals(SIPUSH, intToInsn(Short.MAX_VALUE / 2).getOpcode());\n\n\t\t// Int range\n\t\tassertEquals(LDC, intToInsn(Integer.MIN_VALUE).getOpcode());\n\t\tassertEquals(LDC, intToInsn(Integer.MAX_VALUE).getOpcode());\n\t\tassertEquals(LDC, intToInsn(Integer.MIN_VALUE / 2).getOpcode());\n\t\tassertEquals(LDC, intToInsn(Integer.MAX_VALUE / 2).getOpcode());\n\t}\n\n\t@Test\n\tvoid testFloatToInsn() {\n\t\t// Special FCONST_X cases\n\t\tassertEquals(FCONST_0, floatToInsn(0).getOpcode());\n\t\tassertEquals(FCONST_1, floatToInsn(1).getOpcode());\n\t\tassertEquals(FCONST_2, floatToInsn(2).getOpcode());\n\t\tassertEquals(FCONST_0, floatToInsn(0.0F).getOpcode());\n\t\tassertEquals(FCONST_1, floatToInsn(1.0F).getOpcode());\n\t\tassertEquals(FCONST_2, floatToInsn(2.0F).getOpcode());\n\n\t\t// Float range\n\t\tassertEquals(LDC, floatToInsn(5F).getOpcode());\n\t\tassertEquals(LDC, floatToInsn(Float.MIN_VALUE).getOpcode());\n\t\tassertEquals(LDC, floatToInsn(Float.MAX_VALUE).getOpcode());\n\t}\n\n\t@Test\n\tvoid testDoubleToInsn() {\n\t\t// Special DCONST_X cases\n\t\tassertEquals(DCONST_0, doubleToInsn(0).getOpcode());\n\t\tassertEquals(DCONST_1, doubleToInsn(1).getOpcode());\n\t\tassertEquals(DCONST_0, doubleToInsn(0.0F).getOpcode());\n\t\tassertEquals(DCONST_1, doubleToInsn(1.0F).getOpcode());\n\t\tassertEquals(DCONST_0, doubleToInsn(0.0D).getOpcode());\n\t\tassertEquals(DCONST_1, doubleToInsn(1.0D).getOpcode());\n\n\t\t// Double range\n\t\tassertEquals(LDC, doubleToInsn(5.5F).getOpcode());\n\t\tassertEquals(LDC, doubleToInsn(5.5D).getOpcode());\n\t\tassertEquals(LDC, doubleToInsn(Float.MIN_VALUE).getOpcode());\n\t\tassertEquals(LDC, doubleToInsn(Float.MAX_VALUE).getOpcode());\n\t\tassertEquals(LDC, doubleToInsn(Double.MIN_VALUE).getOpcode());\n\t\tassertEquals(LDC, doubleToInsn(Double.MAX_VALUE).getOpcode());\n\t}\n\n\t@Test\n\tvoid testLongToInsn() {\n\t\t// Special LCONST_X cases\n\t\tassertEquals(LCONST_0, longToInsn(0).getOpcode());\n\t\tassertEquals(LCONST_1, longToInsn(1).getOpcode());\n\t\tassertEquals(LCONST_0, longToInsn(0L).getOpcode());\n\t\tassertEquals(LCONST_1, longToInsn(1L).getOpcode());\n\n\t\t// Double range\n\t\tassertEquals(LDC, longToInsn(100L).getOpcode());\n\t\tassertEquals(LDC, longToInsn(100000000L).getOpcode());\n\t\tassertEquals(LDC, longToInsn(Long.MIN_VALUE).getOpcode());\n\t\tassertEquals(LDC, longToInsn(Long.MAX_VALUE).getOpcode());\n\t\tassertEquals(LDC, longToInsn(Integer.MIN_VALUE).getOpcode());\n\t\tassertEquals(LDC, longToInsn(Integer.MAX_VALUE).getOpcode());\n\t}\n\n\t@Test\n\tvoid testGetReturnOpcode() {\n\t\tassertEquals(RETURN, getReturnOpcode(VOID_TYPE));\n\t\tassertEquals(IRETURN, getReturnOpcode(BOOLEAN_TYPE));\n\t\tassertEquals(IRETURN, getReturnOpcode(CHAR_TYPE));\n\t\tassertEquals(IRETURN, getReturnOpcode(BYTE_TYPE));\n\t\tassertEquals(IRETURN, getReturnOpcode(SHORT_TYPE));\n\t\tassertEquals(IRETURN, getReturnOpcode(INT_TYPE));\n\t\tassertEquals(FRETURN, getReturnOpcode(FLOAT_TYPE));\n\t\tassertEquals(LRETURN, getReturnOpcode(LONG_TYPE));\n\t\tassertEquals(DRETURN, getReturnOpcode(DOUBLE_TYPE));\n\t\tassertEquals(ARETURN, getReturnOpcode(Types.OBJECT_TYPE));\n\t\tassertEquals(ARETURN, getReturnOpcode(Types.STRING_TYPE));\n\t\tassertEquals(ARETURN, getReturnOpcode(Types.ARRAY_1D_BYTE));\n\t}\n\n\t@Test\n\tvoid testIsReturn() {\n\t\tassertTrue(isReturn(new InsnNode(RETURN)));\n\t\tassertTrue(isReturn(new InsnNode(IRETURN)));\n\t\tassertTrue(isReturn(new InsnNode(LRETURN)));\n\t\tassertTrue(isReturn(new InsnNode(FRETURN)));\n\t\tassertTrue(isReturn(new InsnNode(DRETURN)));\n\t\tassertTrue(isReturn(new InsnNode(ARETURN)));\n\n\t\tassertFalse(isReturn(new InsnNode(NOP)));\n\t\tassertFalse(isReturn(new VarInsnNode(ILOAD, 0)));\n\t\tassertFalse(isReturn(new LdcInsnNode(\"return\")));\n\t}\n\n\t@Test\n\tvoid testIsFlowControl() {\n\t\tassertTrue(isFlowControl(new InsnNode(RET))); // Paired to JSR\n\t\tassertTrue(isFlowControl(new InsnNode(ATHROW)));\n\t\tassertTrue(isFlowControl(new JumpInsnNode(IFEQ, new LabelNode())));\n\t\tassertTrue(isFlowControl(new TableSwitchInsnNode(0, 1, new LabelNode(), new LabelNode())));\n\t\tassertTrue(isFlowControl(new LookupSwitchInsnNode(new LabelNode(), new int[]{0}, new LabelNode[]{new LabelNode()})));\n\n\t\tassertFalse(isFlowControl(new InsnNode(RETURN)));\n\t\tassertFalse(isFlowControl(new InsnNode(NOP)));\n\t\tassertFalse(isFlowControl(new VarInsnNode(ILOAD, 0)));\n\t}\n\n\t@Test\n\tvoid testIsTerminalOrAlwaysTakeFlowControl() {\n\t\t// Throw --> terminal\n\t\tassertTrue(isTerminalOrAlwaysTakeFlowControl(ATHROW));\n\n\t\t// Return --> terminal\n\t\tassertTrue(isTerminalOrAlwaysTakeFlowControl(RETURN));\n\t\tassertTrue(isTerminalOrAlwaysTakeFlowControl(IRETURN));\n\t\tassertTrue(isTerminalOrAlwaysTakeFlowControl(FRETURN));\n\t\tassertTrue(isTerminalOrAlwaysTakeFlowControl(DRETURN));\n\t\tassertTrue(isTerminalOrAlwaysTakeFlowControl(LRETURN));\n\t\tassertTrue(isTerminalOrAlwaysTakeFlowControl(ARETURN));\n\n\t\t// Goto, Jsr, Switch --> not conditional\n\t\tassertTrue(isTerminalOrAlwaysTakeFlowControl(JSR));\n\t\tassertTrue(isTerminalOrAlwaysTakeFlowControl(GOTO));\n\t\tassertTrue(isTerminalOrAlwaysTakeFlowControl(LOOKUPSWITCH));\n\t\tassertTrue(isTerminalOrAlwaysTakeFlowControl(TABLESWITCH));\n\n\t\t// Conditional jumps\n\t\tassertFalse(isTerminalOrAlwaysTakeFlowControl(IFEQ));\n\t\tassertFalse(isTerminalOrAlwaysTakeFlowControl(IFNULL));\n\t\tassertFalse(isTerminalOrAlwaysTakeFlowControl(IF_ICMPLT));\n\n\t\t// Other instructions\n\t\tassertFalse(isTerminalOrAlwaysTakeFlowControl(NOP));\n\t\tassertFalse(isTerminalOrAlwaysTakeFlowControl(IDIV));\n\t}\n\n\t@Test\n\tvoid testIsSwitchEffectiveGoto() {\n\t\t// When all labels are the same, it is basically a goto.\n\t\tLabelNode defaultLabel = new LabelNode();\n\t\tassertTrue(isSwitchEffectiveGoto(new TableSwitchInsnNode(0, 1, defaultLabel, defaultLabel)));\n\t\tassertTrue(isSwitchEffectiveGoto(new LookupSwitchInsnNode(defaultLabel, new int[]{0}, new LabelNode[]{defaultLabel})));\n\n\t\t// When any label is different, its no longer effectively goto.\n\t\tassertFalse(isSwitchEffectiveGoto(new TableSwitchInsnNode(0, 1, defaultLabel, new LabelNode())));\n\t\tassertFalse(isSwitchEffectiveGoto(new LookupSwitchInsnNode(defaultLabel, new int[]{0}, new LabelNode[]{new LabelNode()})));\n\t}\n\n\t@Test\n\tvoid testIsMetaData() {\n\t\t// ASM uses '-1' to indicate metadata instructions\n\t\tassertTrue(isMetaData(new InsnNode(-1)));\n\t\tassertTrue(isMetaData(new FrameNode(0, 0, new Object[0], 0, new Object[0])));\n\t\tassertTrue(isMetaData(new LabelNode()));\n\t\tassertTrue(isMetaData(new LineNumberNode(24, new LabelNode())));\n\n\t\tassertFalse(isMetaData(new InsnNode(NOP)));\n\t\tassertFalse(isMetaData(new IntInsnNode(BIPUSH, 100)));\n\t}\n\n\t@Test\n\tvoid testGetNextInsn() {\n\t\tInsnList list = new InsnList();\n\t\tlist.add(new InsnNode(NOP));\n\t\tlist.add(new LabelNode());\n\t\tlist.add(new LabelNode());\n\t\tlist.add(new LabelNode());\n\t\tlist.add(new LabelNode());\n\t\tlist.add(new InsnNode(NOP));\n\n\t\t// The \"next\" instruction skips meta-data values like labels.\n\t\tassertSame(list.getLast(), getNextInsn(list.getFirst()));\n\t}\n\n\t@Test\n\tvoid testGetNextFollowGoto() {\n\t\tLabelNode gotoTarget = new LabelNode();\n\t\tInsnList list = new InsnList();\n\t\tlist.add(new InsnNode(NOP));\n\t\tlist.add(new JumpInsnNode(GOTO, gotoTarget));\n\t\tlist.add(gotoTarget);\n\t\tlist.add(new InsnNode(RETURN));\n\n\t\t// The \"next\" instruction skips meta-data values like labels and follows goto instructions instead\n\t\t// of yielding them.\n\t\tassertSame(list.getLast(), getNextFollowGoto(list.getFirst()));\n\n\t\t// Otherwise it is just a normal next (skipping metadata)\n\t\tlist = new InsnList();\n\t\tlist.add(new InsnNode(NOP));\n\t\tlist.add(new LabelNode());\n\t\tlist.add(new LabelNode());\n\t\tlist.add(new LabelNode());\n\t\tlist.add(new LabelNode());\n\t\tlist.add(new InsnNode(RETURN));\n\t\tassertSame(list.getLast(), getNextFollowGoto(list.getFirst()));\n\t}\n\n\t@Test\n\tvoid testGetPreviousInsn() {\n\t\tInsnList list = new InsnList();\n\t\tlist.add(new InsnNode(NOP));\n\t\tlist.add(new LabelNode());\n\t\tlist.add(new LabelNode());\n\t\tlist.add(new LabelNode());\n\t\tlist.add(new LabelNode());\n\t\tlist.add(new InsnNode(RETURN));\n\n\t\t// The \"previous\" instruction skips meta-data values like labels.\n\t\tassertSame(list.getFirst(), getPreviousInsn(list.getLast()));\n\n\t\t// Standard backwards for no meta-data.\n\t\tlist = new InsnList();\n\t\tlist.add(new InsnNode(NOP));\n\t\tlist.add(new InsnNode(ICONST_0));\n\t\tlist.add(new InsnNode(POP));\n\t\tlist.add(new InsnNode(RETURN));\n\t\tassertEquals(POP, Objects.requireNonNull(getPreviousInsn(list.getLast())).getOpcode());\n\t}\n\n\t@Test\n\tvoid testHasHandlerFlowIntoBlock() {\n\t\t// try { return a / b; } catch (...) { return 0; }\n\t\tLabelNode tryStart = new LabelNode();\n\t\tLabelNode tryEnd = new LabelNode();\n\t\tLabelNode tryHandler = new LabelNode();\n\t\tMethodNode method = new MethodNode(ACC_STATIC, \"div\", \"(II)I\", null, null);\n\t\tmethod.tryCatchBlocks.add(new TryCatchBlockNode(tryStart, tryEnd, tryHandler, null));\n\t\tmethod.instructions.add(tryStart);\n\t\tmethod.instructions.add(new VarInsnNode(ILOAD, 0));\n\t\tmethod.instructions.add(new VarInsnNode(ILOAD, 1));\n\t\tmethod.instructions.add(new InsnNode(IDIV));\n\t\tmethod.instructions.add(new InsnNode(IRETURN));\n\t\tmethod.instructions.add(tryEnd);\n\t\tmethod.instructions.add(tryHandler);\n\t\tmethod.instructions.add(new InsnNode(ICONST_0));\n\t\tmethod.instructions.add(new InsnNode(IRETURN));\n\n\t\t// Get the last 3 instructions starting with the handler label.\n\t\tList<AbstractInsnNode> block = new ArrayList<>();\n\t\tListIterator<AbstractInsnNode> it = method.instructions.iterator(method.instructions.indexOf(tryHandler));\n\t\twhile (it.hasNext())\n\t\t\tblock.add(it.next());\n\n\t\t// If we skip the first insn, then we don't see that the catch handler is in the block.\n\t\tassertFalse(hasHandlerFlowIntoBlock(method, block, false));\n\n\t\t// If we include the first insn, then we do see the catch handler is the first insn in the block.\n\t\tassertTrue(hasHandlerFlowIntoBlock(method, block, true));\n\t}\n\n\t@Test\n\tvoid testHasInboundFlowReferencesFromJump() {\n\t\tLabelNode gotoDest = new LabelNode();\n\t\tMethodNode method = new MethodNode(ACC_STATIC, \"div\", \"(II)V\", null, null);\n\t\tmethod.instructions.add(new VarInsnNode(ILOAD, 0));\n\t\tmethod.instructions.add(new JumpInsnNode(IFEQ, gotoDest));\n\t\tmethod.instructions.add(new InsnNode(RETURN));\n\t\tmethod.instructions.add(gotoDest);\n\t\tmethod.instructions.add(new InsnNode(RETURN));\n\n\t\t// If the block is the whole method then it cannot possibly have inbound references\n\t\tList<AbstractInsnNode> block = new ArrayList<>();\n\t\tListIterator<AbstractInsnNode> it = method.instructions.iterator(0);\n\t\twhile (it.hasNext())\n\t\t\tblock.add(it.next());\n\t\tassertFalse(hasInboundFlowReferences(method, block));\n\n\t\t// If we have a label used by the ifeq, it will have an inbound reference\n\t\tblock.clear();\n\t\tit = method.instructions.iterator(method.instructions.indexOf(gotoDest));\n\t\twhile (it.hasNext())\n\t\t\tblock.add(it.next());\n\t\tassertTrue(hasInboundFlowReferences(method, block));\n\n\t\t// And if we exclude that label, back to being false.\n\t\tblock.clear();\n\t\tit = method.instructions.iterator(method.instructions.indexOf(gotoDest) + 1);\n\t\twhile (it.hasNext())\n\t\t\tblock.add(it.next());\n\t\tassertFalse(hasInboundFlowReferences(method, block));\n\t}\n\n\t@Test\n\tvoid testHasInboundFlowReferencesFromTableSwitch() {\n\t\tLabelNode labelA = new LabelNode();\n\t\tLabelNode labelB = new LabelNode();\n\t\tLabelNode labelC = new LabelNode();\n\t\tMethodNode method = new MethodNode(ACC_STATIC, \"div\", \"(I)V\", null, null);\n\t\tmethod.instructions.add(new VarInsnNode(ILOAD, 0));\n\t\tmethod.instructions.add(new TableSwitchInsnNode(0, 1, labelB, labelA));\n\t\tmethod.instructions.add(labelA);\n\t\tmethod.instructions.add(new InsnNode(RETURN));\n\t\tmethod.instructions.add(labelB);\n\t\tmethod.instructions.add(new InsnNode(RETURN));\n\t\tmethod.instructions.add(labelC); // Dead code\n\t\tmethod.instructions.add(new InsnNode(RETURN));\n\n\t\t// Check the case\n\t\tList<AbstractInsnNode> block = new ArrayList<>();\n\t\tListIterator<AbstractInsnNode> it = method.instructions.iterator(method.instructions.indexOf(labelA));\n\t\twhile (it.hasNext() && block.size() < 2)\n\t\t\tblock.add(it.next());\n\t\tassertTrue(hasInboundFlowReferences(method, block));\n\n\t\t// Check the default\n\t\tblock.clear();\n\t\tit = method.instructions.iterator(method.instructions.indexOf(labelB));\n\t\twhile (it.hasNext() && block.size() < 2)\n\t\t\tblock.add(it.next());\n\t\tassertTrue(hasInboundFlowReferences(method, block));\n\n\t\t// Dead code --> no inbound flow\n\t\tblock.clear();\n\t\tit = method.instructions.iterator(method.instructions.indexOf(labelC));\n\t\twhile (it.hasNext())\n\t\t\tblock.add(it.next());\n\t\tassertFalse(hasInboundFlowReferences(method, block));\n\t}\n\n\t@Test\n\tvoid testHasInboundFlowReferencesFromLookupSwitch() {\n\t\tLabelNode labelA = new LabelNode();\n\t\tLabelNode labelB = new LabelNode();\n\t\tLabelNode labelC = new LabelNode();\n\t\tMethodNode method = new MethodNode(ACC_STATIC, \"div\", \"(I)V\", null, null);\n\t\tmethod.instructions.add(new VarInsnNode(ILOAD, 0));\n\t\tmethod.instructions.add(new LookupSwitchInsnNode(labelB, new int[]{0}, new LabelNode[]{labelA}));\n\t\tmethod.instructions.add(labelA);\n\t\tmethod.instructions.add(new InsnNode(RETURN));\n\t\tmethod.instructions.add(labelB);\n\t\tmethod.instructions.add(new InsnNode(RETURN));\n\t\tmethod.instructions.add(labelC); // Dead code\n\t\tmethod.instructions.add(new InsnNode(RETURN));\n\n\t\t// Check the case\n\t\tList<AbstractInsnNode> block = new ArrayList<>();\n\t\tListIterator<AbstractInsnNode> it = method.instructions.iterator(method.instructions.indexOf(labelA));\n\t\twhile (it.hasNext() && block.size() < 2)\n\t\t\tblock.add(it.next());\n\t\tassertTrue(hasInboundFlowReferences(method, block));\n\n\t\t// Check the default\n\t\tblock.clear();\n\t\tit = method.instructions.iterator(method.instructions.indexOf(labelB));\n\t\twhile (it.hasNext() && block.size() < 2)\n\t\t\tblock.add(it.next());\n\t\tassertTrue(hasInboundFlowReferences(method, block));\n\n\t\t// Dead code --> no inbound flow\n\t\tblock.clear();\n\t\tit = method.instructions.iterator(method.instructions.indexOf(labelC));\n\t\twhile (it.hasNext())\n\t\t\tblock.add(it.next());\n\t\tassertFalse(hasInboundFlowReferences(method, block));\n\t}\n\n\t@Test\n\tvoid testGetSizeConsumed() {\n\t\t// MULTIANEWARRAY --> pop a value off the stack for the given array dimensions\n\t\tassertEquals(1, getSizeConsumed(new MultiANewArrayInsnNode(\"[LExample;\", 1)));\n\t\tassertEquals(2, getSizeConsumed(new MultiANewArrayInsnNode(\"[[LExample;\", 2)));\n\t\tassertEquals(1, getSizeConsumed(new MultiANewArrayInsnNode(\"[[[LExample;\", 1)));\n\n\t\t// INVOKE-X --> pop calling context (unless static invoke) and parameter types\n\t\tassertEquals(1, getSizeConsumed(new MethodInsnNode(INVOKEVIRTUAL, \"Owner\", \"methodName\", \"()V\")));\n\t\tassertEquals(1, getSizeConsumed(new MethodInsnNode(INVOKESPECIAL, \"Owner\", \"methodName\", \"()V\")));\n\t\tassertEquals(1, getSizeConsumed(new MethodInsnNode(INVOKEINTERFACE, \"Owner\", \"methodName\", \"()V\")));\n\t\tassertEquals(0, getSizeConsumed(new MethodInsnNode(INVOKESTATIC, \"Owner\", \"methodName\", \"()V\")));\n\t\tassertEquals(1, getSizeConsumed(new MethodInsnNode(INVOKESTATIC, \"Owner\", \"methodName\", \"(I)V\")));\n\t\tassertEquals(1, getSizeConsumed(new MethodInsnNode(INVOKESTATIC, \"Owner\", \"methodName\", \"(LExample;)V\")));\n\t\tassertEquals(1, getSizeConsumed(new MethodInsnNode(INVOKESTATIC, \"Owner\", \"methodName\", \"([LExample;)V\")));\n\t\tassertEquals(2, getSizeConsumed(new MethodInsnNode(INVOKESTATIC, \"Owner\", \"methodName\", \"(J)V\")));\n\t\tassertEquals(2, getSizeConsumed(new MethodInsnNode(INVOKESTATIC, \"Owner\", \"methodName\", \"(FF)V\")));\n\t\tassertEquals(5, getSizeConsumed(new MethodInsnNode(INVOKESTATIC, \"Owner\", \"methodName\", \"(JFJ)V\")));\n\n\t\t// INVOKE-DYNAMIC -> Pop argument types from method type (derived from descriptor)\n\t\tHandle bogusHandle = new Handle(H_INVOKESTATIC, \"Owner\", \"name\", \"()V\", false);\n\t\tassertEquals(0, getSizeConsumed(new InvokeDynamicInsnNode(\"name\", \"()V\", bogusHandle)));\n\t\tassertEquals(1, getSizeConsumed(new InvokeDynamicInsnNode(\"name\", \"(I)V\", bogusHandle)));\n\t\tassertEquals(2, getSizeConsumed(new InvokeDynamicInsnNode(\"name\", \"(II)V\", bogusHandle)));\n\t\tassertEquals(2, getSizeConsumed(new InvokeDynamicInsnNode(\"name\", \"(J)V\", bogusHandle)));\n\t\tassertEquals(3, getSizeConsumed(new InvokeDynamicInsnNode(\"name\", \"(JI)V\", bogusHandle)));\n\n\t\t// GET-X -> pop field context (unless static)\n\t\tassertEquals(0, getSizeConsumed(new FieldInsnNode(GETSTATIC, \"Owner\", \"name\", \"I\")));\n\t\tassertEquals(1, getSizeConsumed(new FieldInsnNode(GETFIELD, \"Owner\", \"name\", \"I\")));\n\n\t\t// PUT-X -> pop field context (unless static) and field type\n\t\tassertEquals(1, getSizeConsumed(new FieldInsnNode(PUTSTATIC, \"Owner\", \"name\", \"I\")));\n\t\tassertEquals(2, getSizeConsumed(new FieldInsnNode(PUTFIELD, \"Owner\", \"name\", \"I\")));\n\t\tassertEquals(2, getSizeConsumed(new FieldInsnNode(PUTSTATIC, \"Owner\", \"name\", \"J\")));\n\t\tassertEquals(3, getSizeConsumed(new FieldInsnNode(PUTFIELD, \"Owner\", \"name\", \"J\")));\n\n\t\t// metadata --> nothing\n\t\tassertEquals(0, getSizeConsumed(new LabelNode()));\n\t\tassertEquals(0, getSizeConsumed(new LineNumberNode(25, new LabelNode())));\n\t\tassertEquals(0, getSizeConsumed(new FrameNode(0, 0, new Object[0], 0, new Object[0])));\n\n\t\t// TODO: The rest are specific per-op and not by instruction 'type'\n\t}\n\n\t@Test\n\tvoid testGetSizeProduced() {\n\t\t// INVOKE-X --> return type produced regardless of opcode\n\t\tassertEquals(0, getSizeProduced(new MethodInsnNode(INVOKEVIRTUAL, \"Owner\", \"methodName\", \"()V\")));\n\t\tassertEquals(0, getSizeProduced(new MethodInsnNode(INVOKESPECIAL, \"Owner\", \"methodName\", \"()V\")));\n\t\tassertEquals(0, getSizeProduced(new MethodInsnNode(INVOKEINTERFACE, \"Owner\", \"methodName\", \"()V\")));\n\t\tassertEquals(0, getSizeProduced(new MethodInsnNode(INVOKESTATIC, \"Owner\", \"methodName\", \"()V\")));\n\t\tassertEquals(1, getSizeProduced(new MethodInsnNode(INVOKESTATIC, \"Owner\", \"methodName\", \"()I\")));\n\t\tassertEquals(2, getSizeProduced(new MethodInsnNode(INVOKESTATIC, \"Owner\", \"methodName\", \"()J\")));\n\t\tassertEquals(1, getSizeProduced(new MethodInsnNode(INVOKESTATIC, \"Owner\", \"methodName\", \"()[J\")));\n\n\t\t// INVOKE-DYNAMIC -> return type produced (from descriptor)\n\t\tHandle bogusHandle = new Handle(H_INVOKESTATIC, \"Owner\", \"name\", \"()V\", false);\n\t\tassertEquals(0, getSizeProduced(new InvokeDynamicInsnNode(\"name\", \"()V\", bogusHandle)));\n\t\tassertEquals(0, getSizeProduced(new InvokeDynamicInsnNode(\"name\", \"(I)V\", bogusHandle)));\n\t\tassertEquals(1, getSizeProduced(new InvokeDynamicInsnNode(\"name\", \"()I\", bogusHandle)));\n\t\tassertEquals(2, getSizeProduced(new InvokeDynamicInsnNode(\"name\", \"()J\", bogusHandle)));\n\t\tassertEquals(1, getSizeProduced(new InvokeDynamicInsnNode(\"name\", \"()[J\", bogusHandle)));\n\n\t\t// GET-X -> field type produced (regardless of static)\n\t\tassertEquals(1, getSizeProduced(new FieldInsnNode(GETFIELD, \"Owner\", \"name\", \"I\")));\n\t\tassertEquals(1, getSizeProduced(new FieldInsnNode(GETSTATIC, \"Owner\", \"name\", \"I\")));\n\t\tassertEquals(2, getSizeProduced(new FieldInsnNode(GETFIELD, \"Owner\", \"name\", \"J\")));\n\t\tassertEquals(1, getSizeProduced(new FieldInsnNode(GETFIELD, \"Owner\", \"name\", \"[J\")));\n\n\t\t// PUT-X -> nothing produced\n\t\tassertEquals(0, getSizeProduced(new FieldInsnNode(PUTFIELD, \"Owner\", \"name\", \"I\")));\n\t\tassertEquals(0, getSizeProduced(new FieldInsnNode(PUTSTATIC, \"Owner\", \"name\", \"I\")));\n\t\tassertEquals(0, getSizeProduced(new FieldInsnNode(PUTFIELD, \"Owner\", \"name\", \"J\")));\n\t\tassertEquals(0, getSizeProduced(new FieldInsnNode(PUTFIELD, \"Owner\", \"name\", \"[J\")));\n\n\t\t// LDC --> depends on value\n\t\tassertEquals(1, getSizeProduced(new LdcInsnNode(0)));\n\t\tassertEquals(1, getSizeProduced(new LdcInsnNode(\"a\")));\n\t\tassertEquals(1, getSizeProduced(new LdcInsnNode(1.5F)));\n\t\tassertEquals(2, getSizeProduced(new LdcInsnNode(Double.MAX_VALUE)));\n\t\tassertEquals(2, getSizeProduced(new LdcInsnNode(Long.MAX_VALUE)));\n\n\t\t// Jump --> Only if JSR\n\t\tassertEquals(1, getSizeProduced(new JumpInsnNode(JSR, new LabelNode())));\n\t\tassertEquals(0, getSizeProduced(new JumpInsnNode(IFEQ, new LabelNode())));\n\t\tassertEquals(0, getSizeProduced(new JumpInsnNode(IF_ACMPNE, new LabelNode())));\n\n\t\t// metadata --> nothing\n\t\tassertEquals(0, getSizeProduced(new LabelNode()));\n\t\tassertEquals(0, getSizeProduced(new LineNumberNode(25, new LabelNode())));\n\t\tassertEquals(0, getSizeProduced(new FrameNode(0, 0, new Object[0], 0, new Object[0])));\n\n\t\t// TODO: The rest are specific per-op and not by instruction 'type'\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/util/EscapeUtilTest.java",
    "content": "package software.coley.recaf.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static software.coley.recaf.util.EscapeUtil.*;\n\n/**\n * Tests for {@link EscapeUtil}\n */\nclass EscapeUtilTest {\n\t@Test\n\tvoid testCasePairs() {\n\t\tescapeUnescape(\n\t\t\t\tnew Case(\"foobar\", \"foobar\", \"foobar\", \"foobar\"),\n\n\t\t\t\t// Spaces\n\t\t\t\tnew Case(\"foo bar\", \"foo bar\", \"foo bar\", \"foo\\\\u0020bar\"),\n\t\t\t\tnew Case(\"foo\\nbar\", \"foo\\\\nbar\", \"foo\\\\nbar\", \"foo\\\\u000Abar\"),\n\t\t\t\tnew Case(\"foo\\rbar\", \"foo\\\\rbar\", \"foo\\\\rbar\", \"foo\\\\u000Dbar\"),\n\t\t\t\tnew Case(\"foo\\tbar\", \"foo\\\\tbar\", \"foo\\\\tbar\", \"foo\\\\u0009bar\"),\n\t\t\t\tnew Case(\"foo\\u2004bar\", \"foo\\u2004bar\", \"foo\\\\u2004bar\", \"foo\\\\u2004bar\"),\n\t\t\t\tnew Case(\"foo\\u2800bar\", \"foo\\u2800bar\", \"foo\\\\u2800bar\", \"foo\\\\u2800bar\"),\n\t\t\t\tnew Case(\"foo\\u3000bar\", \"foo\\u3000bar\", \"foo\\\\u3000bar\", \"foo\\\\u3000bar\"),\n\n\t\t\t\t// Slashes\n\t\t\t\tnew Case(\"foo/bar\", \"foo/bar\", \"foo/bar\", \"foo/bar\"),\n\t\t\t\tnew Case(\"foo\\\\bar\", \"foo\\\\\\\\bar\", \"foo\\\\\\\\bar\", \"foo\\\\\\\\bar\"),\n\n\t\t\t\t// Quotes\n\t\t\t\tnew Case(\"foo\\\"bar\", \"foo\\\\\\\"bar\", \"foo\\\\\\\"bar\", \"foo\\\\u0022bar\"),\n\t\t\t\tnew Case(\"foo'bar\", \"foo'bar\", \"foo'bar\", \"foo'bar\"),\n\n\t\t\t\t// Emoji \"Bridge at Night\" - https://www.compart.com/en/unicode/U+1F309\n\t\t\t\tnew Case(\"foo\\ud83c\\udf09bar\", \"foo\\ud83c\\udf09bar\", \"foo\\ud83c\\udf09bar\", \"foo\\ud83c\\udf09bar\")\n\t\t);\n\t}\n\n\tvoid escapeUnescape(Case... cases) {\n\t\tfor (Case c : cases) {\n\t\t\t// Escaping\n\t\t\tassertEquals(c.standard, escapeStandard(c.original));\n\t\t\tassertEquals(c.standardAndUnicode, escapeStandardAndUnicodeWhitespace(c.original));\n\t\t\tassertEquals(c.standardAndUnicodeAlt, escapeStandardAndUnicodeWhitespaceAlt(c.original));\n\t\t\t// Unescaping\n\t\t\tassertEquals(c.original, unescapeStandard(c.standard));\n\t\t\tassertEquals(c.original, unescapeStandardAndUnicodeWhitespace(c.standardAndUnicode));\n\t\t\tassertEquals(c.original, unescapeUnicode(c.standardAndUnicodeAlt));\n\t\t}\n\t}\n\n\trecord Case(String original, String standard, String standardAndUnicode, String standardAndUnicodeAlt) {}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/util/NumberUtilTest.java",
    "content": "package software.coley.recaf.util;\n\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.Type;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link NumberUtil}\n */\nclass NumberUtilTest {\n\t@Test\n\tvoid testToString() {\n\t\tassertEquals(\"0\", NumberUtil.toString(0));\n\t\tassertEquals(\"0.0\", NumberUtil.toString(0.0));\n\t\tassertEquals(\"0.0\", NumberUtil.toString(0D));\n\t\tassertEquals(\"0.0F\", NumberUtil.toString(0F));\n\t\tassertEquals(\"1.0F\", NumberUtil.toString(1F));\n\t\tassertEquals(\"0L\", NumberUtil.toString(0L));\n\t}\n\n\t@Test\n\tvoid testParse() {\n\t\tassertEquals(0, NumberUtil.parse(\"0\"));\n\t\tassertEquals(0L, NumberUtil.parse(\"0L\"));\n\t\tassertEquals(0D, NumberUtil.parse(\"0.0\"));\n\t\tassertEquals(0D, NumberUtil.parse(\"0.0d\"));\n\t\tassertEquals(0D, NumberUtil.parse(\"0.0D\"));\n\t\tassertEquals(0F, NumberUtil.parse(\"0.0f\"));\n\t\tassertEquals(0F, NumberUtil.parse(\"0.0F\"));\n\t\tassertEquals(0D, NumberUtil.parse(\"0.\"));\n\t\tassertEquals(0F, NumberUtil.parse(\"0.F\"));\n\t\tassertEquals(0, NumberUtil.parse(\"0x\"));\n\t\tassertEquals(0L, NumberUtil.parse(\"0xL\"));\n\t\tassertEquals(0, NumberUtil.parse(\"0x0\"));\n\t\tassertEquals(0L, NumberUtil.parse(\"0x0L\"));\n\t}\n\n\t@Test\n\tvoid testGetWidestType() {\n\t\tassertEquals(Type.DOUBLE_TYPE, NumberUtil.getWidestType(Type.DOUBLE_TYPE, Type.FLOAT_TYPE));\n\t\tassertEquals(Type.DOUBLE_TYPE, NumberUtil.getWidestType(Type.DOUBLE_TYPE, Type.INT_TYPE));\n\t\tassertEquals(Type.DOUBLE_TYPE, NumberUtil.getWidestType(Type.DOUBLE_TYPE, Type.SHORT_TYPE));\n\t\tassertEquals(Type.DOUBLE_TYPE, NumberUtil.getWidestType(Type.DOUBLE_TYPE, Type.BYTE_TYPE));\n\t\tassertEquals(Type.DOUBLE_TYPE, NumberUtil.getWidestType(Type.DOUBLE_TYPE, Type.LONG_TYPE));\n\t\tassertEquals(Type.LONG_TYPE, NumberUtil.getWidestType(Type.LONG_TYPE, Type.INT_TYPE));\n\t\tassertEquals(Type.LONG_TYPE, NumberUtil.getWidestType(Type.LONG_TYPE, Type.FLOAT_TYPE));\n\t\tassertEquals(Type.LONG_TYPE, NumberUtil.getWidestType(Type.LONG_TYPE, Type.SHORT_TYPE));\n\t\tassertEquals(Type.INT_TYPE, NumberUtil.getWidestType(Type.INT_TYPE, Type.SHORT_TYPE));\n\t\tassertEquals(Type.SHORT_TYPE, NumberUtil.getWidestType(Type.SHORT_TYPE, Type.BYTE_TYPE));\n\t}\n\n\t@Test\n\tvoid testCmp() {\n\t\t// Int input\n\t\tassertEquals(0, NumberUtil.cmp(0, 0));\n\t\tassertEquals(1, NumberUtil.cmp(1, 0));\n\t\tassertEquals(-1, NumberUtil.cmp(0, 1));\n\n\t\t// Long input\n\t\tassertEquals(0, NumberUtil.cmp(0L, 0L));\n\t\tassertEquals(1, NumberUtil.cmp(1L, 0L));\n\t\tassertEquals(-1, NumberUtil.cmp(0L, 1L));\n\n\t\t// Double input\n\t\tassertEquals(0, NumberUtil.cmp(0.0, 0.0));\n\t\tassertEquals(1, NumberUtil.cmp(1.0, 0.0));\n\t\tassertEquals(-1, NumberUtil.cmp(0.0, 1.0));\n\n\t\t// Float input\n\t\tassertEquals(0, NumberUtil.cmp(0.0F, 0.0F));\n\t\tassertEquals(1, NumberUtil.cmp(1.0F, 0.0F));\n\t\tassertEquals(-1, NumberUtil.cmp(0.0F, 1.0F));\n\n\t\t// Mixed input\n\t\tassertEquals(0, NumberUtil.cmp(0, 0.0F));\n\t\tassertEquals(1, NumberUtil.cmp(1.0F, 0L));\n\t\tassertEquals(-1, NumberUtil.cmp(0x0L, 1.0));\n\t}\n\n\t@Test\n\tvoid testSub() {\n\t\tassertEquals(3, NumberUtil.sub(10, 7));\n\t\tassertEquals(3D, NumberUtil.sub(10, 7.0D));\n\t\tassertEquals(3F, NumberUtil.sub(10, 7.0F));\n\t\tassertEquals(3L, NumberUtil.sub(10, 7L));\n\t}\n\n\t@Test\n\tvoid testAdd() {\n\t\tassertEquals(10, NumberUtil.add(3, 7));\n\t\tassertEquals(10D, NumberUtil.add(3, 7.0D));\n\t\tassertEquals(10F, NumberUtil.add(3, 7.0F));\n\t\tassertEquals(10L, NumberUtil.add(3, 7L));\n\t}\n\n\t@Test\n\tvoid testMul() {\n\t\tassertEquals(21, NumberUtil.mul(3, 7));\n\t\tassertEquals(21D, NumberUtil.mul(3, 7.0D));\n\t\tassertEquals(21F, NumberUtil.mul(3, 7.0F));\n\t\tassertEquals(21L, NumberUtil.mul(3, 7L));\n\t}\n\n\t@Test\n\tvoid testDiv() {\n\t\tassertEquals(3, NumberUtil.div(21, 7));\n\t\tassertEquals(3D, NumberUtil.div(21, 7.0D));\n\t\tassertEquals(3F, NumberUtil.div(21, 7.0F));\n\t\tassertEquals(3L, NumberUtil.div(21, 7L));\n\t}\n\n\t@Test\n\tvoid testRem() {\n\t\tassertEquals(4, NumberUtil.rem(13, 9));\n\t\tassertEquals(4D, NumberUtil.rem(13, 9.0D));\n\t\tassertEquals(4F, NumberUtil.rem(13, 9.0F));\n\t\tassertEquals(4L, NumberUtil.rem(13, 9L));\n\t}\n\n\t@Test\n\tvoid testAnd() {\n\t\tassertEquals(0b00100, NumberUtil.and(0b11100, 0b00111));\n\t\tassertEquals(0b00100L, NumberUtil.and(0b11100, 0b00111L));\n\t}\n\n\t@Test\n\tvoid testOr() {\n\t\tassertEquals(0b111111, NumberUtil.or(0b101010, 0b010101));\n\t\tassertEquals(0b111111L, NumberUtil.or(0b101010, 0b010101L));\n\t}\n\n\t@Test\n\tvoid testXor() {\n\t\tassertEquals(0b11011, NumberUtil.xor(0b11100, 0b00111));\n\t\tassertEquals(0b11011L, NumberUtil.xor(0b11100, 0b00111L));\n\t}\n\n\t@Test\n\tvoid testNeg() {\n\t\tassertEquals(-1, NumberUtil.neg(1));\n\t\tassertEquals(-1D, NumberUtil.neg(1.0));\n\t\tassertEquals(-1F, NumberUtil.neg(1.0F));\n\t\tassertEquals(-1L, NumberUtil.neg(1L));\n\t}\n\n\t@Test\n\tvoid testShiftLeft() {\n\t\tassertEquals(2 << 1, NumberUtil.shiftLeft(2, 1));\n\t\tassertEquals(2L << 1, NumberUtil.shiftLeft(2L, 1));\n\t}\n\n\t@Test\n\tvoid testShiftRight() {\n\t\tassertEquals(16 >> 1, NumberUtil.shiftRight(16, 1));\n\t\tassertEquals(16L >> 1, NumberUtil.shiftRight(16L, 1));\n\t}\n\n\t@Test\n\tvoid testShiftRightU() {\n\t\tassertEquals(16 >> 1, NumberUtil.shiftRightU(16, 1));\n\t\tassertEquals(16L >> 1, NumberUtil.shiftRightU(16L, 1));\n\t}\n\n\t@Test\n\tvoid testIntPow() {\n\t\tassertEquals(1, NumberUtil.intPow(28419284, 0));\n\t\tassertEquals(100, NumberUtil.intPow(10, 2));\n\t\tassertThrows(IllegalArgumentException.class, () -> NumberUtil.intPow(10, -1));\n\t}\n\n\t@Test\n\tvoid testIntClamp() {\n\t\tassertEquals(5, NumberUtil.intClamp(100, 0, 5));\n\t\tassertEquals(5, NumberUtil.intClamp(-25, 5, 10));\n\t\tassertEquals(5, NumberUtil.intClamp(5, 0, 10));\n\t}\n\n\t@Test\n\tvoid testDoubleClamp() {\n\t\tassertEquals(5D, NumberUtil.doubleClamp(100.0, 0, 5));\n\t\tassertEquals(5D, NumberUtil.doubleClamp(-25.0, 5, 10));\n\t\tassertEquals(5D, NumberUtil.doubleClamp(5.0, 0, 10));\n\t}\n\n\t@Test\n\tvoid testIsNonZero() {\n\t\tassertTrue(NumberUtil.isNonZero(1));\n\t\tassertTrue(NumberUtil.isNonZero(-1));\n\t\tassertFalse(NumberUtil.isNonZero(0));\n\t}\n\n\t@Test\n\tvoid testIsZero() {\n\t\tassertFalse(NumberUtil.isZero(1));\n\t\tassertFalse(NumberUtil.isZero(-1));\n\t\tassertTrue(NumberUtil.isZero(0));\n\t}\n\n\t@Test\n\tvoid testHaveSameSign() {\n\t\tassertTrue(NumberUtil.haveSameSign(1, 1));\n\t\tassertTrue(NumberUtil.haveSameSign(-1, -1));\n\t\tassertFalse(NumberUtil.haveSameSign(1, -1));\n\t\tassertFalse(NumberUtil.haveSameSign(-1, 1));\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/util/StringDiffTest.java",
    "content": "package software.coley.recaf.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests for {@link StringDiff}.\n */\nclass StringDiffTest {\n\t@Test\n\tvoid testIdentity() {\n\t\tString original = \"\"\"\n\t\t\t\tvoid foo() {\n\t\t\t\t\tprint(\"hello\");\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tList<StringDiff.Diff> diffs = StringDiff.diff(original, original);\n\t\tassertEquals(0, diffs.size(), \"No diffs expected\");\n\t}\n\n\t@Test\n\tvoid testSingleUpdate() {\n\t\tString original = \"\"\"\n\t\t\t\tvoid foo() {\n\t\t\t\t\tprint(\"hello\");\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\t\tString modified = \"\"\"\n\t\t\t\tvoid foo() {\n\t\t\t\t\tprint(\"goodbye\");\n\t\t\t\t}\n\t\t\t\t\"\"\";\n\n\t\t// Expect one CHANGE diff\n\t\tList<StringDiff.Diff> diffs = StringDiff.diff(original, modified);\n\t\tassertEquals(1, diffs.size(), \"Mismatch number of expected diffs\");\n\t\tStringDiff.Diff diff = diffs.getFirst();\n\t\tassertEquals(StringDiff.DiffType.CHANGE, diff.type());\n\n\t\t// Only the string content changed\n\t\tassertEquals(\"hello\", diff.textA());\n\t\tassertEquals(\"goodbye\", diff.textB());\n\t\tassertEquals(original.indexOf(\"hello\"), diff.startA());\n\t\tassertEquals(original.indexOf(\"hello\") + \"hello\".length(), diff.endA());\n\t\tassertEquals(modified.indexOf(\"goodbye\"), diff.startB());\n\t\tassertEquals(modified.indexOf(\"goodbye\") + \"goodbye\".length(), diff.endB());\n\n\t\t// Patch should equal modified\n\t\tString patched = diff.apply(original);\n\t\tassertEquals(modified, patched);\n\t}\n\n\t@Test\n\tvoid testSingleRemove() {\n\t\tString original = \"\"\"\n\t\t\t\tvoid foo() {\n\t\t\t\t\tprint(\"hello\");\n\t\t\t\t}\"\"\";\n\t\tString modified = \"\"\"\n\t\t\t\tvoid foo() {\n\t\t\t\t\t\t\t\t\n\t\t\t\t}\"\"\";\n\n\t\t// Expect one REMOVE diff\n\t\tList<StringDiff.Diff> diffs = StringDiff.diff(original, modified);\n\t\tassertEquals(1, diffs.size(), \"Mismatch number of expected diffs\");\n\t\tStringDiff.Diff diff = diffs.getFirst();\n\t\tassertEquals(StringDiff.DiffType.REMOVE, diff.type());\n\n\t\t// The print call got removed\n\t\tassertEquals(\"\\tprint(\\\"hello\\\");\", diff.textA());\n\t\tassertEquals(\"\", diff.textB());\n\n\t\t// Patch should equal modified\n\t\tString patched = diff.apply(original);\n\t\tassertEquals(modified, patched);\n\t}\n\n\t@Test\n\tvoid testSingleRemoveCollapseLines() {\n\t\tString original = \"\"\"\n\t\t\t\tvoid foo() {\n\t\t\t\t\tprint(\"hello\");\n\t\t\t\t}\"\"\";\n\t\tString modified = \"void foo() {}\";\n\n\t\t// Expect one REMOVE diff\n\t\tList<StringDiff.Diff> diffs = StringDiff.diff(original, modified);\n\t\tassertEquals(1, diffs.size(), \"Mismatch number of expected diffs\");\n\t\tStringDiff.Diff diff = diffs.getFirst();\n\t\tassertEquals(StringDiff.DiffType.REMOVE, diff.type());\n\n\t\t// We removed multiple \"lines\" to turn this into a single line\n\t\tassertEquals(\"\\n\\tprint(\\\"hello\\\");\\n\", diff.textA());\n\t\tassertEquals(\"\", diff.textB());\n\n\t\t// Patch should equal modified\n\t\tString patched = diff.apply(original);\n\t\tassertEquals(modified, patched);\n\t}\n\n\t@Test\n\tvoid testExampleTernary() {\n\t\tString original = \"\"\"\n\t\t\t\tvoid foo() {\n\t\t\t\t\tif (bar())\n\t\t\t\t\t   println(\"is bar\");\n\t\t\t\t\telse\n\t\t\t\t\t   println(\"not bar\");\n\t\t\t\t}\"\"\";\n\t\tString modified = \"\"\"\n\t\t\t\tvoid foo() {\n\t\t\t\t\tprintln(bar() ? \"is bar\" : \"not bar\");\n\t\t\t\t}\"\"\";\n\n\t\t// Expect one CHANGE diff\n\t\tList<StringDiff.Diff> diffs = StringDiff.diff(original, modified);\n\t\tassertEquals(1, diffs.size(), \"Mismatch number of expected diffs\");\n\t\tStringDiff.Diff diff = diffs.getFirst();\n\t\tassertEquals(StringDiff.DiffType.CHANGE, diff.type());\n\n\t\t// The \"not bar\"); is the common suffix so the code before contains the part we need to swap out.\n\t\tassertEquals(\"\"\"\n\t\t\t\tif (bar())\n\t\t\t\t\\t   println(\"is bar\");\n\t\t\t\t\\telse\n\t\t\t\t\\t   println(\"\"\", diff.textA());\n\t\tassertEquals(\"println(bar() ? \\\"is bar\\\" : \", diff.textB());\n\n\t\t// Patch should equal modified\n\t\tString patched = diff.apply(original);\n\t\tassertEquals(modified, patched);\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/util/StringUtilTest.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport software.coley.recaf.test.TestBase;\n\nimport java.io.File;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link StringUtil}\n */\nclass StringUtilTest {\n\t@Test\n\tvoid indicesOf() {\n\t\tassertArrayEquals(new int[]{0}, StringUtil.indicesOf(\"\\n\", '\\n'));\n\t\tassertArrayEquals(new int[]{0, 1, 2}, StringUtil.indicesOf(\"\\n\\n\\n\", '\\n'));\n\t\tassertArrayEquals(new int[]{1, 3, 4}, StringUtil.indicesOf(\"a\\na\\n\\n\", '\\n'));\n\t\tassertArrayEquals(new int[]{0, 1, 3}, StringUtil.indicesOf(\"\\n\\na\\na\", '\\n'));\n\t}\n\n\t@Test\n\tvoid testFastSplit() {\n\t\t// Empty discarded\n\t\tassertEquals(List.of(\"a\", \"b\", \"c\", \"d\"), StringUtil.fastSplit(\"a/b/c/d\", false, '/'));\n\t\tassertEquals(List.of(\"a\", \"b\", \"c\"), StringUtil.fastSplit(\"a/b/c/\", false, '/'));\n\t\tassertEquals(List.of(\"b\", \"c\", \"d\"), StringUtil.fastSplit(\"/b/c/d\", false, '/'));\n\t\tassertEquals(Collections.emptyList(), StringUtil.fastSplit(\"/\", false, '/'));\n\t\tassertEquals(Collections.emptyList(), StringUtil.fastSplit(\"//\", false, '/'));\n\t\tassertEquals(Collections.emptyList(), StringUtil.fastSplit(\"///\", false, '/'));\n\n\t\t// Empty included\n\t\tassertEquals(List.of(\"a\", \"b\", \"c\", \"\"), StringUtil.fastSplit(\"a/b/c/\", true, '/'));\n\t\tassertEquals(List.of(\"\", \"b\", \"c\", \"d\"), StringUtil.fastSplit(\"/b/c/d\", true, '/'));\n\t\tassertEquals(List.of(\"\", \"\"), StringUtil.fastSplit(\"/\", true, '/'));\n\t\tassertEquals(List.of(\"\", \"\", \"\"), StringUtil.fastSplit(\"//\", true, '/'));\n\t\tassertEquals(List.of(\"\", \"\", \"\", \"\"), StringUtil.fastSplit(\"///\", true, '/'));\n\t}\n\n\t@Test\n\tvoid fastSplitNonIdentifier() {\n\t\tassertEquals(List.of(\"a\", \"b\", \"c\", \"d\"), StringUtil.fastSplitNonIdentifier(\"a/b/c/d\"));\n\t\tassertEquals(List.of(\"a\", \"b\", \"c\", \"d\"), StringUtil.fastSplitNonIdentifier(\"a b c d\"));\n\t\tassertEquals(List.of(\"a\", \"b\", \"c\", \"d\"), StringUtil.fastSplitNonIdentifier(\"/a/b/c/d/\"));\n\t\tassertEquals(List.of(\"a\", \"b\", \"c\", \"d\"), StringUtil.fastSplitNonIdentifier(\" a b c d \"));\n\t\tassertEquals(Collections.emptyList(), StringUtil.fastSplitNonIdentifier(\"/\"));\n\t\tassertEquals(Collections.emptyList(), StringUtil.fastSplitNonIdentifier(\"//\"));\n\t\tassertEquals(Collections.emptyList(), StringUtil.fastSplitNonIdentifier(\"///\"));\n\t\tassertEquals(Collections.emptyList(), StringUtil.fastSplitNonIdentifier(\" \"));\n\t\tassertEquals(Collections.emptyList(), StringUtil.fastSplitNonIdentifier(\"  \"));\n\t\tassertEquals(Collections.emptyList(), StringUtil.fastSplitNonIdentifier(\"   \"));\n\t\tassertEquals(List.of(\"if\", \"true\", \"while\", \"condition\", \"a\", \"b\", \"c\", \"continue\"),\n\t\t\t\tStringUtil.fastSplitNonIdentifier(\"if(true){while(condition||-a*+b<c){continue;}}\"));\n\t}\n\n\t@Test\n\tvoid testGetTabAdjustedLength() {\n\t\tassertEquals(4, StringUtil.getTabAdjustedLength(\"\\t\", 4));\n\t\tassertEquals(4, StringUtil.getTabAdjustedLength(\" \\t\", 4));\n\t\tassertEquals(4, StringUtil.getTabAdjustedLength(\"  \\t\", 4));\n\t\tassertEquals(4, StringUtil.getTabAdjustedLength(\"   \\t\", 4));\n\t\tassertEquals(4, StringUtil.getTabAdjustedLength(\"    \", 4));\n\t\tassertEquals(10, StringUtil.getTabAdjustedLength(\"\\t\\t\", 5));\n\t\tassertEquals(10, StringUtil.getTabAdjustedLength(\" \\t\\t\", 5));\n\t\tassertEquals(10, StringUtil.getTabAdjustedLength(\"  \\t\\t\", 5));\n\t\tassertEquals(10, StringUtil.getTabAdjustedLength(\"   \\t\\t\", 5));\n\t\tassertEquals(10, StringUtil.getTabAdjustedLength(\"     \\t\", 5));\n\t}\n\n\t@Test\n\tvoid testGetWhitespacePrefixLength() {\n\t\tassertEquals(4, StringUtil.getWhitespacePrefixLength(\"    text\", 4));\n\t\tassertEquals(4, StringUtil.getWhitespacePrefixLength(\"   \\ttext\", 4));\n\t\tassertEquals(4, StringUtil.getWhitespacePrefixLength(\"  \\ttext\", 4));\n\t\tassertEquals(4, StringUtil.getWhitespacePrefixLength(\" \\ttext\", 4));\n\t\tassertEquals(4, StringUtil.getWhitespacePrefixLength(\"\\ttext\", 4));\n\t\tassertEquals(8, StringUtil.getWhitespacePrefixLength(\"    \\ttext\", 4));\n\t\tassertEquals(8, StringUtil.getWhitespacePrefixLength(\"   \\t \\ttext\", 4));\n\t\tassertEquals(8, StringUtil.getWhitespacePrefixLength(\"  \\t   \\ttext\", 4));\n\t\tassertEquals(8, StringUtil.getWhitespacePrefixLength(\"\\t \\ttext\", 4));\n\t\tassertEquals(8, StringUtil.getWhitespacePrefixLength(\" \\t   \\ttext\", 4));\n\t}\n\n\t@Test\n\tvoid testSplitNewline() {\n\t\tassertEquals(1, StringUtil.splitNewline(\"\").length);\n\t\tassertEquals(1, StringUtil.splitNewline(\"a\").length);\n\t\tassertEquals(2, StringUtil.splitNewline(\"a\\nb\").length);\n\t\tassertEquals(2, StringUtil.splitNewline(\"a\\n\\rb\").length);\n\t\tassertEquals(2, StringUtil.splitNewline(\"a\\r\\nb\").length);\n\t\tassertEquals(3, StringUtil.splitNewline(\"a\\n\\nb\").length);\n\t\tassertEquals(3, StringUtil.splitNewline(\"a\\n\\r\\r\\nb\").length);\n\t\tassertEquals(3, StringUtil.splitNewline(\"a\\r\\n\\r\\nb\").length);\n\t\tassertEquals(3, StringUtil.splitNewline(\"a\\r\\n\\n\\rb\").length);\n\t\tassertEquals(3, StringUtil.splitNewline(\"a\\nb\\nc\").length);\n\t}\n\n\t@Test\n\tvoid testSplitNewlineSkipEmpty() {\n\t\tassertEquals(1, StringUtil.splitNewlineSkipEmpty(\"\").length);\n\t\tassertEquals(1, StringUtil.splitNewlineSkipEmpty(\"a\").length);\n\t\tassertEquals(2, StringUtil.splitNewlineSkipEmpty(\"a\\nb\").length);\n\t\tassertEquals(2, StringUtil.splitNewlineSkipEmpty(\"a\\n\\rb\").length);\n\t\tassertEquals(2, StringUtil.splitNewlineSkipEmpty(\"a\\r\\nb\").length);\n\t\tassertEquals(2, StringUtil.splitNewlineSkipEmpty(\"a\\n\\nb\").length);\n\t\tassertEquals(2, StringUtil.splitNewlineSkipEmpty(\"a\\n\\r\\r\\nb\").length);\n\t\tassertEquals(2, StringUtil.splitNewlineSkipEmpty(\"a\\r\\n\\r\\nb\").length);\n\t\tassertEquals(2, StringUtil.splitNewlineSkipEmpty(\"a\\r\\n\\n\\rb\").length);\n\t\tassertEquals(2, StringUtil.splitNewlineSkipEmpty(\"a\\r\\n\\n\\n\\n\\n\\rb\").length);\n\t}\n\n\t@Test\n\tvoid testCutOff() {\n\t\tassertEquals(\"\", StringUtil.cutOff(\"\", 0));\n\t\tassertEquals(\"\", StringUtil.cutOff(\"\", 1));\n\t\tassertEquals(\"\", StringUtil.cutOff(\"abcdefgd\", 0));\n\t\tassertEquals(\"abc\", StringUtil.cutOff(\"abcdefg\", 3));\n\t}\n\n\t@Test\n\tvoid testCutOffAtFirst() {\n\t\t// chars\n\t\tassertEquals(\"\", StringUtil.cutOffAtFirst(\"\", 'd'));\n\t\tassertEquals(\"abc\", StringUtil.cutOffAtFirst(\"abcdefg\", 'd'));\n\t\tassertEquals(\"abc\", StringUtil.cutOffAtFirst(\"abcdefgd\", 'd'));\n\t\t// strings\n\t\tassertEquals(\"\", StringUtil.cutOffAtFirst(\"\", \"d\"));\n\t\tassertEquals(\"abc\", StringUtil.cutOffAtFirst(\"abcdefg\", \"d\"));\n\t\tassertEquals(\"abc\", StringUtil.cutOffAtFirst(\"abcdefgd\", \"d\"));\n\t}\n\n\t@Test\n\tvoid testCutOffAtLast() {\n\t\t// chars\n\t\tassertEquals(\"\", StringUtil.cutOffAtLast(\"\", 'd'));\n\t\tassertEquals(\"abc\", StringUtil.cutOffAtLast(\"abcdefg\", 'd'));\n\t\tassertEquals(\"abcdefg\", StringUtil.cutOffAtLast(\"abcdefgd\", 'd'));\n\t\t// strings\n\t\tassertEquals(\"\", StringUtil.cutOffAtLast(\"\", \"d\"));\n\t\tassertEquals(\"abc\", StringUtil.cutOffAtLast(\"abcdefg\", \"d\"));\n\t\tassertEquals(\"abcdefg\", StringUtil.cutOffAtLast(\"abcdefgd\", \"d\"));\n\t}\n\n\t@Test\n\tvoid testCutOffAtNth() {\n\t\t// chars\n\t\tassertEquals(\"\", StringUtil.cutOffAtNth(\"\", 'a', -1));\n\t\tassertEquals(\"\", StringUtil.cutOffAtNth(\"\", 'a', 0));\n\t\tassertEquals(\"\", StringUtil.cutOffAtNth(\"\", 'a', 1));\n\t\tassertEquals(\"aaa\", StringUtil.cutOffAtNth(\"aaa\", 'a', -1));\n\t\tassertEquals(\"aaa\", StringUtil.cutOffAtNth(\"aaa\", 'a', 0));\n\t\tassertEquals(\"\", StringUtil.cutOffAtNth(\"aaa\", 'a', 1));\n\t\tassertEquals(\"a\", StringUtil.cutOffAtNth(\"aaa\", 'a', 2));\n\t\tassertEquals(\"aa\", StringUtil.cutOffAtNth(\"aaa\", 'a', 3));\n\t\tassertEquals(\"aaa\", StringUtil.cutOffAtNth(\"aaa\", 'a', 4));\n\t\t// strings\n\t\tassertEquals(\"\", StringUtil.cutOffAtNth(\"\", \"a\", -1));\n\t\tassertEquals(\"\", StringUtil.cutOffAtNth(\"\", \"a\", 0));\n\t\tassertEquals(\"\", StringUtil.cutOffAtNth(\"\", \"a\", 1));\n\t\tassertEquals(\"aaa\", StringUtil.cutOffAtNth(\"aaa\", \"a\", -1));\n\t\tassertEquals(\"aaa\", StringUtil.cutOffAtNth(\"aaa\", \"a\", 0));\n\t\tassertEquals(\"\", StringUtil.cutOffAtNth(\"aaa\", \"a\", 1));\n\t\tassertEquals(\"a\", StringUtil.cutOffAtNth(\"aaa\", \"a\", 2));\n\t\tassertEquals(\"aa\", StringUtil.cutOffAtNth(\"aaa\", \"a\", 3));\n\t\tassertEquals(\"aaa\", StringUtil.cutOffAtNth(\"aaa\", \"a\", 4));\n\t\tassertEquals(\"b\", StringUtil.cutOffAtNth(\"bbbbbbbbbb\", \"bb\", 2));\n\t\tassertEquals(\"bb\", StringUtil.cutOffAtNth(\"bbbbbbbbbb\", \"bb\", 3));\n\t\tassertEquals(\"bbb\", StringUtil.cutOffAtNth(\"bbbbbbbbbb\", \"bb\", 4));\n\t}\n\n\t@Test\n\tvoid testGetAfter() {\n\t\tassertEquals(\"\", StringUtil.getAfter(\"\", \"\"));\n\t\tassertEquals(\"d\", StringUtil.getAfter(\"a b c d\", \"c \"));\n\t\tassertEquals(\"a b c d\", StringUtil.getAfter(\"a b c d\", \"z\"));\n\t}\n\n\t@Test\n\tvoid testInsert() {\n\t\tassertEquals(\"in\", StringUtil.insert(\"in\", -1, \"prefix\"));\n\t\tassertEquals(\"in\", StringUtil.insert(\"in\", 0, null));\n\t\tassertEquals(\"in\", StringUtil.insert(\"in\", 0, \"\"));\n\t\tassertEquals(\"in\", StringUtil.insert(\"in\", 999, \"suffix\"));\n\t\tassertEquals(\"prefixin\", StringUtil.insert(\"in\", 0, \"prefix\"));\n\t\tassertEquals(\"insuffix\", StringUtil.insert(\"in\", 2, \"suffix\"));\n\t}\n\n\t@Test\n\tvoid testRemove() {\n\t\tassertEquals(\"demo\", StringUtil.remove(\"demo\", -1, 4));\n\t\tassertEquals(\"demo\", StringUtil.remove(\"demo\", 999, 4));\n\t\tassertEquals(\"do\", StringUtil.remove(\"demo\", 1, 2));\n\t\tassertEquals(\"\", StringUtil.remove(\"demo\", 0, 4));\n\t\tassertEquals(\"\", StringUtil.remove(\"demo\", 0, 999));\n\t}\n\n\t@Test\n\tvoid testWithEmptyFallback() {\n\t\tassertEquals(\"fall\", StringUtil.withEmptyFallback(null, \"fall\"));\n\t\tassertEquals(\"fall\", StringUtil.withEmptyFallback(\"\", \"fall\"));\n\t\tassertEquals(\"fall\", StringUtil.withEmptyFallback(\" \", \"fall\"));\n\t\tassertEquals(\"fall\", StringUtil.withEmptyFallback(\"\\n\", \"fall\"));\n\t\tassertEquals(\"demo\", StringUtil.withEmptyFallback(\"demo\", \"fall\"));\n\t}\n\n\t@Test\n\tvoid testIsDecimal() {\n\t\tassertTrue(StringUtil.isDecimal(\"1\"));\n\t\tassertTrue(StringUtil.isDecimal(\"1.0\"));\n\t\tassertTrue(StringUtil.isDecimal(\"-1.0\"));\n\t\tassertTrue(StringUtil.isDecimal(\"1\" + \"0\".repeat(Short.MAX_VALUE)));\n\t\tassertFalse(StringUtil.isDecimal(\"\"));\n\t\tassertFalse(StringUtil.isDecimal(\" \"));\n\t\tassertFalse(StringUtil.isDecimal(\"\\0\"));\n\t}\n\n\t@Test\n\tvoid testReplaceLast() {\n\t\tassertEquals(\"a a x\", StringUtil.replaceLast(\"a a a\", \"a\", \"x\"));\n\t\tassertEquals(\"a a a\", StringUtil.replaceLast(\"a a a\", \"z\", \"x\"));\n\t}\n\n\t@Test\n\tvoid testReplacePrefix() {\n\t\tassertEquals(\"xa a a\", StringUtil.replacePrefix(\"a a a\", null, \"x\"));\n\t\tassertEquals(\"xa a a\", StringUtil.replacePrefix(\"a a a\", \"\", \"x\"));\n\t\tassertEquals(\"x a a\", StringUtil.replacePrefix(\"a a a\", \"a\", \"x\"));\n\t\tassertEquals(\"a a a\", StringUtil.replacePrefix(\"a a a\", \"z\", \"x\"));\n\t}\n\n\t@Test\n\tvoid testReplaceRange() {\n\t\tassertEquals(\"_cdefg\", StringUtil.replaceRange(\"abcdefg\", 0, 2, \"_\"));\n\t}\n\n\t@Test\n\tvoid testCount() {\n\t\tassertEquals(0, StringUtil.count('a', null));\n\t\tassertEquals(0, StringUtil.count(\"a\", null));\n\t\tassertEquals(0, StringUtil.count('a', \"\"));\n\t\tassertEquals(0, StringUtil.count(\"a\", \"\"));\n\t\tassertEquals(0, StringUtil.count('a', \"foo\"));\n\t\tassertEquals(0, StringUtil.count(\"a\", \"foo\"));\n\t\tassertEquals(2, StringUtil.count('a', \"example bar\"));\n\t\tassertEquals(2, StringUtil.count(\"a\", \"example bar\"));\n\t}\n\n\t@Test\n\tvoid testCountRegex() {\n\t\tassertEquals(0, StringUtil.countRegex(\"\\\\w\", null));\n\t\tassertEquals(0, StringUtil.countRegex(\"\\\\w\", null));\n\t\tassertEquals(0, StringUtil.countRegex(\"\\\\w\", \"\"));\n\t\tassertEquals(3, StringUtil.countRegex(\"\\\\w\", \"foo\"));\n\t\tassertEquals(1, StringUtil.countRegex(\"\\\\w{2}\", \"foo\"));\n\t\tassertEquals(2, StringUtil.countRegex(\"\\\\w+\", \"example bar\"));\n\t}\n\n\t@Test\n\tvoid testPathToString() {\n\t\tassertEquals(\"foo\" + File.separator + \"bar.txt\", StringUtil.pathToString(Paths.get(\"foo/bar.txt\")));\n\t}\n\n\t@Test\n\tvoid testPathToNameString() {\n\t\tassertEquals(\"bar.txt\", StringUtil.pathToNameString(Paths.get(\"foo/bar.txt\")));\n\t}\n\n\t@Test\n\tvoid testShortenPath() {\n\t\tassertEquals(\"bar.txt\", StringUtil.shortenPath(\"a/b/c/d/bar.txt\"));\n\t}\n\n\t@Test\n\tvoid testRemoveExtension() {\n\t\tassertEquals(\"bar\", StringUtil.removeExtension(\"bar.txt\"));\n\t\tassertEquals(\"bar.tar\", StringUtil.removeExtension(\"bar.tar.gz\"));\n\t}\n\n\t@Test\n\tvoid testLimit() {\n\t\tassertEquals(\"abcdefg\", StringUtil.limit(\"abcdefg\", 999));\n\t\tassertEquals(\"abc\", StringUtil.limit(\"abcdefg\", 3));\n\t\tassertEquals(\"\", StringUtil.limit(\"abcdefg\", 0));\n\t\tassertEquals(\"\", StringUtil.limit(\"abcdefg\", -1));\n\t\tassertEquals(\"abc...\", StringUtil.limit(\"abcdefg\", \"...\", 3));\n\t}\n\n\t@Test\n\tvoid testLowercaseFirstChar() {\n\t\tassertEquals(\"\", StringUtil.lowercaseFirstChar(\"\"));\n\t\tassertEquals(\"f\", StringUtil.lowercaseFirstChar(\"F\"));\n\t\tassertEquals(\"foo\", StringUtil.lowercaseFirstChar(\"Foo\"));\n\t\tassertEquals(\"fOO\", StringUtil.lowercaseFirstChar(\"FOO\"));\n\t}\n\n\t@Test\n\tvoid testUppercaseFirstChar() {\n\t\tassertEquals(\"\", StringUtil.uppercaseFirstChar(\"\"));\n\t\tassertEquals(\"F\", StringUtil.uppercaseFirstChar(\"f\"));\n\t\tassertEquals(\"Foo\", StringUtil.uppercaseFirstChar(\"foo\"));\n\t\tassertEquals(\"FOO\", StringUtil.uppercaseFirstChar(\"fOO\"));\n\t}\n\n\t@Test\n\tvoid testGetCommonPrefix() {\n\t\tassertEquals(\"\", StringUtil.getCommonPrefix(\"\", \"\"));\n\t\tassertEquals(\"1\", StringUtil.getCommonPrefix(\"123\", \"1bc\"));\n\t\tassertEquals(\"\", StringUtil.getCommonPrefix(\"aaa\", \"bbb\"));\n\t\tassertEquals(\"com/\", StringUtil.getCommonPrefix(\"com/foo\", \"com/bar\"));\n\t}\n\n\t@Test\n\tvoid testGetCommonSuffix() {\n\t\tassertEquals(\"\", StringUtil.getCommonSuffix(\"\", \"\"));\n\t\tassertEquals(\"1\", StringUtil.getCommonSuffix(\"321\", \"ab1\"));\n\t\tassertEquals(\"1\", StringUtil.getCommonSuffix(\"31\", \"ab1\"));\n\t\tassertEquals(\"1\", StringUtil.getCommonSuffix(\"1\", \"ab1\"));\n\t\tassertEquals(\"\", StringUtil.getCommonSuffix(\"\", \"ab1\"));\n\t\tassertEquals(\"\", StringUtil.getCommonSuffix(\"ab1\", \"\"));\n\t\tassertEquals(\"\", StringUtil.getCommonSuffix(\"aaa\", \"bbb\"));\n\t\tString codeIfElse = \"\"\"\n\t\t\t\tif (bar())\n\t\t\t\t   println(\"is bar\");\n\t\t\t\telse\n\t\t\t\t   println(\"not bar\");\"\"\";\n\t\tString codeTernary = \"\"\"\n\t\t\t\tprintln(bar() ? \"is bar\" : \"not bar\");\"\"\";\n\t\tassertEquals(\"\\\"not bar\\\");\", StringUtil.getCommonSuffix(codeIfElse, codeTernary));\n\t}\n\n\t@Test\n\tvoid testFillLeft() {\n\t\tassertEquals(\"aaab\", StringUtil.fillLeft(4, \"a\", \"b\"));\n\t\tassertEquals(\"aaaa\", StringUtil.fillLeft(4, \"a\", null));\n\t}\n\n\t@Test\n\tvoid testFillRight() {\n\t\tassertEquals(\"baaa\", StringUtil.fillRight(4, \"a\", \"b\"));\n\t\tassertEquals(\"aaaa\", StringUtil.fillRight(4, \"a\", null));\n\t}\n\n\t@Test\n\tvoid testWordWrap() {\n\t\tString text = assertDoesNotThrow(() -> Files.readString(Paths.get(\"src/testFixtures/resources/lorem-long-ascii.txt\")));\n\t\tint[] lengths = new int[] {25, 50, 75, 100, 200};\n\t\tfor (int length : lengths) {\n\t\t\tString wrapped = StringUtil.wordWrap(text, length);\n\t\t\tassertNotEquals(wrapped, text);\n\n\t\t\tString[] lines = wrapped.split(\"\\n\");\n\t\t\tfor (String line : lines)\n\t\t\t\tassertTrue(line.length() <= length);\n\t\t}\n\t}\n\n\t@Test\n\tvoid testIsAnyNullOrEmpty() {\n\t\tassertTrue(StringUtil.isAnyNullOrEmpty(\"\", \"a\", null));\n\t\tassertTrue(StringUtil.isAnyNullOrEmpty(\"\", \"a\"));\n\t\tassertTrue(StringUtil.isAnyNullOrEmpty(\"a\", null));\n\t\tassertTrue(StringUtil.isAnyNullOrEmpty(\"\"));\n\t\tassertTrue(StringUtil.isAnyNullOrEmpty((String) null));\n\t\tassertFalse(StringUtil.isAnyNullOrEmpty(\"a\", \"b\", \"c\"));\n\t}\n\n\t@Test\n\tvoid testGetEntropy() {\n\t\tassertEquals(0, StringUtil.getEntropy(null));\n\t\tassertEquals(0, StringUtil.getEntropy(\"\"));\n\t\tassertEquals(0, StringUtil.getEntropy(\"a\"));\n\t\tassertEquals(0, StringUtil.getEntropy(\"aa\"));\n\t\tassertEquals(0, StringUtil.getEntropy(\"aaa\"));\n\t\t//\n\t\tassertEquals(1, StringUtil.getEntropy(\"ab\"));\n\t\tassertEquals(1, StringUtil.getEntropy(\"\\u0000\\uFFFF\"));\n\t\t//\n\t\tassertEquals(1.5849625, StringUtil.getEntropy(\"abc\"), 0.0001);\n\t\tassertEquals(1.5849625, StringUtil.getEntropy(\"aabbcc\"), 0.0001);\n\t\tassertEquals(0.6500224, StringUtil.getEntropy(\"aaaaax\"), 0.0001);\n\t}\n\n\t@Test\n\tvoid testTraceToString() {\n\t\tString trace = StringUtil.traceToString(new Throwable(\"Message\"));\n\t\tassertTrue(trace.startsWith(\"java.lang.Throwable: Message\"));\n\t\tassertTrue(trace.contains(\"at \" + getClass().getName() + \".testTraceToString\"));\n\t}\n\n\t@Test\n\tvoid testToHexString() {\n\t\tassertEquals(\"0\", StringUtil.toHexString(0));\n\t\tassertEquals(\"ffffffff\", StringUtil.toHexString(-1));\n\t\tassertEquals(\"10\", StringUtil.toHexString(16));\n\t\tassertEquals(\"fffffff0\", StringUtil.toHexString(-16));\n\t}\n\n\t@Test\n\tvoid testGenerateIncrementingName() {\n\t\tassertEquals(\"a\", StringUtil.generateIncrementingName(\"abc\", 0));\n\t\tassertEquals(\"b\", StringUtil.generateIncrementingName(\"abc\", 1));\n\t\tassertEquals(\"c\", StringUtil.generateIncrementingName(\"abc\", 2));\n\t\tassertEquals(\"aa\", StringUtil.generateIncrementingName(\"abc\", 3));\n\t\tassertEquals(\"ab\", StringUtil.generateIncrementingName(\"abc\", 4));\n\t\tassertEquals(\"ac\", StringUtil.generateIncrementingName(\"abc\", 5));\n\t\tassertEquals(\"ba\", StringUtil.generateIncrementingName(\"abc\", 6));\n\t}\n\n\t@Test\n\tvoid testGenerateName() {\n\t\tString generated = StringUtil.generateName(\"abcdefg\", 10, 0);\n\t\tassertNotNull(generated);\n\t\tassertEquals(10, generated.length());\n\t}\n\n\t@Nested\n\tclass StringDecoding {\n\t\t@Test\n\t\tvoid testDecodeString() {\n\t\t\t// Text only\n\t\t\tvar result = StringUtil.decodeString(\"hello\".getBytes(StandardCharsets.UTF_8));\n\t\t\tassertTrue(result.couldDecode(), \"Failed to decode 'hello' in UTF8\");\n\t\t\tassertEquals(\"hello\", result.text(), \"Decoded result did not yield original content\");\n\t\t\tassertEquals(StandardCharsets.UTF_8, result.charset(), \"Unexpected charset for decoding 'hello' in UTF8\");\n\n\t\t\tresult = StringUtil.decodeString(\"hello\".getBytes(StandardCharsets.ISO_8859_1));\n\t\t\tassertTrue(result.couldDecode(), \"Failed to decode 'hello' in ISO_8859_1\");\n\t\t\tassertEquals(\"hello\", result.text(), \"Decoded result did not yield original content\");\n\t\t\tassertEquals(StandardCharsets.UTF_8, result.charset(), \"Unexpected charset for decoding 'hello' in ISO_8859_1\");\n\n\t\t\tresult = StringUtil.decodeString(\"12345\".getBytes(StandardCharsets.UTF_8));\n\t\t\tassertTrue(result.couldDecode(), \"Failed to decode '12345' in UTF8\");\n\t\t\tassertEquals(\"12345\", result.text(), \"Decoded result did not yield original content\");\n\n\t\t\t// Ensure that long text doesn't get decoded wrong.\n\t\t\tString longString = \"\\\"the quick brown fox jumps over the lazy dog\\\"\".repeat(2000);\n\t\t\tresult = StringUtil.decodeString(longString.getBytes(StandardCharsets.ISO_8859_1));\n\t\t\tassertTrue(result.couldDecode(), \"Failed to decode 'the-quick-brown-fox' in ISO_8859_1\");\n\t\t\tassertEquals(longString, result.text(), \"Decoded result did not yield original content\");\n\n\t\t\t// Empty\n\t\t\tresult = StringUtil.decodeString(new byte[0]);\n\t\t\tassertFalse(result.couldDecode(), \"Should fail to decode empty data\");\n\n\t\t\t// Control category only\n\t\t\tresult = StringUtil.decodeString(\"\\0\\1\\2\\3\".getBytes(StandardCharsets.UTF_8));\n\t\t\tassertFalse(result.couldDecode(), \"Should fail to decode control category chars\");\n\n\t\t\t// Format category only\n\t\t\tresult = StringUtil.decodeString(\"\\u00AD\\u0600\\u202E\\uFFFA\".getBytes(StandardCharsets.UTF_8));\n\t\t\tassertFalse(result.couldDecode(), \"Should fail to decode format category chars\");\n\n\t\t\t// Private use category only\n\t\t\tresult = StringUtil.decodeString(\"\\uE000\\uE001\\uE002\\uE003\".getBytes(StandardCharsets.UTF_8));\n\t\t\tassertFalse(result.couldDecode(), \"Should fail to decode private-use category chars\");\n\n\t\t\t// Surrogate category only\n\t\t\tresult = StringUtil.decodeString(\"\\uD83C\\uDF09\\uD83C\\uDF09\".getBytes(StandardCharsets.UTF_8));\n\t\t\tassertFalse(result.couldDecode(), \"Should fail to decode surrogate category chars\");\n\n\t\t\t// Unassigned category only\n\t\t\tresult = StringUtil.decodeString(\"\\u0378\\u0378\\u0378\\u0378\".getBytes(StandardCharsets.UTF_8));\n\t\t\tassertEquals(StandardCharsets.ISO_8859_1, result.charset(), \"Should fail to decode unassigned category chars as UTF8 - fall back to ISO_8859_1\");\n\n\t\t\t// Chars outside the acceptable range won't get decoded, which tells us our input has non-text chars.\n\t\t\tresult = StringUtil.decodeString(new byte[]{0, 1, 15, 20, 26, 31});\n\t\t\tassertFalse(result.couldDecode(), \"Should fail to decode with control chars (char < 32)\");\n\t\t}\n\n\t\t@ParameterizedTest\n\t\t@ValueSource(strings = {\n\t\t\t \t\"lorem-long-ascii.txt\",\n\t\t\t \t\"lorem-long-cn.txt\",\n\t\t\t \t\"lorem-long-ru.txt\",\n\t\t\t \t\"lorem-short-ascii.txt\",\n\t\t\t \t\"lorem-short-cn.txt\",\n\t\t\t\t\"lorem-short-ru.txt\"\n\t\t})\n\t\tvoid testDecodeLoremIpsum(@Nonnull String name) throws Exception {\n\t\t\tdecodeFromPath(name);\n\t\t}\n\n\t\tvoid decodeFromPath(@Nonnull String path) throws Exception {\n\t\t\tbyte[] bytes = Files.readAllBytes(Paths.get(\"src/testFixtures/resources/\" + path));\n\t\t\tvar result = StringUtil.decodeString(bytes);\n\t\t\tassertTrue(result.couldDecode());\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/util/TypesTest.java",
    "content": "package software.coley.recaf.util;\n\nimport org.junit.jupiter.api.Test;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.Type;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for {@link Types}\n */\n@SuppressWarnings(\"ConstantValue\")\npublic class TypesTest {\n\t@Test\n\tvoid testIsPrimitive() {\n\t\tfor (Type primitive : Types.PRIMITIVES) {\n\t\t\tassertTrue(Types.isPrimitive(primitive), \"Failed on: \" + primitive);\n\t\t}\n\t\tfor (String primitiveBox : Types.PRIMITIVE_BOXES) {\n\t\t\tassertFalse(Types.isPrimitive(primitiveBox), \"Failed on: \" + primitiveBox);\n\t\t}\n\t\tassertFalse(Types.isPrimitive(Types.STRING_TYPE));\n\t\tassertFalse(Types.isPrimitive((Type) null));\n\t\tassertFalse(Types.isPrimitive((String) null));\n\t}\n\n\t@Test\n\tvoid testIsPrimitiveBox() {\n\t\tfor (Type primitive : Types.PRIMITIVES) {\n\t\t\tassertFalse(Types.isBoxedPrimitive(primitive.getDescriptor()), \"Failed on: \" + primitive);\n\t\t}\n\t\tfor (String primitiveBox : Types.PRIMITIVE_BOXES) {\n\t\t\tassertTrue(Types.isBoxedPrimitive(primitiveBox), \"Failed on: \" + primitiveBox);\n\t\t}\n\t\tassertFalse(Types.isBoxedPrimitive(null));\n\t}\n\n\t@Test\n\tvoid testClassToPrimitive() {\n\t\tassertEquals(\"V\", Types.classToPrimitive(\"void\"));\n\t\tassertEquals(\"Z\", Types.classToPrimitive(\"boolean\"));\n\t\tassertEquals(\"B\", Types.classToPrimitive(\"byte\"));\n\t\tassertEquals(\"C\", Types.classToPrimitive(\"char\"));\n\t\tassertEquals(\"S\", Types.classToPrimitive(\"short\"));\n\t\tassertEquals(\"I\", Types.classToPrimitive(\"int\"));\n\t\tassertEquals(\"F\", Types.classToPrimitive(\"float\"));\n\t\tassertEquals(\"D\", Types.classToPrimitive(\"double\"));\n\t\tassertEquals(\"J\", Types.classToPrimitive(\"long\"));\n\t\tassertThrows(IllegalArgumentException.class, () -> Types.classToPrimitive(\"foo\"));\n\t}\n\n\t@Test\n\tvoid testIsPrimitiveClassName() {\n\t\tassertTrue(Types.isPrimitiveClassName(\"void\"));\n\t\tassertTrue(Types.isPrimitiveClassName(\"boolean\"));\n\t\tassertTrue(Types.isPrimitiveClassName(\"byte\"));\n\t\tassertTrue(Types.isPrimitiveClassName(\"char\"));\n\t\tassertTrue(Types.isPrimitiveClassName(\"short\"));\n\t\tassertTrue(Types.isPrimitiveClassName(\"int\"));\n\t\tassertTrue(Types.isPrimitiveClassName(\"float\"));\n\t\tassertTrue(Types.isPrimitiveClassName(\"double\"));\n\t\tassertTrue(Types.isPrimitiveClassName(\"long\"));\n\t\t//\n\t\tassertFalse(Types.isPrimitiveClassName(\"Z\"));\n\t\tassertFalse(Types.isPrimitiveClassName(\"[I\"));\n\t\tassertFalse(Types.isPrimitiveClassName(\"VOID\"));\n\t\tassertFalse(Types.isPrimitiveClassName(\"\"));\n\t\tassertFalse(Types.isPrimitiveClassName(null));\n\t}\n\n\t@Test\n\tvoid testIsVoid() {\n\t\tassertTrue(Types.isVoid(Type.getType(\"V\")));\n\t\tassertFalse(Types.isVoid(Type.getType(\"()V\")));\n\t\tassertFalse(Types.isVoid(Type.getType(\"[V\")));\n\t\tassertFalse(Types.isVoid(null));\n\t}\n\n\t@Test\n\tvoid testMakeArray() {\n\t\tassertEquals(Type.getType(\"[I\"), Types.array(Type.getType(\"I\"), 1));\n\t\tassertEquals(Type.getType(\"[[I\"), Types.array(Type.getType(\"I\"), 2));\n\t\tassertEquals(Type.getType(\"[[[I\"), Types.array(Type.getType(\"I\"), 3));\n\t}\n\n\t@Test\n\tvoid testCountParameterSlots() {\n\t\tassertEquals(0, Types.countParameterSlots(Type.getMethodType(\"()V\")));\n\t\tassertEquals(1, Types.countParameterSlots(Type.getMethodType(\"(I)V\")));\n\t\tassertEquals(2, Types.countParameterSlots(Type.getMethodType(\"(II)V\")));\n\t\tassertEquals(3, Types.countParameterSlots(Type.getMethodType(\"(III)V\")));\n\t\tassertEquals(4, Types.countParameterSlots(Type.getMethodType(\"(JII)V\")));\n\t\tassertEquals(4, Types.countParameterSlots(Type.getMethodType(\"(IJI)V\")));\n\t\tassertEquals(6, Types.countParameterSlots(Type.getMethodType(\"(JJJ)V\")));\n\t\tassertEquals(3, Types.countParameterSlots(Type.getMethodType(\"([I[[J[[I)V\")));\n\t\tassertEquals(1, Types.countParameterSlots(Type.getMethodType(\"(Ljava/lang/String;)V\")));\n\t}\n\n\t@Test\n\tvoid testIsValidDesc() {\n\t\tassertTrue(Types.isValidDesc(\"([I[[J[[I)V\"), \"method desc\");\n\t\tassertTrue(Types.isValidDesc(\"[I\"), \"array desc\");\n\t\tassertTrue(Types.isValidDesc(\"Ljava/lang/String;\"), \"object desc\");\n\t\t//\n\t\tassertTrue(Types.isValidDesc(\"LLLLj/av/a/la/ng/S/t/ri/n/g;\"), \"ugly but valid\");\n\t\tassertTrue(Types.isValidDesc(\"L\\0;\"), \"null-terminator is valid\");\n\t\tassertTrue(Types.isValidDesc(\"L\\n;\"), \"newline is valid\");\n\t\tassertTrue(Types.isValidDesc(\"L;\"), \"empty is valid\");\n\t\t//\n\t\tassertFalse(Types.isValidDesc(null), \"null is invalid\");\n\t\tassertFalse(Types.isValidDesc(\"\"), \"empty string is invalid\");\n\t\tassertFalse(Types.isValidDesc(\"[\"), \"array without type is invalid\");\n\t\tassertFalse(Types.isValidDesc(\"[P\"), \"array without valid type is invalid\");\n\t\tassertFalse(Types.isValidDesc(\"java/lang/String\"), \"internal name is invalid\");\n\t\tassertFalse(Types.isValidDesc(\"(L;;)V\"), \"double ;; is unresolvable in method desc args\");\n\t}\n\n\t@Test\n\tvoid testIsValidSignature() {\n\t\tassertTrue(Types.isValidMethodSignature(\"()V\"));\n\t\tassertTrue(Types.isValidFieldSignature(\"Ljava/lang/Supplier;\"));\n\t\tassertTrue(Types.isValidClassSignature(\"Ljava/lang/Supplier;\"));\n\t\tassertTrue(Types.isValidClassSignature(\"Ljava/lang/Supplier<Ljava/lang/String;>;\"));\n\t\tassertTrue(Types.isValidClassSignature(\"Ljava/util/List<[Ljava/util/List<Ljava/lang/String;>;>;\"));\n\t\tassertTrue(Types.isValidFieldSignature(\"Ljava/util/List<[Ljava/util/List<Ljava/lang/String;>;>;\"));\n\t\t//\n\t\tassertFalse(Types.isValidClassSignature(\"\"), \"Empty signature must be invalid\");\n\t\tassertFalse(Types.isValidFieldSignature(\"\"), \"Empty signature must be invalid\");\n\t\tassertFalse(Types.isValidMethodSignature(\"\"), \"Empty signature must be invalid\");\n\t\tassertFalse(Types.isValidFieldSignature(\"V\"), \"Fields cannot be 'void'\");\n\t\tassertFalse(Types.isValidClassSignature(\"Ljava/lang/Supplier<I>;\"), \"Primitive int cannot be used as type argument\");\n\t}\n\n\t@Test\n\tvoid testIsWide() {\n\t\tassertTrue(Types.isWide(Type.getType(\"D\")));\n\t\tassertTrue(Types.isWide(Type.getType(\"J\")));\n\t\t//\n\t\tassertFalse(Types.isWide(Type.getType(\"V\")));\n\t\tassertFalse(Types.isWide(Type.getType(\"Z\")));\n\t\tassertFalse(Types.isWide(Type.getType(\"B\")));\n\t\tassertFalse(Types.isWide(Type.getType(\"C\")));\n\t\tassertFalse(Types.isWide(Type.getType(\"S\")));\n\t\tassertFalse(Types.isWide(Type.getType(\"I\")));\n\t\tassertFalse(Types.isWide(Type.getType(\"F\")));\n\t\tassertFalse(Types.isWide(Type.getType(\"[D\")));\n\t\tassertFalse(Types.isWide(Type.getType(\"[J\")));\n\t\tassertFalse(Types.isWide(Type.getType(\"Ljava/lang/String;\")));\n\t}\n\n\t@Test\n\tvoid testFromVarOpcode() {\n\t\tint[] ints = {Opcodes.IINC, Opcodes.ILOAD, Opcodes.ISTORE};\n\t\tint[] floats = {Opcodes.FLOAD, Opcodes.FSTORE};\n\t\tint[] doubles = {Opcodes.DLOAD, Opcodes.DSTORE};\n\t\tint[] longs = {Opcodes.LLOAD, Opcodes.LSTORE};\n\t\tint[] objects = {Opcodes.ALOAD, Opcodes.ASTORE};\n\t\tfor (int v : ints) assertSame(Type.INT_TYPE, Types.fromVarOpcode(v));\n\t\tfor (int v : floats) assertSame(Type.FLOAT_TYPE, Types.fromVarOpcode(v));\n\t\tfor (int v : doubles) assertSame(Type.DOUBLE_TYPE, Types.fromVarOpcode(v));\n\t\tfor (int v : longs) assertSame(Type.LONG_TYPE, Types.fromVarOpcode(v));\n\t\tfor (int v : objects) assertSame(Types.OBJECT_TYPE, Types.fromVarOpcode(v));\n\t\tassertNull(Types.fromVarOpcode(-1));\n\t}\n\n\t@Test\n\tvoid testFromArrayOpcode() {\n\t\tint[] ints = {Opcodes.ARRAYLENGTH, Opcodes.BALOAD, Opcodes.CALOAD, Opcodes.SALOAD, Opcodes.IALOAD,\n\t\t\t\tOpcodes.BASTORE, Opcodes.CASTORE, Opcodes.SASTORE, Opcodes.IASTORE};\n\t\tint[] floats = {Opcodes.FALOAD, Opcodes.FASTORE};\n\t\tint[] doubles = {Opcodes.DALOAD, Opcodes.DASTORE};\n\t\tint[] longs = {Opcodes.LALOAD, Opcodes.LASTORE};\n\t\tint[] objects = {Opcodes.AALOAD, Opcodes.AASTORE};\n\t\tfor (int v : ints) assertSame(Type.INT_TYPE, Types.fromArrayOpcode(v));\n\t\tfor (int v : floats) assertSame(Type.FLOAT_TYPE, Types.fromArrayOpcode(v));\n\t\tfor (int v : doubles) assertSame(Type.DOUBLE_TYPE, Types.fromArrayOpcode(v));\n\t\tfor (int v : longs) assertSame(Type.LONG_TYPE, Types.fromArrayOpcode(v));\n\t\tfor (int v : objects) assertSame(Types.OBJECT_TYPE, Types.fromArrayOpcode(v));\n\t\tassertNull(Types.fromArrayOpcode(-1));\n\t}\n\n\t@Test\n\tvoid testGetNormalizedSort() {\n\t\tassertEquals(Type.METHOD, Types.getNormalizedSort(Type.METHOD));\n\t\tassertEquals(Type.OBJECT, Types.getNormalizedSort(Type.OBJECT));\n\t\tassertEquals(Type.OBJECT, Types.getNormalizedSort(Type.ARRAY));\n\t\tassertEquals(Type.DOUBLE, Types.getNormalizedSort(Type.DOUBLE));\n\t\tassertEquals(Type.LONG, Types.getNormalizedSort(Type.LONG));\n\t\tassertEquals(Type.FLOAT, Types.getNormalizedSort(Type.FLOAT));\n\t\tassertEquals(Type.INT, Types.getNormalizedSort(Type.INT));\n\t\tassertEquals(Type.INT, Types.getNormalizedSort(Type.SHORT));\n\t\tassertEquals(Type.INT, Types.getNormalizedSort(Type.BYTE));\n\t\tassertEquals(Type.INT, Types.getNormalizedSort(Type.CHAR));\n\t\tassertEquals(Type.INT, Types.getNormalizedSort(Type.BOOLEAN));\n\t\tassertEquals(Type.VOID, Types.getNormalizedSort(Type.VOID));\n\t}\n\n\t@Test\n\tvoid testGetSortName() {\n\t\tassertEquals(\"void\", Types.getSortName(Type.VOID));\n\t\tassertEquals(\"boolean\", Types.getSortName(Type.BOOLEAN));\n\t\tassertEquals(\"char\", Types.getSortName(Type.CHAR));\n\t\tassertEquals(\"byte\", Types.getSortName(Type.BYTE));\n\t\tassertEquals(\"short\", Types.getSortName(Type.SHORT));\n\t\tassertEquals(\"int\", Types.getSortName(Type.INT));\n\t\tassertEquals(\"float\", Types.getSortName(Type.FLOAT));\n\t\tassertEquals(\"long\", Types.getSortName(Type.LONG));\n\t\tassertEquals(\"double\", Types.getSortName(Type.DOUBLE));\n\t\tassertEquals(\"array\", Types.getSortName(Type.ARRAY));\n\t\tassertEquals(\"object\", Types.getSortName(Type.OBJECT));\n\t\tassertEquals(\"method\", Types.getSortName(Type.METHOD));\n\t}\n\n\t@Test\n\tvoid testPrettify() {\n\t\tassertEquals(\"int\", Types.pretty(Type.getType(\"I\")));\n\t\tassertEquals(\"int[]\", Types.pretty(Type.getType(\"[I\")));\n\t\tassertEquals(\"(int, float) void\", Types.pretty(Type.getMethodType(\"(IF)V\")));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/util/android/AndroidResConversion.java",
    "content": "package software.coley.recaf.util.android;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport com.fasterxml.jackson.dataformat.xml.XmlMapper;\n\n/**\n * Not actually a test but a utility for {@link AndroidRes}.\n *\n * @author Matt Coley\n */\npublic class AndroidResConversion {\n\tprivate static final XmlMapper MAPPER = new XmlMapper();\n\n\tpublic static void main(String[] args) throws Exception {\n\t\t// We just want to merge these two XML files into one model and then slap it back into JSON\n\t\tJsonNode tree1 = MAPPER.readTree(AndroidRes.class.getResourceAsStream(\"/android/attrs_manifest.xml\"));\n\t\tJsonNode tree2 = MAPPER.readTree(AndroidRes.class.getResourceAsStream(\"/android/attrs.xml\"));\n\t\tObjectNode merged = MAPPER.createObjectNode();\n\t\tmerged.putIfAbsent(\"attrs_manifest\", tree1);\n\t\tmerged.putIfAbsent(\"attrs\", tree2);\n\t\tString string = merged.toString();\n\t\tSystem.out.println(string);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/test/java/software/coley/recaf/workspace/model/WorkspaceModelTest.java",
    "content": "package software.coley.recaf.workspace.model;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.info.StubFileInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.test.dummy.AccessibleFields;\nimport software.coley.recaf.test.dummy.AccessibleMethods;\nimport software.coley.recaf.test.dummy.AccessibleMethodsChild;\nimport software.coley.recaf.test.dummy.ClassWithAnnotation;\nimport software.coley.recaf.test.dummy.ClassWithConstructor;\nimport software.coley.recaf.test.dummy.ClassWithExceptions;\nimport software.coley.recaf.workspace.model.bundle.BasicAndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.AndroidApiResource;\nimport software.coley.recaf.workspace.model.resource.RuntimeWorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResourceBuilder;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.SortedSet;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\nimport static software.coley.recaf.test.TestClassUtils.fromClasses;\n\n/**\n * Tests for the {@link Workspace} model.\n */\nclass WorkspaceModelTest {\n\t@Nested\n\tclass ResourceModel {\n\t\t@Test\n\t\tvoid getPrimaryResource() {\n\t\t\tWorkspaceResource primary = new WorkspaceResourceBuilder().build();\n\t\t\tWorkspace workspace = new BasicWorkspace(primary);\n\t\t\tassertSame(primary, workspace.getPrimaryResource());\n\t\t}\n\n\t\t@Test\n\t\tvoid getSupportingResources() {\n\t\t\tWorkspaceResource primary = new WorkspaceResourceBuilder().build();\n\t\t\tWorkspaceResource support1 = new WorkspaceResourceBuilder().build();\n\t\t\tWorkspaceResource support2 = new WorkspaceResourceBuilder().build();\n\t\t\tWorkspaceResource support3 = new WorkspaceResourceBuilder().build();\n\t\t\tWorkspace workspace = new BasicWorkspace(primary, List.of(support1, support2, support3));\n\t\t\tList<WorkspaceResource> supportingResources = workspace.getSupportingResources();\n\t\t\tassertEquals(3, supportingResources.size());\n\t\t\tassertTrue(supportingResources.containsAll(List.of(support1, support2, support3)));\n\t\t}\n\n\t\t@Test\n\t\tvoid getInternalSupportingResources_JVM() {\n\t\t\tWorkspaceResource primary = new WorkspaceResourceBuilder().build();\n\t\t\tWorkspace workspace = new BasicWorkspace(primary);\n\n\t\t\t// Will only contain the runtime resource\n\t\t\tList<WorkspaceResource> internal = workspace.getInternalSupportingResources();\n\t\t\tassertEquals(1, internal.size());\n\t\t\tassertTrue(internal.contains(RuntimeWorkspaceResource.getInstance()));\n\t\t}\n\n\t\t@Test\n\t\tvoid getInternalSupportingResources_Android() {\n\t\t\tWorkspaceResource primary = new WorkspaceResourceBuilder()\n\t\t\t\t\t.withAndroidClassBundles(Map.of(\"classes.dex\", new BasicAndroidClassBundle()))\n\t\t\t\t\t.build();\n\t\t\tWorkspace workspace = new BasicWorkspace(primary);\n\t\t\tList<WorkspaceResource> internal = workspace.getInternalSupportingResources();\n\n\t\t\t// Will contain the runtime resource AND the Android API resource since android content was detected\n\t\t\tassertEquals(2, internal.size());\n\t\t\tassertTrue(internal.contains(RuntimeWorkspaceResource.getInstance()));\n\t\t\tassertTrue(internal.contains(AndroidApiResource.getInstance()));\n\t\t}\n\n\t\t@Test\n\t\tvoid addAndRemoveSupportingResource() {\n\t\t\tWorkspaceResource primary = new WorkspaceResourceBuilder().build();\n\t\t\tWorkspace workspace = new BasicWorkspace(primary);\n\n\t\t\t// No supporting resources by default\n\t\t\tassertTrue(workspace.getSupportingResources().isEmpty());\n\n\t\t\t// Add a listener\n\t\t\tWorkspaceModificationListener listener = mock(WorkspaceModificationListener.class);\n\t\t\tworkspace.addWorkspaceModificationListener(listener);\n\n\t\t\t// Add a supporting resource\n\t\t\tWorkspaceResource supporting = new WorkspaceResourceBuilder().build();\n\t\t\tworkspace.addSupportingResource(supporting);\n\n\t\t\t// The listener should be called, and we should see it in the list\n\t\t\tverify(listener).onAddLibrary(same(workspace), same(supporting));\n\t\t\tassertEquals(1, workspace.getSupportingResources().size());\n\n\t\t\t// Same idea in reverse\n\t\t\tassertTrue(workspace.removeSupportingResource(supporting));\n\t\t\tverify(listener).onRemoveLibrary(same(workspace), same(supporting));\n\t\t\tassertTrue(workspace.getSupportingResources().isEmpty());\n\t\t}\n\n\t\t@Test\n\t\tvoid getAllResources() {\n\t\t\tWorkspaceResource primary = new WorkspaceResourceBuilder().build();\n\t\t\tWorkspaceResource supporting1 = new WorkspaceResourceBuilder().build();\n\t\t\tWorkspaceResource supporting2 = new WorkspaceResourceBuilder().build();\n\t\t\tWorkspace workspace = new BasicWorkspace(primary, List.of(supporting1, supporting2));\n\n\t\t\t// Should give us the primary/supporting resources in order.\n\t\t\tList<WorkspaceResource> streamResult = workspace.allResourcesStream(false).toList();\n\t\t\tassertEquals(List.of(primary, supporting1, supporting2), streamResult);\n\n\t\t\t// Should give us the expected primary/supporting resources, then the internal resources as well in order.\n\t\t\tstreamResult = workspace.allResourcesStream(true).toList();\n\t\t\tassertEquals(List.of(primary, supporting1, supporting2, RuntimeWorkspaceResource.getInstance()), streamResult);\n\t\t}\n\t}\n\n\t@Nested\n\tclass Finding {\n\t\t@Test\n\t\tvoid findClass() throws IOException {\n\t\t\tWorkspaceResource primary = new WorkspaceResourceBuilder()\n\t\t\t\t\t.withJvmClassBundle(fromClasses(\n\t\t\t\t\t\t\tAccessibleFields.class,\n\t\t\t\t\t\t\tAccessibleMethods.class,\n\t\t\t\t\t\t\tAccessibleMethodsChild.class\n\t\t\t\t\t)).build();\n\t\t\tWorkspaceResource supporting = new WorkspaceResourceBuilder()\n\t\t\t\t\t.withJvmClassBundle(fromClasses(\n\t\t\t\t\t\t\tClassWithAnnotation.class,\n\t\t\t\t\t\t\tClassWithConstructor.class,\n\t\t\t\t\t\t\tClassWithExceptions.class\n\t\t\t\t\t)).build();\n\t\t\tWorkspace workspace = new BasicWorkspace(primary, List.of(supporting));\n\n\t\t\t// Find a class in the primary resource\n\t\t\tClassPathNode result = findClass(workspace, AccessibleFields.class, false);\n\t\t\tassertNotNull(result);\n\t\t\tassertSame(primary, result.getValueOfType(WorkspaceResource.class));\n\t\t\tassertSame(workspace, result.getValueOfType(Workspace.class));\n\n\t\t\t// Find a class in the secondary resource\n\t\t\tresult = findClass(workspace, ClassWithExceptions.class, false);\n\t\t\tassertNotNull(result);\n\t\t\tassertSame(supporting, result.getValueOfType(WorkspaceResource.class));\n\t\t\tassertSame(workspace, result.getValueOfType(Workspace.class));\n\t\t}\n\n\t\t@Test\n\t\tvoid findClass_fromRuntime() {\n\t\t\tWorkspaceResource primary = new WorkspaceResourceBuilder().build();\n\t\t\tWorkspace workspace = new BasicWorkspace(primary);\n\n\t\t\t// Find a class that exists in the runtime, but not explicitly in the provided resources of the workspace\n\t\t\tClassPathNode result = findClass(workspace, String.class, true);\n\t\t\tassertNotNull(result);\n\t\t\tassertSame(RuntimeWorkspaceResource.getInstance(), result.getValueOfType(WorkspaceResource.class));\n\t\t\tassertSame(workspace, result.getValueOfType(Workspace.class));\n\t\t}\n\n\t\t@Test\n\t\tvoid findClass_embeddedInPrimary() throws IOException {\n\t\t\tWorkspaceFileResource embeddedResource = new WorkspaceFileResourceBuilder()\n\t\t\t\t\t.withFileInfo(new StubFileInfo(\"embedded.jar\"))\n\t\t\t\t\t.withJvmClassBundle(fromClasses(\n\t\t\t\t\t\t\tAccessibleFields.class\n\t\t\t\t\t)).build();\n\t\t\tWorkspaceResource primary = new WorkspaceResourceBuilder()\n\t\t\t\t\t.withEmbeddedResources(Map.of(\"embedded.jar\", embeddedResource))\n\t\t\t\t\t.build();\n\t\t\tWorkspace workspace = new BasicWorkspace(primary);\n\n\t\t\t// Find a class in the primary resource's embedded resource\n\t\t\tClassPathNode result = findClass(workspace, AccessibleFields.class, false);\n\t\t\tassertNotNull(result);\n\n\n\t\t\t// The path should have:\n\t\t\t// - The resource be the embedded one\n\t\t\t// - The bundle be the embedded one's jvm bundle\n\t\t\tvar bundle = result.getValueOfType(Bundle.class);\n\t\t\tassertNotNull(bundle);\n\t\t\tassertSame(embeddedResource, result.getValueOfType(WorkspaceResource.class));\n\t\t\tassertSame(embeddedResource.getJvmClassBundle(), bundle);\n\n\t\t\t// Should be able to resolve the embedded resource by also doing a bundle resolution on the primary resource.\n\t\t\tassertSame(embeddedResource, primary.resolveBundleContainer(bundle));\n\t\t\tassertSame(primary, embeddedResource.getContainingResource());\n\t\t}\n\n\t\t@Test\n\t\tvoid findClass_embeddedDeeperInPrimary() throws IOException {\n\t\t\t// Build the following model:\n\t\t\t//  > 1.jar\n\t\t\t//   > ...\n\t\t\t//    > 5.jar\n\t\t\t//      - AccessibleFields.class\n\t\t\tWorkspaceFileResource deepestResource = new WorkspaceFileResourceBuilder()\n\t\t\t\t\t.withFileInfo(new StubFileInfo(\"5.jar\"))\n\t\t\t\t\t.withJvmClassBundle(fromClasses(AccessibleFields.class))\n\t\t\t\t\t.build();\n\t\t\tWorkspaceFileResourceBuilder embeddedBuilder =\n\t\t\t\t\tnew WorkspaceFileResourceBuilder().withFileInfo(new StubFileInfo(\"1.jar\")).withEmbeddedResources(Map.of(\"2.jar\",\n\t\t\t\t\t\t\tnew WorkspaceFileResourceBuilder().withFileInfo(new StubFileInfo(\"2.jar\")).withEmbeddedResources(Map.of(\"3.jar\",\n\t\t\t\t\t\t\t\t\tnew WorkspaceFileResourceBuilder().withFileInfo(new StubFileInfo(\"3.jar\")).withEmbeddedResources(Map.of(\"4.jar\",\n\t\t\t\t\t\t\t\t\t\t\tnew WorkspaceFileResourceBuilder().withFileInfo(new StubFileInfo(\"4.jar\")).withEmbeddedResources(Map.of(\"5.jar\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tdeepestResource)\n\t\t\t\t\t\t\t\t\t\t\t).build())\n\t\t\t\t\t\t\t\t\t).build())\n\t\t\t\t\t\t\t).build())\n\t\t\t\t\t);\n\n\n\t\t\t// Build the workspace\n\t\t\t//  - using the basic workspace with 'false' parameter so that we include only our resources\n\t\t\t//    and no internal resources like the runtime classpath.\n\t\t\tWorkspaceResource primary = new WorkspaceResourceBuilder()\n\t\t\t\t\t.withEmbeddedResources(Map.of(\"1.jar\", embeddedBuilder.build()))\n\t\t\t\t\t.build();\n\t\t\tWorkspace workspace = new BasicWorkspace(primary, Collections.emptyList(), false);\n\n\t\t\t// Find a class in the primary resource's embedded resource.\n\t\t\tClassPathNode result = findClass(workspace, AccessibleFields.class, false);\n\t\t\tassertNotNull(result);\n\n\t\t\t// Bundle/resource should belong to the deepest embedded resource.\n\t\t\tvar bundle = result.getValueOfType(Bundle.class);\n\t\t\tassertNotNull(bundle);\n\t\t\tWorkspaceResource valueOfType = result.getValueOfType(WorkspaceResource.class);\n\t\t\tassertSame(deepestResource, valueOfType);\n\n\t\t\t// We should be able to also find the deepest embedded resource from the primary resource\n\t\t\t// if we have a reference to the deepest bundle.\n\t\t\tvar resolvedBundleContainer = (WorkspaceFileResource) (primary.resolveBundleContainer(bundle));\n\t\t\tassertNotNull(resolvedBundleContainer);\n\t\t\tassertEquals(\"5.jar\", resolvedBundleContainer.getFileInfo().getName());\n\n\t\t\t// The predicate-based find operation should yield the same path.\n\t\t\tSortedSet<ClassPathNode> allClassPaths = workspace.findClasses(c -> true);\n\t\t\tassertEquals(1, allClassPaths.size());\n\t\t\tassertEquals(result, allClassPaths.first());\n\t\t}\n\n\t\t/**\n\t\t * @param workspace\n\t\t * \t\tWorkspace to search in.\n\t\t * @param type\n\t\t * \t\tClass to look for.\n\t\t * @param includeInternal\n\t\t * \t\tFlag to include internal workspace resources in the search.\n\t\t *\n\t\t * @return Path to resource in the workspace.\n\t\t */\n\t\t@Nullable\n\t\tprivate static ClassPathNode findClass(@Nonnull Workspace workspace, @Nonnull Class<?> type, boolean includeInternal) {\n\t\t\treturn workspace.findClass(includeInternal, type.getName().replace('.', '/'));\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-core/src/test/resources/android/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- From Platform 31 -->\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n\n<!-- Formatting note: terminate all comments with a period, to avoid breaking\n     the documentation output. To suppress comment lines from the documentation\n     output, insert an eat-comment element after the comment lines.\n-->\n\n<resources>\n    <!-- These are the standard attributes that make up a complete theme. -->\n    <declare-styleable name=\"Theme\">\n        <!-- ============== -->\n        <!-- Generic styles -->\n        <!-- ============== -->\n        <eat-comment />\n\n        <!-- Specifies that a theme has a light background with dark text on top.  -->\n        <attr name=\"isLightTheme\" format=\"boolean\" />\n\n        <!-- Default color of foreground imagery. -->\n        <attr name=\"colorForeground\" format=\"color\" />\n        <!-- Default color of foreground imagery on an inverted background. -->\n        <attr name=\"colorForegroundInverse\" format=\"color\" />\n        <!-- Default color of background imagery, ex. full-screen windows. -->\n        <attr name=\"colorBackground\" format=\"color\" />\n        <!-- Default color of background imagery for floating components, ex. dialogs, popups, and cards. -->\n        <attr name=\"colorBackgroundFloating\" format=\"color\" />\n        <!-- This is a hint for a solid color that can be used for caching\n             rendered views.  This should be the color of the background when\n             there is a solid background color; it should be null when the\n             background is a texture or translucent.  When a device is able\n             to use accelerated drawing (thus setting state_accelerated), the\n             cache hint is ignored and always assumed to be transparent. -->\n        <attr name=\"colorBackgroundCacheHint\" format=\"color\" />\n\n        <!-- Default highlight color for items that are pressed. -->\n        <attr name=\"colorPressedHighlight\" format=\"color\" />\n        <!-- Default highlight color for items that are long-pressed. -->\n        <attr name=\"colorLongPressedHighlight\" format=\"color\" />\n        <!-- Default highlight color for items that are\n             focused. (Focused meaning cursor-based selection.) -->\n        <attr name=\"colorFocusedHighlight\" format=\"color\" />\n        <!-- Default highlight color for items that are\n             activated. (Activated meaning persistent selection.) -->\n        <attr name=\"colorActivatedHighlight\" format=\"color\" />\n        <!-- Default highlight color for items in multiple selection\n             mode. -->\n        <attr name=\"colorMultiSelectHighlight\" format=\"color\" />\n\n        <!-- Drawable to be drawn over the view to mark it as autofilled-->\n        <attr name=\"autofilledHighlight\" format=\"reference\" />\n\n        <!-- Max width of the autofill data set picker as a fraction of the screen width -->\n        <attr name=\"autofillDatasetPickerMaxWidth\" format=\"reference\" />\n\n        <!-- Max height of the autofill data set picker as a fraction of the screen height -->\n        <attr name=\"autofillDatasetPickerMaxHeight\" format=\"reference\" />\n\n        <!-- Max height of the the autofill save custom subtitle as a fraction of the screen width/height -->\n        <attr name=\"autofillSaveCustomSubtitleMaxHeight\" format=\"reference\" />\n\n        <!-- Default disabled alpha for widgets that set enabled/disabled alpha programmatically. -->\n        <attr name=\"disabledAlpha\" format=\"float\" />\n        <!-- The alpha applied to the foreground color to create the primary text color. -->\n        <attr name=\"primaryContentAlpha\" format=\"float\" />\n        <!-- The alpha applied to the foreground color to create the secondary text color. -->\n        <attr name=\"secondaryContentAlpha\" format=\"float\" />\n        <!-- Color used for error states and things that need to be drawn to\n             the users attention.. -->\n        <attr name=\"colorError\" format=\"reference|color\" />\n        <!-- Default background dim amount when a menu, dialog, or something similar pops up. -->\n        <attr name=\"backgroundDimAmount\" format=\"float\" />\n        <!-- Control whether dimming behind the window is enabled.  The default\n             theme does not set this value, meaning it is based on whether the\n             window is floating. -->\n        <attr name=\"backgroundDimEnabled\" format=\"boolean\" />\n        <!-- When windowBlurBehindEnabled is set, this is the amount of blur to apply\n             behind the window. The range is from 0, which means no blur, to 150.  -->\n        <attr name=\"windowBlurBehindRadius\" format=\"dimension\"/>\n        <!-- If set, everything behind the window will be blurred with radius\n             windowBackgroundBlurRadius. -->\n        <attr name=\"windowBlurBehindEnabled\" format=\"boolean\" />\n\n\n        <!-- Color of background imagery used for popup windows. -->\n        <attr name=\"colorPopupBackground\" format=\"color\" />\n        <!-- Color used for list divider. -->\n        <attr name=\"colorListDivider\" format=\"color\" />\n        <!-- Opacity used for list divider. -->\n        <attr name=\"opacityListDivider\" format=\"color\" />\n\n        <!-- =========== -->\n        <!-- Text styles -->\n        <!-- =========== -->\n        <eat-comment />\n\n        <!-- Default appearance of text: color, typeface, size, and style. -->\n        <attr name=\"textAppearance\" format=\"reference\" />\n        <!-- Default appearance of text against an inverted background:\n             color, typeface, size, and style. -->\n        <attr name=\"textAppearanceInverse\" format=\"reference\" />\n\n        <!-- The most prominent text color.  -->\n        <attr name=\"textColorPrimary\" format=\"reference|color\" />\n        <!-- Secondary text color. -->\n        <attr name=\"textColorSecondary\" format=\"reference|color\" />\n        <!-- Tertiary text color. -->\n        <attr name=\"textColorTertiary\" format=\"reference|color\" />\n\n        <!-- Primary inverse text color, useful for inverted backgrounds. -->\n        <attr name=\"textColorPrimaryInverse\" format=\"reference|color\" />\n        <!-- Secondary inverse text color, useful for inverted backgrounds. -->\n        <attr name=\"textColorSecondaryInverse\" format=\"reference|color\" />\n        <!-- Tertiary inverse text color, useful for inverted backgrounds. -->\n        <attr name=\"textColorTertiaryInverse\" format=\"reference|color\" />\n\n        <!-- Inverse hint text color. -->\n        <attr name=\"textColorHintInverse\" format=\"reference|color\" />\n\n        <!-- Bright text color. Only differentiates based on the disabled state. -->\n        <attr name=\"textColorPrimaryDisableOnly\" format=\"reference|color\" />\n\n        <!-- Bright inverse text color. Only differentiates based on the disabled state. -->\n        <attr name=\"textColorPrimaryInverseDisableOnly\" format=\"reference|color\" />\n\n        <!-- Bright text color. This does not differentiate the disabled state. As an example,\n             buttons use this since they display the disabled state via the background and not the\n             foreground text color. -->\n        <attr name=\"textColorPrimaryNoDisable\" format=\"reference|color\" />\n        <!-- Dim text color. This does not differentiate the disabled state. -->\n        <attr name=\"textColorSecondaryNoDisable\" format=\"reference|color\" />\n\n        <!-- Bright inverse text color. This does not differentiate the disabled state. -->\n        <attr name=\"textColorPrimaryInverseNoDisable\" format=\"reference|color\" />\n        <!-- Dim inverse text color. This does not differentiate the disabled state. -->\n        <attr name=\"textColorSecondaryInverseNoDisable\" format=\"reference|color\" />\n\n        <!-- Bright text color for use over activated backgrounds. -->\n        <attr name=\"textColorPrimaryActivated\" format=\"reference|color\" />\n        <!-- Dim text color for use over activated backgrounds. -->\n        <attr name=\"textColorSecondaryActivated\" format=\"reference|color\" />\n\n        <!-- Text color for urls in search suggestions, used by things like global search and the browser. @hide -->\n        <attr name=\"textColorSearchUrl\" format=\"reference|color\" />\n\n        <!-- Color of highlighted text, when used in a light theme. -->\n        <attr name=\"textColorHighlightInverse\" format=\"reference|color\" />\n        <!-- Color of link text (URLs), when used in a light theme. -->\n        <attr name=\"textColorLinkInverse\" format=\"reference|color\" />\n\n        <!-- Color of list item text in alert dialogs. -->\n        <attr name=\"textColorAlertDialogListItem\" format=\"reference|color\" />\n\n        <!-- Search widget more corpus result item background. -->\n        <attr name=\"searchWidgetCorpusItemBackground\" format=\"reference|color\" />\n\n        <!-- Text color, typeface, size, and style for \"large\" text. Defaults to primary text color. -->\n        <attr name=\"textAppearanceLarge\" format=\"reference\" />\n        <!-- Text color, typeface, size, and style for \"medium\" text. Defaults to primary text color. -->\n        <attr name=\"textAppearanceMedium\" format=\"reference\" />\n        <!-- Text color, typeface, size, and style for \"small\" text. Defaults to secondary text color. -->\n        <attr name=\"textAppearanceSmall\" format=\"reference\" />\n\n        <!-- Text color, typeface, size, and style for \"large\" inverse text. Defaults to primary inverse text color. -->\n        <attr name=\"textAppearanceLargeInverse\" format=\"reference\" />\n        <!-- Text color, typeface, size, and style for \"medium\" inverse text. Defaults to primary inverse text color. -->\n        <attr name=\"textAppearanceMediumInverse\" format=\"reference\" />\n        <!-- Text color, typeface, size, and style for \"small\" inverse text. Defaults to secondary inverse text color. -->\n        <attr name=\"textAppearanceSmallInverse\" format=\"reference\" />\n\n        <!-- Text color, typeface, size, and style for system search result title. Defaults to primary inverse text color. -->\n        <attr name=\"textAppearanceSearchResultTitle\" format=\"reference\" />\n        <!-- Text color, typeface, size, and style for system search result subtitle. Defaults to primary inverse text color. -->\n        <attr name=\"textAppearanceSearchResultSubtitle\" format=\"reference\" />\n\n        <!-- Text color, typeface, size, and style for the text inside of a button. -->\n        <attr name=\"textAppearanceButton\" format=\"reference\" />\n\n        <!-- Text color, typeface, size, and style for the text inside of a popup menu. -->\n        <attr name=\"textAppearanceLargePopupMenu\" format=\"reference\" />\n\n        <!-- Text color, typeface, size, and style for small text inside of a popup menu. -->\n        <attr name=\"textAppearanceSmallPopupMenu\" format=\"reference\" />\n\n        <!-- Text color, typeface, size, and style for header text inside of a popup menu. -->\n        <attr name=\"textAppearancePopupMenuHeader\" format=\"reference\" />\n\n        <!-- The underline color and thickness for easy correct suggestion -->\n        <attr name=\"textAppearanceEasyCorrectSuggestion\" format=\"reference\" />\n\n        <!-- The underline color and thickness for misspelled suggestion -->\n        <attr name=\"textAppearanceMisspelledSuggestion\" format=\"reference\" />\n\n        <!-- The underline color and thickness for auto correction suggestion -->\n        <attr name=\"textAppearanceAutoCorrectionSuggestion\" format=\"reference\" />\n\n        <!-- The underline color and thickness for grammar error suggestion -->\n        <attr name=\"textAppearanceGrammarErrorSuggestion\" format=\"reference\" />\n\n        <!--  The underline color -->\n        <attr name=\"textUnderlineColor\" format=\"reference|color\" />\n        <!--  The underline thickness -->\n        <attr name=\"textUnderlineThickness\" format=\"reference|dimension\" />\n\n        <!-- EditText text foreground color. -->\n        <attr name=\"editTextColor\" format=\"reference|color\" />\n        <!-- EditText background drawable. -->\n        <attr name=\"editTextBackground\" format=\"reference\" />\n\n        <!-- Popup text displayed in TextView when setError is used. -->\n        <attr name=\"errorMessageBackground\" format=\"reference\" />\n        <!-- Background used instead of errorMessageBackground when the popup has to be above. -->\n        <attr name=\"errorMessageAboveBackground\" format=\"reference\" />\n\n        <!-- A styled string, specifying the style to be used for showing\n             inline candidate text when composing with an input method.  The\n             text itself will be ignored, but the style spans will be applied\n             to the candidate text as it is edited. -->\n        <attr name=\"candidatesTextStyleSpans\" format=\"reference|string\" />\n\n        <!-- Drawable to use for check marks. -->\n        <attr name=\"textCheckMark\" format=\"reference\" />\n        <attr name=\"textCheckMarkInverse\" format=\"reference\" />\n\n        <!-- Drawable to use for multiple choice indicators. -->\n        <attr name=\"listChoiceIndicatorMultiple\" format=\"reference\" />\n\n        <!-- Drawable to use for single choice indicators. -->\n        <attr name=\"listChoiceIndicatorSingle\" format=\"reference\" />\n\n        <!-- Drawable used as a background for selected list items. -->\n        <attr name=\"listChoiceBackgroundIndicator\" format=\"reference\" />\n\n        <!-- Drawable used as a background for activated items. -->\n        <attr name=\"activatedBackgroundIndicator\" format=\"reference\" />\n\n        <!-- ============= -->\n        <!-- Button styles -->\n        <!-- ============= -->\n        <eat-comment />\n\n        <!-- Normal Button style. -->\n        <attr name=\"buttonStyle\" format=\"reference\" />\n\n        <!-- Small Button style. -->\n        <attr name=\"buttonStyleSmall\" format=\"reference\" />\n\n        <!-- Button style to inset into an EditText. -->\n        <attr name=\"buttonStyleInset\" format=\"reference\" />\n\n        <!-- ToggleButton style. -->\n        <attr name=\"buttonStyleToggle\" format=\"reference\" />\n\n        <!-- ============== -->\n        <!-- Gallery styles -->\n        <!-- ============== -->\n        <eat-comment />\n\n        <!-- The preferred background for gallery items. This should be set\n             as the background of any Views you provide from the Adapter. -->\n        <attr name=\"galleryItemBackground\" format=\"reference\" />\n\n        <!-- =========== -->\n        <!-- List styles -->\n        <!-- =========== -->\n        <eat-comment />\n\n        <!-- The preferred list item height. -->\n        <attr name=\"listPreferredItemHeight\" format=\"dimension\" />\n        <!-- A smaller, sleeker list item height. -->\n        <attr name=\"listPreferredItemHeightSmall\" format=\"dimension\" />\n        <!-- A larger, more robust list item height. -->\n        <attr name=\"listPreferredItemHeightLarge\" format=\"dimension\" />\n        <!-- The list item height for search results. @hide -->\n        <attr name=\"searchResultListItemHeight\" format=\"dimension\" />\n\n        <!-- The preferred padding along the left edge of list items. -->\n        <attr name=\"listPreferredItemPaddingLeft\" format=\"dimension\" />\n        <!-- The preferred padding along the right edge of list items. -->\n        <attr name=\"listPreferredItemPaddingRight\" format=\"dimension\" />\n\n        <!-- The preferred TextAppearance for the primary text of list items. -->\n        <attr name=\"textAppearanceListItem\" format=\"reference\" />\n        <!-- The preferred TextAppearance for the secondary text of list items. -->\n        <attr name=\"textAppearanceListItemSecondary\" format=\"reference\" />\n        <!-- The preferred TextAppearance for the primary text of small list items. -->\n        <attr name=\"textAppearanceListItemSmall\" format=\"reference\" />\n\n        <!-- The drawable for the list divider. -->\n        <attr name=\"listDivider\" format=\"reference\" />\n        <!-- The list divider used in alert dialogs. -->\n        <attr name=\"listDividerAlertDialog\" format=\"reference\" />\n        <!-- TextView style for list separators. -->\n        <attr name=\"listSeparatorTextViewStyle\" format=\"reference\" />\n        <!-- The preferred left padding for an expandable list item (for child-specific layouts,\n             use expandableListPreferredChildPaddingLeft). This takes into account\n             the indicator that will be shown to next to the item. -->\n        <attr name=\"expandableListPreferredItemPaddingLeft\" format=\"dimension\" />\n        <!-- The preferred left padding for an expandable list item that is a child.\n             If this is not provided, it defaults to the expandableListPreferredItemPaddingLeft. -->\n        <attr name=\"expandableListPreferredChildPaddingLeft\" format=\"dimension\" />\n        <!-- The preferred left bound for an expandable list item's indicator. For a child-specific\n             indicator, use expandableListPreferredChildIndicatorLeft. -->\n        <attr name=\"expandableListPreferredItemIndicatorLeft\" format=\"dimension\" />\n        <!-- The preferred right bound for an expandable list item's indicator. For a child-specific\n             indicator, use expandableListPreferredChildIndicatorRight. -->\n        <attr name=\"expandableListPreferredItemIndicatorRight\" format=\"dimension\" />\n        <!-- The preferred left bound for an expandable list child's indicator. -->\n        <attr name=\"expandableListPreferredChildIndicatorLeft\" format=\"dimension\" />\n        <!-- The preferred right bound for an expandable list child's indicator. -->\n        <attr name=\"expandableListPreferredChildIndicatorRight\" format=\"dimension\" />\n\n        <!-- The preferred item height for dropdown lists. -->\n        <attr name=\"dropdownListPreferredItemHeight\" format=\"dimension\" />\n\n        <!-- The preferred padding along the start edge of list items. -->\n        <attr name=\"listPreferredItemPaddingStart\" format=\"dimension\" />\n        <!-- The preferred padding along the end edge of list items. -->\n        <attr name=\"listPreferredItemPaddingEnd\" format=\"dimension\" />\n\n        <!-- ============= -->\n        <!-- Window styles -->\n        <!-- ============= -->\n        <eat-comment />\n\n        <!-- Drawable to use as the overall window background.  As of\n             {@link android.os.Build.VERSION_CODES#HONEYCOMB}, this may\n             be a selector that uses state_accelerated to pick a non-solid\n             color when running on devices that can draw such a bitmap\n             with complex compositing on top at 60fps.\n\n             <p>There are a few special considerations to use when setting this\n             drawable:\n             <ul>\n             <li> This information will be used to infer the pixel format\n                  for your window's surface.  If the drawable has any\n                  non-opaque pixels, your window will be translucent\n                  (32 bpp).\n             <li> If you want to draw the entire background\n                  yourself, you should set this drawable to some solid\n                  color that closely matches that background (so the\n                  system's preview of your window will match), and\n                  then in code manually set your window's background to\n                  null so it will not be drawn.\n             </ul> -->\n        <attr name=\"windowBackground\" format=\"reference|color\" />\n        <!-- Drawable to draw selectively within the inset areas when the windowBackground\n             has been set to null. This protects against seeing visual garbage in the\n             surface when the app has not drawn any content into this area. One example is\n             when the user is resizing a window of an activity in multi-window mode. -->\n        <attr name=\"windowBackgroundFallback\" format=\"reference|color\" />\n        <!-- Blur the screen behind the window with the bounds of the window.\n             The radius defines the size of the neighbouring area, from which pixels will be\n             averaged to form the final color for each pixel in the region.\n             A radius of 0 means no blur. The higher the radius, the denser the blur.\n             Corresponds to {@link android.view.Window#setBackgroundBlurRadius}. -->\n        <attr name=\"windowBackgroundBlurRadius\" format=\"dimension\" />\n        <!-- Drawable to use as a frame around the window. -->\n        <attr name=\"windowFrame\" format=\"reference\" />\n        <!-- Flag indicating whether there should be no title on this window. -->\n        <attr name=\"windowNoTitle\" format=\"boolean\" />\n        <!-- Flag indicating whether this window should fill the entire screen.  Corresponds\n             to {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN}. -->\n        <attr name=\"windowFullscreen\" format=\"boolean\" />\n        <!-- Flag indicating whether this window should extend into overscan region.  Corresponds\n             to {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_OVERSCAN}.\n             @deprecated Overscan areas aren't set by any Android product anymore as of Android 11.\n             -->\n        <attr name=\"windowOverscan\" format=\"boolean\" />\n        <!-- Flag indicating whether this is a floating window. -->\n        <attr name=\"windowIsFloating\" format=\"boolean\" />\n        <!-- Flag indicating whether this is a translucent window. -->\n        <attr name=\"windowIsTranslucent\" format=\"boolean\" />\n        <!-- Flag indicating that this window's background should be the\n             user's current wallpaper.  Corresponds\n             to {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WALLPAPER}. -->\n        <attr name=\"windowShowWallpaper\" format=\"boolean\" />\n        <!-- This Drawable is overlaid over the foreground of the Window's content area, usually\n             to place a shadow below the title.  -->\n        <attr name=\"windowContentOverlay\" format=\"reference\" />\n        <!-- The style resource to use for a window's title bar height. -->\n        <attr name=\"windowTitleSize\" format=\"dimension\" />\n        <!-- The style resource to use for a window's title text. -->\n        <attr name=\"windowTitleStyle\" format=\"reference\" />\n        <!-- The style resource to use for a window's title area. -->\n        <attr name=\"windowTitleBackgroundStyle\" format=\"reference\" />\n\n        <!-- Reference to a style resource holding\n             the set of window animations to use, which can be\n             any of the attributes defined by\n             {@link android.R.styleable#WindowAnimation}. -->\n        <attr name=\"windowAnimationStyle\" format=\"reference\" />\n\n        <!-- Flag indicating whether this window should have an Action Bar\n             in place of the usual title bar. -->\n        <attr name=\"windowActionBar\" format=\"boolean\" />\n\n        <!-- Flag indicating whether this window's Action Bar should overlay\n             application content. Does nothing if the window would not\n             have an Action Bar. -->\n        <attr name=\"windowActionBarOverlay\" format=\"boolean\" />\n\n        <!-- Flag indicating whether action modes should overlay window content\n             when there is not reserved space for their UI (such as an Action Bar). -->\n        <attr name=\"windowActionModeOverlay\" format=\"boolean\" />\n\n        <!-- Defines the default soft input state that this window would\n             like when it is displayed.  Corresponds\n             to {@link android.view.WindowManager.LayoutParams#softInputMode}. -->\n        <attr name=\"windowSoftInputMode\">\n            <!-- Not specified, use what the system thinks is best.  This\n                 is the default. -->\n            <flag name=\"stateUnspecified\" value=\"0\" />\n            <!-- Leave the soft input window as-is, in whatever state it\n                 last was. -->\n            <flag name=\"stateUnchanged\" value=\"1\" />\n            <!-- Make the soft input area hidden when normally appropriate\n                 (when the user is navigating forward to your window). -->\n            <flag name=\"stateHidden\" value=\"2\" />\n            <!-- Always make the soft input area hidden when this window\n                 has input focus. -->\n            <flag name=\"stateAlwaysHidden\" value=\"3\" />\n            <!-- Make the soft input area visible when normally appropriate\n                 (when the user is navigating forward to your window). -->\n            <flag name=\"stateVisible\" value=\"4\" />\n            <!-- Always make the soft input area visible when this window\n                 has input focus. -->\n            <flag name=\"stateAlwaysVisible\" value=\"5\" />\n\n            <!-- The window resize/pan adjustment has not been specified,\n                 the system will automatically select between resize and pan\n                 modes, depending\n                 on whether the content of the window has any layout views\n                 that can scroll their contents.  If there is such a view,\n                 then the window will be resized, with the assumption being\n                 that the resizeable area can be reduced to make room for\n                 the input UI. -->\n            <flag name=\"adjustUnspecified\" value=\"0x00\" />\n            <!-- Always resize the window: the content area of the window is\n                 reduced to make room for the soft input area. -->\n            <flag name=\"adjustResize\" value=\"0x10\" />\n            <!-- Don't resize the window to make room for the soft input area;\n                 instead pan the contents of the window as focus moves inside\n                 of it so that the user can see what they are typing.  This is\n                 generally less desireable than panning because the user may\n                 need to close the input area to get at and interact with\n                 parts of the window. -->\n            <flag name=\"adjustPan\" value=\"0x20\" />\n            <!-- Don't resize <em>or</em> pan the window to make room for the\n                 soft input area; the window is never adjusted for it. -->\n            <flag name=\"adjustNothing\" value=\"0x30\" />\n        </attr>\n\n        <!-- Flag allowing you to disable the splash screen for a window. The default value is\n             false; if set to true, the system can never use the window's theme to show a splash\n             screen before your actual instance is shown to the user. -->\n        <attr name=\"windowDisablePreview\" format=\"boolean\" />\n\n        <!-- Flag indicating that this window should not be displayed at all.\n             The default value is false; if set to true, and this window is\n             the main window of an Activity, then it will never actually\n             be added to the window manager.  This means that your activity\n             must immediately quit without waiting for user interaction,\n             because there will be no such interaction coming. -->\n        <attr name=\"windowNoDisplay\" format=\"boolean\" />\n\n        <!-- Flag indicating that this window should allow touches to be split\n             across other windows that also support split touch.\n             The default value is true for applications with a targetSdkVersion\n             of Honeycomb or newer; false otherwise.\n             When this flag is false, the first pointer that goes down determines\n             the window to which all subsequent touches go until all pointers go up.\n             When this flag is true, each pointer (not necessarily the first) that\n             goes down determines the window to which all subsequent touches of that\n             pointer will go until that pointers go up thereby enabling touches\n             with multiple pointers to be split across multiple windows. -->\n        <attr name=\"windowEnableSplitTouch\" format=\"boolean\" />\n\n        <!-- Control whether a container should automatically close itself if\n             the user touches outside of it.  This only applies to activities\n             and dialogs.\n\n             <p>Note: this attribute will only be respected for applications\n             that are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}\n             or later. -->\n        <attr name=\"windowCloseOnTouchOutside\" format=\"boolean\" />\n\n        <!-- Flag indicating whether this window requests a translucent status bar.  Corresponds\n             to {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS}. -->\n        <attr name=\"windowTranslucentStatus\" format=\"boolean\" />\n\n        <!-- Flag indicating whether this window requests a translucent navigation bar.  Corresponds\n             to {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION}. -->\n        <attr name=\"windowTranslucentNavigation\" format=\"boolean\" />\n\n        <!-- Flag to indicate that a window can be swiped away to be dismissed.\n             Corresponds to {@link android.view.Window#FEATURE_SWIPE_TO_DISMISS}. It will also\n             dynamically change translucency of the window, if the windowIsTranslucent is not set.\n             If windowIsTranslucent is set (to either true or false) it will obey that setting.\n             @deprecated Swipe-to-dismiss isn't functional anymore.\n             -->\n        <attr name=\"windowSwipeToDismiss\" format=\"boolean\" />\n\n        <!-- Flag indicating whether this window requests that content changes be performed\n             as scene changes with transitions. Corresponds to\n             {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS}. -->\n        <attr name=\"windowContentTransitions\" format=\"boolean\" />\n\n        <!-- Reference to a TransitionManager XML resource defining the desired\n             transitions between different window content. -->\n        <attr name=\"windowContentTransitionManager\" format=\"reference\" />\n\n        <!-- Flag indicating whether this window allows Activity Transitions.\n             Corresponds to {@link android.view.Window#FEATURE_ACTIVITY_TRANSITIONS}. -->\n        <attr name=\"windowActivityTransitions\" format=\"boolean\" />\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used to move Views into the initial Window's content Scene. Corresponds to\n             {@link android.view.Window#setEnterTransition(android.transition.Transition)}. -->\n        <attr name=\"windowEnterTransition\" format=\"reference\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used to move Views out of the scene when the Window is\n             preparing to close. Corresponds to\n             {@link android.view.Window#setReturnTransition(android.transition.Transition)}. -->\n        <attr name=\"windowReturnTransition\" format=\"reference\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used to move Views out of the Window's content Scene when launching a new Activity.\n             Corresponds to\n             {@link android.view.Window#setExitTransition(android.transition.Transition)}. -->\n        <attr name=\"windowExitTransition\" format=\"reference\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used to move Views in to the scene when returning from a previously-started Activity.\n             Corresponds to\n             {@link android.view.Window#setReenterTransition(android.transition.Transition)}. -->\n        <attr name=\"windowReenterTransition\" format=\"reference\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used to move shared elements transferred into the Window's initial content Scene.\n             Corresponds to {@link android.view.Window#setSharedElementEnterTransition(\n             android.transition.Transition)}. -->\n        <attr name=\"windowSharedElementEnterTransition\" format=\"reference\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used to move shared elements transferred back to a calling Activity.\n             Corresponds to {@link android.view.Window#setSharedElementReturnTransition(\n             android.transition.Transition)}. -->\n        <attr name=\"windowSharedElementReturnTransition\" format=\"reference\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used when starting a new Activity to move shared elements prior to transferring\n             to the called Activity.\n             Corresponds to {@link android.view.Window#setSharedElementExitTransition(\n             android.transition.Transition)}. -->\n        <attr name=\"windowSharedElementExitTransition\" format=\"reference\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used for shared elements transferred back to a calling Activity.\n             Corresponds to {@link android.view.Window#setSharedElementReenterTransition(\n             android.transition.Transition)}. -->\n        <attr name=\"windowSharedElementReenterTransition\" format=\"reference\"/>\n\n        <!-- Flag indicating whether this Window's transition should overlap with\n             the exiting transition of the calling Activity. Corresponds to\n             {@link android.view.Window#setAllowEnterTransitionOverlap(boolean)}.\n             The default value is true. -->\n        <attr name=\"windowAllowEnterTransitionOverlap\" format=\"boolean\"/>\n\n        <!-- Flag indicating whether this Window's transition should overlap with\n             the exiting transition of the called Activity when the called Activity\n             finishes. Corresponds to\n             {@link android.view.Window#setAllowReturnTransitionOverlap(boolean)}.\n             The default value is true. -->\n        <attr name=\"windowAllowReturnTransitionOverlap\" format=\"boolean\"/>\n\n        <!-- Indicates whether or not shared elements should use an overlay\n             during transitions. The default value is true. -->\n        <attr name=\"windowSharedElementsUseOverlay\" format=\"boolean\"/>\n\n        <!-- Internal layout used internally for window decor -->\n        <attr name=\"windowActionBarFullscreenDecorLayout\" format=\"reference\" />\n\n        <!-- The duration, in milliseconds, of the window background fade duration\n             when transitioning into or away from an Activity when called with an\n             Activity Transition. Corresponds to\n             {@link android.view.Window#setTransitionBackgroundFadeDuration(long)}. -->\n        <attr name=\"windowTransitionBackgroundFadeDuration\" format=\"integer\"/>\n\n        <!-- ============ -->\n        <!-- Floating toolbar styles -->\n        <!-- ============ -->\n       <eat-comment />\n       <attr name=\"floatingToolbarCloseDrawable\" format=\"reference\" />\n       <attr name=\"floatingToolbarForegroundColor\" format=\"reference|color\" />\n       <attr name=\"floatingToolbarItemBackgroundBorderlessDrawable\" format=\"reference\" />\n       <attr name=\"floatingToolbarItemBackgroundDrawable\" format=\"reference\" />\n       <attr name=\"floatingToolbarOpenDrawable\" format=\"reference\" />\n       <attr name=\"floatingToolbarPopupBackgroundDrawable\" format=\"reference\" />\n       <attr name=\"floatingToolbarDividerColor\" format=\"reference\" />\n\n        <!-- ============ -->\n        <!-- Alert Dialog styles -->\n        <!-- ============ -->\n        <eat-comment />\n        <attr name=\"alertDialogStyle\" format=\"reference\" />\n        <attr name=\"alertDialogButtonGroupStyle\" format=\"reference\" />\n        <attr name=\"alertDialogCenterButtons\" format=\"boolean\" />\n\n        <!-- ============== -->\n        <!-- Image elements -->\n        <!-- ============== -->\n        <eat-comment />\n\n        <!-- Background that can be used behind parts of a UI that provide\n             details on data the user is selecting.  For example, this is\n             the background element of PreferenceActivity's embedded\n             preference fragment. -->\n        <attr name=\"detailsElementBackground\" format=\"reference\" />\n\n        <!-- Icon that should be used to indicate that an app is waiting for a fingerprint scan.\n             This should be used whenever an app is requesting the user to place a finger on the\n             fingerprint sensor. It can be combined with other drawables such as colored circles, so\n             the appearance matches the branding of the app requesting the fingerprint scan.-->\n        <attr name=\"fingerprintAuthDrawable\" format=\"reference\" />\n\n        <!-- ============ -->\n        <!-- Panel styles -->\n        <!-- ============ -->\n        <eat-comment />\n\n        <!-- The background of a panel when it is inset from the left and right edges of the screen. -->\n        <attr name=\"panelBackground\" format=\"reference|color\" />\n        <!-- The background of a panel when it extends to the left and right edges of the screen. -->\n        <attr name=\"panelFullBackground\" format=\"reference|color\" />\n        <!-- Default color of foreground panel imagery. -->\n        <attr name=\"panelColorForeground\" format=\"reference|color\" />\n        <!-- Color that matches (as closely as possible) the panel background. -->\n        <attr name=\"panelColorBackground\" format=\"reference|color\" />\n        <!-- Default appearance of panel text. -->\n        <attr name=\"panelTextAppearance\" format=\"reference\" />\n\n        <attr name=\"panelMenuIsCompact\" format=\"boolean\" />\n        <attr name=\"panelMenuListWidth\" format=\"dimension\" />\n        <attr name=\"panelMenuListTheme\" format=\"reference\" />\n\n        <!-- =================== -->\n        <!-- Other widget styles -->\n        <!-- =================== -->\n        <eat-comment />\n\n        <!-- Default AbsListView style. -->\n        <attr name=\"absListViewStyle\" format=\"reference\" />\n        <!-- Default AutoCompleteTextView style. -->\n        <attr name=\"autoCompleteTextViewStyle\" format=\"reference\" />\n        <!-- Default Checkbox style. -->\n        <attr name=\"checkboxStyle\" format=\"reference\" />\n        <!-- Default CheckedTextView style. -->\n        <attr name=\"checkedTextViewStyle\" format=\"reference\" />\n        <!-- Default ListView style for drop downs. -->\n        <attr name=\"dropDownListViewStyle\" format=\"reference\" />\n        <!-- Default EditText style. -->\n        <attr name=\"editTextStyle\" format=\"reference\" />\n        <!-- Default ExpandableListView style. -->\n        <attr name=\"expandableListViewStyle\" format=\"reference\" />\n        <!-- ExpandableListView with white background. -->\n        <attr name=\"expandableListViewWhiteStyle\" format=\"reference\" />\n        <!-- Default Gallery style. -->\n        <attr name=\"galleryStyle\" format=\"reference\" />\n        <!-- Default GestureOverlayView style. -->\n        <attr name=\"gestureOverlayViewStyle\" format=\"reference\" />\n        <!-- Default GridView style. -->\n        <attr name=\"gridViewStyle\" format=\"reference\" />\n        <!-- The style resource to use for an ImageButton. -->\n        <attr name=\"imageButtonStyle\" format=\"reference\" />\n        <!-- The style resource to use for an ImageButton that is an image well. -->\n        <attr name=\"imageWellStyle\" format=\"reference\" />\n        <!-- Default menu-style ListView style. -->\n        <attr name=\"listMenuViewStyle\" format=\"reference\" />\n        <!-- Default ListView style. -->\n        <attr name=\"listViewStyle\" format=\"reference\" />\n        <!-- ListView with white background. -->\n        <attr name=\"listViewWhiteStyle\" format=\"reference\" />\n        <!-- Default PopupWindow style. -->\n        <attr name=\"popupWindowStyle\" format=\"reference\" />\n        <!-- Default ProgressBar style. This is a medium circular progress bar. -->\n        <attr name=\"progressBarStyle\" format=\"reference\" />\n        <!-- Horizontal ProgressBar style. This is a horizontal progress bar. -->\n        <attr name=\"progressBarStyleHorizontal\" format=\"reference\" />\n        <!-- Small ProgressBar style. This is a small circular progress bar. -->\n        <attr name=\"progressBarStyleSmall\" format=\"reference\" />\n        <!-- Small ProgressBar in title style. This is a small circular progress bar that will be placed in title bars. -->\n        <attr name=\"progressBarStyleSmallTitle\" format=\"reference\" />\n        <!-- Large ProgressBar style. This is a large circular progress bar. -->\n        <attr name=\"progressBarStyleLarge\" format=\"reference\" />\n        <!-- Inverse ProgressBar style. This is a medium circular progress bar. -->\n        <attr name=\"progressBarStyleInverse\" format=\"reference\" />\n        <!-- Small inverse ProgressBar style. This is a small circular progress bar. -->\n        <attr name=\"progressBarStyleSmallInverse\" format=\"reference\" />\n        <!-- Large inverse ProgressBar style. This is a large circular progress bar. -->\n        <attr name=\"progressBarStyleLargeInverse\" format=\"reference\" />\n        <!-- Default SeekBar style. -->\n        <attr name=\"seekBarStyle\" format=\"reference\" />\n        <!-- Default RatingBar style. -->\n        <attr name=\"ratingBarStyle\" format=\"reference\" />\n        <!-- Indicator RatingBar style. -->\n        <attr name=\"ratingBarStyleIndicator\" format=\"reference\" />\n        <!-- Small indicator RatingBar style. -->\n        <attr name=\"ratingBarStyleSmall\" format=\"reference\" />\n        <!-- Default RadioButton style. -->\n        <attr name=\"radioButtonStyle\" format=\"reference\" />\n        <!-- Default ScrollView style. -->\n        <attr name=\"scrollViewStyle\" format=\"reference\" />\n        <!-- Default HorizontalScrollView style. -->\n        <attr name=\"horizontalScrollViewStyle\" format=\"reference\" />\n        <!-- Default Spinner style. -->\n        <attr name=\"spinnerStyle\" format=\"reference\" />\n        <!-- Default dropdown Spinner style. -->\n        <attr name=\"dropDownSpinnerStyle\" format=\"reference\" />\n        <!-- Default ActionBar dropdown style. -->\n        <attr name=\"actionDropDownStyle\" format=\"reference\" />\n        <!-- Default action button style. -->\n        <attr name=\"actionButtonStyle\" format=\"reference\" />\n        <!-- Default Star style. -->\n        <attr name=\"starStyle\" format=\"reference\" />\n        <!-- Default TabWidget style. -->\n        <attr name=\"tabWidgetStyle\" format=\"reference\" />\n        <!-- Default TextView style. -->\n        <attr name=\"textViewStyle\" format=\"reference\" />\n        <!-- Default WebTextView style. -->\n        <attr name=\"webTextViewStyle\" format=\"reference\" />\n        <!-- Default WebView style. -->\n        <attr name=\"webViewStyle\" format=\"reference\" />\n        <!-- Default style for drop down items. -->\n        <attr name=\"dropDownItemStyle\" format=\"reference\" />\n         <!-- Default style for spinner drop down items. -->\n        <attr name=\"spinnerDropDownItemStyle\" format=\"reference\" />\n        <!-- Default style for drop down hints. -->\n        <attr name=\"dropDownHintAppearance\" format=\"reference\" />\n        <!-- Default spinner item style. -->\n        <attr name=\"spinnerItemStyle\" format=\"reference\" />\n        <!-- Default MapView style. -->\n        <attr name=\"mapViewStyle\" format=\"reference\" />\n        <!-- Drawable used as an overlay on top of quickcontact photos. -->\n        <attr name=\"quickContactBadgeOverlay\" format=\"reference\" />\n        <!-- Default quickcontact badge style with small quickcontact window. -->\n        <attr name=\"quickContactBadgeStyleWindowSmall\" format=\"reference\" />\n        <!-- Default quickcontact badge style with medium quickcontact window. -->\n        <attr name=\"quickContactBadgeStyleWindowMedium\" format=\"reference\" />\n        <!-- Default quickcontact badge style with large quickcontact window. -->\n        <attr name=\"quickContactBadgeStyleWindowLarge\" format=\"reference\" />\n        <!-- Default quickcontact badge style with small quickcontact window. -->\n        <attr name=\"quickContactBadgeStyleSmallWindowSmall\" format=\"reference\" />\n        <!-- Default quickcontact badge style with medium quickcontact window. -->\n        <attr name=\"quickContactBadgeStyleSmallWindowMedium\" format=\"reference\" />\n        <!-- Default quickcontact badge style with large quickcontact window. -->\n        <attr name=\"quickContactBadgeStyleSmallWindowLarge\" format=\"reference\" />\n        <!-- Reference to a style that will be used for the window containing a text\n             selection anchor. -->\n        <attr name=\"textSelectHandleWindowStyle\" format=\"reference\" />\n        <!-- Reference to a style that will be used for the window containing a list of possible\n             text suggestions in an EditText. -->\n        <attr name=\"textSuggestionsWindowStyle\" format=\"reference\" />\n        <!-- Default ListPopupWindow style. -->\n        <attr name=\"listPopupWindowStyle\" format=\"reference\" />\n        <!-- Default PopupMenu style. -->\n        <attr name=\"popupMenuStyle\" format=\"reference\" />\n        <!-- Default context menu PopupMenu style. -->\n        <attr name=\"contextPopupMenuStyle\" format=\"reference\" />\n        <!-- Default StackView style. -->\n        <attr name=\"stackViewStyle\" format=\"reference\" />\n        <!-- Magnifier style. -->\n        <attr name=\"magnifierStyle\" format=\"reference\" />\n\n        <!-- Default style for the FragmentBreadCrumbs widget. This widget is deprecated\n             starting in API level 21 ({@link android.os.Build.VERSION_CODES#.L}). -->\n        <attr name=\"fragmentBreadCrumbsStyle\" format=\"reference\" />\n\n        <!-- NumberPicker style. -->\n        <attr name=\"numberPickerStyle\" format=\"reference\" />\n\n        <!-- The CalendarView style. -->\n        <attr name=\"calendarViewStyle\" format=\"reference\" />\n\n        <!-- The TimePicker style. -->\n        <attr name=\"timePickerStyle\" format=\"reference\" />\n\n        <!-- The TimePicker dialog theme. -->\n        <attr name=\"timePickerDialogTheme\" format=\"reference\" />\n\n        <!-- The DatePicker style. -->\n        <attr name=\"datePickerStyle\" format=\"reference\" />\n\n        <!-- The DatePicker dialog theme. -->\n        <attr name=\"datePickerDialogTheme\" format=\"reference\" />\n\n        <!-- Default ActivityChooserView style. -->\n        <attr name=\"activityChooserViewStyle\" format=\"reference\" />\n\n        <!-- Default Toolbar style. -->\n        <attr name=\"toolbarStyle\" format=\"reference\" />\n\n        <!-- Fast scroller styles -->\n        <eat-comment />\n\n        <!-- Drawable to use as the fast scroll thumb. -->\n        <attr name=\"fastScrollThumbDrawable\" format=\"reference\" />\n        <!-- Drawable to use as the fast scroll index preview window background\n             when shown on the right. -->\n        <attr name=\"fastScrollPreviewBackgroundRight\" format=\"reference\" />\n        <!-- Drawable to use as the fast scroll index preview window background\n             when shown on the left. -->\n        <attr name=\"fastScrollPreviewBackgroundLeft\" format=\"reference\" />\n        <!-- Drawable to use as the track for the fast scroll thumb.\n             This may be null. -->\n        <attr name=\"fastScrollTrackDrawable\" format=\"reference\" />\n        <!-- Position of the fast scroll index overlay window. -->\n        <attr name=\"fastScrollOverlayPosition\">\n            <enum name=\"floating\" value=\"0\" />\n            <enum name=\"atThumb\" value=\"1\" />\n            <enum name=\"aboveThumb\" value=\"2\" />\n        </attr>\n        <!-- Text color for the fast scroll index overlay. Make sure it\n             plays nicely with fastScrollPreviewBackground[Left|Right]. -->\n        <attr name=\"fastScrollTextColor\" format=\"color\" />\n\n        <!-- =================== -->\n        <!-- Action bar styles   -->\n        <!-- =================== -->\n        <eat-comment />\n        <!-- Default style for tabs within an action bar. -->\n        <attr name=\"actionBarTabStyle\" format=\"reference\" />\n        <!-- Reference to a style for the Action Bar Tab Bar. -->\n        <attr name=\"actionBarTabBarStyle\" format=\"reference\" />\n        <!-- Reference to a style for the Action Bar Tab text. -->\n        <attr name=\"actionBarTabTextStyle\" format=\"reference\" />\n        <!-- Reference to a style for Action Bar overflow buttons. -->\n        <attr name=\"actionOverflowButtonStyle\" format=\"reference\" />\n        <!-- Reference to a style for the Action Bar menu. -->\n        <attr name=\"actionOverflowMenuStyle\" format=\"reference\" />\n        <!-- Reference to a theme that should be used to inflate popups\n             shown by widgets in the action bar. -->\n        <attr name=\"actionBarPopupTheme\" format=\"reference\" />\n        <!-- Reference to a style for the Action Bar. -->\n        <attr name=\"actionBarStyle\" format=\"reference\" />\n        <!-- Reference to a style for the split Action Bar. This style\n             controls the split component that holds the menu/action\n             buttons. actionBarStyle is still used for the primary\n             bar. -->\n        <attr name=\"actionBarSplitStyle\" format=\"reference\" />\n        <!-- Reference to a theme that should be used to inflate the\n             action bar. This will be inherited by any widget inflated\n             into the action bar. -->\n        <attr name=\"actionBarTheme\" format=\"reference\" />\n        <!-- Reference to a theme that should be used to inflate widgets\n             and layouts destined for the action bar. Most of the time\n             this will be a reference to the current theme, but when\n             the action bar has a significantly different contrast\n             profile than the rest of the activity the difference\n             can become important. If this is set to @null the current\n             theme will be used.-->\n        <attr name=\"actionBarWidgetTheme\" format=\"reference\" />\n        <!-- Size of the Action Bar, including the contextual\n             bar used to present Action Modes. -->\n        <attr name=\"actionBarSize\" format=\"dimension\" >\n            <enum name=\"wrap_content\" value=\"0\" />\n        </attr>\n        <!-- Custom divider drawable to use for elements in the action bar. -->\n        <attr name=\"actionBarDivider\" format=\"reference\" />\n        <!-- Custom item state list drawable background for action bar items. -->\n        <attr name=\"actionBarItemBackground\" format=\"reference\" />\n        <!-- TextAppearance style that will be applied to text that\n             appears within action menu items. -->\n        <attr name=\"actionMenuTextAppearance\" format=\"reference\" />\n        <!-- Color for text that appears within action menu items. -->\n        <attr name=\"actionMenuTextColor\" format=\"color|reference\" />\n\n        <!-- =================== -->\n        <!-- Action mode styles  -->\n        <!-- =================== -->\n        <eat-comment />\n        <!-- Reference to a style for the Action Mode. -->\n        <attr name=\"actionModeStyle\" format=\"reference\" />\n        <!-- Reference to a style for the Action Mode close button. -->\n        <attr name=\"actionModeCloseButtonStyle\" format=\"reference\" />\n        <!-- Background drawable to use for action mode UI. -->\n        <attr name=\"actionModeBackground\" format=\"reference\" />\n        <!-- Background drawable to use for action mode UI in the lower split bar. -->\n        <attr name=\"actionModeSplitBackground\" format=\"reference\" />\n        <!-- Drawable to use for the close action mode button. -->\n        <attr name=\"actionModeCloseDrawable\" format=\"reference\" />\n\n        <!-- Drawable to use for the Cut action button in Contextual Action Bar. -->\n        <attr name=\"actionModeCutDrawable\" format=\"reference\" />\n        <!-- Drawable to use for the Copy action button in Contextual Action Bar. -->\n        <attr name=\"actionModeCopyDrawable\" format=\"reference\" />\n        <!-- Drawable to use for the Paste action button in Contextual Action Bar. -->\n        <attr name=\"actionModePasteDrawable\" format=\"reference\" />\n        <!-- Drawable to use for the Select all action button in Contextual Action Bar. -->\n        <attr name=\"actionModeSelectAllDrawable\" format=\"reference\" />\n        <!-- Drawable to use for the Share action button in WebView selection action modes. -->\n        <attr name=\"actionModeShareDrawable\" format=\"reference\" />\n        <!-- Drawable to use for the Find action button in WebView selection action modes. -->\n        <attr name=\"actionModeFindDrawable\" format=\"reference\" />\n        <!-- Drawable to use for the Web Search action button in WebView selection action modes. -->\n        <attr name=\"actionModeWebSearchDrawable\" format=\"reference\" />\n\n        <!-- PopupWindow style to use for action modes when showing as a window overlay. -->\n        <attr name=\"actionModePopupWindowStyle\" format=\"reference\" />\n\n        <!-- =================== -->\n        <!-- Preference styles   -->\n        <!-- =================== -->\n        <eat-comment />\n\n        <!-- Default style for PreferenceScreen. -->\n        <attr name=\"preferenceScreenStyle\" format=\"reference\" />\n        <!-- Default style for the PreferenceActivity. -->\n        <attr name=\"preferenceActivityStyle\" format=\"reference\" />\n        <!-- Default style for Headers pane in PreferenceActivity. -->\n        <attr name=\"preferenceFragmentStyle\" format=\"reference\" />\n        <!-- Default style for PreferenceCategory. -->\n        <attr name=\"preferenceCategoryStyle\" format=\"reference\" />\n        <!-- Default style for Preference. -->\n        <attr name=\"preferenceStyle\" format=\"reference\" />\n        <!-- Default style for informational Preference. -->\n        <attr name=\"preferenceInformationStyle\" format=\"reference\" />\n        <!-- Default style for CheckBoxPreference. -->\n        <attr name=\"checkBoxPreferenceStyle\" format=\"reference\" />\n        <!-- Default style for YesNoPreference. -->\n        <attr name=\"yesNoPreferenceStyle\" format=\"reference\" />\n        <!-- Default style for DialogPreference. -->\n        <attr name=\"dialogPreferenceStyle\" format=\"reference\" />\n        <!-- Default style for EditTextPreference. -->\n        <attr name=\"editTextPreferenceStyle\" format=\"reference\" />\n        <!-- @hide Default style for SeekBarDialogPreference. -->\n        <attr name=\"seekBarDialogPreferenceStyle\" format=\"reference\" />\n        <!-- Default style for RingtonePreference. -->\n        <attr name=\"ringtonePreferenceStyle\" format=\"reference\" />\n        <!-- The preference layout that has the child/tabbed effect. -->\n        <attr name=\"preferenceLayoutChild\" format=\"reference\" />\n        <!-- Preference panel style -->\n        <attr name=\"preferencePanelStyle\" format=\"reference\" />\n        <!-- Preference headers panel style -->\n        <attr name=\"preferenceHeaderPanelStyle\" format=\"reference\" />\n        <!-- Preference list style -->\n        <attr name=\"preferenceListStyle\" format=\"reference\" />\n        <!-- Preference fragment list style -->\n        <attr name=\"preferenceFragmentListStyle\" format=\"reference\" />\n        <!-- Preference fragment padding side -->\n        <attr name=\"preferenceFragmentPaddingSide\" format=\"dimension\" />\n        <!-- Default style for switch preferences. -->\n        <attr name=\"switchPreferenceStyle\" format=\"reference\" />\n        <!-- Default style for seekbar preferences. -->\n        <attr name=\"seekBarPreferenceStyle\" format=\"reference\" />\n\n        <!-- ============================ -->\n        <!-- Text selection handle styles -->\n        <!-- ============================ -->\n        <eat-comment />\n\n        <!-- Reference to a drawable that will be used to display a text selection\n             anchor on the left side of a selection region. -->\n        <attr name=\"textSelectHandleLeft\" format=\"reference\" />\n        <!-- Reference to a drawable that will be used to display a text selection\n             anchor on the right side of a selection region. -->\n        <attr name=\"textSelectHandleRight\" format=\"reference\" />\n        <!-- Reference to a drawable that will be used to display a text selection\n             anchor for positioning the cursor within text. -->\n        <attr name=\"textSelectHandle\" format=\"reference\" />\n        <!-- The layout of the view that is displayed on top of the cursor to paste inside a\n             TextEdit field. -->\n        <attr name=\"textEditPasteWindowLayout\" format=\"reference\" />\n        <!-- Variation of textEditPasteWindowLayout displayed when the clipboard is empty. -->\n        <attr name=\"textEditNoPasteWindowLayout\" format=\"reference\" />\n        <!-- Used instead of textEditPasteWindowLayout when the window is moved on the side of the\n             insertion cursor because it would be clipped if it were positioned on top. -->\n        <attr name=\"textEditSidePasteWindowLayout\" format=\"reference\" />\n        <!-- Variation of textEditSidePasteWindowLayout displayed when the clipboard is empty. -->\n        <attr name=\"textEditSideNoPasteWindowLayout\" format=\"reference\" />\n\n        <!-- Layout of the TextView item that will populate the suggestion popup window. -->\n        <attr name=\"textEditSuggestionItemLayout\" format=\"reference\" />\n        <!-- Layout of the container of the suggestion popup window. -->\n        <attr name=\"textEditSuggestionContainerLayout\" format=\"reference\" />\n        <!-- Text appearance of the focused words to be replaced by suggested word. -->\n        <attr name=\"textEditSuggestionHighlightStyle\" format=\"reference\" />\n\n        <!-- Theme to use for dialogs spawned from this theme. -->\n        <attr name=\"dialogTheme\" format=\"reference\" />\n        <!-- Window decor layout to use in dialog mode with icons. -->\n        <attr name=\"dialogTitleIconsDecorLayout\" format=\"reference\" />\n        <!-- Window decor layout to use in dialog mode with custom titles. -->\n        <attr name=\"dialogCustomTitleDecorLayout\" format=\"reference\" />\n        <!-- Window decor layout to use in dialog mode with title only. -->\n        <attr name=\"dialogTitleDecorLayout\" format=\"reference\" />\n        <!-- Preferred padding for dialog content. -->\n        <attr name=\"dialogPreferredPadding\" format=\"dimension\" />\n        <!-- Corner radius of dialogs. -->\n        <attr name=\"dialogCornerRadius\" format=\"dimension\" />\n\n        <!-- Theme to use for alert dialogs spawned from this theme. -->\n        <attr name=\"alertDialogTheme\" format=\"reference\" />\n        <!-- Icon drawable to use for alerts. -->\n        <attr name=\"alertDialogIcon\" format=\"reference\" />\n\n        <!-- Theme to use for presentations spawned from this theme. -->\n        <attr name=\"presentationTheme\" format=\"reference\" />\n\n        <!-- Drawable to use for generic vertical dividers. -->\n        <attr name=\"dividerVertical\" format=\"reference\" />\n\n        <!-- Drawable to use for generic horizontal dividers. -->\n        <attr name=\"dividerHorizontal\" format=\"reference\" />\n\n        <!-- Style for button bars. -->\n        <attr name=\"buttonBarStyle\" format=\"reference\" />\n\n        <!-- Style for buttons within button bars. -->\n        <attr name=\"buttonBarButtonStyle\" format=\"reference\" />\n\n        <!-- Style for the \"positive\" buttons within button bars. -->\n        <attr name=\"buttonBarPositiveButtonStyle\" format=\"reference\" />\n\n        <!-- Style for the \"negative\" buttons within button bars. -->\n        <attr name=\"buttonBarNegativeButtonStyle\" format=\"reference\" />\n\n        <!-- Style for the \"neutral\" buttons within button bars. -->\n        <attr name=\"buttonBarNeutralButtonStyle\" format=\"reference\" />\n\n        <!-- Corner radius of buttons. -->\n        <attr name=\"buttonCornerRadius\" format=\"dimension\" />\n\n        <!-- Corner radius of progress bars. -->\n        <attr name=\"progressBarCornerRadius\" format=\"dimension\" />\n\n        <!-- Style for the search query widget. -->\n        <attr name=\"searchViewStyle\" format=\"reference\" />\n\n        <!-- Style for segmented buttons - a container that houses several buttons\n             with the appearance of a singel button broken into segments. -->\n        <attr name=\"segmentedButtonStyle\" format=\"reference\" />\n\n        <!-- Background drawable for bordered standalone items that need focus/pressed states. -->\n        <attr name=\"selectableItemBackground\" format=\"reference\" />\n\n        <!-- Background drawable for borderless standalone items that need focus/pressed states. -->\n        <attr name=\"selectableItemBackgroundBorderless\" format=\"reference\" />\n\n        <!-- Style for buttons without an explicit border, often used in groups. -->\n        <attr name=\"borderlessButtonStyle\" format=\"reference\" />\n\n        <!-- Background to use for toasts. -->\n        <attr name=\"toastFrameBackground\" format=\"reference\" />\n\n        <!-- Background to use for tooltip popups. -->\n        <attr name=\"tooltipFrameBackground\" format=\"reference\" />\n\n        <!-- Foreground color to use for tooltip popups. -->\n        <attr name=\"tooltipForegroundColor\" format=\"reference|color\" />\n\n        <!-- Background color to use for tooltip popups. -->\n        <attr name=\"tooltipBackgroundColor\" format=\"reference|color\" />\n\n        <!-- Theme to use for Search Dialogs. -->\n        <attr name=\"searchDialogTheme\" format=\"reference\" />\n\n        <!-- Specifies a drawable to use for the 'home as up' indicator. -->\n        <attr name=\"homeAsUpIndicator\" format=\"reference\" />\n\n        <!-- Preference frame layout styles. -->\n        <attr name=\"preferenceFrameLayoutStyle\" format=\"reference\" />\n\n        <!-- Default style for the Switch widget. -->\n        <attr name=\"switchStyle\" format=\"reference\" />\n\n        <!-- Default style for the MediaRouteButton widget. -->\n        <attr name=\"mediaRouteButtonStyle\" format=\"reference\" />\n\n        <!-- ============== -->\n        <!-- Pointer styles -->\n        <!-- ============== -->\n        <eat-comment />\n\n        <!-- The drawable for accessibility focused views. -->\n        <attr name=\"accessibilityFocusedDrawable\" format=\"reference\" />\n\n        <!-- Drawable for WebView find-on-page dialogue's \"next\" button. @hide -->\n        <attr name=\"findOnPageNextDrawable\" format=\"reference\" />\n\n        <!-- Drawable for WebView find-on-page dialogue's \"previous\" button. @hide -->\n        <attr name=\"findOnPagePreviousDrawable\" format=\"reference\" />\n\n        <!-- ============= -->\n        <!-- Color palette -->\n        <!-- ============= -->\n        <eat-comment />\n\n        <!-- The primary branding color for the app. By default, this is the color applied to the\n             action bar background. -->\n        <attr name=\"colorPrimary\" format=\"color\" />\n\n        <!-- Dark variant of the primary branding color. By default, this is the color applied to\n             the status bar (via statusBarColor) and navigation bar (via navigationBarColor). -->\n        <attr name=\"colorPrimaryDark\" format=\"color\" />\n\n        <!-- The secondary branding color for the app. -->\n        <attr name=\"colorSecondary\" format=\"color\" />\n\n        <!-- Bright complement to the primary branding color. By default, this is the color applied\n             to framework controls (via colorControlActivated). -->\n        <attr name=\"colorAccent\" format=\"color\" />\n\n        <!-- Light accent color used on Material NEXT buttons. @hide -->\n        <attr name=\"colorAccentPrimary\" format=\"color\" />\n\n        <!-- Secondary accent color used on Material NEXT buttons. @hide -->\n        <attr name=\"colorAccentSecondary\" format=\"color\" />\n\n        <!-- Tertiary accent color used on Material NEXT buttons. @hide -->\n        <attr name=\"colorAccentTertiary\" format=\"color\" />\n\n        <!-- Darker accent color used on Material NEXT buttons. @hide -->\n        <attr name=\"colorAccentPrimaryVariant\" format=\"color\" />\n\n        <!-- Text color used on top of Material NEXT accent colors. @hide -->\n        <attr name=\"textColorOnAccent\" format=\"color\" />\n\n        <!-- Secondary darker accent color used on Material NEXT buttons. @hide -->\n        <attr name=\"colorAccentSecondaryVariant\" format=\"color\" />\n\n        <!-- Tertiary darker accent color used on Material NEXT buttons. @hide -->\n        <attr name=\"colorAccentTertiaryVariant\" format=\"color\" />\n\n        <!-- The color applied to framework controls in their normal state. -->\n        <attr name=\"colorControlNormal\" format=\"color\" />\n\n        <!-- The color applied to framework controls in their activated (ex. checked) state. -->\n        <attr name=\"colorControlActivated\" format=\"color\" />\n\n        <!-- The color applied to framework control highlights (ex. ripples, list selectors). -->\n        <attr name=\"colorControlHighlight\" format=\"color\" />\n\n        <!-- The color applied to framework buttons in their normal state. -->\n        <attr name=\"colorButtonNormal\" format=\"color\" />\n\n        <!-- The color applied to framework switch thumbs in their normal state. -->\n        <attr name=\"colorSwitchThumbNormal\" format=\"color\" />\n\n        <!-- The color applied to framework progress and seek bar backgrounds in their normal state. -->\n        <attr name=\"colorProgressBackgroundNormal\" format=\"color\" />\n\n        <!-- The color applied to the edge effect on scrolling containers. -->\n        <attr name=\"colorEdgeEffect\" format=\"color\" />\n\n        <!-- The color applied to surfaces on top of colorBackground. @hide -->\n        <attr name=\"colorSurface\" format=\"color\" />\n\n        <!-- Alternative color applied to surfaces on top of colorBackground. @hide -->\n        <attr name=\"colorSurfaceHighlight\" format=\"color\" />\n\n        <!-- Alternative color applied to surfaces on top of colorBackground. @hide -->\n        <attr name=\"colorSurfaceVariant\" format=\"color\" />\n\n        <!-- Alternative color applied to surfaces on top of colorBackground. @hide -->\n        <attr name=\"colorSurfaceHeader\" format=\"color\" />\n\n        <!-- Color applied to effects. -->\n        <attr name=\"effectColor\" format=\"color\" />\n\n        <!-- =================== -->\n        <!-- Lighting properties -->\n        <!-- =================== -->\n        <eat-comment />\n\n        <!-- @hide The default Y position of the light used to project view shadows. -->\n        <attr name=\"lightY\" format=\"dimension\" />\n\n        <!-- @hide The default Z position of the light used to project view shadows. -->\n        <attr name=\"lightZ\" format=\"dimension\" />\n\n        <!-- @hide The default radius of the light used to project view shadows. -->\n        <attr name=\"lightRadius\" format=\"dimension\" />\n\n        <!-- Alpha value of the ambient shadow projected by elevated views, between 0 and 1. -->\n        <attr name=\"ambientShadowAlpha\" format=\"float\" />\n\n        <!-- Alpha value of the spot shadow projected by elevated views, between 0 and 1. -->\n        <attr name=\"spotShadowAlpha\" format=\"float\" />\n\n        <!-- <p>Whether or not the force dark feature is allowed to be applied to this theme.\n             <p>Setting this to false will disable the auto-dark feature on everything this\n             theme is applied to along with anything drawn by any children of views using\n             this theme.\n             <p>Setting this to true will allow this view to be automatically made dark, however\n             a value of 'true' will not override any 'false' value in its parent chain nor will\n             it prevent any 'false' in any of its children. -->\n        <attr name=\"forceDarkAllowed\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- **************************************************************** -->\n    <!-- Other non-theme attributes. -->\n    <!-- **************************************************************** -->\n    <eat-comment />\n\n    <!-- Size of text. Recommended dimension type for text is \"sp\" for scaled-pixels (example: 15sp).\n         Supported values include the following:<p/>\n    <ul>\n        <li><b>px</b> Pixels</li>\n        <li><b>sp</b> Scaled pixels (scaled to relative pixel size on screen). See {@link android.util.DisplayMetrics} for more information.</li>\n        <li><b>pt</b> Points</li>\n        <li><b>dip</b> Device independent pixels. See {@link android.util.DisplayMetrics} for more information.</li>\n    </ul>\n        -->\n    <attr name=\"textSize\" format=\"dimension\" />\n\n    <!-- Default font family. -->\n    <attr name=\"fontFamily\" format=\"string\" />\n\n    <!-- Default text typeface. -->\n    <attr name=\"typeface\">\n        <enum name=\"normal\" value=\"0\" />\n        <enum name=\"sans\" value=\"1\" />\n        <enum name=\"serif\" value=\"2\" />\n        <enum name=\"monospace\" value=\"3\" />\n    </attr>\n\n    <!-- Default text typeface style. -->\n    <attr name=\"textStyle\">\n        <flag name=\"normal\" value=\"0\" />\n        <flag name=\"bold\" value=\"1\" />\n        <flag name=\"italic\" value=\"2\" />\n    </attr>\n\n    <!-- Color of text (usually same as colorForeground). -->\n    <attr name=\"textColor\" format=\"reference|color\" />\n\n    <!-- Color of highlighted text. -->\n    <attr name=\"textColorHighlight\" format=\"reference|color\" />\n\n    <!-- Color of hint text (displayed when the field is empty). -->\n    <attr name=\"textColorHint\" format=\"reference|color\" />\n\n    <!-- Color of link text (URLs). -->\n    <attr name=\"textColorLink\" format=\"reference|color\" />\n\n    <!-- Reference to a drawable that will be drawn under the insertion cursor. -->\n    <attr name=\"textCursorDrawable\" format=\"reference\" />\n\n    <!-- Indicates that the content of a non-editable TextView can be selected.\n     Default value is false. EditText content is always selectable. -->\n    <attr name=\"textIsSelectable\" format=\"boolean\" />\n\n    <!-- Where to ellipsize text. -->\n    <attr name=\"ellipsize\">\n        <enum name=\"none\" value=\"0\" />\n        <enum name=\"start\" value=\"1\" />\n        <enum name=\"middle\" value=\"2\" />\n        <enum name=\"end\" value=\"3\" />\n        <enum name=\"marquee\" value=\"4\" />\n    </attr>\n\n    <!-- The type of data being placed in a text field, used to help an\n         input method decide how to let the user enter text.  The constants\n         here correspond to those defined by\n         {@link android.text.InputType}.  Generally you can select\n         a single value, though some can be combined together as\n         indicated.  Setting this attribute to anything besides\n         <var>none</var> also implies that the text is editable. -->\n    <attr name=\"inputType\">\n        <!-- There is no content type.  The text is not editable. -->\n        <flag name=\"none\" value=\"0x00000000\" />\n        <!-- Just plain old text.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_NORMAL}. -->\n        <flag name=\"text\" value=\"0x00000001\" />\n        <!-- Can be combined with <var>text</var> and its variations to\n             request capitalization of all characters.  Corresponds to\n             {@link android.text.InputType#TYPE_TEXT_FLAG_CAP_CHARACTERS}. -->\n        <flag name=\"textCapCharacters\" value=\"0x00001001\" />\n        <!-- Can be combined with <var>text</var> and its variations to\n             request capitalization of the first character of every word.  Corresponds to\n             {@link android.text.InputType#TYPE_TEXT_FLAG_CAP_WORDS}. -->\n        <flag name=\"textCapWords\" value=\"0x00002001\" />\n        <!-- Can be combined with <var>text</var> and its variations to\n             request capitalization of the first character of every sentence.  Corresponds to\n             {@link android.text.InputType#TYPE_TEXT_FLAG_CAP_SENTENCES}. -->\n        <flag name=\"textCapSentences\" value=\"0x00004001\" />\n        <!-- Can be combined with <var>text</var> and its variations to\n             request auto-correction of text being input.  Corresponds to\n             {@link android.text.InputType#TYPE_TEXT_FLAG_AUTO_CORRECT}. -->\n        <flag name=\"textAutoCorrect\" value=\"0x00008001\" />\n        <!-- Can be combined with <var>text</var> and its variations to\n             specify that this field will be doing its own auto-completion and\n             talking with the input method appropriately.  Corresponds to\n             {@link android.text.InputType#TYPE_TEXT_FLAG_AUTO_COMPLETE}. -->\n        <flag name=\"textAutoComplete\" value=\"0x00010001\" />\n        <!-- Can be combined with <var>text</var> and its variations to\n             allow multiple lines of text in the field.  If this flag is not set,\n             the text field will be constrained to a single line.  Corresponds to\n             {@link android.text.InputType#TYPE_TEXT_FLAG_MULTI_LINE}.\n\n             Note: If this flag is not set and the text field doesn't have max length limit, the\n             framework automatically set maximum length of the characters to 5000 for the\n             performance reasons.\n             -->\n        <flag name=\"textMultiLine\" value=\"0x00020001\" />\n        <!-- Can be combined with <var>text</var> and its variations to\n             indicate that though the regular text view should not be multiple\n             lines, the IME should provide multiple lines if it can.  Corresponds to\n             {@link android.text.InputType#TYPE_TEXT_FLAG_IME_MULTI_LINE}. -->\n        <flag name=\"textImeMultiLine\" value=\"0x00040001\" />\n        <!-- Can be combined with <var>text</var> and its variations to\n             indicate that the IME should not show any\n             dictionary-based word suggestions.  Corresponds to\n             {@link android.text.InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS}. -->\n        <flag name=\"textNoSuggestions\" value=\"0x00080001\" />\n        <!-- Text that will be used as a URI.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_URI}. -->\n        <flag name=\"textUri\" value=\"0x00000011\" />\n        <!-- Text that will be used as an e-mail address.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_EMAIL_ADDRESS}. -->\n        <flag name=\"textEmailAddress\" value=\"0x00000021\" />\n        <!-- Text that is being supplied as the subject of an e-mail.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}. -->\n        <flag name=\"textEmailSubject\" value=\"0x00000031\" />\n        <!-- Text that is the content of a short message.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE}. -->\n        <flag name=\"textShortMessage\" value=\"0x00000041\" />\n        <!-- Text that is the content of a long message.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}. -->\n        <flag name=\"textLongMessage\" value=\"0x00000051\" />\n        <!-- Text that is the name of a person.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_PERSON_NAME}. -->\n        <flag name=\"textPersonName\" value=\"0x00000061\" />\n        <!-- Text that is being supplied as a postal mailing address.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_POSTAL_ADDRESS}. -->\n        <flag name=\"textPostalAddress\" value=\"0x00000071\" />\n        <!-- Text that is a password.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_PASSWORD}. -->\n        <flag name=\"textPassword\" value=\"0x00000081\" />\n        <!-- Text that is a password that should be visible.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_VISIBLE_PASSWORD}. -->\n        <flag name=\"textVisiblePassword\" value=\"0x00000091\" />\n        <!-- Text that is being supplied as text in a web form.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. -->\n        <flag name=\"textWebEditText\" value=\"0x000000a1\" />\n        <!-- Text that is filtering some other data.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_FILTER}. -->\n        <flag name=\"textFilter\" value=\"0x000000b1\" />\n        <!-- Text that is for phonetic pronunciation, such as a phonetic name\n             field in a contact entry.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_PHONETIC}. -->\n        <flag name=\"textPhonetic\" value=\"0x000000c1\" />\n        <!-- Text that will be used as an e-mail address on a web form.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS}. -->\n        <flag name=\"textWebEmailAddress\" value=\"0x000000d1\" />\n        <!-- Text that will be used as a password on a web form.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_TEXT} |\n             {@link android.text.InputType#TYPE_TEXT_VARIATION_WEB_PASSWORD}. -->\n        <flag name=\"textWebPassword\" value=\"0x000000e1\" />\n        <!-- A numeric only field.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_NUMBER} |\n             {@link android.text.InputType#TYPE_NUMBER_VARIATION_NORMAL}. -->\n        <flag name=\"number\" value=\"0x00000002\" />\n        <!-- Can be combined with <var>number</var> and its other options to\n             allow a signed number.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_NUMBER} |\n             {@link android.text.InputType#TYPE_NUMBER_FLAG_SIGNED}. -->\n        <flag name=\"numberSigned\" value=\"0x00001002\" />\n        <!-- Can be combined with <var>number</var> and its other options to\n             allow a decimal (fractional) number.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_NUMBER} |\n             {@link android.text.InputType#TYPE_NUMBER_FLAG_DECIMAL}. -->\n        <flag name=\"numberDecimal\" value=\"0x00002002\" />\n        <!-- A numeric password field.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_NUMBER} |\n             {@link android.text.InputType#TYPE_NUMBER_VARIATION_PASSWORD}. -->\n        <flag name=\"numberPassword\" value=\"0x00000012\" />\n        <!-- For entering a phone number.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_PHONE}. -->\n        <flag name=\"phone\" value=\"0x00000003\" />\n        <!-- For entering a date and time.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_DATETIME} |\n             {@link android.text.InputType#TYPE_DATETIME_VARIATION_NORMAL}. -->\n        <flag name=\"datetime\" value=\"0x00000004\" />\n        <!-- For entering a date.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_DATETIME} |\n             {@link android.text.InputType#TYPE_DATETIME_VARIATION_DATE}. -->\n        <flag name=\"date\" value=\"0x00000014\" />\n        <!-- For entering a time.  Corresponds to\n             {@link android.text.InputType#TYPE_CLASS_DATETIME} |\n             {@link android.text.InputType#TYPE_DATETIME_VARIATION_TIME}. -->\n        <flag name=\"time\" value=\"0x00000024\" />\n    </attr>\n\n    <!-- Additional features you can enable in an IME associated with an editor\n         to improve the integration with your application.  The constants\n         here correspond to those defined by\n         {@link android.view.inputmethod.EditorInfo#imeOptions}. -->\n    <attr name=\"imeOptions\">\n        <!-- There are no special semantics associated with this editor. -->\n        <flag name=\"normal\" value=\"0x00000000\" />\n        <!-- There is no specific action associated with this editor, let the\n             editor come up with its own if it can.\n             Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_NULL}. -->\n        <flag name=\"actionUnspecified\" value=\"0x00000000\" />\n        <!-- This editor has no action associated with it.\n             Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_ACTION_NONE}. -->\n        <flag name=\"actionNone\" value=\"0x00000001\" />\n        <!-- The action key performs a \"go\"\n             operation to take the user to the target of the text they typed.\n             Typically used, for example, when entering a URL.\n             Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_ACTION_GO}. -->\n        <flag name=\"actionGo\" value=\"0x00000002\" />\n        <!-- The action key performs a \"search\"\n             operation, taking the user to the results of searching for the text\n             the have typed (in whatever context is appropriate).\n             Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_ACTION_SEARCH}. -->\n        <flag name=\"actionSearch\" value=\"0x00000003\" />\n        <!-- The action key performs a \"send\"\n             operation, delivering the text to its target.  This is typically used\n             when composing a message.\n             Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_ACTION_SEND}. -->\n        <flag name=\"actionSend\" value=\"0x00000004\" />\n        <!-- The action key performs a \"next\"\n             operation, taking the user to the next field that will accept text.\n             Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_ACTION_NEXT}. -->\n        <flag name=\"actionNext\" value=\"0x00000005\" />\n        <!-- The action key performs a \"done\"\n             operation, closing the soft input method.\n             Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE}. -->\n        <flag name=\"actionDone\" value=\"0x00000006\" />\n        <!-- The action key performs a \"previous\"\n             operation, taking the user to the previous field that will accept text.\n             Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_ACTION_PREVIOUS}. -->\n        <flag name=\"actionPrevious\" value=\"0x00000007\" />\n        <!-- Used to request that the IME should not update any personalized data such as typing\n             history and personalized language model based on what the user typed on this text\n             editing object. Typical use cases are:\n             <ul>\n                 <li>When the application is in a special mode, where user's activities are expected\n                 to be not recorded in the application's history. Some web browsers and chat\n                 applications may have this kind of modes.</li>\n                 <li>When storing typing history does not make much sense.  Specifying this flag in\n                 typing games may help to avoid typing history from being filled up with words that\n                 the user is less likely to type in their daily life.  Another example is that when\n                 the application already knows that the expected input is not a valid word (e.g. a\n                 promotion code that is not a valid word in any natural language).</li>\n             </ul>\n             <p>Applications need to be aware that the flag is not a guarantee, and some IMEs may\n             not respect it.</p> -->\n        <flag name=\"flagNoPersonalizedLearning\" value=\"0x1000000\" />\n        <!-- Used to request that the IME never go\n             into fullscreen mode.  Applications need to be aware that the flag is not\n             a guarantee, and not all IMEs will respect it.\n             <p>Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_FLAG_NO_FULLSCREEN}. -->\n        <flag name=\"flagNoFullscreen\" value=\"0x2000000\" />\n        <!-- Like flagNavigateNext, but\n             specifies there is something interesting that a backward navigation\n             can focus on.  If the user selects the IME's facility to backward\n             navigate, this will show up in the application as an actionPrevious\n             at {@link android.view.inputmethod.InputConnection#performEditorAction(int)\n             InputConnection.performEditorAction(int)}.\n             <p>Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_FLAG_NAVIGATE_PREVIOUS}. -->\n        <flag name=\"flagNavigatePrevious\" value=\"0x4000000\" />\n        <!-- Used to specify that there is something\n             interesting that a forward navigation can focus on. This is like using\n             actionNext, except allows the IME to be multiline (with\n             an enter key) as well as provide forward navigation.  Note that some\n             IMEs may not be able to do this, especially when running on a small\n             screen where there is little space.  In that case it does not need to\n             present a UI for this option.  Like actionNext, if the\n             user selects the IME's facility to forward navigate, this will show up\n             in the application at\n             {@link android.view.inputmethod.InputConnection#performEditorAction(int)\n             InputConnection.performEditorAction(int)}.\n             <p>Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_FLAG_NAVIGATE_NEXT}. -->\n        <flag name=\"flagNavigateNext\" value=\"0x8000000\" />\n        <!-- Used to specify that the IME does not need\n             to show its extracted text UI.  For input methods that may be fullscreen,\n             often when in landscape mode, this allows them to be smaller and let part\n             of the application be shown behind.  Though there will likely be limited\n             access to the application available from the user, it can make the\n             experience of a (mostly) fullscreen IME less jarring.  Note that when\n             this flag is specified the IME may <em>not</em> be set up to be able\n             to display text, so it should only be used in situations where this is\n             not needed.\n             <p>Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_FLAG_NO_EXTRACT_UI}. -->\n        <flag name=\"flagNoExtractUi\" value=\"0x10000000\" />\n        <!-- Used in conjunction with a custom action, this indicates that the\n             action should not be available as an accessory button when the\n             input method is full-screen.\n             Note that by setting this flag, there can be cases where the action\n             is simply never available to the user.  Setting this generally means\n             that you think showing text being edited is more important than the\n             action you have supplied.\n             <p>Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_FLAG_NO_ACCESSORY_ACTION}. -->\n        <flag name=\"flagNoAccessoryAction\" value=\"0x20000000\" />\n        <!-- Used in conjunction with a custom action,\n             this indicates that the action should not be available in-line as\n             a replacement for the \"enter\" key.  Typically this is\n             because the action has such a significant impact or is not recoverable\n             enough that accidentally hitting it should be avoided, such as sending\n             a message.    Note that {@link android.widget.TextView} will\n             automatically set this flag for you on multi-line text views.\n             <p>Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_FLAG_NO_ENTER_ACTION}. -->\n        <flag name=\"flagNoEnterAction\" value=\"0x40000000\" />\n        <!-- Used to request that the IME should be capable of inputting ASCII\n             characters.  The intention of this flag is to ensure that the user\n             can type Roman alphabet characters in a {@link android.widget.TextView}\n             used for, typically, account ID or password input.  It is expected that IMEs\n             normally are able to input ASCII even without being told so (such IMEs\n             already respect this flag in a sense), but there could be some cases they\n             aren't when, for instance, only non-ASCII input languages like Arabic,\n             Greek, Hebrew, Russian are enabled in the IME.  Applications need to be\n             aware that the flag is not a guarantee, and not all IMEs will respect it.\n             However, it is strongly recommended for IME authors to respect this flag\n             especially when their IME could end up with a state that has only non-ASCII\n             input languages enabled.\n             <p>Corresponds to\n             {@link android.view.inputmethod.EditorInfo#IME_FLAG_FORCE_ASCII}. -->\n        <flag name=\"flagForceAscii\" value=\"0x80000000\" />\n    </attr>\n\n    <!-- A coordinate in the X dimension. -->\n    <attr name=\"x\" format=\"dimension\" />\n    <!-- A coordinate in the Y dimension. -->\n    <attr name=\"y\" format=\"dimension\" />\n\n    <!-- Specifies how an object should position its content, on both the X and Y axes,\n         within its own bounds.  -->\n    <attr name=\"gravity\">\n        <!-- Push object to the top of its container, not changing its size. -->\n        <flag name=\"top\" value=\"0x30\" />\n        <!-- Push object to the bottom of its container, not changing its size. -->\n        <flag name=\"bottom\" value=\"0x50\" />\n        <!-- Push object to the left of its container, not changing its size. -->\n        <flag name=\"left\" value=\"0x03\" />\n        <!-- Push object to the right of its container, not changing its size. -->\n        <flag name=\"right\" value=\"0x05\" />\n        <!-- Place object in the vertical center of its container, not changing its size. -->\n        <flag name=\"center_vertical\" value=\"0x10\" />\n        <!-- Grow the vertical size of the object if needed so it completely fills its container. -->\n        <flag name=\"fill_vertical\" value=\"0x70\" />\n        <!-- Place object in the horizontal center of its container, not changing its size. -->\n        <flag name=\"center_horizontal\" value=\"0x01\" />\n        <!-- Grow the horizontal size of the object if needed so it completely fills its container. -->\n        <flag name=\"fill_horizontal\" value=\"0x07\" />\n        <!-- Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. -->\n        <flag name=\"center\" value=\"0x11\" />\n        <!-- Grow the horizontal and vertical size of the object if needed so it completely fills its container. -->\n        <flag name=\"fill\" value=\"0x77\" />\n        <!-- Additional option that can be set to have the top and/or bottom edges of\n             the child clipped to its container's bounds.\n             The clip will be based on the vertical gravity: a top gravity will clip the bottom\n             edge, a bottom gravity will clip the top edge, and neither will clip both edges. -->\n        <flag name=\"clip_vertical\" value=\"0x80\" />\n        <!-- Additional option that can be set to have the left and/or right edges of\n             the child clipped to its container's bounds.\n             The clip will be based on the horizontal gravity: a left gravity will clip the right\n             edge, a right gravity will clip the left edge, and neither will clip both edges. -->\n        <flag name=\"clip_horizontal\" value=\"0x08\" />\n        <!-- Push object to the beginning of its container, not changing its size. -->\n        <flag name=\"start\" value=\"0x00800003\" />\n        <!-- Push object to the end of its container, not changing its size. -->\n        <flag name=\"end\" value=\"0x00800005\" />\n    </attr>\n\n    <!-- Controls whether links such as urls and email addresses are\n         automatically found and converted to clickable links.  The default\n         value is \"none\", disabling this feature. -->\n    <attr name=\"autoLink\">\n        <!-- Match no patterns (default). -->\n        <flag name=\"none\" value=\"0x00\" />\n        <!-- Match Web URLs. -->\n        <flag name=\"web\" value=\"0x01\" />\n        <!-- Match email addresses. -->\n        <flag name=\"email\" value=\"0x02\" />\n        <!-- Match phone numbers. -->\n        <flag name=\"phone\" value=\"0x04\" />\n        <!-- Match map addresses.\n             Deprecated: see {@link android.text.util.Linkify#MAP_ADDRESSES}. -->\n        <flag name=\"map\" value=\"0x08\" />\n        <!-- Match all patterns (equivalent to web|email|phone|map). -->\n        <flag name=\"all\" value=\"0x0f\" />\n    </attr>\n\n    <!-- Reference to an array resource that will populate a list/adapter. -->\n    <attr name=\"entries\" format=\"reference\" />\n\n    <!-- Standard gravity constant that a child supplies to its parent.\n         Defines how the child view should be positioned, on both the X and Y axes, within its enclosing layout. -->\n    <attr name=\"layout_gravity\">\n        <!-- Push object to the top of its container, not changing its size. -->\n        <flag name=\"top\" value=\"0x30\" />\n        <!-- Push object to the bottom of its container, not changing its size. -->\n        <flag name=\"bottom\" value=\"0x50\" />\n        <!-- Push object to the left of its container, not changing its size. -->\n        <flag name=\"left\" value=\"0x03\" />\n        <!-- Push object to the right of its container, not changing its size. -->\n        <flag name=\"right\" value=\"0x05\" />\n        <!-- Place object in the vertical center of its container, not changing its size. -->\n        <flag name=\"center_vertical\" value=\"0x10\" />\n        <!-- Grow the vertical size of the object if needed so it completely fills its container. -->\n        <flag name=\"fill_vertical\" value=\"0x70\" />\n        <!-- Place object in the horizontal center of its container, not changing its size. -->\n        <flag name=\"center_horizontal\" value=\"0x01\" />\n        <!-- Grow the horizontal size of the object if needed so it completely fills its container. -->\n        <flag name=\"fill_horizontal\" value=\"0x07\" />\n        <!-- Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. -->\n        <flag name=\"center\" value=\"0x11\" />\n        <!-- Grow the horizontal and vertical size of the object if needed so it completely fills its container. -->\n        <flag name=\"fill\" value=\"0x77\" />\n        <!-- Additional option that can be set to have the top and/or bottom edges of\n             the child clipped to its container's bounds.\n             The clip will be based on the vertical gravity: a top gravity will clip the bottom\n             edge, a bottom gravity will clip the top edge, and neither will clip both edges. -->\n        <flag name=\"clip_vertical\" value=\"0x80\" />\n        <!-- Additional option that can be set to have the left and/or right edges of\n             the child clipped to its container's bounds.\n             The clip will be based on the horizontal gravity: a left gravity will clip the right\n             edge, a right gravity will clip the left edge, and neither will clip both edges. -->\n        <flag name=\"clip_horizontal\" value=\"0x08\" />\n        <!-- Push object to the beginning of its container, not changing its size. -->\n        <flag name=\"start\" value=\"0x00800003\" />\n        <!-- Push object to the end of its container, not changing its size. -->\n        <flag name=\"end\" value=\"0x00800005\" />\n    </attr>\n\n    <!-- Standard orientation constant. -->\n    <attr name=\"orientation\">\n        <!-- Defines an horizontal widget. -->\n        <enum name=\"horizontal\" value=\"0\" />\n        <!-- Defines a vertical widget. -->\n        <enum name=\"vertical\" value=\"1\" />\n    </attr>\n\n    <!-- Alignment constants. -->\n    <attr name=\"alignmentMode\">\n        <!-- Align the bounds of the children.\n        See {@link android.widget.GridLayout#ALIGN_BOUNDS}. -->\n        <enum name=\"alignBounds\" value=\"0\" />\n        <!-- Align the margins of the children.\n        See {@link android.widget.GridLayout#ALIGN_MARGINS}. -->\n        <enum name=\"alignMargins\" value=\"1\" />\n    </attr>\n\n    <!-- ========================== -->\n    <!-- Key Codes                  -->\n    <!-- ========================== -->\n    <eat-comment />\n\n    <!-- This enum provides the same keycode values as can be found in\n        {@link android.view.KeyEvent}. -->\n    <attr name=\"keycode\">\n        <enum name=\"KEYCODE_UNKNOWN\" value=\"0\" />\n        <enum name=\"KEYCODE_SOFT_LEFT\" value=\"1\" />\n        <enum name=\"KEYCODE_SOFT_RIGHT\" value=\"2\" />\n        <enum name=\"KEYCODE_HOME\" value=\"3\" />\n        <enum name=\"KEYCODE_BACK\" value=\"4\" />\n        <enum name=\"KEYCODE_CALL\" value=\"5\" />\n        <enum name=\"KEYCODE_ENDCALL\" value=\"6\" />\n        <enum name=\"KEYCODE_0\" value=\"7\" />\n        <enum name=\"KEYCODE_1\" value=\"8\" />\n        <enum name=\"KEYCODE_2\" value=\"9\" />\n        <enum name=\"KEYCODE_3\" value=\"10\" />\n        <enum name=\"KEYCODE_4\" value=\"11\" />\n        <enum name=\"KEYCODE_5\" value=\"12\" />\n        <enum name=\"KEYCODE_6\" value=\"13\" />\n        <enum name=\"KEYCODE_7\" value=\"14\" />\n        <enum name=\"KEYCODE_8\" value=\"15\" />\n        <enum name=\"KEYCODE_9\" value=\"16\" />\n        <enum name=\"KEYCODE_STAR\" value=\"17\" />\n        <enum name=\"KEYCODE_POUND\" value=\"18\" />\n        <enum name=\"KEYCODE_DPAD_UP\" value=\"19\" />\n        <enum name=\"KEYCODE_DPAD_DOWN\" value=\"20\" />\n        <enum name=\"KEYCODE_DPAD_LEFT\" value=\"21\" />\n        <enum name=\"KEYCODE_DPAD_RIGHT\" value=\"22\" />\n        <enum name=\"KEYCODE_DPAD_CENTER\" value=\"23\" />\n        <enum name=\"KEYCODE_VOLUME_UP\" value=\"24\" />\n        <enum name=\"KEYCODE_VOLUME_DOWN\" value=\"25\" />\n        <enum name=\"KEYCODE_POWER\" value=\"26\" />\n        <enum name=\"KEYCODE_CAMERA\" value=\"27\" />\n        <enum name=\"KEYCODE_CLEAR\" value=\"28\" />\n        <enum name=\"KEYCODE_A\" value=\"29\" />\n        <enum name=\"KEYCODE_B\" value=\"30\" />\n        <enum name=\"KEYCODE_C\" value=\"31\" />\n        <enum name=\"KEYCODE_D\" value=\"32\" />\n        <enum name=\"KEYCODE_E\" value=\"33\" />\n        <enum name=\"KEYCODE_F\" value=\"34\" />\n        <enum name=\"KEYCODE_G\" value=\"35\" />\n        <enum name=\"KEYCODE_H\" value=\"36\" />\n        <enum name=\"KEYCODE_I\" value=\"37\" />\n        <enum name=\"KEYCODE_J\" value=\"38\" />\n        <enum name=\"KEYCODE_K\" value=\"39\" />\n        <enum name=\"KEYCODE_L\" value=\"40\" />\n        <enum name=\"KEYCODE_M\" value=\"41\" />\n        <enum name=\"KEYCODE_N\" value=\"42\" />\n        <enum name=\"KEYCODE_O\" value=\"43\" />\n        <enum name=\"KEYCODE_P\" value=\"44\" />\n        <enum name=\"KEYCODE_Q\" value=\"45\" />\n        <enum name=\"KEYCODE_R\" value=\"46\" />\n        <enum name=\"KEYCODE_S\" value=\"47\" />\n        <enum name=\"KEYCODE_T\" value=\"48\" />\n        <enum name=\"KEYCODE_U\" value=\"49\" />\n        <enum name=\"KEYCODE_V\" value=\"50\" />\n        <enum name=\"KEYCODE_W\" value=\"51\" />\n        <enum name=\"KEYCODE_X\" value=\"52\" />\n        <enum name=\"KEYCODE_Y\" value=\"53\" />\n        <enum name=\"KEYCODE_Z\" value=\"54\" />\n        <enum name=\"KEYCODE_COMMA\" value=\"55\" />\n        <enum name=\"KEYCODE_PERIOD\" value=\"56\" />\n        <enum name=\"KEYCODE_ALT_LEFT\" value=\"57\" />\n        <enum name=\"KEYCODE_ALT_RIGHT\" value=\"58\" />\n        <enum name=\"KEYCODE_SHIFT_LEFT\" value=\"59\" />\n        <enum name=\"KEYCODE_SHIFT_RIGHT\" value=\"60\" />\n        <enum name=\"KEYCODE_TAB\" value=\"61\" />\n        <enum name=\"KEYCODE_SPACE\" value=\"62\" />\n        <enum name=\"KEYCODE_SYM\" value=\"63\" />\n        <enum name=\"KEYCODE_EXPLORER\" value=\"64\" />\n        <enum name=\"KEYCODE_ENVELOPE\" value=\"65\" />\n        <enum name=\"KEYCODE_ENTER\" value=\"66\" />\n        <enum name=\"KEYCODE_DEL\" value=\"67\" />\n        <enum name=\"KEYCODE_GRAVE\" value=\"68\" />\n        <enum name=\"KEYCODE_MINUS\" value=\"69\" />\n        <enum name=\"KEYCODE_EQUALS\" value=\"70\" />\n        <enum name=\"KEYCODE_LEFT_BRACKET\" value=\"71\" />\n        <enum name=\"KEYCODE_RIGHT_BRACKET\" value=\"72\" />\n        <enum name=\"KEYCODE_BACKSLASH\" value=\"73\" />\n        <enum name=\"KEYCODE_SEMICOLON\" value=\"74\" />\n        <enum name=\"KEYCODE_APOSTROPHE\" value=\"75\" />\n        <enum name=\"KEYCODE_SLASH\" value=\"76\" />\n        <enum name=\"KEYCODE_AT\" value=\"77\" />\n        <enum name=\"KEYCODE_NUM\" value=\"78\" />\n        <enum name=\"KEYCODE_HEADSETHOOK\" value=\"79\" />\n        <enum name=\"KEYCODE_FOCUS\" value=\"80\" />\n        <enum name=\"KEYCODE_PLUS\" value=\"81\" />\n        <enum name=\"KEYCODE_MENU\" value=\"82\" />\n        <enum name=\"KEYCODE_NOTIFICATION\" value=\"83\" />\n        <enum name=\"KEYCODE_SEARCH\" value=\"84\" />\n        <enum name=\"KEYCODE_MEDIA_PLAY_PAUSE\" value=\"85\" />\n        <enum name=\"KEYCODE_MEDIA_STOP\" value=\"86\" />\n        <enum name=\"KEYCODE_MEDIA_NEXT\" value=\"87\" />\n        <enum name=\"KEYCODE_MEDIA_PREVIOUS\" value=\"88\" />\n        <enum name=\"KEYCODE_MEDIA_REWIND\" value=\"89\" />\n        <enum name=\"KEYCODE_MEDIA_FAST_FORWARD\" value=\"90\" />\n        <enum name=\"KEYCODE_MUTE\" value=\"91\" />\n        <enum name=\"KEYCODE_PAGE_UP\" value=\"92\" />\n        <enum name=\"KEYCODE_PAGE_DOWN\" value=\"93\" />\n        <enum name=\"KEYCODE_PICTSYMBOLS\" value=\"94\" />\n        <enum name=\"KEYCODE_SWITCH_CHARSET\" value=\"95\" />\n        <enum name=\"KEYCODE_BUTTON_A\" value=\"96\" />\n        <enum name=\"KEYCODE_BUTTON_B\" value=\"97\" />\n        <enum name=\"KEYCODE_BUTTON_C\" value=\"98\" />\n        <enum name=\"KEYCODE_BUTTON_X\" value=\"99\" />\n        <enum name=\"KEYCODE_BUTTON_Y\" value=\"100\" />\n        <enum name=\"KEYCODE_BUTTON_Z\" value=\"101\" />\n        <enum name=\"KEYCODE_BUTTON_L1\" value=\"102\" />\n        <enum name=\"KEYCODE_BUTTON_R1\" value=\"103\" />\n        <enum name=\"KEYCODE_BUTTON_L2\" value=\"104\" />\n        <enum name=\"KEYCODE_BUTTON_R2\" value=\"105\" />\n        <enum name=\"KEYCODE_BUTTON_THUMBL\" value=\"106\" />\n        <enum name=\"KEYCODE_BUTTON_THUMBR\" value=\"107\" />\n        <enum name=\"KEYCODE_BUTTON_START\" value=\"108\" />\n        <enum name=\"KEYCODE_BUTTON_SELECT\" value=\"109\" />\n        <enum name=\"KEYCODE_BUTTON_MODE\" value=\"110\" />\n        <enum name=\"KEYCODE_ESCAPE\" value=\"111\" />\n        <enum name=\"KEYCODE_FORWARD_DEL\" value=\"112\" />\n        <enum name=\"KEYCODE_CTRL_LEFT\" value=\"113\" />\n        <enum name=\"KEYCODE_CTRL_RIGHT\" value=\"114\" />\n        <enum name=\"KEYCODE_CAPS_LOCK\" value=\"115\" />\n        <enum name=\"KEYCODE_SCROLL_LOCK\" value=\"116\" />\n        <enum name=\"KEYCODE_META_LEFT\" value=\"117\" />\n        <enum name=\"KEYCODE_META_RIGHT\" value=\"118\" />\n        <enum name=\"KEYCODE_FUNCTION\" value=\"119\" />\n        <enum name=\"KEYCODE_SYSRQ\" value=\"120\" />\n        <enum name=\"KEYCODE_BREAK\" value=\"121\" />\n        <enum name=\"KEYCODE_MOVE_HOME\" value=\"122\" />\n        <enum name=\"KEYCODE_MOVE_END\" value=\"123\" />\n        <enum name=\"KEYCODE_INSERT\" value=\"124\" />\n        <enum name=\"KEYCODE_FORWARD\" value=\"125\" />\n        <enum name=\"KEYCODE_MEDIA_PLAY\" value=\"126\" />\n        <enum name=\"KEYCODE_MEDIA_PAUSE\" value=\"127\" />\n        <enum name=\"KEYCODE_MEDIA_CLOSE\" value=\"128\" />\n        <enum name=\"KEYCODE_MEDIA_EJECT\" value=\"129\" />\n        <enum name=\"KEYCODE_MEDIA_RECORD\" value=\"130\" />\n        <enum name=\"KEYCODE_F1\" value=\"131\" />\n        <enum name=\"KEYCODE_F2\" value=\"132\" />\n        <enum name=\"KEYCODE_F3\" value=\"133\" />\n        <enum name=\"KEYCODE_F4\" value=\"134\" />\n        <enum name=\"KEYCODE_F5\" value=\"135\" />\n        <enum name=\"KEYCODE_F6\" value=\"136\" />\n        <enum name=\"KEYCODE_F7\" value=\"137\" />\n        <enum name=\"KEYCODE_F8\" value=\"138\" />\n        <enum name=\"KEYCODE_F9\" value=\"139\" />\n        <enum name=\"KEYCODE_F10\" value=\"140\" />\n        <enum name=\"KEYCODE_F11\" value=\"141\" />\n        <enum name=\"KEYCODE_F12\" value=\"142\" />\n        <enum name=\"KEYCODE_NUM_LOCK\" value=\"143\" />\n        <enum name=\"KEYCODE_NUMPAD_0\" value=\"144\" />\n        <enum name=\"KEYCODE_NUMPAD_1\" value=\"145\" />\n        <enum name=\"KEYCODE_NUMPAD_2\" value=\"146\" />\n        <enum name=\"KEYCODE_NUMPAD_3\" value=\"147\" />\n        <enum name=\"KEYCODE_NUMPAD_4\" value=\"148\" />\n        <enum name=\"KEYCODE_NUMPAD_5\" value=\"149\" />\n        <enum name=\"KEYCODE_NUMPAD_6\" value=\"150\" />\n        <enum name=\"KEYCODE_NUMPAD_7\" value=\"151\" />\n        <enum name=\"KEYCODE_NUMPAD_8\" value=\"152\" />\n        <enum name=\"KEYCODE_NUMPAD_9\" value=\"153\" />\n        <enum name=\"KEYCODE_NUMPAD_DIVIDE\" value=\"154\" />\n        <enum name=\"KEYCODE_NUMPAD_MULTIPLY\" value=\"155\" />\n        <enum name=\"KEYCODE_NUMPAD_SUBTRACT\" value=\"156\" />\n        <enum name=\"KEYCODE_NUMPAD_ADD\" value=\"157\" />\n        <enum name=\"KEYCODE_NUMPAD_DOT\" value=\"158\" />\n        <enum name=\"KEYCODE_NUMPAD_COMMA\" value=\"159\" />\n        <enum name=\"KEYCODE_NUMPAD_ENTER\" value=\"160\" />\n        <enum name=\"KEYCODE_NUMPAD_EQUALS\" value=\"161\" />\n        <enum name=\"KEYCODE_NUMPAD_LEFT_PAREN\" value=\"162\" />\n        <enum name=\"KEYCODE_NUMPAD_RIGHT_PAREN\" value=\"163\" />\n        <enum name=\"KEYCODE_VOLUME_MUTE\" value=\"164\" />\n        <enum name=\"KEYCODE_INFO\" value=\"165\" />\n        <enum name=\"KEYCODE_CHANNEL_UP\" value=\"166\" />\n        <enum name=\"KEYCODE_CHANNEL_DOWN\" value=\"167\" />\n        <enum name=\"KEYCODE_ZOOM_IN\" value=\"168\" />\n        <enum name=\"KEYCODE_ZOOM_OUT\" value=\"169\" />\n        <enum name=\"KEYCODE_TV\" value=\"170\" />\n        <enum name=\"KEYCODE_WINDOW\" value=\"171\" />\n        <enum name=\"KEYCODE_GUIDE\" value=\"172\" />\n        <enum name=\"KEYCODE_DVR\" value=\"173\" />\n        <enum name=\"KEYCODE_BOOKMARK\" value=\"174\" />\n        <enum name=\"KEYCODE_CAPTIONS\" value=\"175\" />\n        <enum name=\"KEYCODE_SETTINGS\" value=\"176\" />\n        <enum name=\"KEYCODE_TV_POWER\" value=\"177\" />\n        <enum name=\"KEYCODE_TV_INPUT\" value=\"178\" />\n        <enum name=\"KEYCODE_STB_POWER\" value=\"179\" />\n        <enum name=\"KEYCODE_STB_INPUT\" value=\"180\" />\n        <enum name=\"KEYCODE_AVR_POWER\" value=\"181\" />\n        <enum name=\"KEYCODE_AVR_INPUT\" value=\"182\" />\n        <enum name=\"KEYCODE_PROG_GRED\" value=\"183\" />\n        <enum name=\"KEYCODE_PROG_GREEN\" value=\"184\" />\n        <enum name=\"KEYCODE_PROG_YELLOW\" value=\"185\" />\n        <enum name=\"KEYCODE_PROG_BLUE\" value=\"186\" />\n        <enum name=\"KEYCODE_APP_SWITCH\" value=\"187\" />\n        <enum name=\"KEYCODE_BUTTON_1\" value=\"188\" />\n        <enum name=\"KEYCODE_BUTTON_2\" value=\"189\" />\n        <enum name=\"KEYCODE_BUTTON_3\" value=\"190\" />\n        <enum name=\"KEYCODE_BUTTON_4\" value=\"191\" />\n        <enum name=\"KEYCODE_BUTTON_5\" value=\"192\" />\n        <enum name=\"KEYCODE_BUTTON_6\" value=\"193\" />\n        <enum name=\"KEYCODE_BUTTON_7\" value=\"194\" />\n        <enum name=\"KEYCODE_BUTTON_8\" value=\"195\" />\n        <enum name=\"KEYCODE_BUTTON_9\" value=\"196\" />\n        <enum name=\"KEYCODE_BUTTON_10\" value=\"197\" />\n        <enum name=\"KEYCODE_BUTTON_11\" value=\"198\" />\n        <enum name=\"KEYCODE_BUTTON_12\" value=\"199\" />\n        <enum name=\"KEYCODE_BUTTON_13\" value=\"200\" />\n        <enum name=\"KEYCODE_BUTTON_14\" value=\"201\" />\n        <enum name=\"KEYCODE_BUTTON_15\" value=\"202\" />\n        <enum name=\"KEYCODE_BUTTON_16\" value=\"203\" />\n        <enum name=\"KEYCODE_LANGUAGE_SWITCH\" value=\"204\" />\n        <enum name=\"KEYCODE_MANNER_MODE\" value=\"205\" />\n        <enum name=\"KEYCODE_3D_MODE\" value=\"206\" />\n        <enum name=\"KEYCODE_CONTACTS\" value=\"207\" />\n        <enum name=\"KEYCODE_CALENDAR\" value=\"208\" />\n        <enum name=\"KEYCODE_MUSIC\" value=\"209\" />\n        <enum name=\"KEYCODE_CALCULATOR\" value=\"210\" />\n        <enum name=\"KEYCODE_ZENKAKU_HANKAKU\" value=\"211\" />\n        <enum name=\"KEYCODE_EISU\" value=\"212\" />\n        <enum name=\"KEYCODE_MUHENKAN\" value=\"213\" />\n        <enum name=\"KEYCODE_HENKAN\" value=\"214\" />\n        <enum name=\"KEYCODE_KATAKANA_HIRAGANA\" value=\"215\" />\n        <enum name=\"KEYCODE_YEN\" value=\"216\" />\n        <enum name=\"KEYCODE_RO\" value=\"217\" />\n        <enum name=\"KEYCODE_KANA\" value=\"218\" />\n        <enum name=\"KEYCODE_ASSIST\" value=\"219\" />\n        <enum name=\"KEYCODE_BRIGHTNESS_DOWN\" value=\"220\" />\n        <enum name=\"KEYCODE_BRIGHTNESS_UP\" value=\"221\" />\n        <enum name=\"KEYCODE_MEDIA_AUDIO_TRACK\" value=\"222\" />\n        <enum name=\"KEYCODE_MEDIA_SLEEP\" value=\"223\" />\n        <enum name=\"KEYCODE_MEDIA_WAKEUP\" value=\"224\" />\n        <enum name=\"KEYCODE_PAIRING\" value=\"225\" />\n        <enum name=\"KEYCODE_MEDIA_TOP_MENU\" value=\"226\" />\n        <enum name=\"KEYCODE_11\" value=\"227\" />\n        <enum name=\"KEYCODE_12\" value=\"228\" />\n        <enum name=\"KEYCODE_LAST_CHANNEL\" value=\"229\" />\n        <enum name=\"KEYCODE_TV_DATA_SERVICE\" value=\"230\" />\n        <enum name=\"KEYCODE_VOICE_ASSIST\" value=\"231\" />\n        <enum name=\"KEYCODE_TV_RADIO_SERVICE\" value=\"232\" />\n        <enum name=\"KEYCODE_TV_TELETEXT\" value=\"233\" />\n        <enum name=\"KEYCODE_TV_NUMBER_ENTRY\" value=\"234\" />\n        <enum name=\"KEYCODE_TV_TERRESTRIAL_ANALOG\" value=\"235\" />\n        <enum name=\"KEYCODE_TV_TERRESTRIAL_DIGITAL\" value=\"236\" />\n        <enum name=\"KEYCODE_TV_SATELLITE\" value=\"237\" />\n        <enum name=\"KEYCODE_TV_SATELLITE_BS\" value=\"238\" />\n        <enum name=\"KEYCODE_TV_SATELLITE_CS\" value=\"239\" />\n        <enum name=\"KEYCODE_TV_SATELLITE_SERVICE\" value=\"240\" />\n        <enum name=\"KEYCODE_TV_NETWORK\" value=\"241\" />\n        <enum name=\"KEYCODE_TV_ANTENNA_CABLE\" value=\"242\" />\n        <enum name=\"KEYCODE_TV_INPUT_HDMI_1\" value=\"243\" />\n        <enum name=\"KEYCODE_TV_INPUT_HDMI_2\" value=\"244\" />\n        <enum name=\"KEYCODE_TV_INPUT_HDMI_3\" value=\"245\" />\n        <enum name=\"KEYCODE_TV_INPUT_HDMI_4\" value=\"246\" />\n        <enum name=\"KEYCODE_TV_INPUT_COMPOSITE_1\" value=\"247\" />\n        <enum name=\"KEYCODE_TV_INPUT_COMPOSITE_2\" value=\"248\" />\n        <enum name=\"KEYCODE_TV_INPUT_COMPONENT_1\" value=\"249\" />\n        <enum name=\"KEYCODE_TV_INPUT_COMPONENT_2\" value=\"250\" />\n        <enum name=\"KEYCODE_TV_INPUT_VGA_1\" value=\"251\" />\n        <enum name=\"KEYCODE_TV_AUDIO_DESCRIPTION\" value=\"252\" />\n        <enum name=\"KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP\" value=\"253\" />\n        <enum name=\"KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN\" value=\"254\" />\n        <enum name=\"KEYCODE_TV_ZOOM_MODE\" value=\"255\" />\n        <enum name=\"KEYCODE_TV_CONTENTS_MENU\" value=\"256\" />\n        <enum name=\"KEYCODE_TV_MEDIA_CONTEXT_MENU\" value=\"257\" />\n        <enum name=\"KEYCODE_TV_TIMER_PROGRAMMING\" value=\"258\" />\n        <enum name=\"KEYCODE_HELP\" value=\"259\" />\n        <enum name=\"KEYCODE_NAVIGATE_PREVIOUS\" value=\"260\" />\n        <enum name=\"KEYCODE_NAVIGATE_NEXT\" value=\"261\" />\n        <enum name=\"KEYCODE_NAVIGATE_IN\" value=\"262\" />\n        <enum name=\"KEYCODE_NAVIGATE_OUT\" value=\"263\" />\n        <enum name=\"KEYCODE_STEM_PRIMARY\" value=\"264\" />\n        <enum name=\"KEYCODE_STEM_1\" value=\"265\" />\n        <enum name=\"KEYCODE_STEM_2\" value=\"266\" />\n        <enum name=\"KEYCODE_STEM_3\" value=\"267\" />\n        <enum name=\"KEYCODE_DPAD_UP_LEFT\" value=\"268\" />\n        <enum name=\"KEYCODE_DPAD_DOWN_LEFT\" value=\"269\" />\n        <enum name=\"KEYCODE_DPAD_UP_RIGHT\" value=\"270\" />\n        <enum name=\"KEYCODE_DPAD_DOWN_RIGHT\" value=\"271\" />\n        <enum name=\"KEYCODE_MEDIA_SKIP_FORWARD\" value=\"272\" />\n        <enum name=\"KEYCODE_MEDIA_SKIP_BACKWARD\" value=\"273\" />\n        <enum name=\"KEYCODE_MEDIA_STEP_FORWARD\" value=\"274\" />\n        <enum name=\"KEYCODE_MEDIA_STEP_BACKWARD\" value=\"275\" />\n        <enum name=\"KEYCODE_SOFT_SLEEP\" value=\"276\" />\n        <enum name=\"KEYCODE_CUT\" value=\"277\" />\n        <enum name=\"KEYCODE_COPY\" value=\"278\" />\n        <enum name=\"KEYCODE_PASTE\" value=\"279\" />\n        <enum name=\"KEYCODE_SYSTEM_NAVIGATION_UP\" value=\"280\" />\n        <enum name=\"KEYCODE_SYSTEM_NAVIGATION_DOWN\" value=\"281\" />\n        <enum name=\"KEYCODE_SYSTEM_NAVIGATION_LEFT\" value=\"282\" />\n        <enum name=\"KEYCODE_SYSTEM_NAVIGATION_RIGHT\" value=\"283\" />\n        <enum name=\"KEYCODE_ALL_APPS\" value=\"284\" />\n        <enum name=\"KEYCODE_REFRESH\" value=\"285\" />\n        <enum name=\"KEYCODE_THUMBS_UP\" value=\"286\" />\n        <enum name=\"KEYCODE_THUMBS_DOWN\" value=\"287\" />\n        <enum name=\"KEYCODE_PROFILE_SWITCH\" value=\"288\" />\n    </attr>\n\n    <!-- ***************************************************************** -->\n    <!-- These define collections of attributes that can are with classes. -->\n    <!-- ***************************************************************** -->\n\n    <!-- ========================== -->\n    <!-- Special attribute classes. -->\n    <!-- ========================== -->\n    <eat-comment />\n\n    <!-- The set of attributes that describe a Windows's theme. -->\n    <declare-styleable name=\"Window\">\n        <attr name=\"windowBackground\" />\n        <attr name=\"windowBackgroundFallback\" />\n        <attr name=\"windowBackgroundBlurRadius\" />\n        <attr name=\"windowContentOverlay\" />\n        <attr name=\"windowFrame\" />\n        <attr name=\"windowNoTitle\" />\n        <attr name=\"windowFullscreen\" />\n        <attr name=\"windowOverscan\" />\n        <attr name=\"windowIsFloating\" />\n        <attr name=\"windowIsTranslucent\" />\n        <attr name=\"windowShowWallpaper\" />\n        <attr name=\"windowAnimationStyle\" />\n        <attr name=\"windowSoftInputMode\" />\n        <attr name=\"windowDisablePreview\" />\n        <attr name=\"windowNoDisplay\" />\n        <attr name=\"textColor\" />\n        <attr name=\"backgroundDimEnabled\" />\n        <attr name=\"backgroundDimAmount\" />\n        <attr name=\"windowBlurBehindEnabled\" />\n        <attr name=\"windowBlurBehindRadius\" />\n        <attr name=\"windowActionBar\" />\n        <attr name=\"windowActionModeOverlay\" />\n        <attr name=\"windowActionBarOverlay\" />\n        <attr name=\"windowEnableSplitTouch\" />\n        <attr name=\"windowCloseOnTouchOutside\" />\n        <attr name=\"windowTranslucentStatus\" />\n        <attr name=\"windowTranslucentNavigation\" />\n        <attr name=\"windowContentTransitions\" />\n        <attr name=\"windowActivityTransitions\" />\n        <attr name=\"windowContentTransitionManager\" />\n        <attr name=\"windowActionBarFullscreenDecorLayout\" />\n\n        <!-- The minimum width the window is allowed to be, along the major\n             axis of the screen.  That is, when in landscape.  Can be either\n             an absolute dimension or a fraction of the screen size in that\n             dimension. -->\n        <attr name=\"windowMinWidthMajor\" format=\"dimension|fraction\" />\n        <!-- The minimum width the window is allowed to be, along the minor\n             axis of the screen.  That is, when in portrait.  Can be either\n             an absolute dimension or a fraction of the screen size in that\n             dimension. -->\n        <attr name=\"windowMinWidthMinor\" format=\"dimension|fraction\" />\n\n        <!-- A fixed width for the window along the major axis of the screen,\n             that is, when in landscape. Can be either an absolute dimension\n             or a fraction of the screen size in that dimension. -->\n        <attr name=\"windowFixedWidthMajor\" format=\"dimension|fraction\" />\n        <!-- A fixed height for the window along the minor axis of the screen,\n             that is, when in landscape. Can be either an absolute dimension\n             or a fraction of the screen size in that dimension. -->\n        <attr name=\"windowFixedHeightMinor\" format=\"dimension|fraction\" />\n\n        <!-- A fixed width for the window along the minor axis of the screen,\n             that is, when in portrait. Can be either an absolute dimension\n             or a fraction of the screen size in that dimension. -->\n        <attr name=\"windowFixedWidthMinor\" format=\"dimension|fraction\" />\n        <!-- A fixed height for the window along the major axis of the screen,\n             that is, when in portrait. Can be either an absolute dimension\n             or a fraction of the screen size in that dimension. -->\n        <attr name=\"windowFixedHeightMajor\" format=\"dimension|fraction\" />\n        <attr name=\"windowOutsetBottom\" format=\"dimension\" />\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used to move Views into the initial Window's content Scene. Corresponds to\n             {@link android.view.Window#setEnterTransition(android.transition.Transition)}. -->\n        <attr name=\"windowEnterTransition\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used to move Views out of the scene when the Window is\n             preparing to close. Corresponds to\n             {@link android.view.Window#setReturnTransition(android.transition.Transition)}. -->\n        <attr name=\"windowReturnTransition\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used to move Views out of the Window's content Scene when launching a new Activity.\n             Corresponds to\n             {@link android.view.Window#setExitTransition(android.transition.Transition)}. -->\n        <attr name=\"windowExitTransition\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used to move Views in to the scene when returning from a previously-started Activity.\n             Corresponds to\n             {@link android.view.Window#setReenterTransition(android.transition.Transition)}. -->\n        <attr name=\"windowReenterTransition\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used to move shared elements transferred into the Window's initial content Scene.\n             Corresponds to {@link android.view.Window#setSharedElementEnterTransition(\n             android.transition.Transition)}. -->\n        <attr name=\"windowSharedElementEnterTransition\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used to move shared elements transferred back to a calling Activity.\n             Corresponds to {@link android.view.Window#setSharedElementReturnTransition(\n             android.transition.Transition)}. -->\n        <attr name=\"windowSharedElementReturnTransition\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used when starting a new Activity to move shared elements prior to transferring\n             to the called Activity.\n             Corresponds to {@link android.view.Window#setSharedElementExitTransition(\n             android.transition.Transition)}. -->\n        <attr name=\"windowSharedElementExitTransition\"/>\n\n        <!-- Reference to a Transition XML resource defining the desired Transition\n             used for shared elements transferred back to a calling Activity.\n             Corresponds to {@link android.view.Window#setSharedElementReenterTransition(\n             android.transition.Transition)}. -->\n        <attr name=\"windowSharedElementReenterTransition\"/>\n\n\n        <!-- Flag indicating whether this Window's transition should overlap with\n             the exiting transition of the calling Activity. Corresponds to\n             {@link android.view.Window#setAllowEnterTransitionOverlap(boolean)}. -->\n        <attr name=\"windowAllowEnterTransitionOverlap\"/>\n\n        <!-- Flag indicating whether this Window's transition should overlap with\n             the exiting transition of the called Activity when the called Activity\n             finishes. Corresponds to\n             {@link android.view.Window#setAllowReturnTransitionOverlap(boolean)}. -->\n        <attr name=\"windowAllowReturnTransitionOverlap\"/>\n\n        <!-- Indicates whether or not shared elements should use an overlay\n             during transitions. The default value is true. -->\n        <attr name=\"windowSharedElementsUseOverlay\"/>\n\n        <!-- Flag indicating whether this Window is responsible for drawing the background for the\n             system bars. If true and the window is not floating, the system bars are drawn with a\n             transparent background and the corresponding areas in this window are filled with the\n             colors specified in {@link android.R.attr#statusBarColor} and\n             {@link android.R.attr#navigationBarColor}. Corresponds to\n             {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS}. -->\n        <attr name=\"windowDrawsSystemBarBackgrounds\" format=\"boolean\" />\n\n        <!-- The color for the status bar. If the color is not opaque, consider setting\n             {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and\n             {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.\n             For this to take effect, the window must be drawing the system bar backgrounds with\n             {@link android.R.attr#windowDrawsSystemBarBackgrounds} and the status bar must not\n             have been requested to be translucent with\n             {@link android.R.attr#windowTranslucentStatus}.\n             Corresponds to {@link android.view.Window#setStatusBarColor(int)}. -->\n        <attr name=\"statusBarColor\" format=\"color\" />\n\n        <!-- The color for the navigation bar. If the color is not opaque, consider setting\n             {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and\n             {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.\n             For this to take effect, the window must be drawing the system bar backgrounds with\n             {@link android.R.attr#windowDrawsSystemBarBackgrounds} and the navigation bar must not\n             have been requested to be translucent with\n             {@link android.R.attr#windowTranslucentNavigation}.\n             Corresponds to {@link android.view.Window#setNavigationBarColor(int)}. -->\n        <attr name=\"navigationBarColor\" format=\"color\" />\n\n        <!-- Shows a thin line of the specified color between the navigation bar and the app\n             content.\n             <p>For this to take effect, the window must be drawing the system bar backgrounds with\n             {@link android.R.attr#windowDrawsSystemBarBackgrounds} and the navigation bar must not\n             have been requested to be translucent with\n             {@link android.R.attr#windowTranslucentNavigation}.\n             Corresponds to {@link android.view.Window#setNavigationBarDividerColor(int)}. -->\n        <attr name=\"navigationBarDividerColor\" format=\"color\" />\n\n        <!-- Sets whether the system should ensure that the status bar has enough\n             contrast when a fully transparent background is requested.\n\n             <p>If set to this value, the system will determine whether a scrim is necessary\n             to ensure that the status bar has enough contrast with the contents of\n             this app, and set an appropriate effective bar background color accordingly.\n\n             <p>When the status bar color has a non-zero alpha value, the value of this\n             attribute has no effect.\n\n             <p>If the app does not target at least {@link android.os.Build.VERSION_CODES#Q Q},\n             this attribute is ignored.\n\n             @see android.view.Window#setStatusBarContrastEnforced -->\n        <attr name=\"enforceStatusBarContrast\" format=\"boolean\" />\n\n        <!-- Sets whether the system should ensure that the navigation bar has enough\n             contrast when a fully transparent background is requested.\n\n             <p>If set to this value, the system will determine whether a scrim is necessary\n             to ensure that the navigation bar has enough contrast with the contents of\n             this app, and set an appropriate effective bar background color accordingly.\n\n             <p>When the navigation bar color has a non-zero alpha value, the value of this\n             attribute has no effect.\n\n             <p>If the app does not target at least {@link android.os.Build.VERSION_CODES#Q Q},\n             this attribute is ignored.\n\n             @see android.view.Window#setNavigationBarContrastEnforced -->\n        <attr name=\"enforceNavigationBarContrast\" format=\"boolean\" />\n\n        <!-- The duration, in milliseconds, of the window background fade duration\n             when transitioning into or away from an Activity when called with an\n             Activity Transition. Corresponds to\n             {@link android.view.Window#setTransitionBackgroundFadeDuration(long)}. -->\n        <attr name=\"windowTransitionBackgroundFadeDuration\" />\n\n        <!-- Elevation to use for the window. -->\n        <attr name=\"windowElevation\" format=\"dimension\" />\n\n        <!-- Whether to clip window content to the outline of the window background. -->\n        <attr name=\"windowClipToOutline\" format=\"boolean\" />\n\n        <!-- If set, the status bar will be drawn such that it is compatible with a light\n             status bar background.\n             <p>For this to take effect, the window must be drawing the system bar backgrounds with\n             {@link android.R.attr#windowDrawsSystemBarBackgrounds} and the status bar must not\n             have been requested to be translucent with\n             {@link android.R.attr#windowTranslucentStatus}.\n             Corresponds to setting {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_STATUS_BAR} on\n             the decor view. -->\n        <attr name=\"windowLightStatusBar\" format=\"boolean\" />\n\n        <!-- Reference to a drawable to be used as the splash screen content of the window. This\n             drawable will be placed on top of the {@link android.R.attr#windowBackground} with its\n             bounds inset by the system bars. If the drawable should not be inset by the system\n             bars, use a fullscreen theme.\n             <p>\n             Note that even if no splashscreen content is set on the theme, the system may still\n             show a splash screen using the other attributes on the theme, like the\n             {@link android.R.attr#windowBackground}.\n             {@deprecated Use windowSplashscreenAnimatedIcon instead.}\n             -->\n        <attr name=\"windowSplashscreenContent\" format=\"reference\" />\n\n        <!-- If set, the navigation bar will be drawn such that it is compatible with a light\n             navigation bar background.\n             <p>For this to take effect, the window must be drawing the system bar backgrounds with\n             {@link android.R.attr#windowDrawsSystemBarBackgrounds} and the navigation bar must not\n             have been requested to be translucent with\n             {@link android.R.attr#windowTranslucentNavigation}.\n             Corresponds to setting {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} on\n             the decor view. -->\n        <attr name=\"windowLightNavigationBar\" format=\"boolean\" />\n\n        <!-- Controls how the window is laid out if there is a {@code DisplayCutout}.\n        <p>\n        Defaults to {@code default}.\n        <p>\n        See also\n        {@link android.view.WindowManager.LayoutParams#layoutInDisplayCutoutMode\n                WindowManager.LayoutParams.layoutInDisplayCutoutMode},\n        {@link android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT},\n        {@link android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES},\n        {@link android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER},\n        and {@link android.view.DisplayCutout DisplayCutout}\n        -->\n        <attr name=\"windowLayoutInDisplayCutoutMode\">\n            <!-- <p>\n            The window is allowed to extend into the <code>DisplayCutout</code> area, only if\n            the <code>DisplayCutout</code> is fully contained within a system bar. Otherwise, the\n            window is laid out such that it does not overlap with the <code>DisplayCutout</code>\n            area.\n            <p>\n            Corresponds to <code>LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT</code>.\n            -->\n            <enum name=\"default\" value=\"0\" />\n            <!-- <p>\n            The window is always allowed to extend into the <code>DisplayCutout</code> areas on the\n            short edges of the screen even if fullscreen or in landscape.\n            The window will never extend into a <code>DisplayCutout</code> area on the long edges of\n            the screen.\n            <p>\n            The window must make sure that no important content overlaps with the\n            <code>DisplayCutout</code>.\n            <p>\n            Corresponds to <code>LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES</code>.\n            -->\n            <enum name=\"shortEdges\" value=\"1\" />\n            <!-- <p>\n            The window is never allowed to overlap with the <code>DisplayCutout</code> area.\n            <p>\n            This should be used with windows that transiently set\n            <code>SYSTEM_UI_FLAG_FULLSCREEN</code> to avoid a relayout of the window when the\n            flag is set or cleared.\n            <p>\n            Corresponds to <code>LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER</code>.\n            -->\n            <enum name=\"never\" value=\"2\" />\n            <!-- <p>\n            The window is always allowed to extend into the <code>DisplayCutout</code> areas on the\n            all edges of the screen.\n            <p>\n            The window must make sure that no important content overlaps with the\n            <code>DisplayCutout</code>.\n            <p>\n            Corresponds to <code>LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS</code>.\n            -->\n            <enum name=\"always\" value=\"3\" />\n        </attr>\n\n        <!-- The background color for the splash screen, if not specify then system will\n             calculate from windowBackground. -->\n        <attr name=\"windowSplashScreenBackground\" format=\"color\"/>\n\n        <!-- Replace an icon in the center of the starting window, if the object is animated\n             and drawable(e.g. AnimationDrawable, AnimatedVectorDrawable), then it will also\n             play the animation while showing the starting window. -->\n        <attr name=\"windowSplashScreenAnimatedIcon\" format=\"reference\"/>\n        <!-- The duration, in milliseconds, of the window splash screen icon animation duration\n             when playing the splash screen starting window. The maximum animation duration should\n             be limited below 1000ms. -->\n        <attr name=\"windowSplashScreenAnimationDuration\" format=\"integer\"/>\n\n        <!-- Place an drawable image in the bottom of the starting window, it can be used to\n             represent the branding of the application. -->\n        <attr name=\"windowSplashScreenBrandingImage\" format=\"reference\"/>\n        <!-- Set a background behind the splash screen icon. This is useful if there is not enough\n             contrast between the window background and the icon. Note the shape would also be\n             masking like an icon. -->\n        <attr name=\"windowSplashScreenIconBackgroundColor\" format=\"color\"/>\n    </declare-styleable>\n\n    <!-- The set of attributes that describe a AlertDialog's theme. -->\n    <declare-styleable name=\"AlertDialog\">\n        <attr name=\"fullDark\" format=\"reference|color\" />\n        <attr name=\"topDark\" format=\"reference|color\" />\n        <attr name=\"centerDark\" format=\"reference|color\" />\n        <attr name=\"bottomDark\" format=\"reference|color\" />\n        <attr name=\"fullBright\" format=\"reference|color\" />\n        <attr name=\"topBright\" format=\"reference|color\" />\n        <attr name=\"centerBright\" format=\"reference|color\" />\n        <attr name=\"bottomBright\" format=\"reference|color\" />\n        <attr name=\"bottomMedium\" format=\"reference|color\" />\n        <attr name=\"centerMedium\" format=\"reference|color\" />\n        <attr name=\"layout\" />\n        <attr name=\"buttonPanelSideLayout\" format=\"reference\" />\n        <attr name=\"listLayout\" format=\"reference\" />\n        <attr name=\"multiChoiceItemLayout\" format=\"reference\" />\n        <attr name=\"singleChoiceItemLayout\" format=\"reference\" />\n        <attr name=\"listItemLayout\" format=\"reference\" />\n        <attr name=\"progressLayout\" format=\"reference\" />\n        <attr name=\"horizontalProgressLayout\" format=\"reference\" />\n        <!-- @hide Not ready for public use. -->\n        <attr name=\"showTitle\" format=\"boolean\" />\n        <!-- @hide Whether fullDark, etc. should use default values if null. -->\n        <attr name=\"needsDefaultBackgrounds\" format=\"boolean\" />\n        <!-- @hide Workaround until we replace AlertController with custom layout. -->\n        <attr name=\"controllerType\">\n            <!-- The default controller. -->\n            <enum name=\"normal\" value=\"0\" />\n            <!-- Controller for micro specific layout. -->\n            <enum name=\"micro\" value=\"1\" />\n        </attr>\n        <!-- @hide Offset when scrolling to a selection. -->\n        <attr name=\"selectionScrollOffset\" format=\"dimension\" />\n    </declare-styleable>\n\n    <!-- @hide -->\n    <declare-styleable name=\"ButtonBarLayout\">\n        <!-- Whether to automatically stack the buttons when there is not\n             enough space to lay them out side-by-side. -->\n        <attr name=\"allowStacking\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Fragment animation class attributes. -->\n    <declare-styleable name=\"FragmentAnimation\">\n        <attr name=\"fragmentOpenEnterAnimation\" format=\"reference\" />\n        <attr name=\"fragmentOpenExitAnimation\" format=\"reference\" />\n        <attr name=\"fragmentCloseEnterAnimation\" format=\"reference\" />\n        <attr name=\"fragmentCloseExitAnimation\" format=\"reference\" />\n        <attr name=\"fragmentFadeEnterAnimation\" format=\"reference\" />\n        <attr name=\"fragmentFadeExitAnimation\" format=\"reference\" />\n    </declare-styleable>\n\n    <!-- Window animation class attributes. -->\n    <declare-styleable name=\"WindowAnimation\">\n        <!-- The animation used when a window is being added. -->\n        <attr name=\"windowEnterAnimation\" format=\"reference\" />\n        <!-- The animation used when a window is being removed. -->\n        <attr name=\"windowExitAnimation\" format=\"reference\" />\n        <!-- The animation used when a window is going from INVISIBLE to VISIBLE. -->\n        <attr name=\"windowShowAnimation\" format=\"reference\" />\n        <!-- The animation used when a window is going from VISIBLE to INVISIBLE. -->\n        <attr name=\"windowHideAnimation\" format=\"reference\" />\n\n        <!--  When opening a new activity, this is the animation that is\n              run on the next activity (which is entering the screen). -->\n        <attr name=\"activityOpenEnterAnimation\" format=\"reference\" />\n        <!--  When opening a new activity, this is the animation that is\n              run on the previous activity (which is exiting the screen). -->\n        <attr name=\"activityOpenExitAnimation\" format=\"reference\" />\n        <!--  When closing the current activity, this is the animation that is\n              run on the next activity (which is entering the screen). -->\n        <attr name=\"activityCloseEnterAnimation\" format=\"reference\" />\n        <!--  When closing the current activity, this is the animation that is\n              run on the current activity (which is exiting the screen). -->\n        <attr name=\"activityCloseExitAnimation\" format=\"reference\" />\n        <!--  When opening an activity in a new task, this is the animation that is\n              run on the activity of the new task (which is entering the screen). -->\n        <attr name=\"taskOpenEnterAnimation\" format=\"reference\" />\n        <!--  When opening an activity in a new task, this is the animation that is\n              run on the activity of the old task (which is exiting the screen). -->\n        <attr name=\"taskOpenExitAnimation\" format=\"reference\" />\n        <!--  When opening an activity in a new task using Intent/FLAG_ACTIVITY_LAUNCH_BEHIND,\n              this is the animation that is run on the activity of the new task (which is\n              entering the screen and then leaving). -->\n        <attr name=\"launchTaskBehindTargetAnimation\" format=\"reference\" />\n        <!--  When opening an activity in a new task using Intent.FLAG_ACTIVITY_LAUNCH_BEHIND,\n              this is the animation that is run on the activity of the old task (which is\n              already on the screen and then stays on). -->\n        <attr name=\"launchTaskBehindSourceAnimation\" format=\"reference\" />\n        <!--  When closing the last activity of a task, this is the animation that is\n              run on the activity of the next task (which is entering the screen). -->\n        <attr name=\"taskCloseEnterAnimation\" format=\"reference\" />\n        <!--  When opening an activity in a new task, this is the animation that is\n              run on the activity of the old task (which is exiting the screen). -->\n        <attr name=\"taskCloseExitAnimation\" format=\"reference\" />\n        <!--  When bringing an existing task to the foreground, this is the\n              animation that is run on the top activity of the task being brought\n              to the foreground (which is entering the screen). -->\n        <attr name=\"taskToFrontEnterAnimation\" format=\"reference\" />\n        <!--  When bringing an existing task to the foreground, this is the\n              animation that is run on the current foreground activity\n              (which is exiting the screen). -->\n        <attr name=\"taskToFrontExitAnimation\" format=\"reference\" />\n        <!--  When sending the current task to the background, this is the\n              animation that is run on the top activity of the task behind\n              it (which is entering the screen). -->\n        <attr name=\"taskToBackEnterAnimation\" format=\"reference\" />\n        <!--  When sending the current task to the background, this is the\n              animation that is run on the top activity of the current task\n              (which is exiting the screen). -->\n        <attr name=\"taskToBackExitAnimation\" format=\"reference\" />\n\n        <!--  When opening a new activity that shows the wallpaper, while\n              currently not showing the wallpaper, this is the animation that\n              is run on the new wallpaper activity (which is entering the screen). -->\n        <attr name=\"wallpaperOpenEnterAnimation\" format=\"reference\" />\n        <!--  When opening a new activity that shows the wallpaper, while\n              currently not showing the wallpaper, this is the animation that\n              is run on the current activity (which is exiting the screen). -->\n        <attr name=\"wallpaperOpenExitAnimation\" format=\"reference\" />\n        <!--  When opening a new activity that hides the wallpaper, while\n              currently showing the wallpaper, this is the animation that\n              is run on the new activity (which is entering the screen). -->\n        <attr name=\"wallpaperCloseEnterAnimation\" format=\"reference\" />\n        <!--  When opening a new activity that hides the wallpaper, while\n              currently showing the wallpaper, this is the animation that\n              is run on the old wallpaper activity (which is exiting the screen). -->\n        <attr name=\"wallpaperCloseExitAnimation\" format=\"reference\" />\n\n        <!--  When opening a new activity that is on top of the wallpaper\n              when the current activity is also on top of the wallpaper,\n              this is the animation that is run on the new activity\n              (which is entering the screen).  The wallpaper remains\n              static behind the animation. -->\n        <attr name=\"wallpaperIntraOpenEnterAnimation\" format=\"reference\" />\n        <!--  When opening a new activity that is on top of the wallpaper\n              when the current activity is also on top of the wallpaper,\n              this is the animation that is run on the current activity\n              (which is exiting the screen).  The wallpaper remains\n              static behind the animation. -->\n        <attr name=\"wallpaperIntraOpenExitAnimation\" format=\"reference\" />\n        <!--  When closing a foreround activity that is on top of the wallpaper\n              when the previous activity is also on top of the wallpaper,\n              this is the animation that is run on the previous activity\n              (which is entering the screen).  The wallpaper remains\n              static behind the animation. -->\n        <attr name=\"wallpaperIntraCloseEnterAnimation\" format=\"reference\" />\n        <!--  When closing a foreround activity that is on top of the wallpaper\n              when the previous activity is also on top of the wallpaper,\n              this is the animation that is run on the current activity\n              (which is exiting the screen).  The wallpaper remains\n              static behind the animation. -->\n        <attr name=\"wallpaperIntraCloseExitAnimation\" format=\"reference\" />\n\n        <!--  When opening a new activity from a RemoteViews, this is the\n              animation that is run on the next activity (which is entering the\n              screen). Requires config_overrideRemoteViewsActivityTransition to\n              be true. -->\n        <attr name=\"activityOpenRemoteViewsEnterAnimation\" format=\"reference\" />\n\n    </declare-styleable>\n\n    <!-- ============================= -->\n    <!-- View package class attributes -->\n    <!-- ============================= -->\n    <eat-comment />\n\n    <!-- Removed View attributes without a specified format (b/131100106) -->\n    <attr name=\"__removed3\" />\n    <attr name=\"__removed4\" />\n    <attr name=\"__removed5\" />\n    <attr name=\"__removed6\" />\n\n    <!-- Attributes that can be used with {@link android.view.View} or\n         any of its subclasses.  Also see {@link #ViewGroup_Layout} for\n         attributes that are processed by the view's parent. -->\n    <declare-styleable name=\"View\">\n        <!-- Supply an identifier name for this view, to later retrieve it\n             with {@link android.view.View#findViewById View.findViewById()} or\n             {@link android.app.Activity#findViewById Activity.findViewById()}.\n             This must be a\n             resource reference; typically you set this using the\n             <code>@+</code> syntax to create a new ID resources.\n             For example: <code>android:id=\"@+id/my_id\"</code> which\n             allows you to later retrieve the view\n             with <code>findViewById(R.id.my_id)</code>. -->\n        <attr name=\"id\" format=\"reference\" />\n\n        <!-- Supply a tag for this view containing a String, to be retrieved\n             later with {@link android.view.View#getTag View.getTag()} or\n             searched for with {@link android.view.View#findViewWithTag\n             View.findViewWithTag()}.  It is generally preferable to use\n             IDs (through the android:id attribute) instead of tags because\n             they are faster and allow for compile-time type checking. -->\n        <attr name=\"tag\" format=\"string\" />\n\n        <!-- The initial horizontal scroll offset, in pixels.-->\n        <attr name=\"scrollX\" format=\"dimension\" />\n\n        <!-- The initial vertical scroll offset, in pixels. -->\n        <attr name=\"scrollY\" format=\"dimension\" />\n\n        <!-- A drawable to use as the background.  This can be either a reference\n             to a full drawable resource (such as a PNG image, 9-patch,\n             XML state list description, etc), or a solid color such as \"#ff000000\"\n            (black). -->\n        <attr name=\"background\" format=\"reference|color\" />\n\n        <!-- Sets the padding, in pixels, of all four edges. Padding is defined as\n             space between the edges of the view and the view's content. This value will take\n             precedence over any of the edge-specific values (paddingLeft, paddingTop,\n             paddingRight, paddingBottom, paddingHorizontal and paddingVertical), but will\n             not override paddingStart or paddingEnd, if set. A view's size\n             will include its padding. If a {@link android.R.attr#background}\n             is provided, the padding will initially be set to that (0 if the\n             drawable does not have padding). Explicitly setting a padding value\n             will override the corresponding padding found in the background. -->\n        <attr name=\"padding\" format=\"dimension\" />\n        <!-- Sets the padding, in pixels, of the left and right edges; see\n             {@link android.R.attr#padding}. This value will take precedence over\n             paddingLeft and paddingRight, but not paddingStart or paddingEnd (if set). -->\n        <attr name=\"paddingHorizontal\" format=\"dimension\" />\n        <!-- Sets the padding, in pixels, of the top and bottom edges; see\n             {@link android.R.attr#padding}. This value will take precedence over\n             paddingTop and paddingBottom, if set. -->\n        <attr name=\"paddingVertical\" format=\"dimension\" />\n        <!-- Sets the padding, in pixels, of the left edge; see {@link android.R.attr#padding}. -->\n        <attr name=\"paddingLeft\" format=\"dimension\" />\n        <!-- Sets the padding, in pixels, of the top edge; see {@link android.R.attr#padding}. -->\n        <attr name=\"paddingTop\" format=\"dimension\" />\n        <!-- Sets the padding, in pixels, of the right edge; see {@link android.R.attr#padding}. -->\n        <attr name=\"paddingRight\" format=\"dimension\" />\n        <!-- Sets the padding, in pixels, of the bottom edge; see {@link android.R.attr#padding}. -->\n        <attr name=\"paddingBottom\" format=\"dimension\" />\n        <!-- Sets the padding, in pixels, of the start edge; see {@link android.R.attr#padding}. -->\n        <attr name=\"paddingStart\" format=\"dimension\" />\n        <!-- Sets the padding, in pixels, of the end edge; see {@link android.R.attr#padding}. -->\n        <attr name=\"paddingEnd\" format=\"dimension\" />\n\n        <!-- Controls whether a view can take focus.  By default, this is \"auto\" which lets the\n             framework determine whether a user can move focus to a view.  By setting this attribute\n             to true the view is allowed to take focus. By setting it to \"false\" the view will not\n             take focus. This value does not impact the behavior of\n             directly calling {@link android.view.View#requestFocus}, which will\n             always request focus regardless of this view.  It only impacts where\n             focus navigation will try to move focus. -->\n        <attr name=\"focusable\" format=\"boolean|enum\">\n            <enum name=\"auto\" value=\"0x00000010\" />\n        </attr>\n\n        <attr name=\"__removed3\" />\n        <attr name=\"__removed4\" />\n        <attr name=\"__removed5\" />\n\n        <!-- Describes the content of a view so that a autofill service can fill in the appropriate\n             data. Multiple hints can be combined in a comma separated list or an array of strings\n             to mean e.g. emailAddress or postalAddress. -->\n        <attr name=\"autofillHints\" format=\"string|reference\" />\n\n        <!-- Hints the Android System whether the view node associated with this View should be\n             included in a view structure used for autofill purposes. -->\n        <attr name=\"importantForAutofill\">\n            <!-- Let the Android System use its heuristics to determine if the view is important for autofill. -->\n            <flag name=\"auto\" value=\"0\" />\n            <!-- Hint the Android System that this view is important for autofill,\n                  and its children (if any) will be traversed.. -->\n            <flag name=\"yes\" value=\"0x1\" />\n            <!-- Hint the Android System that this view is *not* important for autofill,\n                  but its children (if any) will be traversed.. -->\n            <flag name=\"no\" value=\"0x2\" />\n            <!-- Hint the Android System that this view is important for autofill,\n                 but its children (if any) will not be traversed. -->\n            <flag name=\"yesExcludeDescendants\" value=\"0x4\" />\n            <!-- Hint the Android System that this view is *not* important for autofill,\n                 and its children (if any) will not be traversed. -->\n            <flag name=\"noExcludeDescendants\" value=\"0x8\" />\n        </attr>\n\n        <!-- Hints the Android System whether the view node associated with this View should be\n             use for content capture purposes. -->\n        <attr name=\"importantForContentCapture\">\n            <!-- Let the Android System use its heuristics to determine if the view is important for content capture. -->\n            <flag name=\"auto\" value=\"0\" />\n            <!-- Hint the Android System that this view is important for content capture,\n                  and its children (if any) will be traversed.. -->\n            <flag name=\"yes\" value=\"0x1\" />\n            <!-- Hint the Android System that this view is *not* important for content capture,\n                  but its children (if any) will be traversed.. -->\n            <flag name=\"no\" value=\"0x2\" />\n            <!-- Hint the Android System that this view is important for content capture,\n                 but its children (if any) will not be traversed. -->\n            <flag name=\"yesExcludeDescendants\" value=\"0x4\" />\n            <!-- Hint the Android System that this view is *not* important for content capture,\n                 and its children (if any) will not be traversed. -->\n            <flag name=\"noExcludeDescendants\" value=\"0x8\" />\n        </attr>\n\n        <!-- Hints the Android System whether the this View should be considered a scroll capture target. -->\n        <attr name=\"scrollCaptureHint\">\n            <!-- Let the Android System  determine if the view can be a scroll capture target. -->\n            <flag name=\"auto\" value=\"0\" />\n            <!-- Hint the Android System that this view is a likely target. If capable, it will\n                 be ranked above other views without this flag. -->\n            <flag name=\"include\" value=\"0x1\" />\n            <!-- Hint the Android System that this view should never be considered a scroll capture\n                 target. -->\n            <flag name=\"exclude\" value=\"0x2\" />\n            <!-- Hint the Android System that this view's children should not be examined and should\n                 be excluded as a scroll capture target. -->\n            <flag name=\"excludeDescendants\" value=\"0x4\" />\n        </attr>\n\n        <!-- Boolean that controls whether a view can take focus while in touch mode.\n             If this is true for a view, that view can gain focus when clicked on, and can keep\n             focus if another view is clicked on that doesn't have this attribute set to true. -->\n        <attr name=\"focusableInTouchMode\" format=\"boolean\" />\n\n        <!-- Controls the initial visibility of the view.  -->\n        <attr name=\"visibility\">\n            <!-- Visible on screen; the default value. -->\n            <enum name=\"visible\" value=\"0\" />\n            <!-- Not displayed, but taken into account during layout (space is left for it). -->\n            <enum name=\"invisible\" value=\"1\" />\n            <!-- Completely hidden, as if the view had not been added. -->\n            <enum name=\"gone\" value=\"2\" />\n        </attr>\n\n        <!-- Boolean internal attribute to adjust view layout based on\n             system windows such as the status bar.\n             If true, adjusts the padding of this view to leave space for the system windows.\n             Will only take effect if this view is in a non-embedded activity. -->\n        <attr name=\"fitsSystemWindows\" format=\"boolean\" />\n\n        <!-- Defines which scrollbars should be displayed on scrolling or not. -->\n        <attr name=\"scrollbars\">\n            <!-- No scrollbar is displayed. -->\n            <flag name=\"none\" value=\"0x00000000\" />\n            <!-- Displays horizontal scrollbar only. -->\n            <flag name=\"horizontal\" value=\"0x00000100\" />\n            <!-- Displays vertical scrollbar only. -->\n            <flag name=\"vertical\" value=\"0x00000200\" />\n        </attr>\n\n        <!-- Controls the scrollbar style and position. The scrollbars can be overlaid or\n             inset. When inset, they add to the padding of the view. And the\n             scrollbars can be drawn inside the padding area or on the edge of\n             the view. For example, if a view has a background drawable and you\n             want to draw the scrollbars inside the padding specified by the\n             drawable, you can use insideOverlay or insideInset. If you want them\n             to appear at the edge of the view, ignoring the padding, then you can\n             use outsideOverlay or outsideInset.-->\n        <attr name=\"scrollbarStyle\">\n            <!-- Inside the padding and overlaid. -->\n            <enum name=\"insideOverlay\" value=\"0x0\" />\n            <!-- Inside the padding and inset. -->\n            <enum name=\"insideInset\" value=\"0x01000000\" />\n            <!-- Edge of the view and overlaid. -->\n            <enum name=\"outsideOverlay\" value=\"0x02000000\" />\n            <!-- Edge of the view and inset. -->\n            <enum name=\"outsideInset\" value=\"0x03000000\" />\n        </attr>\n\n        <!-- Set this if the view will serve as a scrolling container, meaning\n             that it can be resized to shrink its overall window so that there\n             will be space for an input method.  If not set, the default\n             value will be true if \"scrollbars\" has the vertical scrollbar\n             set, else it will be false. -->\n        <attr name=\"isScrollContainer\" format=\"boolean\" />\n\n          <!-- Defines whether to fade out scrollbars when they are not in use. -->\n         <attr name=\"fadeScrollbars\" format=\"boolean\" />\n         <!-- Defines the delay in milliseconds that a scrollbar takes to fade out. -->\n         <attr name=\"scrollbarFadeDuration\" format=\"integer\" />\n         <!-- Defines the delay in milliseconds that a scrollbar waits before fade out. -->\n        <attr name=\"scrollbarDefaultDelayBeforeFade\" format=\"integer\" />\n        <!-- Sets the width of vertical scrollbars and height of horizontal scrollbars. -->\n        <attr name=\"scrollbarSize\" format=\"dimension\" />\n        <!-- Defines the horizontal scrollbar thumb drawable. -->\n        <attr name=\"scrollbarThumbHorizontal\" format=\"reference\" />\n        <!-- Defines the vertical scrollbar thumb drawable. -->\n        <attr name=\"scrollbarThumbVertical\" format=\"reference\" />\n        <!-- Defines the horizontal scrollbar track drawable. -->\n        <attr name=\"scrollbarTrackHorizontal\" format=\"reference\" />\n        <!-- Defines the vertical scrollbar track drawable. -->\n        <attr name=\"scrollbarTrackVertical\" format=\"reference\" />\n        <!-- Defines whether the horizontal scrollbar track should always be drawn. -->\n        <attr name=\"scrollbarAlwaysDrawHorizontalTrack\" format=\"boolean\" />\n        <!-- Defines whether the vertical scrollbar track should always be drawn. -->\n        <attr name=\"scrollbarAlwaysDrawVerticalTrack\" format=\"boolean\" />\n\n        <!-- This attribute is ignored in API level 14\n             ({@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}) and higher.\n             Using fading edges may introduce noticeable performance\n             degradations and should be used only when required by the application's\n             visual design. To request fading edges with API level 14 and above,\n             use the <code>android:requiresFadingEdge</code> attribute instead. -->\n        <attr name=\"fadingEdge\">\n            <!-- No edge is faded. -->\n            <flag name=\"none\" value=\"0x00000000\" />\n            <!-- Fades horizontal edges only. -->\n            <flag name=\"horizontal\" value=\"0x00001000\" />\n            <!-- Fades vertical edges only. -->\n            <flag name=\"vertical\" value=\"0x00002000\" />\n        </attr>\n        <!-- Defines which edges should be faded on scrolling. -->\n        <attr name=\"requiresFadingEdge\">\n            <!-- No edge is faded. -->\n            <flag name=\"none\" value=\"0x00000000\" />\n            <!-- Fades horizontal edges only. -->\n            <flag name=\"horizontal\" value=\"0x00001000\" />\n            <!-- Fades vertical edges only. -->\n            <flag name=\"vertical\" value=\"0x00002000\" />\n        </attr>\n        <!-- Defines the length of the fading edges. -->\n        <attr name=\"fadingEdgeLength\" format=\"dimension\" />\n\n        <!-- Defines the next view to give focus to when the next focus is\n             {@link android.view.View#FOCUS_LEFT}.\n\n             If the reference refers to a view that does not exist or is part\n             of a hierarchy that is invisible, a {@link java.lang.RuntimeException}\n             will result when the reference is accessed.-->\n        <attr name=\"nextFocusLeft\" format=\"reference\"/>\n\n        <!-- Defines the next view to give focus to when the next focus is\n             {@link android.view.View#FOCUS_RIGHT}\n\n             If the reference refers to a view that does not exist or is part\n             of a hierarchy that is invisible, a {@link java.lang.RuntimeException}\n             will result when the reference is accessed.-->\n        <attr name=\"nextFocusRight\" format=\"reference\"/>\n\n        <!-- Defines the next view to give focus to when the next focus is\n             {@link android.view.View#FOCUS_UP}\n\n             If the reference refers to a view that does not exist or is part\n             of a hierarchy that is invisible, a {@link java.lang.RuntimeException}\n             will result when the reference is accessed.-->\n        <attr name=\"nextFocusUp\" format=\"reference\"/>\n\n        <!-- Defines the next view to give focus to when the next focus is\n             {@link android.view.View#FOCUS_DOWN}\n\n             If the reference refers to a view that does not exist or is part\n             of a hierarchy that is invisible, a {@link java.lang.RuntimeException}\n             will result when the reference is accessed.-->\n        <attr name=\"nextFocusDown\" format=\"reference\"/>\n\n        <!-- Defines the next view to give focus to when the next focus is\n             {@link android.view.View#FOCUS_FORWARD}\n\n             If the reference refers to a view that does not exist or is part\n             of a hierarchy that is invisible, a {@link java.lang.RuntimeException}\n             will result when the reference is accessed.-->\n        <attr name=\"nextFocusForward\" format=\"reference\"/>\n\n        <!-- Defines whether this view reacts to click events. -->\n        <attr name=\"clickable\" format=\"boolean\" />\n\n        <!-- Defines whether this view reacts to long click events. -->\n        <attr name=\"longClickable\" format=\"boolean\" />\n\n        <!--  Defines whether this view reacts to context click events. -->\n        <attr name=\"contextClickable\" format=\"boolean\" />\n\n        <!-- If false, no state will be saved for this view when it is being\n             frozen. The default is true, allowing the view to be saved\n             (however it also must have an ID assigned to it for its\n             state to be saved).  Setting this to false only disables the\n             state for this view, not for its children which may still\n             be saved. -->\n        <attr name=\"saveEnabled\" format=\"boolean\" />\n\n        <!-- Specifies whether to filter touches when the view's window is obscured by\n             another visible window.  When set to true, the view will not receive touches\n             whenever a toast, dialog or other window appears above the view's window.\n             Refer to the {@link android.view.View} security documentation for more details. -->\n        <attr name=\"filterTouchesWhenObscured\" format=\"boolean\" />\n\n        <!-- Defines the quality of translucent drawing caches. This property is used\n             only when the drawing cache is enabled and translucent. The default value is auto.\n             Deprecated: The view drawing cache was largely made obsolete with the introduction of\n             hardware-accelerated rendering in API 11. -->\n        <attr name=\"drawingCacheQuality\">\n            <!-- Lets the framework decide what quality level should be used\n                 for the drawing cache.\n                 Deprecated: The view drawing cache was largely made obsolete with the introduction\n                 of hardware-accelerated rendering in API 11. -->\n            <enum name=\"auto\" value=\"0\" />\n            <!-- Low quality. When set to low quality, the drawing cache uses a lower color\n                 depth, thus losing precision in rendering gradients, but uses less memory.\n                 Deprecated: The view drawing cache was largely made obsolete with the introduction\n                 of hardware-accelerated rendering in API 11. -->\n            <enum name=\"low\" value=\"1\" />\n            <!-- High quality. When set to high quality, the drawing cache uses a higher\n                 color depth but uses more memory.\n                 Deprecated: The view drawing cache was largely made obsolete with the introduction\n                 of hardware-accelerated rendering in API 11. -->\n            <enum name=\"high\" value=\"2\" />\n        </attr>\n\n        <!-- Controls whether the view's window should keep the screen on\n             while visible. -->\n        <attr name=\"keepScreenOn\" format=\"boolean\" />\n\n        <!-- When this attribute is set to true, the view gets its drawable state\n             (focused, pressed, etc.) from its direct parent rather than from itself. -->\n        <attr name=\"duplicateParentState\" format=\"boolean\" />\n\n        <!-- Defines the minimum height of the view. It is not guaranteed\n             the view will be able to achieve this minimum height (for example,\n             if its parent layout constrains it with less available height). -->\n        <attr name=\"minHeight\" />\n\n        <!-- Defines the minimum width of the view. It is not guaranteed\n             the view will be able to achieve this minimum width (for example,\n             if its parent layout constrains it with less available width). -->\n        <attr name=\"minWidth\" />\n\n        <!-- Boolean that controls whether a view should have sound effects\n             enabled for events such as clicking and touching. -->\n        <attr name=\"soundEffectsEnabled\" format=\"boolean\" />\n\n        <!-- Boolean that controls whether a view should have haptic feedback\n             enabled for events such as long presses. -->\n        <attr name=\"hapticFeedbackEnabled\" format=\"boolean\" />\n\n        <!-- Defines text that briefly describes content of the view. This property is used\n             primarily for accessibility. Since some views do not have textual\n             representation this attribute can be used for providing such. -->\n        <attr name=\"contentDescription\" format=\"string\" localization=\"suggested\" />\n\n        <!-- Sets the id of a view before which this one is visited in accessibility traversal.\n             A screen-reader must visit the content of this view before the content of the one\n             it precedes.\n             {@see android.view.View#setAccessibilityTraversalBefore(int)} -->\n        <attr name=\"accessibilityTraversalBefore\" format=\"integer\" />\n\n        <!-- Sets the id of a view after which this one is visited in accessibility traversal.\n             A screen-reader must visit the content of the other view before the content of\n             this one.\n             {@see android.view.View#setAccessibilityTraversalAfter(int)} -->\n        <attr name=\"accessibilityTraversalAfter\" format=\"integer\" />\n\n        <!-- Name of the method in this View's context to invoke when the view is\n             clicked. This name must correspond to a public method that takes\n             exactly one parameter of type View. For instance, if you specify\n             <code>android:onClick=\"sayHello\"</code>, you must declare a\n             <code>public void sayHello(View v)</code> method of your context\n             (typically, your Activity).\n             {@deprecated View actually traverses the Context\n             hierarchy looking for the relevant method, which is fragile (an intermediate \n             ContextWrapper adding a same-named method would change behavior) and restricts\n             bytecode optimizers such as R8. Instead, use View.setOnClickListener.}-->\n        <attr name=\"onClick\" format=\"string\" />\n\n        <!-- Defines over-scrolling behavior. This property is used only if the\n             View is scrollable. Over-scrolling is the ability for the user to\n             receive feedback when attempting to scroll beyond meaningful content. -->\n        <attr name=\"overScrollMode\">\n            <!-- Always show over-scroll effects, even if the content fits entirely\n                 within the available space. -->\n            <enum name=\"always\" value=\"0\" />\n            <!-- Only show over-scroll effects if the content is large\n                 enough to meaningfully scroll. -->\n            <enum name=\"ifContentScrolls\" value=\"1\" />\n            <!-- Never show over-scroll effects. -->\n            <enum name=\"never\" value=\"2\" />\n        </attr>\n\n        <!-- alpha property of the view, as a value between 0 (completely transparent) and 1\n             (completely opaque). -->\n        <attr name=\"alpha\" format=\"float\" />\n\n        <!-- base z depth of the view. -->\n        <attr name=\"elevation\" format=\"dimension\" />\n\n        <!-- translation in x of the view. This value is added post-layout to the left\n             property of the view, which is set by its layout. -->\n        <attr name=\"translationX\" format=\"dimension\" />\n\n        <!-- translation in y of the view. This value is added post-layout to the top\n             property of the view, which is set by its layout. -->\n        <attr name=\"translationY\" format=\"dimension\" />\n\n        <!-- translation in z of the view. This value is added to its elevation. -->\n        <attr name=\"translationZ\" format=\"dimension\" />\n\n        <!-- x location of the pivot point around which the view will rotate and scale.\n             This xml attribute sets the pivotX property of the View. -->\n        <attr name=\"transformPivotX\" format=\"dimension\" />\n\n        <!-- y location of the pivot point around which the view will rotate and scale.\n             This xml attribute sets the pivotY property of the View. -->\n        <attr name=\"transformPivotY\" format=\"dimension\" />\n\n        <!-- rotation of the view, in degrees. -->\n        <attr name=\"rotation\" format=\"float\" />\n\n        <!-- rotation of the view around the x axis, in degrees. -->\n        <attr name=\"rotationX\" format=\"float\" />\n\n        <!-- rotation of the view around the y axis, in degrees. -->\n        <attr name=\"rotationY\" format=\"float\" />\n\n        <!-- scale of the view in the x direction. -->\n        <attr name=\"scaleX\" format=\"float\" />\n\n        <!-- scale of the view in the y direction. -->\n        <attr name=\"scaleY\" format=\"float\" />\n\n        <!-- Determines which side the vertical scroll bar should be placed on. -->\n        <attr name=\"verticalScrollbarPosition\">\n            <!-- Place the scroll bar wherever the system default determines. -->\n            <enum name=\"defaultPosition\" value=\"0\" />\n            <!-- Place the scroll bar on the left. -->\n            <enum name=\"left\" value=\"1\" />\n            <!-- Place the scroll bar on the right. -->\n            <enum name=\"right\" value=\"2\" />\n        </attr>\n\n        <!-- Specifies the type of layer backing this view. The default value is none.\n             Refer to {@link android.view.View#setLayerType(int, android.graphics.Paint)}\n             for more information.-->\n        <attr name=\"layerType\">\n            <!-- Don't use a layer. -->\n            <enum name=\"none\" value=\"0\" />\n            <!-- Use a software layer. Refer to\n                 {@link android.view.View#setLayerType(int, android.graphics.Paint)} for\n                 more information. -->\n            <enum name=\"software\" value=\"1\" />\n            <!-- Use a hardware layer. Refer to\n                 {@link android.view.View#setLayerType(int, android.graphics.Paint)} for\n                 more information. -->\n            <enum name=\"hardware\" value=\"2\" />\n        </attr>\n\n        <!-- Defines the direction of layout drawing. This typically is associated with writing\n             direction of the language script used. The possible values are \"ltr\" for Left-to-Right,\n             \"rtl\" for Right-to-Left, \"locale\", and \"inherit\" from parent view. If there is nothing\n             to inherit, \"locale\" is used. \"locale\" falls back to \"en-US\". \"ltr\" is the direction\n             used in \"en-US\". The default for this attribute is \"inherit\". -->\n        <attr name=\"layoutDirection\">\n            <!-- Left-to-Right. -->\n            <enum name=\"ltr\" value=\"0\" />\n            <!-- Right-to-Left. -->\n            <enum name=\"rtl\" value=\"1\" />\n            <!-- Inherit from parent. -->\n            <enum name=\"inherit\" value=\"2\" />\n            <!-- Locale. -->\n            <enum name=\"locale\" value=\"3\" />\n        </attr>\n\n        <!-- Defines the direction of the text. -->\n         <attr name=\"textDirection\" format=\"integer\">\n            <!-- Default. -->\n            <enum name=\"inherit\" value=\"0\" />\n            <!-- Default for the root view. The first strong directional character determines the\n                 paragraph direction.  If there is no strong directional character, the paragraph\n                 direction is the view’s resolved layout direction. -->\n            <enum name=\"firstStrong\" value=\"1\" />\n            <!-- The paragraph direction is RTL if it contains any strong RTL character, otherwise\n                 it is LTR if it contains any strong LTR characters.  If there are neither, the\n                 paragraph direction is the view’s resolved layout direction. -->\n            <enum name=\"anyRtl\" value=\"2\" />\n            <!-- The paragraph direction is left to right. -->\n            <enum name=\"ltr\" value=\"3\" />\n            <!-- The paragraph direction is right to left. -->\n            <enum name=\"rtl\" value=\"4\" />\n            <!-- The paragraph direction is coming from the system Locale. -->\n            <enum name=\"locale\" value=\"5\" />\n            <!-- The first strong directional character determines the paragraph direction. If\n                 there is no strong directional character, the paragraph direction is LTR. -->\n            <enum name=\"firstStrongLtr\" value=\"6\" />\n            <!-- The first strong directional character determines the paragraph direction. If\n                 there is no strong directional character, the paragraph direction is RTL. -->\n            <enum name=\"firstStrongRtl\" value=\"7\" />\n        </attr>\n\n        <!-- Defines the alignment of the text. -->\n        <attr name=\"textAlignment\" format=\"integer\">\n            <!-- Default. -->\n            <enum name=\"inherit\" value=\"0\" />\n            <!-- Default for the root view. The gravity determines the alignment, ALIGN_NORMAL,\n                ALIGN_CENTER, or ALIGN_OPPOSITE, which are relative to each paragraph’s\n                text direction. -->\n            <enum name=\"gravity\" value=\"1\" />\n            <!-- Align to the start of the paragraph, for example: ALIGN_NORMAL. -->\n            <enum name=\"textStart\" value=\"2\" />\n            <!-- Align to the end of the paragraph, for example: ALIGN_OPPOSITE. -->\n            <enum name=\"textEnd\" value=\"3\" />\n            <!-- Center the paragraph, for example: ALIGN_CENTER. -->\n            <enum name=\"center\" value=\"4\" />\n            <!-- Align to the start of the view, which is ALIGN_LEFT if the view’s resolved\n                layoutDirection is LTR, and ALIGN_RIGHT otherwise. -->\n            <enum name=\"viewStart\" value=\"5\" />\n            <!-- Align to the end of the view, which is ALIGN_RIGHT if the view’s resolved\n                layoutDirection is LTR, and ALIGN_LEFT otherwise. -->\n            <enum name=\"viewEnd\" value=\"6\" />\n        </attr>\n\n        <!-- Describes whether or not this view is important for accessibility.\n             If it is important, the view fires accessibility events and is\n             reported to accessibility services that query the screen. Note:\n             While not recommended, an accessibility service may decide to\n             ignore this attribute and operate on all views in the view tree. -->\n        <attr name=\"importantForAccessibility\" format=\"integer\">\n            <!-- The system determines whether the view is important for accessibility - default\n                 (recommended). -->\n            <enum name=\"auto\" value=\"0\" />\n            <!-- The view is important for accessibility. -->\n            <enum name=\"yes\" value=\"1\" />\n            <!-- The view is not important for accessibility. -->\n            <enum name=\"no\" value=\"2\" />\n            <!-- The view is not important for accessibility, nor are any of its descendant\n                 views. -->\n            <enum name=\"noHideDescendants\" value=\"4\" />\n        </attr>\n\n        <!-- Indicates to accessibility services whether the user should be notified when\n             this view changes. -->\n        <attr name=\"accessibilityLiveRegion\" format=\"integer\">\n            <!-- Accessibility services should not announce changes to this view. -->\n            <enum name=\"none\" value=\"0\" />\n            <!-- Accessibility services should announce changes to this view. -->\n            <enum name=\"polite\" value=\"1\" />\n            <!-- Accessibility services should interrupt ongoing speech to immediately\n                 announce changes to this view. -->\n            <enum name=\"assertive\" value=\"2\" />\n        </attr>\n\n        <!-- Specifies the id of a view for which this view serves as a label for\n             accessibility purposes. For example, a TextView before an EditText in\n             the UI usually specifies what infomation is contained in the EditText.\n             Hence, the TextView is a label for the EditText. -->\n        <attr name=\"labelFor\" format=\"reference\" />\n\n        <!-- Specifies a theme override for a view. When a theme override is set, the\n             view will be inflated using a {@link android.content.Context} themed with\n             the specified resource. During XML inflation, any child views under the\n             view with a theme override will inherit the themed context. -->\n        <attr name=\"theme\" />\n\n        <!-- Names a View such that it can be identified for Transitions. Names should be\n             unique in the View hierarchy. -->\n        <attr name=\"transitionName\" format=\"string\" />\n\n        <!-- Specifies that this view should permit nested scrolling within a compatible\n             ancestor view. -->\n        <attr name=\"nestedScrollingEnabled\" format=\"boolean\" />\n\n        <!-- Sets the state-based animator for the View. -->\n        <attr name=\"stateListAnimator\" format=\"reference\"/>\n\n        <!-- Tint to apply to the background. -->\n        <attr name=\"backgroundTint\" format=\"color\" />\n\n        <!-- Blending mode used to apply the background tint. -->\n        <attr name=\"backgroundTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n\n        <!-- ViewOutlineProvider used to determine the View's Outline. -->\n        <attr name=\"outlineProvider\">\n            <!-- Default, background drawable-driven outline. -->\n            <enum name=\"background\" value=\"0\" />\n            <!-- No outline provider. -->\n            <enum name=\"none\" value=\"1\" />\n            <!-- Generates an opaque outline for the bounds of the view. -->\n            <enum name=\"bounds\" value=\"2\" />\n            <!-- Generates an opaque outline for the padded bounds of the view. -->\n            <enum name=\"paddedBounds\" value=\"3\" />\n        </attr>\n\n        <!-- Defines the drawable to draw over the content. This can be used as an overlay.\n             The foreground drawable participates in the padding of the content if the gravity\n             is set to fill. -->\n        <attr name=\"foreground\" format=\"reference|color\" />\n        <!-- Defines the gravity to apply to the foreground drawable. The gravity defaults\n             to fill. -->\n        <attr name=\"foregroundGravity\">\n            <!-- Push object to the top of its container, not changing its size. -->\n            <flag name=\"top\" value=\"0x30\" />\n            <!-- Push object to the bottom of its container, not changing its size. -->\n            <flag name=\"bottom\" value=\"0x50\" />\n            <!-- Push object to the left of its container, not changing its size. -->\n            <flag name=\"left\" value=\"0x03\" />\n            <!-- Push object to the right of its container, not changing its size. -->\n            <flag name=\"right\" value=\"0x05\" />\n            <!-- Place object in the vertical center of its container, not changing its size. -->\n            <flag name=\"center_vertical\" value=\"0x10\" />\n            <!-- Grow the vertical size of the object if needed so it completely fills its container. -->\n            <flag name=\"fill_vertical\" value=\"0x70\" />\n            <!-- Place object in the horizontal center of its container, not changing its size. -->\n            <flag name=\"center_horizontal\" value=\"0x01\" />\n            <!-- Grow the horizontal size of the object if needed so it completely fills its container. -->\n            <flag name=\"fill_horizontal\" value=\"0x07\" />\n            <!-- Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. -->\n            <flag name=\"center\" value=\"0x11\" />\n            <!-- Grow the horizontal and vertical size of the object if needed so it completely fills its container. -->\n            <flag name=\"fill\" value=\"0x77\" />\n            <!-- Additional option that can be set to have the top and/or bottom edges of\n                 the child clipped to its container's bounds.\n                 The clip will be based on the vertical gravity: a top gravity will clip the bottom\n                 edge, a bottom gravity will clip the top edge, and neither will clip both edges. -->\n            <flag name=\"clip_vertical\" value=\"0x80\" />\n            <!-- Additional option that can be set to have the left and/or right edges of\n                 the child clipped to its container's bounds.\n                 The clip will be based on the horizontal gravity: a left gravity will clip the right\n                 edge, a right gravity will clip the left edge, and neither will clip both edges. -->\n            <flag name=\"clip_horizontal\" value=\"0x08\" />\n        </attr>\n        <!-- Defines whether the foreground drawable should be drawn inside the padding.\n             This property is turned on by default. -->\n        <attr name=\"foregroundInsidePadding\" format=\"boolean\" />\n        <!-- Tint to apply to the foreground. -->\n        <attr name=\"foregroundTint\" format=\"color\" />\n        <!-- Blending mode used to apply the foreground tint. -->\n        <attr name=\"foregroundTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n\n        <!-- Defines which scroll indicators should be displayed when the view\n             can be scrolled. Multiple values may be combined using logical OR,\n             for example \"top|bottom\". -->\n        <attr name=\"scrollIndicators\">\n            <!-- No scroll indicators are displayed. -->\n            <flag name=\"none\" value=\"0x00\" />\n            <!-- Displays top scroll indicator when view can be scrolled up. -->\n            <flag name=\"top\" value=\"0x01\" />\n            <!-- Displays bottom scroll indicator when vew can be scrolled down. -->\n            <flag name=\"bottom\" value=\"0x02\" />\n            <!-- Displays left scroll indicator when vew can be scrolled left. -->\n            <flag name=\"left\" value=\"0x04\" />\n            <!-- Displays right scroll indicator when vew can be scrolled right. -->\n            <flag name=\"right\" value=\"0x08\" />\n            <!-- Displays right scroll indicator when vew can be scrolled in the\n                 start direction. -->\n            <flag name=\"start\" value=\"0x10\" />\n            <!-- Displays right scroll indicator when vew can be scrolled in the\n                 end direction. -->\n            <flag name=\"end\" value=\"0x20\" />\n        </attr>\n\n        <attr name=\"pointerIcon\">\n            <!-- Null icon, pointer becomes invisible. -->\n            <enum name=\"none\" value=\"0\" />\n            <!-- The default icon of arrow pointer. -->\n            <enum name=\"arrow\" value=\"1000\" />\n            <!-- Pointer icon indicating context-menu will appear. -->\n            <enum name=\"context_menu\" value=\"1001\" />\n            <!-- Pointer icon of a hand with the index finger. -->\n            <enum name=\"hand\" value=\"1002\" />\n            <!-- Pointer icon indicating help. -->\n            <enum name=\"help\" value=\"1003\" />\n            <!-- Pointer icon indicating something is going on and waiting. -->\n            <enum name=\"wait\" value=\"1004\" />\n            <!-- Pointer icon for cell and grid. -->\n            <enum name=\"cell\" value=\"1006\" />\n            <!-- Pointer icon of crosshair, indicating to spot a location. -->\n            <enum name=\"crosshair\" value=\"1007\" />\n            <!-- Pointer icon of I-beam, usually for text. -->\n            <enum name=\"text\" value=\"1008\" />\n            <!-- Pointer icon of I-beam with 90-degree rotated, for vertical text. -->\n            <enum name=\"vertical_text\" value=\"1009\" />\n            <!-- Pointer icon of 'alias', indicating an alias of/shortcut to something is to be\n                 created. -->\n            <enum name=\"alias\" value=\"1010\" />\n            <!-- Pointer icon of 'copy', used for drag/drop. -->\n            <enum name=\"copy\" value=\"1011\" />\n            <!-- Pointer icon of 'no-drop', indicating the drop will not be accepted at the\n                 current location. -->\n            <enum name=\"no_drop\" value=\"1012\" />\n            <!-- Pointer icon of four-way arrows, indicating scrolling all direction. -->\n            <enum name=\"all_scroll\" value=\"1013\" />\n            <!-- Pointer icon of horizontal double arrow, indicating horizontal resize. -->\n            <enum name=\"horizontal_double_arrow\" value=\"1014\" />\n            <!-- Pointer icon of vertical double arrow, indicating vertical resize. -->\n            <enum name=\"vertical_double_arrow\" value=\"1015\" />\n            <!-- Pointer icon of diagonal double arrow, starting from top-right to bottom-left.\n                 Indicating freeform resize. -->\n            <enum name=\"top_right_diagonal_double_arrow\" value=\"1016\" />\n            <!-- Pointer icon of diagonal double arrow, starting from top-left to bottom-right.\n                 Indicating freeform resize. -->\n            <enum name=\"top_left_diagonal_double_arrow\" value=\"1017\" />\n            <!-- Pointer icon indicating zoom-in. -->\n            <enum name=\"zoom_in\" value=\"1018\" />\n            <!-- Pointer icon indicating zoom-out. -->\n            <enum name=\"zoom_out\" value=\"1019\" />\n            <!-- Pointer icon of a hand sign to grab something. -->\n            <enum name=\"grab\" value=\"1020\" />\n            <!-- Pointer icon of a hand sign while grabbing something. -->\n            <enum name=\"grabbing\" value=\"1021\" />\n        </attr>\n\n        <!-- Whether this view has elements that may overlap when drawn. See\n             {@link android.view.View#forceHasOverlappingRendering(boolean)}. -->\n        <attr name=\"forceHasOverlappingRendering\" format=\"boolean\" />\n\n        <!-- Defines text displayed in a small popup window on hover or long press. -->\n        <attr name=\"tooltipText\" format=\"string\" localization=\"suggested\" />\n\n        <!-- Whether this view is a root of a keyboard navigation cluster.\n             See {@link android.view.View#setKeyboardNavigationCluster(boolean)}. -->\n        <attr name=\"keyboardNavigationCluster\" format=\"boolean\" />\n\n        <attr name=\"__removed0\" format=\"boolean\" />\n\n        <!-- Defines the next keyboard navigation cluster.\n\n             If the reference refers to a view that does not exist or is part\n             of a hierarchy that is invisible, a {@link java.lang.RuntimeException}\n             will result when the reference is accessed.-->\n        <attr name=\"nextClusterForward\" format=\"reference\"/>\n\n        <attr name=\"__removed1\" format=\"reference\"/>\n\n        <!-- Whether this view is a default-focus view.\n             Only one view per keyboard navigation cluster can have this attribute set to true.\n             See {@link android.view.View#setFocusedByDefault(boolean)}. -->\n        <attr name=\"focusedByDefault\" format=\"boolean\" />\n\n        <!-- Whether this View should use a default focus highlight when it gets focused but\n             doesn't have {@link android.R.attr#state_focused} defined in its background. -->\n        <attr name=\"defaultFocusHighlightEnabled\" format=\"boolean\" />\n\n        <!-- Whether this view should be treated as a focusable unit by screen reader accessibility\n             tools. See {@link android.view.View#setScreenReaderFocusable(boolean)}. The default\n             value, {@code false}, leaves the screen reader to consider other signals, such as\n             focusability or the presence of text, to decide what it focus.-->\n        <attr name=\"screenReaderFocusable\" format=\"boolean\" />\n\n        <!-- The title this view should present to accessibility as a pane title.\n             See {@link android.view.View#setAccessibilityPaneTitle(CharSequence)} -->\n        <attr name=\"accessibilityPaneTitle\" format=\"string\" />\n\n        <!-- Whether or not this view is a heading for accessibility purposes. -->\n        <attr name=\"accessibilityHeading\" format=\"boolean\"/>\n\n        <!-- Whether or not allow clicks on disabled view. -->\n        <attr name=\"allowClickWhenDisabled\" format=\"boolean\"/>\n\n        <!-- Sets the color of the spot shadow that is drawn when the view has a positive Z or\n             elevation value.\n             <p>\n             By default the shadow color is black. Generally, this color will be opaque so the\n             intensity of the shadow is consistent between different views with different colors.\n             <p>\n             The opacity of the final spot shadow is a function of the shadow caster height, the\n             alpha channel of the outlineSpotShadowColor (typically opaque), and the\n             {@link android.R.attr#spotShadowAlpha} theme attribute. -->\n        <attr name=\"outlineSpotShadowColor\" format=\"color\" />\n\n        <!-- Sets the color of the ambient shadow that is drawn when the view has a positive Z\n             or elevation value.\n             <p>\n             By default the shadow color is black. Generally, this color will be opaque so the\n             intensity of the shadow is consistent between different views with different colors.\n             <p>\n             The opacity of the final ambient shadow is a function of the shadow caster height,\n             the alpha channel of the outlineAmbientShadowColor (typically opaque), and the\n             {@link android.R.attr#ambientShadowAlpha} theme attribute. -->\n        <attr name=\"outlineAmbientShadowColor\" format=\"color\" />\n\n        <!-- <p>Whether or not the force dark feature is allowed to be applied to this View.\n             <p>Setting this to false will disable the auto-dark feature on this View draws\n             including any descendants.\n             <p>Setting this to true will allow this view to be automatically made dark, however\n             a value of 'true' will not override any 'false' value in its parent chain nor will\n             it prevent any 'false' in any of its children. -->\n        <attr name=\"forceDarkAllowed\" format=\"boolean\" />\n\n        <!-- <p>Whether the View's Outline should be used to clip the contents of the View.\n             <p>Only a single non-rectangular clip can be applied on a View at any time. Circular\n             clips from a\n             {@link android.view.ViewAnimationUtils#createCircularReveal(View, int, int, float,\n             float)} circular reveal animation take priority over Outline clipping, and child\n             Outline clipping takes priority over Outline clipping done by a parent.\n             <p>Note that this flag will only be respected if the View's Outline returns true from\n             {@link android.graphics.Outline#canClip()}. -->\n        <attr name=\"clipToOutline\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Attributes that can be assigned to a tag for a particular View. -->\n    <declare-styleable name=\"ViewTag\">\n        <!-- Specifies the key identifying a tag. This must be a resource reference. -->\n        <attr name=\"id\" />\n        <!-- Specifies the value with which to tag the view. -->\n        <attr name=\"value\" />\n    </declare-styleable>\n\n    <!-- Attributes that can be assigned to an &lt;include&gt; tag.\n         @hide -->\n    <declare-styleable name=\"Include\">\n        <attr name=\"id\" />\n        <attr name=\"visibility\" />\n    </declare-styleable>\n\n    <!-- Attributes that can be used with a {@link android.view.ViewGroup} or any\n         of its subclasses.  Also see {@link #ViewGroup_Layout} for\n         attributes that this class processes in its children. -->\n    <declare-styleable name=\"ViewGroup\">\n        <!-- Defines whether changes in layout (caused by adding and removing items) should\n             cause a LayoutTransition to run. When this flag is set to true, a default\n             LayoutTransition object will be set on the ViewGroup container and default\n             animations will run when these layout changes occur.-->\n        <attr name=\"animateLayoutChanges\" format=\"boolean\" />\n        <!-- Defines whether a child is limited to draw inside of its bounds or not.\n             This is useful with animations that scale the size of the children to more\n             than 100% for instance. In such a case, this property should be set to false\n             to allow the children to draw outside of their bounds. The default value of\n             this property is true. -->\n        <attr name=\"clipChildren\" format=\"boolean\" />\n        <!-- Defines whether the ViewGroup will clip its children and resize (but not clip) any\n             EdgeEffect to its padding, if padding is not zero. This property is set to true by\n             default. -->\n        <attr name=\"clipToPadding\" format=\"boolean\" />\n        <!-- Defines the layout animation to use the first time the ViewGroup is laid out.\n             Layout animations can also be started manually after the first layout. -->\n        <attr name=\"layoutAnimation\" format=\"reference\" />\n        <!-- Defines whether layout animations should create a drawing cache for their\n             children. Enabling the animation cache consumes more memory and requires\n             a longer initialization but provides better performance. The animation\n             cache is enabled by default. -->\n        <attr name=\"animationCache\" format=\"boolean\" />\n        <!-- Defines the persistence of the drawing cache. The drawing cache might be\n             enabled by a ViewGroup for all its children in specific situations (for\n             instance during a scrolling.) This property lets you persist the cache\n             in memory after its initial usage. Persisting the cache consumes more\n             memory but may prevent frequent garbage collection if the cache is created\n             over and over again. By default the persistence is set to scrolling.\n             Deprecated: The view drawing cache was largely made obsolete with the introduction of\n             hardware-accelerated rendering in API 11. -->\n        <attr name=\"persistentDrawingCache\">\n            <!-- The drawing cache is not persisted after use. -->\n            <flag name=\"none\" value=\"0x0\" />\n            <!-- The drawing cache is persisted after a layout animation. -->\n            <flag name=\"animation\" value=\"0x1\" />\n            <!-- The drawing cache is persisted after a scroll. -->\n            <flag name=\"scrolling\" value=\"0x2\" />\n            <!-- The drawing cache is always persisted. -->\n            <flag name=\"all\" value=\"0x3\" />\n        </attr>\n        <!-- Defines whether the ViewGroup should always draw its children using their\n             drawing cache or not. The default value is true.\n             Deprecated: The view drawing cache was largely made obsolete with the introduction of\n             hardware-accelerated rendering in API 11. -->\n        <attr name=\"alwaysDrawnWithCache\" format=\"boolean\" />\n        <!-- Sets whether this ViewGroup's drawable states also include\n             its children's drawable states.  This is used, for example, to\n             make a group appear to be focused when its child EditText or button\n             is focused. -->\n        <attr name=\"addStatesFromChildren\" format=\"boolean\" />\n\n        <!-- Defines the relationship between the ViewGroup and its descendants\n             when looking for a View to take focus. -->\n        <attr name=\"descendantFocusability\">\n            <!-- The ViewGroup will get focus before any of its descendants. -->\n            <enum name=\"beforeDescendants\" value=\"0\" />\n            <!-- The ViewGroup will get focus only if none of its descendants want it. -->\n            <enum name=\"afterDescendants\" value=\"1\" />\n            <!-- The ViewGroup will block its descendants from receiving focus. -->\n            <enum name=\"blocksDescendants\" value=\"2\" />\n        </attr>\n\n        <!-- Set to true if this ViewGroup blocks focus in the presence of a touchscreen. -->\n        <attr name=\"touchscreenBlocksFocus\" format=\"boolean\" />\n\n        <!-- Sets whether this ViewGroup should split MotionEvents\n             to separate child views during touch event dispatch.\n             If false (default prior to HONEYCOMB), touch events will be dispatched to\n             the child view where the first pointer went down until\n             the last pointer goes up.\n             If true (default for HONEYCOMB and later), touch events may be dispatched to\n             multiple children. MotionEvents for each pointer will be dispatched to the child\n             view where the initial ACTION_DOWN event happened.\n             See {@link android.view.ViewGroup#setMotionEventSplittingEnabled(boolean)}\n             for more information. -->\n        <attr name=\"splitMotionEvents\" format=\"boolean\" />\n\n        <!-- Defines the layout mode of this ViewGroup. -->\n        <attr name=\"layoutMode\">\n            <!-- Use the children's clip bounds when laying out this container. -->\n            <enum name=\"clipBounds\" value=\"0\" />\n            <!-- Use the children's optical bounds when laying out this container. -->\n            <enum name=\"opticalBounds\" value=\"1\" />\n        </attr>\n\n        <!-- Sets whether or not this ViewGroup should be treated as a single entity\n             when doing an Activity transition. Typically, the elements inside a\n             ViewGroup are each transitioned from the scene individually. The default\n             for a ViewGroup is false unless it has a background. See\n             {@link android.app.ActivityOptions#makeSceneTransitionAnimation(android.app.Activity,\n             android.view.View, String)} for more information. Corresponds to\n             {@link android.view.ViewGroup#setTransitionGroup(boolean)}.-->\n        <attr name=\"transitionGroup\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- A {@link android.view.ViewStub} lets you lazily include other XML layouts\n         inside your application at runtime. -->\n    <declare-styleable name=\"ViewStub\">\n        <!-- Supply an identifier name for this view. -->\n        <attr name=\"id\" />\n        <!-- Supply an identifier for the layout resource to inflate when the ViewStub\n             becomes visible or when forced to do so. The layout resource must be a\n             valid reference to a layout. -->\n        <attr name=\"layout\" format=\"reference\" />\n        <!-- Overrides the id of the inflated View with this value. -->\n        <attr name=\"inflatedId\" format=\"reference\" />\n    </declare-styleable>\n\n    <!-- ===================================== -->\n    <!-- View package parent layout attributes -->\n    <!-- ===================================== -->\n    <eat-comment />\n\n    <!-- This is the basic set of layout attributes that are common to all\n         layout managers.  These attributes are specified with the rest of\n         a view's normal attributes (such as {@link android.R.attr#background},\n         but will be parsed by the view's parent and ignored by the child.\n        <p>The values defined here correspond to the base layout attribute\n        class {@link android.view.ViewGroup.LayoutParams}. -->\n    <declare-styleable name=\"ViewGroup_Layout\">\n        <!-- Specifies the basic width of the view.  This is a required attribute\n             for any view inside of a containing layout manager.  Its value may\n             be a dimension (such as \"12dip\") for a constant width or one of\n             the special constants. -->\n        <attr name=\"layout_width\" format=\"dimension\">\n            <!-- The view should be as big as its parent (minus padding).\n                 This constant is deprecated starting from API Level 8 and\n                 is replaced by {@code match_parent}. -->\n            <enum name=\"fill_parent\" value=\"-1\" />\n            <!-- The view should be as big as its parent (minus padding).\n                 Introduced in API Level 8. -->\n            <enum name=\"match_parent\" value=\"-1\" />\n            <!-- The view should be only big enough to enclose its content (plus padding). -->\n            <enum name=\"wrap_content\" value=\"-2\" />\n        </attr>\n\n        <!-- Specifies the basic height of the view.  This is a required attribute\n             for any view inside of a containing layout manager.  Its value may\n             be a dimension (such as \"12dip\") for a constant height or one of\n             the special constants. -->\n        <attr name=\"layout_height\" format=\"dimension\">\n            <!-- The view should be as big as its parent (minus padding).\n                 This constant is deprecated starting from API Level 8 and\n                 is replaced by {@code match_parent}. -->\n            <enum name=\"fill_parent\" value=\"-1\" />\n            <!-- The view should be as big as its parent (minus padding).\n                 Introduced in API Level 8. -->\n            <enum name=\"match_parent\" value=\"-1\" />\n            <!-- The view should be only big enough to enclose its content (plus padding). -->\n            <enum name=\"wrap_content\" value=\"-2\" />\n        </attr>\n    </declare-styleable>\n\n    <!-- This is the basic set of layout attributes for layout managers that\n         wish to place margins around their child views.\n         These attributes are specified with the rest of\n         a view's normal attributes (such as {@link android.R.attr#background},\n         but will be parsed by the view's parent and ignored by the child.\n        <p>The values defined here correspond to the base layout attribute\n        class {@link android.view.ViewGroup.MarginLayoutParams}. -->\n    <declare-styleable name=\"ViewGroup_MarginLayout\">\n        <attr name=\"layout_width\" />\n        <attr name=\"layout_height\" />\n        <!--  Specifies extra space on the left, top, right and bottom\n              sides of this view.  If both layout_margin and any of layout_marginLeft,\n              layout_marginRight, layout_marginStart, layout_marginEnd,\n              layout_marginTop, and layout_marginBottom are\n              also specified, the layout_margin value will take precedence over the\n              edge-specific values. This space is outside this view's bounds.\n              Margin values should be positive. -->\n        <attr name=\"layout_margin\" format=\"dimension\"  />\n        <!--  Specifies extra space on the left side of this view.\n              This space is outside this view's bounds.\n              Margin values should be positive. -->\n        <attr name=\"layout_marginLeft\" format=\"dimension\"  />\n        <!--  Specifies extra space on the top side of this view.\n              This space is outside this view's bounds.\n              Margin values should be positive.-->\n        <attr name=\"layout_marginTop\" format=\"dimension\" />\n        <!--  Specifies extra space on the right side of this view.\n              This space is outside this view's bounds.\n              Margin values should be positive.-->\n        <attr name=\"layout_marginRight\" format=\"dimension\"  />\n        <!--  Specifies extra space on the bottom side of this view.\n              This space is outside this view's bounds.\n              Margin values should be positive.-->\n        <attr name=\"layout_marginBottom\" format=\"dimension\"  />\n        <!--  Specifies extra space on the start side of this view.\n              This space is outside this view's bounds.\n              Margin values should be positive.-->\n        <attr name=\"layout_marginStart\" format=\"dimension\"  />\n        <!--  Specifies extra space on the end side of this view.\n              This space is outside this view's bounds.\n              Margin values should be positive.-->\n        <attr name=\"layout_marginEnd\" format=\"dimension\"  />\n        <!--  Specifies extra space on the left and right sides of this view.\n              Specifying layout_marginHorizontal is equivalent to specifying\n              layout_marginLeft and layout_marginRight.\n              If both layout_marginHorizontal and either/both of layout_marginLeft\n              and layout_marginRight are also specified, the layout_marginHorizontal\n              value will take precedence over the\n              edge-specific values. Also, layout_margin will always take precedence over\n              any of these values, including layout_marginHorizontal.\n              This space is outside this view's bounds.\n              Margin values should be positive.-->\n        <attr name=\"layout_marginHorizontal\" format=\"dimension\"  />\n        <!--  Specifies extra space on the top and bottom sides of this view.\n              Specifying layout_marginVertical is equivalent to specifying\n              layout_marginTop and layout_marginBottom with that same value.\n              If both layout_marginVertical and either/both of layout_marginTop and\n              layout_marginBottom are also specified, the layout_marginVertical value\n              will take precedence over the edge-specific values.\n              Also, layout_margin will always take precedence over\n              any of these values, including layout_marginVertical.\n              This space is outside this view's bounds.\n              Margin values should be positive.-->\n        <attr name=\"layout_marginVertical\" format=\"dimension\"  />\n    </declare-styleable>\n\n    <!-- Use <code>input-method</code> as the root tag of the XML resource that\n         describes an\n         {@link android.view.inputmethod.InputMethod} service, which is\n         referenced from its\n         {@link android.view.inputmethod.InputMethod#SERVICE_META_DATA}\n         meta-data entry.  Described here are the attributes that can be\n         included in that tag. -->\n    <declare-styleable name=\"InputMethod\">\n        <!-- Component name of an activity that allows the user to modify\n             the settings for this service. -->\n        <attr name=\"settingsActivity\" format=\"string\" />\n        <!-- Set to true in all of the configurations for which this input\n             method should be considered an option as the default. -->\n        <attr name=\"isDefault\" format=\"boolean\" />\n        <!-- Set to true if this input method supports ways to switch to\n             a next input method (for example, a globe key.). When this is true and\n             InputMethodManager#shouldOfferSwitchingToNextInputMethod() returns true,\n             the IME has to offer ways to invoke InputMethodManager#switchToNextInputMethod()\n             accordingly.\n             <p> Note that the system determines the most appropriate next input method\n             and subtype in order to provide the consistent user experience in switching\n             between IMEs and subtypes. -->\n        <attr name=\"supportsSwitchingToNextInputMethod\" format=\"boolean\" />\n        <!-- Specifies if an IME can only be used while a device is in VR mode or on a dedicated\n             device -->\n        <attr name=\"isVrOnly\" format=\"boolean\"/>\n        <attr name=\"__removed2\" format=\"boolean\" />\n        <!-- Specifies whether the IME supports showing inline suggestions. -->\n        <attr name=\"supportsInlineSuggestions\" format=\"boolean\" />\n        <!-- Specifies whether the IME suppresses system spell checker.\n             The default value is false. If an IME sets this attribute to true,\n             the system spell checker will be disabled while the IME has an\n             active input session. -->\n        <attr name=\"suppressesSpellChecker\" format=\"boolean\" />\n        <!-- Specifies whether the IME wants to be shown in the Input Method picker. Defaults to\n             true. Set this to false if the IME is intended to be accessed programmatically.\n             <p>\n             Note: This functions as a hint to the system, which may choose to ignore this\n             preference in certain situations or in future releases.-->\n        <attr name=\"showInInputMethodPicker\" format=\"boolean\" />\n        <!-- Specify one or more configuration changes that the IME will handle itself. If not\n             specified, the IME will be restarted if any of these configuration changes happen in\n              the system.  Otherwise, the IME will remain running and its\n             {@link android.inputmethodservice.InputMethodService#onConfigurationChanged}\n             method is called with the new configuration.\n             <p>Note that all of these configuration changes can impact the\n             resource values seen by the application, so you will generally need\n             to re-retrieve all resources (including view layouts, drawables, etc)\n             to correctly handle any configuration change.-->\n        <attr name=\"configChanges\" />\n    </declare-styleable>\n\n    <!-- This is the subtype of InputMethod. Subtype can describe locales (for example, en_US and\n         fr_FR) and modes (for example, voice and keyboard), and is used for IME switch. This\n         subtype allows the system to call the specified subtype of the IME directly. -->\n    <declare-styleable name=\"InputMethod_Subtype\">\n        <!-- The name of the subtype. -->\n        <attr name=\"label\" />\n        <!-- The icon of the subtype. -->\n        <attr name=\"icon\" />\n        <!-- The locale of the subtype. This string should be a locale (for example en_US and fr_FR)\n             and will be passed to the IME when the framework calls the IME\n             with the subtype. This is also used by the framework to know the supported locales\n             of the IME.  -->\n        <attr name=\"imeSubtypeLocale\" format=\"string\" />\n        <!-- The mode of the subtype. This string can be a mode (for example, voice and keyboard)\n             and this string will be passed to the IME when the framework calls the IME with the\n             subtype.  {@link android.view.inputmethod.InputMethodSubtype#getLocale()} returns the\n             value specified in this attribute.  -->\n        <attr name=\"imeSubtypeMode\" format=\"string\" />\n        <!-- Set true if the subtype is auxiliary.  An auxiliary subtype won't be shown in the\n             input method selection list in the settings app.\n             InputMethodManager#switchToLastInputMethod will ignore auxiliary subtypes when it\n             chooses a target subtype. -->\n        <attr name=\"isAuxiliary\" format=\"boolean\" />\n        <!-- Set true when this subtype should be selected by default if no other subtypes are\n             selected explicitly. Note that a subtype with this parameter being true will\n             not be shown in the subtypes list. -->\n        <attr name=\"overridesImplicitlyEnabledSubtype\" format=\"boolean\" />\n        <!-- The extra value of the subtype. This string can be any string and will be passed to\n             the IME when the framework calls the IME with the subtype.  -->\n        <attr name=\"imeSubtypeExtraValue\" format=\"string\" />\n        <!-- The unique id for the subtype. The input method framework keeps track of enabled\n             subtypes by ID. When the IME package gets upgraded, enabled IDs will stay enabled even\n             if other attributes are different. If the ID is unspecified (by calling the other\n             constructor or 0. Arrays.hashCode(new Object[] {locale, mode, extraValue,\n             isAuxiliary, overridesImplicitlyEnabledSubtype}) will be used instead. -->\n        <attr name=\"subtypeId\" format=\"integer\"/>\n        <!-- Set to {@code true} if this subtype is ASCII capable. If the subtype is ASCII\n             capable, it should guarantee that the user can input ASCII characters with\n             this subtype. This is important because many password fields only allow\n             ASCII-characters.\n\n             <p>Note: In order to avoid some known system issues on\n             {@link android.os.Build.VERSION_CODES#P} and prior OSes, you may want to include\n             {@code \"AsciiCapable\"} in\n             {@link android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue} when you specify\n             {@code true} to this attribute.-->\n        <attr name=\"isAsciiCapable\" format=\"boolean\" />\n        <!-- The BCP-47 Language Tag of the subtype.  This replaces\n        {@link android.R.styleable#InputMethod_Subtype_imeSubtypeLocale}.  -->\n        <attr name=\"languageTag\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- Use <code>spell-checker</code> as the root tag of the XML resource that\n         describes an\n         {@link android.service.textservice.SpellCheckerService} service, which is\n         referenced from its\n         {@link android.view.textservice.SpellCheckerSession#SERVICE_META_DATA}\n         meta-data entry.  Described here are the attributes that can be\n         included in that tag. -->\n    <declare-styleable name=\"SpellChecker\">\n        <!-- The name of the spell checker. -->\n        <attr name=\"label\" />\n        <!-- Component name of an activity that allows the user to modify\n             the settings for this service. -->\n        <attr name=\"settingsActivity\"/>\n    </declare-styleable>\n\n    <!-- This is the subtype of the spell checker. Subtype can describe locales (for example,\n             en_US and fr_FR). -->\n    <declare-styleable name=\"SpellChecker_Subtype\">\n        <!-- The name of the subtype. -->\n        <attr name=\"label\" />\n        <!-- The locale of the subtype. This string should be a locale (for example, en_US and\n             fr_FR). This is also used by the framework to know the supported locales\n             of the spell checker. {@link android.view.textservice.SpellCheckerSubtype#getLocale()}\n             returns the value specified in this attribute.  -->\n        <attr name=\"subtypeLocale\" format=\"string\" />\n        <!-- The extra value of the subtype. This string can be any string and will be passed to\n             the SpellChecker.  -->\n        <attr name=\"subtypeExtraValue\" format=\"string\" />\n        <!-- The unique id for the subtype. The text service (spell checker) framework keeps track\n             of enabled subtypes by ID. When the spell checker package gets upgraded, enabled IDs\n             will stay enabled even if other attributes are different. If the ID is unspecified or\n             explicitly specified to 0 in XML resources,\n             {@code Arrays.hashCode(new Object[] {subtypeLocale, extraValue})} will be used instead.\n              -->\n        <attr name=\"subtypeId\" />\n        <!-- The BCP-47 Language Tag of the subtype.  This replaces\n        {@link android.R.styleable#SpellChecker_Subtype_subtypeLocale}.  -->\n        <attr name=\"languageTag\" />\n    </declare-styleable>\n\n    <!-- Use <code>accessibility-service</code> as the root tag of the XML resource that\n         describes an {@link android.accessibilityservice.AccessibilityService} service,\n         which is referenced from its\n         {@link android.accessibilityservice.AccessibilityService#SERVICE_META_DATA}\n         meta-data entry. -->\n    <declare-styleable name=\"AccessibilityService\">\n        <!-- The event types this service would like to receive as specified in\n             {@link android.view.accessibility.AccessibilityEvent}. This setting\n             can be changed at runtime by calling\n             {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)\n             android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. -->\n        <attr name=\"accessibilityEventTypes\">\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED} events.-->\n            <flag name=\"typeViewClicked\" value=\"0x00000001\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_LONG_CLICKED} events. -->\n            <flag name=\"typeViewLongClicked\" value=\"0x00000002\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED} events. -->\n            <flag name=\"typeViewSelected\" value=\"0x00000004\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED} events. -->\n            <flag name=\"typeViewFocused\" value=\"0x00000008\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED} events. -->\n            <flag name=\"typeViewTextChanged\" value=\"0x00000010\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED} events. -->\n            <flag name=\"typeWindowStateChanged\" value=\"0x00000020\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED} events. -->\n            <flag name=\"typeNotificationStateChanged\" value=\"0x00000040\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_ENTER} events. -->\n            <flag name=\"typeViewHoverEnter\" value=\"0x00000080\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_HOVER_EXIT} events. -->\n            <flag name=\"typeViewHoverExit\" value=\"0x00000100\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START} events. -->\n            <flag name=\"typeTouchExplorationGestureStart\" value=\"0x00000200\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END} events. -->\n            <flag name=\"typeTouchExplorationGestureEnd\" value=\"0x00000400\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events. -->\n            <flag name=\"typeWindowContentChanged\" value=\"0x00000800\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED} events. -->\n            <flag name=\"typeViewScrolled\" value=\"0x000001000\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED} events. -->\n            <flag name=\"typeViewTextSelectionChanged\" value=\"0x000002000\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_ANNOUNCEMENT} events. -->\n            <flag name=\"typeAnnouncement\" value=\"0x00004000\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED} events. -->\n            <flag name=\"typeViewAccessibilityFocused\" value=\"0x00008000\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED} events. -->\n            <flag name=\"typeViewAccessibilityFocusCleared\" value=\"0x00010000\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY} events. -->\n            <flag name=\"typeViewTextTraversedAtMovementGranularity\" value=\"0x00020000\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_GESTURE_DETECTION_START} events. -->\n            <flag name=\"typeGestureDetectionStart\" value=\"0x00040000\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_GESTURE_DETECTION_END} events. -->\n            <flag name=\"typeGestureDetectionEnd\" value=\"0x00080000\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_INTERACTION_START} events. -->\n            <flag name=\"typeTouchInteractionStart\" value=\"0x00100000\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_INTERACTION_END} events. -->\n            <flag name=\"typeTouchInteractionEnd\" value=\"0x00200000\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_WINDOWS_CHANGED} events. -->\n            <flag name=\"typeWindowsChanged\" value=\"0x00400000\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CONTEXT_CLICKED} events. -->\n            <flag name=\"typeContextClicked\" value=\"0x00800000\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPE_ASSIST_READING_CONTEXT} events. -->\n            <flag name=\"typeAssistReadingContext\" value=\"0x01000000\" />\n            <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPES_ALL_MASK} i.e. all events. -->\n            <flag name=\"typeAllMask\" value=\"0xffffffff\" />\n        </attr>\n        <!-- Comma separated package names from which this service would like to receive events (leave out for all packages).\n             {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)\n             android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. -->\n        <attr name=\"packageNames\" format=\"string\" />\n        <!-- The feedback types this service provides as specified in\n             {@link android.accessibilityservice.AccessibilityServiceInfo}. This setting\n             can be changed at runtime by calling\n             {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)\n             android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. -->\n        <attr name=\"accessibilityFeedbackType\">\n            <!-- Provides {@link android.accessibilityservice.AccessibilityServiceInfo#FEEDBACK_SPOKEN} feedback. -->\n            <flag name=\"feedbackSpoken\" value=\"0x00000001\" />\n            <!-- Provides {@link android.accessibilityservice.AccessibilityServiceInfo#FEEDBACK_HAPTIC} feedback. -->\n            <flag name=\"feedbackHaptic\" value=\"0x00000002\" />\n            <!-- Provides {@link android.accessibilityservice.AccessibilityServiceInfo#FEEDBACK_AUDIBLE} feedback. -->\n            <flag name=\"feedbackAudible\" value=\"0x00000004\" />\n            <!-- Provides {@link android.accessibilityservice.AccessibilityServiceInfo#FEEDBACK_VISUAL} feedback. -->\n            <flag name=\"feedbackVisual\" value=\"0x00000008\" />\n            <!-- Provides {@link android.accessibilityservice.AccessibilityServiceInfo#FEEDBACK_GENERIC} feedback. -->\n            <flag name=\"feedbackGeneric\" value=\"0x00000010\" />\n            <!-- Provides {@link android.accessibilityservice.AccessibilityServiceInfo#FEEDBACK_ALL_MASK} feedback. -->\n            <flag name=\"feedbackAllMask\" value=\"0xffffffff\" />\n        </attr>\n        <!-- The minimal period in milliseconds between two accessibility events of the same type\n             are sent to this service. This setting can be changed at runtime by calling\n             {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)\n             android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. -->\n        <attr name=\"notificationTimeout\" format=\"integer\" />\n        <!-- A recommended timeout in milliseconds used in\n             {@link android.view.accessibility.AccessibilityManager#getRecommendedTimeoutMillis(int, int)\n             android.view.accessibility.AccessibilityManager.getRecommendedTimeoutMillis(int, int)}\n             to return a suitable value for UIs that do not include interactive controls.\n             This setting can be changed at runtime by calling\n             {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)\n             android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. -->\n        <attr name=\"nonInteractiveUiTimeout\" format=\"integer\" />\n        <!-- A recommended timeout in milliseconds used in\n             {@link android.view.accessibility.AccessibilityManager#getRecommendedTimeoutMillis(int, int)\n             android.view.accessibility.AccessibilityManager.getRecommendedTimeoutMillis(int, int)}\n             to return a suitable value for interactive controls.\n             This setting can be changed at runtime by calling\n             {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)\n             android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. -->\n        <attr name=\"interactiveUiTimeout\" format=\"integer\" />\n        <!-- Additional flags as specified in\n             {@link android.accessibilityservice.AccessibilityServiceInfo}.\n             This setting can be changed at runtime by calling\n             {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)\n             android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. -->\n        <attr name=\"accessibilityFlags\">\n            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#DEFAULT}. -->\n            <flag name=\"flagDefault\" value=\"0x00000001\" />\n            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS}. -->\n            <flag name=\"flagIncludeNotImportantViews\" value=\"0x00000002\" />\n            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}. -->\n            <flag name=\"flagRequestTouchExplorationMode\" value=\"0x00000004\" />\n            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY}.\n                 Not used by the framework.\n            -->\n            <flag name=\"flagRequestEnhancedWebAccessibility\" value=\"0x00000008\" />\n            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}. -->\n            <flag name=\"flagReportViewIds\" value=\"0x00000010\" />\n            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_FILTER_KEY_EVENTS}. -->\n            <flag name=\"flagRequestFilterKeyEvents\" value=\"0x00000020\" />\n            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}. -->\n            <flag name=\"flagRetrieveInteractiveWindows\" value=\"0x00000040\" />\n            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_ENABLE_ACCESSIBILITY_VOLUME}. -->\n            <flag name=\"flagEnableAccessibilityVolume\" value=\"0x00000080\" />\n            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON}. -->\n            <flag name=\"flagRequestAccessibilityButton\" value=\"0x00000100\" />\n            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_FINGERPRINT_GESTURES}. -->\n            <flag name=\"flagRequestFingerprintGestures\" value=\"0x00000200\" />\n            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK}. -->\n            <flag name=\"flagRequestShortcutWarningDialogSpokenFeedback\" value=\"0x00000400\" />\n            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_SERVICE_HANDLES_DOUBLE_TAP}. -->\n            <flag name=\"flagServiceHandlesDoubleTap\" value=\"0x00000800\" />\n            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_MULTI_FINGER_GESTURES}. -->\n            <flag name=\"flagRequestMultiFingerGestures\" value=\"0x00001000\" />\n            <flag name=\"flagSendMotionEvents\" value=\"0x0004000\" />\n        </attr>\n        <!-- Component name of an activity that allows the user to modify\n             the settings for this service. This setting cannot be changed at runtime. -->\n        <attr name=\"settingsActivity\" />\n        <!-- Attribute whether the accessibility service wants to be able to retrieve the\n             active window content. This setting cannot be changed at runtime.\n             <p>\n             Required to allow setting the {@link android.accessibilityservice\n             #AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.\n             </p>\n         -->\n        <attr name=\"canRetrieveWindowContent\" format=\"boolean\" />\n        <!-- Attribute whether the accessibility service wants to be able to request touch\n             exploration mode in which touched items are spoken aloud and the UI can be\n             explored via gestures.\n             <p>\n             Required to allow setting the {@link android.accessibilityservice\n             #AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} flag.\n             </p>\n         -->\n        <attr name=\"canRequestTouchExplorationMode\" format=\"boolean\" />\n        <!-- Attribute whether the accessibility service wants to be able to request enhanced\n             web accessibility enhancements.\n             {@deprecated Not used by the framework}\n         -->\n        <attr name=\"canRequestEnhancedWebAccessibility\" format=\"boolean\" />\n        <!-- Attribute whether the accessibility service wants to be able to request to\n             filter key events.\n             <p>\n             Required to allow setting the {@link android.accessibilityservice\n             #AccessibilityServiceInfo#FLAG_REQUEST_FILTER_KEY_EVENTS} flag.\n             </p>\n         -->\n        <attr name=\"canRequestFilterKeyEvents\" format=\"boolean\" />\n        <!-- Attribute whether the accessibility service wants to be able to control\n             display magnification.\n         -->\n        <attr name=\"canControlMagnification\" format=\"boolean\" />\n        <!-- Attribute whether the accessibility service wants to be able to perform gestures. -->\n        <attr name=\"canPerformGestures\" format=\"boolean\" />\n        <!-- Attribute whether the accessibility service wants to be able to capture gestures from\n             the fingerprint sensor.\n             <p>\n             Required to allow setting the {@link android.accessibilityservice\n             #AccessibilityServiceInfo#FLAG_REQUEST_FINGERPRINT_GESTURES} flag to have any effect.\n             </p>\n         -->\n        <attr name=\"canRequestFingerprintGestures\" format=\"boolean\" />\n        <!-- Attribute whether the accessibility service wants to be able to take screenshot. -->\n        <attr name=\"canTakeScreenshot\" format=\"boolean\" />\n\n        <!-- Attribute indicating whether the accessibility service is used to assist users with\n             disabilities. This criteria might be defined by the installer. The default is false.\n             <p>\n             Note: If this flag is false, system will show a notification after a duration to\n             inform the user about the privacy implications of the service.\n             </p>\n        -->\n        <attr name=\"isAccessibilityTool\" format=\"boolean\" />\n\n        <!-- Animated image of the accessibility service purpose or behavior, to help users\n             understand how the service can help them.-->\n        <attr name=\"animatedImageDrawable\" format=\"reference\"/>\n        <!-- Html description of the accessibility service, to help users understand\n             how the service can help them.-->\n        <attr name=\"htmlDescription\" format=\"reference\"/>\n\n        <!-- Short description of the accessibility service purpose or behavior.-->\n        <attr name=\"description\" />\n        <!-- Brief summary of the accessibility service purpose or behavior. -->\n        <attr name=\"summary\" />\n    </declare-styleable>\n\n    <!-- Use <code>accessibility-shortcut-target</code> as the root tag of the XML resource that\n         describes an activity, which is referenced from the\n         <code>android.accessibilityshortcut.target</code> meta-data entry. -->\n    <declare-styleable name=\"AccessibilityShortcutTarget\">\n        <!-- Short description of the target of accessibility shortcut purpose or behavior.-->\n        <attr name=\"description\" />\n        <!-- Brief summary of the target of accessibility shortcut purpose or behavior. -->\n        <attr name=\"summary\" />\n        <!-- Animated image of the target of accessibility shortcut purpose or behavior, to help\n             users understand how the target of accessibility shortcut can help them.-->\n        <attr name=\"animatedImageDrawable\" format=\"reference\"/>\n        <!-- Html description of the target of accessibility shortcut purpose or behavior, to help\n             users understand how the target of accessibility shortcut can help them. -->\n        <attr name=\"htmlDescription\" format=\"reference\"/>\n        <!-- Component name of an activity that allows the user to modify the settings for this\n             target of accessibility shortcut. -->\n        <attr name=\"settingsActivity\" />\n    </declare-styleable>\n\n    <!-- Use <code>print-service</code> as the root tag of the XML resource that\n         describes an {@link android.printservice.PrintService} service, which is\n         referenced from its {@link android.printservice.PrintService#SERVICE_META_DATA}\n         meta-data entry. -->\n    <declare-styleable name=\"PrintService\">\n        <!-- Fully qualified class name of an activity that allows the user to modify\n             the settings for this service. -->\n        <attr name=\"settingsActivity\" />\n        <!-- Fully qualified class name of an activity that allows the user to manually\n             add printers to this print service. -->\n        <attr name=\"addPrintersActivity\" format=\"string\"/>\n        <!-- Fully qualified class name of an activity with advanced print options\n             specific to this print service. -->\n        <attr name=\"advancedPrintOptionsActivity\" format=\"string\"/>\n        <!-- The vendor name if this print service is vendor specific. -->\n        <attr name=\"vendor\" format=\"string\"/>\n    </declare-styleable>\n\n    <!-- Use <code>host-apdu-service</code> as the root tag of the XML resource that\n         describes an {@link android.nfc.cardemulation.HostApduService} service, which\n         is referenced from its {@link android.nfc.cardemulation.HostApduService#SERVICE_META_DATA}\n         entry. -->\n    <declare-styleable name=\"HostApduService\">\n        <!-- Short description of the functionality the service implements. This attribute\n             is mandatory.-->\n        <attr name=\"description\" />\n        <!-- Whether the device must be unlocked before routing data to this service.\n             The default is false.-->\n        <attr name=\"requireDeviceUnlock\" format=\"boolean\"/>\n        <!-- A drawable that can be rendered in Android's system UI for representing\n             the service. -->\n        <attr name=\"apduServiceBanner\" format=\"reference\"/>\n        <!-- Component name of an activity that allows the user to modify\n             the settings for this service. -->\n        <attr name=\"settingsActivity\"/>\n        <!-- Whether the device must be screen on before routing data to this service.\n             The default is true.-->\n        <attr name=\"requireDeviceScreenOn\" format=\"boolean\"/>\n    </declare-styleable>\n\n    <!-- Use <code>offhost-apdu-service</code> as the root tag of the XML resource that\n         describes an {@link android.nfc.cardemulation.OffHostApduService}\n         service, which is referenced from its\n         {@link android.nfc.cardemulation.OffHostApduService#SERVICE_META_DATA} entry. -->\n    <declare-styleable name=\"OffHostApduService\">\n        <!-- Short description of the functionality the service implements. This attribute\n             is mandatory.-->\n        <attr name=\"description\" />\n        <!-- A drawable that can be rendered in Android's system UI for representing\n             the service. -->\n        <attr name=\"apduServiceBanner\"/>\n        <!-- Component name of an activity that allows the user to modify\n             the settings for this service. -->\n        <attr name=\"settingsActivity\"/>\n        <!-- Secure Element which the AIDs should be routed to -->\n        <attr name=\"secureElementName\" format=\"string\"/>\n        <!-- Whether the device must be unlocked before routing data to this service.\n             The default is false.-->\n        <attr name=\"requireDeviceUnlock\"/>\n        <!-- Whether the device must be screen on before routing data to this service.\n             The default is false.-->\n        <attr name=\"requireDeviceScreenOn\"/>\n    </declare-styleable>\n\n    <!-- Specify one or more <code>aid-group</code> elements inside a\n         <code>host-apdu-service</code> or <code>offhost-apdu-service</code>\n         element to define a group of ISO7816 Application ID (AIDs) that\n         your service can handle.-->\n    <declare-styleable name=\"AidGroup\">\n        <!-- Short description of what the AID group implements. This attribute is mandatory.-->\n        <attr name=\"description\" />\n        <!-- The category attribute will be used by the Android platform to present\n             multiple applications that register ISO 7816 Application IDs (AIDs) in the\n             same category uniformly.\n             Additionally, when a category is specified, Android will ensure that either\n             all AIDs in this group are routed to this application, or none at all.\n             This attribute is optional.-->\n        <attr name=\"category\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- Specify one or more <code>aid-filter</code> elements inside a\n         <code>aid-group</code> element to specify an ISO7816 Application ID (AID)\n         your service can handle. -->\n    <declare-styleable name=\"AidFilter\">\n        <!-- The ISO7816 Application ID. This attribute is mandatory. -->\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- Specify one or more <code>aid-prefix-filter</code> elements inside a\n         <code>aid-group</code> element to specify an ISO7816 Application ID (AID)\n         prefix your service can handle. -->\n    <declare-styleable name=\"AidPrefixFilter\">\n        <!-- The ISO7816 Application ID. This attribute is mandatory. -->\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- Use <code>host-nfcf-service</code> as the root tag of the XML resource that\n         describes an {@link android.nfc.cardemulation.HostNfcFService} service, which\n         is referenced from its {@link android.nfc.cardemulation.HostNfcFService#SERVICE_META_DATA}\n         entry. -->\n    <declare-styleable name=\"HostNfcFService\">\n        <!-- Short description of the functionality the service implements. This attribute\n             is mandatory.-->\n        <attr name=\"description\" />\n    </declare-styleable>\n\n    <!-- Specify one or more <code>system-code-filter</code> elements inside a\n         <code>host-nfcf-service</code> element to specify a System Code\n         your service can handle. -->\n    <declare-styleable name=\"SystemCodeFilter\">\n        <!-- The System Code. This attribute is mandatory. -->\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- Specify one or more <code>nfcid2-filter</code> elements inside a\n         <code>host-nfcf-service</code> element to specify a NFCID2\n         your service can handle. -->\n    <declare-styleable name=\"Nfcid2Filter\">\n        <!-- The NFCID2. This attribute is mandatory. -->\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- Specify one or more <code>t3tPmm-filter</code> elements inside a\n         <code>host-nfcf-service</code> element to specify a LF_T3T_PMM. -->\n    <declare-styleable name=\"T3tPmmFilter\">\n        <attr name=\"name\" />\n\n    </declare-styleable>\n\n    <declare-styleable name=\"ActionMenuItemView\">\n        <attr name=\"minWidth\" />\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- Widget package class attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <declare-styleable name=\"AbsListView\">\n         <!-- Drawable used to indicate the currently selected item in the list. -->\n        <attr name=\"listSelector\" format=\"color|reference\" />\n        <!-- When set to true, the selector will be drawn over the selected item.\n             Otherwise the selector is drawn behind the selected item. The default\n             value is false. -->\n        <attr name=\"drawSelectorOnTop\" format=\"boolean\" />\n        <!-- Used by ListView and GridView to stack their content from the bottom. -->\n        <attr name=\"stackFromBottom\" format=\"boolean\" />\n        <!-- When set to true, the list uses a drawing cache during scrolling.\n             This makes the rendering faster but uses more memory. The default\n             value is true. -->\n        <attr name=\"scrollingCache\" format=\"boolean\" />\n        <!-- When set to true, the list will filter results as the user types. The\n             List's adapter must support the Filterable interface for this to work. -->\n        <attr name=\"textFilterEnabled\" format=\"boolean\" />\n        <!-- Sets the transcript mode for the list. In transcript mode, the list\n             scrolls to the bottom to make new items visible when they are added. -->\n        <attr name=\"transcriptMode\">\n            <!-- Disables transcript mode. This is the default value. -->\n            <enum name=\"disabled\" value=\"0\"/>\n            <!-- The list will automatically scroll to the bottom when\n                 a data set change notification is received and only if the last item is\n                 already visible on screen. -->\n            <enum name=\"normal\" value=\"1\" />\n            <!-- The list will automatically scroll to the bottom, no matter what items\n                 are currently visible. -->\n            <enum name=\"alwaysScroll\" value=\"2\" />\n        </attr>\n        <!-- Indicates that this list will always be drawn on top of solid, single-color\n             opaque background. This allows the list to optimize drawing. -->\n        <attr name=\"cacheColorHint\" format=\"color\" />\n        <!-- Enables the fast scroll thumb that can be dragged to quickly scroll through\n             the list. -->\n        <attr name=\"fastScrollEnabled\" format=\"boolean\" />\n        <!-- Specifies the style of the fast scroll decorations. -->\n        <attr name=\"fastScrollStyle\" format=\"reference\" />\n        <!-- When set to true, the list will use a more refined calculation\n             method based on the pixels height of the items visible on screen. This\n             property is set to true by default but should be set to false if your adapter\n             will display items of varying heights. When this property is set to true and\n             your adapter displays items of varying heights, the scrollbar thumb will\n             change size as the user scrolls through the list. When set to false, the list\n             will use only the number of items in the adapter and the number of items visible\n             on screen to determine the scrollbar's properties. -->\n        <attr name=\"smoothScrollbar\" format=\"boolean\" />\n        <!-- Defines the choice behavior for the view. By default, lists do not have\n             any choice behavior. By setting the choiceMode to singleChoice, the list\n             allows up to one item to be in a chosen state. By setting the choiceMode to\n             multipleChoice, the list allows any number of items to be chosen.\n             Finally, by setting the choiceMode to multipleChoiceModal the list allows\n             any number of items to be chosen in a special selection mode.\n             The application will supply a\n             {@link android.widget.AbsListView.MultiChoiceModeListener} using\n             {@link android.widget.AbsListView#setMultiChoiceModeListener} to control the\n             selection mode. This uses the {@link android.view.ActionMode} API. -->\n        <attr name=\"choiceMode\">\n            <!-- Normal list that does not indicate choices. -->\n            <enum name=\"none\" value=\"0\" />\n            <!-- The list allows up to one choice. -->\n            <enum name=\"singleChoice\" value=\"1\" />\n            <!-- The list allows multiple choices. -->\n            <enum name=\"multipleChoice\" value=\"2\" />\n            <!-- The list allows multiple choices in a custom selection mode. -->\n            <enum name=\"multipleChoiceModal\" value=\"3\" />\n        </attr>\n\n        <!-- When set to true, the list will always show the fast scroll interface.\n             This setting implies fastScrollEnabled. -->\n        <attr name=\"fastScrollAlwaysVisible\" format=\"boolean\" />\n    </declare-styleable>\n    <!-- @hide -->\n    <declare-styleable name=\"RecycleListView\">\n        <!-- Bottom padding to use when no buttons are present. -->\n        <attr name=\"paddingBottomNoButtons\" format=\"dimension\" />\n        <!-- Top padding to use when no title is present. -->\n        <attr name=\"paddingTopNoTitle\" format=\"dimension\" />\n    </declare-styleable>\n    <declare-styleable name=\"AbsSpinner\">\n        <!-- Reference to an array resource that will populate the Spinner.  For static content,\n             this is simpler than populating the Spinner programmatically. -->\n        <attr name=\"entries\" />\n    </declare-styleable>\n    <declare-styleable name=\"AnalogClock\">\n        <attr name=\"dial\" format=\"reference\"/>\n        <attr name=\"hand_hour\" format=\"reference\"/>\n        <attr name=\"hand_minute\" format=\"reference\"/>\n        <attr name=\"hand_second\" format=\"reference\"/>\n        <!-- Specifies the time zone to use. When this attribute is specified, the\n             TextClock will ignore the time zone of the system. To use the user's\n             time zone, do not specify this attribute. The default value is the\n             user's time zone. Please refer to {@link java.util.TimeZone} for more\n             information about time zone ids. -->\n        <attr name=\"timeZone\" format=\"string\"/>\n        <!-- Tint to apply to the dial graphic. -->\n        <attr name=\"dialTint\" format=\"color\" />\n        <!-- Blending mode used to apply the dial graphic tint. -->\n        <attr name=\"dialTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n        <!-- Tint to apply to the hour hand graphic. -->\n        <attr name=\"hand_hourTint\" format=\"color\" />\n        <!-- Blending mode used to apply the hour hand graphic tint. -->\n        <attr name=\"hand_hourTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n        <!-- Tint to apply to the minute hand graphic. -->\n        <attr name=\"hand_minuteTint\" format=\"color\" />\n        <!-- Blending mode used to apply the minute hand graphic tint. -->\n        <attr name=\"hand_minuteTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n        <!-- Tint to apply to the second hand graphic. -->\n        <attr name=\"hand_secondTint\" format=\"color\" />\n        <!-- Blending mode used to apply the second hand graphic tint. -->\n        <attr name=\"hand_secondTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n    </declare-styleable>\n    <declare-styleable name=\"Button\">\n    </declare-styleable>\n    <declare-styleable name=\"Chronometer\">\n        <!-- Format string: if specified, the Chronometer will display this\n             string, with the first \"%s\" replaced by the current timer value\n             in \"MM:SS\" or \"H:MM:SS\" form.\n             If no format string is specified, the Chronometer will simply display\n             \"MM:SS\" or \"H:MM:SS\". -->\n        <attr name=\"format\" format=\"string\" localization=\"suggested\" />\n        <!-- Specifies whether this Chronometer counts down or counts up from the base.\n              If not specified this is false and the Chronometer counts up. -->\n        <attr name=\"countDown\" format=\"boolean\" />\n    </declare-styleable>\n    <declare-styleable name=\"CompoundButton\">\n        <!-- Indicates the initial checked state of this button. -->\n        <attr name=\"checked\" format=\"boolean\" />\n        <!-- Drawable used for the button graphic (for example, checkbox and radio button). -->\n        <attr name=\"button\" format=\"reference\" />\n        <!-- Tint to apply to the button graphic. -->\n        <attr name=\"buttonTint\" format=\"color\" />\n        <!-- Blending mode used to apply the button graphic tint. -->\n        <attr name=\"buttonTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n    </declare-styleable>\n    <declare-styleable name=\"CheckedTextView\">\n        <!-- Indicates the initial checked state of this text. -->\n        <attr name=\"checked\" />\n        <!-- Drawable used for the check mark graphic. -->\n        <attr name=\"checkMark\" format=\"reference\"/>\n        <!-- Tint to apply to the check mark. -->\n        <attr name=\"checkMarkTint\" format=\"color\" />\n        <!-- Blending mode used to apply the check mark tint. -->\n        <attr name=\"checkMarkTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n        <!-- Gravity for aligning a CheckedTextView's checkmark to one side or the other. -->\n        <attr name=\"checkMarkGravity\">\n            <!-- Push object to the left of its container, not changing its size. -->\n            <flag name=\"left\" value=\"0x03\" />\n            <!-- Push object to the right of its container, not changing its size. -->\n            <flag name=\"right\" value=\"0x05\" />\n            <!-- Push object to the beginning of its container, not changing its size. -->\n            <flag name=\"start\" value=\"0x00800003\" />\n            <!-- Push object to the end of its container, not changing its size. -->\n            <flag name=\"end\" value=\"0x00800005\" />\n        </attr>\n    </declare-styleable>\n    <declare-styleable name=\"EditText\">\n    </declare-styleable>\n    <declare-styleable name=\"FastScroll\">\n        <!-- Drawable used for the scroll bar thumb. -->\n        <attr name=\"thumbDrawable\" format=\"reference\" />\n        <!-- Minimum width of the thumb. -->\n        <attr name=\"thumbMinWidth\" format=\"dimension\" />\n        <!-- Minimum height of the thumb. -->\n        <attr name=\"thumbMinHeight\" format=\"dimension\" />\n        <!-- Drawable used for the scroll bar track. -->\n        <attr name=\"trackDrawable\" format=\"reference\" />\n        <!-- Drawable used for the section header preview when right-aligned. -->\n        <attr name=\"backgroundRight\" format=\"reference\" />\n        <!-- Drawable used for the section header preview when left-aligned. -->\n        <attr name=\"backgroundLeft\" format=\"reference\" />\n        <!-- Position of section header preview. -->\n        <attr name=\"position\">\n            <!-- Floating at the top of the content. -->\n            <enum name=\"floating\" value=\"0\" />\n            <!-- Pinned to the thumb, vertically centered with the middle of the thumb. -->\n            <enum name=\"atThumb\" value=\"1\" />\n            <!-- Pinned to the thumb, vertically centered with the top edge of the thumb. -->\n            <enum name=\"aboveThumb\" value=\"2\" />\n        </attr>\n        <attr name=\"textAppearance\" />\n        <attr name=\"textColor\" />\n        <attr name=\"textSize\" />\n        <!-- Minimum width of the section header preview. -->\n        <attr name=\"minWidth\" />\n        <!-- Minimum height of the section header preview. -->\n        <attr name=\"minHeight\" />\n        <!-- Padding for the section header preview. -->\n        <attr name=\"padding\" />\n        <!-- Position of thumb in relation to the track. -->\n        <attr name=\"thumbPosition\">\n            <!-- The thumb's midpoint is anchored to the track. At its\n                 extremes, the thumb will extend half-way outside the\n                 track. -->\n            <enum name=\"midpoint\" value=\"0\" />\n            <!-- The thumb is entirely inside the track. At its extremes,\n                 the thumb will be contained entirely within the track. -->\n            <enum name=\"inside\" value=\"1\" />\n        </attr>\n    </declare-styleable>\n    <declare-styleable name=\"FrameLayout\">\n        <!-- Determines whether to measure all children or just those in\n             the VISIBLE or INVISIBLE state when measuring. Defaults to false. -->\n        <attr name=\"measureAllChildren\" format=\"boolean\" />\n    </declare-styleable>\n    <declare-styleable name=\"ExpandableListView\">\n        <!-- Indicator shown beside the group View. This can be a stateful Drawable. -->\n        <attr name=\"groupIndicator\" format=\"reference\" />\n        <!-- Indicator shown beside the child View. This can be a stateful Drawable. -->\n        <attr name=\"childIndicator\" format=\"reference\" />\n        <!-- The left bound for an item's indicator. To specify a left bound specific to children,\n             use childIndicatorLeft. -->\n        <attr name=\"indicatorLeft\" format=\"dimension\" />\n        <!-- The right bound for an item's indicator. To specify a right bound specific to children,\n             use childIndicatorRight. -->\n        <attr name=\"indicatorRight\" format=\"dimension\" />\n        <!-- The left bound for a child's indicator. -->\n        <attr name=\"childIndicatorLeft\" format=\"dimension\" />\n        <!-- The right bound for a child's indicator. -->\n        <attr name=\"childIndicatorRight\" format=\"dimension\" />\n        <!-- Drawable or color that is used as a divider for children. (It will drawn\n             below and above child items.) The height of this will be the same as\n             the height of the normal list item divider. -->\n        <attr name=\"childDivider\" format=\"reference|color\" />\n        <!-- The start bound for an item's indicator. To specify a start bound specific to children,\n             use childIndicatorStart. -->\n        <attr name=\"indicatorStart\" format=\"dimension\" />\n        <!-- The end bound for an item's indicator. To specify a right bound specific to children,\n             use childIndicatorEnd. -->\n        <attr name=\"indicatorEnd\" format=\"dimension\" />\n        <!-- The start bound for a child's indicator. -->\n        <attr name=\"childIndicatorStart\" format=\"dimension\" />\n        <!-- The end bound for a child's indicator. -->\n        <attr name=\"childIndicatorEnd\" format=\"dimension\" />\n    </declare-styleable>\n    <declare-styleable name=\"Gallery\">\n        <attr name=\"gravity\" />\n        <!-- Sets how long a transition animation should run (in milliseconds)\n             when layout has changed.  Only relevant if animation is turned on. -->\n        <attr name=\"animationDuration\" format=\"integer\" min=\"0\" />\n        <attr name=\"spacing\" format=\"dimension\" />\n        <!-- Sets the alpha on the items that are not selected. -->\n        <attr name=\"unselectedAlpha\" format=\"float\" />\n    </declare-styleable>\n    <declare-styleable name=\"GridView\">\n        <!-- Defines the default horizontal spacing between columns. -->\n        <attr name=\"horizontalSpacing\" format=\"dimension\" />\n        <!-- Defines the default vertical spacing between rows. -->\n        <attr name=\"verticalSpacing\" format=\"dimension\" />\n        <!-- Defines how columns should stretch to fill the available empty space, if any. -->\n        <attr name=\"stretchMode\">\n            <!-- Stretching is disabled. -->\n            <enum name=\"none\" value=\"0\"/>\n            <!-- The spacing between each column is stretched. -->\n            <enum name=\"spacingWidth\" value=\"1\" />\n            <!-- Each column is stretched equally. -->\n            <enum name=\"columnWidth\" value=\"2\" />\n            <!-- The spacing between each column is uniformly stretched.. -->\n            <enum name=\"spacingWidthUniform\" value=\"3\" />\n        </attr>\n        <!-- Specifies the fixed width for each column. -->\n        <attr name=\"columnWidth\" format=\"dimension\" />\n        <!-- Defines how many columns to show. -->\n        <attr name=\"numColumns\" format=\"integer\" min=\"0\">\n            <!-- Display as many columns as possible to fill the available space. -->\n            <enum name=\"auto_fit\" value=\"-1\" />\n        </attr>\n        <!-- Specifies the gravity within each cell. -->\n        <attr name=\"gravity\" />\n    </declare-styleable>\n    <declare-styleable name=\"ImageSwitcher\">\n    </declare-styleable>\n    <declare-styleable name=\"ImageView\">\n        <!-- Sets a drawable as the content of this ImageView. -->\n        <attr name=\"src\" format=\"reference|color\" />\n        <!-- Controls how the image should be resized or moved to match the size\n             of this ImageView.  See {@link android.widget.ImageView.ScaleType} -->\n        <attr name=\"scaleType\">\n            <!-- Scale using the image matrix when drawing. See\n                 {@link android.widget.ImageView#setImageMatrix(Matrix)}. -->\n            <enum name=\"matrix\" value=\"0\" />\n            <!-- Scale the image using {@link android.graphics.Matrix.ScaleToFit#FILL}. -->\n            <enum name=\"fitXY\" value=\"1\" />\n            <!-- Scale the image using {@link android.graphics.Matrix.ScaleToFit#START}. -->\n            <enum name=\"fitStart\" value=\"2\" />\n            <!-- Scale the image using {@link android.graphics.Matrix.ScaleToFit#CENTER}. -->\n            <enum name=\"fitCenter\" value=\"3\" />\n            <!-- Scale the image using {@link android.graphics.Matrix.ScaleToFit#END}. -->\n            <enum name=\"fitEnd\" value=\"4\" />\n            <!-- Center the image in the view, but perform no scaling. -->\n            <enum name=\"center\" value=\"5\" />\n            <!-- Scale the image uniformly (maintain the image's aspect ratio) so both dimensions\n                 (width and height) of the image will be equal to or larger than the corresponding\n                 dimension of the view (minus padding). The image is then centered in the view. -->\n            <enum name=\"centerCrop\" value=\"6\" />\n            <!-- Scale the image uniformly (maintain the image's aspect ratio) so that both\n                 dimensions (width and height) of the image will be equal to or less than the\n                 corresponding dimension of the view (minus padding). The image is then centered in\n                 the view. -->\n            <enum name=\"centerInside\" value=\"7\" />\n        </attr>\n        <!-- Set this to true if you want the ImageView to adjust its bounds\n             to preserve the aspect ratio of its drawable. -->\n        <attr name=\"adjustViewBounds\" format=\"boolean\" />\n        <!-- An optional argument to supply a maximum width for this view.\n             See {see android.widget.ImageView#setMaxWidth} for details. -->\n        <attr name=\"maxWidth\" format=\"dimension\" />\n        <!-- An optional argument to supply a maximum height for this view.\n             See {see android.widget.ImageView#setMaxHeight} for details. -->\n        <attr name=\"maxHeight\" format=\"dimension\" />\n        <!-- The tinting color for the image. By default, the tint will blend using SRC_ATOP mode.\n             Please note that for compatibility reasons, this is NOT consistent with the default\n             SRC_IN tint mode used by {@link android.widget.ImageView#setImageTintList} and by\n             similar tint attributes on other views. -->\n        <attr name=\"tint\" format=\"color\" />\n        <!-- If true, the image view will be baseline aligned with based on its\n             bottom edge. -->\n        <attr name=\"baselineAlignBottom\" format=\"boolean\" />\n         <!-- If true, the image will be cropped to fit within its padding. -->\n        <attr name=\"cropToPadding\" format=\"boolean\" />\n        <!-- The offset of the baseline within this view. See {see android.view.View#getBaseline}\n             for details -->\n        <attr name=\"baseline\" format=\"dimension\" />\n        <!-- @hide The alpha value (0-255) set on the ImageView's drawable. Equivalent\n             to calling ImageView.setAlpha(int), not the same as View.setAlpha(float). -->\n        <attr name=\"drawableAlpha\" format=\"integer\" />\n        <!-- Blending mode used to apply the image tint. -->\n        <attr name=\"tintMode\" />\n    </declare-styleable>\n    <declare-styleable name=\"ToggleButton\">\n        <!-- The text for the button when it is checked. -->\n        <attr name=\"textOn\" format=\"string\" />\n        <!-- The text for the button when it is not checked. -->\n        <attr name=\"textOff\" format=\"string\" />\n        <!-- The alpha to apply to the indicator when disabled. -->\n        <attr name=\"disabledAlpha\" />\n    </declare-styleable>\n    <declare-styleable name=\"RelativeLayout\">\n        <attr name=\"gravity\" />\n        <!-- Indicates what view should not be affected by gravity. -->\n        <attr name=\"ignoreGravity\" format=\"reference\" />\n    </declare-styleable>\n    <declare-styleable name=\"LinearLayout\">\n        <!-- Should the layout be a column or a row?  Use \"horizontal\"\n             for a row, \"vertical\" for a column.  The default is\n             horizontal. -->\n        <attr name=\"orientation\" />\n        <attr name=\"gravity\" />\n        <!-- When set to false, prevents the layout from aligning its children's\n             baselines. This attribute is particularly useful when the children\n             use different values for gravity. The default value is true. -->\n        <attr name=\"baselineAligned\" format=\"boolean\" />\n        <!-- When a linear layout is part of another layout that is baseline\n          aligned, it can specify which of its children to baseline align to\n          (that is, which child TextView).-->\n        <attr name=\"baselineAlignedChildIndex\" format=\"integer\" min=\"0\"/>\n        <!-- Defines the maximum weight sum. If unspecified, the sum is computed\n             by adding the layout_weight of all of the children. This can be\n             used for instance to give a single child 50% of the total available\n             space by giving it a layout_weight of 0.5 and setting the weightSum\n             to 1.0. -->\n        <attr name=\"weightSum\" format=\"float\" />\n        <!-- When set to true, all children with a weight will be considered having\n             the minimum size of the largest child. If false, all children are\n             measured normally. -->\n        <attr name=\"measureWithLargestChild\" format=\"boolean\" />\n        <!-- Drawable to use as a vertical divider between buttons. -->\n        <attr name=\"divider\" />\n        <!-- Setting for which dividers to show. -->\n        <attr name=\"showDividers\">\n            <flag name=\"none\" value=\"0\" />\n            <flag name=\"beginning\" value=\"1\" />\n            <flag name=\"middle\" value=\"2\" />\n            <flag name=\"end\" value=\"4\" />\n        </attr>\n        <!-- Size of padding on either end of a divider. -->\n        <attr name=\"dividerPadding\" format=\"dimension\" />\n    </declare-styleable>\n    <declare-styleable name=\"GridLayout\">\n        <!-- The orientation property is not used during layout. It is only used to\n        allocate row and column parameters when they are not specified by its children's\n        layout paramters. GridLayout works like LinearLayout in this case;\n        putting all the components either in a single row or in a single column -\n        depending on the value of this flag. In the horizontal case, a columnCount\n        property may be additionally supplied to force new rows to be created when a\n        row is full. The rowCount attribute may be used similarly in the vertical case.\n        The default is horizontal. -->\n        <attr name=\"orientation\" />\n        <!-- The maximum number of rows to create when automatically positioning children. -->\n        <attr name=\"rowCount\" format=\"integer\" />\n        <!-- The maximum number of columns to create when automatically positioning children. -->\n        <attr name=\"columnCount\" format=\"integer\" />\n        <!-- When set to true, tells GridLayout to use default margins when none are specified\n        in a view's layout parameters.\n        The default value is false.\n        See {@link android.widget.GridLayout#setUseDefaultMargins(boolean)}.-->\n        <attr name=\"useDefaultMargins\" format=\"boolean\" />\n        <!-- When set to alignMargins, causes alignment to take place between the outer\n        boundary of a view, as defined by its margins. When set to alignBounds,\n        causes alignment to take place between the edges of the view.\n        The default is alignMargins.\n        See {@link android.widget.GridLayout#setAlignmentMode(int)}.-->\n        <attr name=\"alignmentMode\" />\n        <!-- When set to true, forces row boundaries to appear in the same order\n        as row indices.\n        The default is true.\n        See {@link android.widget.GridLayout#setRowOrderPreserved(boolean)}.-->\n        <attr name=\"rowOrderPreserved\" format=\"boolean\" />\n        <!-- When set to true, forces column boundaries to appear in the same order\n        as column indices.\n        The default is true.\n        See {@link android.widget.GridLayout#setColumnOrderPreserved(boolean)}.-->\n        <attr name=\"columnOrderPreserved\" format=\"boolean\" />\n    </declare-styleable>\n    <declare-styleable name=\"ListView\">\n        <!-- Reference to an array resource that will populate the ListView.  For static content,\n             this is simpler than populating the ListView programmatically. -->\n        <attr name=\"entries\" />\n        <!-- Drawable or color to draw between list items. -->\n        <attr name=\"divider\" format=\"reference|color\" />\n        <!-- Height of the divider. Will use the intrinsic height of the divider if this\n             is not specified. -->\n        <attr name=\"dividerHeight\" format=\"dimension\" />\n        <!-- When set to false, the ListView will not draw the divider after each header view.\n             The default value is true. -->\n        <attr name=\"headerDividersEnabled\" format=\"boolean\" />\n        <!-- When set to false, the ListView will not draw the divider before each footer view.\n             The default value is true. -->\n        <attr name=\"footerDividersEnabled\" format=\"boolean\" />\n        <!-- Drawable to draw above list content. -->\n        <attr name=\"overScrollHeader\" format=\"reference|color\" />\n        <!-- Drawable to draw below list content. -->\n        <attr name=\"overScrollFooter\" format=\"reference|color\" />\n    </declare-styleable>\n    <declare-styleable name=\"PreferenceFrameLayout\">\n        <!-- Padding to use at the top of the prefs content. -->\n        <attr name=\"borderTop\" format=\"dimension\" />\n        <!-- Padding to use at the bottom of the prefs content. -->\n        <attr name=\"borderBottom\" format=\"dimension\" />\n        <!-- Padding to use at the left of the prefs content. -->\n        <attr name=\"borderLeft\" format=\"dimension\" />\n        <!-- Padding to use at the right of the prefs content. -->\n        <attr name=\"borderRight\" format=\"dimension\" />\n    </declare-styleable>\n    <declare-styleable name=\"PreferenceFrameLayout_Layout\">\n        <!-- Padding to use at the top of the prefs content. -->\n        <attr name=\"layout_removeBorders\" format=\"boolean\" />\n    </declare-styleable>\n    <declare-styleable name=\"MenuView\">\n        <!-- Default appearance of menu item text. -->\n        <attr name=\"itemTextAppearance\" format=\"reference\" />\n        <!-- Default horizontal divider between rows of menu items. -->\n        <attr name=\"horizontalDivider\" format=\"reference\" />\n        <!-- Default vertical divider between menu items. -->\n        <attr name=\"verticalDivider\" format=\"reference\" />\n        <!-- Default background for the menu header. -->\n        <attr name=\"headerBackground\" format=\"color|reference\" />\n        <!-- Default background for each menu item. -->\n        <attr name=\"itemBackground\" format=\"color|reference\" />\n        <!-- Default animations for the menu. -->\n        <attr name=\"windowAnimationStyle\" />\n        <!-- Default disabled icon alpha for each menu item that shows an icon. -->\n        <attr name=\"itemIconDisabledAlpha\" format=\"float\" />\n        <!-- Whether space should be reserved in layout when an icon is missing. -->\n        <attr name=\"preserveIconSpacing\" format=\"boolean\" />\n        <!-- Drawable for the arrow icon indicating a particular item is a submenu. -->\n        <attr name=\"subMenuArrow\" format=\"reference\" />\n    </declare-styleable>\n    <declare-styleable name=\"IconMenuView\">\n        <!-- Defines the height of each row. -->\n        <attr name=\"rowHeight\" format=\"dimension\" />\n        <!-- Defines the maximum number of rows displayed. -->\n        <attr name=\"maxRows\" format=\"integer\" />\n        <!-- Defines the maximum number of items per row. -->\n        <attr name=\"maxItemsPerRow\" format=\"integer\" />\n        <!-- Defines the maximum number of items to show. -->\n        <attr name=\"maxItems\" format=\"integer\" />\n        <!-- 'More' icon. -->\n        <attr name=\"moreIcon\" format=\"reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"ProgressBar\">\n        <!-- Defines the minimum value. -->\n        <attr name=\"min\" format=\"integer\" />\n        <!-- Defines the maximum value. -->\n        <attr name=\"max\" format=\"integer\" />\n        <!-- Defines the default progress value, between 0 and max. -->\n        <attr name=\"progress\" format=\"integer\" />\n        <!-- Defines the secondary progress value, between 0 and max. This progress is drawn between\n             the primary progress and the background.  It can be ideal for media scenarios such as\n             showing the buffering progress while the default progress shows the play progress. -->\n        <attr name=\"secondaryProgress\" format=\"integer\" />\n        <!-- Allows to enable the indeterminate mode. In this mode the progress\n         bar plays an infinite looping animation. -->\n        <attr name=\"indeterminate\" format=\"boolean\" />\n        <!-- Restricts to ONLY indeterminate mode (state-keeping progress mode will not work). -->\n        <attr name=\"indeterminateOnly\" format=\"boolean\" />\n        <!-- Drawable used for the indeterminate mode. One that implements Animatable offers more\n             control over the animation.-->\n        <attr name=\"indeterminateDrawable\" format=\"reference\" />\n        <!-- Drawable used for the progress mode. -->\n        <attr name=\"progressDrawable\" format=\"reference\" />\n        <!-- Duration of the indeterminate animation. Only affects the indeterminate animation\n             if the indeterminate Drawable does not implement\n             android.graphics.drawable.Animatable. -->\n        <attr name=\"indeterminateDuration\" format=\"integer\" min=\"1\" />\n        <!-- Defines how the indeterminate mode should behave when the progress reaches max. Only\n             affects the indeterminate animation if the indeterminate Drawable does not implement\n             android.graphics.drawable.Animatable. -->\n        <attr name=\"indeterminateBehavior\">\n            <!-- Progress starts over from 0. -->\n            <enum name=\"repeat\" value=\"1\" />\n            <!-- Progress keeps the current value and goes back to 0. -->\n            <enum name=\"cycle\" value=\"2\" />\n        </attr>\n        <attr name=\"minWidth\" format=\"dimension\" />\n        <attr name=\"maxWidth\" />\n        <attr name=\"minHeight\" format=\"dimension\" />\n        <attr name=\"maxHeight\" />\n        <!-- Sets the acceleration curve for the indeterminate animation. Defaults to a linear\n             interpolation. Only affects the indeterminate animation if the indeterminate Drawable\n             does not implement android.graphics.drawable.Animatable.-->\n        <attr name=\"interpolator\" format=\"reference\" />\n        <!-- Timeout between frames of animation in milliseconds.\n             {@deprecated Not used by the framework}. -->\n        <attr name=\"animationResolution\" format=\"integer\" />\n        <!-- Defines if the associated drawables need to be mirrored when in RTL mode.\n             Default is false. -->\n        <attr name=\"mirrorForRtl\" format=\"boolean\" />\n        <!-- Tint to apply to the progress indicator. -->\n        <attr name=\"progressTint\" format=\"color\" />\n        <!-- Blending mode used to apply the progress indicator tint. -->\n        <attr name=\"progressTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n        <!-- Tint to apply to the progress indicator background. -->\n        <attr name=\"progressBackgroundTint\" format=\"color\" />\n        <!-- Blending mode used to apply the progress indicator background tint. -->\n        <attr name=\"progressBackgroundTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n        <!-- Tint to apply to the secondary progress indicator. -->\n        <attr name=\"secondaryProgressTint\" format=\"color\" />\n        <!-- Blending mode used to apply the secondary progress indicator tint. -->\n        <attr name=\"secondaryProgressTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n        <!-- Tint to apply to the indeterminate progress indicator. -->\n        <attr name=\"indeterminateTint\" format=\"color\" />\n        <!-- Blending mode used to apply the indeterminate progress indicator tint. -->\n        <attr name=\"indeterminateTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n        <!-- Tint to apply to the background. -->\n        <attr name=\"backgroundTint\" />\n        <!-- Blending mode used to apply the background tint. -->\n        <attr name=\"backgroundTintMode\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"SeekBar\">\n        <!-- Draws the thumb on a seekbar. -->\n        <attr name=\"thumb\" format=\"reference\" />\n        <!-- An offset for the thumb that allows it to extend out of the range of the track. -->\n        <attr name=\"thumbOffset\" format=\"dimension\" />\n        <!-- Whether to split the track and leave a gap for the thumb drawable. -->\n        <attr name=\"splitTrack\" format=\"boolean\" />\n        <!-- Whether to force the track's alpha to ?android:attr/disabledAlpha\n             when disabled. This is required for Holo and Gingerbread, but\n             should always be false for Material and  beyond.\n             @hide Developers shouldn't need to change this. -->\n        <attr name=\"useDisabledAlpha\" format=\"boolean\" />\n        <!-- Tint to apply to the thumb drawable. -->\n        <attr name=\"thumbTint\" format=\"color\" />\n        <!-- Blending mode used to apply the thumb tint. -->\n        <attr name=\"thumbTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n        <!-- Drawable displayed at each progress position on a seekbar. -->\n        <attr name=\"tickMark\" format=\"reference\" />\n        <!-- Tint to apply to the tick mark drawable. -->\n        <attr name=\"tickMarkTint\" format=\"color\" />\n        <!-- Blending mode used to apply the tick mark tint. -->\n        <attr name=\"tickMarkTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n    </declare-styleable>\n\n    <declare-styleable name=\"StackView\">\n        <!-- Color of the res-out outline. -->\n        <attr name=\"resOutColor\" format=\"color\" />\n        <!-- Color of the outline of click feedback. -->\n        <attr name=\"clickColor\" format=\"color\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"RatingBar\">\n        <!-- The number of stars (or rating items) to show. -->\n        <attr name=\"numStars\" format=\"integer\" />\n        <!-- The rating to set by default. -->\n        <attr name=\"rating\" format=\"float\" />\n        <!-- The step size of the rating. -->\n        <attr name=\"stepSize\" format=\"float\" />\n        <!-- Whether this rating bar is an indicator (and non-changeable by the user). -->\n        <attr name=\"isIndicator\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"RadioGroup\">\n        <!-- The id of the child radio button that should be checked by default\n             within this radio group. -->\n        <attr name=\"checkedButton\" format=\"integer\" />\n        <!-- Should the radio group be a column or a row?  Use \"horizontal\"\n             for a row, \"vertical\" for a column.  The default is\n             vertical. -->\n        <attr name=\"orientation\" />\n    </declare-styleable>\n    <declare-styleable name=\"TableLayout\">\n        <!-- The zero-based index of the columns to stretch. The column indices\n             must be separated by a comma: 1, 2, 5. Illegal and duplicate\n             indices are ignored. You can stretch all columns by using the\n             value \"*\" instead. Note that a column can be marked stretchable\n             and shrinkable at the same time. -->\n        <attr name=\"stretchColumns\" format=\"string\" />\n       <!-- The zero-based index of the columns to shrink. The column indices\n             must be separated by a comma: 1, 2, 5. Illegal and duplicate\n             indices are ignored. You can shrink all columns by using the\n             value \"*\" instead. Note that a column can be marked stretchable\n             and shrinkable at the same time. -->\n        <attr name=\"shrinkColumns\" format=\"string\" />\n        <!-- The zero-based index of the columns to collapse. The column indices\n             must be separated by a comma: 1, 2, 5. Illegal and duplicate\n             indices are ignored. -->\n        <attr name=\"collapseColumns\" format=\"string\" />\n    </declare-styleable>\n    <declare-styleable name=\"TableRow\">\n\n    </declare-styleable>\n    <declare-styleable name=\"TableRow_Cell\">\n        <!-- The index of the column in which this child should be. -->\n        <attr name=\"layout_column\" format=\"integer\" />\n        <!-- Defines how many columns this child should span.  Must be >= 1.-->\n        <attr name=\"layout_span\" format=\"integer\" />\n    </declare-styleable>\n    <declare-styleable name=\"TabWidget\">\n        <!-- Drawable used to draw the divider between tabs. -->\n        <attr name=\"divider\" />\n        <!-- Determines whether the strip under the tab indicators is drawn or not. -->\n        <attr name=\"tabStripEnabled\" format=\"boolean\" />\n        <!-- Drawable used to draw the left part of the strip underneath the tabs. -->\n        <attr name=\"tabStripLeft\" format=\"reference\" />\n        <!-- Drawable used to draw the right part of the strip underneath the tabs. -->\n        <attr name=\"tabStripRight\" format=\"reference\" />\n        <!-- Layout used to organize each tab's content. -->\n        <attr name=\"tabLayout\" format=\"reference\" />\n    </declare-styleable>\n    <declare-styleable name=\"TextAppearance\">\n        <!-- Text color. -->\n        <attr name=\"textColor\" />\n        <!-- Size of the text. Recommended dimension type for text is \"sp\" for scaled-pixels (example: 15sp). -->\n        <attr name=\"textSize\" />\n        <!-- Style (normal, bold, italic, bold|italic) for the text. -->\n        <attr name=\"textStyle\" />\n        <!-- Weight for the font used in the TextView. -->\n        <attr name=\"textFontWeight\" />\n        <!-- Typeface (normal, sans, serif, monospace) for the text. -->\n        <attr name=\"typeface\" />\n        <!-- Font family (named by string or as a font resource reference) for the text. -->\n        <attr name=\"fontFamily\" />\n        <!-- Specifies the {@link android.os.LocaleList} for the text.\n             May be a string value, which is a comma-separated language tag list, such as \"ja-JP,zh-CN\".\n             When not specified or an empty string is given, it will fallback to the default one.\n             {@see android.os.LocaleList#forLanguageTags(String)} -->\n        <attr name=\"textLocale\" format=\"string\" />\n        <!-- Color of the text selection highlight. -->\n        <attr name=\"textColorHighlight\" />\n        <!-- Color of the hint text. -->\n        <attr name=\"textColorHint\" />\n        <!-- Color of the links. -->\n        <attr name=\"textColorLink\" />\n        <!-- Present the text in ALL CAPS. This may use a small-caps form when available. -->\n        <attr name=\"textAllCaps\" format=\"boolean\" />\n        <!-- Place a blurred shadow of text underneath the text, drawn with the\n             specified color. The text shadow produced does not interact with\n             properties on View that are responsible for real time shadows,\n             {@link android.R.styleable#View_elevation elevation} and\n             {@link android.R.styleable#View_translationZ translationZ}. -->\n        <attr name=\"shadowColor\" format=\"color\" />\n        <!-- Horizontal offset of the text shadow. -->\n        <attr name=\"shadowDx\" format=\"float\" />\n        <!-- Vertical offset of the text shadow. -->\n        <attr name=\"shadowDy\" format=\"float\" />\n        <!-- Blur radius of the text shadow. -->\n        <attr name=\"shadowRadius\" format=\"float\" />\n        <!-- Elegant text height, especially for less compacted complex script text. -->\n        <attr name=\"elegantTextHeight\" format=\"boolean\" />\n        <!-- Whether to respect the ascent and descent of the fallback fonts that are used in\n        displaying the text. When true, fallback fonts that end up getting used can increase\n        the ascent and descent of the lines that they are used on. -->\n        <attr name=\"fallbackLineSpacing\" format=\"boolean\"/>\n        <!-- Text letter-spacing. -->\n        <attr name=\"letterSpacing\" format=\"float\" />\n        <!-- Font feature settings. -->\n        <attr name=\"fontFeatureSettings\" format=\"string\" />\n        <!-- Font variation settings. -->\n        <attr name=\"fontVariationSettings\" format=\"string\"/>\n    </declare-styleable>\n    <declare-styleable name=\"TextClock\">\n        <!-- Specifies the formatting pattern used to show the time and/or date\n             in 12-hour mode. Please refer to {@link android.text.format.DateFormat}\n             for a complete description of accepted formatting patterns.\n             The default pattern is a locale-appropriate equivalent of \"h:mm a\". -->\n        <attr name=\"format12Hour\" format=\"string\"/>\n        <!-- Specifies the formatting pattern used to show the time and/or date\n             in 24-hour mode. Please refer to {@link android.text.format.DateFormat}\n             for a complete description of accepted formatting patterns.\n             The default pattern is a locale-appropriate equivalent of \"H:mm\". -->\n        <attr name=\"format24Hour\" format=\"string\"/>\n        <!-- Specifies the time zone to use. When this attribute is specified, the\n             TextClock will ignore the time zone of the system. To use the user's\n             time zone, do not specify this attribute. The default value is the\n             user's time zone. Please refer to {@link java.util.TimeZone} for more\n             information about time zone ids. -->\n        <attr name=\"timeZone\" format=\"string\"/>\n    </declare-styleable>\n    <declare-styleable name=\"TextSwitcher\">\n    </declare-styleable>\n    <declare-styleable name=\"TextView\">\n        <!-- Determines the minimum type that getText() will return.\n             The default is \"normal\".\n             Note that EditText and LogTextBox always return Editable,\n             even if you specify something less powerful here. -->\n        <attr name=\"bufferType\">\n            <!-- Can return any CharSequence, possibly a\n             Spanned one if the source text was Spanned. -->\n            <enum name=\"normal\" value=\"0\" />\n            <!-- Can only return Spannable. -->\n            <enum name=\"spannable\" value=\"1\" />\n            <!-- Can only return Spannable and Editable. -->\n            <enum name=\"editable\" value=\"2\" />\n        </attr>\n        <!-- Text to display. -->\n        <attr name=\"text\" format=\"string\" localization=\"suggested\" />\n        <!-- Hint text to display when the text is empty. -->\n        <attr name=\"hint\" format=\"string\" />\n        <!-- Text color. -->\n        <attr name=\"textColor\" />\n        <!-- Color of the text selection highlight. -->\n        <attr name=\"textColorHighlight\" />\n        <!-- Color of the hint text. -->\n        <attr name=\"textColorHint\" />\n        <!-- Base text color, typeface, size, and style. -->\n        <attr name=\"textAppearance\" />\n        <!-- Size of the text. Recommended dimension type for text is \"sp\" for scaled-pixels (example: 15sp). -->\n        <attr name=\"textSize\" />\n        <!-- Sets the horizontal scaling factor for the text. -->\n        <attr name=\"textScaleX\" format=\"float\" />\n        <!-- Typeface (normal, sans, serif, monospace) for the text. -->\n        <attr name=\"typeface\" />\n        <!-- Style (normal, bold, italic, bold|italic) for the text. -->\n        <attr name=\"textStyle\" />\n        <!-- Weight for the font used in the TextView. -->\n        <attr name=\"textFontWeight\" format=\"integer\"/>\n        <!-- Font family (named by string or as a font resource reference) for the text. -->\n        <attr name=\"fontFamily\" />\n        <!-- Specifies the {@link android.os.LocaleList} for the text in this TextView.\n             If not given, the system default will be used.\n             May be a string value, which is a comma-separated language tag list, such as \"ja-JP,zh-CN\".\n             When not specified or an empty string is given, it will fallback to the default one.\n             {@see android.os.LocaleList#forLanguageTags(String)}\n             {@see android.widget.TextView#setTextLocales(android.os.LocaleList)} -->\n        <attr name=\"textLocale\" format=\"string\" />\n        <!-- Text color for links. -->\n        <attr name=\"textColorLink\" />\n        <!-- Makes the cursor visible (the default) or invisible. -->\n        <attr name=\"cursorVisible\" format=\"boolean\" />\n        <!-- Makes the TextView be at most this many lines tall.\n\n        When used on an editable text, the <code>inputType</code> attribute's value must be\n        combined with the <code>textMultiLine</code> flag for the maxLines attribute to apply. -->\n        <attr name=\"maxLines\" format=\"integer\" min=\"0\" />\n        <!-- Makes the TextView be at most this many pixels tall. -->\n        <attr name=\"maxHeight\" />\n        <!-- Makes the TextView be exactly this many lines tall. -->\n        <attr name=\"lines\" format=\"integer\" min=\"0\" />\n        <!-- Makes the TextView be exactly this tall.\n             You could get the same effect by specifying this number in the\n             layout parameters. -->\n        <attr name=\"height\" format=\"dimension\" />\n        <!-- Makes the TextView be at least this many lines tall.\n\n        When used on an editable text, the <code>inputType</code> attribute's value must be\n        combined with the <code>textMultiLine</code> flag for the minLines attribute to apply. -->\n        <attr name=\"minLines\" format=\"integer\" min=\"0\" />\n        <!-- Makes the TextView be at least this many pixels tall. -->\n        <attr name=\"minHeight\" />\n        <!-- Makes the TextView be at most this many ems wide. -->\n        <attr name=\"maxEms\" format=\"integer\" min=\"0\" />\n        <!-- Makes the TextView be at most this many pixels wide. -->\n        <attr name=\"maxWidth\" />\n        <!-- Makes the TextView be exactly this many ems wide. -->\n        <attr name=\"ems\" format=\"integer\" min=\"0\" />\n        <!-- Makes the TextView be exactly this wide.\n             You could get the same effect by specifying this number in the\n             layout parameters. -->\n        <attr name=\"width\" format=\"dimension\" />\n        <!-- Makes the TextView be at least this many ems wide. -->\n        <attr name=\"minEms\" format=\"integer\" min=\"0\" />\n        <!-- Makes the TextView be at least this many pixels wide. -->\n        <attr name=\"minWidth\" />\n        <!-- Specifies how to align the text by the view's x- and/or y-axis\n             when the text is smaller than the view. -->\n        <attr name=\"gravity\" />\n        <!-- Whether the text is allowed to be wider than the view (and\n             therefore can be scrolled horizontally). -->\n        <attr name=\"scrollHorizontally\" format=\"boolean\" />\n        <!-- Whether the characters of the field are displayed as\n             password dots instead of themselves.\n             {@deprecated Use inputType instead.} -->\n        <attr name=\"password\" format=\"boolean\" />\n        <!-- Constrains the text to a single horizontally scrolling line\n             instead of letting it wrap onto multiple lines, and advances\n             focus instead of inserting a newline when you press the\n             enter key.\n\n             The default value is false (multi-line wrapped text mode) for non-editable text, but if\n             you specify any value for inputType, the default is true (single-line input field mode).\n\n             {@deprecated This attribute is deprecated. Use <code>maxLines</code> instead to change\n             the layout of a static text, and use the <code>textMultiLine</code> flag in the\n             inputType attribute instead for editable text views (if both singleLine and inputType\n             are supplied, the inputType flags will override the value of singleLine). } -->\n        <attr name=\"singleLine\" format=\"boolean\" />\n        <!-- Specifies whether the widget is enabled. The interpretation of the enabled state varies by subclass.\n             For example, a non-enabled EditText prevents the user from editing the contained text, and\n             a non-enabled Button prevents the user from tapping the button.\n             The appearance of enabled and non-enabled widgets may differ, if the drawables referenced\n             from evaluating state_enabled differ. -->\n        <attr name=\"enabled\" format=\"boolean\" />\n        <!-- If the text is selectable, select it all when the view takes\n             focus. -->\n        <attr name=\"selectAllOnFocus\" format=\"boolean\" />\n        <!-- Leave enough room for ascenders and descenders instead of\n             using the font ascent and descent strictly.  (Normally true). -->\n        <attr name=\"includeFontPadding\" format=\"boolean\" />\n        <!-- Set an input filter to constrain the text length to the\n             specified number. -->\n        <attr name=\"maxLength\" format=\"integer\" min=\"0\" />\n        <!-- Place a blurred shadow of text underneath the text, drawn with the\n             specified color. The text shadow produced does not interact with\n             properties on View that are responsible for real time shadows,\n             {@link android.R.styleable#View_elevation elevation} and\n             {@link android.R.styleable#View_translationZ translationZ}. -->\n        <attr name=\"shadowColor\" />\n        <!-- Horizontal offset of the text shadow. -->\n        <attr name=\"shadowDx\" />\n        <!-- Vertical offset of the text shadow. -->\n        <attr name=\"shadowDy\" />\n        <!-- Blur radius of the text shadow. -->\n        <attr name=\"shadowRadius\" />\n        <attr name=\"autoLink\" />\n        <!-- If set to false, keeps the movement method from being set\n             to the link movement method even if autoLink causes links\n             to be found. -->\n        <attr name=\"linksClickable\" format=\"boolean\" />\n        <!-- If set, specifies that this TextView has a numeric input method.\n             The default is false.\n             {@deprecated Use inputType instead.} -->\n        <attr name=\"numeric\">\n            <!-- Input is numeric. -->\n            <flag name=\"integer\" value=\"0x01\" />\n            <!-- Input is numeric, with sign allowed. -->\n            <flag name=\"signed\" value=\"0x03\" />\n            <!-- Input is numeric, with decimals allowed. -->\n            <flag name=\"decimal\" value=\"0x05\" />\n        </attr>\n        <!-- If set, specifies that this TextView has a numeric input method\n             and that these specific characters are the ones that it will\n             accept.\n             If this is set, numeric is implied to be true.\n             The default is false. -->\n        <attr name=\"digits\" format=\"string\" />\n        <!-- If set, specifies that this TextView has a phone number input\n             method. The default is false.\n             {@deprecated Use inputType instead.} -->\n        <attr name=\"phoneNumber\" format=\"boolean\" />\n        <!-- If set, specifies that this TextView should use the specified\n             input method (specified by fully-qualified class name).\n             {@deprecated Use inputType instead.} -->\n        <attr name=\"inputMethod\" format=\"string\" />\n        <!-- If set, specifies that this TextView has a textual input method\n             and should automatically capitalize what the user types.\n             The default is \"none\".\n             {@deprecated Use inputType instead.} -->\n        <attr name=\"capitalize\">\n            <!-- Don't automatically capitalize anything. -->\n            <enum name=\"none\" value=\"0\" />\n            <!-- Capitalize the first word of each sentence. -->\n            <enum name=\"sentences\" value=\"1\" />\n            <!-- Capitalize the first letter of every word. -->\n            <enum name=\"words\" value=\"2\" />\n            <!-- Capitalize every character. -->\n            <enum name=\"characters\" value=\"3\" />\n        </attr>\n        <!-- If set, specifies that this TextView has a textual input method\n             and automatically corrects some common spelling errors.\n             The default is \"false\".\n             {@deprecated Use inputType instead.} -->\n        <attr name=\"autoText\" format=\"boolean\" />\n        <!-- If set, specifies that this TextView has an input method.\n             It will be a textual one unless it has otherwise been specified.\n             For TextView, this is false by default.  For EditText, it is\n             true by default.\n             {@deprecated Use inputType instead.} -->\n        <attr name=\"editable\" format=\"boolean\" />\n        <!-- If set, the text view will include its current complete text\n             inside of its frozen icicle in addition to meta-data such as\n             the current cursor position.  By default this is disabled;\n             it can be useful when the contents of a text view is not stored\n             in a persistent place such as a content provider. For\n             {@link android.widget.EditText} it is always enabled, regardless\n             of the value of the attribute. -->\n        <attr name=\"freezesText\" format=\"boolean\" />\n        <!-- If set, causes words that are longer than the view is wide\n             to be ellipsized instead of broken in the middle.\n             You will often also want to set scrollHorizontally or singleLine\n             as well so that the text as a whole is also constrained to\n             a single line instead of still allowed to be broken onto\n             multiple lines. -->\n        <attr name=\"ellipsize\" />\n        <!-- The drawable to be drawn above the text. -->\n        <attr name=\"drawableTop\" format=\"reference|color\" />\n        <!-- The drawable to be drawn below the text. -->\n        <attr name=\"drawableBottom\" format=\"reference|color\" />\n        <!-- The drawable to be drawn to the left of the text. -->\n        <attr name=\"drawableLeft\" format=\"reference|color\" />\n        <!-- The drawable to be drawn to the right of the text. -->\n        <attr name=\"drawableRight\" format=\"reference|color\" />\n        <!-- The drawable to be drawn to the start of the text. -->\n        <attr name=\"drawableStart\" format=\"reference|color\" />\n        <!-- The drawable to be drawn to the end of the text. -->\n        <attr name=\"drawableEnd\" format=\"reference|color\" />\n        <!-- The padding between the drawables and the text. -->\n        <attr name=\"drawablePadding\" format=\"dimension\" />\n        <!-- Tint to apply to the compound (left, top, etc.) drawables. -->\n        <attr name=\"drawableTint\" format=\"color\" />\n        <!-- Blending mode used to apply the compound (left, top, etc.) drawables tint. -->\n        <attr name=\"drawableTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n        <!-- Extra spacing between lines of text. The value will not be applied for the last\n             line of text. -->\n        <attr name=\"lineSpacingExtra\" format=\"dimension\" />\n        <!-- Extra spacing between lines of text, as a multiplier. The value will not be applied\n             for the last line of text.-->\n        <attr name=\"lineSpacingMultiplier\" format=\"float\" />\n        <!-- Explicit height between lines of text. If set, this will override the values set\n             for lineSpacingExtra and lineSpacingMultiplier. -->\n        <attr name=\"lineHeight\" format=\"dimension\" />\n        <!-- Distance from the top of the TextView to the first text baseline. If set, this\n             overrides the value set for paddingTop. -->\n        <attr name=\"firstBaselineToTopHeight\" format=\"dimension\" />\n        <!-- Distance from the bottom of the TextView to the last text baseline. If set, this\n             overrides the value set for paddingBottom. -->\n        <attr name=\"lastBaselineToBottomHeight\" format=\"dimension\" />\n        <!-- The number of times to repeat the marquee animation. Only applied if the\n             TextView has marquee enabled. -->\n        <attr name=\"marqueeRepeatLimit\" format=\"integer\">\n            <!-- Indicates that marquee should repeat indefinitely. -->\n            <enum name=\"marquee_forever\" value=\"-1\" />\n        </attr>\n        <attr name=\"inputType\" />\n        <!-- Whether undo should be allowed for editable text. Defaults to true. -->\n        <attr name=\"allowUndo\" format=\"boolean\" />\n        <attr name=\"imeOptions\" />\n        <!-- An addition content type description to supply to the input\n             method attached to the text view, which is private to the\n             implementation of the input method.  This simply fills in\n             the {@link android.view.inputmethod.EditorInfo#privateImeOptions\n             EditorInfo.privateImeOptions} field when the input\n             method is connected. -->\n        <attr name=\"privateImeOptions\" format=\"string\" />\n        <!-- Supply a value for\n             {@link android.view.inputmethod.EditorInfo#actionLabel EditorInfo.actionLabel}\n             used when an input method is connected to the text view. -->\n        <attr name=\"imeActionLabel\" format=\"string\" />\n        <!-- Supply a value for\n             {@link android.view.inputmethod.EditorInfo#actionId EditorInfo.actionId}\n             used when an input method is connected to the text view. -->\n        <attr name=\"imeActionId\" format=\"integer\" />\n        <!-- Reference to an\n             {@link android.R.styleable#InputExtras &lt;input-extras&gt;}\n             XML resource containing additional data to\n             supply to an input method, which is private to the implementation\n             of the input method.  This simply fills in\n             the {@link android.view.inputmethod.EditorInfo#extras\n             EditorInfo.extras} field when the input\n             method is connected. -->\n        <attr name=\"editorExtras\" format=\"reference\" />\n\n        <!-- Reference to a drawable that will be used to display a text selection\n             anchor on the left side of a selection region. -->\n        <attr name=\"textSelectHandleLeft\" />\n        <!-- Reference to a drawable that will be used to display a text selection\n             anchor on the right side of a selection region. -->\n        <attr name=\"textSelectHandleRight\" />\n        <!-- Reference to a drawable that will be used to display a text selection\n             anchor for positioning the cursor within text. -->\n        <attr name=\"textSelectHandle\" />\n        <!-- The layout of the view that is displayed on top of the cursor to paste inside a\n             TextEdit field. -->\n        <attr name=\"textEditPasteWindowLayout\" />\n        <!-- Variation of textEditPasteWindowLayout displayed when the clipboard is empty. -->\n        <attr name=\"textEditNoPasteWindowLayout\" />\n        <!-- Used instead of textEditPasteWindowLayout when the window is moved on the side of the\n             insertion cursor because it would be clipped if it were positioned on top. -->\n        <attr name=\"textEditSidePasteWindowLayout\" />\n        <!-- Variation of textEditSidePasteWindowLayout displayed when the clipboard is empty. -->\n        <attr name=\"textEditSideNoPasteWindowLayout\" />\n\n        <!-- Layout of the TextView item that will populate the suggestion popup window. -->\n        <attr name=\"textEditSuggestionItemLayout\" />\n        <!-- Layout of the container of the suggestion popup window. -->\n        <attr name=\"textEditSuggestionContainerLayout\" />\n        <!-- Style of the highlighted string in the suggestion popup window. -->\n        <attr name=\"textEditSuggestionHighlightStyle\" />\n\n\n        <!-- Reference to a drawable that will be drawn under the insertion cursor. -->\n        <attr name=\"textCursorDrawable\" />\n\n        <!-- Indicates that the content of a non-editable text can be selected. -->\n        <attr name=\"textIsSelectable\" />\n        <!-- Present the text in ALL CAPS. This may use a small-caps form when available. -->\n        <attr name=\"textAllCaps\" />\n        <!-- Elegant text height, especially for less compacted complex script text. -->\n        <attr name=\"elegantTextHeight\" />\n        <!-- Whether to respect the ascent and descent of the fallback fonts that are used in\n        displaying the text. When true, fallback fonts that end up getting used can increase\n        the ascent and descent of the lines that they are used on. -->\n        <attr name=\"fallbackLineSpacing\" format=\"boolean\"/>\n        <!-- Text letter-spacing. -->\n        <attr name=\"letterSpacing\" />\n        <!-- Font feature settings. -->\n        <attr name=\"fontFeatureSettings\" />\n        <!-- Font variation settings. -->\n        <attr name=\"fontVariationSettings\" />\n        <!-- Break strategy (control over paragraph layout). -->\n        <attr name=\"breakStrategy\">\n            <!-- Line breaking uses simple strategy. -->\n            <enum name=\"simple\" value=\"0\" />\n            <!-- Line breaking uses high-quality strategy, including hyphenation. -->\n            <enum name=\"high_quality\" value=\"1\" />\n            <!-- Line breaking strategy balances line lengths. -->\n            <enum name=\"balanced\" value=\"2\" />\n        </attr>\n        <!-- Frequency of automatic hyphenation. -->\n        <attr name=\"hyphenationFrequency\">\n            <!-- No hyphenation. -->\n            <enum name=\"none\" value=\"0\" />\n            <!-- Less frequent hyphenation, useful for informal use cases, such\n            as chat messages. -->\n            <enum name=\"normal\" value=\"1\" />\n            <!-- Standard amount of hyphenation, useful for running text and for\n            screens with limited space for text. -->\n            <enum name=\"full\" value=\"2\" />\n        </attr>\n        <!-- Specify the type of auto-size. Note that this feature is not supported by EditText,\n        works only for TextView. -->\n        <attr name=\"autoSizeTextType\" format=\"enum\">\n            <!-- No auto-sizing (default). -->\n            <enum name=\"none\" value=\"0\" />\n            <!-- Uniform horizontal and vertical text size scaling to fit within the\n            container. -->\n            <enum name=\"uniform\" value=\"1\" />\n        </attr>\n        <!-- Specify the auto-size step size if <code>autoSizeTextType</code> is set to\n        <code>uniform</code>. The default is 1px. Overwrites\n        <code>autoSizePresetSizes</code> if set. -->\n        <attr name=\"autoSizeStepGranularity\" format=\"dimension\" />\n        <!-- Resource array of dimensions to be used in conjunction with\n        <code>autoSizeTextType</code> set to <code>uniform</code>. Overrides\n        <code>autoSizeStepGranularity</code> if set. -->\n        <attr name=\"autoSizePresetSizes\"/>\n        <!-- The minimum text size constraint to be used when auto-sizing text. -->\n        <attr name=\"autoSizeMinTextSize\" format=\"dimension\" />\n        <!-- The maximum text size constraint to be used when auto-sizing text. -->\n        <attr name=\"autoSizeMaxTextSize\" format=\"dimension\" />\n        <!-- Mode for justification. -->\n        <attr name=\"justificationMode\">\n            <!-- No justification. -->\n            <enum name=\"none\" value=\"0\" />\n            <!-- Justification by stretching word spacing. -->\n            <enum name=\"inter_word\" value = \"1\" />\n        </attr>\n    </declare-styleable>\n    <declare-styleable name=\"TextViewAppearance\">\n        <!-- Base text color, typeface, size, and style. -->\n        <attr name=\"textAppearance\" />\n    </declare-styleable>\n    <declare-styleable name=\"SelectionModeDrawables\">\n        <attr name=\"actionModeSelectAllDrawable\" />\n        <attr name=\"actionModeCutDrawable\" />\n        <attr name=\"actionModeCopyDrawable\" />\n        <attr name=\"actionModePasteDrawable\" />\n    </declare-styleable>\n    <declare-styleable name=\"SuggestionSpan\">\n        <attr name=\"textUnderlineColor\" />\n        <attr name=\"textUnderlineThickness\" />\n    </declare-styleable>\n    <!-- An <code>input-extras</code> is a container for extra data to supply to\n         an input method.  Contains\n         one more more {@link #Extra <extra>} tags.  -->\n    <declare-styleable name=\"InputExtras\">\n    </declare-styleable>\n    <declare-styleable name=\"AutoCompleteTextView\">\n        <!-- Defines the hint displayed in the drop down menu. -->\n        <attr name=\"completionHint\" format=\"string\" />\n        <!-- Defines the hint view displayed in the drop down menu. -->\n        <attr name=\"completionHintView\" format=\"reference\" />\n        <!-- Defines the number of characters that the user must type before\n         completion suggestions are displayed in a drop down menu. -->\n        <attr name=\"completionThreshold\" format=\"integer\" min=\"1\" />\n        <!-- Selector in a drop down list. -->\n        <attr name=\"dropDownSelector\" format=\"reference|color\" />\n        <!-- View to anchor the auto-complete dropdown to. If not specified, the text view itself\n             is used. -->\n        <attr name=\"dropDownAnchor\" format=\"reference\" />\n        <!-- Specifies the basic width of the dropdown. Its value may\n             be a dimension (such as \"12dip\") for a constant width,\n             fill_parent or match_parent to match the width of the\n             screen, or wrap_content to match the width of\n             the anchored view. -->\n        <attr name=\"dropDownWidth\" format=\"dimension\">\n            <!-- The dropdown should fill the width of the screen.\n                 This constant is deprecated starting from API Level 8 and\n                 is replaced by {@code match_parent}. -->\n            <enum name=\"fill_parent\" value=\"-1\" />\n            <!-- The dropdown should fit the width of the screen.\n                 Introduced in API Level 8. -->\n            <enum name=\"match_parent\" value=\"-1\" />\n            <!-- The dropdown should fit the width of its anchor. -->\n            <enum name=\"wrap_content\" value=\"-2\" />\n        </attr>\n        <!-- Specifies the basic height of the dropdown. Its value may\n             be a dimension (such as \"12dip\") for a constant height,\n             fill_parent or match_parent to fill the height of the\n             screen, or wrap_content to match the height of\n             the content of the drop down. -->\n        <attr name=\"dropDownHeight\" format=\"dimension\">\n            <!-- The dropdown should fit the height of the screen.\n                 This constant is deprecated starting from API Level 8 and\n                 is replaced by {@code match_parent}. -->\n            <enum name=\"fill_parent\" value=\"-1\" />\n            <!-- The dropdown should fit the height of the screen.\n                 Introduced in API Level 8. -->\n            <enum name=\"match_parent\" value=\"-1\" />\n            <!-- The dropdown should fit the height of the content. -->\n            <enum name=\"wrap_content\" value=\"-2\" />\n        </attr>\n        <attr name=\"inputType\" />\n        <!-- Theme to use for the completion popup window. -->\n        <attr name=\"popupTheme\" />\n    </declare-styleable>\n    <declare-styleable name=\"PopupWindow\">\n        <!-- The background to use for the popup window. -->\n        <attr name=\"popupBackground\" format=\"reference|color\" />\n        <!-- Window elevation to use for the popup window. -->\n        <attr name=\"popupElevation\" format=\"dimension\" />\n        <!-- The animation style to use for the popup window. -->\n        <attr name=\"popupAnimationStyle\" format=\"reference\" />\n        <!-- Whether the popup window should overlap its anchor view. -->\n        <attr name=\"overlapAnchor\" format=\"boolean\" />\n        <!-- Transition used to move views into the popup window. -->\n        <attr name=\"popupEnterTransition\" format=\"reference\" />\n        <!-- Transition used to move views out of the popup window. -->\n        <attr name=\"popupExitTransition\" format=\"reference\" />\n    </declare-styleable>\n    <declare-styleable name=\"ListPopupWindow\">\n        <!-- Amount of pixels by which the drop down should be offset vertically. -->\n        <attr name=\"dropDownVerticalOffset\" format=\"dimension\" />\n        <!-- Amount of pixels by which the drop down should be offset horizontally. -->\n        <attr name=\"dropDownHorizontalOffset\" format=\"dimension\" />\n    </declare-styleable>\n    <declare-styleable name=\"ViewAnimator\">\n        <!-- Identifier for the animation to use when a view is shown. -->\n        <attr name=\"inAnimation\" format=\"reference\" />\n        <!-- Identifier for the animation to use when a view is hidden. -->\n        <attr name=\"outAnimation\" format=\"reference\" />\n        <!-- Defines whether to animate the current View when the ViewAnimation\n             is first displayed. -->\n        <attr name=\"animateFirstView\" format=\"boolean\" />\n    </declare-styleable>\n    <declare-styleable name=\"ViewFlipper\">\n        <attr name=\"flipInterval\" format=\"integer\" min=\"0\" />\n        <!-- When true, automatically start animating. -->\n        <attr name=\"autoStart\" format=\"boolean\" />\n    </declare-styleable>\n    <declare-styleable name=\"AdapterViewAnimator\">\n        <!-- Identifier for the animation to use when a view is shown. -->\n        <attr name=\"inAnimation\" />\n        <!-- Identifier for the animation to use when a view is hidden. -->\n        <attr name=\"outAnimation\" />\n        <!--Defines whether the animator loops to the first view once it\n        has reached the end of the list. -->\n        <attr name=\"loopViews\" format=\"boolean\" />\n        <!-- Defines whether to animate the current View when the ViewAnimation\n        is first displayed. -->\n        <attr name=\"animateFirstView\" />\n    </declare-styleable>\n    <declare-styleable name=\"AdapterViewFlipper\">\n        <attr name=\"flipInterval\" />\n        <!-- When true, automatically start animating. -->\n        <attr name=\"autoStart\" />\n    </declare-styleable>\n    <declare-styleable name=\"ViewSwitcher\">\n    </declare-styleable>\n    <declare-styleable name=\"ScrollView\">\n        <!-- Defines whether the scrollview should stretch its content to fill the viewport. -->\n        <attr name=\"fillViewport\" format=\"boolean\" />\n    </declare-styleable>\n    <declare-styleable name=\"HorizontalScrollView\">\n        <!-- Defines whether the scrollview should stretch its content to fill the viewport. -->\n        <attr name=\"fillViewport\" />\n    </declare-styleable>\n    <declare-styleable name=\"Spinner\">\n        <!-- The prompt to display when the spinner's dialog is shown. -->\n        <attr name=\"prompt\" format=\"reference\" />\n        <!-- Display mode for spinner options. -->\n        <attr name=\"spinnerMode\" format=\"enum\">\n            <!-- Spinner options will be presented to the user as a dialog window. -->\n            <enum name=\"dialog\" value=\"0\" />\n            <!-- Spinner options will be presented to the user as an inline dropdown\n                 anchored to the spinner widget itself. -->\n            <enum name=\"dropdown\" value=\"1\" />\n        </attr>\n        <!-- List selector to use for spinnerMode=\"dropdown\" display. -->\n        <attr name=\"dropDownSelector\" />\n        <!-- Theme to use for the drop-down or dialog popup window. -->\n        <attr name=\"popupTheme\" />\n        <!-- Background drawable to use for the dropdown in spinnerMode=\"dropdown\". -->\n        <attr name=\"popupBackground\" />\n        <!-- Window elevation to use for the dropdown in spinnerMode=\"dropdown\". -->\n        <attr name=\"popupElevation\" />\n        <!-- Width of the dropdown in spinnerMode=\"dropdown\". -->\n        <attr name=\"dropDownWidth\" />\n        <!-- Reference to a layout to use for displaying a prompt in the dropdown for\n             spinnerMode=\"dropdown\". This layout must contain a TextView with the id\n             {@code @android:id/text1} to be populated with the prompt text. -->\n        <attr name=\"popupPromptView\" format=\"reference\" />\n        <!-- Gravity setting for positioning the currently selected item. -->\n        <attr name=\"gravity\" />\n        <!-- Whether this spinner should mark child views as enabled/disabled when\n             the spinner itself is enabled/disabled. -->\n        <attr name=\"disableChildrenWhenDisabled\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"DatePicker\">\n        <!-- The first day of week according to {@link java.util.Calendar}. -->\n        <attr name=\"firstDayOfWeek\" />\n        <!-- The minimal date shown by this calendar view in mm/dd/yyyy format. -->\n        <attr name=\"minDate\" format=\"string\" />\n        <!-- The maximal date shown by this calendar view in mm/dd/yyyy format. -->\n        <attr name=\"maxDate\" format=\"string\" />\n\n        <!-- Whether the spinners are shown. Only valid for \"spinner\" mode. -->\n        <attr name=\"spinnersShown\" format=\"boolean\" />\n        <!-- Whether the calendar view is shown. Only valid for \"spinner\" mode. -->\n        <attr name=\"calendarViewShown\" format=\"boolean\" />\n\n        <!-- @hide The layout of the date picker. -->\n        <attr name=\"internalLayout\" format=\"reference\"  />\n        <!-- @hide The layout of the legacy DatePicker. -->\n        <attr name=\"legacyLayout\" />\n\n        <!-- The text color for the selected date header text, ex. \"2014\" or\n             \"Tue, Mar 18\". This should be a color state list where the\n             activated state will be used when the year picker or day picker is\n             active.-->\n        <attr name=\"headerTextColor\" format=\"color\" />\n        <!-- The background for the selected date header. -->\n        <attr name=\"headerBackground\" />\n\n        <!-- The list year's text appearance in the list.\n             {@deprecated Use yearListTextColor. }-->\n        <attr name=\"yearListItemTextAppearance\" format=\"reference\" />\n        <!-- @hide The list year's text appearance in the list when activated. -->\n        <attr name=\"yearListItemActivatedTextAppearance\" format=\"reference\" />\n        <!-- The text color list of the calendar. -->\n        <attr name=\"calendarTextColor\" format=\"color\" />\n\n        <!-- Defines the look of the widget. Prior to the L release, the only choice was\n             spinner. As of L, with the Material theme selected, the default layout is calendar,\n             but this attribute can be used to force spinner to be used instead. -->\n        <attr name=\"datePickerMode\">\n            <!-- Date picker with spinner controls to select the date. -->\n            <enum name=\"spinner\" value=\"1\" />\n            <!-- Date picker with calendar to select the date. -->\n            <enum name=\"calendar\" value=\"2\" />\n        </attr>\n\n        <!-- The first year (inclusive), for example \"1940\".\n             {@deprecated Use minDate instead.} -->\n        <attr name=\"startYear\" format=\"integer\" />\n        <!-- The last year (inclusive), for example \"2010\".\n             {@deprecated Use maxDate instead.} -->\n        <attr name=\"endYear\" format=\"integer\" />\n        <!-- The text appearance for the month (ex. May) in the selected date header.\n             {@deprecated Use headerTextColor instead.} -->\n        <attr name=\"headerMonthTextAppearance\" format=\"reference\" />\n        <!-- The text appearance for the day of month (ex. 28) in the selected date header.\n             {@deprecated Use headerTextColor instead.} -->\n        <attr name=\"headerDayOfMonthTextAppearance\" format=\"reference\" />\n        <!-- The text appearance for the year (ex. 2014) in the selected date header.\n             {@deprecated Use headerTextColor instead.} -->\n        <attr name=\"headerYearTextAppearance\" format=\"reference\" />\n        <!-- The background color for the header's day of week.\n             {@deprecated No longer displayed.} -->\n        <attr name=\"dayOfWeekBackground\" format=\"color\" />\n        <!-- The text color for the header's day of week.\n             {@deprecated No longer displayed.} -->\n        <attr name=\"dayOfWeekTextAppearance\" format=\"reference\" />\n        <!-- The list year's selected circle color in the list.\n             {@deprecated No longer displayed.} -->\n        <attr name=\"yearListSelectorColor\" format=\"color\" />\n\n        <!-- @hide Whether this time picker is being displayed within a dialog,\n             in which case it may ignore the requested time picker mode due to\n             space considerations. -->\n        <attr name=\"dialogMode\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"TwoLineListItem\">\n        <attr name=\"mode\">\n            <!-- Always show only the first line. -->\n            <enum name=\"oneLine\" value=\"1\" />\n            <!-- When selected show both lines, otherwise show only the first line.\n                 This is the default mode. -->\n            <enum name=\"collapsing\" value=\"2\" />\n            <!-- Always show both lines. -->\n            <enum name=\"twoLine\" value=\"3\" />\n        </attr>\n    </declare-styleable>\n\n    <!-- SlidingDrawer specific attributes. These attributes are used to configure\n         a SlidingDrawer from XML. -->\n    <declare-styleable name=\"SlidingDrawer\">\n        <!-- Identifier for the child that represents the drawer's handle. -->\n        <attr name=\"handle\" format=\"reference\" />\n        <!-- Identifier for the child that represents the drawer's content. -->\n        <attr name=\"content\" format=\"reference\" />\n        <!-- Orientation of the SlidingDrawer. -->\n        <attr name=\"orientation\" />\n        <!-- Extra offset for the handle at the bottom of the SlidingDrawer. -->\n        <attr name=\"bottomOffset\" format=\"dimension\"  />\n        <!-- Extra offset for the handle at the top of the SlidingDrawer. -->\n        <attr name=\"topOffset\" format=\"dimension\"  />\n        <!-- Indicates whether the drawer can be opened/closed by a single tap\n             on the handle.  (If false, the user must drag or fling, or click\n             using the trackball, to open/close the drawer.)  Default is true. -->\n        <attr name=\"allowSingleTap\" format=\"boolean\" />\n        <!-- Indicates whether the drawer should be opened/closed with an animation\n             when the user clicks the handle. Default is true. -->\n        <attr name=\"animateOnClick\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- GestureOverlayView specific attributes. These attributes are used to configure\n         a GestureOverlayView from XML. -->\n    <declare-styleable name=\"GestureOverlayView\">\n        <!-- Width of the stroke used to draw the gesture. -->\n        <attr name=\"gestureStrokeWidth\" format=\"float\" />\n        <!-- Color used to draw a gesture. -->\n        <attr name=\"gestureColor\" format=\"color\" />\n        <!-- Color used to draw the user's strokes until we are sure it's a gesture. -->\n        <attr name=\"uncertainGestureColor\" format=\"color\" />\n        <!-- Time, in milliseconds, to wait before the gesture fades out after the user\n             is done drawing it. -->\n        <attr name=\"fadeOffset\" format=\"integer\" />\n        <!-- Duration, in milliseconds, of the fade out effect after the user is done\n             drawing a gesture. -->\n        <attr name=\"fadeDuration\" format=\"integer\" />\n        <!-- Defines the type of strokes that define a gesture. -->\n        <attr name=\"gestureStrokeType\">\n            <!-- A gesture is made of only one stroke. -->\n            <enum name=\"single\" value=\"0\" />\n            <!-- A gesture is made of multiple strokes. -->\n            <enum name=\"multiple\" value=\"1\" />\n        </attr>\n        <!-- Minimum length of a stroke before it is recognized as a gesture. -->\n        <attr name=\"gestureStrokeLengthThreshold\" format=\"float\" />\n        <!-- Squareness threshold of a stroke before it is recognized as a gesture. -->\n        <attr name=\"gestureStrokeSquarenessThreshold\" format=\"float\" />\n        <!-- Minimum curve angle a stroke must contain before it is recognized as a gesture. -->\n        <attr name=\"gestureStrokeAngleThreshold\" format=\"float\" />\n        <!-- Defines whether the overlay should intercept the motion events when a gesture\n             is recognized. -->\n        <attr name=\"eventsInterceptionEnabled\" format=\"boolean\" />\n        <!-- Defines whether the gesture will automatically fade out after being recognized. -->\n        <attr name=\"fadeEnabled\" format=\"boolean\" />\n        <!-- Indicates whether horizontal (when the orientation is vertical) or vertical\n             (when orientation is horizontal) strokes automatically define a gesture. -->\n        <attr name=\"orientation\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"QuickContactBadge\">\n        <attr name=\"quickContactWindowSize\">\n            <enum name=\"modeSmall\" value=\"1\" />\n            <enum name=\"modeMedium\" value=\"2\" />\n            <enum name=\"modeLarge\" value=\"3\" />\n        </attr>\n    </declare-styleable>\n\n    <!-- ======================================= -->\n    <!-- Widget package parent layout attributes -->\n    <!-- ======================================= -->\n    <eat-comment />\n\n    <declare-styleable name=\"AbsoluteLayout_Layout\">\n        <attr name=\"layout_x\" format=\"dimension\" />\n        <attr name=\"layout_y\" format=\"dimension\" />\n    </declare-styleable>\n    <declare-styleable name=\"LinearLayout_Layout\">\n        <attr name=\"layout_width\" />\n        <attr name=\"layout_height\" />\n        <!-- Indicates how much of the extra space in the LinearLayout is\n        allocated to the view associated with these LayoutParams. Specify\n        0 if the view should not be stretched. Otherwise the extra pixels\n        will be pro-rated among all views whose weight is greater than 0. -->\n        <attr name=\"layout_weight\" format=\"float\" />\n        <!-- Gravity specifies how a component should be placed in its group of cells.\n        The default is {@link android.view.Gravity#TOP}.\n        See {@link android.widget.LinearLayout#setGravity(int)}. -->\n        <attr name=\"layout_gravity\" />\n    </declare-styleable>\n    <declare-styleable name=\"GridLayout_Layout\">\n        <!-- The row boundary delimiting the top of the group of cells\n        occupied by this view. -->\n        <attr name=\"layout_row\" format=\"integer\" />\n        <!-- The row span: the difference between the top and bottom\n        boundaries delimiting the group of cells occupied by this view.\n        The default is one.\n        See {@link android.widget.GridLayout.Spec}. -->\n        <attr name=\"layout_rowSpan\" format=\"integer\" min=\"1\" />\n        <!-- The relative proportion of vertical space that should be allocated to this view\n        during excess space distribution. -->\n        <attr name=\"layout_rowWeight\" format=\"float\" />\n        <!-- The column boundary delimiting the left of the group of cells\n        occupied by this view. -->\n        <attr name=\"layout_column\" />\n        <!-- The column span: the difference between the right and left\n        boundaries delimiting the group of cells occupied by this view.\n        The default is one.\n        See {@link android.widget.GridLayout.Spec}. -->\n        <attr name=\"layout_columnSpan\" format=\"integer\" min=\"1\" />\n        <!-- The relative proportion of horizontal space that should be allocated to this view\n        during excess space distribution. -->\n        <attr name=\"layout_columnWeight\" format=\"float\" />\n        <!-- Gravity specifies how a component should be placed in its group of cells.\n        The default is LEFT | BASELINE.\n        See {@link android.widget.GridLayout.LayoutParams#setGravity(int)}. -->\n        <attr name=\"layout_gravity\" />\n    </declare-styleable>\n    <declare-styleable name=\"FrameLayout_Layout\">\n        <attr name=\"layout_gravity\" />\n    </declare-styleable>\n    <declare-styleable name=\"RelativeLayout_Layout\">\n        <!-- Positions the right edge of this view to the left of the given anchor view ID.\n             Accommodates right margin of this view and left margin of anchor view. -->\n        <attr name=\"layout_toLeftOf\" format=\"reference\" />\n        <!-- Positions the left edge of this view to the right of the given anchor view ID.\n            Accommodates left margin of this view and right margin of anchor view. -->\n        <attr name=\"layout_toRightOf\" format=\"reference\" />\n        <!-- Positions the bottom edge of this view above the given anchor view ID.\n            Accommodates bottom margin of this view and top margin of anchor view. -->\n        <attr name=\"layout_above\" format=\"reference\" />\n        <!-- Positions the top edge of this view below the given anchor view ID.\n            Accommodates top margin of this view and bottom margin of anchor view. -->\n        <attr name=\"layout_below\" format=\"reference\" />\n        <!-- Positions the baseline of this view on the baseline of the given anchor view ID. -->\n        <attr name=\"layout_alignBaseline\" format=\"reference\" />\n        <!-- Makes the left edge of this view match the left edge of the given anchor view ID.\n            Accommodates left margin. -->\n        <attr name=\"layout_alignLeft\" format=\"reference\" />\n        <!-- Makes the top edge of this view match the top edge of the given anchor view ID.\n            Accommodates top margin. -->\n        <attr name=\"layout_alignTop\" format=\"reference\" />\n        <!-- Makes the right edge of this view match the right edge of the given anchor view ID.\n            Accommodates right margin. -->\n        <attr name=\"layout_alignRight\" format=\"reference\" />\n        <!-- Makes the bottom edge of this view match the bottom edge of the given anchor view ID.\n            Accommodates bottom margin. -->\n        <attr name=\"layout_alignBottom\" format=\"reference\" />\n        <!-- If true, makes the left edge of this view match the left edge of the parent.\n            Accommodates left margin. -->\n        <attr name=\"layout_alignParentLeft\" format=\"boolean\" />\n        <!-- If true, makes the top edge of this view match the top edge of the parent.\n            Accommodates top margin. -->\n        <attr name=\"layout_alignParentTop\" format=\"boolean\" />\n        <!-- If true, makes the right edge of this view match the right edge of the parent.\n            Accommodates right margin. -->\n        <attr name=\"layout_alignParentRight\" format=\"boolean\" />\n        <!-- If true, makes the bottom edge of this view match the bottom edge of the parent.\n            Accommodates bottom margin. -->\n        <attr name=\"layout_alignParentBottom\" format=\"boolean\" />\n        <!-- If true, centers this child horizontally and vertically within its parent. -->\n        <attr name=\"layout_centerInParent\" format=\"boolean\" />\n        <!-- If true, centers this child horizontally within its parent. -->\n        <attr name=\"layout_centerHorizontal\" format=\"boolean\" />\n        <!-- If true, centers this child vertically within its parent. -->\n        <attr name=\"layout_centerVertical\" format=\"boolean\" />\n        <!-- If set to true, the parent will be used as the anchor when the anchor cannot be\n             be found for layout_toLeftOf, layout_toRightOf, etc. -->\n        <attr name=\"layout_alignWithParentIfMissing\" format=\"boolean\" />\n        <!-- Positions the end edge of this view to the start of the given anchor view ID.\n             Accommodates end margin of this view and start margin of anchor view. -->\n        <attr name=\"layout_toStartOf\" format=\"reference\" />\n        <!-- Positions the start edge of this view to the end of the given anchor view ID.\n             Accommodates start margin of this view and end margin of anchor view. -->\n        <attr name=\"layout_toEndOf\" format=\"reference\" />\n        <!-- Makes the start edge of this view match the start edge of the given anchor view ID.\n            Accommodates start margin. -->\n        <attr name=\"layout_alignStart\" format=\"reference\" />\n        <!-- Makes the end edge of this view match the end edge of the given anchor view ID.\n            Accommodates end margin. -->\n        <attr name=\"layout_alignEnd\" format=\"reference\" />\n        <!-- If true, makes the start edge of this view match the start edge of the parent.\n            Accommodates start margin. -->\n        <attr name=\"layout_alignParentStart\" format=\"boolean\" />\n        <!-- If true, makes the end edge of this view match the end edge of the parent.\n            Accommodates end margin. -->\n        <attr name=\"layout_alignParentEnd\" format=\"boolean\" />\n    </declare-styleable>\n    <declare-styleable name=\"VerticalSlider_Layout\">\n        <attr name=\"layout_scale\" format=\"float\" />\n    </declare-styleable>\n\n    <!-- @hide -->\n    <declare-styleable name=\"WeightedLinearLayout\">\n        <attr name=\"majorWeightMin\" format=\"float\" />\n        <attr name=\"minorWeightMin\" format=\"float\" />\n        <attr name=\"majorWeightMax\" format=\"float\" />\n        <attr name=\"minorWeightMax\" format=\"float\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"CalendarView\">\n        <!-- The first day of week according to {@link java.util.Calendar}. -->\n        <attr name=\"firstDayOfWeek\" format=\"integer\" />\n        <!-- The minimal date shown by this calendar view in mm/dd/yyyy format. -->\n        <attr name=\"minDate\" />\n        <!-- The maximal date shown by this calendar view in mm/dd/yyyy format. -->\n        <attr name=\"maxDate\" />\n        <!-- The text appearance for the month and year in the calendar header. -->\n        <attr name=\"monthTextAppearance\" format=\"reference\" />\n        <!-- The text appearance for the week day abbreviation in the calendar header. -->\n        <attr name=\"weekDayTextAppearance\" format=\"reference\" />\n        <!-- The text appearance for the day numbers in the calendar grid. -->\n        <attr name=\"dateTextAppearance\" format=\"reference\" />\n        <!-- @hide The background color used for the day selection indicator. -->\n        <attr name=\"daySelectorColor\" format=\"color\" />\n        <!-- @hide The background color used for the day highlight indicator. -->\n        <attr name=\"dayHighlightColor\" format=\"color\" />\n        <!-- @hide Which style of calendar delegate to use. -->\n        <attr name=\"calendarViewMode\">\n            <enum name=\"holo\" value=\"0\" />\n            <enum name=\"material\" value=\"1\" />\n        </attr>\n\n        <!-- @deprecated Whether do show week numbers. -->\n        <attr name=\"showWeekNumber\" format=\"boolean\" />\n        <!-- @deprecated The number of weeks to be shown. -->\n        <attr name=\"shownWeekCount\" format=\"integer\"/>\n        <!-- @deprecated The background color for the selected week. -->\n        <attr name=\"selectedWeekBackgroundColor\" format=\"color|reference\" />\n        <!-- @deprecated The color for the dates of the focused month. -->\n        <attr name=\"focusedMonthDateColor\" format=\"color|reference\" />\n        <!-- @deprecated The color for the dates of an unfocused month. -->\n        <attr name=\"unfocusedMonthDateColor\" format=\"color|reference\" />\n        <!-- @deprecated The color for the week numbers. -->\n        <attr name=\"weekNumberColor\" format=\"color|reference\" />\n        <!-- @deprecated The color for the separator line between weeks. -->\n        <attr name=\"weekSeparatorLineColor\" format=\"color|reference\" />\n        <!-- @deprecated Drawable for the vertical bar shown at the beginning and at the end of the selected date. -->\n        <attr name=\"selectedDateVerticalBar\" format=\"reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"NumberPicker\">\n        <!-- @hide Color for the solid color background if such for optimized rendering. -->\n        <attr name=\"solidColor\" format=\"color|reference\" />\n        <!-- @hide The divider for making the selection area. -->\n        <attr name=\"selectionDivider\" format=\"reference\" />\n        <!-- The height of the selection divider. -->\n        <attr name=\"selectionDividerHeight\" format=\"dimension\" />\n        <!-- @hide The distance between the two selection dividers. -->\n        <attr name=\"selectionDividersDistance\" format=\"dimension\" />\n        <!-- @hide The min height of the NumberPicker. -->\n        <attr name=\"internalMinHeight\" format=\"dimension\" />\n        <!-- @hide The max height of the NumberPicker. -->\n        <attr name=\"internalMaxHeight\" format=\"dimension\" />\n        <!-- @hide The min width of the NumberPicker. -->\n        <attr name=\"internalMinWidth\" format=\"dimension\" />\n        <!-- @hide The max width of the NumberPicker. -->\n        <attr name=\"internalMaxWidth\" format=\"dimension\" />\n        <!-- @hide The layout of the number picker. -->\n        <attr name=\"internalLayout\" />\n        <!-- @hide The drawable for pressed virtual (increment/decrement) buttons. -->\n        <attr name=\"virtualButtonPressedDrawable\" format=\"reference\"/>\n        <!-- @hide If true then the selector wheel is hidden until the picker has focus. -->\n        <attr name=\"hideWheelUntilFocused\" format=\"boolean\"/>\n    </declare-styleable>\n\n    <declare-styleable name=\"TimePicker\">\n        <!-- @hide The layout of the legacy time picker. -->\n        <attr name=\"legacyLayout\" format=\"reference\" />\n        <!-- @hide The layout of the time picker. -->\n        <attr name=\"internalLayout\" />\n\n        <!-- The text color for the selected time header text, ex. \"12\" or\n             \"PM\". This should be a color state list where the activated state\n             will be used when the minute picker or hour picker is active.-->\n        <attr name=\"headerTextColor\" />\n        <!-- The background for the header containing the currently selected time. -->\n        <attr name=\"headerBackground\" />\n\n        <!-- The color for the hours/minutes numbers. This should be a color\n             state list where the activated state will be used when the number\n             is active.-->\n        <attr name=\"numbersTextColor\" format=\"color\" />\n        <!-- The color for the inner hours numbers used in 24-hour mode. This\n             should be a color state list where the activated state will be\n             used when the number is active.-->\n        <attr name=\"numbersInnerTextColor\" format=\"color\" />\n        <!-- The background color for the hours/minutes numbers. -->\n        <attr name=\"numbersBackgroundColor\" format=\"color\" />\n        <!-- The color for the hours/minutes selector. -->\n        <attr name=\"numbersSelectorColor\" format=\"color\" />\n\n        <!-- Defines the look of the widget. Prior to the L release, the only choice was\n             spinner. As of L, with the Material theme selected, the default layout is clock,\n             but this attribute can be used to force spinner to be used instead. -->\n        <attr name=\"timePickerMode\">\n            <!-- Time picker with spinner controls to select the time. -->\n            <enum name=\"spinner\" value=\"1\" />\n            <!-- Time picker with clock face to select the time. -->\n            <enum name=\"clock\" value=\"2\" />\n        </attr>\n\n        <!-- The text appearance for the AM/PM header.\n             @deprecated Use headerTextColor instead. -->\n        <attr name=\"headerAmPmTextAppearance\" format=\"reference\" />\n        <!-- The text appearance for the time header.\n             @deprecated Use headerTextColor instead. -->\n        <attr name=\"headerTimeTextAppearance\" format=\"reference\" />\n        <!-- The color for the AM/PM selectors.\n             {@deprecated Use headerTextColor instead.}-->\n        <attr name=\"amPmTextColor\" format=\"color\" />\n        <!-- The background color state list for the AM/PM selectors.\n             {@deprecated Use headerBackground instead.}-->\n        <attr name=\"amPmBackgroundColor\" format=\"color\" />\n\n        <!-- @hide Whether this time picker is being displayed within a dialog,\n             in which case it may ignore the requested time picker mode due to\n             space considerations. -->\n        <attr name=\"dialogMode\" />\n    </declare-styleable>\n\n    <!-- ========================= -->\n    <!-- Drawable class attributes -->\n    <!-- ========================= -->\n    <eat-comment />\n\n    <!-- Base attributes that are available to all Drawable objects. -->\n    <declare-styleable name=\"Drawable\">\n        <!-- Provides initial visibility state of the drawable; the default\n             value is false.  See\n             {@link android.graphics.drawable.Drawable#setVisible}. -->\n        <attr name=\"visible\" format=\"boolean\" />\n        <!-- Indicates if the drawable needs to be mirrored when its layout direction is\n             RTL (right-to-left).  See\n             {@link android.graphics.drawable.Drawable#setAutoMirrored}. -->\n        <attr name=\"autoMirrored\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Drawable class used to wrap other drawables. -->\n    <declare-styleable name=\"DrawableWrapper\">\n        <!-- The wrapped drawable. -->\n        <attr name=\"drawable\" />\n    </declare-styleable>\n\n    <!-- Drawable used to render several states. Each state is represented by\n         a child drawable. -->\n    <declare-styleable name=\"StateListDrawable\">\n        <!-- Indicates whether the drawable should be initially visible. -->\n        <attr name=\"visible\" />\n        <!-- If true, allows the drawable's padding to change based on the\n             current state that is selected.  If false, the padding will\n             stay the same (based on the maximum padding of all the states).\n             Enabling this feature requires that the owner of the drawable\n             deal with performing layout when the state changes, which is\n             often not supported. -->\n        <attr name=\"variablePadding\" format=\"boolean\" />\n        <!-- If true, the drawable's reported internal size will remain\n             constant as the state changes; the size is the maximum of all\n             of the states.  If false, the size will vary based on the\n             current state. -->\n        <attr name=\"constantSize\" format=\"boolean\" />\n        <!-- Enables or disables dithering of the bitmap if the bitmap does not have the\n             same pixel configuration as the screen (for instance: a ARGB 8888 bitmap with\n             an RGB 565 screen). -->\n        <attr name=\"dither\" format=\"boolean\" />\n        <!-- Amount of time (in milliseconds) to fade in a new state drawable. -->\n        <attr name=\"enterFadeDuration\" format=\"integer\" />\n        <!-- Amount of time (in milliseconds) to fade out an old state drawable. -->\n        <attr name=\"exitFadeDuration\" format=\"integer\" />\n        <!-- Indicates if the drawable needs to be mirrored when its layout direction is\n             RTL (right-to-left). -->\n        <attr name=\"autoMirrored\"/>\n    </declare-styleable>\n\n    <!-- Drawable used to render several states with animated transitions. Each state\n         is represented by a child drawable with an optional keyframe ID. -->\n    <declare-styleable name=\"AnimatedStateListDrawable\">\n        <!-- Indicates whether the drawable should be initially visible. -->\n        <attr name=\"visible\" />\n        <!-- If true, allows the drawable's padding to change based on the\n             current state that is selected.  If false, the padding will\n             stay the same (based on the maximum padding of all the states).\n             Enabling this feature requires that the owner of the drawable\n             deal with performing layout when the state changes, which is\n             often not supported. -->\n        <attr name=\"variablePadding\" />\n        <!-- If true, the drawable's reported internal size will remain\n             constant as the state changes; the size is the maximum of all\n             of the states.  If false, the size will vary based on the\n             current state. -->\n        <attr name=\"constantSize\" />\n        <!-- Enables or disables dithering of the bitmap if the bitmap does not have the\n             same pixel configuration as the screen (for instance: a ARGB 8888 bitmap with\n             an RGB 565 screen). -->\n        <attr name=\"dither\" />\n        <!-- Amount of time (in milliseconds) to fade in a new state drawable. -->\n        <attr name=\"enterFadeDuration\" />\n        <!-- Amount of time (in milliseconds) to fade out an old state drawable. -->\n        <attr name=\"exitFadeDuration\" />\n        <!-- Indicates if the drawable needs to be mirrored when its layout direction is\n             RTL (right-to-left). -->\n        <attr name=\"autoMirrored\"/>\n    </declare-styleable>\n\n    <!-- Represents a single state inside a StateListDrawable. -->\n    <declare-styleable name=\"StateListDrawableItem\">\n        <!-- Reference to a drawable resource to use for the state. If not\n             given, the drawable must be defined by the first child tag. -->\n        <attr name=\"drawable\" />\n    </declare-styleable>\n\n    <!-- Transition used to animate between states with keyframe IDs. -->\n    <declare-styleable name=\"AnimatedStateListDrawableItem\">\n        <!-- Reference to a drawable resource to use for the frame.  If not\n             given, the drawable must be defined by the first child tag. -->\n        <attr name=\"drawable\" />\n        <!-- Keyframe identifier for use in specifying transitions. -->\n        <attr name=\"id\" />\n    </declare-styleable>\n\n    <!-- Transition used to animate between states with keyframe IDs. -->\n    <declare-styleable name=\"AnimatedStateListDrawableTransition\">\n        <!-- Keyframe identifier for the starting state. -->\n        <attr name=\"fromId\" format=\"reference\" />\n        <!-- Keyframe identifier for the ending state. -->\n        <attr name=\"toId\" format=\"reference\" />\n        <!-- Reference to a animation drawable resource to use for the frame.  If not\n             given, the animation drawable must be defined by the first child tag. -->\n        <attr name=\"drawable\" />\n        <!-- Whether this transition is reversible. -->\n        <attr name=\"reversible\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Drawable used to render several animated frames. -->\n    <declare-styleable name=\"AnimationDrawable\">\n        <attr name=\"visible\" />\n        <attr name=\"variablePadding\" />\n        <!-- If true, the animation will only run a single time and then\n             stop.  If false (the default), it will continually run,\n             restarting at the first frame after the last has finished. -->\n        <attr name=\"oneshot\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Represents a single frame inside an AnimationDrawable. -->\n    <declare-styleable name=\"AnimationDrawableItem\">\n        <!-- Amount of time (in milliseconds) to display this frame. -->\n        <attr name=\"duration\" format=\"integer\" />\n        <!-- Reference to a drawable resource to use for the frame.  If not\n             given, the drawable must be defined by the first child tag. -->\n        <attr name=\"drawable\" format=\"reference\" />\n    </declare-styleable>\n\n    <!-- Attributes that can be assigned to a StateListAnimator item. -->\n    <declare-styleable name=\"StateListAnimatorItem\">\n        <attr name=\"animation\"/>\n    </declare-styleable>\n\n    <!-- Attributes that can be assigned to a ColorStateList item. -->\n    <declare-styleable name=\"ColorStateListItem\">\n        <!-- Base color for this state. -->\n        <attr name=\"color\" />\n        <!-- Alpha multiplier applied to the base color. -->\n        <attr name=\"alpha\" />\n        <!-- Perceptual luminance applied to the base color. From 0 to 100. -->\n        <attr name=\"lStar\" format=\"float\" />\n    </declare-styleable>\n\n    <!-- Drawable used to render according to the animation scale. Esp. when it is 0 due to battery\n         saver mode. It should contain one animatable drawable and one static drawable.\n         @hide -->\n    <declare-styleable name=\"AnimationScaleListDrawable\">\n    </declare-styleable>\n\n    <!-- Attributes that can be assigned to a AnimationScaleListDrawable item.\n         @hide -->\n    <declare-styleable name=\"AnimationScaleListDrawableItem\">\n        <!-- Reference to a drawable resource to use for the state. If not\n             given, the drawable must be defined by the first child tag. -->\n        <attr name=\"drawable\" />\n    </declare-styleable>\n\n\n    <!-- Drawable used to render a geometric shape, with a gradient or a solid color. -->\n    <declare-styleable name=\"GradientDrawable\">\n        <!-- Indicates whether the drawable should intially be visible. -->\n        <attr name=\"visible\" />\n        <!-- Enables or disables dithering. -->\n        <attr name=\"dither\" />\n        <!-- Indicates what shape to fill with a gradient. -->\n        <attr name=\"shape\">\n            <!-- Rectangle shape, with optional rounder corners. -->\n            <enum name=\"rectangle\" value=\"0\" />\n            <!-- Oval shape. -->\n            <enum name=\"oval\" value=\"1\" />\n            <!-- Line shape. -->\n            <enum name=\"line\" value=\"2\" />\n            <!-- Ring shape. -->\n            <enum name=\"ring\" value=\"3\" />\n        </attr>\n        <!-- Inner radius of the ring expressed as a ratio of the ring's width. For instance,\n             if innerRadiusRatio=9, then the inner radius equals the ring's width divided by 9.\n             This value is ignored if innerRadius is defined. Default value is 9. -->\n        <attr name=\"innerRadiusRatio\" format=\"float\" />\n        <!-- Thickness of the ring expressed as a ratio of the ring's width. For instance,\n             if thicknessRatio=3, then the thickness equals the ring's width divided by 3.\n             This value is ignored if innerRadius is defined. Default value is 3. -->\n        <attr name=\"thicknessRatio\" format=\"float\" />\n        <!-- Inner radius of the ring. When defined, innerRadiusRatio is ignored. -->\n        <attr name=\"innerRadius\" format=\"dimension\" />\n        <!-- Thickness of the ring. When defined, thicknessRatio is ignored. -->\n        <attr name=\"thickness\" format=\"dimension\" />\n        <!-- Whether the drawable level value (see\n             {@link android.graphics.drawable.Drawable#getLevel()}) is used to scale the shape.\n             Scaling behavior depends on the shape type. For \"ring\", the angle is scaled from 0 to\n             360. For all other types, there is no effect. The default value is true. -->\n        <attr name=\"useLevel\" />\n        <!-- If set, specifies the color to apply to the drawable as a tint. By default,\n             no tint is applied. May be a color state list. -->\n        <attr name=\"tint\" />\n        <!-- When a tint color is set, specifies its Porter-Duff blending mode. The\n             default value is src_in, which treats the drawable as an alpha mask. -->\n        <attr name=\"tintMode\" />\n        <!-- Left optical inset. -->\n        <attr name=\"opticalInsetLeft\" />\n        <!-- Top optical inset. -->\n        <attr name=\"opticalInsetTop\" />\n        <!-- Right optical inset. -->\n        <attr name=\"opticalInsetRight\" />\n        <!-- Bottom optical inset. -->\n        <attr name=\"opticalInsetBottom\" />\n    </declare-styleable>\n\n    <!-- Used to specify the size of the shape for GradientDrawable. -->\n    <declare-styleable name=\"GradientDrawableSize\">\n        <!-- Width of the gradient shape. -->\n        <attr name=\"width\" />\n        <!-- Height of the gradient shape. -->\n        <attr name=\"height\" />\n    </declare-styleable>\n\n    <!-- Used to describe the gradient used to fill the shape of a GradientDrawable. -->\n    <declare-styleable name=\"GradientDrawableGradient\">\n        <!-- Start color of the gradient. -->\n        <attr name=\"startColor\" format=\"color\" />\n        <!-- Optional center color. For linear gradients, use centerX or centerY to place the center\n             color. -->\n        <attr name=\"centerColor\" format=\"color\" />\n        <!-- End color of the gradient. -->\n        <attr name=\"endColor\" format=\"color\" />\n        <!-- Whether the drawable level value (see\n             {@link android.graphics.drawable.Drawable#getLevel()}) is used to scale the gradient.\n             Scaling behavior varies based on gradient type. For \"linear\", adjusts the ending\n             position along the gradient's axis of orientation. For \"radial\", adjusts the outer\n             radius. For \"sweep\", adjusts the ending angle. The default value is false. -->\n        <attr name=\"useLevel\" format=\"boolean\" />\n        <!-- Angle of the gradient, used only with linear gradient. Must be a multiple of 45 in the\n             range [0, 315]. -->\n        <attr name=\"angle\" format=\"float\" />\n        <!-- Type of gradient. The default type is linear. -->\n        <attr name=\"type\">\n            <!-- Linear gradient extending across the center point. -->\n            <enum name=\"linear\" value=\"0\" />\n            <!-- Radial gradient extending from the center point outward. -->\n            <enum name=\"radial\" value=\"1\" />\n            <!-- Sweep (or angular) gradient sweeping counter-clockwise around the center point. -->\n            <enum name=\"sweep\"  value=\"2\" />\n        </attr>\n        <!-- X-position of the center point of the gradient within the shape as a fraction of the\n             width. The default value is 0.5. -->\n        <attr name=\"centerX\" format=\"float|fraction\" />\n        <!-- Y-position of the center point of the gradient within the shape as a fraction of the\n             height. The default value is 0.5. -->\n        <attr name=\"centerY\" format=\"float|fraction\" />\n        <!-- Radius of the gradient, used only with radial gradient. May be an explicit dimension\n             or a fractional value relative to the shape's minimum dimension. -->\n        <attr name=\"gradientRadius\" format=\"float|fraction|dimension\" />\n    </declare-styleable>\n\n    <!-- Used to fill the shape of GradientDrawable with a solid color. -->\n    <declare-styleable name=\"GradientDrawableSolid\">\n        <!-- Solid color for the gradient shape. -->\n        <attr name=\"color\" format=\"color\" />\n    </declare-styleable>\n\n    <!-- Used to describe the optional stroke of a GradientDrawable. -->\n    <declare-styleable name=\"GradientDrawableStroke\">\n        <!-- Width of the gradient shape's stroke. -->\n        <attr name=\"width\" />\n        <!-- Color of the gradient shape's stroke. -->\n        <attr name=\"color\" />\n        <!-- Length of a dash in the stroke. -->\n        <attr name=\"dashWidth\" format=\"dimension\" />\n        <!-- Gap between dashes in the stroke. -->\n        <attr name=\"dashGap\" format=\"dimension\" />\n    </declare-styleable>\n\n    <!-- Describes the corners for the rectangle shape of a GradientDrawable.\n         This can be used to render rounded corners. -->\n    <declare-styleable name=\"DrawableCorners\">\n        <!-- Defines the radius of the four corners. -->\n        <attr name=\"radius\" format=\"dimension\" />\n        <!-- Radius of the top left corner. -->\n        <attr name=\"topLeftRadius\" format=\"dimension\" />\n        <!-- Radius of the top right corner. -->\n        <attr name=\"topRightRadius\" format=\"dimension\" />\n        <!-- Radius of the bottom left corner. -->\n        <attr name=\"bottomLeftRadius\" format=\"dimension\" />\n        <!-- Radius of the bottom right corner. -->\n        <attr name=\"bottomRightRadius\" format=\"dimension\" />\n    </declare-styleable>\n\n    <!-- Used to specify the optional padding of a GradientDrawable. -->\n    <declare-styleable name=\"GradientDrawablePadding\">\n        <!-- Amount of left padding inside the gradient shape. -->\n        <attr name=\"left\" format=\"dimension\" />\n        <!-- Amount of top padding inside the gradient shape. -->\n        <attr name=\"top\" format=\"dimension\" />\n        <!-- Amount of right padding inside the gradient shape. -->\n        <attr name=\"right\" format=\"dimension\" />\n        <!-- Amount of bottom padding inside the gradient shape. -->\n        <attr name=\"bottom\" format=\"dimension\" />\n    </declare-styleable>\n\n    <!-- Drawable used to render several drawables stacked on top of each other.\n         Each child drawable can be controlled individually. -->\n    <declare-styleable name=\"LayerDrawable\">\n        <!-- Indicates the opacity of the layer. This can be useful to allow the\n              system to enable drawing optimizations. The default value is\n              translucent. -->\n        <attr name=\"opacity\">\n            <!-- Indicates that the layer is opaque and contains no transparent\n                 nor translucent pixels. -->\n            <enum name=\"opaque\" value=\"-1\" />\n            <!-- The layer is completely transparent (no pixel will be drawn). -->\n            <enum name=\"transparent\" value=\"-2\" />\n            <!-- The layer has translucent pixels. -->\n            <enum name=\"translucent\" value=\"-3\" />\n        </attr>\n        <!-- Indicates if the drawable needs to be mirrored when its layout direction is\n             RTL (right-to-left). -->\n        <attr name=\"autoMirrored\" />\n        <!-- Indicates how layer padding should affect the bounds of subsequent layers.\n             The default padding mode value is nest. -->\n        <attr name=\"paddingMode\">\n            <!-- Nest each layer inside the padding of the previous layer. -->\n            <enum name=\"nest\" value=\"0\" />\n            <!-- Stack each layer directly atop the previous layer. -->\n            <enum name=\"stack\" value=\"1\" />\n        </attr>\n        <!-- Explicit top padding. Overrides child padding. -->\n        <attr name=\"paddingTop\" />\n        <!-- Explicit bottom padding. Overrides child padding. -->\n        <attr name=\"paddingBottom\" />\n        <!-- Explicit left padding. Overrides child padding. -->\n        <attr name=\"paddingLeft\" />\n        <!-- Explicit right padding. Overrides child padding. -->\n        <attr name=\"paddingRight\" />\n        <!-- Explicit start padding. Overrides child padding. Takes precedence\n             over absolute padding (for example, left when layout direction is LTR). -->\n        <attr name=\"paddingStart\" />\n        <!-- Explicit end padding. Overrides child padding. Takes precedence\n             over absolute padding (for example, right when layout direction is LTR). -->\n        <attr name=\"paddingEnd\" />\n    </declare-styleable>\n\n    <!-- Describes an item (or child) of a LayerDrawable. -->\n    <declare-styleable name=\"LayerDrawableItem\">\n        <!-- Left inset to apply to the layer. -->\n        <attr name=\"left\" />\n        <!-- Top inset to apply to the layer. -->\n        <attr name=\"top\" />\n        <!-- Right inset to apply to the layer. -->\n        <attr name=\"right\" />\n        <!-- Bottom inset to apply to the layer. -->\n        <attr name=\"bottom\" />\n        <!-- Start inset to apply to the layer. Overrides {@code left} or\n             {@code right} depending on layout direction. -->\n        <attr name=\"start\" format=\"dimension\" />\n        <!-- End inset to apply to the layer. Overrides {@code left} or\n             {@code right} depending on layout direction. -->\n        <attr name=\"end\" format=\"dimension\" />\n        <!-- Width of the layer. Defaults to the layer's intrinsic width. -->\n        <attr name=\"width\" />\n        <!-- Height of the layer. Defaults to the layer's intrinsic height. -->\n        <attr name=\"height\" />\n        <!-- Gravity used to align the layer within its container. If no value\n             is specified, the default behavior depends on whether an explicit\n             width or height has been set, If no dimension is set, gravity in\n             that direction defaults to {@code fill_horizontal} or\n             {@code fill_vertical}; otherwise, it defaults to {@code left} or\n             {@code top}. -->\n        <attr name=\"gravity\" />\n        <!-- Drawable used to render the layer. -->\n        <attr name=\"drawable\" />\n        <!-- Identifier of the layer. This can be used to retrieve the layer\n             from a drawable container. -->\n        <attr name=\"id\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"LevelListDrawableItem\">\n        <!-- The minimum level allowed for this item. -->\n        <attr name=\"minLevel\" format=\"integer\" />\n        <!-- The maximum level allowed for this item. -->\n        <attr name=\"maxLevel\" format=\"integer\" />\n        <attr name=\"drawable\" />\n    </declare-styleable>\n\n    <!-- Drawable used to rotate another drawable. -->\n    <declare-styleable name=\"RotateDrawable\">\n        <attr name=\"visible\" />\n        <attr name=\"fromDegrees\" format=\"float\" />\n        <attr name=\"toDegrees\" format=\"float\" />\n        <attr name=\"pivotX\" format=\"float|fraction\" />\n        <attr name=\"pivotY\" format=\"float|fraction\" />\n        <attr name=\"drawable\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"AnimatedRotateDrawable\">\n        <attr name=\"visible\" />\n        <attr name=\"frameDuration\" format=\"integer\" />\n        <attr name=\"framesCount\" format=\"integer\" />\n        <attr name=\"pivotX\" />\n        <attr name=\"pivotY\" />\n        <attr name=\"drawable\" />\n    </declare-styleable>\n\n    <!-- Drawable used to render the Material progress indicator. -->\n    <declare-styleable name=\"MaterialProgressDrawable\">\n        <attr name=\"visible\" />\n        <attr name=\"thickness\" />\n        <attr name=\"innerRadius\" />\n        <attr name=\"width\" />\n        <attr name=\"height\" />\n        <attr name=\"color\" />\n    </declare-styleable>\n\n    <!-- Drawable used to wrap and inset another drawable. -->\n    <declare-styleable name=\"InsetDrawable\">\n        <attr name=\"visible\" />\n        <attr name=\"drawable\" />\n        <attr name=\"inset\"  format=\"fraction|dimension\"/>\n        <attr name=\"insetLeft\" format=\"fraction|dimension\" />\n        <attr name=\"insetRight\" format=\"fraction|dimension\" />\n        <attr name=\"insetTop\" format=\"fraction|dimension\" />\n        <attr name=\"insetBottom\" format=\"fraction|dimension\" />\n    </declare-styleable>\n\n    <!-- Drawable used to draw animated images (gif). -->\n    <declare-styleable name=\"AnimatedImageDrawable\">\n        <!-- Identifier of the image file. This attribute is mandatory.\n             It must be an image file with multiple frames, e.g. gif or webp -->\n        <attr name=\"src\" />\n        <!-- Indicates if the drawable needs to be mirrored when its layout direction is\n             RTL (right-to-left). -->\n        <attr name=\"autoMirrored\" />\n        <!-- Replace the loop count in the encoded data. A repeat count of 0 means that\n             the animation will play once, regardless of the number of times specified\n             in the encoded data. Setting this to infinite (-1) will result in the\n             animation repeating as long as it is displayed (once start() is called). -->\n        <attr name=\"repeatCount\"/>\n        <!-- When true, automatically start animating. The default is false, meaning\n             that the animation will not start until start() is called. -->\n        <attr name=\"autoStart\" />\n    </declare-styleable>\n\n    <!-- Drawable used to draw bitmaps. -->\n    <declare-styleable name=\"BitmapDrawable\">\n        <!-- Identifier of the bitmap file. This attribute is mandatory. -->\n        <attr name=\"src\" />\n        <!-- Enables or disables antialiasing. Antialiasing can be used to smooth the\n             edges of a bitmap when rotated. Default value is false. -->\n        <attr name=\"antialias\" format=\"boolean\" />\n        <!-- Enables or disables bitmap filtering. Filtering is used when the bitmap is\n             shrunk or stretched to smooth its apperance. Default value is true. -->\n        <attr name=\"filter\" format=\"boolean\" />\n        <!-- Enables or disables dithering of the bitmap if the bitmap does not have the\n             same pixel configuration as the screen (for instance: a ARGB 8888 bitmap with\n             an RGB 565 screen). Default value is true. -->\n        <attr name=\"dither\" />\n        <!-- Defines the gravity for the bitmap. The gravity indicates where to position\n             the drawable in its container if the bitmap is smaller than the container. -->\n        <attr name=\"gravity\" />\n        <!-- Defines the tile mode. When the tile mode is enabled, the bitmap is repeated.\n             Gravity is ignored when the tile mode is enabled. Default value is \"disabled\". -->\n        <attr name=\"tileMode\">\n            <!-- Do not tile the bitmap. This is the default value. -->\n            <enum name=\"disabled\" value=\"-1\" />\n            <!-- Replicates the edge color. -->\n            <enum name=\"clamp\" value=\"0\" />\n            <!-- Repeats the bitmap in both direction. -->\n            <enum name=\"repeat\" value=\"1\" />\n            <!-- Repeats the shader's image horizontally and vertically, alternating\n                 mirror images so that adjacent images always seam. -->\n            <enum name=\"mirror\" value=\"2\" />\n        </attr>\n        <!-- Defines the horizontal tile mode. When the tile mode is enabled, the bitmap is repeated.\n             Gravity is ignored when the tile mode is enabled. Default value is \"disabled\". -->\n        <attr name=\"tileModeX\">\n            <!-- Do not tile the bitmap. This is the default value. -->\n            <enum name=\"disabled\" value=\"-1\" />\n            <!-- Replicates the edge color. -->\n            <enum name=\"clamp\" value=\"0\" />\n            <!-- Repeats the bitmap horizontally. -->\n            <enum name=\"repeat\" value=\"1\" />\n            <!-- Repeats the shader's image horizontally, alternating\n                 mirror images so that adjacent images always seam. -->\n            <enum name=\"mirror\" value=\"2\" />\n        </attr>\n        <!-- Defines the vertical tile mode. When the tile mode is enabled, the bitmap is repeated.\n             Gravity is ignored when the tile mode is enabled. Default value is \"disabled\". -->\n        <attr name=\"tileModeY\">\n            <!-- Do not tile the bitmap. This is the default value. -->\n            <enum name=\"disabled\" value=\"-1\" />\n            <!-- Replicates the edge color. -->\n            <enum name=\"clamp\" value=\"0\" />\n            <!-- Repeats the bitmap vertically. -->\n            <enum name=\"repeat\" value=\"1\" />\n            <!-- Repeats the shader's image vertically, alternating\n                 mirror images so that adjacent images always seam. -->\n            <enum name=\"mirror\" value=\"2\" />\n        </attr>\n        <!-- Enables or disables the mipmap hint. See\n            {@link android.graphics.Bitmap#setHasMipMap(boolean)} for more information.\n            Default value is false. -->\n        <attr name=\"mipMap\" format=\"boolean\" />\n        <!-- Indicates if the drawable needs to be mirrored when its layout direction is\n             RTL (right-to-left). -->\n        <attr name=\"autoMirrored\" />\n        <!-- If set, specifies the color to apply to the drawable as a tint. By default,\n             no tint is applied. May be a color state list. -->\n        <attr name=\"tint\" />\n        <!-- When a tint color is set, specifies its Porter-Duff blending mode. The\n             default value is src_in, which treats the drawable as an alpha mask. -->\n        <attr name=\"tintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n        <!-- Specifies the alpha multiplier to apply to the base drawable. -->\n        <attr name=\"alpha\" />\n    </declare-styleable>\n\n    <!-- Drawable used to draw 9-patches. -->\n    <declare-styleable name=\"NinePatchDrawable\">\n        <!-- Identifier of the bitmap file. This attribute is mandatory. -->\n        <attr name=\"src\" />\n        <!-- Enables or disables dithering of the bitmap if the bitmap does not have the\n             same pixel configuration as the screen (for instance: a ARGB 8888 bitmap with\n             an RGB 565 screen). -->\n        <attr name=\"dither\" />\n        <!-- Indicates if the drawable needs to be mirrored when its layout direction is\n             RTL (right-to-left). -->\n        <attr name=\"autoMirrored\" />\n        <!-- If set, specifies the color to apply to the drawable as a tint. By default,\n             no tint is applied. May be a color state list. -->\n        <attr name=\"tint\" />\n        <!-- When a tint color is set, specifies its Porter-Duff blending mode. The\n             default value is src_in, which treats the drawable as an alpha mask. -->\n        <attr name=\"tintMode\" />\n        <!-- Specifies the alpha multiplier to apply to the base drawable. -->\n        <attr name=\"alpha\" />\n    </declare-styleable>\n\n    <!-- Drawable used to draw a single color. -->\n    <declare-styleable name=\"ColorDrawable\">\n        <!-- The color to use. -->\n        <attr name=\"color\" />\n    </declare-styleable>\n\n    <!-- Drawable used to draw adaptive icons with foreground and background layers. -->\n    <declare-styleable name=\"AdaptiveIconDrawableLayer\">\n        <!-- The drawable to use for the layer. -->\n        <attr name=\"drawable\" />\n     </declare-styleable>\n\n    <!-- Drawable used to show animated touch feedback. -->\n    <declare-styleable name=\"RippleDrawable\">\n        <!-- The color to use for ripple effects. This attribute is required. -->\n        <attr name=\"color\" />\n        <!-- The radius of the ripple when fully expanded. By default, the\n             radius is computed based on the size of the ripple's container. -->\n        <attr name=\"radius\" />\n        <!-- Secondary color of the ripple effect. -->\n        <attr name=\"effectColor\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"ScaleDrawable\">\n        <!-- Scale width, expressed as a percentage of the drawable's bound. The value's\n             format is XX%. For instance: 100%, 12.5%, etc.-->\n        <attr name=\"scaleWidth\" format=\"string\" />\n        <!-- Scale height, expressed as a percentage of the drawable's bound. The value's\n             format is XX%. For instance: 100%, 12.5%, etc.-->\n        <attr name=\"scaleHeight\" format=\"string\" />\n        <!-- Specifies where the drawable is positioned after scaling. The default value is\n             left. -->\n        <attr name=\"scaleGravity\">\n            <!-- Push object to the top of its container, not changing its size. -->\n            <flag name=\"top\" value=\"0x30\" />\n            <!-- Push object to the bottom of its container, not changing its size. -->\n            <flag name=\"bottom\" value=\"0x50\" />\n            <!-- Push object to the left of its container, not changing its size. -->\n            <flag name=\"left\" value=\"0x03\" />\n            <!-- Push object to the right of its container, not changing its size. -->\n            <flag name=\"right\" value=\"0x05\" />\n            <!-- Place object in the vertical center of its container, not changing its size. -->\n            <flag name=\"center_vertical\" value=\"0x10\" />\n            <!-- Grow the vertical size of the object if needed so it completely fills its container. -->\n            <flag name=\"fill_vertical\" value=\"0x70\" />\n            <!-- Place object in the horizontal center of its container, not changing its size. -->\n            <flag name=\"center_horizontal\" value=\"0x01\" />\n            <!-- Grow the horizontal size of the object if needed so it completely fills its container. -->\n            <flag name=\"fill_horizontal\" value=\"0x07\" />\n            <!-- Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. -->\n            <flag name=\"center\" value=\"0x11\" />\n            <!-- Grow the horizontal and vertical size of the object if needed so it completely fills its container. -->\n            <flag name=\"fill\" value=\"0x77\" />\n            <!-- Additional option that can be set to have the top and/or bottom edges of\n                 the child clipped to its container's bounds.\n                 The clip will be based on the vertical gravity: a top gravity will clip the bottom\n                 edge, a bottom gravity will clip the top edge, and neither will clip both edges. -->\n            <flag name=\"clip_vertical\" value=\"0x80\" />\n            <!-- Additional option that can be set to have the left and/or right edges of\n                 the child clipped to its container's bounds.\n                 The clip will be based on the horizontal gravity: a left gravity will clip the right\n                 edge, a right gravity will clip the left edge, and neither will clip both edges. -->\n            <flag name=\"clip_horizontal\" value=\"0x08\" />\n            <!-- Push object to the beginning of its container, not changing its size. -->\n            <flag name=\"start\" value=\"0x00800003\" />\n            <!-- Push object to the end of its container, not changing its size. -->\n            <flag name=\"end\" value=\"0x00800005\" />\n        </attr>\n        <!-- Specifies the initial drawable level in the range 0 to 10000. -->\n        <attr name=\"level\" format=\"integer\" />\n        <!-- Reference to a drawable resource to draw with the specified scale. -->\n        <attr name=\"drawable\" />\n        <!-- Use the drawable's intrinsic width and height as minimum size values.\n             Useful if the target drawable is a 9-patch or otherwise should not be scaled\n             down beyond a minimum size. -->\n        <attr name=\"useIntrinsicSizeAsMinimum\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"ClipDrawable\">\n        <!-- The orientation for the clip. -->\n        <attr name=\"clipOrientation\">\n            <!-- Clip the drawable horizontally. -->\n            <flag name=\"horizontal\" value=\"1\" />\n            <!-- Clip the drawable vertically. -->\n            <flag name=\"vertical\" value=\"2\" />\n        </attr>\n        <!-- Specifies where to clip within the drawable. The default value is\n             left. -->\n        <attr name=\"gravity\" />\n        <!-- Reference to a drawable resource to draw with the specified scale. -->\n        <attr name=\"drawable\" />\n    </declare-styleable>\n\n    <!-- Defines the padding of a ShapeDrawable. -->\n    <declare-styleable name=\"ShapeDrawablePadding\">\n        <!-- Left padding. -->\n        <attr name=\"left\" />\n        <!-- Top padding. -->\n        <attr name=\"top\" />\n        <!-- Right padding. -->\n        <attr name=\"right\" />\n        <!-- Bottom padding. -->\n        <attr name=\"bottom\" />\n    </declare-styleable>\n\n    <!-- Drawable used to draw shapes. -->\n    <declare-styleable name=\"ShapeDrawable\">\n        <!-- Defines the color of the shape. -->\n        <attr name=\"color\" />\n        <!-- Defines the width of the shape. -->\n        <attr name=\"width\" />\n        <!-- Defines the height of the shape. -->\n        <attr name=\"height\" />\n        <!-- Enables or disables dithering. -->\n        <attr name=\"dither\" />\n        <!-- If set, specifies the color to apply to the drawable as a tint. By default,\n             no tint is applied. May be a color state list. -->\n        <attr name=\"tint\" />\n        <!-- When a tint color is set, specifies its Porter-Duff blending mode. The\n             default value is src_in, which treats the drawable as an alpha mask. -->\n        <attr name=\"tintMode\" />\n    </declare-styleable>\n\n    <!-- ========================== -->\n    <!--   VectorDrawable class   -->\n    <!-- ========================== -->\n    <eat-comment />\n\n    <!-- Drawable used to draw vector paths. -->\n    <declare-styleable name=\"VectorDrawable\">\n        <!-- If set, specifies the color to apply to the drawable as a tint. By default,\n             no tint is applied. May be a color state list. -->\n        <attr name=\"tint\" />\n        <!-- When a tint color is set, specifies its Porter-Duff blending mode. The\n             default value is src_in, which treats the drawable as an alpha mask. -->\n        <attr name=\"tintMode\" />\n        <!-- Indicates if the drawable needs to be mirrored when its layout direction is\n             RTL (right-to-left). -->\n        <attr name=\"autoMirrored\" />\n        <!-- The intrinsic width of the Vector Drawable. -->\n        <attr name=\"width\" />\n        <!-- The intrinsic height of the Vector Drawable. -->\n        <attr name=\"height\" />\n        <!-- The width of the canvas the drawing is on. -->\n        <attr name=\"viewportWidth\" format=\"float\"/>\n        <!-- The height of the canvas the drawing is on. -->\n        <attr name=\"viewportHeight\" format=\"float\"/>\n        <!-- The name of this vector drawable. -->\n        <attr name=\"name\" />\n        <!-- The opacity of the whole vector drawable, as a value between 0\n             (completely transparent) and 1 (completely opaque). -->\n        <attr name=\"alpha\" />\n        <!-- Left optical inset. -->\n        <attr name=\"opticalInsetLeft\" format=\"dimension\" />\n        <!-- Top optical inset. -->\n        <attr name=\"opticalInsetTop\" format=\"dimension\" />\n        <!-- Right optical inset. -->\n        <attr name=\"opticalInsetRight\" format=\"dimension\" />\n        <!-- Bottom optical inset. -->\n        <attr name=\"opticalInsetBottom\" format=\"dimension\" />\n    </declare-styleable>\n\n    <!-- Defines the group used in VectorDrawables. -->\n    <declare-styleable name=\"VectorDrawableGroup\">\n        <!-- The name of this group. -->\n        <attr name=\"name\" />\n        <!-- The amount to rotate the group. -->\n        <attr name=\"rotation\" />\n        <!-- The X coordinate of the center of rotation of a group. -->\n        <attr name=\"pivotX\" />\n        <!-- The Y coordinate of the center of rotation of a group. -->\n        <attr name=\"pivotY\" />\n        <!-- The amount to translate the group on X coordinate. -->\n        <attr name=\"translateX\" format=\"float\"/>\n        <!-- The amount to translate the group on Y coordinate. -->\n        <attr name=\"translateY\" format=\"float\"/>\n        <!-- The amount to scale the group on X coordinate. -->\n        <attr name=\"scaleX\" />\n        <!-- The amount to scale the group on X coordinate. -->\n        <attr name=\"scaleY\" />\n    </declare-styleable>\n\n    <!-- Defines the path used in VectorDrawables. -->\n    <declare-styleable name=\"VectorDrawablePath\">\n        <!-- The name of this path. -->\n        <attr name=\"name\" />\n        <!-- The width a path stroke. -->\n        <attr name=\"strokeWidth\" format=\"float\" />\n        <!-- The color to stroke the path if not defined implies no stroke. -->\n        <attr name=\"strokeColor\" format=\"color\" />\n        <!-- The opacity of a path stroke, as a value between 0 (completely transparent)\n             and 1 (completely opaque). -->\n        <attr name=\"strokeAlpha\" format=\"float\" />\n        <!-- The color to fill the path if not defined implies no fill. -->\n        <attr name=\"fillColor\" format=\"color\" />\n        <!-- The alpha of the path fill, as a value between 0 (completely transparent)\n             and 1 (completely opaque). -->\n        <attr name=\"fillAlpha\" format=\"float\" />\n        <!-- The specification of the operations that define the path. -->\n        <attr name=\"pathData\" format=\"string\" />\n        <!-- The fraction of the path to trim from the start from 0 to 1. -->\n        <attr name=\"trimPathStart\" format=\"float\" />\n        <!-- The fraction of the path to trim from the end from 0 to 1 . -->\n        <attr name=\"trimPathEnd\" format=\"float\" />\n        <!-- Shift trim region (allows visible region to include the start and end) from 0 to 1. -->\n        <attr name=\"trimPathOffset\" format=\"float\" />\n        <!-- sets the linecap for a stroked path. -->\n        <attr name=\"strokeLineCap\" format=\"enum\">\n            <enum name=\"butt\" value=\"0\"/>\n            <enum name=\"round\" value=\"1\"/>\n            <enum name=\"square\" value=\"2\"/>\n        </attr>\n        <!-- sets the lineJoin for a stroked path. -->\n        <attr name=\"strokeLineJoin\" format=\"enum\">\n            <enum name=\"miter\" value=\"0\"/>\n            <enum name=\"round\" value=\"1\"/>\n            <enum name=\"bevel\" value=\"2\"/>\n        </attr>\n        <!-- sets the Miter limit for a stroked path. -->\n        <attr name=\"strokeMiterLimit\" format=\"float\"/>\n        <!-- sets the fillType for a path. It is the same as SVG's \"fill-rule\" properties.\n             For more details, see https://www.w3.org/TR/SVG/painting.html#FillRuleProperty. -->\n        <attr name=\"fillType\" format=\"enum\">\n            <enum name=\"nonZero\" value=\"0\"/>\n            <enum name=\"evenOdd\" value=\"1\"/>\n        </attr>\n    </declare-styleable>\n\n    <!-- Defines the clip path used in VectorDrawables. -->\n    <declare-styleable name=\"VectorDrawableClipPath\">\n        <!-- The Name of this path. -->\n        <attr name=\"name\" />\n        <!-- The specification of the operations that define the path. -->\n        <attr name=\"pathData\"/>\n    </declare-styleable>\n\n    <!-- ========================== -->\n    <!--   AnimatedVectorDrawable class   -->\n    <!-- ========================== -->\n    <eat-comment />\n\n    <!-- Define the AnimatedVectorDrawable. -->\n    <declare-styleable name=\"AnimatedVectorDrawable\">\n        <!-- The static vector drawable. -->\n        <attr name=\"drawable\" />\n    </declare-styleable>\n\n    <!-- Defines the target used in the AnimatedVectorDrawable. -->\n    <declare-styleable name=\"AnimatedVectorDrawableTarget\">\n        <!-- The name of the target path, group or vector drawable. -->\n        <attr name=\"name\" />\n        <!-- The animation for the target path, group or vector drawable. -->\n        <attr name=\"animation\" />\n    </declare-styleable>\n\n    <!-- ========================== -->\n    <!-- Animation class attributes -->\n    <!-- ========================== -->\n    <eat-comment />\n\n    <declare-styleable name=\"Animation\">\n        <!-- Defines the interpolator used to smooth the animation movement in time. -->\n        <attr name=\"interpolator\" />\n        <!-- When set to true, the value of fillBefore is taken into account. -->\n        <attr name=\"fillEnabled\" format=\"boolean\" />\n        <!-- When set to true or when fillEnabled is not set to true, the animation transformation\n             is applied before the animation has started. The default value is true. -->\n        <attr name=\"fillBefore\" format=\"boolean\" />\n        <!-- When set to true, the animation transformation is applied after the animation is\n             over. The default value is false. If fillEnabled is not set to true and the\n             animation is not set on a View, fillAfter is assumed to be true.-->\n        <attr name=\"fillAfter\" format=\"boolean\" />\n        <!-- Amount of time (in milliseconds) for the animation to run. -->\n        <attr name=\"duration\" />\n        <!-- Delay in milliseconds before the animation runs, once start time is reached. -->\n        <attr name=\"startOffset\" format=\"integer\" />\n        <!-- Defines how many times the animation should repeat. The default value is 0. -->\n        <attr name=\"repeatCount\" format=\"integer\">\n            <enum name=\"infinite\" value=\"-1\" />\n        </attr>\n        <!-- Defines the animation behavior when it reaches the end and the repeat count is\n             greater than 0 or infinite. The default value is restart. -->\n        <attr name=\"repeatMode\">\n            <!-- The animation starts again from the beginning. -->\n            <enum name=\"restart\" value=\"1\" />\n            <!-- The animation plays backward. -->\n            <enum name=\"reverse\" value=\"2\" />\n        </attr>\n        <!-- Allows for an adjustment of the Z ordering of the content being\n             animated for the duration of the animation.  The default value is normal. -->\n        <attr name=\"zAdjustment\">\n            <!-- The content being animated be kept in its current Z order. -->\n            <enum name=\"normal\" value=\"0\" />\n            <!-- The content being animated is forced on top of all other\n                 content for the duration of the animation. -->\n            <enum name=\"top\" value=\"1\" />\n            <!-- The content being animated is forced under all other\n                 content for the duration of the animation. -->\n            <enum name=\"bottom\" value=\"-1\" />\n        </attr>\n        <!-- Special background behind animation.  Only for use with window\n             animations.  Can only be a color, and only black.  If 0, the\n             default, there is no background. -->\n        <attr name=\"background\" />\n        <!-- Special option for window animations: if this window is on top\n             of a wallpaper, don't animate the wallpaper with it. -->\n        <attr name=\"detachWallpaper\" format=\"boolean\" />\n        <!-- Special option for window animations: show the wallpaper behind when running this\n             animation. -->\n        <attr name=\"showWallpaper\" format=\"boolean\" />\n        <!-- Special option for window animations: whether window should have rounded corners.\n             @see ScreenDecorationsUtils#getWindowCornerRadius(Resources) -->\n        <attr name=\"hasRoundedCorners\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"AnimationSet\">\n        <attr name=\"shareInterpolator\" format=\"boolean\" />\n        <attr name=\"fillBefore\" />\n        <attr name=\"fillAfter\" />\n        <attr name=\"duration\" />\n        <attr name=\"startOffset\" />\n        <attr name=\"repeatMode\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"RotateAnimation\">\n        <attr name=\"fromDegrees\" />\n        <attr name=\"toDegrees\" />\n        <attr name=\"pivotX\" />\n        <attr name=\"pivotY\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"ScaleAnimation\">\n        <attr name=\"fromXScale\" format=\"float|fraction|dimension\" />\n        <attr name=\"toXScale\" format=\"float|fraction|dimension\" />\n        <attr name=\"fromYScale\" format=\"float|fraction|dimension\" />\n        <attr name=\"toYScale\" format=\"float|fraction|dimension\" />\n        <attr name=\"pivotX\" />\n        <attr name=\"pivotY\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"TranslateAnimation\">\n        <attr name=\"fromXDelta\" format=\"float|fraction\" />\n        <attr name=\"toXDelta\" format=\"float|fraction\" />\n        <attr name=\"fromYDelta\" format=\"float|fraction\" />\n        <attr name=\"toYDelta\" format=\"float|fraction\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"AlphaAnimation\">\n        <attr name=\"fromAlpha\" format=\"float\" />\n        <attr name=\"toAlpha\" format=\"float\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"ClipRectAnimation\">\n        <attr name=\"fromLeft\" format=\"fraction\" />\n        <attr name=\"fromTop\" format=\"fraction\" />\n        <attr name=\"fromRight\" format=\"fraction\" />\n        <attr name=\"fromBottom\" format=\"fraction\" />\n        <attr name=\"toLeft\" format=\"fraction\" />\n        <attr name=\"toTop\" format=\"fraction\" />\n        <attr name=\"toRight\" format=\"fraction\" />\n        <attr name=\"toBottom\" format=\"fraction\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"LayoutAnimation\">\n        <!-- Fraction of the animation duration used to delay the beginning of\n         the animation of each child. -->\n        <attr name=\"delay\" format=\"float|fraction\" />\n        <!-- Animation to use on each child. -->\n        <attr name=\"animation\" format=\"reference\" />\n        <!-- The order in which the animations will be started. -->\n        <attr name=\"animationOrder\">\n            <!-- Animations are started in the natural order. -->\n            <enum name=\"normal\" value=\"0\" />\n            <!-- Animations are started in the reverse order. -->\n            <enum name=\"reverse\" value=\"1\" />\n            <!-- Animations are started randomly. -->\n            <enum name=\"random\" value=\"2\" />\n        </attr>\n        <!-- Interpolator used to interpolate the delay between the start of\n         each animation. -->\n        <attr name=\"interpolator\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"GridLayoutAnimation\">\n        <!-- Fraction of the animation duration used to delay the beginning of\n         the animation of each column. -->\n        <attr name=\"columnDelay\" format=\"float|fraction\" />\n        <!-- Fraction of the animation duration used to delay the beginning of\n         the animation of each row. -->\n        <attr name=\"rowDelay\" format=\"float|fraction\" />\n        <!-- Direction of the animation in the grid. -->\n        <attr name=\"direction\">\n            <!-- Animates columns from left to right. -->\n            <flag name=\"left_to_right\" value=\"0x0\" />\n            <!-- Animates columns from right to left. -->\n            <flag name=\"right_to_left\" value=\"0x1\" />\n            <!-- Animates rows from top to bottom. -->\n            <flag name=\"top_to_bottom\" value=\"0x0\" />\n            <!-- Animates rows from bottom to top. -->\n            <flag name=\"bottom_to_top\" value=\"0x2\" />\n        </attr>\n        <!-- Priority of the rows and columns. When the priority is none,\n         both rows and columns have the same priority. When the priority is\n         column, the animations will be applied on the columns first. The same\n         goes for rows. -->\n        <attr name=\"directionPriority\">\n            <!-- Rows and columns are animated at the same time. -->\n            <enum name=\"none\"   value=\"0\" />\n            <!-- Columns are animated first. -->\n            <enum name=\"column\" value=\"1\" />\n            <!-- Rows are animated first. -->\n            <enum name=\"row\"    value=\"2\" />\n        </attr>\n    </declare-styleable>\n\n    <declare-styleable name=\"AccelerateInterpolator\">\n        <!-- This is the amount of deceleration to add when easing in. -->\n        <attr name=\"factor\" format=\"float\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"DecelerateInterpolator\">\n        <!-- This is the amount of acceleration to add when easing out. -->\n        <attr name=\"factor\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"CycleInterpolator\">\n        <attr name=\"cycles\" format=\"float\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"AnticipateInterpolator\">\n        <!-- This is the amount of tension. -->\n        <attr name=\"tension\" format=\"float\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"OvershootInterpolator\">\n        <!-- This is the amount of tension. -->\n        <attr name=\"tension\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"AnticipateOvershootInterpolator\">\n        <!-- This is the amount of tension. -->\n        <attr name=\"tension\" />\n        <!-- This is the amount by which to multiply the tension. -->\n        <attr name=\"extraTension\" format=\"float\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"PathInterpolator\">\n        <!-- The x coordinate of the first control point of the cubic Bezier. -->\n        <attr name=\"controlX1\" format=\"float\" />\n        <!-- The y coordinate of the first control point of the cubic Bezier. -->\n        <attr name=\"controlY1\" format=\"float\" />\n        <!-- The x coordinate of the second control point of the cubic Bezier. -->\n        <attr name=\"controlX2\" format=\"float\" />\n        <!-- The y coordinate of the second control point of the cubic Bezier. -->\n        <attr name=\"controlY2\" format=\"float\" />\n        <!-- The control points defined as a path.\n             When pathData is defined, then both of the control points of the\n             cubic Bezier will be ignored. -->\n        <attr name=\"pathData\"/>\n    </declare-styleable>\n\n    <!-- ========================== -->\n    <!-- Transition attributes -->\n    <!-- ========================== -->\n    <eat-comment />\n\n    <!-- Use specific transition subclass names as the root tag of the XML resource that\n         describes a {@link android.transition.Transition Transition},\n         such as <code>changeBounds</code>, <code>fade</code>, and <code>transitionSet</code>. -->\n    <declare-styleable name=\"Transition\">\n        <!-- Amount of time (in milliseconds) that the transition should run. -->\n        <attr name=\"duration\" />\n        <!-- Delay in milliseconds before the transition starts. -->\n        <attr name=\"startDelay\" format=\"integer\" />\n        <!-- Interpolator to be used in the animations spawned by this transition. -->\n        <attr name=\"interpolator\" />\n        <!-- The match order to use for the transition. This is a comma-separated\n             list of values, containing one or more of the following:\n             id, itemId, name, instance. These correspond to\n             {@link android.transition.Transition#MATCH_ID},\n             {@link android.transition.Transition#MATCH_ITEM_ID},\n             {@link android.transition.Transition#MATCH_NAME}, and\n             {@link android.transition.Transition#MATCH_INSTANCE}, respectively.\n             This corresponds to {@link android.transition.Transition#setMatchOrder(int...)}. -->\n        <attr name=\"matchOrder\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- @hide For internal use only. Use only as directed. -->\n    <declare-styleable name=\"EpicenterTranslateClipReveal\">\n        <attr name=\"interpolatorX\" format=\"reference\" />\n        <attr name=\"interpolatorY\" format=\"reference\" />\n        <attr name=\"interpolatorZ\" format=\"reference\" />\n    </declare-styleable>\n\n    <!-- Use <code>fade</code>as the root tag of the XML resource that\n         describes a {@link android.transition.Fade Fade} transition.\n         The attributes of the {@link android.R.styleable#Transition Transition}\n         resource are available in addition to the specific attributes of Fade\n         described here. -->\n    <declare-styleable name=\"Fade\">\n        <!-- Equivalent to <code>transitionVisibilityMode</code>, fadingMode works only\n             with the Fade transition. -->\n        <attr name=\"fadingMode\">\n            <!-- Fade will only fade appearing items in. -->\n            <enum name=\"fade_in\" value=\"1\" />\n            <!-- Fade will only fade disappearing items out. -->\n            <enum name=\"fade_out\" value=\"2\" />\n            <!-- Fade will fade appearing items in and disappearing items out. -->\n            <enum name=\"fade_in_out\" value=\"3\" />\n        </attr>\n    </declare-styleable>\n\n    <!-- Use <code>slide</code>as the root tag of the XML resource that\n         describes a {@link android.transition.Slide Slide} transition.\n         The attributes of the {@link android.R.styleable#Transition Transition}\n         resource are available in addition to the specific attributes of Slide\n         described here. -->\n    <declare-styleable name=\"Slide\">\n        <attr name=\"slideEdge\">\n            <!-- Slide to and from the left edge of the Scene. -->\n            <enum name=\"left\" value=\"0x03\" />\n            <!-- Slide to and from the top edge of the Scene. -->\n            <enum name=\"top\" value=\"0x30\" />\n            <!-- Slide to and from the right edge of the Scene. -->\n            <enum name=\"right\" value=\"0x05\" />\n            <!-- Slide to and from the bottom edge of the Scene. -->\n            <enum name=\"bottom\" value=\"0x50\" />\n            <!-- Slide to and from the x-axis position at the start of the Scene root. -->\n            <enum name=\"start\" value=\"0x00800003\"/>\n            <!-- Slide to and from the x-axis position at the end of the Scene root. -->\n            <enum name=\"end\" value=\"0x00800005\"/>\n        </attr>\n    </declare-styleable>\n\n    <!-- Use with {@link android.transition.Visibility} transitions, such as\n         <code>slide</code>, <code>explode</code>, and <code>fade</code> to mark which\n         views are supported. -->\n    <declare-styleable name=\"VisibilityTransition\">\n        <!-- Changes whether the transition supports appearing and/or disappearing Views.\n             Corresponds to {@link android.transition.Visibility#setMode(int)}. -->\n        <attr name=\"transitionVisibilityMode\">\n            <!-- Only appearing Views will be supported. -->\n            <flag name=\"mode_in\" value=\"1\" />\n            <!-- Only disappearing Views will be supported. -->\n            <flag name=\"mode_out\" value=\"2\" />\n        </attr>\n    </declare-styleable>\n    <!-- Use <code>target</code> as the root tag of the XML resource that\n     describes a {@link android.transition.Transition#addTarget(int)\n     targetId} of a transition. There can be one or more targets inside\n     a <code>targets</code> tag, which is itself inside an appropriate\n     {@link android.R.styleable#Transition Transition} tag.\n     -->\n    <declare-styleable name=\"TransitionTarget\">\n        <!-- The id of a target on which this transition will animate changes. -->\n        <attr name=\"targetId\" format=\"reference\" />\n        <!-- The id of a target to exclude from this transition. -->\n        <attr name=\"excludeId\" format=\"reference\" />\n        <!-- The fully-qualified name of the Class to include in this transition. -->\n        <attr name=\"targetClass\" />\n        <!-- The fully-qualified name of the Class to exclude from this transition. -->\n        <attr name=\"excludeClass\" format=\"string\" />\n        <!-- The transitionName of the target on which this transition will animation changes. -->\n        <attr name=\"targetName\" format=\"string\" />\n        <!-- The transitionName of the target to exclude from this transition. -->\n        <attr name=\"excludeName\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- Use <code>set</code> as the root tag of the XML resource that\n         describes a {@link android.transition.TransitionSet\n         TransitionSet} transition. -->\n    <declare-styleable name=\"TransitionSet\">\n        <attr name=\"transitionOrdering\">\n            <!-- child transitions should be played together. -->\n            <enum name=\"together\" value=\"0\" />\n            <!-- child transitions should be played sequentially, in the same order\n            as the xml. -->\n            <enum name=\"sequential\" value=\"1\" />\n        </attr>\n    </declare-styleable>\n\n    <!-- Use <code>changeTransform</code> as the root tag of the XML resource that\n         describes a {@link android.transition.ChangeTransform} transition. -->\n    <declare-styleable name=\"ChangeTransform\">\n        <!-- A parent change should use an overlay or affect the transform of the\n             transitionining View. Default is true. Corresponds to\n             {@link android.transition.ChangeTransform#setReparentWithOverlay(boolean)}. -->\n        <attr name=\"reparentWithOverlay\" format=\"boolean\"/>\n\n        <!-- Tells ChangeTransform to track parent changes. Default is true. Corresponds to\n             {@link android.transition.ChangeTransform#setReparent(boolean)}. -->\n        <attr name=\"reparent\" format=\"boolean\"/>\n    </declare-styleable>\n\n    <!-- Use <code>changeBounds</code>as the root tag of the XML resource that\n         describes a {@link android.transition.ChangeBounds} transition.\n         The attributes of the {@link android.R.styleable#Transition Transition}\n         resource are available in addition to the specific attributes of ChangeBounds\n         described here. -->\n    <declare-styleable name=\"ChangeBounds\">\n        <!-- Resize the view by adjusting the clipBounds rather than changing the\n             dimensions of the view itself. The default value is false. -->\n        <attr name=\"resizeClip\" format=\"boolean\"/>\n    </declare-styleable>\n\n    <!-- Use <code>transitionManager</code> as the root tag of the XML resource that\n         describes a {@link android.transition.TransitionManager\n         TransitionManager}. -->\n    <declare-styleable name=\"TransitionManager\">\n        <!-- The id of a transition to be used in a particular scene change. -->\n        <attr name=\"transition\" format=\"reference\" />\n        <!-- The originating scene in this scene change. -->\n        <attr name=\"fromScene\" format=\"reference\" />\n        <!-- The destination scene in this scene change. -->\n        <attr name=\"toScene\" format=\"reference\" />\n    </declare-styleable>\n\n    <!-- Use <code>arcMotion</code> as the root tag of the XML resource that\n         describes a {@link android.transition.ArcMotion}. This must be used\n         within a transition with which the PathMotion should be associated. -->\n    <declare-styleable name=\"ArcMotion\">\n        <!-- The minimum arc angle in degrees between the start and end points when\n             they are close to horizontal. -->\n        <attr name=\"minimumHorizontalAngle\" format=\"float\" />\n        <!-- The minimum arc angle in degrees between the start and end points when\n             they are close to vertical. -->\n        <attr name=\"minimumVerticalAngle\" format=\"float\" />\n        <!-- The maximum arc angle in degrees between the start and end points. -->\n        <attr name=\"maximumAngle\" format=\"float\" />\n    </declare-styleable>\n\n    <!-- Use <code>patternPathMotion</code> as the root tag of the XML resource that\n         describes a {@link android.transition.PatternPathMotion}. This must be used\n         within a transition with which the PathMotion should be associated. -->\n    <declare-styleable name=\"PatternPathMotion\">\n        <!-- The path string describing the pattern to use for the PathPathMotion. -->\n        <attr name=\"patternPathData\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- ========================== -->\n    <!-- ValueAnimator class attributes -->\n    <!-- ========================== -->\n    <eat-comment />\n\n    <declare-styleable name=\"Animator\">\n        <!-- Defines the interpolator used to smooth the animation movement in time. -->\n        <attr name=\"interpolator\" />\n        <!-- Amount of time (in milliseconds) for the animation to run. -->\n        <attr name=\"duration\" />\n        <!-- Delay in milliseconds before the animation runs, once start time is reached. -->\n        <attr name=\"startOffset\"/>\n        <!-- Defines how many times the animation should repeat. The default value is 0. -->\n        <attr name=\"repeatCount\"/>\n        <!-- Defines the animation behavior when it reaches the end and the repeat count is\n             greater than 0 or infinite. The default value is restart. -->\n        <attr name=\"repeatMode\"/>\n        <!-- Value the animation starts from. -->\n        <attr name=\"valueFrom\" format=\"float|integer|color|dimension|string\"/>\n        <!-- Value the animation animates to. -->\n        <attr name=\"valueTo\" format=\"float|integer|color|dimension|string\"/>\n        <!-- The type of valueFrom and valueTo. -->\n        <attr name=\"valueType\">\n            <!-- The given values are floats. This is the default value if valueType is\n                 unspecified. Note that if any value attribute has a color value\n                 (beginning with \"#\"), then this attribute is ignored and the color values are\n                 interpreted as integers. -->\n            <enum name=\"floatType\" value=\"0\" />\n            <!-- values are integers. -->\n            <enum name=\"intType\"   value=\"1\" />\n            <!-- values are paths defined as strings.\n                 This type is used for path morphing in AnimatedVectorDrawable. -->\n            <enum name=\"pathType\"   value=\"2\" />\n            <!-- values are colors, which are integers starting with \"#\". -->\n            <enum name=\"colorType\"   value=\"3\" />\n        </attr>\n        <!-- Placeholder for a deleted attribute. This should be removed before M release. -->\n        <attr name=\"removeBeforeMRelease\" format=\"integer\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"PropertyValuesHolder\">\n        <attr name=\"valueType\" />\n        <attr name=\"propertyName\" />\n        <attr name=\"valueFrom\" />\n        <attr name=\"valueTo\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"Keyframe\">\n        <attr name=\"valueType\" />\n        <attr name=\"value\" />\n        <attr name=\"fraction\" format=\"float\" />\n        <!-- Defines a per-interval interpolator for this keyframe. This interpolator will be used\n             to interpolate between this keyframe and the previous keyframe. -->\n        <attr name=\"interpolator\" />\n    </declare-styleable>\n\n    <!-- ========================== -->\n    <!-- ObjectAnimator class attributes -->\n    <!-- ========================== -->\n    <eat-comment />\n\n    <declare-styleable name=\"PropertyAnimator\">\n        <!-- Name of the property being animated. -->\n        <attr name=\"propertyName\" format=\"string\"/>\n        <!-- Name of the property being animated as the X coordinate of the pathData. -->\n        <attr name=\"propertyXName\" format=\"string\"/>\n        <!-- Name of the property being animated as the Y coordinate of the pathData. -->\n        <attr name=\"propertyYName\" format=\"string\"/>\n        <!-- The path used to animate the properties in the ObjectAnimator. -->\n        <attr name=\"pathData\"/>\n    </declare-styleable>\n\n\n    <!-- ========================== -->\n    <!-- AnimatorSet class attributes -->\n    <!-- ========================== -->\n    <eat-comment />\n\n    <declare-styleable name=\"AnimatorSet\">\n        <!-- Name of the property being animated. -->\n        <attr name=\"ordering\">\n            <!-- child animations should be played together. -->\n            <enum name=\"together\" value=\"0\" />\n            <!-- child animations should be played sequentially, in the same order as the xml. -->\n            <enum name=\"sequentially\" value=\"1\" />\n        </attr>\n    </declare-styleable>\n\n    <!-- ========================== -->\n    <!-- State attributes           -->\n    <!-- ========================== -->\n    <eat-comment />\n\n    <!-- Set of framework-provided states that may be specified on a Drawable. Actual usage of\n         states may vary between view implementations, as documented on the individual state\n         attributes. -->\n    <declare-styleable name=\"DrawableStates\">\n        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},\n             set when a view has input focus. -->\n        <attr name=\"state_focused\" format=\"boolean\" />\n        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},\n             set when a view's window has input focus. -->\n        <attr name=\"state_window_focused\" format=\"boolean\" />\n        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},\n             set when a view is enabled. -->\n        <attr name=\"state_enabled\" format=\"boolean\" />\n        <!-- State identifier indicating that the object <var>may</var> display a check mark. See\n             {@link android.R.attr#state_checked} for the identifier that indicates whether it is\n             actually checked. -->\n        <attr name=\"state_checkable\" format=\"boolean\"/>\n        <!-- State identifier indicating that the object is currently checked.  See\n             {@link android.R.attr#state_checkable} for an additional identifier that can indicate\n             if any object may ever display a check, regardless of whether state_checked is\n             currently set. -->\n        <attr name=\"state_checked\" format=\"boolean\"/>\n        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},\n             set when a view (or one of its parents) is currently selected. -->\n        <attr name=\"state_selected\" format=\"boolean\" />\n        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},\n             set when the user is pressing down in a view. -->\n        <attr name=\"state_pressed\" format=\"boolean\" />\n        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},\n             set when a view or its parent has been \"activated\" meaning the user has currently\n             marked it as being of interest.  This is an alternative representation of\n             state_checked for when the state should be propagated down the view hierarchy. -->\n        <attr name=\"state_activated\" format=\"boolean\" />\n        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},\n             set when a view or drawable is considered \"active\" by its host. Actual usage may vary\n             between views. Consult the host view documentation for details. -->\n        <attr name=\"state_active\" format=\"boolean\" />\n        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},\n             set when a view or drawable is considered \"single\" by its host. Actual usage may vary\n             between views. Consult the host view documentation for details. -->\n        <attr name=\"state_single\" format=\"boolean\" />\n        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},\n             set when a view or drawable is in the first position in an ordered set. Actual usage\n             may vary between views. Consult the host view documentation for details. -->\n        <attr name=\"state_first\" format=\"boolean\" />\n        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},\n             set when a view or drawable is in the middle position in an ordered set. Actual usage\n             may vary between views. Consult the host view documentation for details. -->\n        <attr name=\"state_middle\" format=\"boolean\" />\n        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},\n             set when a view or drawable is in the last position in an ordered set. Actual usage\n             may vary between views. Consult the host view documentation for details. -->\n        <attr name=\"state_last\" format=\"boolean\" />\n        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},\n             indicating that the Drawable is in a view that is hardware accelerated.\n             This means that the device can at least render a full-screen scaled\n             bitmap with one layer of text and bitmaps composited on top of it\n             at 60fps.  When this is set, the colorBackgroundCacheHint will be\n             ignored even if it specifies a solid color, since that optimization\n             is not needed. -->\n        <attr name=\"state_accelerated\" format=\"boolean\" />\n        <!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},\n             set when a pointer is hovering over the view. -->\n        <attr name=\"state_hovered\" format=\"boolean\" />\n        <!-- State for {@link android.graphics.drawable.StateListDrawable StateListDrawable}\n             indicating that the Drawable is in a view that is capable of accepting a drop of\n             the content currently being manipulated in a drag-and-drop operation. -->\n        <attr name=\"state_drag_can_accept\" format=\"boolean\" />\n        <!-- State for {@link android.graphics.drawable.StateListDrawable StateListDrawable}\n             indicating that a drag operation (for which the Drawable's view is a valid recipient)\n             is currently positioned over the Drawable. -->\n        <attr name=\"state_drag_hovered\" format=\"boolean\" />\n        <!-- State for {@link android.graphics.drawable.StateListDrawable StateListDrawable}\n             indicating that a View has accessibility focus. -->\n        <attr name=\"state_accessibility_focused\" format=\"boolean\" />\n    </declare-styleable>\n    <declare-styleable name=\"ViewDrawableStates\">\n        <attr name=\"state_pressed\" />\n        <attr name=\"state_focused\" />\n        <attr name=\"state_selected\" />\n        <attr name=\"state_window_focused\" />\n        <attr name=\"state_enabled\" />\n        <attr name=\"state_activated\" />\n        <attr name=\"state_accelerated\" />\n        <attr name=\"state_hovered\" />\n        <attr name=\"state_drag_can_accept\" />\n        <attr name=\"state_drag_hovered\" />\n    </declare-styleable>\n    <!-- State array representing a menu item that is currently checked. -->\n    <declare-styleable name=\"MenuItemCheckedState\">\n        <attr name=\"state_checkable\" />\n        <attr name=\"state_checked\" />\n    </declare-styleable>\n    <!-- State array representing a menu item that is checkable but is not currently checked. -->\n    <declare-styleable name=\"MenuItemUncheckedState\">\n        <attr name=\"state_checkable\" />\n    </declare-styleable>\n    <!-- State array representing a menu item that is currently focused and checked. -->\n    <declare-styleable name=\"MenuItemCheckedFocusedState\">\n        <attr name=\"state_checkable\" />\n        <attr name=\"state_checked\" />\n        <attr name=\"state_focused\" />\n    </declare-styleable>\n    <!-- State array representing a menu item that is focused and checkable but is not currently checked. -->\n    <declare-styleable name=\"MenuItemUncheckedFocusedState\">\n        <attr name=\"state_checkable\" />\n        <attr name=\"state_focused\" />\n    </declare-styleable>\n    <!-- State array representing an expandable list child's indicator. -->\n    <declare-styleable name=\"ExpandableListChildIndicatorState\">\n        <!-- State identifier indicating the child is the last child within its group. -->\n        <attr name=\"state_last\" />\n    </declare-styleable>\n    <!-- State array representing an expandable list group's indicator. -->\n    <declare-styleable name=\"ExpandableListGroupIndicatorState\">\n        <!-- State identifier indicating the group is expanded. -->\n        <attr name=\"state_expanded\" format=\"boolean\" />\n        <!-- State identifier indicating the group is empty (has no children). -->\n        <attr name=\"state_empty\" format=\"boolean\" />\n    </declare-styleable>\n    <declare-styleable name=\"PopupWindowBackgroundState\">\n        <!-- State identifier indicating the popup will be above the anchor. -->\n        <attr name=\"state_above_anchor\" format=\"boolean\" />\n    </declare-styleable>\n    <declare-styleable name=\"TextViewMultiLineBackgroundState\">\n        <!-- State identifier indicating a TextView has a multi-line layout. -->\n        <attr name=\"state_multiline\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- ***************************************************************** -->\n    <!-- Support for Searchable activities. -->\n    <!-- ***************************************************************** -->\n    <eat-comment />\n\n    <!-- Searchable activities and applications must provide search configuration information\n        in an XML file, typically called searchable.xml.  This file is referenced in your manifest.\n        For a more in-depth discussion of search configuration, please refer to\n        {@link android.app.SearchManager}. -->\n    <declare-styleable name=\"Searchable\">\n          <!--<strong>This is deprecated.</strong><br/>The default\n              application icon is now always used, so this attribute is\n              obsolete.-->\n        <attr name=\"icon\" />\n        <!-- This is the user-displayed name of the searchable activity.  <i>Required\n            attribute.</i> -->\n        <attr name=\"label\" />\n        <!-- If supplied, this string will be displayed as a hint to the user.  <i>Optional\n            attribute.</i> -->\n        <attr name=\"hint\" />\n        <!-- If supplied, this string will be displayed as the text of the \"Search\" button.\n          <i>Optional attribute.</i>\n          {@deprecated This will create a non-standard UI appearance, because the search bar UI is\n                       changing to use only icons for its buttons.}-->\n        <attr name=\"searchButtonText\" format=\"string\" />\n        <attr name=\"inputType\" />\n        <attr name=\"imeOptions\" />\n\n        <!-- Additional features are controlled by mode bits in this field.  Omitting\n            this field, or setting to zero, provides default behavior.  <i>Optional attribute.</i>\n        -->\n        <attr name=\"searchMode\">\n          <!-- If set, this flag enables the display of the search target (label) within the\n               search bar.  If neither bad mode is selected, no badge will be shown. -->\n          <flag name=\"showSearchLabelAsBadge\" value=\"0x04\" />\n          <!--<strong>This is deprecated.</strong><br/>The default\n              application icon is now always used, so this option is\n              obsolete.-->\n          <flag name=\"showSearchIconAsBadge\" value=\"0x08\" />\n          <!-- If set, this flag causes the suggestion column SUGGEST_COLUMN_INTENT_DATA to\n               be considered as the text for suggestion query rewriting.  This should only\n               be used when the values in SUGGEST_COLUMN_INTENT_DATA are suitable for user\n               inspection and editing - typically, HTTP/HTTPS Uri's. -->\n          <flag name=\"queryRewriteFromData\" value=\"0x10\" />\n          <!-- If set, this flag causes the suggestion column SUGGEST_COLUMN_TEXT_1 to\n               be considered as the text for suggestion query rewriting.  This should be used\n               for suggestions in which no query text is provided and the SUGGEST_COLUMN_INTENT_DATA\n               values are not suitable for user inspection and editing. -->\n          <flag name=\"queryRewriteFromText\" value=\"0x20\" />\n        </attr>\n\n        <!-- Voice search features are controlled by mode bits in this field.  Omitting\n            this field, or setting to zero, provides default behavior.\n            If showVoiceSearchButton is set, then launchWebSearch or launchRecognizer must\n            also be set.  <i>Optional attribute.</i>\n        -->\n        <attr name=\"voiceSearchMode\">\n          <!-- If set, display a voice search button.  This only takes effect if voice search is\n               available on the device. -->\n          <flag name=\"showVoiceSearchButton\" value=\"0x01\" />\n          <!-- If set, the voice search button will take the user directly to a built-in\n               voice web search activity.  Most applications will not use this flag, as it\n               will take the user away from the activity in which search was invoked. -->\n          <flag name=\"launchWebSearch\" value=\"0x02\" />\n          <!-- If set, the voice search button will take the user directly to a built-in\n               voice recording activity.  This activity will prompt the user to speak,\n               transcribe the spoken text, and forward the resulting query\n               text to the searchable activity, just as if the user had typed it into\n               the search UI and clicked the search button. -->\n          <flag name=\"launchRecognizer\" value=\"0x04\" />\n        </attr>\n\n        <!-- If provided, this specifies the language model that should be used by the\n             voice recognition system.  See\n             {@link android.speech.RecognizerIntent#EXTRA_LANGUAGE_MODEL } for more information.\n             If not provided, the default value\n             {@link android.speech.RecognizerIntent#LANGUAGE_MODEL_FREE_FORM } will be used. -->\n        <attr name=\"voiceLanguageModel\" format=\"string\" />\n        <!-- If provided, this specifies a prompt that will be displayed during voice input. -->\n        <attr name=\"voicePromptText\" format=\"string\" />\n        <!-- If provided, this specifies the spoken language to be expected, and that it will be\n             different than the one set in the {@link java.util.Locale#getDefault()}. -->\n        <attr name=\"voiceLanguage\" format=\"string\" />\n        <!-- If provided, enforces the maximum number of results to return, including the \"best\"\n             result which will always be provided as the SEARCH intent's primary query.  Must be one\n             or greater.  If not provided, the recognizer will choose how many results to return.\n             -->\n        <attr name=\"voiceMaxResults\" format=\"integer\" />\n\n        <!-- If provided, this is the trigger indicating that the searchable activity\n            provides suggestions as well.  The value must be a fully-qualified content provider\n            authority (for example, \"com.example.android.apis.SuggestionProvider\") and should match\n            the \"android:authorities\" tag in your content provider's manifest entry.  <i>Optional\n            attribute.</i> -->\n        <attr name=\"searchSuggestAuthority\" format=\"string\" />\n        <!-- If provided, this will be inserted in the suggestions query Uri, after the authority\n            you have provide but before the standard suggestions path. <i>Optional attribute.</i>\n            -->\n        <attr name=\"searchSuggestPath\" format=\"string\" />\n        <!-- If provided, suggestion queries will be passed into your query function\n            as the <i>selection</i> parameter.  Typically this will be a WHERE clause for your\n            database, and will contain a single question mark, which represents the actual query\n            string that has been typed by the user.  If not provided, then the user query text\n            will be appended to the query Uri (after an additional \"/\".)  <i>Optional\n            attribute.</i> -->\n        <attr name=\"searchSuggestSelection\" format=\"string\" />\n\n        <!-- If provided, and not overridden by an action in the selected suggestion, this\n            string will be placed in the action field of the {@link android.content.Intent Intent}\n            when the user clicks a suggestion.  <i>Optional attribute.</i> -->\n        <attr name=\"searchSuggestIntentAction\" format=\"string\" />\n        <!-- If provided, and not overridden by an action in the selected suggestion, this\n            string will be placed in the data field of the {@link android.content.Intent Intent}\n            when the user clicks a suggestion.  <i>Optional attribute.</i> -->\n        <attr name=\"searchSuggestIntentData\" format=\"string\" />\n\n        <!-- If provided, this is the minimum number of characters needed to trigger\n             search suggestions. The default value is 0. <i>Optional attribute.</i> -->\n        <attr name=\"searchSuggestThreshold\" format=\"integer\" />\n\n        <!-- If provided and <code>true</code>, this searchable activity will be\n             included in any global lists of search targets.\n             The default value is <code>false</code>. <i>Optional attribute.</i>. -->\n        <attr name=\"includeInGlobalSearch\" format=\"boolean\" />\n\n        <!-- If provided and <code>true</code>, this searchable activity will be invoked for all\n             queries in a particular session. If set to <code>false</code> and the activity\n             returned zero results for a query, it will not be invoked again in that session for\n             supersets of that zero-results query. For example, if the activity returned zero\n             results for \"bo\", it would not be queried again for \"bob\".\n             The default value is <code>false</code>. <i>Optional attribute.</i>. -->\n        <attr name=\"queryAfterZeroResults\" format=\"boolean\" />\n        <!-- If provided, this string will be used to describe the searchable item in the\n             searchable items settings within system search settings. <i>Optional\n             attribute.</i> -->\n        <attr name=\"searchSettingsDescription\" format=\"string\" />\n\n        <!-- If provided and <code>true</code>, URLs entered in the search dialog while searching\n             within this activity would be detected and treated as URLs (show a 'go' button in the\n             keyboard and invoke the browser directly when user launches the URL instead of passing\n             the URL to the activity). If set to <code>false</code> any URLs entered are treated as\n             normal query text.\n             The default value is <code>false</code>. <i>Optional attribute.</i>. -->\n        <attr name=\"autoUrlDetect\" format=\"boolean\" />\n\n    </declare-styleable>\n\n    <!-- In order to process special action keys during search, you must define them using\n            one or more \"ActionKey\" elements in your Searchable metadata.  For a more in-depth\n            discussion of action code handling, please refer to {@link android.app.SearchManager}.\n    -->\n    <declare-styleable name=\"SearchableActionKey\">\n        <!-- This attribute denotes the action key you wish to respond to.  Note that not\n            all action keys are actually supported using this mechanism, as many of them are\n            used for typing, navigation, or system functions.  This will be added to the\n            {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to your\n            searchable activity.  To examine the key code, use\n            {@link android.content.Intent#getIntExtra getIntExtra(SearchManager.ACTION_KEY)}.\n            <p>Note, in addition to the keycode, you must also provide one or more of the action\n            specifier attributes.  <i>Required attribute.</i> -->\n        <attr name=\"keycode\" />\n\n        <!-- If you wish to handle an action key during normal search query entry, you\n            must define an action string here.  This will be added to the\n            {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to your\n            searchable activity.  To examine the string, use\n            {@link android.content.Intent#getStringExtra getStringExtra(SearchManager.ACTION_MSG)}.\n            <i>Optional attribute.</i> -->\n        <attr name=\"queryActionMsg\"  format=\"string\" />\n\n        <!-- If you wish to handle an action key while a suggestion is being displayed <i>and\n            selected</i>, there are two ways to handle this.  If <i>all</i> of your suggestions\n            can handle the action key, you can simply define the action message using this\n            attribute.  This will be added to the\n            {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to your\n            searchable activity.  To examine the string, use\n            {@link android.content.Intent#getStringExtra getStringExtra(SearchManager.ACTION_MSG)}.\n            <i>Optional attribute.</i> -->\n        <attr name=\"suggestActionMsg\"  format=\"string\" />\n\n        <!-- If you wish to handle an action key while a suggestion is being displayed <i>and\n            selected</i>, but you do not wish to enable this action key for every suggestion,\n            then you can use this attribute to control it on a suggestion-by-suggestion basis.\n            First, you must define a column (and name it here) where your suggestions will include\n            the action string.  Then, in your content provider, you must provide this column, and\n            when desired, provide data in this column.\n            The search manager will look at your suggestion cursor, using the string\n            provided here in order to select a column, and will use that to select a string from\n            the cursor.  That string will be added to the\n            {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to\n            your searchable activity.  To examine the string, use\n            {@link android.content.Intent#getStringExtra\n            getStringExtra(SearchManager.ACTION_MSG)}.  <i>If the data does not exist for the\n            selection suggestion, the action key will be ignored.</i><i>Optional attribute.</i> -->\n        <attr name=\"suggestActionMsgColumn\" format=\"string\" />\n\n    </declare-styleable>\n\n    <!-- ***************************************************************** -->\n    <!-- Support for MapView. -->\n    <!-- ***************************************************************** -->\n    <eat-comment />\n\n    <!-- The set of attributes for a MapView. -->\n    <declare-styleable name=\"MapView\">\n        <!-- Value is a string that specifies the Maps API Key to use. -->\n        <attr name=\"apiKey\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- **************************************************************** -->\n    <!-- Menu XML inflation. -->\n    <!-- **************************************************************** -->\n    <eat-comment />\n\n    <!-- Base attributes that are available to all Menu objects. -->\n    <declare-styleable name=\"Menu\">\n    </declare-styleable>\n\n    <!-- Base attributes that are available to all groups. -->\n    <declare-styleable name=\"MenuGroup\">\n\n        <!-- The ID of the group. -->\n        <attr name=\"id\" />\n\n        <!-- The category applied to all items within this group.\n             (This will be or'ed with the orderInCategory attribute.) -->\n        <attr name=\"menuCategory\">\n            <!-- Items are part of a container. -->\n            <enum name=\"container\" value=\"0x00010000\" />\n            <!-- Items are provided by the system. -->\n            <enum name=\"system\" value=\"0x00020000\" />\n            <!-- Items are user-supplied secondary (infrequently used). -->\n            <enum name=\"secondary\" value=\"0x00030000\" />\n            <!-- Items are alternative actions. -->\n            <enum name=\"alternative\" value=\"0x00040000\" />\n        </attr>\n\n        <!-- The order within the category applied to all items within this group.\n             (This will be or'ed with the category attribute.) -->\n        <attr name=\"orderInCategory\" format=\"integer\" />\n\n        <!-- Whether the items are capable of displaying a check mark. -->\n        <attr name=\"checkableBehavior\">\n            <!-- The items are not checkable. -->\n            <enum name=\"none\" value=\"0\" />\n            <!-- The items are all checkable. -->\n            <enum name=\"all\" value=\"1\" />\n            <!-- The items are checkable and there will only be a single checked item in\n                 this group. -->\n            <enum name=\"single\" value=\"2\" />\n        </attr>\n\n        <!-- Whether the items are shown/visible. -->\n        <attr name=\"visible\" />\n\n        <!-- Whether the items are enabled. -->\n        <attr name=\"enabled\" />\n\n    </declare-styleable>\n\n    <!-- Base attributes that are available to all Item objects. -->\n    <declare-styleable name=\"MenuItem\">\n\n        <!-- The ID of the item. -->\n        <attr name=\"id\" />\n\n        <!-- The category applied to the item.\n             (This will be or'ed with the orderInCategory attribute.) -->\n        <attr name=\"menuCategory\" />\n\n        <!-- The order within the category applied to the item.\n             (This will be or'ed with the category attribute.) -->\n        <attr name=\"orderInCategory\" />\n\n        <!-- The title associated with the item. -->\n        <attr name=\"title\" format=\"string\" />\n\n        <!-- The condensed title associated with the item.  This is used in situations where the\n             normal title may be too long to be displayed. -->\n        <attr name=\"titleCondensed\" format=\"string\" />\n\n        <!-- The icon associated with this item.  This icon will not always be shown, so\n             the title should be sufficient in describing this item. -->\n        <attr name=\"icon\" />\n\n        <!-- Tint to apply to the icon. -->\n        <attr name=\"iconTint\" format=\"color\" />\n\n        <!-- Blending mode used to apply the icon tint. -->\n        <attr name=\"iconTintMode\">\n            <!-- The tint is drawn on top of the icon.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the icon. The icon’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the icon, but with the icon’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the icon with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and icon color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n\n        <!-- The alphabetic shortcut key.  This is the shortcut when using a keyboard\n             with alphabetic keys. -->\n        <attr name=\"alphabeticShortcut\" format=\"string\" />\n\n        <!-- The alphabetic modifier key. This is the modifier when using a keyboard\n             with alphabetic keys. The values should be kept in sync with KeyEvent -->\n        <attr name=\"alphabeticModifiers\">\n            <flag name=\"META\" value=\"0x10000\" />\n            <flag name=\"CTRL\" value=\"0x1000\" />\n            <flag name=\"ALT\" value=\"0x02\" />\n            <flag name=\"SHIFT\" value=\"0x1\" />\n            <flag name=\"SYM\" value=\"0x4\" />\n            <flag name=\"FUNCTION\" value=\"0x8\" />\n        </attr>\n\n        <!-- The numeric shortcut key.  This is the shortcut when using a numeric (for example,\n             12-key) keyboard. -->\n        <attr name=\"numericShortcut\" format=\"string\" />\n\n        <!-- The numeric modifier key. This is the modifier when using a numeric (for example,\n             12-key) keyboard. The values should be kept in sync with KeyEvent -->\n        <attr name=\"numericModifiers\">\n            <flag name=\"META\" value=\"0x10000\" />\n            <flag name=\"CTRL\" value=\"0x1000\" />\n            <flag name=\"ALT\" value=\"0x02\" />\n            <flag name=\"SHIFT\" value=\"0x1\" />\n            <flag name=\"SYM\" value=\"0x4\" />\n            <flag name=\"FUNCTION\" value=\"0x8\" />\n        </attr>\n\n        <!-- Whether the item is capable of displaying a check mark. -->\n        <attr name=\"checkable\" format=\"boolean\" />\n\n        <!-- Whether the item is checked.  Note that you must first have enabled checking with\n             the checkable attribute or else the check mark will not appear. -->\n        <attr name=\"checked\" />\n\n        <!-- Whether the item is shown/visible. -->\n        <attr name=\"visible\" />\n\n        <!-- Whether the item is enabled. -->\n        <attr name=\"enabled\" />\n\n        <!-- Name of a method on the Context used to inflate the menu that will be\n             called when the item is clicked.\n             {@deprecated Menu actually traverses the Context hierarchy looking for the \n             relevant method, which is fragile (an intermediate ContextWrapper adding a\n             same-named method would change behavior) and restricts bytecode optimizers\n             such as R8. Instead, use MenuItem.setOnMenuItemClickListener.} -->\n        <attr name=\"onClick\" />\n\n        <!-- How this item should display in the Action Bar, if present. -->\n        <attr name=\"showAsAction\">\n            <!-- Never show this item in an action bar, show it in the overflow menu instead.\n                 Mutually exclusive with \"ifRoom\" and \"always\". -->\n            <flag name=\"never\" value=\"0\" />\n            <!-- Show this item in an action bar if there is room for it as determined\n                 by the system. Favor this option over \"always\" where possible.\n                 Mutually exclusive with \"never\" and \"always\". -->\n            <flag name=\"ifRoom\" value=\"1\" />\n            <!-- Always show this item in an actionbar, even if it would override\n                 the system's limits of how much stuff to put there. This may make\n                 your action bar look bad on some screens. In most cases you should\n                 use \"ifRoom\" instead. Mutually exclusive with \"ifRoom\" and \"never\". -->\n            <flag name=\"always\" value=\"2\" />\n            <!-- When this item is shown as an action in the action bar, show a text\n                 label with it even if it has an icon representation. -->\n            <flag name=\"withText\" value=\"4\" />\n            <!-- This item's action view collapses to a normal menu\n                 item. When expanded, the action view takes over a\n                 larger segment of its container. -->\n            <flag name=\"collapseActionView\" value=\"8\" />\n        </attr>\n\n        <!-- An optional layout to be used as an action view.\n             See {@link android.view.MenuItem#setActionView(android.view.View)}\n             for more info. -->\n        <attr name=\"actionLayout\" format=\"reference\" />\n\n        <!-- The name of an optional View class to instantiate and use as an\n             action view. See {@link android.view.MenuItem#setActionView(android.view.View)}\n             for more info. -->\n        <attr name=\"actionViewClass\" format=\"string\" />\n\n        <!-- The name of an optional ActionProvider class to instantiate an action view\n             and perform operations such as default action for that menu item.\n             See {@link android.view.MenuItem#setActionProvider(android.view.ActionProvider)}\n             for more info. -->\n        <attr name=\"actionProviderClass\" format=\"string\" />\n\n        <!-- The content description associated with the item. -->\n        <attr name=\"contentDescription\" format=\"string\" />\n\n        <!-- The tooltip text associated with the item. -->\n        <attr name=\"tooltipText\" format=\"string\" />\n\n    </declare-styleable>\n\n    <!-- Attrbitutes for a ActvityChooserView. -->\n    <declare-styleable name=\"ActivityChooserView\">\n        <!-- The maximal number of items initially shown in the activity list. -->\n        <attr name=\"initialActivityCount\" format=\"string\" />\n        <!-- The drawable to show in the button for expanding the activities overflow popup.\n             <strong>Note:</strong> Clients would like to set this drawable\n             as a clue about the action the chosen activity will perform. For\n             example, if share activity is to be chosen the drawable should\n             give a clue that sharing is to be performed.\n         -->\n        <attr name=\"expandActivityOverflowButtonDrawable\" format=\"reference\" />\n    </declare-styleable>\n\n    <!-- **************************************************************** -->\n    <!-- Preferences framework. -->\n    <!-- **************************************************************** -->\n    <eat-comment />\n\n    <!-- Base attributes available to PreferenceGroup. -->\n    <declare-styleable name=\"PreferenceGroup\">\n        <!-- Whether to order the Preference under this group as they appear in the XML file.\n             If this is false, the ordering will follow the Preference order attribute and\n             default to alphabetic for those without the order attribute. -->\n        <attr name=\"orderingFromXml\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Attribute for a header describing the item shown in the top-level list\n         from which the selects the set of preference to dig in to. -->\n    <declare-styleable name=\"PreferenceHeader\">\n        <!-- Identifier value for the header. -->\n        <attr name=\"id\" />\n        <!-- The title of the item that is shown to the user. -->\n        <attr name=\"title\" />\n        <!-- The summary for the item. -->\n        <attr name=\"summary\" format=\"string\" />\n        <!-- The title for the bread crumb of this item. -->\n        <attr name=\"breadCrumbTitle\" format=\"string\" />\n        <!-- The short title for the bread crumb of this item. -->\n        <attr name=\"breadCrumbShortTitle\" format=\"string\" />\n        <!-- An icon for the item. -->\n        <attr name=\"icon\" />\n        <!-- The fragment that is displayed when the user selects this item. -->\n        <attr name=\"fragment\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- WARNING:  If adding attributes to Preference, make sure it does not conflict\n                   with a View's attributes.  Some subclasses (for example, EditTextPreference)\n                   proxy all attributes to its EditText widget. -->\n    <eat-comment />\n\n    <!-- Base attributes available to Preference. -->\n    <declare-styleable name=\"Preference\">\n        <!-- The optional icon for the preference. -->\n        <attr name=\"icon\" />\n        <!-- The key to store the Preference value. -->\n        <attr name=\"key\" format=\"string\" />\n        <!-- The title for the Preference. In API 25 and earlier, this value is read as a\n         plain string with styling information stripped. -->\n        <attr name=\"title\" />\n        <!-- The summary for the Preference. In API 25 and earlier, this value is read as a\n         plain string with styling information stripped. -->\n        <attr name=\"summary\" />\n        <!-- The order for the Preference (lower values are to be ordered first). If this is not\n             specified, the default ordering will be alphabetic. -->\n        <attr name=\"order\" format=\"integer\" />\n        <!-- When used inside of a modern PreferenceActivity, this declares\n             a new PreferenceFragment to be shown when the user selects this item. -->\n        <attr name=\"fragment\" />\n        <!-- The layout for the Preference in a PreferenceActivity screen. This should\n             rarely need to be changed, look at widgetLayout instead. -->\n        <attr name=\"layout\" />\n        <!-- The layout for the controllable widget portion of a Preference. This is inflated\n             into the layout for a Preference and should be used more frequently than\n             the layout attribute. For example, a checkbox preference would specify\n             a custom layout (consisting of just the CheckBox) here. -->\n        <attr name=\"widgetLayout\" format=\"reference\" />\n        <!-- Whether the Preference is enabled. -->\n        <attr name=\"enabled\" />\n        <!-- Whether the Preference is selectable. -->\n        <attr name=\"selectable\" format=\"boolean\" />\n        <!-- The key of another Preference that this Preference will depend on.  If the other\n             Preference is not set or is off, this Preference will be disabled. -->\n        <attr name=\"dependency\" format=\"string\" />\n        <!-- Whether the Preference stores its value to the storage. -->\n        <attr name=\"persistent\" />\n        <!-- The default value for the preference, which will be set either if persistence\n             is off or persistence is on and the preference is not found in the persistent\n             storage.  -->\n        <attr name=\"defaultValue\" format=\"string|boolean|integer|reference|float\" />\n        <!-- Whether the view of this Preference should be disabled when\n             this Preference is disabled. -->\n        <attr name=\"shouldDisableView\" format=\"boolean\" />\n        <!-- Whether the preference has enabled to have its view recycled when used in the list\n             view. This is true by default. -->\n        <attr name=\"recycleEnabled\" format=\"boolean\" />\n        <!-- Whether to use single line for the preference title text. By default, preference title\n             will be constrained to one line, so the default value of this attribute is true. -->\n        <attr name=\"singleLineTitle\" format=\"boolean\" />\n        <!-- Whether the space for the preference icon view will be reserved. By default, preference\n             icon view visibility will be set to GONE when there is no icon provided, so the default\n             value of this attribute is false. -->\n        <attr name=\"iconSpaceReserved\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Base attributes available to CheckBoxPreference. -->\n    <declare-styleable name=\"CheckBoxPreference\">\n        <!-- The summary for the Preference in a PreferenceActivity screen when the\n             CheckBoxPreference is checked. If separate on/off summaries are not\n             needed, the summary attribute can be used instead. -->\n        <attr name=\"summaryOn\" format=\"string\" />\n        <!-- The summary for the Preference in a PreferenceActivity screen when the\n             CheckBoxPreference is unchecked. If separate on/off summaries are not\n             needed, the summary attribute can be used instead. -->\n        <attr name=\"summaryOff\" format=\"string\" />\n        <!-- The state (true for on, or false for off) that causes dependents to be disabled. By default,\n             dependents will be disabled when this is unchecked, so the value of this preference is false. -->\n        <attr name=\"disableDependentsState\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Base attributes available to DialogPreference. -->\n    <declare-styleable name=\"DialogPreference\">\n        <!-- The title in the dialog. -->\n        <attr name=\"dialogTitle\" format=\"string\" />\n        <!-- The message in the dialog. If a dialogLayout is provided and contains\n             a TextView with ID android:id/message, this message will be placed in there. -->\n        <attr name=\"dialogMessage\" format=\"string\" />\n        <!-- The icon for the dialog. -->\n        <attr name=\"dialogIcon\" format=\"reference\" />\n        <!-- The positive button text for the dialog. Set to @null to hide the positive button. -->\n        <attr name=\"positiveButtonText\" format=\"string\" />\n        <!-- The negative button text for the dialog. Set to @null to hide the negative button. -->\n        <attr name=\"negativeButtonText\" format=\"string\" />\n        <!-- A layout to be used as the content View for the dialog. By default, this shouldn't\n             be needed. If a custom DialogPreference is required, this should be set. For example,\n             the EditTextPreference uses a layout with an EditText as this attribute. -->\n        <attr name=\"dialogLayout\" format=\"reference\" />\n    </declare-styleable>\n\n    <!-- Base attributes available to ListPreference. -->\n    <declare-styleable name=\"ListPreference\">\n        <!-- The human-readable array to present as a list. Each entry must have a corresponding\n             index in entryValues. -->\n        <attr name=\"entries\" />\n        <!-- The array to find the value to save for a preference when an entry from\n             entries is selected. If a user clicks on the second item in entries, the\n             second item in this array will be saved to the preference. -->\n        <attr name=\"entryValues\" format=\"reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"MultiSelectListPreference\">\n        <!-- The human-readable array to present as a list. Each entry must have a corresponding\n             index in entryValues. -->\n        <attr name=\"entries\" />\n        <!-- The array to find the value to save for a preference when an entry from\n             entries is selected. If a user clicks the second item in entries, the\n             second item in this array will be saved to the preference. -->\n        <attr name=\"entryValues\" />\n    </declare-styleable>\n\n    <!-- Base attributes available to RingtonePreference. -->\n    <declare-styleable name=\"RingtonePreference\">\n        <!-- Which ringtone type(s) to show in the picker. -->\n        <attr name=\"ringtoneType\">\n            <!-- Ringtones. -->\n            <flag name=\"ringtone\" value=\"1\" />\n            <!-- Notification sounds. -->\n            <flag name=\"notification\" value=\"2\" />\n            <!-- Alarm sounds. -->\n            <flag name=\"alarm\" value=\"4\" />\n            <!-- All available ringtone sounds. -->\n            <flag name=\"all\" value=\"7\" />\n        </attr>\n        <!-- Whether to show an item for a default sound. -->\n        <attr name=\"showDefault\" format=\"boolean\" />\n        <!-- Whether to show an item for 'Silent'. -->\n        <attr name=\"showSilent\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Base attributes available to VolumePreference. -->\n    <declare-styleable name=\"VolumePreference\">\n        <!-- Different audio stream types. -->\n        <attr name=\"streamType\">\n            <enum name=\"voice\" value=\"0\" />\n            <enum name=\"system\" value=\"1\" />\n            <enum name=\"ring\" value=\"2\" />\n            <enum name=\"music\" value=\"3\" />\n            <enum name=\"alarm\" value=\"4\" />\n        </attr>\n    </declare-styleable>\n\n    <declare-styleable name=\"InputMethodService\">\n        <!-- Background to use for entire input method when it is being\n             shown in fullscreen mode with the extract view, to ensure\n             that it completely covers the application.  This allows,\n             for example, the candidate view to be hidden\n             while in fullscreen mode without having the application show through\n             behind it.-->\n        <attr name=\"imeFullscreenBackground\" format=\"reference|color\" />\n        <!-- Animation to use when showing the fullscreen extract UI after\n             it had previously been hidden. -->\n        <attr name=\"imeExtractEnterAnimation\" format=\"reference\" />\n        <!-- Animation to use when hiding the fullscreen extract UI after\n             it had previously been shown. -->\n        <attr name=\"imeExtractExitAnimation\" format=\"reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"VoiceInteractionSession\">\n    </declare-styleable>\n\n    <!-- {@deprecated Copy this definition into your own application project.} -->\n    <declare-styleable name=\"KeyboardView\">\n        <!-- Default KeyboardView style.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyboardViewStyle\" format=\"reference\" />\n\n        <!-- Image for the key. This image needs to be a StateListDrawable, with the following\n             possible states: normal, pressed, checkable, checkable+pressed, checkable+checked,\n             checkable+checked+pressed.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyBackground\" format=\"reference\" />\n\n        <!-- Size of the text for character keys.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyTextSize\" format=\"dimension\" />\n\n        <!-- Size of the text for custom keys with some text and no icon.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"labelTextSize\" format=\"dimension\" />\n\n        <!-- Color to use for the label in a key.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyTextColor\" format=\"color\" />\n\n        <!-- Layout resource for key press feedback.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyPreviewLayout\" format=\"reference\" />\n\n        <!-- Vertical offset of the key press feedback from the key.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyPreviewOffset\" format=\"dimension\" />\n\n        <!-- Height of the key press feedback popup.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyPreviewHeight\" format=\"dimension\" />\n\n        <!-- Amount to offset the touch Y coordinate by, for bias correction.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"verticalCorrection\" format=\"dimension\" />\n\n        <!-- Layout resource for popup keyboards.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"popupLayout\" format=\"reference\" />\n\n        <!-- {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"shadowColor\" />\n        <!-- {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"shadowRadius\" />\n    </declare-styleable>\n\n    <!-- {@deprecated Copy this definition into your own application project.} -->\n    <declare-styleable name=\"KeyboardViewPreviewState\">\n        <!-- State for {@link android.inputmethodservice.KeyboardView KeyboardView}\n                key preview background.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"state_long_pressable\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- {@deprecated Copy this definition into your own application project.} -->\n    <declare-styleable name=\"Keyboard\">\n        <!-- Default width of a key, in pixels or percentage of display width.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyWidth\" format=\"dimension|fraction\" />\n        <!-- Default height of a key, in pixels or percentage of display width.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyHeight\" format=\"dimension|fraction\" />\n        <!-- Default horizontal gap between keys.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"horizontalGap\" format=\"dimension|fraction\" />\n        <!-- Default vertical gap between rows of keys.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"verticalGap\" format=\"dimension|fraction\" />\n    </declare-styleable>\n\n    <!-- {@deprecated Copy this definition into your own application project.} -->\n    <declare-styleable name=\"Keyboard_Row\">\n        <!-- Row edge flags.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"rowEdgeFlags\">\n            <!-- Row is anchored to the top of the keyboard.\n             {@deprecated Copy this definition into your own application project.} -->\n            <flag name=\"top\" value=\"4\" />\n            <!-- Row is anchored to the bottom of the keyboard.\n             {@deprecated Copy this definition into your own application project.} -->\n            <flag name=\"bottom\" value=\"8\" />\n        </attr>\n        <!-- Mode of the keyboard. If the mode doesn't match the\n             requested keyboard mode, the row will be skipped.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyboardMode\" format=\"reference\" />\n    </declare-styleable>\n\n    <!-- {@deprecated Copy this definition into your own application project.} -->\n    <declare-styleable name=\"Keyboard_Key\">\n        <!-- The unicode value or comma-separated values that this key outputs.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"codes\" format=\"integer|string\" />\n        <!-- The XML keyboard layout of any popup keyboard.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"popupKeyboard\" format=\"reference\" />\n        <!-- The characters to display in the popup keyboard.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"popupCharacters\" format=\"string\" />\n        <!-- Key edge flags.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyEdgeFlags\">\n            <!-- Key is anchored to the left of the keyboard.\n                 {@deprecated Copy this definition into your own application project.} -->\n            <flag name=\"left\" value=\"1\" />\n            <!-- Key is anchored to the right of the keyboard.\n                 {@deprecated Copy this definition into your own application project.} -->\n            <flag name=\"right\" value=\"2\" />\n        </attr>\n        <!-- Whether this is a modifier key such as Alt or Shift.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"isModifier\" format=\"boolean\" />\n        <!-- Whether this is a toggle key.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"isSticky\" format=\"boolean\" />\n        <!-- Whether long-pressing on this key will make it repeat.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"isRepeatable\" format=\"boolean\" />\n        <!-- The icon to show in the popup preview.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"iconPreview\" format=\"reference\" />\n        <!-- The string of characters to output when this key is pressed.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyOutputText\" format=\"string\" />\n        <!-- The label to display on the key.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyLabel\" format=\"string\" />\n        <!-- The icon to display on the key instead of the label.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyIcon\" format=\"reference\" />\n        <!-- Mode of the keyboard. If the mode doesn't match the\n             requested keyboard mode, the key will be skipped.\n             {@deprecated Copy this definition into your own application project.} -->\n        <attr name=\"keyboardMode\" />\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- AppWidget package class attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <!-- Use <code>appwidget-provider</code> as the root tag of the XML resource that\n         describes an AppWidget provider.  See {@link android.appwidget android.appwidget}\n         package for more info.\n     -->\n    <declare-styleable name=\"AppWidgetProviderInfo\">\n        <!-- Minimum width of the AppWidget. -->\n        <attr name=\"minWidth\"/>\n        <!-- Minimum height of the AppWidget. -->\n        <attr name=\"minHeight\"/>\n        <!-- Minimum width that the AppWidget can be resized to. -->\n        <attr name=\"minResizeWidth\" format=\"dimension\"/>\n        <!-- Minimum height that the AppWidget can be resized to. -->\n        <attr name=\"minResizeHeight\" format=\"dimension\"/>\n        <!-- Maximum width that the AppWidget can be resized to. -->\n        <attr name=\"maxResizeWidth\" format=\"dimension\"/>\n        <!-- Maximum height that the AppWidget can be resized to. -->\n        <attr name=\"maxResizeHeight\" format=\"dimension\"/>\n        <!-- Default width of the AppWidget in units of launcher grid cells. -->\n        <attr name=\"targetCellWidth\" format=\"integer\"/>\n        <!-- Default height of the AppWidget in units of launcher grid cells. -->\n        <attr name=\"targetCellHeight\" format=\"integer\"/>\n        <!-- Update period in milliseconds, or 0 if the AppWidget will update itself. -->\n        <attr name=\"updatePeriodMillis\" format=\"integer\" />\n        <!-- A resource id of a layout. -->\n        <attr name=\"initialLayout\" format=\"reference\" />\n        <!-- A resource id of a layout. -->\n        <attr name=\"initialKeyguardLayout\" format=\"reference\" />\n        <!-- A class name in the AppWidget's package to be launched to configure.\n             If not supplied, then no activity will be launched. -->\n        <attr name=\"configure\" format=\"string\" />\n        <!-- A preview, in a drawable resource id, of what the AppWidget will look like after it's\n             configured.\n             If not supplied, the AppWidget's icon will be used. -->\n        <attr name=\"previewImage\" format=\"reference\" />\n        <!-- The layout resource id of a preview of what the AppWidget will look like after it's\n             configured.\n             Unlike previewImage, previewLayout can better showcase AppWidget in different locales,\n             system themes, display sizes & density etc.\n             If supplied, this will take precedence over the previewImage on supported widget hosts.\n             Otherwise, previewImage will be used. -->\n        <attr name=\"previewLayout\" format=\"reference\" />\n        <!-- The view id of the AppWidget subview which should be auto-advanced.\n             by the widget's host. -->\n        <attr name=\"autoAdvanceViewId\" format=\"reference\" />\n        <!-- Optional parameter which indicates if and how this widget can be\n             resized. Supports combined values using | operator. -->\n        <attr name=\"resizeMode\" format=\"integer\">\n            <flag name=\"none\" value=\"0x0\" />\n            <flag name=\"horizontal\" value=\"0x1\" />\n            <flag name=\"vertical\" value=\"0x2\" />\n        </attr>\n        <!-- Optional parameter which indicates where this widget can be shown,\n             ie. home screen, keyguard, search bar or any combination thereof.\n             Supports combined values using | operator. -->\n        <attr name=\"widgetCategory\" format=\"integer\">\n            <flag name=\"home_screen\" value=\"0x1\" />\n            <flag name=\"keyguard\" value=\"0x2\" />\n            <flag name=\"searchbox\" value=\"0x4\" />\n        </attr>\n        <!-- Flags indicating various features supported by the widget. These are hints to the\n         widget host, and do not actually change the behavior of the widget. -->\n        <attr name=\"widgetFeatures\" format=\"integer\">\n            <!-- The widget can be reconfigured anytime after it is bound -->\n            <flag name=\"reconfigurable\" value=\"0x1\" />\n            <!-- The widget is added directly by the app, and does not need to appear in\n                 the global list of available widgets -->\n            <flag name=\"hide_from_picker\" value=\"0x2\" />\n              <!-- The widget provides a default configuration. The host may decide not to launch\n                   the provided configuration activity. -->\n           <flag name=\"configuration_optional\" value=\"0x4\" />\n        </attr>\n        <!-- A resource identifier for a string containing a short description of the widget. -->\n        <attr name=\"description\" />\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- Wallpaper preview attributes    -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <!-- Use <code>wallpaper-preview</code> as the root tag of the XML resource that\n         describes a wallpaper preview. -->\n    <declare-styleable name=\"WallpaperPreviewInfo\">\n        <!-- A resource id of a static drawable. -->\n        <attr name=\"staticWallpaperPreview\" format=\"reference\" />\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- App package class attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <!-- ============================= -->\n    <!-- View package class attributes -->\n    <!-- ============================= -->\n    <eat-comment />\n\n    <!-- Attributes that can be used with <code>&lt;fragment&gt;</code>\n         tags inside of the layout of an Activity.  This instantiates\n         the given {@link android.app.Fragment} and inserts its content\n         view into the current location in the layout. -->\n    <declare-styleable name=\"Fragment\">\n        <!-- Supply the name of the fragment class to instantiate. -->\n        <attr name=\"name\" />\n\n        <!-- Supply an identifier name for the top-level view, to later retrieve it\n             with {@link android.view.View#findViewById View.findViewById()} or\n             {@link android.app.Activity#findViewById Activity.findViewById()}.\n             This must be a\n             resource reference; typically you set this using the\n             <code>@+</code> syntax to create a new ID resources.\n             For example: <code>android:id=\"@+id/my_id\"</code> which\n             allows you to later retrieve the view\n             with <code>findViewById(R.id.my_id)</code>. -->\n        <attr name=\"id\" />\n\n        <!-- Supply a tag for the top-level view containing a String, to be retrieved\n             later with {@link android.view.View#getTag View.getTag()} or\n             searched for with {@link android.view.View#findViewWithTag\n             View.findViewWithTag()}.  It is generally preferable to use\n             IDs (through the android:id attribute) instead of tags because\n             they are faster and allow for compile-time type checking. -->\n        <attr name=\"tag\" />\n\n        <!-- The Transition that will be used to move Views out of the scene when the\n             fragment is removed, hidden, or detached when not popping the back stack.\n             Corresponds to {@link android.app.Fragment#setExitTransition(\n             android.transition.Transition)} -->\n        <attr name=\"fragmentExitTransition\" format=\"reference\"/>\n\n        <!-- The Transition that will be used to move Views into the initial scene.\n             Corresponds to {@link android.app.Fragment#setEnterTransition(\n             android.transition.Transition)} -->\n        <attr name=\"fragmentEnterTransition\" format=\"reference\"/>\n\n        <!-- The Transition that will be used for shared elements transferred into the content\n             Scene.\n             Corresponds to {@link android.app.Fragment#setSharedElementEnterTransition(\n             android.transition.Transition)} -->\n        <attr name=\"fragmentSharedElementEnterTransition\" format=\"reference\"/>\n\n        <!-- The Transition that will be used to move Views out of the scene when the Fragment is\n             preparing to be removed, hidden, or detached because of popping the back stack.\n             Corresponds to {@link android.app.Fragment#setReturnTransition(\n             android.transition.Transition)} -->\n        <attr name=\"fragmentReturnTransition\" format=\"reference\"/>\n\n        <!-- The Transition that will be used for shared elements transferred back during a\n             pop of the back stack. This Transition acts in the leaving Fragment.\n             Corresponds to {@link android.app.Fragment#setSharedElementReturnTransition(\n             android.transition.Transition)} -->\n        <attr name=\"fragmentSharedElementReturnTransition\" format=\"reference\"/>\n\n        <!-- The Transition that will be used to move Views in to the scene when returning due\n             to popping a back stack.\n             Corresponds to {@link android.app.Fragment#setReenterTransition(\n             android.transition.Transition)} -->\n        <attr name=\"fragmentReenterTransition\" format=\"reference\"/>\n\n        <!-- Sets whether the enter and exit transitions should overlap when transitioning\n             forward.\n             Corresponds to {@link android.app.Fragment#setAllowEnterTransitionOverlap(\n             boolean)} -->\n        <attr name=\"fragmentAllowEnterTransitionOverlap\" format=\"reference\"/>\n\n        <!-- Sets whether the enter and exit transitions should overlap when transitioning\n             because of popping the back stack.\n             Corresponds to {@link android.app.Fragment#setAllowReturnTransitionOverlap(\n             boolean)} -->\n        <attr name=\"fragmentAllowReturnTransitionOverlap\" format=\"reference\"/>\n    </declare-styleable>\n\n    <!-- Use <code>device-admin</code> as the root tag of the XML resource that\n         describes a\n         {@link android.app.admin.DeviceAdminReceiver}, which is\n         referenced from its\n         {@link android.app.admin.DeviceAdminReceiver#DEVICE_ADMIN_META_DATA}\n         meta-data entry.  Described here are the attributes that can be\n         included in that tag. -->\n    <declare-styleable name=\"DeviceAdmin\">\n        <!-- Control whether the admin is visible to the user, even when it\n             is not enabled.  This is true by default.  You may want to make\n             it false if your admin does not make sense to be turned on\n             unless some explicit action happens in your app. -->\n        <attr name=\"visible\" />\n    </declare-styleable>\n\n    <!-- Use <code>wallpaper</code> as the root tag of the XML resource that\n         describes an\n         {@link android.service.wallpaper.WallpaperService}, which is\n         referenced from its\n         {@link android.service.wallpaper.WallpaperService#SERVICE_META_DATA}\n         meta-data entry.  Described here are the attributes that can be\n         included in that tag. -->\n    <declare-styleable name=\"Wallpaper\">\n        <attr name=\"settingsActivity\" />\n\n        <!-- Reference to the wallpaper's thumbnail bitmap. -->\n        <attr name=\"thumbnail\" format=\"reference\" />\n\n        <!-- Name of the author and/or source/collection of this component, for example,\n             Art Collection, Picasso. -->\n        <attr name=\"author\" format=\"reference\" />\n\n        <!-- Short description of the component's purpose or behavior. -->\n        <attr name=\"description\" />\n\n        <!-- Uri that specifies a link for further context of this wallpaper, for example,\n             http://www.picasso.org. -->\n        <attr name=\"contextUri\" format=\"reference\" />\n\n        <!-- Title of the uri that specifies a link for further context of this wallpaper,\n             for example, Explore collection. -->\n        <attr name=\"contextDescription\" format=\"reference\" />\n\n        <!-- Whether to show any metadata when previewing the wallpaper. If this value is\n             set to true, any component that shows a preview of this live wallpaper should also show\n             accompanying information like the title, the description, the author and the context\n             description of this wallpaper so the user gets to know further information about this\n             wallpaper. -->\n        <attr name=\"showMetadataInPreview\" format=\"boolean\" />\n\n        <!-- Wallpapers optimized and capable of drawing in ambient mode will return true.\n             This feature requires the android.permission.AMBIENT_WALLPAPER permission.\n             @hide @SystemApi -->\n        <attr name=\"supportsAmbientMode\" format=\"boolean\" />\n\n        <!-- Uri that specifies a settings Slice for this wallpaper. -->\n        <attr name=\"settingsSliceUri\" format=\"string\"/>\n\n        <!-- Indicates that this wallpaper service can support multiple engines to render on each\n             surface independently. An example use case is a multi-display set-up where the\n             wallpaper service can render surfaces to each of the connected displays. Corresponds to\n             {@link android.app.WallpaperInfo#supportsMultipleDisplays()} -->\n        <attr name=\"supportsMultipleDisplays\" format=\"boolean\" />\n\n    </declare-styleable>\n\n    <!-- Use <code>dream</code> as the root tag of the XML resource that\n         describes an\n         {@link android.service.dreams.DreamService}, which is\n         referenced from its\n         {@link android.service.dreams.DreamService#DREAM_META_DATA}\n         meta-data entry.  Described here are the attributes that can be\n         included in that tag. -->\n    <declare-styleable name=\"Dream\">\n        <!-- Component name of an activity that allows the user to modify\n             the settings for this dream. -->\n        <attr name=\"settingsActivity\" />\n    </declare-styleable>\n\n    <!--  Use <code>trust-agent</code> as the root tag of the XML resource that\n         describes an {@link android.service.trust.TrustAgentService}, which is\n         referenced from its {@link android.service.trust.TrustAgentService#TRUST_AGENT_META_DATA}\n         meta-data entry.  Described here are the attributes that can be included in that tag.\n         @hide -->\n    <declare-styleable name=\"TrustAgent\">\n        <!--  Component name of an activity that allows the user to modify\n             the settings for this trust agent. @hide -->\n        <attr name=\"settingsActivity\" />\n        <!--  Title for a preference that allows that user to launch the\n             activity to modify trust agent settings. @hide -->\n        <attr name=\"title\" />\n        <!--  Summary for the same preference as the title. @hide -->\n        <attr name=\"summary\" />\n        <!--  Whether trust agent can unlock a user profile @hide -->\n        <attr name=\"unlockProfile\" format=\"boolean\"/>\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- Accounts package class attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <!-- Use <code>account-authenticator</code> as the root tag of the XML resource that\n         describes an account authenticator.\n     -->\n    <declare-styleable name=\"AccountAuthenticator\">\n        <!-- The account type this authenticator handles. -->\n        <attr name=\"accountType\" format=\"string\"/>\n        <!-- The user-visible name of the authenticator. -->\n        <attr name=\"label\"/>\n        <!-- The icon of the authenticator. -->\n        <attr name=\"icon\"/>\n        <!-- Smaller icon of the authenticator. -->\n        <attr name=\"smallIcon\" format=\"reference\"/>\n        <!-- A preferences.xml file for authenticator-specific settings. -->\n        <attr name=\"accountPreferences\" format=\"reference\"/>\n        <!-- Account handles its own token storage and permissions.\n             Default to false\n          -->\n        <attr name=\"customTokens\" format=\"boolean\"/>\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- Accounts package class attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <!-- Use <code>account-authenticator</code> as the root tag of the XML resource that\n         describes an account authenticator.\n     -->\n    <declare-styleable name=\"SyncAdapter\">\n        <!-- the authority of a content provider. -->\n        <attr name=\"contentAuthority\" format=\"string\"/>\n        <attr name=\"accountType\"/>\n        <attr name=\"userVisible\" format=\"boolean\"/>\n        <attr name=\"supportsUploading\" format=\"boolean\"/>\n        <!-- Set to true to tell the SyncManager that this SyncAdapter supports\n             multiple simultaneous syncs for the same account type and authority.\n             Otherwise the SyncManager will be sure not to issue a start sync request\n             to this SyncAdapter if the SyncAdapter is already syncing another account.\n             Defaults to false.\n             -->\n        <attr name=\"allowParallelSyncs\" format=\"boolean\"/>\n        <!-- Set to true to tell the SyncManager to automatically call setIsSyncable(..., ..., 1)\n             for the SyncAdapter instead of issuaing an initialization sync to the SyncAdapter.\n             Defaults to false.\n             -->\n        <attr name=\"isAlwaysSyncable\" format=\"boolean\"/>\n        <!-- If provided, specifies the action of the settings\n             activity for this SyncAdapter.\n             -->\n        <attr name=\"settingsActivity\"/>\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- Autofill attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <!-- Use <code>autofill-service</code> as the root tag of the XML resource that describes a\n         {@link android.service.autofill.AutofillService}, which is referenced from its\n         {@link android.service.autofill.AutofillService#SERVICE_META_DATA} meta-data entry.\n    -->\n    <declare-styleable name=\"AutofillService\">\n        <!-- Fully qualified class name of an activity that allows the user to modify\n             the settings for this service. -->\n        <attr name=\"settingsActivity\" />\n        <!-- Fully qualified class name of an activity that allows the user to view any passwords\n             saved by this service. -->\n        <attr name=\"passwordsActivity\" format=\"string\" />\n\n        <!-- Specifies whether the AutofillService supports inline suggestions-->\n        <attr name=\"supportsInlineSuggestions\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Use <code>compatibility-package</code> as a child tag of <code>autofill-service</code>\n         in the XML resource that describes an {@link android.service.autofill.AutofillService}\n         to specify a package and an optional max version code for which to request compatibility\n         mode. If no max version code is specified compatibility mode is requested for all package\n         versions. The max version code is useful to avoid requesting compatibility mode for newer\n         package versions that are known to natively support autofill.\n    -->\n    <declare-styleable name=\"AutofillService_CompatibilityPackage\">\n        <!-- The package name for which compatibility mode is requested. -->\n        <attr name=\"name\" />\n        <!-- The max version code of the package for which compatibility mode is\n             requested. This corresponds to the long value returned by {@link\n             android.content.pm.PackageInfo#getLongVersionCode()} for the target package.\n        -->\n        <attr name=\"maxLongVersionCode\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- System Speech Recognition attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <!-- Use <code>on-device-recognition-service</code> as the root tag of the XML resource that\n         describes a {@link android.service.speech.RecognitionService}, which is referenced\n         from its {@link android.service.speech.RecognitionService#SERVICE_META_DATA} meta-data\n         entry.\n         @hide @SystemApi\n    -->\n    <declare-styleable name=\"OnDeviceRecognitionService\">\n        <!-- Fully qualified class name of an activity that allows the user to modify\n             the settings for this service. -->\n        <attr name=\"settingsActivity\" />\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- Content Capture attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <!-- Use <code>content-capture-service</code> as the root tag of the XML resource that describes\n         a {@link android.service.contentcapture.ContentCaptureService}, which is referenced from\n         its {@link android.service.contentcapture.ContentCaptureService#SERVICE_META_DATA}\n         meta-data entry.\n         @hide @SystemApi\n    -->\n    <declare-styleable name=\"ContentCaptureService\">\n        <!-- Fully qualified class name of an activity that allows the user to modify\n             the settings for this service. -->\n        <attr name=\"settingsActivity\" />\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- Translation attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <!-- Use <code>translation-service</code> as the root tag of the XML resource that describes\n         a {@link android.service.translation.TranslationService}, which is referenced from\n         its {@link android.service.translation.TranslationService#SERVICE_META_DATA} meta-data\n         entry.\n         @hide @SystemApi\n    -->\n    <declare-styleable name=\"TranslationService\">\n        <!-- Fully qualified class name of an activity that allows the user to modify\n             the settings for this service. -->\n        <attr name=\"settingsActivity\" />\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- Contacts meta-data attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <eat-comment />\n    <declare-styleable name=\"Icon\">\n        <attr name=\"icon\" />\n        <attr name=\"mimeType\" />\n    </declare-styleable>\n\n    <eat-comment />\n    <declare-styleable name=\"IconDefault\">\n        <attr name=\"icon\" />\n    </declare-styleable>\n\n    <!-- Maps a specific contact data MIME-type to styling information. -->\n    <declare-styleable name=\"ContactsDataKind\">\n        <!-- Mime-type handled by this mapping. -->\n        <attr name=\"mimeType\" />\n        <!-- Icon used to represent data of this kind. -->\n        <attr name=\"icon\" />\n        <!-- Column in data table that summarizes this data. -->\n        <attr name=\"summaryColumn\" format=\"string\" />\n        <!-- Column in data table that contains details for this data. -->\n        <attr name=\"detailColumn\" format=\"string\" />\n        <!-- Flag indicating that detail should be built from SocialProvider. -->\n        <attr name=\"detailSocialSummary\" format=\"boolean\" />\n        <!-- Resource representing the term \"All Contacts\" (for example, \"All Friends\" or\n        \"All connections\"). Optional (Default is \"All Contacts\"). -->\n        <attr name=\"allContactsName\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- TabSelector class attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <declare-styleable name=\"SlidingTab\">\n        <!-- Use \"horizontal\" for a row, \"vertical\" for a column.  The default is horizontal. -->\n        <attr name=\"orientation\" />\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- GlowPadView class attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n    <declare-styleable name=\"GlowPadView\">\n        <!-- Reference to an array resource that be used as description for the targets around the circle.\n             {@deprecated Removed.} -->\n        <attr name=\"targetDescriptions\" format=\"reference\" />\n\n        <!-- Reference to an array resource that be used to announce the directions with targets around the circle.\n             {@deprecated Removed.} -->\n        <attr name=\"directionDescriptions\" format=\"reference\" />\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- Location package class attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <!-- Use <code>injected-location-setting</code> as the root tag of the XML resource that\n         describes an injected \"Location services\" setting. Note that the status value (subtitle)\n         for the setting is specified dynamically by a subclass of SettingInjectorService.\n     -->\n    <declare-styleable name=\"SettingInjectorService\">\n        <!-- The title for the preference. -->\n        <attr name=\"title\"/>\n        <!-- The icon for the preference, should refer to all apps covered by the setting. Typically\n             a generic icon for the developer. -->\n        <attr name=\"icon\"/>\n        <!-- The activity to launch when the setting is clicked on. -->\n        <attr name=\"settingsActivity\"/>\n        <!-- The user restriction for this preference. -->\n        <attr name=\"userRestriction\" format=\"string\"/>\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- LockPatternView class attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <declare-styleable name=\"LockPatternView\">\n        <!-- Aspect to use when drawing LockPatternView. Choices are \"square\"(default), \"lock_width\"\n             or \"lock_height\" -->\n        <attr name=\"aspect\" format=\"string\" />\n        <!-- Color to use when drawing LockPatternView paths. -->\n        <attr name=\"pathColor\" format=\"color|reference\" />\n        <!-- The regular pattern color -->\n        <attr name=\"regularColor\" format=\"color|reference\" />\n        <!-- The error color -->\n        <attr name=\"errorColor\" format=\"color|reference\" />\n        <!-- The success color -->\n        <attr name=\"successColor\" format=\"color|reference\"/>\n        <!-- The dot color -->\n        <attr name=\"dotColor\" format=\"color|reference\"/>\n\n    </declare-styleable>\n\n    <!-- =============================== -->\n    <!-- QuickAccessWallet attributes -->\n    <!-- =============================== -->\n    <eat-comment />\n\n    <!-- Use <code>quickaccesswallet-service</code> as the root tag of the XML resource\n         that describes a {@link android.service.quickaccesswallet.QuickAccessWalletService},\n         which is referenced from its\n         {@link android.service.quickaccesswallet.QuickAccessWalletService#SERVICE_META_DATA}\n         meta-data entry.\n    -->\n    <declare-styleable name=\"QuickAccessWalletService\">\n        <!-- Fully qualified class name of an activity that allows the user to modify\n             the settings for this service. -->\n        <attr name=\"settingsActivity\" format=\"string\"/>\n        <!-- Fully qualified class name of an activity that allows the user to view\n             their entire wallet -->\n        <attr name=\"targetActivity\" format=\"string\"/>\n        <!-- Text shown on the empty state button if no cards are provided -->\n        <attr name=\"shortcutLongLabel\"/>\n        <!-- Text shown on the button that takes users to the wallet application -->\n        <attr name=\"shortcutShortLabel\"/>\n\n    </declare-styleable>\n\n    <!-- Use <code>recognition-service</code> as the root tag of the XML resource that\n         describes a {@link android.speech.RecognitionService}, which is referenced from\n         its {@link android.speech.RecognitionService#SERVICE_META_DATA} meta-data entry.\n         Described here are the attributes that can be included in that tag. -->\n    <declare-styleable name=\"RecognitionService\">\n        <attr name=\"settingsActivity\" />\n        <!-- Flag indicating whether a recognition service can be selected as default. The default\n             value of this flag is true. -->\n        <attr name=\"selectableAsDefault\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Use <code>voice-interaction-service</code> as the root tag of the XML resource that\n         describes a {@link android.service.voice.VoiceInteractionService}, which is referenced from\n         its {@link android.service.voice.VoiceInteractionService#SERVICE_META_DATA} meta-data entry.\n         Described here are the attributes that can be included in that tag. -->\n    <declare-styleable name=\"VoiceInteractionService\">\n        <!-- The service that hosts active voice interaction sessions.  This is required. -->\n        <attr name=\"sessionService\" format=\"string\" />\n        <!-- The service that provides voice recognition.  This is required.  When the user\n             selects this voice interaction service, they will also be implicitly selecting\n             the component here for their recognition service. -->\n        <attr name=\"recognitionService\" format=\"string\" />\n        <attr name=\"settingsActivity\" />\n        <!-- Flag indicating whether this voice interaction service is capable of handling the\n             assist action. -->\n        <attr name=\"supportsAssist\" format=\"boolean\" />\n        <!-- Flag indicating whether this voice interaction service is capable of being launched\n             from the keyguard. -->\n        <attr name=\"supportsLaunchVoiceAssistFromKeyguard\" format=\"boolean\" />\n        <!-- Flag indicating whether this voice interaction service can handle local voice\n             interaction requests from an Activity. This flag is new in\n             {@link android.os.Build.VERSION_CODES#N} and not used in previous versions. -->\n        <attr name=\"supportsLocalInteraction\" format=\"boolean\" />\n        <!-- The service that provides {@link android.service.voice.HotwordDetectionService}.\n             @hide @SystemApi -->\n        <attr name=\"hotwordDetectionService\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- Use <code>voice-enrollment-application</code>\n         as the root tag of the XML resource that escribes the supported keyphrases (hotwords)\n         by the enrollment application.\n         Described here are the attributes that can be included in that tag.\n         @hide\n          -->\n    <declare-styleable name=\"VoiceEnrollmentApplication\">\n        <!-- A globally unique ID for the keyphrase. @hide  -->\n        <attr name=\"searchKeyphraseId\" format=\"integer\" />\n        <!-- The actual keyphrase/hint text, or empty if not keyphrase dependent. @hide  -->\n        <attr name=\"searchKeyphrase\" format=\"string\" />\n        <!-- A comma separated list of BCP-47 language tag for locales that are supported\n             for this keyphrase, or empty if not locale dependent. @hide  -->\n        <attr name=\"searchKeyphraseSupportedLocales\" format=\"string\" />\n        <!-- Flags for supported recognition modes. @hide  -->\n        <attr name=\"searchKeyphraseRecognitionFlags\">\n            <flag name=\"none\" value=\"0\" />\n            <flag name=\"voiceTrigger\" value=\"0x1\" />\n            <flag name=\"userIdentification\" value=\"0x2\" />\n        </attr>\n    </declare-styleable>\n\n    <!-- Attributes used to style the Action Bar. -->\n    <declare-styleable name=\"ActionBar\">\n        <!-- The type of navigation to use. -->\n        <attr name=\"navigationMode\">\n            <!-- Normal static title text. -->\n            <enum name=\"normal\" value=\"0\" />\n            <!-- The action bar will use a selection list for navigation. -->\n            <enum name=\"listMode\" value=\"1\" />\n            <!-- The action bar will use a series of horizontal tabs for navigation. -->\n            <enum name=\"tabMode\" value=\"2\" />\n        </attr>\n        <!-- Options affecting how the action bar is displayed. -->\n        <attr name=\"displayOptions\">\n            <flag name=\"none\" value=\"0\" />\n            <flag name=\"useLogo\" value=\"0x1\" />\n            <flag name=\"showHome\" value=\"0x2\" />\n            <flag name=\"homeAsUp\" value=\"0x4\" />\n            <flag name=\"showTitle\" value=\"0x8\" />\n            <flag name=\"showCustom\" value=\"0x10\" />\n            <flag name=\"disableHome\" value=\"0x20\" />\n        </attr>\n        <!-- Specifies title text used for navigationMode=\"normal\". -->\n        <attr name=\"title\" />\n        <!-- Specifies subtitle text used for navigationMode=\"normal\". -->\n        <attr name=\"subtitle\" format=\"string\" />\n        <!-- Specifies a style to use for title text. -->\n        <attr name=\"titleTextStyle\" format=\"reference\" />\n        <!-- Specifies a style to use for subtitle text. -->\n        <attr name=\"subtitleTextStyle\" format=\"reference\" />\n        <!-- Specifies the drawable used for the application icon. -->\n        <attr name=\"icon\" />\n        <!-- Specifies the drawable used for the application logo. -->\n        <attr name=\"logo\" />\n        <!-- Specifies the drawable used for item dividers. -->\n        <attr name=\"divider\" />\n        <!-- Specifies a background drawable for the action bar. -->\n        <attr name=\"background\" />\n        <!-- Specifies a background drawable for a second stacked row of the action bar. -->\n        <attr name=\"backgroundStacked\" format=\"reference|color\" />\n        <!-- Specifies a background drawable for the bottom component of a split action bar. -->\n        <attr name=\"backgroundSplit\" format=\"reference|color\" />\n        <!-- Specifies a layout for custom navigation. Overrides navigationMode. -->\n        <attr name=\"customNavigationLayout\" format=\"reference\" />\n        <!-- Specifies a fixed height. -->\n        <attr name=\"height\" />\n        <!-- Specifies a layout to use for the \"home\" section of the action bar. -->\n        <attr name=\"homeLayout\" format=\"reference\" />\n        <!-- Specifies a style resource to use for an embedded progress bar. -->\n        <attr name=\"progressBarStyle\" />\n        <!-- Specifies a style resource to use for an indeterminate progress spinner. -->\n        <attr name=\"indeterminateProgressStyle\" format=\"reference\" />\n        <!-- Specifies the horizontal padding on either end for an embedded progress bar. -->\n        <attr name=\"progressBarPadding\" format=\"dimension\" />\n        <!-- Up navigation glyph. -->\n        <attr name=\"homeAsUpIndicator\" />\n        <!-- Specifies padding that should be applied to the left and right sides of\n             system-provided items in the bar. -->\n        <attr name=\"itemPadding\" format=\"dimension\" />\n        <!-- Set true to hide the action bar on a vertical nested scroll of content. -->\n        <attr name=\"hideOnContentScroll\" format=\"boolean\" />\n        <!-- Minimum inset for content views within a bar. Navigation buttons and\n             menu views are excepted. Only valid for some themes and configurations. -->\n        <attr name=\"contentInsetStart\" format=\"dimension\" />\n        <!-- Minimum inset for content views within a bar. Navigation buttons and\n             menu views are excepted. Only valid for some themes and configurations. -->\n        <attr name=\"contentInsetEnd\" format=\"dimension\" />\n        <!-- Minimum inset for content views within a bar. Navigation buttons and\n             menu views are excepted. Only valid for some themes and configurations. -->\n        <attr name=\"contentInsetLeft\" format=\"dimension\" />\n        <!-- Minimum inset for content views within a bar. Navigation buttons and\n             menu views are excepted. Only valid for some themes and configurations. -->\n        <attr name=\"contentInsetRight\" format=\"dimension\" />\n        <!-- Minimum inset for content views within a bar when a navigation button\n             is present, such as the Up button. Only valid for some themes and configurations. -->\n        <attr name=\"contentInsetStartWithNavigation\" format=\"dimension\" />\n        <!-- Minimum inset for content views within a bar when actions from a menu\n             are present. Only valid for some themes and configurations. -->\n        <attr name=\"contentInsetEndWithActions\" format=\"dimension\" />\n        <!-- Elevation for the action bar itself. -->\n        <attr name=\"elevation\" />\n        <!-- Reference to a theme that should be used to inflate popups\n             shown by widgets in the action bar. -->\n        <attr name=\"popupTheme\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"ActionMode\">\n        <!-- Specifies a style to use for title text. -->\n        <attr name=\"titleTextStyle\" />\n        <!-- Specifies a style to use for subtitle text. -->\n        <attr name=\"subtitleTextStyle\" />\n        <!-- Specifies a background for the action mode bar. -->\n        <attr name=\"background\" />\n        <!-- Specifies a background for the split action mode bar. -->\n        <attr name=\"backgroundSplit\" />\n        <!-- Specifies a fixed height for the action mode bar. -->\n        <attr name=\"height\" />\n        <!-- Specifies a layout to use for the \"close\" item at the starting edge. -->\n        <attr name=\"closeItemLayout\" format=\"reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"SearchView\">\n        <!-- The layout to use for the search view. -->\n        <attr name=\"layout\" />\n        <!-- The default state of the SearchView. If true, it will be iconified when not in\n             use and expanded when clicked. -->\n        <attr name=\"iconifiedByDefault\" format=\"boolean\" />\n        <!-- An optional maximum width of the SearchView. -->\n        <attr name=\"maxWidth\" />\n        <!-- An optional query hint string to be displayed in the empty query field. -->\n        <attr name=\"queryHint\" format=\"string\" />\n        <!-- Default query hint used when {@code queryHint} is undefined and\n             the search view's {@code SearchableInfo} does not provide a hint.\n             @hide -->\n        <attr name=\"defaultQueryHint\" format=\"string\" />\n        <!-- The IME options to set on the query text field. -->\n        <attr name=\"imeOptions\" />\n        <!-- The input type to set on the query text field. -->\n        <attr name=\"inputType\" />\n        <!-- Close button icon. -->\n        <attr name=\"closeIcon\" format=\"reference\" />\n        <!-- Go button icon. -->\n        <attr name=\"goIcon\" format=\"reference\" />\n        <!-- Search icon. -->\n        <attr name=\"searchIcon\" format=\"reference\" />\n        <!-- Search icon displayed as a text field hint. -->\n        <attr name=\"searchHintIcon\" format=\"reference\" />\n        <!-- Voice button icon. -->\n        <attr name=\"voiceIcon\" format=\"reference\" />\n        <!-- Commit icon shown in the query suggestion row. -->\n        <attr name=\"commitIcon\" format=\"reference\" />\n        <!-- Layout for query suggestion rows. -->\n        <attr name=\"suggestionRowLayout\" format=\"reference\" />\n        <!-- Background for the section containing the search query. -->\n        <attr name=\"queryBackground\" format=\"reference\" />\n        <!-- Background for the section containing the action (for example, voice search). -->\n        <attr name=\"submitBackground\" format=\"reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"Switch\">\n        <!-- Drawable to use as the \"thumb\" that switches back and forth. -->\n        <attr name=\"thumb\" />\n        <!-- Tint to apply to the thumb. -->\n        <attr name=\"thumbTint\" />\n        <!-- Blending mode used to apply the thumb tint. -->\n        <attr name=\"thumbTintMode\" />\n        <!-- Drawable to use as the \"track\" that the switch thumb slides within. -->\n        <attr name=\"track\" format=\"reference\" />\n        <!-- Tint to apply to the track. -->\n        <attr name=\"trackTint\" format=\"color\" />\n        <!-- Blending mode used to apply the track tint. -->\n        <attr name=\"trackTintMode\">\n            <!-- The tint is drawn on top of the drawable.\n                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->\n            <enum name=\"src_over\" value=\"3\" />\n            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s\n                 color channels are thrown out. [Sa * Da, Sc * Da] -->\n            <enum name=\"src_in\" value=\"5\" />\n            <!-- The tint is drawn above the drawable, but with the drawable’s alpha\n                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->\n            <enum name=\"src_atop\" value=\"9\" />\n            <!-- Multiplies the color and alpha channels of the drawable with those of\n                 the tint. [Sa * Da, Sc * Dc] -->\n            <enum name=\"multiply\" value=\"14\" />\n            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->\n            <enum name=\"screen\" value=\"15\" />\n            <!-- Combines the tint and drawable color and alpha channels, clamping the\n                 result to valid color values. Saturate(S + D) -->\n            <enum name=\"add\" value=\"16\" />\n        </attr>\n        <!-- Text to use when the switch is in the checked/\"on\" state. -->\n        <attr name=\"textOn\" />\n        <!-- Text to use when the switch is in the unchecked/\"off\" state. -->\n        <attr name=\"textOff\" />\n        <!-- Amount of padding on either side of text within the switch thumb. -->\n        <attr name=\"thumbTextPadding\" format=\"dimension\" />\n        <!-- TextAppearance style for text displayed on the switch thumb. -->\n        <attr name=\"switchTextAppearance\" format=\"reference\" />\n        <!-- Minimum width for the switch component. -->\n        <attr name=\"switchMinWidth\" format=\"dimension\" />\n        <!-- Minimum space between the switch and caption text. -->\n        <attr name=\"switchPadding\" format=\"dimension\" />\n        <!-- Whether to split the track and leave a gap for the thumb drawable. -->\n        <attr name=\"splitTrack\" />\n        <!-- Whether to draw on/off text. -->\n        <attr name=\"showText\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"Pointer\">\n        <!-- Reference to a pointer icon drawable with STYLE_ARROW. -->\n        <attr name=\"pointerIconArrow\" format=\"reference\" />\n        <!-- Reference to a pointer icon drawable with STYLE_SPOT_HOVER. -->\n        <attr name=\"pointerIconSpotHover\" format=\"reference\" />\n        <!-- Reference to a pointer icon drawable with STYLE_SPOT_TOUCH. -->\n        <attr name=\"pointerIconSpotTouch\" format=\"reference\" />\n        <!-- Reference to a pointer icon drawable with STYLE_SPOT_ANCHOR. -->\n        <attr name=\"pointerIconSpotAnchor\" format=\"reference\" />\n        <!-- Reference to a pointer drawable with STYLE_CONTEXT_MENU. -->\n        <attr name=\"pointerIconContextMenu\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_HAND. -->\n        <attr name=\"pointerIconHand\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_HELP. -->\n        <attr name=\"pointerIconHelp\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_WAIT. -->\n        <attr name=\"pointerIconWait\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_CELL. -->\n        <attr name=\"pointerIconCell\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_CROSSHAIR. -->\n        <attr name=\"pointerIconCrosshair\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_TEXT. -->\n        <attr name=\"pointerIconText\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_VERTICAL_TEXT. -->\n        <attr name=\"pointerIconVerticalText\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_ALIAS. -->\n        <attr name=\"pointerIconAlias\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_COPY. -->\n        <attr name=\"pointerIconCopy\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_NODROP. -->\n        <attr name=\"pointerIconNodrop\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_ALL_SCROLL. -->\n        <attr name=\"pointerIconAllScroll\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_HORIZONTAL_DOUBLE_ARROW. -->\n        <attr name=\"pointerIconHorizontalDoubleArrow\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_VERTICAL_DOUBLE_ARROW. -->\n        <attr name=\"pointerIconVerticalDoubleArrow\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW. -->\n        <attr name=\"pointerIconTopRightDiagonalDoubleArrow\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW. -->\n        <attr name=\"pointerIconTopLeftDiagonalDoubleArrow\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_ZOOM_IN. -->\n        <attr name=\"pointerIconZoomIn\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_ZOOM_OUT. -->\n        <attr name=\"pointerIconZoomOut\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_GRAB. -->\n        <attr name=\"pointerIconGrab\" format=\"reference\"/>\n        <!-- Reference to a pointer drawable with STYLE_GRABBING. -->\n        <attr name=\"pointerIconGrabbing\" format=\"reference\"/>\n    </declare-styleable>\n\n    <declare-styleable name=\"PointerIcon\">\n        <!-- Drawable to use as the icon bitmap. -->\n        <attr name=\"bitmap\" format=\"reference\" />\n        <!-- X coordinate of the icon hot spot. -->\n        <attr name=\"hotSpotX\" format=\"dimension\" />\n        <!-- Y coordinate of the icon hot spot. -->\n        <attr name=\"hotSpotY\" format=\"dimension\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"Storage\">\n        <!-- path to mount point for the storage. -->\n        <attr name=\"mountPoint\" format=\"string\" />\n        <!-- user visible description of the storage. -->\n        <attr name=\"storageDescription\" format=\"string\" />\n        <!-- true if the storage is the primary external storage. -->\n        <attr name=\"primary\" format=\"boolean\" />\n        <!-- true if the storage is removable. -->\n        <attr name=\"removable\" format=\"boolean\" />\n        <!-- true if the storage is emulated via the FUSE sdcard daemon. -->\n        <attr name=\"emulated\" format=\"boolean\" />\n        <!-- number of megabytes of storage MTP should reserve for free storage\n             (used for emulated storage that is shared with system's data partition). -->\n        <attr name=\"mtpReserve\" format=\"integer\" />\n        <!-- true if the storage can be shared via USB mass storage. -->\n        <attr name=\"allowMassStorage\" format=\"boolean\" />\n        <!-- maximum file size for the volume in megabytes, zero or unspecified if it is unbounded. -->\n        <attr name=\"maxFileSize\" format=\"integer\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"SwitchPreference\">\n        <!-- The summary for the Preference in a PreferenceActivity screen when the\n             SwitchPreference is checked. If separate on/off summaries are not\n             needed, the summary attribute can be used instead. -->\n        <attr name=\"summaryOn\" />\n        <!-- The summary for the Preference in a PreferenceActivity screen when the\n             SwitchPreference is unchecked. If separate on/off summaries are not\n             needed, the summary attribute can be used instead. -->\n        <attr name=\"summaryOff\" />\n        <!-- The text used on the switch itself when in the \"on\" state.\n             This should be a very SHORT string, as it appears in a small space. -->\n        <attr name=\"switchTextOn\" format=\"string\" />\n        <!-- The text used on the switch itself when in the \"off\" state.\n             This should be a very SHORT string, as it appears in a small space. -->\n        <attr name=\"switchTextOff\" format=\"string\" />\n        <!-- The state (true for on, or false for off) that causes dependents to be disabled. By default,\n             dependents will be disabled when this is unchecked, so the value of this preference is false. -->\n        <attr name=\"disableDependentsState\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"SeekBarPreference\">\n        <attr name=\"layout\" />\n        <!-- Attribute indicating whether the slider within this preference can be adjusted, that is\n        pressing left/right keys when this preference is focused will move the slider accordingly\n        (for example, inline adjustable preferences). False, if the slider within the preference is\n        read-only and cannot be adjusted. By default, the seekbar is adjustable. -->\n        <attr name=\"adjustable\" format=\"boolean\" />\n        <!-- Flag indicating whether the TextView next to the seekbar that shows the current seekbar value will be\n        displayed. If true, the view is VISIBLE; if false, the view will be GONE. By default, this view is VISIBLE. -->\n        <attr name=\"showSeekBarValue\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Base attributes available to PreferenceFragment. -->\n    <declare-styleable name=\"PreferenceFragment\">\n        <!-- The layout for the PreferenceFragment. This should rarely need to be changed. -->\n        <attr name=\"layout\" />\n        <attr name=\"divider\" />\n    </declare-styleable>\n\n    <!-- Base attributes available to PreferenceScreen. -->\n    <declare-styleable name=\"PreferenceScreen\">\n        <!-- The layout for the PreferenceScreen. This should rarely need to be changed. -->\n        <attr name=\"screenLayout\" format=\"reference\" />\n        <attr name=\"divider\" />\n    </declare-styleable>\n\n    <!-- Base attributes available to PreferenceActivity. -->\n    <declare-styleable name=\"PreferenceActivity\">\n        <!-- The layout for the Preference Activity. This should rarely need to be changed. -->\n        <attr name=\"layout\" />\n        <!-- The layout for the Preference Header. This should rarely need to be changed. -->\n        <attr name=\"headerLayout\" format=\"reference\" />\n        <!-- true if the Icon view will be removed when there is none and thus not showing\n             the fixed margins. -->\n        <attr name=\"headerRemoveIconIfEmpty\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Use <code>tts-engine</code> as the root tag of the XML resource that\n         describes a text to speech engine implemented as a subclass of\n         {@link android.speech.tts.TextToSpeechService}.\n\n         The XML resource must be referenced from its\n         {@link android.speech.tts.TextToSpeech.Engine#SERVICE_META_DATA} meta-data\n         entry. -->\n    <declare-styleable name=\"TextToSpeechEngine\">\n        <attr name=\"settingsActivity\" />\n    </declare-styleable>\n\n    <!-- Use <code>keyboard-layouts</code> as the root tag of the XML resource that\n         describes a collection of keyboard layouts provided by an application.\n         Each keyboard layout is declared by a <code>keyboard-layout</code> tag\n         with these attributes.\n\n         The XML resource that contains the keyboard layouts must be referenced from its\n         {@link android.hardware.input.InputManager#META_DATA_KEYBOARD_LAYOUTS}\n         meta-data entry used with broadcast receivers for\n         {@link android.hardware.input.InputManager#ACTION_QUERY_KEYBOARD_LAYOUTS}. -->\n    <declare-styleable name=\"KeyboardLayout\">\n        <!-- The name of the keyboard layout, must be unique in the receiver. -->\n        <attr name=\"name\" />\n        <!-- The display label of the keyboard layout. -->\n        <attr name=\"label\" />\n        <!-- The key character map file resource. -->\n        <attr name=\"keyboardLayout\" format=\"reference\" />\n        <!-- The locales the given keyboard layout corresponds to. -->\n        <attr name=\"locale\" format=\"string\" />\n        <!-- The vendor ID of the hardware the given layout corresponds to. @hide -->\n        <attr name=\"vendorId\" format=\"integer\" />\n        <!-- The product ID of the hardware the given layout corresponds to. @hide -->\n        <attr name=\"productId\" format=\"integer\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"MediaRouteButton\">\n        <!-- This drawable is a state list where the \"activated\" state\n             indicates active media routing. Non-activated indicates\n             that media is playing to the local device only.\n             @hide -->\n        <attr name=\"externalRouteEnabledDrawable\" format=\"reference\" />\n\n        <!-- The types of media routes the button and its resulting\n             chooser will filter by. -->\n        <attr name=\"mediaRouteTypes\" format=\"integer\">\n            <!-- Allow selection of live audio routes. -->\n            <enum name=\"liveAudio\" value=\"0x1\" />\n            <!-- Allow selection of user (app-specified) routes. -->\n            <enum name=\"user\" value=\"0x800000\" />\n        </attr>\n\n        <attr name=\"minWidth\" />\n        <attr name=\"minHeight\" />\n    </declare-styleable>\n\n    <!-- PagedView specific attributes. These attributes are used to customize\n         a PagedView view in XML files. -->\n    <declare-styleable name=\"PagedView\">\n        <!-- The space between adjacent pages of the PagedView. -->\n        <attr name=\"pageSpacing\" format=\"dimension\" />\n        <!-- The padding for the scroll indicator area. -->\n        <attr name=\"scrollIndicatorPaddingLeft\" format=\"dimension\" />\n        <attr name=\"scrollIndicatorPaddingRight\" format=\"dimension\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"KeyguardGlowStripView\">\n        <attr name=\"dotSize\" format=\"dimension\" />\n        <attr name=\"numDots\" format=\"integer\" />\n        <attr name=\"glowDot\" format=\"reference\" />\n        <attr name=\"leftToRight\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Some child types have special behavior. -->\n    <attr name=\"layout_childType\">\n        <!-- No special behavior. Layout will proceed as normal. -->\n        <enum name=\"none\" value=\"0\" />\n        <!-- Widget container.\n             This will be resized in response to certain events. -->\n        <enum name=\"widget\" value=\"1\" />\n        <!-- Security challenge container.\n             This will be dismissed/shown in response to certain events,\n             possibly obscuring widget elements. -->\n        <enum name=\"challenge\" value=\"2\" />\n        <!-- User switcher.\n             This will consume space from the total layout area. -->\n        <enum name=\"userSwitcher\" value=\"3\" />\n        <!-- Scrim. This will block access to child views that\n             come before it in the child list in bouncer mode. -->\n        <enum name=\"scrim\" value=\"4\" />\n        <!-- The home for widgets. All widgets will be descendents of this. -->\n        <enum name=\"widgets\" value=\"5\" />\n        <!-- This is a handle that is used for expanding the\n             security challenge container when it is collapsed. -->\n        <enum name=\"expandChallengeHandle\" value=\"6\" />\n        <!-- Delete drop target.  This will be the drop target to delete pages. -->\n        <enum name=\"pageDeleteDropTarget\" value=\"7\" />\n    </attr>\n\n    <!-- Attributes that can be used with <code>&lt;FragmentBreadCrumbs&gt;</code>\n    tags. -->\n    <declare-styleable name=\"FragmentBreadCrumbs\">\n        <attr name=\"gravity\" />\n        <attr name=\"itemLayout\" format=\"reference\" />\n        <attr name=\"itemColor\" format=\"color|reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"Toolbar\">\n        <attr name=\"titleTextAppearance\" format=\"reference\" />\n        <attr name=\"subtitleTextAppearance\" format=\"reference\" />\n        <attr name=\"title\" />\n        <attr name=\"subtitle\" />\n        <attr name=\"gravity\" />\n        <!--  Specifies extra space on the left, start, right and end sides\n              of the toolbar's title. Margin values should be positive. -->\n        <attr name=\"titleMargin\" format=\"dimension\" />\n        <!--  Specifies extra space on the start side of the toolbar's title.\n              If both this attribute and titleMargin are specified, then this\n              attribute takes precedence. Margin values should be positive. -->\n        <attr name=\"titleMarginStart\" format=\"dimension\" />\n        <!--  Specifies extra space on the end side of the toolbar's title.\n              If both this attribute and titleMargin are specified, then this\n              attribute takes precedence. Margin values should be positive. -->\n        <attr name=\"titleMarginEnd\" format=\"dimension\" />\n        <!--  Specifies extra space on the top side of the toolbar's title.\n              If both this attribute and titleMargin are specified, then this\n              attribute takes precedence. Margin values should be positive. -->\n        <attr name=\"titleMarginTop\" format=\"dimension\" />\n        <!--  Specifies extra space on the bottom side of the toolbar's title.\n              If both this attribute and titleMargin are specified, then this\n              attribute takes precedence. Margin values should be positive. -->\n        <attr name=\"titleMarginBottom\" format=\"dimension\" />\n        <attr name=\"contentInsetStart\" />\n        <attr name=\"contentInsetEnd\" />\n        <attr name=\"contentInsetLeft\" />\n        <attr name=\"contentInsetRight\" />\n        <attr name=\"contentInsetStartWithNavigation\" />\n        <attr name=\"contentInsetEndWithActions\" />\n        <attr name=\"maxButtonHeight\" format=\"dimension\" />\n        <attr name=\"navigationButtonStyle\" format=\"reference\" />\n        <attr name=\"buttonGravity\">\n            <!-- Push object to the top of its container, not changing its size. -->\n            <flag name=\"top\" value=\"0x30\" />\n            <!-- Push object to the bottom of its container, not changing its size. -->\n            <flag name=\"bottom\" value=\"0x50\" />\n        </attr>\n        <!-- Icon drawable to use for the collapse button. -->\n        <attr name=\"collapseIcon\" format=\"reference\" />\n        <!-- Text to set as the content description for the collapse button. -->\n        <attr name=\"collapseContentDescription\" format=\"string\" />\n        <!-- Reference to a theme that should be used to inflate popups\n             shown by widgets in the toolbar. -->\n        <attr name=\"popupTheme\" format=\"reference\" />\n        <!-- Icon drawable to use for the navigation button located at\n             the start of the toolbar. -->\n        <attr name=\"navigationIcon\" format=\"reference\" />\n        <!-- Text to set as the content description for the navigation button\n             located at the start of the toolbar. -->\n        <attr name=\"navigationContentDescription\" format=\"string\" />\n        <!-- Drawable to set as the logo that appears at the starting side of\n             the Toolbar, just after the navigation button. -->\n        <attr name=\"logo\" />\n        <!-- A content description string to describe the appearance of the\n             associated logo image. -->\n        <attr name=\"logoDescription\" format=\"string\" />\n        <!-- A color to apply to the title string. -->\n        <attr name=\"titleTextColor\" format=\"color\" />\n        <!-- A color to apply to the subtitle string. -->\n        <attr name=\"subtitleTextColor\" format=\"color\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"Toolbar_LayoutParams\">\n        <attr name=\"layout_gravity\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"ActionBar_LayoutParams\">\n        <attr name=\"layout_gravity\" />\n    </declare-styleable>\n\n    <!-- Used as a filter array on the theme to pull out only the EdgeEffect-relevant bits. -->\n    <declare-styleable name=\"EdgeEffect\">\n        <attr name=\"colorEdgeEffect\" />\n    </declare-styleable>\n\n    <!-- Use <code>tv-input</code> as the root tag of the XML resource that describes a\n         {@link android.media.tv.TvInputService}, which is referenced from its\n         {@link android.media.tv.TvInputService#SERVICE_META_DATA} meta-data entry.\n         Described here are the attributes that can be included in that tag. -->\n    <declare-styleable name=\"TvInputService\">\n        <!-- Component name of an activity that allows the user to set up this service. -->\n        <attr name=\"setupActivity\" format=\"string\" />\n        <!-- Component name of an activity that allows the user to modify the settings for this\n             service.\n             {@deprecated This value is deprecated and not used by the framework starting from API\n                         level 26. Use setupActivity instead.} -->\n        <attr name=\"settingsActivity\" />\n        <!-- Attribute whether the TV input service can record programs. This value can be changed\n             at runtime by calling\n             {@link android.media.tv.TvInputManager#updateTvInputInfo(android.media.tv.TvInputInfo)}. -->\n        <attr name=\"canRecord\" format=\"boolean\" />\n        <!-- The number of tuners that the TV input service is associated with. This value can be\n             changed at runtime by calling\n             {@link android.media.tv.TvInputManager#updateTvInputInfo(android.media.tv.TvInputInfo)}. -->\n        <attr name=\"tunerCount\" format=\"integer\" />\n        <!-- Attribute whether the TV input service can pause recording programs.\n             This value can be changed at runtime by calling\n             {@link android.media.tv.TvInputManager#updateTvInputInfo(android.media.tv.TvInputInfo)}\n             . -->\n        <attr name=\"canPauseRecording\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Attributes that can be used with <code>rating-system-definition</code> tags inside of the\n         XML resource that describes TV content rating of a {@link android.media.tv.TvInputService},\n         which is referenced from its\n         {@link android.media.tv.TvInputManager#META_DATA_CONTENT_RATING_SYSTEMS}. -->\n    <declare-styleable name=\"RatingSystemDefinition\">\n        <!-- The unique name of the content rating system. -->\n        <attr name=\"name\" />\n        <!-- The title of the content rating system which is shown to the user. -->\n        <attr name=\"title\" />\n        <!-- The short description of the content rating system. -->\n        <attr name=\"description\" />\n        <!-- The country code associated with the content rating system, which consists of two\n             uppercase letters that conform to the ISO 3166 standard. -->\n        <attr name=\"country\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- Attributes that can be used with <code>rating-definition</code> tags inside of the XML\n         resource that describes TV content rating of a {@link android.media.tv.TvInputService},\n         which is referenced from its\n         {@link android.media.tv.TvInputManager#META_DATA_CONTENT_RATING_SYSTEMS}. -->\n    <declare-styleable name=\"RatingDefinition\">\n        <!-- The unique name of the content rating. -->\n        <attr name=\"name\" />\n        <!-- The title of the content rating which is shown to the user. -->\n        <attr name=\"title\" />\n        <!-- The short description of the content rating. -->\n        <attr name=\"description\" />\n        <!-- The age associated with the content rating. The content of this rating is suitable for\n             people of this age or above. -->\n        <attr name=\"contentAgeHint\" format=\"integer\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"ResolverDrawerLayout\">\n        <attr name=\"maxWidth\" />\n        <attr name=\"maxCollapsedHeight\" format=\"dimension\" />\n        <attr name=\"maxCollapsedHeightSmall\" format=\"dimension\" />\n        <!-- Whether the Drawer should be positioned at the top rather than at the bottom. -->\n        <attr name=\"showAtTop\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"MessagingLinearLayout\">\n        <attr name=\"spacing\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"DateTimeView\">\n        <attr name=\"showRelative\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"ResolverDrawerLayout_LayoutParams\">\n        <attr name=\"layout_alwaysShow\" format=\"boolean\" />\n        <attr name=\"layout_ignoreOffset\" format=\"boolean\" />\n        <attr name=\"layout_gravity\" />\n        <attr name=\"layout_hasNestedScrollIndicator\" format=\"boolean\" />\n        <attr name=\"layout_maxHeight\" format=\"dimension\"/>\n    </declare-styleable>\n\n    <!-- @hide -->\n    <declare-styleable name=\"Lighting\">\n        <attr name=\"lightY\" />\n        <attr name=\"lightZ\" />\n        <attr name=\"lightRadius\" />\n        <attr name=\"ambientShadowAlpha\" />\n        <attr name=\"spotShadowAlpha\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"RestrictionEntry\">\n        <attr name=\"key\" />\n        <attr name=\"restrictionType\">\n            <enum name=\"hidden\" value=\"0\" />\n            <enum name=\"bool\" value=\"1\" />\n            <enum name=\"choice\" value=\"2\" />\n            <enum name=\"multi-select\" value=\"4\" />\n            <enum name=\"integer\" value=\"5\" />\n            <enum name=\"string\" value=\"6\" />\n            <enum name=\"bundle\" value=\"7\" />\n            <enum name=\"bundle_array\" value=\"8\" />\n        </attr>\n        <attr name=\"title\" />\n        <attr name=\"description\" />\n        <attr name=\"defaultValue\" />\n        <attr name=\"entries\" />\n        <attr name=\"entryValues\" />\n    </declare-styleable>\n\n    <!-- Used to describe the gradient for fill or stroke in a path of VectorDrawable. -->\n    <declare-styleable name=\"GradientColor\">\n        <!-- Start color of the gradient. -->\n        <attr name=\"startColor\" />\n        <!-- Optional center color. -->\n        <attr name=\"centerColor\" />\n        <!-- End color of the gradient. -->\n        <attr name=\"endColor\" />\n        <!-- Type of gradient. The default type is linear. -->\n        <attr name=\"type\" />\n\n        <!-- Only applied to RadialGradient-->\n        <!-- Radius of the gradient, used only with radial gradient. -->\n        <attr name=\"gradientRadius\" />\n\n        <!-- Only applied to SweepGradient / RadialGradient-->\n        <!-- X coordinate of the center of the gradient within the path. -->\n        <attr name=\"centerX\" />\n        <!-- Y coordinate of the center of the gradient within the path. -->\n        <attr name=\"centerY\" />\n\n        <!-- LinearGradient specific -->\n        <!-- X coordinate of the start point origin of the gradient.\n             Defined in same coordinates as the path itself -->\n        <attr name=\"startX\" format=\"float\" />\n        <!-- Y coordinate of the start point of the gradient within the shape.\n             Defined in same coordinates as the path itself -->\n        <attr name=\"startY\" format=\"float\" />\n        <!-- X coordinate of the end point origin of the gradient.\n             Defined in same coordinates as the path itself -->\n        <attr name=\"endX\" format=\"float\" />\n        <!-- Y coordinate of the end point of the gradient within the shape.\n             Defined in same coordinates as the path itself -->\n        <attr name=\"endY\" format=\"float\" />\n\n        <!-- Defines the tile mode of the gradient. SweepGradient don't support tiling. -->\n        <attr name=\"tileMode\"/>\n    </declare-styleable>\n\n    <!-- Describes an item of a GradientColor. Minimally need 2 items to define the gradient\n         Colors defined in <item> override the simple color attributes such as\n         \"startColor / centerColor / endColor\" are ignored. -->\n    <declare-styleable name=\"GradientColorItem\">\n        <!-- The offset (or ratio) of this current color item inside the gradient.\n             The value is only meaningful when it is between 0 and 1. -->\n        <attr name=\"offset\" format=\"float\" />\n        <!-- The current color for the offset inside the gradient. -->\n        <attr name=\"color\" />\n    </declare-styleable>\n\n    <!-- @hide Attributes which will be read by the Activity to intialize the\n               base activity TaskDescription. -->\n    <declare-styleable name=\"ActivityTaskDescription\">\n        <!-- @hide From Theme.colorPrimary, used for the TaskDescription primary\n                   color. -->\n        <attr name=\"colorPrimary\" />\n        <!-- @hide From Theme.colorBackground, used for the TaskDescription background\n                   color. -->\n        <attr name=\"colorBackground\" />\n        <!-- @hide From Theme.colorBackgroundFloating, used for the TaskDescription background\n                   color floating. -->\n        <attr name=\"colorBackgroundFloating\" />\n        <!-- @hide From Theme.statusBarColor, used for the TaskDescription status bar color. -->\n        <attr name=\"statusBarColor\"/>\n        <!-- @hide From Theme.navigationBarColor, used for the TaskDescription navigation bar\n                   color. -->\n        <attr name=\"navigationBarColor\"/>\n        <!-- @hide From Window.enforceStatusBarContrast -->\n        <attr name=\"enforceStatusBarContrast\"/>\n        <!-- @hide From Window.enforceNavigationBarContrast -->\n        <attr name=\"enforceNavigationBarContrast\"/>\n    </declare-styleable>\n\n    <declare-styleable name=\"Shortcut\">\n        <attr name=\"shortcutId\" format=\"string\" />\n        <attr name=\"enabled\" />\n        <attr name=\"icon\" />\n        <attr name=\"shortcutShortLabel\" format=\"reference\" />\n        <attr name=\"shortcutLongLabel\" format=\"reference\" />\n        <attr name=\"shortcutDisabledMessage\" format=\"reference\" />\n        <attr name=\"splashScreenTheme\" format=\"reference\"/>\n    </declare-styleable>\n\n    <declare-styleable name=\"ShortcutCategories\">\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- Attributes that are read when parsing a &lt;font&gt; tag, which is a child of\n         &lt;font-family&gt;. This represents an actual font file and its attributes. -->\n    <declare-styleable name=\"FontFamilyFont\">\n        <!-- The style of the given font file. This will be used when the font is being loaded into\n         the font stack and will override any style information in the font's header tables. If\n         unspecified, the value in the font's header tables will be used. -->\n        <attr name=\"fontStyle\">\n            <enum name=\"normal\" value=\"0\" />\n            <enum name=\"italic\" value=\"1\" />\n        </attr>\n        <!-- The reference to the font file to be used. This should be a file in the res/font folder\n         and should therefore have an R reference value. E.g. @font/myfont -->\n        <attr name=\"font\" format=\"reference\" />\n        <!-- The weight of the given font file. This will be used when the font is being loaded into\n         the font stack and will override any weight information in the font's header tables. Must\n         be a positive number, a multiple of 100, and between 100 and 900, inclusive. The most\n         common values are 400 for regular weight and 700 for bold weight. If unspecified, the value\n         in the font's header tables will be used. -->\n        <attr name=\"fontWeight\" format=\"integer\" />\n        <!-- The index of the font in the ttc (TrueType Collection) font file. If the font file\n         referenced is not in the ttc format, this attribute needs not be specified.\n         {@link android.graphics.Typeface.Builder#setTtcIndex(int)}.\n         The default value is 0. More details about the TrueType Collection font format can be found\n         here: https://en.wikipedia.org/wiki/TrueType#TrueType_Collection. -->\n        <attr name=\"ttcIndex\" format=\"integer\" />\n        <!-- The variation settings to be applied to the font. The string should be in the following\n         format: \"'tag1' value1, 'tag2' value2, ...\". If the default variation settings should be\n         used, or the font used does not support variation settings, this attribute needs not be\n         specified. -->\n        <attr name=\"fontVariationSettings\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- Attributes that are read when parsing a &lt;fontfamily&gt; tag.\n         {@deprecated Use Jetpack Core library instead.}\n     -->\n    <declare-styleable name=\"FontFamily\">\n        <!-- The authority of the Font Provider to be used for the request.\n             {@deprecated Use app:fontProviderAuthority with Jetpack Core library instead for\n              consistent behavior across all devices.}\n         -->\n        <attr name=\"fontProviderAuthority\" format=\"string\" />\n        <!-- The package for the Font Provider to be used for the request. This is used to verify\n        the identity of the provider.\n             {@deprecated Use app:fontProviderPackage with Jetpack Core library instead.}\n         -->\n        <attr name=\"fontProviderPackage\" format=\"string\" />\n        <!-- The query to be sent over to the provider. Refer to your font provider's documentation\n        on the format of this string.\n             {@deprecated Use app:fontProviderQuery with Jetpack Core library instead.}\n         -->\n        <attr name=\"fontProviderQuery\" format=\"string\" />\n        <!-- The sets of hashes for the certificates the provider should be signed with. This is\n        used to verify the identity of the provider, and is only required if the provider is not\n        part of the system image. This value may point to one list or a list of lists, where each\n        individual list represents one collection of signature hashes. Refer to your font provider's\n        documentation for these values.\n             {@deprecated Use app:fontProviderCerts with Jetpack Core library instead.}\n         -->\n        <attr name=\"fontProviderCerts\" format=\"reference\" />\n        <!-- Provides the system font family name to check before downloading the font. For example\n        if the fontProviderQuery asked for \"Sans Serif\", it is possible to define\n        fontProviderSystemFontFamily as \"sans-serif\" to tell the system to use \"sans-serif\" font\n        family if it exists on the system.\n         -->\n        <attr name=\"fontProviderSystemFontFamily\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- Attributes that are read when parsing a  tag. -->\n    <declare-styleable name=\"VideoView2\">\n        <attr name=\"enableControlView\" format=\"boolean\" />\n        <attr name=\"enableSubtitle\" format=\"boolean\" />\n        <attr name=\"viewType\" format=\"enum\">\n            <enum name=\"surfaceView\" value=\"0\" />\n            <enum name=\"textureView\" value=\"1\" />\n        </attr>\n    </declare-styleable>\n\n    <!-- @hide -->\n    <declare-styleable name=\"RecyclerView\">\n        <attr name=\"layoutManager\" format=\"string\" />\n        <attr name=\"orientation\" />\n        <attr name=\"descendantFocusability\" />\n        <attr name=\"spanCount\" format=\"integer\"/>\n        <attr name=\"reverseLayout\" format=\"boolean\" />\n        <attr name=\"stackFromEnd\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- @hide -->\n    <declare-styleable name=\"NotificationTheme\">\n        <attr name=\"notificationHeaderStyle\" format=\"reference\" />\n        <attr name=\"notificationHeaderTextAppearance\" format=\"reference\" />\n        <attr name=\"notificationHeaderIconSize\" format=\"dimension\" />\n        <attr name=\"notificationHeaderAppNameVisibility\" format=\"enum\">\n            <!-- Visible on screen; the default value. -->\n            <enum name=\"visible\" value=\"0\" />\n            <!-- Not displayed, but taken into account during layout (space is left for it). -->\n            <enum name=\"invisible\" value=\"1\" />\n            <!-- Completely hidden, as if the view had not been added. -->\n            <enum name=\"gone\" value=\"2\" />\n        </attr>\n    </declare-styleable>\n\n    <attr name=\"lockPatternStyle\" format=\"reference\" />\n\n    <declare-styleable name=\"Magnifier\">\n        <attr name=\"magnifierWidth\" format=\"dimension\" />\n        <attr name=\"magnifierHeight\" format=\"dimension\" />\n        <attr name=\"magnifierZoom\" format=\"float\" />\n        <attr name=\"magnifierElevation\" format=\"dimension\" />\n        <attr name=\"magnifierVerticalOffset\" format=\"dimension\" />\n        <attr name=\"magnifierHorizontalOffset\" format=\"dimension\" />\n        <attr name=\"magnifierColorOverlay\" format=\"color\" />\n    </declare-styleable>\n\n    <attr name=\"autoSizePresetSizes\" />\n\n    <attr name=\"iconfactoryIconSize\" format=\"dimension\"/>\n    <attr name=\"iconfactoryBadgeSize\" format=\"dimension\"/>\n    <!-- Perceptual luminance of a color, in accessibility friendly color space. From 0 to 100. -->\n    <attr name=\"lStar\" format=\"float\"/>\n</resources>\n"
  },
  {
    "path": "recaf-core/src/test/resources/android/attrs_manifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- From Platform 31 -->\n<!--\n/* Copyright 2006, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\");\n** you may not use this file except in compliance with the License.\n** You may obtain a copy of the License at\n**\n**     http://www.apache.org/licenses/LICENSE-2.0\n**\n** Unless required by applicable law or agreed to in writing, software\n** distributed under the License is distributed on an \"AS IS\" BASIS,\n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n** See the License for the specific language governing permissions and\n** limitations under the License.\n*/\n-->\n<resources>\n    <!-- **************************************************************** -->\n    <!-- These are the attributes used in AndroidManifest.xml. -->\n    <!-- **************************************************************** -->\n    <eat-comment />\n\n    <!-- The overall theme to use for an activity.  Use with either the\n         application tag (to supply a default theme for all activities) or\n         the activity tag (to supply a specific theme for that activity).\n\n         <p>This automatically sets\n         your activity's Context to use this theme, and may also be used\n         for \"starting\" animations prior to the activity being launched (to\n         better match what the activity actually looks like).  It is a reference\n         to a style resource defining the theme.  If not set, the default\n         system theme will be used. -->\n    <attr name=\"theme\" format=\"reference\" />\n\n    <!-- A user-legible name for the given item.  Use with the\n         application tag (to supply a default label for all application\n         components), or with the activity, receiver, service, or instrumentation\n         tag (to supply a specific label for that component).  It may also be\n         used with the intent-filter tag to supply a label to show to the\n         user when an activity is being selected based on a particular Intent.\n\n         <p>The given label will be used wherever the user sees information\n         about its associated component; for example, as the name of a\n         main activity that is displayed in the launcher.  You should\n         generally set this to a reference to a string resource, so that\n         it can be localized, however it is also allowed to supply a plain\n         string for quick and dirty programming. -->\n    <attr name=\"label\" format=\"reference|string\" />\n\n    <!-- A Drawable resource providing a graphical representation of its\n         associated item.  Use with the\n         application tag (to supply a default icon for all application\n         components), or with the activity, receiver, service, or instrumentation\n         tag (to supply a specific icon for that component).  It may also be\n         used with the intent-filter tag to supply an icon to show to the\n         user when an activity is being selected based on a particular Intent.\n\n         <p>The given icon will be used to display to the user a graphical\n         representation of its associated component; for example, as the icon\n         for main activity that is displayed in the launcher.  This must be\n         a reference to a Drawable resource containing the image definition. -->\n    <attr name=\"icon\" format=\"reference\" />\n\n    <!-- A Drawable resource providing a graphical representation of its\n         associated item.  Use with the\n         application tag (to supply a default round icon for all application\n         components), or with the activity, receiver, service, or instrumentation\n         tag (to supply a specific round icon for that component).  It may also be\n         used with the intent-filter tag to supply a round icon to show to the\n         user when an activity is being selected based on a particular Intent.\n\n         <p>The given round icon will be used to display to the user a graphical\n         representation of its associated component; for example, as the round icon\n         for main activity that is displayed in the launcher.  This must be\n         a reference to a Drawable resource containing the image definition. -->\n    <attr name=\"roundIcon\" format=\"reference\" />\n\n    <!-- A Drawable resource providing an extended graphical banner for its\n         associated item. Use with the application tag (to supply a default\n         banner for all application activities), or with the activity, tag to\n         supply a banner for a specific activity.\n\n         <p>The given banner will be used to display to the user a graphical\n         representation of an activity in the Leanback application launcher.\n         Since banners are displayed only in the Leanback launcher, they should\n         only be used with activities (and applications) that support Leanback\n         mode. These are activities that handle Intents of category\n         {@link android.content.Intent#CATEGORY_LEANBACK_LAUNCHER\n         Intent.CATEGORY_LEANBACK_LAUNCHER}.\n         <p>This must be a reference to a Drawable resource containing the image definition. -->\n    <attr name=\"banner\" format=\"reference\" />\n\n    <!-- A Drawable resource providing an extended graphical logo for its\n         associated item. Use with the application tag (to supply a default\n         logo for all application components), or with the activity, receiver,\n         service, or instrumentation tag (to supply a specific logo for that\n         component). It may also be used with the intent-filter tag to supply\n         a logo to show to the user when an activity is being selected based\n         on a particular Intent.\n\n         <p>The given logo will be used to display to the user a graphical\n         representation of its associated component; for example as the\n         header in the Action Bar. The primary differences between an icon\n         and a logo are that logos are often wider and more detailed, and are\n         used without an accompanying text caption. This must be a reference\n         to a Drawable resource containing the image definition. -->\n    <attr name=\"logo\" format=\"reference\" />\n\n    <!-- Name of the activity to be launched to manage application's space on\n         device. The specified activity gets automatically launched when the\n         application's space needs to be managed and is usually invoked\n         through user actions. Applications can thus provide their own custom\n         behavior for managing space for various scenarios like out of memory\n         conditions. This is an optional attribute and\n         applications can choose not to specify a default activity to\n         manage space. -->\n    <attr name=\"manageSpaceActivity\" format=\"string\" />\n\n    <!-- Option to let applications specify that user data can/cannot be\n         cleared. This flag is turned on by default.\n         <p>Starting from API level 29 this flag only controls if the user can\n         clear app data from Settings. To control clearing the data after a\n         failed restore use allowClearUserDataOnFailedRestore flag.\n         <p><em>This attribute is usable only by applications\n         included in the system image. Third-party apps cannot use it.</em> -->\n    <attr name=\"allowClearUserData\" format=\"boolean\" />\n\n    <!-- Option to indicate this application is only for testing purposes.\n         For example, it may expose functionality or data outside of itself\n         that would cause a security hole, but is useful for testing.  This\n         kind of application can not be installed without the\n         INSTALL_ALLOW_TEST flag, which means only through adb install.  -->\n    <attr name=\"testOnly\" format=\"boolean\" />\n\n    <!-- A unique name for the given item.  This must use a Java-style naming\n         convention to ensure the name is unique, for example\n         \"com.mycompany.MyName\". -->\n    <attr name=\"name\" format=\"string\" />\n\n    <!-- Specify a permission that a client is required to have in order to\n    \t use the associated object.  If the client does not hold the named\n    \t permission, its request will fail.  See the\n         <a href=\"{@docRoot}guide/topics/security/security.html\">Security and Permissions</a>\n         document for more information on permissions. -->\n    <attr name=\"permission\" format=\"string\" />\n\n    <!-- A specific {@link android.R.attr#permission} name for read-only\n         access to a {@link android.content.ContentProvider}.  See the\n         <a href=\"{@docRoot}guide/topics/security/security.html\">Security and Permissions</a>\n         document for more information on permissions. -->\n    <attr name=\"readPermission\" format=\"string\" />\n\n    <!-- A specific {@link android.R.attr#permission} name for write\n         access to a {@link android.content.ContentProvider}.  See the\n         <a href=\"{@docRoot}guide/topics/security/security.html\">Security and Permissions</a>\n         document for more information on permissions. -->\n    <attr name=\"writePermission\" format=\"string\" />\n\n    <!-- If true, the {@link android.content.Context#grantUriPermission\n         Context.grantUriPermission} or corresponding Intent flags can\n         be used to allow others to access specific URIs in the content\n         provider, even if they do not have an explicit read or write\n         permission.  If you are supporting this feature, you must be\n         sure to call {@link android.content.Context#revokeUriPermission\n         Context.revokeUriPermission} when URIs are deleted from your\n         provider.-->\n    <attr name=\"grantUriPermissions\" format=\"boolean\" />\n\n    <!-- If true, the system will always create URI permission grants\n         in the cases where {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}\n         or {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} would apply.\n         This is useful for a content provider that dynamically enforces permissions\n         on calls in to the provider, instead of through the manifest: the system\n         needs to know that it should always apply permission grants, even if it\n         looks like the target of the grant would already have access to the URI. -->\n    <attr name=\"forceUriPermissions\" format=\"boolean\" />\n\n    <!-- Characterizes the potential risk implied in a permission and\n         indicates the procedure the system should follow when determining\n         whether to grant the permission to an application requesting it. {@link\n         android.Manifest.permission Standard permissions} have a predefined and\n         permanent protectionLevel. If you are creating a custom permission in an\n         application, you can define a protectionLevel attribute with one of the\n         values listed below. If no protectionLevel is defined for a custom\n         permission, the system assigns the default (\"normal\").\n         <p>Each protection level consists of a base permission type and zero or\n         more flags. Use the following functions to extract those.\n         <pre>\n         int basePermissionType = permissionInfo.getProtection();\n         int permissionFlags = permissionInfo.getProtectionFlags();\n         </pre>\n         -->\n    <attr name=\"protectionLevel\">\n        <!-- <strong>Base permission type</strong>: a lower-risk permission that gives\n             an application access to isolated application-level features, with minimal\n             risk to other applications, the system, or the user. The system\n             automatically grants this type of permission to a requesting application at\n             installation, without asking for the user's explicit approval (though the\n             user always has the option to review these permissions before installing). -->\n        <flag name=\"normal\" value=\"0\" />\n        <!-- <strong>Base permission type</strong>: a higher-risk permission that\n             would give a requesting application access to private user data or\n             control over the device that can negatively impact the user.  Because\n             this type of permission introduces potential risk, the system may\n             not automatically grant it to the requesting application.  For example,\n             any dangerous permissions requested by an application may be displayed\n             to the user and require confirmation before proceeding, or some other\n             approach may be taken to avoid the user automatically allowing\n             the use of such facilities.  -->\n        <flag name=\"dangerous\" value=\"1\" />\n        <!-- <strong>Base permission type</strong>: a permission that the system is\n             to grant only if the requesting application is signed with the same\n             certificate as the application that declared the permission. If the\n             certificates match, the system automatically grants the permission\n             without notifying the user or asking for the user's explicit approval. -->\n        <flag name=\"signature\" value=\"2\" />\n        <!-- Old synonym for \"signature|privileged\". Deprecated in API level 23.\n             Base permission type: a permission that the system is to grant only\n             to packages in the Android system image <em>or</em> that are signed\n             with the same certificates. Please avoid using this option, as the\n             signature protection level should be sufficient for most needs and\n             works regardless of exactly where applications are installed.  This\n             permission is used for certain special situations where multiple\n             vendors have applications built in to a system image which need\n             to share specific features explicitly because they are being built\n             together. -->\n        <flag name=\"signatureOrSystem\" value=\"3\" />\n        <!-- <strong>Base permission type</strong>: a permission that is managed internally by the\n             system and only granted according to the protection flags. -->\n        <flag name=\"internal\" value=\"4\" />\n        <!-- Additional flag from base permission type: this permission can also\n             be granted to any applications installed as privileged apps on the system image.\n             Please avoid using this option, as the\n             signature protection level should be sufficient for most needs and\n             works regardless of exactly where applications are installed.  This\n             permission flag is used for certain special situations where multiple\n             vendors have applications built in to a system image which need\n             to share specific features explicitly because they are being built\n             together. -->\n        <flag name=\"privileged\" value=\"0x10\" />\n        <!-- Old synonym for \"privileged\". Deprecated in API level 23. -->\n        <flag name=\"system\" value=\"0x10\" />\n        <!-- Additional flag from base permission type: this permission can also\n             (optionally) be granted to development applications. Although undocumented, the\n              permission state used to be shared by all users (including future users), but it is\n              managed per-user since API level 31. -->\n        <flag name=\"development\" value=\"0x20\" />\n        <!-- Additional flag from base permission type: this permission is closely\n             associated with an app op for controlling access. -->\n        <flag name=\"appop\" value=\"0x40\" />\n        <!-- Additional flag from base permission type: this permission can be automatically\n             granted to apps that target API levels below\n             {@link android.os.Build.VERSION_CODES#M} (before runtime permissions\n             were introduced). -->\n        <flag name=\"pre23\" value=\"0x80\" />\n        <!-- Additional flag from base permission type: this permission can be automatically\n            granted to system apps that install packages. -->\n        <flag name=\"installer\" value=\"0x100\" />\n        <!-- Additional flag from base permission type: this permission can be automatically\n            granted to system apps that verify packages. -->\n        <flag name=\"verifier\" value=\"0x200\" />\n        <!-- Additional flag from base permission type: this permission can be automatically\n            granted any application pre-installed on the system image (not just privileged\n            apps). -->\n        <flag name=\"preinstalled\" value=\"0x400\" />\n        <!-- Additional flag from base permission type: this permission can be automatically\n            granted to the setup wizard app -->\n        <flag name=\"setup\" value=\"0x800\" />\n        <!-- Additional flag from base permission type: this permission can be granted to instant\n             apps -->\n        <flag name=\"instant\" value=\"0x1000\" />\n        <!-- Additional flag from base permission type: this permission can only be granted to apps\n             that target runtime permissions ({@link android.os.Build.VERSION_CODES#M} and above)\n             -->\n        <flag name=\"runtime\" value=\"0x2000\" />\n        <!-- Additional flag from base permission type: this permission can be granted only\n             if its protection level is signature, the requesting app resides on the OEM partition,\n             and the OEM has allowlisted the app to receive this permission by the OEM.\n         -->\n        <flag name=\"oem\" value=\"0x4000\" />\n        <!-- Additional flag from base permission type: this permission can be granted to\n             privileged apps in vendor partition. -->\n        <flag name=\"vendorPrivileged\" value=\"0x8000\" />\n        <!-- Additional flag from base permission type: this permission can be automatically\n            granted to the system default text classifier -->\n        <flag name=\"textClassifier\" value=\"0x10000\" />\n        <!-- Additional flag from base permission type: this permission can be automatically\n            granted to the document manager -->\n        <flag name=\"documenter\" value=\"0x40000\" />\n        <!-- Additional flag from base permission type: this permission automatically\n            granted to device configurator -->\n        <flag name=\"configurator\" value=\"0x80000\" />\n        <!-- Additional flag from base permission type: this permission designates the app\n            that will approve the sharing of incident reports. -->\n        <flag name=\"incidentReportApprover\" value=\"0x100000\" />\n        <!-- Additional flag from base permission type: this permission can be automatically\n            granted to the system app predictor -->\n        <flag name=\"appPredictor\" value=\"0x200000\" />\n        <!-- Additional flag from base permission type: this permission can be automatically\n            granted to the system companion device manager service -->\n        <flag name=\"companion\" value=\"0x800000\" />\n        <!-- Additional flag from base permission type: this permission will be granted to the\n             retail demo app, as defined by the OEM. -->\n        <flag name=\"retailDemo\" value=\"0x1000000\" />\n        <!-- Additional flag from base permission type: this permission will be granted to the\n             recents app. -->\n        <flag name=\"recents\" value=\"0x2000000\" />\n        <!-- Additional flag from base permission type: this permission is managed by role. -->\n        <flag name=\"role\" value=\"0x4000000\" />\n        <!-- Additional flag from base permission type: this permission can also be granted if the\n             requesting application is signed by, or has in its signing lineage, any of the\n             certificate digests declared in {@link android.R.attr#knownCerts}. -->\n        <flag name=\"knownSigner\" value=\"0x8000000\" />\n    </attr>\n\n    <!-- Flags indicating more context for a permission group. -->\n    <attr name=\"permissionGroupFlags\">\n        <!-- Set to indicate that this permission group contains permissions\n             protecting access to some information that is considered\n             personal to the user (such as contacts, e-mails, etc). -->\n        <flag name=\"personalInfo\" value=\"0x0001\" />\n    </attr>\n\n    <!-- Flags indicating more context for a permission. -->\n    <attr name=\"permissionFlags\">\n        <!-- Set to indicate that this permission allows an operation that\n             may cost the user money.  Such permissions may be highlighted\n             when shown to the user with this additional information.  -->\n        <flag name=\"costsMoney\" value=\"0x1\" />\n        <!-- Additional flag from base permission type: this permission has been\n             removed and it is no longer enforced. It shouldn't be shown in the\n             UI. Removed permissions are kept as normal permissions for backwards\n             compatibility as apps may be checking them before calling an API.\n        -->\n        <flag name=\"removed\" value=\"0x2\" />\n        <!-- This permission is restricted by the platform and it would be\n             grantable only to apps that meet special criteria per platform\n             policy.\n        -->\n        <flag name=\"hardRestricted\" value=\"0x4\" />\n        <!-- This permission is restricted by the platform and it would be\n             grantable in its full form to apps that meet special criteria\n             per platform policy. Otherwise, a weaker form of the permission\n             would be granted. The weak grant depends on the permission.\n             <p>What weak grant means is described in the documentation of\n             the permissions.\n        -->\n        <flag name=\"softRestricted\" value=\"0x8\" />\n        <!-- This permission is restricted immutably which means that its\n             restriction state may be specified only on the first install of\n             the app and will stay in this initial allowlist state until\n             the app is uninstalled.\n        -->\n        <flag name=\"immutablyRestricted\" value=\"0x10\" />\n        <!--\n             Modifier for permission restriction. This permission cannot\n             be exempted by the installer.\n        -->\n        <flag name=\"installerExemptIgnored\" value=\"0x20\" />\n    </attr>\n\n    <!-- Specified the name of a group that this permission is associated\n         with.  The group must have been defined with the\n         {@link android.R.styleable#AndroidManifestPermissionGroup permission-group} tag. -->\n    <attr name=\"permissionGroup\" format=\"string\" />\n\n    <!-- A reference to an array resource containing the signing certificate digests to be granted\n         this permission when using the {@code knownSigner} protection flag. The digest should\n         be computed over the DER encoding of the trusted certificate using the SHA-256 digest\n         algorithm.\n         <p>\n         If only a single signer is declared this can also be a string resource, or the digest\n         can be declared inline as the value for this attribute. -->\n    <attr name=\"knownCerts\" format=\"reference|string\" />\n\n    <!-- Specify the name of a user ID that will be shared between multiple\n         packages.  By default, each package gets its own unique user-id.\n         By setting this value on two or more packages, each of these packages\n         will be given a single shared user ID, so they can for example run\n         in the same process.  Note that for them to actually get the same\n         user ID, they must also be signed with the same signature.\n         @deprecated Shared user IDs cause non-deterministic behavior within the\n         package manager. As such, its use is strongly discouraged and may be\n         removed in a future version of Android. Instead, apps should use proper\n         communication mechanisms, such as services and content providers,\n         to facilitate interoperability between shared components. Note that\n         existing apps cannot remove this value, as migrating off a\n         shared user ID is not supported. -->\n    <attr name=\"sharedUserId\" format=\"string\" />\n\n    <!-- Specify a label for the shared user UID of this package.  This is\n         only used if you have also used android:sharedUserId.  This must\n         be a reference to a string resource; it can not be an explicit\n         string.\n         @deprecated There is no replacement for this attribute.\n         {@link android.R.attr#sharedUserId} has been deprecated making\n         this attribute unnecessary. -->\n    <attr name=\"sharedUserLabel\" format=\"reference\" />\n\n    <!-- Internal version code.  This is the number used to determine whether\n         one version is more recent than another: it has no other meaning than\n         that higher numbers are more recent.  You could use this number to\n         encode a \"x.y\" in the lower and upper 16 bits, make it a build\n         number, simply increase it by one each time a new version is\n         released, or define it however else you want, as long as each\n         successive version has a higher number.  This is not a version\n         number generally shown to the user, that is usually supplied\n         with {@link android.R.attr#versionName}.  When an app is delivered\n         as multiple split APKs, each APK must have the exact same versionCode. -->\n    <attr name=\"versionCode\" format=\"integer\" />\n\n    <!-- Internal major version code.  This is essentially additional high bits\n         for the base version code; it has no other meaning than\n         that higher numbers are more recent.  This is not a version\n         number generally shown to the user, that is usually supplied\n         with {@link android.R.attr#versionName}. -->\n    <attr name=\"versionCodeMajor\" format=\"integer\" />\n\n    <!-- Internal revision code.  This number is the number used to determine\n         whether one APK is more recent than another: it has no other meaning\n         than that higher numbers are more recent.  This value is only meaningful\n         when the two {@link android.R.attr#versionCode} values are already\n         identical.  When an app is delivered as multiple split APKs, each\n         APK may have a different revisionCode value. -->\n    <attr name=\"revisionCode\" format=\"integer\" />\n\n    <!-- The text shown to the user to indicate the version they have.  This\n         is used for no other purpose than display to the user; the actual\n         significant version number is given by {@link android.R.attr#versionCode}. -->\n    <attr name=\"versionName\" format=\"string\" />\n\n    <!-- Flag to control special persistent mode of an application.  This should\n         not normally be used by applications; it requires that the system keep\n         your application running at all times. -->\n    <attr name=\"persistent\" format=\"boolean\" />\n\n    <!-- If set, the \"persistent\" attribute will only be honored if the feature\n         specified here is present on the device. -->\n    <attr name=\"persistentWhenFeatureAvailable\" format=\"string\" />\n\n    <!-- Flag to specify if this application needs to be present for all users. Only pre-installed\n         applications can request this feature. Default value is false. -->\n    <attr name=\"requiredForAllUsers\" format=\"boolean\" />\n\n    <!-- Flag indicating whether the application can be debugged, even when\n         running on a device that is running in user mode. -->\n    <attr name=\"debuggable\" format=\"boolean\" />\n\n    <!-- Flag indicating whether the application requests the VM to operate in\n         the safe mode.  -->\n    <attr name=\"vmSafeMode\" format=\"boolean\" />\n\n    <!-- <p>Flag indicating whether the application's rendering should be hardware\n         accelerated if possible. This flag is turned on by default for applications\n         that are targeting {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}\n         or later.</p>\n         <p>This flag can be set on the application and any activity declared\n         in the manifest. When enabled for the application, each activity is\n         automatically assumed to be hardware accelerated. This flag can be\n         overridden in the activity tags, either turning it off (if on for the\n         application) or on (if off for the application.)</p>\n         <p>When this flag is turned on for an activity (either directly or via\n         the application tag), every window created from the activity, including\n         the activity's own window, will be hardware accelerated, if possible.</p>\n         <p>Please refer to the documentation of\n         {@link android.view.WindowManager.LayoutParams#FLAG_HARDWARE_ACCELERATED}\n         for more information on how to control this flag programmatically.</p> -->\n    <attr name=\"hardwareAccelerated\" format=\"boolean\" />\n\n    <!-- Flag indicating whether the given application component is available\n         to other applications.  If false, it can only be accessed by\n         applications with its same user id (which usually means only by\n         code in its own package).  If true, it can be invoked by external\n         entities, though which ones can do so may be controlled through\n         permissions.  The default value is false for activity, receiver,\n         and service components that do not specify any intent filters; it\n         is true for activity, receiver, and service components that do\n         have intent filters (implying they expect to be invoked by others\n         who do not know their particular component name) and for all\n         content providers. -->\n    <attr name=\"exported\" format=\"boolean\" />\n\n    <!-- A boolean flag used to indicate if an application is a Game or not.\n         <p>This information can be used by the system to group together\n         applications that are classified as games, and display them separately\n         from the other applications. -->\n    <attr name=\"isGame\" format=\"boolean\" />\n\n    <!-- If set to true, a single instance of this component will run for\n         all users.  That instance will run as user 0, the default/primary\n         user.  When the app running is in processes for other users and interacts\n         with this component (by binding to a service for example) those processes will\n         always interact with the instance running for user 0.  Enabling\n         single user mode forces \"exported\" of the component to be false, to\n         help avoid introducing multi-user security bugs.  This feature is only\n         available to applications built in to the system image; you must hold the\n         permission INTERACT_ACROSS_USERS in order\n         to use this feature.  This flag can only be used with services,\n         receivers, and providers; it can not be used with activities. -->\n    <attr name=\"singleUser\" format=\"boolean\" />\n\n    <!-- Specify a specific process that the associated code is to run in.\n         Use with the application tag (to supply a default process for all\n         application components), or with the activity, receiver, service,\n         or provider tag (to supply a specific icon for that component).\n\n         <p>Application components are normally run in a single process that\n         is created for the entire application.  You can use this tag to modify\n         where they run.  If the process name begins with a ':' character,\n         a new process private to that application will be created when needed\n         to run that component (allowing you to spread your application across\n         multiple processes).  If the process name begins with a lower-case\n         character, the component will be run in a global process of that name,\n         provided that you have permission to do so, allowing multiple\n         applications to share one process to reduce resource usage. -->\n    <attr name=\"process\" format=\"string\" />\n\n    <!-- Specify a task name that activities have an \"affinity\" to.\n         Use with the application tag (to supply a default affinity for all\n         activities in the application), or with the activity tag (to supply\n         a specific affinity for that component).\n\n         <p>The default value for this attribute is the same as the package\n         name, indicating that all activities in the manifest should generally\n         be considered a single \"application\" to the user.  You can use this\n         attribute to modify that behavior: either giving them an affinity\n         for another task, if the activities are intended to be part of that\n         task from the user's perspective, or using an empty string for\n         activities that have no affinity to a task. -->\n    <attr name=\"taskAffinity\" format=\"string\" />\n\n    <!-- Specify that an activity can be moved out of a task it is in to\n         the task it has an affinity for when appropriate.  Use with the\n         application tag (to supply a default for all activities in the\n         application), or with an activity tag (to supply a specific\n         setting for that component).\n\n         <p>Normally when an application is started, it is associated with\n         the task of the activity that started it and stays there for its\n         entire lifetime.  You can use the allowTaskReparenting feature to force an\n         activity to be re-parented to a different task when the task it is\n         in goes to the background.  Typically this is used to cause the\n         activities of an application to move back to the main task associated\n         with that application.  The activity is re-parented to the task\n         with the same {@link android.R.attr#taskAffinity} as it has. -->\n    <attr name=\"allowTaskReparenting\" format=\"boolean\" />\n\n    <!-- Declare that this application may use cleartext traffic, such as HTTP rather than HTTPS;\n         WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP without STARTTLS or TLS.\n         Defaults to true. If set to false {@code false}, the application declares that it does not\n         intend to use cleartext network traffic, in which case platform components (e.g. HTTP\n         stacks, {@code DownloadManager}, {@code MediaPlayer}) will refuse applications's requests\n         to use cleartext traffic. Third-party libraries are encouraged to honor this flag as well.\n         -->\n    <attr name=\"usesCleartextTraffic\" format=\"boolean\" />\n\n    <!-- Declare that code from this application will need to be loaded into other\n         applications' processes. On devices that support multiple instruction sets,\n         this implies the code might be loaded into a process that's using any of the devices\n         supported instruction sets.\n\n         <p> The system might treat such applications specially, for eg., by\n         extracting the application's native libraries for all supported instruction\n         sets or by compiling the application's dex code for all supported instruction\n         sets. -->\n    <attr name=\"multiArch\" format =\"boolean\" />\n\n    <!-- Specify whether the 32 bit version of the ABI should be used in a\n         multiArch application. If both abioverride flag (i.e. using abi option of abd install)\n         and use32bitAbi are used, then use32bit is ignored.-->\n    <attr name=\"use32bitAbi\" />\n\n    <!-- Specify whether a component is allowed to have multiple instances\n         of itself running in different processes.  Use with the activity\n         and provider tags.\n\n         <p>Normally the system will ensure that all instances of a particular\n         component are only running in a single process.  You can use this\n         attribute to disable that behavior, allowing the system to create\n         instances wherever they are used (provided permissions allow it).\n         This is most often used with content providers, so that instances\n         of a provider can be created in each client process, allowing them\n         to be used without performing IPC.  -->\n    <attr name=\"multiprocess\" format=\"boolean\" />\n\n    <!-- Specify whether an activity should be finished when its task is\n         brought to the foreground by relaunching from the home screen.\n\n         <p>If both this option and {@link android.R.attr#allowTaskReparenting} are\n         specified, the finish trumps the affinity: the affinity will be\n         ignored and the activity simply finished. -->\n    <attr name=\"finishOnTaskLaunch\" format=\"boolean\" />\n\n    <!-- Specify whether an activity should be finished when a \"close system\n         windows\" request has been made.  This happens, for example, when\n         the home key is pressed, when the device is locked, when a system\n         dialog showing recent applications is displayed, etc. -->\n    <attr name=\"finishOnCloseSystemDialogs\" format=\"boolean\" />\n\n    <!-- Specify whether an activity's task should be cleared when it\n         is re-launched from the home screen.  As a result, every time the\n         user starts the task, they will be brought to its root activity,\n         regardless of whether they used BACK or HOME to last leave it.\n         This flag only applies to activities that\n         are used to start the root of a new task.\n\n         <p>An example of the use of this flag would be for the case where\n         a user launches activity A from home, and from there goes to\n         activity B.  They now press home, and then return to activity A.\n         Normally they would see activity B, since that is what they were\n         last doing in A's task.  However, if A has set this flag to true,\n         then upon going to the background all of the tasks on top of it (B\n         in this case) are removed, so when the user next returns to A they\n         will restart at its original activity.\n\n         <p>When this option is used in conjunction with\n         {@link android.R.attr#allowTaskReparenting}, the allowTaskReparenting trumps the\n         clear.  That is, all activities above the root activity of the\n         task will be removed: those that have an affinity will be moved\n         to the task they are associated with, otherwise they will simply\n         be dropped as described here. -->\n    <attr name=\"clearTaskOnLaunch\" format=\"boolean\" />\n\n    <!-- Specify whether an activity should be kept in its history stack.\n         If this attribute is set, then as soon as the user navigates away\n         from the activity it will be finished and they will no longer be\n         able to return to it. -->\n    <attr name=\"noHistory\" format=\"boolean\" />\n\n    <!-- Specify whether an acitivty's task state should always be maintained\n         by the system, or if it is allowed to reset the task to its initial\n         state in certain situations.\n\n         <p>Normally the system will reset a task (remove all activities from\n         the stack and reset the root activity) in certain situations when\n         the user re-selects that task from the home screen.  Typically this\n         will be done if the user hasn't visited that task for a certain\n         amount of time, such as 30 minutes.\n\n         <p>By setting this attribute, the user will always return to your\n         task in its last state, regardless of how they get there.  This is\n         useful, for example, in an application like the web browser where there\n         is a lot of state (such as multiple open tabs) that the application\n         would not like to lose. -->\n    <attr name=\"alwaysRetainTaskState\" format=\"boolean\" />\n\n    <!-- Indicates that an Activity does not need to have its freeze state\n         (as returned by {@link android.app.Activity#onSaveInstanceState}\n         retained in order to be restarted.  Generally you use this for activities\n         that do not store any state.  When this flag is set, if for some reason\n         the activity is killed before it has a chance to save its state,\n         then the system will not remove it from the activity stack like\n         it normally would.  Instead, the next time the user navigates to\n         it its {@link android.app.Activity#onCreate} method will be called\n         with a null icicle, just like it was starting for the first time.\n\n         <p>This is used by the Home activity to make sure it does not get\n         removed if it crashes for some reason. -->\n    <attr name=\"stateNotNeeded\" format=\"boolean\" />\n\n    <!-- Indicates that an Activity should be excluded from the list of\n         recently launched activities. -->\n    <attr name=\"excludeFromRecents\" format=\"boolean\" />\n\n    <!-- Specify that an Activity should be shown over the lock screen and,\n         in a multiuser environment, across all users' windows.\n         @deprecated use {@link android.R.attr#showForAllUsers} instead. -->\n    <attr name=\"showOnLockScreen\" format=\"boolean\" />\n\n    <!-- Specify that an Activity should be shown even if the current/foreground user\n         is different from the user of the Activity. This will also force the\n         <code>android.view.LayoutParams.FLAG_SHOW_WHEN_LOCKED</code> flag\n         to be set for all windows of this activity -->\n    <attr name=\"showForAllUsers\" format=\"boolean\" />\n\n    <!-- Specifies whether an {@link android.app.Activity} should be shown on top of the lock screen\n         whenever the lockscreen is up and the activity is resumed. Normally an activity will be\n         transitioned to the stopped state if it is started while the lockscreen is up, but with\n         this flag set the activity will remain in the resumed state visible on-top of the lock\n         screen.\n\n         <p>This should be used instead of {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED}\n         flag set for Windows. When using the Window flag during activity startup, there may not be\n         time to add it before the system stops your activity for being behind the lock-screen.\n         This leads to a double life-cycle as it is then restarted.</p> -->\n    <attr name=\"showWhenLocked\" format=\"boolean\" />\n\n    <!-- Specifies whether the screen should be turned on when the {@link android.app.Activity} is resumed.\n         Normally an activity will be transitioned to the stopped state if it is started while the\n         screen if off, but with this flag set the activity will cause the screen to turn on if the\n         activity will be visible and resumed due to the screen coming on. The screen will not be\n         turned on if the activity won't be visible after the screen is turned on. This flag is\n         normally used in conjunction with the {@link android.R.attr#showWhenLocked} flag to make\n         sure the activity is visible after the screen is turned on when the lockscreen is up. In\n         addition, if this flag is set and the activity calls\n         {@link android.app.KeyguardManager#requestDismissKeyguard}\n         the screen will turn on.\n\n         <p>This should be used instead of {@link android.view.WindowManager.LayoutParams#FLAG_TURN_SCREEN_ON}\n         flag set for Windows. When using the Window flag during activity startup, there may not be\n         time to add it before the system stops your activity because the screen has not yet turned\n         on. This leads to a double life-cycle as it is then restarted.</p> -->\n    <attr name=\"turnScreenOn\" format=\"boolean\" />\n\n    <!-- Specify the authorities under which this content provider can be\n         found.  Multiple authorities may be supplied by separating them\n         with a semicolon.  Authority names should use a Java-style naming\n         convention (such as <code>com.google.provider.MyProvider</code>)\n         in order to avoid conflicts.  Typically this name is the same\n         as the class implementation describing the provider's data structure. -->\n    <attr name=\"authorities\" format=\"string\" />\n\n    <!-- Flag indicating whether this content provider would like to\n         participate in data synchronization. -->\n    <attr name=\"syncable\" format=\"boolean\" />\n\n    <!-- Flag declaring this activity to be 'immersive'; immersive activities\n         should not be interrupted with other activities or notifications. -->\n    <attr name=\"immersive\" format=\"boolean\" />\n\n    <!-- Flag declaring that this activity will be run in VR mode, and specifying\n         the component of the {@link android.service.vr.VrListenerService} that should be\n         bound while this Activity is visible if it is installed and enabled on this device.\n         This is equivalent to calling {@link android.app.Activity#setVrModeEnabled} with the\n         the given component name within the Activity that this attribute is set for.\n         Declaring this will prevent the system from leaving VR mode during an Activity\n         transtion from one VR activity to another. -->\n    <attr name=\"enableVrMode\" format=\"string\" />\n\n    <!-- Flag allowing the activity to specify which screen rotation animation\n         it desires.  Valid values are \"rotate\", \"crossfade\", and \"jumpcut\"\n         as described in {@link android.view.WindowManager.LayoutParams#rotationAnimation}.\n         Specifying your Rotation animation in the WindowManager.LayoutParams\n         may be racy with app startup and updattransitions occuring during application startup and thusly\n         the manifest attribute is preferred.\n    -->\n    <attr name=\"rotationAnimation\">\n      <flag name=\"rotate\" value= \"0\" />\n      <flag name=\"crossfade\" value = \"1\" />\n      <flag name=\"jumpcut\" value = \"2\" />\n      <flag name=\"seamless\" value = \"3\" />\n    </attr>\n\n    <!-- Specify the order in which content providers hosted by a process\n         are instantiated when that process is created.  Not needed unless\n         you have providers with dependencies between each other, to make\n         sure that they are created in the order needed by those dependencies.\n         The value is a simple integer, with higher numbers being\n         initialized first. -->\n    <attr name=\"initOrder\" format=\"integer\" />\n\n    <!-- Specify the relative importance or ability in handling a particular\n         Intent.  For receivers, this controls the order in which they are\n         executed to receive a broadcast (note that for\n         asynchronous broadcasts, this order is ignored).  For activities,\n         this provides information about how good an activity is handling an\n         Intent; when multiple activities match an intent and have different\n         priorities, only those with the higher priority value will be\n         considered a match.\n\n         <p>Only use if you really need to impose some specific\n         order in which the broadcasts are received, or want to forcibly\n         place an activity to always be preferred over others.  The value is a\n         single integer, with higher numbers considered to be better. -->\n    <attr name=\"priority\" format=\"integer\" />\n\n    <!-- Indicate if this component is aware of direct boot lifecycle, and can be\n         safely run before the user has entered their credentials (such as a lock\n         pattern or PIN). -->\n    <attr name=\"directBootAware\" format=\"boolean\" />\n\n    <!-- Specify how an activity should be launched.  See the\n         <a href=\"{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html\">Tasks and Back\n         Stack</a> document for important information on how these options impact\n         the behavior of your application.\n\n         <p>If this attribute is not specified, <code>standard</code> launch\n         mode will be used.  Note that the particular launch behavior can\n         be changed in some ways at runtime through the\n         {@link android.content.Intent} flags\n         {@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP},\n         {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}, and\n         {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}. -->\n    <attr name=\"launchMode\">\n        <!-- The default mode, which will usually create a new instance of\n             the activity when it is started, though this behavior may change\n             with the introduction of other options such as\n             {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK\n             Intent.FLAG_ACTIVITY_NEW_TASK}. -->\n        <enum name=\"standard\" value=\"0\" />\n        <!-- If, when starting the activity, there is already an\n            instance of the same activity class in the foreground that is\n            interacting with the user, then\n            re-use that instance.  This existing instance will receive a call to\n            {@link android.app.Activity#onNewIntent Activity.onNewIntent()} with\n            the new Intent that is being started. -->\n        <enum name=\"singleTop\" value=\"1\" />\n        <!-- If, when starting the activity, there is already a task running\n            that starts with this activity, then instead of starting a new\n            instance the current task is brought to the front.  The existing\n            instance will receive a call to {@link android.app.Activity#onNewIntent\n            Activity.onNewIntent()}\n            with the new Intent that is being started, and with the\n            {@link android.content.Intent#FLAG_ACTIVITY_BROUGHT_TO_FRONT\n            Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT} flag set.  This is a superset\n            of the singleTop mode, where if there is already an instance\n            of the activity being started at the top of the stack, it will\n            receive the Intent as described there (without the\n            FLAG_ACTIVITY_BROUGHT_TO_FRONT flag set).  See the\n            <a href=\"{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html\">Tasks and Back\n            Stack</a> document for more details about tasks.-->\n        <enum name=\"singleTask\" value=\"2\" />\n        <!-- Only allow one instance of this activity to ever be\n            running.  This activity gets a unique task with only itself running\n            in it; if it is ever launched again with the same Intent, then that\n            task will be brought forward and its\n            {@link android.app.Activity#onNewIntent Activity.onNewIntent()}\n            method called.  If this\n            activity tries to start a new activity, that new activity will be\n            launched in a separate task.  See the\n            <a href=\"{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html\">Tasks and Back\n            Stack</a> document for more details about tasks.-->\n        <enum name=\"singleInstance\" value=\"3\" />\n        <!-- The activity can only be running as the root activity of the task, the first activity\n            that created the task, and therefore there will only be one instance of this activity\n            in a task. In constrast to the {@code singleTask} launch mode, this activity can be\n            started in multiple instances in different tasks if the\n            {@code FLAG_ACTIVITY_MULTIPLE_TASK} or {@code FLAG_ACTIVITY_NEW_DOCUMENT} is set.-->\n        <enum name=\"singleInstancePerTask\" value=\"4\" />\n    </attr>\n    <!-- Specify the orientation an activity should be run in.  If not\n         specified, it will run in the current preferred orientation\n         of the screen.\n         <p>This attribute is supported by the <a\n            href=\"{@docRoot}guide/topics/manifest/activity-element.html\">{@code <activity>}</a>\n            element. -->\n    <attr name=\"screenOrientation\">\n        <!-- No preference specified: let the system decide the best\n             orientation.  This will either be the orientation selected\n             by the activity below, or the user's preferred orientation\n             if this activity is the bottom of a task. If the user\n             explicitly turned off sensor based orientation through settings\n             sensor based device rotation will be ignored. If not by default\n             sensor based orientation will be taken into account and the\n             orientation will changed based on how the user rotates the device.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}. -->\n        <enum name=\"unspecified\" value=\"-1\" />\n        <!-- Would like to have the screen in a landscape orientation: that\n             is, with the display wider than it is tall, ignoring sensor data.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}. -->\n        <enum name=\"landscape\" value=\"0\" />\n        <!-- Would like to have the screen in a portrait orientation: that\n             is, with the display taller than it is wide, ignoring sensor data.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_PORTRAIT}. -->\n        <enum name=\"portrait\" value=\"1\" />\n        <!-- Use the user's current preferred orientation of the handset.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER}. -->\n        <enum name=\"user\" value=\"2\" />\n        <!-- Keep the screen in the same orientation as whatever is behind\n             this activity.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_BEHIND}. -->\n        <enum name=\"behind\" value=\"3\" />\n        <!-- Orientation is determined by a physical orientation sensor:\n             the display will rotate based on how the user moves the device.\n             Ignores user's setting to turn off sensor-based rotation.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_SENSOR}. -->\n        <enum name=\"sensor\" value=\"4\" />\n        <!-- Always ignore orientation determined by orientation sensor:\n             the display will not rotate when the user moves the device.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_NOSENSOR}. -->\n        <enum name=\"nosensor\" value=\"5\" />\n        <!-- Would like to have the screen in landscape orientation, but can\n             use the sensor to change which direction the screen is facing.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_SENSOR_LANDSCAPE}. -->\n        <enum name=\"sensorLandscape\" value=\"6\" />\n        <!-- Would like to have the screen in portrait orientation, but can\n             use the sensor to change which direction the screen is facing.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_SENSOR_PORTRAIT}. -->\n        <enum name=\"sensorPortrait\" value=\"7\" />\n        <!-- Would like to have the screen in landscape orientation, turned in\n             the opposite direction from normal landscape.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_REVERSE_LANDSCAPE}. -->\n        <enum name=\"reverseLandscape\" value=\"8\" />\n        <!-- Would like to have the screen in portrait orientation, turned in\n             the opposite direction from normal portrait.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_REVERSE_PORTRAIT}. -->\n        <enum name=\"reversePortrait\" value=\"9\" />\n        <!-- Orientation is determined by a physical orientation sensor:\n             the display will rotate based on how the user moves the device.\n             This allows any of the 4 possible rotations, regardless of what\n             the device will normally do (for example some devices won't\n             normally use 180 degree rotation).\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_FULL_SENSOR}. -->\n        <enum name=\"fullSensor\" value=\"10\" />\n        <!-- Would like to have the screen in landscape orientation, but if\n             the user has enabled sensor-based rotation then we can use the\n             sensor to change which direction the screen is facing.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER_LANDSCAPE}. -->\n        <enum name=\"userLandscape\" value=\"11\" />\n        <!-- Would like to have the screen in portrait orientation, but if\n             the user has enabled sensor-based rotation then we can use the\n             sensor to change which direction the screen is facing.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER_PORTRAIT}. -->\n        <enum name=\"userPortrait\" value=\"12\" />\n        <!-- Respect the user's sensor-based rotation preference, but if\n             sensor-based rotation is enabled then allow the screen to rotate\n             in all 4 possible directions regardless of what\n             the device will normally do (for example some devices won't\n             normally use 180 degree rotation).\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_FULL_USER}. -->\n        <enum name=\"fullUser\" value=\"13\" />\n        <!-- Screen is locked to its current rotation, whatever that is.\n             Corresponds to\n             {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LOCKED}. -->\n        <enum name=\"locked\" value=\"14\" />\n    </attr>\n\n    <!-- Specify the configuration changes that trigger the system to recreate the\n         current activity if any of these configuration changes happen in the system.\n         The valid configuration changes include mcc and mnc which are the same with\n         those in configChanges. By default from Android O, we don't recreate the activity\n         even the app doesn't specify mcc or mnc in configChanges. If the app wants to\n         be recreated, specify them in recreateOnConfigChanges. -->\n    <attr name=\"recreateOnConfigChanges\">\n        <!-- The IMSI MCC has changed, that is a SIM has been detected and\n             updated the Mobile Country Code. -->\n        <flag name=\"mcc\" value=\"0x0001\" />\n        <!-- The IMSI MNC has changed, that is a SIM has been detected and\n             updated the Mobile Network Code. -->\n        <flag name=\"mnc\" value=\"0x0002\" />\n    </attr>\n\n    <!-- Specify one or more configuration changes that the activity will\n         handle itself.  If not specified, the activity will be restarted\n         if any of these configuration changes happen in the system.  Otherwise,\n         the activity will remain running and its\n         {@link android.app.Activity#onConfigurationChanged Activity.onConfigurationChanged}\n         method called with the new configuration.\n\n         <p>Note that all of these configuration changes can impact the\n         resource values seen by the application, so you will generally need\n         to re-retrieve all resources (including view layouts, drawables, etc)\n         to correctly handle any configuration change.\n\n         <p>These values must be kept in sync with those in\n         {@link android.content.pm.ActivityInfo} and\n         include/utils/ResourceTypes.h. -->\n    <attr name=\"configChanges\">\n        <!-- The IMSI MCC has changed, that is a SIM has been detected and\n             updated the Mobile Country Code. By default from Android O, we\n             don't recreate the activity even the app doesn't specify mcc in\n             configChanges. If the app wants to recreate the activity, specify\n             mcc in recreateOnConfigChanges. -->\n        <flag name=\"mcc\" value=\"0x0001\" />\n        <!-- The IMSI MNC has changed, that is a SIM has been detected and\n             updated the Mobile Network Code. By default from Android O, we\n             don't recreate the activity even the app doesn't specify mnc in\n             configChanges. If the app wants to recreate the acvitity, specify\n             mnc in recreateOnConfigChanges. -->\n        <flag name=\"mnc\" value=\"0x0002\" />\n        <!-- The locale has changed, that is the user has selected a new\n             language that text should be displayed in. -->\n        <flag name=\"locale\" value=\"0x0004\" />\n        <!-- The touchscreen has changed.  Should never normally happen. -->\n        <flag name=\"touchscreen\" value=\"0x0008\" />\n        <!-- The keyboard type has changed, for example the user has plugged\n             in an external keyboard. -->\n        <flag name=\"keyboard\" value=\"0x0010\" />\n        <!-- The keyboard or navigation accessibility has changed, for example\n             the user has slid the keyboard out to expose it.  Note that\n             despite its name, this applied to any accessibility: keyboard\n             or navigation. -->\n        <flag name=\"keyboardHidden\" value=\"0x0020\" />\n        <!-- The navigation type has changed.  Should never normally happen. -->\n        <flag name=\"navigation\" value=\"0x0040\" />\n        <!-- The screen orientation has changed, that is the user has\n             rotated the device. -->\n        <flag name=\"orientation\" value=\"0x0080\" />\n        <!-- The screen layout has changed.  This might be caused by a\n             different display being activated. -->\n        <flag name=\"screenLayout\" value=\"0x0100\" />\n        <!-- The global user interface mode has changed.  For example,\n             going in or out of car mode, night mode changing, etc. -->\n        <flag name=\"uiMode\" value=\"0x0200\" />\n        <!-- The current available screen size has changed.  If applications don't\n             target at least {@link android.os.Build.VERSION_CODES#HONEYCOMB_MR2}\n             then the activity will always handle this itself (the change\n             will not result in a restart).  This represents a change in the\n             currently available size, so will change when the user switches\n             between landscape and portrait. -->\n        <flag name=\"screenSize\" value=\"0x0400\" />\n        <!-- The physical screen size has changed.  If applications don't\n             target at least {@link android.os.Build.VERSION_CODES#HONEYCOMB_MR2}\n             then the activity will always handle this itself (the change\n             will not result in a restart).  This represents a change in size\n             regardless of orientation, so will only change when the actual\n             physical screen size has changed such as switching to an external\n             display. -->\n        <flag name=\"smallestScreenSize\" value=\"0x0800\" />\n        <!-- The display density has changed. This might be caused by the user\n             specifying a different display scale, or it might be caused by a\n             different display being activated. -->\n        <flag name=\"density\" value=\"0x1000\" />\n        <!-- The layout direction has changed. For example going from LTR to RTL. -->\n        <flag name=\"layoutDirection\" value=\"0x2000\" />\n        <!-- The color mode of the screen has changed (color gamut or dynamic range). -->\n        <flag name=\"colorMode\" value=\"0x4000\" />\n        <!-- The font scaling factor has changed, that is the user has\n             selected a new global font size. -->\n        <flag name=\"fontScale\" value=\"0x40000000\" />\n        <!-- The font weight adjustment value has changed. Used to reflect the user increasing font\n             weight. -->\n        <flag name=\"fontWeightAdjustment\" value=\"0x10000000\" />\n    </attr>\n\n    <!-- Indicate that the activity can be launched as the embedded child of another\n         activity. Particularly in the case where the child lives in a container\n         such as a Display owned by another activity.\n\n         <p>The default value of this attribute is <code>false</code>. -->\n    <attr name=\"allowEmbedded\" format=\"boolean\" />\n\n    <!-- Specifies whether this {@link android.app.Activity} should be shown on\n         top of the lock screen whenever the lockscreen is up and this activity has another\n         activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That\n         is, this activity is only visible on the lock screen if there is another activity with\n         the {@link android.R.attr#showWhenLocked} attribute visible at the same time on the\n         lock screen. A use case for this is permission dialogs, that should only be visible on\n         the lock screen if their requesting activity is also visible.\n\n         <p>The default value of this attribute is <code>false</code>. -->\n    <attr name=\"inheritShowWhenLocked\" format=\"boolean\" />\n\n    <!-- Descriptive text for the associated data. -->\n    <attr name=\"description\" format=\"reference\" />\n\n    <!-- The name of the application package that an Instrumentation object\n         will run against. -->\n    <attr name=\"targetPackage\" format=\"string\" />\n\n    <!-- The name of an application's processes that an Instrumentation object\n         will run against.  If not specified, only runs in the main process of the targetPackage.\n         Can either be a comma-separated list of process names or '*' for any process that\n         launches to run targetPackage code. -->\n    <attr name=\"targetProcesses\" format=\"string\" />\n\n    <!-- Flag indicating that an Instrumentation class wants to take care\n         of starting/stopping profiling itself, rather than relying on\n         the default behavior of profiling the complete time it is running.\n         This allows it to target profiling data at a specific set of\n         operations. -->\n    <attr name=\"handleProfiling\" format=\"boolean\" />\n\n    <!-- Flag indicating that an Instrumentation class should be run as a\n         functional test. -->\n    <attr name=\"functionalTest\" format=\"boolean\" />\n\n    <!-- The touch screen type used by an application. -->\n    <attr name=\"reqTouchScreen\">\n        <enum name=\"undefined\" value=\"0\" />\n        <enum name=\"notouch\" value=\"1\" />\n        <enum name=\"stylus\" value=\"2\" />\n        <enum name=\"finger\" value=\"3\" />\n    </attr>\n\n    <!-- The input method preferred by an application. -->\n    <attr name=\"reqKeyboardType\">\n        <enum name=\"undefined\" value=\"0\" />\n        <enum name=\"nokeys\" value=\"1\" />\n        <enum name=\"qwerty\" value=\"2\" />\n        <enum name=\"twelvekey\" value=\"3\" />\n    </attr>\n\n    <!-- Application's requirement for a hard keyboard -->\n    <attr name=\"reqHardKeyboard\" format=\"boolean\" />\n\n    <!-- The navigation device preferred by an application. -->\n    <attr name=\"reqNavigation\">\n        <enum name=\"undefined\" value=\"0\" />\n        <enum name=\"nonav\" value=\"1\" />\n        <enum name=\"dpad\" value=\"2\" />\n        <enum name=\"trackball\" value=\"3\" />\n        <enum name=\"wheel\" value=\"4\" />\n    </attr>\n\n    <!-- Application's requirement for five way navigation -->\n    <attr name=\"reqFiveWayNav\" format=\"boolean\" />\n\n    <!-- The name of the class subclassing <code>BackupAgent</code> to manage\n         backup and restore of the application's data on external storage. -->\n    <attr name=\"backupAgent\" format=\"string\" />\n\n    <!-- Whether to allow the application to participate in the backup\n         and restore infrastructure.  If this attribute is set to <code>false</code>,\n         no backup or restore of the application will ever be performed, even by a\n         full-system backup that would otherwise cause all application data to be saved\n         via adb.  The default value of this attribute is <code>true</code>. -->\n    <attr name=\"allowBackup\" format=\"boolean\" />\n\n    <!-- Applications will set this in their manifest to opt-in to or out of full app data back-up\n         and restore. Alternatively they can set it to an xml resource within their app that will\n         be parsed by the BackupAgent to selectively backup files indicated within that xml. -->\n    <attr name=\"fullBackupContent\" format=\"reference|boolean\" />\n\n    <!-- Indicates that even though the application provides a <code>BackupAgent</code>,\n         only full-data streaming backup operations are to be performed to save the app's\n         data.  This lets the app rely on full-data backups while still participating in\n         the backup and restore process via the BackupAgent's full-data backup APIs.\n         When this attribute is <code>true</code> the app's BackupAgent overrides of\n         the onBackup() and onRestore() callbacks can be empty stubs. -->\n    <attr name=\"fullBackupOnly\" format=\"boolean\" />\n\n    <!-- Whether the application in question should be terminated after its\n         settings have been restored during a full-system restore operation.\n         Single-package restore operations will never cause the application to\n         be shut down.  Full-system restore operations typically only occur once,\n         when the phone is first set up.  Third-party applications will not usually\n         need to use this attribute.\n\n         <p>The default is <code>true</code>, which means that after the application\n         has finished processing its data during a full-system restore, it will be\n         terminated. -->\n    <attr name=\"killAfterRestore\" format=\"boolean\" />\n\n    <!-- @deprecated This attribute is not used by the Android operating system. -->\n    <attr name=\"restoreNeedsApplication\" format=\"boolean\" />\n\n    <!-- Indicate that the application is prepared to attempt a restore of any\n         backed-up dataset, even if the backup is apparently from a newer version\n         of the application than is currently installed on the device.  Setting\n         this attribute to <code>true</code> will permit the Backup Manager to\n         attempt restore even when a version mismatch suggests that the data are\n         incompatible.  <em>Use with caution!</em>\n\n         <p>The default value of this attribute is <code>false</code>. -->\n    <attr name=\"restoreAnyVersion\" format=\"boolean\" />\n\n    <!-- Indicates that full-data backup operations for this application may\n         be performed even if the application is in a foreground-equivalent\n         state.  <em>Use with caution!</em>  Setting this flag to <code>true</code>\n         can impact app behavior while the user is interacting with the device.\n\n         <p>If unspecified, the default value of this attribute is <code>false</code>,\n         which means that the OS will avoid backing up the application while it is\n         running in the foreground (such as a music app that is actively playing\n         music via a service in the startForeground() state). -->\n    <attr name=\"backupInForeground\" format=\"boolean\" />\n\n    <!-- The default install location defined by an application. -->\n    <attr name=\"installLocation\">\n        <!-- Let the system decide ideal install location -->\n        <enum name=\"auto\" value=\"0\" />\n        <!-- Explicitly request to be installed on internal phone storage\n             only. -->\n        <enum name=\"internalOnly\" value=\"1\" />\n        <!-- Prefer to be installed on SD card. There is no guarantee that\n             the system will honor this request. The application might end\n             up being installed on internal storage if external media\n             is unavailable or too full. -->\n        <enum name=\"preferExternal\" value=\"2\" />\n    </attr>\n\n    <!-- If set to <code>true</code>, indicates to the platform that any split APKs\n         installed for this application should be loaded into their own Context\n         objects and not appear in the base application's Context.\n\n         <p>The default value of this attribute is <code>false</code>. -->\n    <attr name=\"isolatedSplits\" format=\"boolean\" />\n\n    <!-- The classname of the classloader used to load the application's classes\n         from its APK. The APK in question can either be the 'base' APK or any\n         of the application's 'split' APKs if it's using a feature split.\n\n         <p>\n         The supported values for this attribute are\n         <code>dalvik.system.PathClassLoader</code> and\n         <code>dalvik.system.DelegateLastClassLoader</code>. If unspecified,\n         the default value of this attribute is <code>dalvik.system.PathClassLoader</code>.\n\n         If an unknown classloader is provided, a PackageParserException with cause\n         <code>PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED</code> will be\n         thrown and the app will not be installed.\n         -->\n    <attr name=\"classLoader\" format=\"string\" />\n\n    <!-- Name of the class that gets invoked for preloading application code, when starting an\n         {@link android.R.attr#isolatedProcess} service that has\n         {@link android.R.attr#useAppZygote} set to <code>true</code>. This is a fully\n         qualified class name (for example, com.mycompany.myapp.MyZygotePreload); as a\n         short-hand if the first character of the class is a period then it is appended\n         to your package name. The class must implement the {@link android.app.ZygotePreload}\n         interface. -->\n    <attr name=\"zygotePreloadName\" format=\"string\"/>\n\n    <!-- If set to <code>true</code>, indicates to the platform that this APK is\n         a 'feature' split and that it implicitly depends on the base APK. This distinguishes\n         this split APK from a 'configuration' split, which provides resource overrides\n         for a particular 'feature' split. Only useful when the base APK specifies\n         <code>android:isolatedSplits=\"true\"</code>.\n\n         <p>The default value of this attribute is <code>false</code>. -->\n    <attr name=\"isFeatureSplit\" format=\"boolean\" />\n\n    <!-- Flag to specify if this APK requires at least one split [either feature or\n         resource] to be present in order to function. Default value is false. -->\n    <attr name=\"isSplitRequired\" format=\"boolean\" />\n\n    <!-- Flag to specify if this app wants to run the dex within its APK but not extracted or\n         locally compiled variants. This keeps the dex code protected by the APK signature. Such\n         apps will always run in JIT mode (same when they are first installed), and the system will\n         never generate ahead-of-time compiled code for them. Depending on the app's workload,\n         there may be some run time performance change, noteably the cold start time. -->\n    <attr name=\"useEmbeddedDex\" format=\"boolean\" />\n\n    <!-- Extra options for an activity's UI. Applies to either the {@code <activity>} or\n         {@code <application>} tag. If specified on the {@code <application>}\n         tag these will be considered defaults for all activities in the\n         application. -->\n    <attr name=\"uiOptions\">\n        <!-- No extra UI options. This is the default. -->\n        <flag name=\"none\" value=\"0\" />\n        <!-- Split the options menu into a separate bar at the bottom of\n             the screen when severely constrained for horizontal space.\n             (e.g. portrait mode on a phone.) Instead of a small number\n             of action buttons appearing in the action bar at the top\n             of the screen, the action bar will split into the top navigation\n             section and the bottom menu section. Menu items will not be\n             split across the two bars; they will always appear together. -->\n        <flag name=\"splitActionBarWhenNarrow\" value=\"1\" />\n    </attr>\n\n    <!-- The name of the logical parent of the activity as it appears in the manifest. -->\n    <attr name=\"parentActivityName\" format=\"string\" />\n\n    <!-- Define how an activity persist across reboots. Activities defined as \"never\" will not\n         be persisted. Those defined as \"always\" will be persisted. Those defined as \"taskOnly\"\n         will persist the root activity of the task only. See below for more detail as to\n         what gets persisted. -->\n    <attr name=\"persistableMode\">\n        <!-- The default. If this activity forms the root of a task then that task will be\n             persisted across reboots but only the launching intent will be used. If the task\n             relinquishes its identity then the intent used is that of the topmost inherited\n             identity. All activities above this activity in the task will not be persisted.\n             In addition this activity will not be passed a PersistableBundle into which it\n             could have stored its state. -->\n        <enum name=\"persistRootOnly\" value=\"0\" />\n        <!-- If this activity forms the root of a task then that task will not be persisted\n             across reboots -->\n        <enum name=\"persistNever\" value=\"1\" />\n        <!-- If this activity forms the root of a task then the task and this activity will\n             be persisted across reboots. If the activity above this activity is also\n             tagged with the attribute <code>\"persist\"</code> then it will be persisted as well.\n             And so on up the task stack until either an activity without the\n             <code>persistableMode=\"persistAcrossReboots\"</code> attribute or one that was launched\n             with the flag Intent.FLAG_CLEAR_TASK_WHEN_RESET is encountered.\n\n             <p>Activities that are declared with the persistAcrossReboots attribute will be\n             provided with a PersistableBundle in onSavedInstanceState(), These activities may\n             use this PeristableBundle to save their state. Then, following a reboot, that\n             PersistableBundle will be provided back to the activity in its onCreate() method. -->\n        <enum name=\"persistAcrossReboots\" value=\"2\" />\n    </attr>\n\n    <!-- This attribute specifies that an activity shall become the root activity of a\n         new task each time it is launched. Using this attribute permits the user to\n         have multiple documents from the same applications appear in the recent tasks list.\n\n         <p>Such a document is any kind of item for which an application may want to\n         maintain multiple simultaneous instances. Examples might be text files, web\n         pages, spreadsheets, or emails. Each such document will be in a separate\n         task in the recent taskss list.\n\n         <p>This attribute is equivalent to adding the flag {@link\n         android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} to every Intent used to launch\n         the activity.\n\n         <p>The documentLaunchMode attribute may be assigned one of four values, \"none\",\n         \"intoExisting\", \"always\" and \"never\", described in detail below. For values other than\n         <code>none</code> and <code>never</code> the activity must be defined with\n         {@link android.R.attr#launchMode} <code>standard</code>.\n         If this attribute is not specified, <code>none</code> will be used.\n         Note that <code>none</code> can be overridden at run time if the Intent used\n         to launch it contains the flag {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT\n         Intent.FLAG_ACTIVITY_NEW_DOCUMENT}.\n         Similarly <code>intoExisting</code> will be overridden by the flag\n         {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT\n         Intent.FLAG_ACTIVITY_NEW_DOCUMENT} combined with\n         {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK\n         Intent.FLAG_ACTIVITY_MULTIPLE_TASK}. If the value of\n         documentLaunchModes is <code>never</code> then any use of\n         {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT\n         Intent.FLAG_ACTIVITY_NEW_DOCUMENT} to launch this activity will be ignored. -->\n    <attr name=\"documentLaunchMode\">\n        <!-- The default mode, which will create a new task only when\n             {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK\n             Intent.FLAG_ACTIVITY_NEW_TASK} is set. -->\n        <enum name=\"none\" value=\"0\" />\n        <!-- All tasks will be searched for one whose base Intent's ComponentName and\n             data URI match those of the launching Intent. If such a task is found\n             that task will be cleared and restarted with the root activity receiving a call\n             to {@link android.app.Activity#onNewIntent Activity.onNewIntent}. If no\n             such task is found a new task will be created.\n             <p>This is the equivalent of launching an activity with {@link\n             android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NEW_DOCUMENT}\n             set and without {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK\n             Intent.FLAG_ACTIVITY_MULTIPLE_TASK} set. -->\n        <enum name=\"intoExisting\" value=\"1\" />\n        <!-- A new task rooted at this activity will be created. This will happen whether or\n             not there is an existing task whose ComponentName and data URI match\n             that of the launcing intent This is the equivalent of launching an activity\n             with {@link\n             android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NEW_DOCUMENT}\n             and {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK\n             Intent.FLAG_ACTIVITY_MULTIPLE_TASK} both set. -->\n        <enum name=\"always\" value=\"2\" />\n        <!-- This activity will not be launched into a new document even if the Intent contains\n             {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT\n             Intent.FLAG_ACTIVITY_NEW_DOCUMENT}. This gives the activity writer ultimate\n             control over how their activity is used. Note that applications prior to api\n             21 will default to documentLaunchMode=\"none\" so only activities that explicitly\n             opt out with <code>\"never\"</code> may do so. -->\n        <enum name=\"never\" value=\"3\" />\n    </attr>\n\n    <!-- The maximum number of entries of tasks rooted at this activity in the recent task list.\n         When this number of entries is reached the least recently used instance of this activity\n         will be removed from recents. The value will be clamped between 1 and 100 inclusive.\n         The default value for this if it is not specified is 15. -->\n    <attr name=\"maxRecents\" format=\"integer\" />\n\n    <!-- Tasks launched by activities with this attribute will remain in the recent tasks\n         list until the last activity in the task is completed.  When that happens the task\n         will be automatically removed from the recent tasks list.  This overrides the caller's\n         use of {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS\n         Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS} -->\n    <attr name=\"autoRemoveFromRecents\" format=\"boolean\" />\n\n    <!-- Tasks whose root has this attribute set to true will replace baseIntent with that of the\n         next activity in the task. If the next activity also has this attribute set to true then\n         it will yield the baseIntent to any activity that it launches in the same task. This\n         continues until an activity is encountered which has this attribute set to false. False\n         is the default. This attribute set to true also permits activity's use of the\n         TaskDescription to change labels, colors and icons in the recent task list.\n\n         <p>NOTE: Setting this flag to <code>true</code> will not change the affinity of the task,\n         which is used for intent resolution during activity launch. The task's root activity will\n         always define its affinity. -->\n    <attr name=\"relinquishTaskIdentity\" format=\"boolean\" />\n\n    <!-- Indicate that it is okay for this activity be resumed while the previous\n         activity is in the process of pausing, without waiting for the previous pause\n         to complete.  Use this with caution: your activity can not acquire any exclusive\n         resources (such as opening the camera or recording audio) when it launches, or it\n         may conflict with the previous activity and fail.\n\n         <p>The default value of this attribute is <code>false</code>. -->\n    <attr name=\"resumeWhilePausing\" format=\"boolean\" />\n\n    <!-- Hint to platform that the activity works well in multi-window mode. Intended for a\n         multi-window device where there can be multiple activities of various sizes on the screen\n         at the same time.\n\n         <p>The default value is <code>false</code> for applications with\n         <code>targetSdkVersion</code> lesser than {@link android.os.Build.VERSION_CODES#N} and\n         <code>true</code> otherwise.\n\n         <p>Setting this flag to <code>false</code> lets the system know that the app may not be\n         tested or optimized for multi-window environment. The system may still put such activity in\n         multi-window with compatibility mode applied. It also does not guarantee that there will be\n         no other apps in multi-window visible on screen (e.g. picture-in-picture) or on other\n         displays. Therefore, this flag cannot be used to assure an exclusive resource access.\n\n         <p>NOTE: A task's root activity value is applied to all additional activities launched in\n         the task. That is if the root activity of a task is resizeable then the system will treat\n         all other activities in the task as resizeable and will not if the root activity isn't\n         resizeable.\n\n         <p>NOTE: The value of {@link android.R.attr#screenOrientation} is ignored for\n         resizeable activities when in multi-window mode before Android 12. -->\n    <attr name=\"resizeableActivity\" format=\"boolean\" />\n\n    <!-- Indicates that the activity specifically supports the picture-in-picture form of\n         multi-window. If true, this activity will support entering picture-in-picture, but will\n         only support split-screen and other forms of multi-window if\n         {@link android.R.attr#resizeableActivity} is also set to true.\n\n         Note that your activity may still be resized even if this attribute is true and\n         {@link android.R.attr#resizeableActivity} is false.\n\n         <p>The default value is <code>false</code>.  -->\n    <attr name=\"supportsPictureInPicture\" format=\"boolean\" />\n\n    <!-- This value indicates the maximum aspect ratio the activity supports. If the app runs on a\n         device with a wider aspect ratio, the system automatically letterboxes the app, leaving\n         portions of the screen unused so the app can run at its specified maximum aspect ratio.\n         <p>\n         Maximum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal\n         form. For example, if the maximum aspect ratio is 7:3, set value to 2.33.\n         <p>\n         Value needs to be greater or equal to 1.0, otherwise it is ignored.\n         <p>\n         NOTE: This attribute is ignored if the activity has\n         {@link android.R.attr#resizeableActivity} set to true. -->\n    <attr name=\"maxAspectRatio\" format=\"float\" />\n\n    <!-- This value indicates the minimum aspect ratio the activity supports. If the app runs on a\n         device with a narrower aspect ratio, the system automatically letterboxes the app, leaving\n         portions of the screen unused so the app can run at its specified minimum aspect ratio.\n         <p>\n         Minimum aspect ratio, expressed as (longer dimension / shorter dimension) in decimal\n         form. For example, if the minimum aspect ratio is 4:3, set value to 1.33.\n         <p>\n         Value needs to be greater or equal to 1.0, otherwise it is ignored.\n         <p>\n         NOTE: This attribute is ignored if the activity has\n         {@link android.R.attr#resizeableActivity} set to true. -->\n    <attr name=\"minAspectRatio\" format=\"float\" />\n\n    <!-- This value indicates how tasks rooted at this activity will behave in lockTask mode.\n         While in lockTask mode the system will not launch non-permitted tasks until\n         lockTask mode is disabled.\n         <p>While in lockTask mode with multiple permitted tasks running, each launched task is\n         permitted to finish, transitioning to the previous locked task, until there is only one\n         task remaining. At that point the last task running is not permitted to finish, unless it\n         uses the value always. -->\n    <attr name=\"lockTaskMode\">\n        <!-- This is the default value. Tasks will not launch into lockTask mode but can be\n             placed there by calling {@link android.app.Activity#startLockTask}. If a task with\n             this mode has been allowlisted using {@link\n             android.app.admin.DevicePolicyManager#setLockTaskPackages} then calling\n             {@link android.app.Activity#startLockTask} will enter lockTask mode immediately,\n             otherwise the user will be presented with a dialog to approve entering pinned mode.\n             <p>If the system is already in lockTask mode when a new task rooted at this activity\n             is launched that task will or will not start depending on whether the package of this\n             activity has been allowlisted.\n             <p>Tasks rooted at this activity can only exit lockTask mode using\n             {@link android.app.Activity#stopLockTask}. -->\n        <enum name=\"normal\" value=\"0\"/>\n        <!-- Tasks will not launch into lockTask mode and cannot be placed there using\n             {@link android.app.Activity#startLockTask} or be pinned from the Overview screen.\n             If the system is already in lockTask mode when a new task rooted at this activity is\n             launched that task will not be started.\n             <p>Note: This mode is only available to system and privileged applications.\n             Non-privileged apps with this value will be treated as normal.\n             -->\n        <enum name=\"never\" value=\"1\"/>\n        <!-- Tasks rooted at this activity will always launch into lockTask mode. If the system is\n             already in lockTask mode when this task is launched then the new task will be launched\n             on top of the current task. Tasks launched in this mode are capable of exiting\n             lockTask mode using {@link android.app.Activity#finish()}.\n             <p>Note: This mode is only available to system and privileged applications.\n             Non-privileged apps with this value will be treated as normal.\n             -->\n        <enum name=\"always\" value=\"2\"/>\n        <!-- If the DevicePolicyManager (DPM) authorizes this package ({@link\n             android.app.admin.DevicePolicyManager#setLockTaskPackages}) then this mode is\n             identical to always, except that the activity needs to call\n             {@link android.app.Activity#stopLockTask} before being able to finish if it is the last\n             locked task.\n             If the DPM does not authorize this package then this mode is identical to normal. -->\n        <enum name=\"if_whitelisted\" value=\"3\"/>\n    </attr>\n    <!-- When set installer will extract native libraries. If set to false\n         libraries in the apk must be stored and page-aligned.  -->\n    <attr name=\"extractNativeLibs\" format=\"boolean\"/>\n\n    <!-- Specify whether an activity intent filter will need to be verified thru its set\n         of data URIs. This will only be used when the Intent's action is set to\n         {@link android.content.Intent#ACTION_VIEW Intent.ACTION_VIEW} and the Intent's category is\n         set to {@link android.content.Intent#CATEGORY_BROWSABLE Intent.CATEGORY_BROWSABLE} and the\n         intern filter data scheme is set to \"http\" or \"https\". When set to true, the intent filter\n         will need to use its data tag for getting the URIs to verify with.\n\n         For each URI, an HTTPS network request will be done to <code>/.well-known/statements.json</code>\n         host to verify that the web site is okay with the app intercepting the URI.\n         -->\n    <attr name=\"autoVerify\" format=\"boolean\" />\n\n    <!-- Specify whether a component should be visible to instant apps.\n         -->\n    <attr name=\"visibleToInstantApps\" format=\"boolean\" />\n\n    <!-- An XML resource with the application's Network Security Config. -->\n    <attr name=\"networkSecurityConfig\" format=\"reference\" />\n\n    <!-- When an application is partitioned into splits, this is the name of the\n         split that contains the defined component. -->\n    <attr name=\"splitName\" format=\"string\" />\n\n    <!-- Specifies the target sandbox this app wants to use. Higher sandbox versions\n         will have increasing levels of security.\n\n         <p>The default value of this attribute is <code>1</code>.\n         <p>\n         @deprecated The security properties have been moved to\n         {@link android.os.Build.VERSION Build.VERSION} 27 and 28. -->\n    <attr name=\"targetSandboxVersion\" format=\"integer\" />\n\n    <!-- The user-visible SDK version (ex. 26) of the framework against which the application was\n         compiled. This attribute is automatically specified by the Android build tools and should\n         NOT be manually specified.\n         <p>\n         This attribute is the compile-time equivalent of\n         {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}. -->\n    <attr name=\"compileSdkVersion\" format=\"integer\" />\n\n    <!-- The development codename (ex. \"O\") of the framework against which the application was\n         compiled, or \"REL\" if the application was compiled against a release build. This attribute\n         is automatically specified by the Android build tools and should NOT be manually\n         specified.\n         <p>\n         This attribute is the compile-time equivalent of\n         {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}. -->\n    <attr name=\"compileSdkVersionCodename\" format=\"string\" />\n\n    <!-- The (optional) fully-qualified name for a subclass of\n         {@link android.app.AppComponentFactory} that the system uses to instantiate\n         every other manifest defined class. Most applications\n         don't need this attribute. If it's not specified, the system\n         instantiates items without it.-->\n    <attr name=\"appComponentFactory\" format=\"string\" />\n\n    <attr name=\"usesNonSdkApi\" format=\"boolean\" />\n\n    <!-- Whether attributions provided are meant to be user-visible. -->\n    <attr name=\"attributionsAreUserVisible\" format=\"boolean\" />\n\n    <!-- Specify the type of foreground service. Multiple types can be specified by ORing the flags\n         together. -->\n    <attr name=\"foregroundServiceType\">\n        <!-- Data (photo, file, account) upload/download, backup/restore, import/export, fetch,\n        transfer over network between device and cloud.  -->\n        <flag name=\"dataSync\" value=\"0x01\" />\n        <!-- Music, video, news or other media play. -->\n        <flag name=\"mediaPlayback\" value=\"0x02\" />\n        <!-- Ongoing operations related to phone calls, video conferencing,\n             or similar interactive communication. -->\n        <flag name=\"phoneCall\" value=\"0x04\" />\n        <!-- GPS, map, navigation location update. -->\n        <flag name=\"location\" value=\"0x08\" />\n        <!-- Auto, bluetooth, TV or other devices connection, monitoring and interaction. -->\n        <flag name=\"connectedDevice\" value=\"0x10\" />\n        <!-- Managing a media projection session, e.g, for screen recording or taking\n             screenshots.-->\n        <flag name=\"mediaProjection\" value=\"0x20\" />\n        <!-- Use the camera device or record video.\n\n            <p>For apps with <code>targetSdkVersion</code> {@link android.os.Build.VERSION_CODES#R}\n            and above, a foreground service will not be able to access the camera if this type is\n            not specified in the manifest and in\n            {@link android.app.Service#startForeground(int, android.app.Notification, int)}.\n            -->\n        <flag name=\"camera\" value=\"0x40\" />\n        <!--Use the microphone device or record audio.\n\n            <p>For apps with <code>targetSdkVersion</code> {@link android.os.Build.VERSION_CODES#R}\n            and above, a foreground service will not be able to access the microphone if this type\n            is not specified in the manifest and in\n            {@link android.app.Service#startForeground(int, android.app.Notification, int)}.\n            -->\n        <flag name=\"microphone\" value=\"0x80\" />\n    </attr>\n\n    <!-- Enable sampled memory bug detection in this process.\n         When enabled, a very small, random subset of native\n         memory allocations are protected with guard pages, providing an\n         ASan-like error report in case of a memory corruption bug.\n\n         GWP-ASan is a recursive acronym. It stands for “GWP-ASan Will Provide Allocation SANity”.\n         See the <a href=\"http://llvm.org/docs/GwpAsan.html\">LLVM documentation</a>\n         for more information about this feature.\n\n         <p>This attribute can be applied to a\n         {@link android.R.styleable#AndroidManifestProcess process} tag, or to an\n         {@link android.R.styleable#AndroidManifestApplication application} tag (to supply\n         a default setting for all application components). -->\n    <attr name=\"gwpAsanMode\">\n        <!-- Default behavior: GwpAsan is disabled in user apps, randomly enabled in system apps. -->\n        <enum name=\"default\" value=\"-1\" />\n        <!-- Never enable GwpAsan. -->\n        <enum name=\"never\" value=\"0\" />\n        <!-- Always enable GwpAsan. -->\n       <enum name=\"always\" value=\"1\" />\n    </attr>\n\n    <!-- Enable hardware memory tagging (ARM MTE) in this process.\n         When enabled, heap memory bugs like use-after-free and buffer overlow\n         are detected and result in an immediate (\"sync\" mode) or delayed (\"async\"\n         mode) crash instead of a silent memory corruption. Sync mode, while slower,\n         provides enhanced bug reports including stack traces at the time of allocation\n         and deallocation of memory, similar to AddressSanitizer.\n\n         See the <a href=\"https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/enhancing-memory-safety\">ARM announcement</a>\n         for more details.\n\n         <p>This attribute can be applied to a\n         {@link android.R.styleable#AndroidManifestProcess process} tag, or to an\n         {@link android.R.styleable#AndroidManifestApplication application} tag (to supply\n         a default setting for all application components). -->\n    <attr name=\"memtagMode\">\n       <enum name=\"default\" value=\"-1\" />\n       <enum name=\"off\" value=\"0\" />\n       <enum name=\"async\" value=\"1\" />\n       <enum name=\"sync\" value=\"2\" />\n    </attr>\n\n    <!-- Attribution tag to be used for permission sub-attribution if a\n      permission is checked in  {@link android.content.Context#sendBroadcast(Intent, String)}.\n      Multiple tags can be specified separated by '|'.\n    -->\n    <attr name=\"attributionTags\" format=\"string\" />\n\n    <!-- The <code>manifest</code> tag is the root of an\n         <code>AndroidManifest.xml</code> file,\n         describing the contents of an Android package (.apk) file.  One\n         attribute must always be supplied: <code>package</code> gives a\n         unique name for the package, using a Java-style naming convention\n         to avoid name collisions.  For example, applications published\n         by Google could have names of the form\n         <code>com.google.app.<em>appname</em></code>\n\n         <p>Inside of the manifest tag, may appear the following tags\n         in any order: {@link #AndroidManifestAttribution attribution},\n         {@link #AndroidManifestPermission permission},\n         {@link #AndroidManifestPermissionGroup permission-group},\n         {@link #AndroidManifestPermissionTree permission-tree},\n         {@link #AndroidManifestUsesSdk uses-sdk},\n         {@link #AndroidManifestUsesPermission uses-permission},\n         {@link #AndroidManifestUsesConfiguration uses-configuration},\n         {@link #AndroidManifestApplication application},\n         {@link #AndroidManifestInstrumentation instrumentation},\n         {@link #AndroidManifestUsesFeature uses-feature}.  -->\n    <declare-styleable name=\"AndroidManifest\">\n        <attr name=\"versionCode\" />\n        <attr name=\"versionCodeMajor\" />\n        <attr name=\"versionName\" />\n        <attr name=\"revisionCode\" />\n        <attr name=\"sharedUserId\" />\n        <attr name=\"sharedUserLabel\" />\n        <attr name=\"installLocation\" />\n        <attr name=\"isolatedSplits\" />\n        <attr name=\"isFeatureSplit\" />\n        <attr name=\"targetSandboxVersion\" />\n        <attr name=\"compileSdkVersion\" />\n        <attr name=\"compileSdkVersionCodename\" />\n        <attr name=\"isSplitRequired\" />\n    </declare-styleable>\n\n    <!-- The <code>application</code> tag describes application-level components\n         contained in the package, as well as general application\n         attributes.  Many of the attributes you can supply here (such\n         as theme, label, icon, permission, process, taskAffinity,\n         and allowTaskReparenting) serve\n         as default values for the corresponding attributes of components\n         declared inside of the application.\n\n         <p>Inside of this element you specify what the application contains,\n         using the elements {@link #AndroidManifestProvider provider},\n         {@link #AndroidManifestService service},\n         {@link #AndroidManifestReceiver receiver},\n         {@link #AndroidManifestActivity activity},\n         {@link #AndroidManifestActivityAlias activity-alias},\n         {@link #AndroidManifestUsesLibrary uses-library},\n         {@link #AndroidManifestUsesStaticLibrary uses-static-library}, and\n         {@link #AndroidManifestUsesPackage uses-package}.\n         The application tag\n         appears as a child of the root {@link #AndroidManifest manifest} tag in\n         an application's manifest file. -->\n    <declare-styleable name=\"AndroidManifestApplication\" parent=\"AndroidManifest\">\n        <!-- The (optional) fully-qualified name for a subclass of\n             {@link android.app.Application} that the system instantiates before\n             any other class when an app's process starts. Most applications\n             don't need this attribute. If it's not specified, the system\n             instantiates the base Application class instead.-->\n        <attr name=\"name\" />\n        <attr name=\"theme\" />\n        <attr name=\"label\" />\n        <attr name=\"icon\" />\n        <attr name=\"roundIcon\" />\n        <attr name=\"banner\" />\n        <attr name=\"logo\" />\n        <attr name=\"description\" />\n        <attr name=\"permission\" />\n        <attr name=\"process\" />\n        <attr name=\"taskAffinity\" />\n        <attr name=\"allowTaskReparenting\" />\n        <!-- Indicate whether this application contains code.  If set to false,\n             there is no code associated with it and thus the system will not\n             try to load its code when launching components.  The default is true\n             for normal behavior. -->\n        <attr name=\"hasCode\" format=\"boolean\" />\n        <attr name=\"persistent\" />\n        <attr name=\"persistentWhenFeatureAvailable\" />\n        <attr name=\"requiredForAllUsers\" />\n        <!-- Specify whether the components in this application are enabled or not (that is, can be\n             instantiated by the system).\n             If \"false\", it overrides any component specific values (a value of \"true\" will not\n             override the component specific values). -->\n        <attr name=\"enabled\" />\n        <attr name=\"debuggable\" />\n        <attr name=\"vmSafeMode\" />\n        <attr name=\"hardwareAccelerated\" />\n        <!-- Name of activity to be launched for managing the application's space on the device. -->\n        <attr name=\"manageSpaceActivity\" />\n        <attr name=\"allowClearUserData\" />\n        <attr name=\"testOnly\" />\n        <attr name=\"backupAgent\" />\n        <attr name=\"allowBackup\" />\n        <attr name=\"fullBackupOnly\" />\n        <attr name=\"fullBackupContent\" />\n        <attr name=\"killAfterRestore\" />\n        <attr name=\"restoreNeedsApplication\" />\n        <attr name=\"restoreAnyVersion\" />\n        <attr name=\"backupInForeground\" />\n        <!-- Request that your application's processes be created with\n             a large Dalvik heap.  This applies to <em>all</em> processes\n             created for the application.  It only applies to the first\n             application loaded into a process; if using a sharedUserId\n             to allow multiple applications to use a process, they all must\n             use this option consistently or will get unpredictable results. -->\n        <attr name=\"largeHeap\" format=\"boolean\" />\n        <!-- Declare that this application can't participate in the normal\n             state save/restore mechanism.  Since it is not able to save and\n             restore its state on demand,\n             it can not participate in the normal activity lifecycle.  It will\n             not be killed while in the background; the user must explicitly\n             quit it.  Only one such app can be running at a time; if the user\n             tries to launch a second such app, they will be prompted\n             to quit the first before doing so.  While the\n             application is running, the user will be informed of this. -->\n        <attr name=\"cantSaveState\" format=\"boolean\" />\n        <attr name=\"uiOptions\" />\n        <!-- Declare that your application will be able to deal with RTL (right to left) layouts.\n             The default value is false. -->\n        <attr name=\"supportsRtl\" format=\"boolean\" />\n        <!-- Declare that this application requires access to restricted accounts of a certain\n             type. The default value is null and restricted accounts won\\'t be visible to this\n             application. The type should correspond to the account authenticator type, such as\n             \"com.google\". -->\n        <attr name=\"restrictedAccountType\" format=\"string\"/>\n        <!-- Declare that this application requires an account of a certain\n             type. The default value is null and indicates that the application can work without\n             any accounts. The type should correspond to the account authenticator type, such as\n             \"com.google\". -->\n        <attr name=\"requiredAccountType\" format=\"string\"/>\n        <!-- @deprecated replaced by setting appCategory attribute to \"game\" -->\n        <attr name=\"isGame\" />\n        <!-- Declare that this application may use cleartext traffic, such as HTTP rather than\n             HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP without STARTTLS or\n             TLS). Defaults to true. If set to false {@code false}, the application declares that it\n             does not intend to use cleartext network traffic, in which case platform components\n             (e.g. HTTP stacks, {@code DownloadManager}, {@code MediaPlayer}) will refuse\n             applications's requests to use cleartext traffic. Third-party libraries are encouraged\n             to honor this flag as well. -->\n        <attr name=\"usesCleartextTraffic\" />\n        <attr name=\"multiArch\" />\n        <attr name=\"useEmbeddedDex\" />\n        <attr name=\"extractNativeLibs\" />\n        <attr name=\"defaultToDeviceProtectedStorage\" format=\"boolean\" />\n        <attr name=\"directBootAware\" />\n        <attr name=\"resizeableActivity\" />\n        <attr name=\"maxAspectRatio\" />\n        <attr name=\"minAspectRatio\" />\n        <attr name=\"networkSecurityConfig\" />\n        <!-- Declare the category of this app. Categories are used to cluster multiple apps\n             together into meaningful groups, such as when summarizing battery, network, or\n             disk usage. Apps should only define this value when they fit well into one of\n             the specific categories. -->\n        <attr name=\"appCategory\">\n            <!-- Apps which are primarily games. -->\n            <enum name=\"game\" value=\"0\" />\n            <!-- Apps which primarily work with audio or music, such as music players. -->\n            <enum name=\"audio\" value=\"1\" />\n            <!-- Apps which primarily work with video or movies, such as streaming video apps. -->\n            <enum name=\"video\" value=\"2\" />\n            <!-- Apps which primarily work with images or photos, such as camera or gallery apps. -->\n            <enum name=\"image\" value=\"3\" />\n            <!-- Apps which are primarily social apps, such as messaging, communication, email, or social network apps. -->\n            <enum name=\"social\" value=\"4\" />\n            <!-- Apps which are primarily news apps, such as newspapers, magazines, or sports apps. -->\n            <enum name=\"news\" value=\"5\" />\n            <!-- Apps which are primarily maps apps, such as navigation apps. -->\n            <enum name=\"maps\" value=\"6\" />\n            <!-- Apps which are primarily productivity apps, such as cloud storage or workplace apps. -->\n            <enum name=\"productivity\" value=\"7\" />\n            <!-- Apps which are primarily accessibility apps, such as screen-readers. -->\n            <enum name=\"accessibility\" value=\"8\" />\n        </attr>\n\n        <!-- Declares the kind of classloader this application's classes must be loaded with -->\n        <attr name=\"classLoader\" />\n\n        <attr name=\"appComponentFactory\" />\n\n        <!-- Declares that this application should be invoked without non-SDK API enforcement -->\n        <attr name=\"usesNonSdkApi\" />\n\n        <!-- If {@code true} the user is prompted to keep the app's data on uninstall -->\n        <attr name=\"hasFragileUserData\" format=\"boolean\"/>\n\n        <attr name=\"zygotePreloadName\" />\n\n        <!-- If {@code true} the system will clear app's data if a restore operation fails.\n             This flag is turned on by default. <em>This attribute is usable only by system apps.\n             </em> -->\n        <attr name=\"allowClearUserDataOnFailedRestore\" format=\"boolean\"/>\n        <!-- If {@code true} the app's non sensitive audio can be captured by other apps with\n             {@link android.media.AudioPlaybackCaptureConfiguration} and a\n             {@link android.media.projection.MediaProjection}.\n\n             If {@code false} the audio played by the application will never be captured by non\n             system apps. It is equivalent to limiting\n             {@link android.media.AudioManager#setAllowedCapturePolicy(int)} to\n             {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM}.\n\n             <p>\n             Non sensitive audio is defined as audio whose {@code AttributeUsage} is\n             {@code USAGE_UNKNOWN}), {@code USAGE_MEDIA}) or {@code USAGE_GAME}).\n             All other usages like {@code USAGE_VOICE_COMMUNICATION} will not be captured.\n\n             <p>\n             The default value is:\n                 - {@code true} for apps with targetSdkVersion >= 29 (Q).\n                 - {@code false} for apps with targetSdkVersion < 29.\n\n             <p>\n             See {@link android.media.AudioPlaybackCaptureConfiguration} for more detail.\n             -->\n        <attr name=\"allowAudioPlaybackCapture\" format=\"boolean\" />\n        <!-- If {@code true} this app would like to run under the legacy storage\n             model. Note that this may not always be respected due to policy or\n             backwards compatibility reasons.\n\n             <p>Apps not requesting legacy storage can continue to discover and\n             read media belonging to other apps via {@code MediaStore}.\n             <p>\n             The default value is:\n                 - {@code false} for apps with targetSdkVersion >= 29 (Q).\n                 - {@code true} for apps with targetSdkVersion < 29.\n             -->\n        <attr name=\"requestLegacyExternalStorage\" format=\"boolean\" />\n\n        <!-- If {@code true} this app would like to preserve the legacy storage\n             model from a previously installed version. Note that this may not always be\n             respected due to policy or backwards compatibility reasons.\n\n             <p>This has no effect on the first install of an app on a device.\n             For an updating app, setting this to {@code true} will preserve the legacy behaviour\n             configured by the {@code requestLegacyExternalStorage} flag. If on an update, this\n             flag is set to {@code false} then the legacy access is not preserved, such an app can\n             only have legacy access with the {@code requestLegacyExternalStorage} flag.\n             <p>\n\n             The default value is {@code false}.\n             -->\n        <attr name=\"preserveLegacyExternalStorage\" format=\"boolean\" />\n\n        <!-- If {@code true} this app would like raw external storage access.\n\n        <p> This flag can only be used by apps holding\n        <ul>\n        <li>{@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission or\n        <li>{@link android.app.role}#SYSTEM_GALLERY role.\n        </ul>\n        <p> When the flag is set, all file path access on external storage will bypass database\n        operations that update MediaStore collection. Raw external storage access as a side effect\n        can improve performance of bulk file path operations but can cause unexpected behavior in\n        apps due to inconsistencies in MediaStore collection and lower file system.\n        When the flag is set, app should scan the file after file path operations to ensure\n        consistency of MediaStore collection.\n        <p> The flag can be set to false if the app doesn't do many bulk file path operations or if\n        app prefers the system to ensure the consistency of the MediaStore collection for file path\n        operations without scanning the file.\n\n        <p> The default value is {@code true} if\n        <ul>\n        <li>app has {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission and\n        targets targetSDK<=30.\n        <li>app has {@link android.app.role}#SYSTEM_GALLERY role and targetSDK<=29\n        </ul>\n        {@code false} otherwise.\n        -->\n        <attr name=\"requestRawExternalStorageAccess\" format=\"boolean\" />\n\n        <!-- If {@code true} this app declares that it should be visible to all other apps on\n             device, regardless of what they declare via the {@code queries} tags in their\n             manifest.\n\n             The default value is {@code false}. -->\n        <attr name=\"forceQueryable\" format=\"boolean\" />\n\n        <!-- If {@code true} indicates that this application is capable of presenting a unified\n             interface representing multiple profiles.\n\n             The default value is {@code false}. -->\n        <attr name=\"crossProfile\" format=\"boolean\" />\n\n        <!-- If {@code true} this app will receive tagged pointers to native heap allocations\n             from functions like malloc() on compatible devices. Note that this may not always\n             be respected due to policy or backwards compatibility reasons. See the\n             <a href=\"https://source.android.com/devices/tech/debug/tagged-pointers\">Tagged Pointers</a>\n             document for more information on this feature.\n\n             The default value is {@code true}. -->\n        <attr name=\"allowNativeHeapPointerTagging\" format=\"boolean\" />\n\n        <attr name=\"gwpAsanMode\" />\n\n        <attr name=\"memtagMode\" />\n\n        <!-- If {@code true} enables automatic zero initialization of all native heap\n             allocations. -->\n        <attr name=\"nativeHeapZeroInitialized\" format=\"boolean\" />\n\n        <!-- @hide no longer used, kept to preserve padding -->\n        <attr name=\"allowAutoRevokePermissionsExemption\" format=\"boolean\" />\n\n        <!-- No longer used. Declaring this does nothing -->\n        <attr name=\"autoRevokePermissions\">\n            <!-- No longer used -->\n            <enum name=\"allowed\" value=\"0\" />\n            <!-- No longer used -->\n            <enum name=\"discouraged\" value=\"1\" />\n            <!-- No longer used -->\n            <enum name=\"disallowed\" value=\"2\" />\n        </attr>\n\n        <!-- Declare the policy to deal with user data when rollback is committed. -->\n        <attr name=\"rollbackDataPolicy\">\n            <!-- User data will be restored during rollback. -->\n            <enum name=\"restore\" value=\"0\" />\n            <!-- User data will be wiped out during rollback. -->\n            <enum name=\"wipe\" value=\"1\" />\n            <!-- User data will remain unchanged during rollback. -->\n            <enum name=\"retain\" value=\"2\" />\n        </attr>\n\n        <!-- Applications can set this attribute to an xml resource within their app where they\n         specified the rules determining which files and directories can be copied from the device\n         as part of backup or transfer operations.\n\n         See the <a href=\"{@docRoot}about/versions/12/backup-restore\">Changes in backup and restore</a>\n         document for the format of the XML file.-->\n        <attr name=\"dataExtractionRules\" format=\"reference\"/>\n\n        <!-- @hide Request exemption from the foreground service restrictions introduced in S\n        (https://developer.android.com/about/versions/12/foreground-services)\n        Note the framework <b>ignores</b> this attribute at this time. Once apps target S or above,\n        there's no way to be exempted (without using a privileged permission).\n        -->\n        <attr name=\"requestForegroundServiceExemption\" format=\"boolean\" />\n\n        <!-- Whether attributions provided are meant to be user-visible. -->\n        <attr name=\"attributionsAreUserVisible\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- An attribution is a logical part of an app and is identified by a tag.\n    E.g. a photo sharing app might include a direct messaging component. To tag certain code as\n    belonging to an attribution, use a context created via\n    {@link android.content.Context#createAttributionContext(String)} for any interaction with the\n    system.\n\n    <p>This appears as a child tag of the root {@link #AndroidManifest manifest} tag.\n\n    <p>In case this attribution inherits from another attribution, this tag can contain one or\n    multiple {@link #AndroidManifestAttributionInheritFrom inherit-from} tags. -->\n    <declare-styleable name=\"AndroidManifestAttribution\" parent=\"AndroidManifest\">\n        <!-- Required identifier for a attribution. Can be passed to\n        {@link android.content.Context#createAttributionContext} to create a context tagged with\n        this attribution\n        -->\n        <attr name=\"tag\" format=\"string\" />\n        <!-- Required user visible label for a attribution. -->\n        <attr name=\"label\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- Declares previously declared attributions this attribution inherits from. -->\n    <declare-styleable name=\"AndroidManifestAttributionInheritFrom\"\n                       parent=\"AndroidManifestAttribution\">\n        <!-- Identifier of the attribution this attribution inherits from -->\n        <attr name=\"tag\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- The <code>permission</code> tag declares a security permission that can be\n         used to control access from other packages to specific components or\n         features in your package (or other packages).  See the\n         <a href=\"{@docRoot}guide/topics/security/security.html\">Security and Permissions</a>\n         document for more information on permissions.\n\n         <p>This appears as a child tag of the root\n         {@link #AndroidManifest manifest} tag. -->\n    <declare-styleable name=\"AndroidManifestPermission\" parent=\"AndroidManifest\">\n        <!-- Required public name of the permission, which other components and\n        packages will use when referring to this permission.  This is a string using\n        Java-style scoping to ensure it is unique.  The prefix will often\n        be the same as our overall package name, for example\n        \"com.mycompany.android.myapp.SomePermission\". -->\n        <attr name=\"name\" />\n        <attr name=\"label\" />\n        <attr name=\"icon\" />\n        <attr name=\"roundIcon\" />\n        <attr name=\"banner\" />\n        <attr name=\"logo\" />\n        <attr name=\"permissionGroup\" />\n        <attr name=\"backgroundPermission\" format=\"string\"/>\n        <attr name=\"description\" />\n        <attr name=\"request\" />\n        <attr name=\"protectionLevel\" />\n        <attr name=\"permissionFlags\" />\n        <attr name=\"knownCerts\" />\n    </declare-styleable>\n\n    <!-- The <code>permission-group</code> tag declares a logical grouping of\n         related permissions.\n\n         <p>Note that this tag does not declare a permission itself, only\n         a namespace in which further permissions can be placed.  See\n         the {@link #AndroidManifestPermission &lt;permission&gt;} tag for\n         more information.\n\n         <p>This appears as a child tag of the root\n         {@link #AndroidManifest manifest} tag. -->\n    <declare-styleable name=\"AndroidManifestPermissionGroup\" parent=\"AndroidManifest\">\n        <!-- Required public name of the permission group, permissions will use\n        to specify the group they are in.  This is a string using\n        Java-style scoping to ensure it is unique.  The prefix will often\n        be the same as our overall package name, for example\n        \"com.mycompany.android.myapp.SomePermission\". -->\n        <attr name=\"name\" />\n        <attr name=\"label\" />\n        <attr name=\"icon\" />\n        <attr name=\"roundIcon\" />\n        <attr name=\"banner\" />\n        <attr name=\"logo\" />\n        <attr name=\"description\" />\n        <attr name=\"request\" format=\"string\"/>\n        <attr name=\"requestDetail\" format=\"string\"/>\n        <attr name=\"backgroundRequest\" format=\"string\"/>\n        <attr name=\"backgroundRequestDetail\" format=\"string\"/>\n        <attr name=\"permissionGroupFlags\" />\n        <attr name=\"priority\" />\n    </declare-styleable>\n\n    <!-- The <code>permission-tree</code> tag declares the base of a tree of\n         permission values: it declares that this package has ownership of\n         the given permission name, as well as all names underneath it\n         (separated by '.').  This allows you to use the\n         {@link android.content.pm.PackageManager#addPermission\n         PackageManager.addPermission()} method to dynamically add new\n         permissions under this tree.\n\n         <p>Note that this tag does not declare a permission itself, only\n         a namespace in which further permissions can be placed.  See\n         the {@link #AndroidManifestPermission &lt;permission&gt;} tag for\n         more information.\n\n         <p>This appears as a child tag of the root\n         {@link #AndroidManifest manifest} tag. -->\n    <declare-styleable name=\"AndroidManifestPermissionTree\" parent=\"AndroidManifest\">\n        <!-- Required public name of the permission tree, which is the base name\n        of all permissions under it.  This is a string using\n        Java-style scoping to ensure it is unique.  The prefix will often\n        be the same as our overall package name, for example\n        \"com.mycompany.android.myapp.SomePermission\".  A permission tree name\n        must have more than two segments in its path; that is,\n        \"com.me.foo\" is okay, but not \"com.me\" or \"com\". -->\n        <attr name=\"name\" />\n        <attr name=\"label\" />\n        <attr name=\"icon\" />\n        <attr name=\"roundIcon\" />\n        <attr name=\"banner\" />\n        <attr name=\"logo\" />\n    </declare-styleable>\n\n    <!-- The <code>uses-permission</code> tag requests a\n         {@link #AndroidManifestPermission &lt;permission&gt;} that the containing\n         package must be granted in order for it to operate correctly. For runtime\n         permissions, i.e. ones with <code>dangerous</code> protection level, on a\n         platform that supports runtime permissions, the permission will not be\n         granted until the app explicitly requests it at runtime and the user approves\n         the grant. You cannot request at runtime permissions that are not declared\n         as used in the manifest. See the\n         <a href=\"{@docRoot}guide/topics/security/security.html\">Security and Permissions</a>\n         document for more information on permissions.  Also available is a\n         {@link android.Manifest.permission list of permissions} included\n         with the base platform.\n\n         <p>This appears as a child tag of the root\n         {@link #AndroidManifest manifest} tag. -->\n    <declare-styleable name=\"AndroidManifestUsesPermission\" parent=\"AndroidManifest\">\n        <!-- Required name of the permission you use, as published with the\n        corresponding name attribute of a\n        {@link android.R.styleable#AndroidManifestPermission &lt;permission&gt;}\n        tag; often this is one of the {@link android.Manifest.permission standard\n        system permissions}. -->\n        <attr name=\"name\" />\n        <!-- Optional: specify the maximum version of the Android OS for which the\n             application wishes to request the permission.  When running on a version\n             of Android higher than the number given here, the permission will not\n             be requested.  -->\n        <attr name=\"maxSdkVersion\" format=\"integer\" />\n        <!-- Optional: the system must support this feature for the permission to be\n        requested.  If it doesn't support the feature, it will be as if the manifest didn't\n        request it at all. -->\n        <attr name=\"requiredFeature\" format=\"string\" />\n        <!-- Optional: the system must NOT support this feature for the permission to be\n        requested.  If it does support the feature, it will be as if the manifest didn't\n        request it at all. -->\n        <attr name=\"requiredNotFeature\" format=\"string\" />\n        <!-- Optional: set of flags that should apply to this permission request. Note that\n             these flags start at 0x4 to match PackageInfo.requestedPermissionsFlags. -->\n        <attr name=\"usesPermissionFlags\">\n            <!-- Strong assertion by a developer that they will never use this\n                 permission to derive the physical location of the device, even\n                 when the app has been granted the ACCESS_FINE_LOCATION and/or\n                 ACCESS_COARSE_LOCATION permissions. -->\n            <flag name=\"neverForLocation\" value=\"0x00010000\" />\n        </attr>\n    </declare-styleable>\n\n    <!-- <code>required-feature</code> and <code>required-not-feature</code> elements inside\n         <code>uses-permission<code/> can be used to request the permission based on the fact\n         whether the system supports or does not support certain features.\n         If multiple <code>required-feature</code> and/or <code>required-not-feature</code> elements\n         are present, the permission will be “requested” only if the system supports all of the\n         listed \"required-features\" and does not support any of the \"required-not-features\".\n         -->\n    <declare-styleable name=\"AndroidManifestRequiredFeature\">\n        <!-- The name of the feature. -->\n        <attr name=\"name\" />\n    </declare-styleable>\n    <declare-styleable name=\"AndroidManifestRequiredNotFeature\">\n        <!-- The name of the feature. -->\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- The <code>uses-configuration</code> tag specifies\n         a specific hardware configuration value used by the application.\n         For example an application might specify that it requires\n         a physical keyboard or a particular navigation method like\n         trackball. Multiple such attribute values can be specified by the\n         application.\n\n         <p>This appears as a child tag of the root\n         {@link #AndroidManifest manifest} tag.\n\n         @deprecated Use <code>feature-group</code> instead.-->\n    <declare-styleable name=\"AndroidManifestUsesConfiguration\" parent=\"AndroidManifest\">\n        <!-- The type of touch screen used by an application. -->\n        <attr name=\"reqTouchScreen\" />\n        <attr name=\"reqKeyboardType\" />\n        <attr name=\"reqHardKeyboard\" />\n        <attr name=\"reqNavigation\" />\n        <attr name=\"reqFiveWayNav\" />\n    </declare-styleable>\n\n    <!-- The <code>uses-feature</code> tag specifies a specific device\n         hardware or software feature used by the application. For\n         example an application might specify that it requires\n         a camera. Multiple attribute values can be specified by the\n         application.\n\n         <p>This appears as a child tag of the root\n         {@link #AndroidManifest manifest} tag. -->\n    <declare-styleable name=\"AndroidManifestUsesFeature\" parent=\"AndroidManifest\">\n        <!-- The name of the feature that is being used. -->\n        <attr name=\"name\" />\n        <!-- The version of the feature that is being used. -->\n        <attr name=\"version\" format=\"integer\" />\n        <!-- The GLES driver version number needed by an application.\n             The higher 16 bits represent the major number and the lower 16 bits\n             represent the minor number. For example for GL 1.2 referring to\n             0x00000102, the actual value should be set as 0x00010002. -->\n        <attr name=\"glEsVersion\" format=\"integer\" />\n        <!--  Specify whether this feature is required for the application.\n              The default is true, meaning the application requires the\n              feature, and does not want to be installed on devices that\n              don't support it.  If you set this to false, then this will\n              not impose a restriction on where the application can be\n              installed. -->\n        <attr name=\"required\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- The <code>feature-group</code> tag specifies\n         a set of one or more <code>uses-feature</code> elements that\n         the application can utilize. An application uses multiple\n         <code>feature-group</code> sets to indicate that it can support\n         different combinations of features.\n\n         <p>This appears as a child tag of the root\n         {@link #AndroidManifest manifest} tag. -->\n    <declare-styleable name=\"AndroidManifestFeatureGroup\">\n        <!-- The human-readable name of the feature group. -->\n        <attr name=\"label\" />\n    </declare-styleable>\n\n    <!-- The <code>uses-sdk</code> tag describes the SDK features that the\n         containing package must be running on to operate correctly.\n\n         <p>This appears as a child tag of the root\n         {@link #AndroidManifest manifest} tag. -->\n    <declare-styleable name=\"AndroidManifestUsesSdk\" parent=\"AndroidManifest\">\n        <!-- This is the minimum SDK version number that the application\n             requires.  This number is an abstract integer, from the list\n             in {@link android.os.Build.VERSION_CODES}  If\n             not supplied, the application will work on any SDK.  This\n             may also be string (such as \"Donut\") if the application was built\n             against a development branch, in which case it will only work against\n             the development builds. -->\n        <attr name=\"minSdkVersion\" format=\"integer|string\" />\n        <!-- This is the SDK version number that the application is targeting.\n             It is able to run on older versions (down to minSdkVersion), but\n             was explicitly tested to work with the version specified here.\n             Specifying this version allows the platform to disable compatibility\n             code that are not required or enable newer features that are not\n             available to older applications.  This may also be a string\n             (such as \"Donut\") if this is built against a development\n             branch, in which case minSdkVersion is also forced to be that\n             string. -->\n        <attr name=\"targetSdkVersion\" format=\"integer|string\" />\n        <!-- This is the maximum SDK version number that an application works\n             on.  You can use this to ensure your application is filtered out\n             of later versions of the platform when you know you have\n             incompatibility with them. -->\n        <attr name=\"maxSdkVersion\" />\n    </declare-styleable>\n\n    <!-- The <code>extension-sdk</code> tag is a child of the <uses-sdk> tag,\n         and specifies required extension sdk features. -->\n    <declare-styleable name=\"AndroidManifestExtensionSdk\">\n        <!-- The extension SDK version that this tag refers to. -->\n        <attr name=\"sdkVersion\" format=\"integer\" />\n        <!-- The minimum version of the extension SDK this application requires.-->\n        <attr name=\"minExtensionVersion\" format=\"integer\" />\n    </declare-styleable>\n\n    <!-- The <code>library</code> tag declares that this apk is providing itself\n         as a shared library for other applications to use.  It can only be used\n         with apks that are built in to the system image.  Other apks can link to\n         it with the {@link #AndroidManifestUsesLibrary uses-library} tag.\n\n         <p>This appears as a child tag of the\n         {@link #AndroidManifestApplication application} tag. -->\n    <declare-styleable name=\"AndroidManifestLibrary\" parent=\"AndroidManifest\">\n        <!-- Required public name of the library, which other components and\n        packages will use when referring to this library.  This is a string using\n        Java-style scoping to ensure it is unique.  The name should typically\n        be the same as the apk's package name. -->\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"AndroidManifestQueries\" parent=\"AndroidManifest\" />\n    <declare-styleable name=\"AndroidManifestQueriesPackage\" parent=\"AndroidManifestQueries\">\n        <attr name=\"name\" />\n    </declare-styleable>\n    <declare-styleable name=\"AndroidManifestQueriesIntent\" parent=\"AndroidManifestQueries\" />\n    <declare-styleable name=\"AndroidManifestQueriesProvider\" parent=\"AndroidManifestQueries\" >\n        <attr name=\"authorities\" />\n    </declare-styleable>\n\n\n    <!-- The <code>static-library</code> tag declares that this apk is providing itself\n       as a static shared library for other applications to use. Any app can declare such\n       a library and there can be only one static shared library per package. These libraries\n       are updatable, multiple versions can be installed at the same time, and an app links\n       against a specific version simulating static linking while allowing code sharing.\n       Other apks can link to it with the {@link #AndroidManifestUsesLibrary uses-static-library}\n       tag.\n\n     <p>This appears as a child tag of the\n     {@link #AndroidManifestApplication application} tag. -->\n    <declare-styleable name=\"AndroidManifestStaticLibrary\" parent=\"AndroidManifestApplication\">\n        <!-- Required public name of the library, which other components and\n        packages will use when referring to this library.  This is a string using\n        Java-style scoping to ensure it is unique.  The name should typically\n        be the same as the apk's package name. -->\n        <attr name=\"name\" />\n        <!-- Required specific library version. -->\n        <attr name=\"version\" />\n        <!-- Required specific library major version code.  This matches\n             android:versionCodeMajor of the library. -->\n        <!-- Required specific library version. -->\n        <attr name=\"versionMajor\" format=\"integer\" />\n    </declare-styleable>\n\n    <!-- The <code>uses-libraries</code> specifies a shared library that this\n         package requires to be linked against.  Specifying this flag tells the\n         system to include this library's code in your class loader.\n\n         <p>This appears as a child tag of the\n         {@link #AndroidManifestApplication application} tag. -->\n    <declare-styleable name=\"AndroidManifestUsesLibrary\" parent=\"AndroidManifestApplication\">\n        <!-- Required name of the library you use. -->\n        <attr name=\"name\" />\n        <!--  Specify whether this library is required for the application.\n              The default is true, meaning the application requires the\n              library, and does not want to be installed on devices that\n              don't support it.  If you set this to false, then this will\n              allow the application to be installed even if the library\n              doesn't exist, and you will need to check for its presence\n              dynamically at runtime. -->\n        <attr name=\"required\" />\n    </declare-styleable>\n\n    <!-- The <code>uses-native-library</code> specifies a native shared library that this\n         package requires to be linked against.  Specifying this flag tells the\n         system to make the native library to be available to your app.\n\n         <p>On devices running R or lower, this is ignored and the app has access to all\n         the public native shared libraries that are exported from the platform. This is\n         also ignored if the app is targeting R or lower.\n\n         <p>This appears as a child tag of the\n         {@link #AndroidManifestApplication application} tag. -->\n    <declare-styleable name=\"AndroidManifestUsesNativeLibrary\" parent=\"AndroidManifestApplication\">\n        <!-- Required name of the library you use. -->\n        <attr name=\"name\" />\n        <!--  Specify whether this native library is required for the application.\n              The default is true, meaning the application requires the\n              library, and does not want to be installed on devices that\n              don't support it. If you set this to false, then this will\n              allow the application to be installed even if the library\n              doesn't exist, and you will need to check for its presence\n              dynamically at runtime. -->\n        <attr name=\"required\" />\n    </declare-styleable>\n\n    <!-- The <code>uses-static-library</code> specifies a shared <strong>static</strong>\n         library that this package requires to be statically linked against. Specifying\n         this tag tells the system to include this library's code in your class loader.\n         Depending on a static shared library is equivalent to statically linking with\n         the library at build time while it offers apps to share code defined in such\n         libraries. Hence, static libraries are strictly required.\n\n         <p>On devices running O MR1 or higher, if the library is singed with multiple\n         signing certificates you must to specify the SHA-256 hashes of the additional\n         certificates via adding\n         {@link #AndroidManifestAdditionalCertificate additional-certificate} tags.\n\n         <p>This appears as a child tag of the\n         {@link #AndroidManifestApplication application} tag. -->\n    <declare-styleable name=\"AndroidManifestUsesStaticLibrary\" parent=\"AndroidManifestApplication\">\n        <!-- Required name of the library you use. -->\n        <attr name=\"name\" />\n        <!-- Specify which version of the shared library should be statically linked. -->\n        <attr name=\"version\" />\n        <!-- The SHA-256 digest of the library signing certificate. -->\n        <attr name=\"certDigest\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- The <code>additional-certificate</code> specifies the SHA-256 digest of a static\n         shared library's additional signing certificate. You need to use this tag if the\n         library is singed with more than one certificate.\n\n         <p>This appears as a child tag of the\n         {@link #AndroidManifestUsesStaticLibrary uses-static-library} or\n         {@link #AndroidManifestUsesPackage uses-package} tag. -->\n    <declare-styleable name=\"AndroidManifestAdditionalCertificate\" parent=\"AndroidManifestUsesStaticLibrary\">\n        <!-- The SHA-256 digest of the library signing certificate. -->\n        <attr name=\"certDigest\" />\n    </declare-styleable>\n\n    <!-- The <code>uses-package</code> specifies some kind of dependency on another\n         package.  It does not have any impact on the app's execution on the device,\n         but provides information about dependencies it has on other packages that need\n         to  be satisfied for it to run correctly.  That is, this is primarily for\n         installers to know what other apps need to be installed along with this one.\n\n         <p>This appears as a child tag of the\n         {@link #AndroidManifestApplication application} tag. -->\n    <declare-styleable name=\"AndroidManifestUsesPackage\" parent=\"AndroidManifestApplication\">\n        <!-- Required type of association with the package, for example \"android.package.ad_service\"\n             if it provides an advertising service.  This should use the standard scoped naming\n             convention as used for other things such as package names, based on the Java naming\n             convention. -->\n        <attr name=\"packageType\" format=\"string\" />\n        <!-- Required name of the package you use. -->\n        <attr name=\"name\" />\n        <!-- Optional minimum version of the package that satisfies the dependency. -->\n        <attr name=\"version\" />\n        <!-- Optional minimum major version of the package that satisfies the dependency. -->\n        <attr name=\"versionMajor\" format=\"integer\" />\n        <!-- Optional SHA-256 digest of the package signing certificate. -->\n        <attr name=\"certDigest\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- The <code>supports-screens</code> specifies the screen dimensions an\n         application supports.  By default a modern application supports all\n         screen sizes and must explicitly disable certain screen sizes here;\n         older applications are assumed to only support the traditional normal\n         (HVGA) screen size.  Note that screen size is a separate axis from\n         density, and is determined as the available pixels to an application\n         after density scaling has been applied.\n\n         <p>This appears as a child tag of the\n         {@link #AndroidManifest manifest} tag. -->\n    <declare-styleable name=\"AndroidManifestSupportsScreens\" parent=\"AndroidManifest\">\n        <!-- Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB_MR2},\n             this is the new way to specify the minimum screen size an application is\n             compatible with.  This attribute provides the required minimum\n             \"smallest screen width\" (as per the -swNNNdp resource configuration)\n             that the application can run on.  For example, a typical phone\n             screen is 320, a 7\" tablet 600, and a 10\" tablet 720.  If the\n             smallest screen width of the device is below the value supplied here,\n             then the application is considered incompatible with that device.\n             If not supplied, then any old smallScreens, normalScreens, largeScreens,\n             or xlargeScreens attributes will be used instead. -->\n        <attr name=\"requiresSmallestWidthDp\" format=\"integer\" />\n        <!-- Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB_MR2},\n             this is the new way to specify the largest screens an application is\n             compatible with.  This attribute provides the maximum\n             \"smallest screen width\" (as per the -swNNNdp resource configuration)\n             that the application is designed for.  If this value is smaller than\n             the \"smallest screen width\" of the device it is running on, the user\n             is offered to run it in a compatibility mode that emulates a\n             smaller screen and zooms it to fit the screen. Currently the compatibility mode only\n             emulates phone screens with a 320dp width, so compatibility mode is not applied if the\n             value for compatibleWidthLimitDp is larger than 320. -->\n        <attr name=\"compatibleWidthLimitDp\" format=\"integer\" />\n        <!-- Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB_MR2},\n             this is the new way to specify the screens an application is\n             compatible with.  This attribute provides the maximum\n             \"smallest screen width\" (as per the -swNNNdp resource configuration)\n             that the application can work well on.  If this value is smaller than\n             the \"smallest screen width\" of the device it is running on, the\n             application will be forced in to screen compatibility mode with\n             no way for the user to turn it off. Currently the compatibility mode only\n             emulates phone screens with a 320dp width, so compatibility mode is not applied if the\n             value for largestWidthLimitDp is larger than 320. -->\n        <attr name=\"largestWidthLimitDp\" format=\"integer\" />\n        <!-- Indicates whether the application supports smaller screen form-factors.\n             A small screen is defined as one with a smaller aspect ratio than\n             the traditional HVGA screen; that is, for a portrait screen, less\n             tall than an HVGA screen.  In practice, this means a QVGA low\n             density or VGA high density screen.  An application that does\n             not support small screens <em>will not be available</em> for\n             small screen devices, since there is little the platform can do\n             to make such an application work on a smaller screen. -->\n        <attr name=\"smallScreens\" format=\"boolean\" />\n        <!-- Indicates whether an application supports the normal screen\n             form-factors.  Traditionally this is an HVGA normal density\n             screen, but WQVGA low density and WVGA high density are also\n             considered to be normal.  This attribute is true by default,\n             and applications currently should leave it that way. -->\n        <attr name=\"normalScreens\" format=\"boolean\" />\n        <!-- Indicates whether the application supports larger screen form-factors.\n             A large screen is defined as a screen that is significantly larger\n             than a normal phone screen, and thus may require some special care\n             on the application's part to make good use of it.  An example would\n             be a VGA <em>normal density</em> screen, though even larger screens\n             are certainly possible.  An application that does not support\n             large screens will be placed as a postage stamp on such a\n             screen, so that it retains the dimensions it was originally\n             designed for. -->\n        <attr name=\"largeScreens\" format=\"boolean\" />\n        <!-- Indicates whether the application supports extra large screen form-factors. -->\n        <attr name=\"xlargeScreens\" format=\"boolean\" />\n        <!-- Indicates whether the application can resize itself to newer\n             screen sizes.  This is mostly used to distinguish between old\n             applications that may not be compatible with newly introduced\n             screen sizes and newer applications that should be; it will be\n             set for you automatically based on whether you are targeting\n             a newer platform that supports more screens. -->\n        <attr name=\"resizeable\" format=\"boolean\" />\n        <!-- Indicates whether the application can accommodate any screen\n             density. This is assumed true if targetSdkVersion is 4 or higher.\n             @deprecated Should always be true by default and not overridden.\n              -->\n        <attr name=\"anyDensity\" format=\"boolean\" />\n    </declare-styleable>\n\n    <!-- Private tag to declare system protected broadcast actions.\n\n         <p>This appears as a child tag of the root\n         {@link #AndroidManifest manifest} tag. -->\n    <declare-styleable name=\"AndroidManifestProtectedBroadcast\" parent=\"AndroidManifest\">\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- Private tag to declare the original package name that this package is\n         based on.  Only used for packages installed in the system image.  If\n         given, and different than the actual package name, and the given\n         original package was previously installed on the device but the new\n         one was not, then the data for the old one will be renamed to be\n         for the new package.\n\n         <p>This appears as a child tag of the root\n         {@link #AndroidManifest manifest} tag. -->\n    <declare-styleable name=\"AndroidManifestOriginalPackage\" parent=\"AndroidManifest\">\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- The <code>processes</code> tag specifies the processes the application will run code in\n         and optionally characteristics of those processes.  This tag is optional; if not\n         specified, components will simply run in the processes they specify.  If supplied,\n         they can only specify processes that are enumerated here, and if they don't this\n         will be treated as a corrupt apk and result in an install failure.\n\n         <p>This appears as a child tag of the\n         {@link #AndroidManifestApplication application} tag. -->\n    <declare-styleable name=\"AndroidManifestProcesses\" parent=\"AndroidManifestApplication\">\n    </declare-styleable>\n\n    <!-- The <code>process</code> tag enumerates one of the available processes under its\n         containing <code>processes</code> tag.\n\n         <p>This appears as a child tag of the\n         {@link #AndroidManifestProcesses processes} tag. -->\n    <declare-styleable name=\"AndroidManifestProcess\" parent=\"AndroidManifestProcesses\">\n        <!-- Required name of the process that is allowed -->\n        <attr name=\"process\" />\n        <attr name=\"gwpAsanMode\" />\n        <attr name=\"memtagMode\" />\n        <attr name=\"nativeHeapZeroInitialized\" />\n    </declare-styleable>\n\n    <!-- The <code>deny-permission</code> tag specifies that a permission is to be denied\n         for a particular process (if specified under the\n         {@link #AndroidManifestProcess process} tag) or by default for all\n         processes {if specified under the\n         @link #AndroidManifestProcesses processes} tag).\n\n         <p>This appears as a child tag of the\n         {@link #AndroidManifestProcesses processes} and\n         {@link #AndroidManifestProcess process} tags. -->\n    <declare-styleable name=\"AndroidManifestDenyPermission\"\n            parent=\"AndroidManifestProcesses\">\n        <!-- Required name of the permission that is to be denied -->\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- The <code>allow-permission</code> tag specifies that a permission is to be allowed\n         for a particular process, when it was previously denied for all processes through\n         {@link #AndroidManifestDenyPermission deny-permission}\n\n         <p>This appears as a child tag of the\n         {@link #AndroidManifestProcesses processes} and\n         {@link #AndroidManifestProcess process} tags. -->\n    <declare-styleable name=\"AndroidManifestAllowPermission\"\n            parent=\"AndroidManifestProcesses\">\n        <!-- Required name of the permission that is to be allowed. -->\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- The <code>provider</code> tag declares a\n         {@link android.content.ContentProvider} class that is available\n         as part of the package's application components, supplying structured\n         access to data managed by the application.\n\n         <p>This appears as a child tag of the\n         {@link #AndroidManifestApplication application} tag. -->\n    <declare-styleable name=\"AndroidManifestProvider\" parent=\"AndroidManifestApplication\">\n        <!-- Required name of the class implementing the provider, deriving from\n            {@link android.content.ContentProvider}.  This is a fully\n            qualified class name (for example, com.mycompany.myapp.MyProvider); as a\n            short-hand if the first character of the class\n            is a period then it is appended to your package name. -->\n        <attr name=\"name\" />\n        <attr name=\"label\" />\n        <attr name=\"description\" />\n        <attr name=\"icon\" />\n        <attr name=\"roundIcon\" />\n        <attr name=\"banner\" />\n        <attr name=\"logo\" />\n        <attr name=\"process\" />\n        <attr name=\"authorities\" />\n        <attr name=\"syncable\" />\n        <attr name=\"readPermission\" />\n        <attr name=\"writePermission\" />\n        <attr name=\"grantUriPermissions\" />\n        <attr name=\"forceUriPermissions\" />\n        <attr name=\"permission\" />\n        <attr name=\"multiprocess\" />\n        <attr name=\"initOrder\" />\n        <!-- Specify whether this provider is enabled or not (that is, can be instantiated by the system).\n             It can also be specified for an application as a whole, in which case a value of \"false\"\n             will override any component specific values (a value of \"true\" will not override the\n             component specific values). -->\n        <attr name=\"enabled\" />\n        <attr name=\"exported\" />\n        <attr name=\"singleUser\" />\n        <attr name=\"directBootAware\" />\n        <attr name=\"visibleToInstantApps\" />\n        <!-- The code for this component is located in the given split.\n             <p>NOTE: This is only applicable to instant app. -->\n        <attr name=\"splitName\" />\n        <!-- Set of attribution tags that should be automatically applied to this component.\n             <p>\n             Each instance of this ContentProvider will be automatically configured with\n             Context.createAttributionContext() using the first attribution tag\n             contained here. -->\n        <attr name=\"attributionTags\" />\n    </declare-styleable>\n\n    <!-- Attributes that can be supplied in an AndroidManifest.xml\n         <code>grant-uri-permission</code> tag, a child of the\n         {@link #AndroidManifestProvider provider} tag, describing a specific\n         URI path that can be granted as a permission.  This tag can be\n         specified multiple time to supply multiple paths. If multiple\n         path matching attributes are supplied, they will be evaluated in the\n         following order with the first attribute being the only one honored:\n          <code>pathAdvancedPattern</code>, <code>pathPattern</code>,\n          <code>pathPrefix</code>, <code>pathSuffix</code>, <code>path</code>. -->\n    <declare-styleable name=\"AndroidManifestGrantUriPermission\"  parent=\"AndroidManifestProvider\">\n        <!-- Specify a URI path that must exactly match, as per\n             {@link android.os.PatternMatcher} with\n             {@link android.os.PatternMatcher#PATTERN_LITERAL}. -->\n        <attr name=\"path\" format=\"string\" />\n        <!-- Specify a URI path that must be a prefix to match, as per\n             {@link android.os.PatternMatcher} with\n             {@link android.os.PatternMatcher#PATTERN_PREFIX}. -->\n        <attr name=\"pathPrefix\" format=\"string\" />\n        <!-- Specify a URI path that matches a simple pattern, as per\n             {@link android.os.PatternMatcher} with\n             {@link android.os.PatternMatcher#PATTERN_SIMPLE_GLOB}.\n             Note that because '\\' is used as an escape character when\n             reading the string from XML (before it is parsed as a pattern),\n             you will need to double-escape: for example a literal \"*\" would\n             be written as \"\\\\*\" and a literal \"\\\" would be written as\n             \"\\\\\\\\\".  This is basically the same as what you would need to\n             write if constructing the string in Java code. -->\n        <attr name=\"pathPattern\" format=\"string\" />\n        <!-- Specify a URI path that matches an advanced pattern, as per\n             {@link android.os.PatternMatcher} with\n             {@link android.os.PatternMatcher#PATTERN_ADVANCED_GLOB}.\n             Note that because '\\' is used as an escape character when\n             reading the string from XML (before it is parsed as a pattern),\n             you will need to double-escape: for example a literal \"*\" would\n             be written as \"\\\\*\" and a literal \"\\\" would be written as\n             \"\\\\\\\\\".  This is basically the same as what you would need to\n             write if constructing the string in Java code. -->\n        <attr name=\"pathAdvancedPattern\" format=\"string\"/>\n        <!-- Specify a URI path that must be a suffix to match, as per\n             {@link android.os.PatternMatcher} with\n             {@link android.os.PatternMatcher#PATTERN_SUFFIX}. -->\n        <attr name=\"pathSuffix\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- Attributes that can be supplied in an AndroidManifest.xml\n         <code>path-permission</code> tag, a child of the\n         {@link #AndroidManifestProvider provider} tag, describing a permission\n         that allows access to a specific path in the provider.  This tag can be\n         specified multiple time to supply multiple paths. If multiple\n         path matching attributes are supplied, they will be evaluated in the\n         following order with the first attribute being the only one honored:\n          <code>pathAdvancedPattern</code>, <code>pathPattern</code>,\n          <code>pathPrefix</code>, <code>pathSuffix</code>, <code>path</code>.-->\n    <declare-styleable name=\"AndroidManifestPathPermission\"  parent=\"AndroidManifestProvider\">\n        <attr name=\"path\" />\n        <attr name=\"pathPrefix\" />\n        <attr name=\"pathPattern\" />\n        <attr name=\"pathAdvancedPattern\" format=\"string\"/>\n        <attr name=\"pathSuffix\" />\n        <attr name=\"permission\" />\n        <attr name=\"readPermission\" />\n        <attr name=\"writePermission\" />\n    </declare-styleable>\n\n    <!-- The <code>service</code> tag declares a\n         {@link android.app.Service} class that is available\n         as part of the package's application components, implementing\n         long-running background operations or a rich communication API\n         that can be called by other packages.\n\n         <p>Zero or more {@link #AndroidManifestIntentFilter intent-filter}\n         tags can be included inside of a service, to specify the Intents\n         that can connect with it.  If none are specified, the service can\n         only be accessed by direct specification of its class name.\n         The service tag appears as a child tag of the\n         {@link #AndroidManifestApplication application} tag. -->\n    <declare-styleable name=\"AndroidManifestService\" parent=\"AndroidManifestApplication\">\n        <!-- Required name of the class implementing the service, deriving from\n            {@link android.app.Service}.  This is a fully\n            qualified class name (for example, com.mycompany.myapp.MyService); as a\n            short-hand if the first character of the class\n            is a period then it is appended to your package name. -->\n        <attr name=\"name\" />\n        <attr name=\"label\" />\n        <attr name=\"description\" />\n        <attr name=\"icon\" />\n        <attr name=\"roundIcon\" />\n        <attr name=\"banner\" />\n        <attr name=\"logo\" />\n        <attr name=\"permission\" />\n        <attr name=\"process\" />\n        <!-- Specify whether the service is enabled or not (that is, can be instantiated by the system).\n             It can also be specified for an application as a whole, in which case a value of \"false\"\n             will override any component specific values (a value of \"true\" will not override the\n             component specific values). -->\n        <attr name=\"enabled\" />\n        <attr name=\"exported\" />\n        <!-- If set to true, this service with be automatically stopped\n             when the user remove a task rooted in an activity owned by\n             the application.  The default is false. -->\n        <attr name=\"stopWithTask\" format=\"boolean\" />\n        <!-- If set to true, this service will run under a special process\n             that is isolated from the rest of the system.  The only communication\n             with it is through the Service API (binding and starting). -->\n        <attr name=\"isolatedProcess\" format=\"boolean\" />\n        <attr name=\"singleUser\" />\n        <attr name=\"directBootAware\" />\n        <!-- If the service is an {@link android.R.attr#isolatedProcess} service, this permits a\n             client to bind to the service as if it were running it its own package.  The service\n             must also be {@link android.R.attr#exported} if this flag is set. -->\n        <attr name=\"externalService\" format=\"boolean\" />\n        <attr name=\"visibleToInstantApps\" />\n        <!-- The code for this component is located in the given split.\n             <p>NOTE: This is only applicable to instant app. -->\n        <attr name=\"splitName\" />\n        <!-- If true, and this is an {@link android.R.attr#isolatedProcess} service, the service\n             will be spawned from an Application Zygote, instead of the regular Zygote.\n             <p>\n             The Application Zygote will first pre-initialize the application's class loader. Then,\n             if the application has defined the {@link android.R.attr#zygotePreloadName} attribute,\n             the Application Zygote will call into that class to allow it to perform\n             application-specific preloads (such as loading a shared library). Therefore,\n             spawning from the Application Zygote will typically reduce the service\n             launch time and reduce its memory usage. The downside of using this flag\n             is that you will have an additional process (the app zygote itself) that\n             is taking up memory. Whether actual memory usage is improved therefore strongly\n             depends on the number of isolated services that an application starts,\n             and how much memory those services save by preloading and sharing memory with\n             the app zygote. Therefore, it is recommended to measure memory usage under\n             typical workloads to determine whether it makes sense to use this flag. -->\n        <attr name=\"useAppZygote\" format=\"boolean\" />\n        <!-- If this is a foreground service, specify its category. -->\n        <attr name=\"foregroundServiceType\" />\n        <!-- Set of attribution tags that should be automatically applied to this component.\n             <p>\n             Each instance of this Service will be automatically configured with\n             Context.createAttributionContext() using the first attribution tag\n             contained here. -->\n        <attr name=\"attributionTags\" />\n    </declare-styleable>\n\n    <!-- The <code>receiver</code> tag declares an\n         {@link android.content.BroadcastReceiver} class that is available\n         as part of the package's application components, allowing the\n         application to receive actions or data broadcast by other\n         applications even if it is not currently running.\n\n         <p>Zero or more {@link #AndroidManifestIntentFilter intent-filter}\n         tags can be included inside of a receiver, to specify the Intents\n         it will receive.  If none are specified, the receiver will only\n         be run when an Intent is broadcast that is directed at its specific\n         class name.  The receiver tag appears as a child tag of the\n         {@link #AndroidManifestApplication application} tag. -->\n    <declare-styleable name=\"AndroidManifestReceiver\" parent=\"AndroidManifestApplication\">\n        <!-- Required name of the class implementing the receiver, deriving from\n            {@link android.content.BroadcastReceiver}.  This is a fully\n            qualified class name (for example, com.mycompany.myapp.MyReceiver); as a\n            short-hand if the first character of the class\n            is a period then it is appended to your package name. -->\n        <attr name=\"name\" />\n        <attr name=\"label\" />\n        <attr name=\"description\" />\n        <attr name=\"icon\" />\n        <attr name=\"roundIcon\" />\n        <attr name=\"banner\" />\n        <attr name=\"logo\" />\n        <attr name=\"permission\" />\n        <attr name=\"process\" />\n        <!-- Specify whether the receiver is enabled or not (that is, can be instantiated by the system).\n             It can also be specified for an application as a whole, in which case a value of \"false\"\n             will override any component specific values (a value of \"true\" will not override the\n             component specific values). -->\n        <attr name=\"enabled\" />\n        <attr name=\"exported\" />\n        <attr name=\"singleUser\" />\n        <attr name=\"directBootAware\" />\n        <!-- Set of attribution tags that should be automatically applied to this component.\n             <p>\n             Each instance of this BroadcastReceiver will be automatically configured with\n             Context.createAttributionContext() using the first attribution tag\n             contained here. -->\n        <attr name=\"attributionTags\" />\n    </declare-styleable>\n\n    <!-- The <code>activity</code> tag declares an\n         {@link android.app.Activity} class that is available\n         as part of the package's application components, implementing\n         a part of the application's user interface.\n\n         <p>Zero or more {@link #AndroidManifestIntentFilter intent-filter}\n         tags can be included inside of an activity, to specify the Intents\n         that it can handle.  If none are specified, the activity can\n         only be started through direct specification of its class name.\n         The activity tag appears as a child tag of the\n         {@link #AndroidManifestApplication application} tag. -->\n    <declare-styleable name=\"AndroidManifestActivity\" parent=\"AndroidManifestApplication\">\n        <!-- Required name of the class implementing the activity, deriving from\n            {@link android.app.Activity}.  This is a fully\n            qualified class name (for example, com.mycompany.myapp.MyActivity); as a\n            short-hand if the first character of the class\n            is a period then it is appended to your package name. -->\n        <attr name=\"name\" />\n        <attr name=\"theme\" />\n        <attr name=\"label\" />\n        <attr name=\"description\" />\n        <attr name=\"icon\" />\n        <attr name=\"roundIcon\" />\n        <attr name=\"banner\" />\n        <attr name=\"logo\" />\n        <attr name=\"launchMode\" />\n        <attr name=\"screenOrientation\" />\n        <attr name=\"configChanges\" />\n        <attr name=\"recreateOnConfigChanges\" />\n        <attr name=\"permission\" />\n        <attr name=\"multiprocess\" />\n        <attr name=\"process\" />\n        <attr name=\"taskAffinity\" />\n        <attr name=\"allowTaskReparenting\" />\n        <attr name=\"finishOnTaskLaunch\" />\n        <attr name=\"finishOnCloseSystemDialogs\" />\n        <attr name=\"clearTaskOnLaunch\" />\n        <attr name=\"noHistory\" />\n        <attr name=\"alwaysRetainTaskState\" />\n        <attr name=\"stateNotNeeded\" />\n        <attr name=\"excludeFromRecents\" />\n        <!-- @deprecated use {@link android.R.attr#showForAllUsers} instead. -->\n        <attr name=\"showOnLockScreen\" />\n        <!-- Specify whether the activity is enabled or not (that is, can be instantiated by the system).\n             It can also be specified for an application as a whole, in which case a value of \"false\"\n             will override any component specific values (a value of \"true\" will not override the\n             component specific values). -->\n        <attr name=\"enabled\" />\n        <attr name=\"exported\" />\n        <!-- Specify the default soft-input mode for the main window of\n             this activity.  A value besides \"unspecified\" here overrides\n             any value in the theme. -->\n        <attr name=\"windowSoftInputMode\" />\n        <attr name=\"immersive\" />\n        <attr name=\"hardwareAccelerated\" />\n        <attr name=\"uiOptions\" />\n        <attr name=\"parentActivityName\" />\n        <attr name=\"singleUser\" />\n        <!-- @hide This broadcast receiver or activity will only receive broadcasts for the\n             system user-->\n        <attr name=\"systemUserOnly\" format=\"boolean\" />\n        <attr name=\"persistableMode\" />\n        <attr name=\"allowEmbedded\" />\n        <attr name=\"documentLaunchMode\" />\n        <attr name=\"maxRecents\" />\n        <attr name=\"autoRemoveFromRecents\" />\n        <attr name=\"relinquishTaskIdentity\" />\n        <attr name=\"resumeWhilePausing\" />\n        <attr name=\"resizeableActivity\" />\n        <attr name=\"supportsPictureInPicture\" />\n        <attr name=\"maxAspectRatio\" />\n        <attr name=\"minAspectRatio\" />\n        <attr name=\"lockTaskMode\" />\n        <attr name=\"showForAllUsers\" />\n\n        <attr name=\"showWhenLocked\" />\n        <attr name=\"inheritShowWhenLocked\" />\n        <attr name=\"turnScreenOn\" />\n\n        <attr name=\"directBootAware\" />\n        <!-- @hide This activity is always focusable regardless of if it is in a task/stack whose\n             activities are normally not focusable.\n             For example, {@link android.R.attr#supportsPictureInPicture} activities are placed\n             in a task/stack that isn't focusable. This flag allows them to be focusable.-->\n        <attr name=\"alwaysFocusable\" format=\"boolean\" />\n        <attr name=\"enableVrMode\" />\n        <attr name=\"rotationAnimation\" />\n        <attr name=\"visibleToInstantApps\" />\n        <!-- The code for this component is located in the given split. -->\n        <attr name=\"splitName\" />\n        <!-- Specify the color mode the activity desires. The requested color mode may be ignored\n             depending on the capabilities of the display the activity is displayed on. -->\n        <attr name=\"colorMode\">\n            <!-- The default color mode (typically sRGB, low-dynamic range). -->\n            <enum name=\"default\" value=\"0\" />\n            <!-- Wide color gamut color mode. -->\n            <enum name=\"wideColorGamut\" value=\"1\" />\n            <!-- High dynamic range color mode. -->\n            <enum name=\"hdr\" value=\"2\" />\n        </attr>\n        <attr name=\"forceQueryable\" format=\"boolean\" />\n        <!-- Indicates whether the activity wants the connected display to do minimal\n             post processing on the produced image or video frames. This will only be\n             requested if this activity's main window is visible on the screen.\n\n             <p> This setting should be used when low latency has a higher priority than\n             image enhancement processing (e.g. for games or video conferencing).\n\n             <p> If the Display sink is connected via HDMI, the device will begin to\n             send infoframes with Auto Low Latency Mode enabled and Game Content Type.\n             This will switch the connected display to a minimal image processing  mode\n             (if available), which reduces latency, improving the user experience for\n             gaming or video conferencing applications. For more information,\n             see HDMI 2.1 specification.\n\n             <p> If the Display sink has an internal connection or uses some other\n             protocol than HDMI, effects may be similar but implementation-defined.\n\n             <p> The ability to switch to a mode with minimal post proessing may be\n             disabled by a user setting in the system settings menu. In that case,\n             this field is ignored and the display will remain in its current\n             mode.\n\n             <p> See {@link android.content.pm.ActivityInfo#FLAG_PREFER_MINIMAL_POST_PROCESSING} -->\n        <attr name=\"preferMinimalPostProcessing\" format=\"boolean\"/>\n        <!-- Set of attribution tags that should be automatically applied to this component.\n             <p>\n             Each instance of this Activity will be automatically configured with\n             Context.createAttributionContext() using the first attribution tag\n             contained here. -->\n        <attr name=\"attributionTags\" />\n        <!-- Specifies whether a home sound effect should be played if the home app moves to\n             front after an activity with this flag set to <code>true</code>.\n             <p>The default value of this attribute is <code>true</code>.\n             <p>Also note that home sounds are only played if the device supports home sounds,\n             usually TVs.\n             <p>Requires permission {@code android.permission.DISABLE_SYSTEM_SOUND_EFFECTS}. -->\n        <attr name=\"playHomeTransitionSound\" format=\"boolean\"/>\n    </declare-styleable>\n\n    <!-- The <code>activity-alias</code> tag declares a new\n         name for an existing {@link #AndroidManifestActivity activity}\n         tag.\n\n         <p>Zero or more {@link #AndroidManifestIntentFilter intent-filter}\n         tags can be included inside of an activity-alias, to specify the Intents\n         that it can handle.  If none are specified, the activity can\n         only be started through direct specification of its class name.\n         The activity-alias tag appears as a child tag of the\n         {@link #AndroidManifestApplication application} tag. -->\n    <declare-styleable name=\"AndroidManifestActivityAlias\" parent=\"AndroidManifestApplication\">\n        <!-- Required name of the class implementing the activity, deriving from\n            {@link android.app.Activity}.  This is a fully\n            qualified class name (for example, com.mycompany.myapp.MyActivity); as a\n            short-hand if the first character of the class\n            is a period then it is appended to your package name. -->\n        <attr name=\"name\" />\n        <!-- The name of the activity this alias should launch.  The activity\n             must be in the same manifest as the alias, and have been defined\n             in that manifest before the alias here.  This must use a Java-style\n             naming convention to ensure the name is unique, for example\n             \"com.mycompany.MyName\". -->\n        <attr name=\"targetActivity\" format=\"string\" />\n        <attr name=\"label\" />\n        <attr name=\"description\" />\n        <attr name=\"icon\" />\n        <attr name=\"roundIcon\" />\n        <attr name=\"banner\" />\n        <attr name=\"logo\" />\n        <attr name=\"permission\" />\n        <!-- Specify whether the activity-alias is enabled or not (that is, can be instantiated by the system).\n             It can also be specified for an application as a whole, in which case a value of \"false\"\n             will override any component specific values (a value of \"true\" will not override the\n             component specific values). -->\n        <attr name=\"enabled\" />\n        <attr name=\"exported\" />\n        <attr name=\"parentActivityName\" />\n        <attr name=\"attributionTags\" />\n    </declare-styleable>\n\n    <!-- The <code>meta-data</code> tag is used to attach additional\n         arbitrary data to an application component.  The data can later\n         be retrieved programmatically from the\n         {@link android.content.pm.ComponentInfo#metaData\n         ComponentInfo.metaData} field.  There is no meaning given to this\n         data by the system.  You may supply the data through either the\n         <code>value</code> or <code>resource</code> attribute; if both\n         are given, then <code>resource</code> will be used.\n\n         <p>It is highly recommended that you avoid supplying related data as\n         multiple separate meta-data entries.  Instead, if you have complex\n         data to associate with a component, then use the <code>resource</code>\n         attribute to assign an XML resource that the client can parse to\n         retrieve the complete data. -->\n    <declare-styleable name=\"AndroidManifestMetaData\"\n         parent=\"AndroidManifestApplication\n                 AndroidManifestActivity\n                 AndroidManifestReceiver\n                 AndroidManifestProvider\n                 AndroidManifestService\n                 AndroidManifestPermission\n                 AndroidManifestPermissionGroup\n                 AndroidManifestInstrumentation\">\n        <attr name=\"name\" />\n        <!-- Concrete value to assign to this piece of named meta-data.\n             The data can later be retrieved from the meta data Bundle\n             through {@link android.os.Bundle#getString Bundle.getString},\n             {@link android.os.Bundle#getInt Bundle.getInt},\n             {@link android.os.Bundle#getBoolean Bundle.getBoolean},\n             or {@link android.os.Bundle#getFloat Bundle.getFloat} depending\n             on the type used here. -->\n        <attr name=\"value\" format=\"string|integer|color|float|boolean\" />\n        <!-- Resource identifier to assign to this piece of named meta-data.\n             The resource identifier can later be retrieved from the meta data\n             Bundle through {@link android.os.Bundle#getInt Bundle.getInt}. -->\n        <attr name=\"resource\" format=\"reference\" />\n    </declare-styleable>\n\n    <!-- The <code>property</code> tag is used to attach additional data that can\n         be supplied to the parent component. A component element can contain any\n         number of <code>property</code> subelements. Valid names are any of the\n         <code>PROPERTY_</code> constants defined in the\n         {@link android.content.pm.PackageManager PackageManager} class. Values\n         are obtained using the appropriate method on the\n         {@link android.content.pm.PackageManager.Property PackageManager.Property} class.\n         <p>Ordinary values are specified through the value attribute. Resource IDs are\n         specified through the resource attribute.\n         <p>It is invalid to specify both a value and resource attributes. -->\n    <declare-styleable name=\"AndroidManifestProperty\"\n         parent=\"AndroidManifestApplication\n                 AndroidManifestActivity\n                 AndroidManifestReceiver\n                 AndroidManifestProvider\n                 AndroidManifestService\">\n        <attr name=\"name\" />\n        <!-- Concrete value to assign to this property.\n             The data can later be retrieved from the property object\n             through\n             {@link android.content.pm.PackageManager.Property#getString Property.getString},\n             {@link android.content.pm.PackageManager.Property#getInteger Property.getInteger},\n             {@link android.content.pm.PackageManager.Property#getBoolean Property.getBoolean},\n             or {@link android.content.pm.PackageManager.Property#getFloat Property.getFloat}\n             depending on the type used here. -->\n        <attr name=\"value\" />\n        <!-- The resource identifier to assign to this property.\n             The resource identifier can later be retrieved from the property object through\n             {@link android.content.pm.PackageManager.Property#getResourceId Property.getResourceId}. -->\n        <attr name=\"resource\" />\n    </declare-styleable>\n\n    <!-- The <code>intent-filter</code> tag is used to construct an\n         {@link android.content.IntentFilter} object that will be used\n         to determine which component can handle a particular\n         {@link android.content.Intent} that has been given to the system.\n         It can be used as a child of the\n         {@link #AndroidManifestActivity activity},\n         {@link #AndroidManifestReceiver receiver} and\n         {@link #AndroidManifestService service}\n         tags.\n\n         <p> Zero or more {@link #AndroidManifestAction action},\n         {@link #AndroidManifestCategory category}, and/or\n         {@link #AndroidManifestData data} tags should be\n         included inside to describe the contents of the filter.\n\n         <p> The optional label and icon attributes here are used with\n         an activity to supply an alternative description of that activity\n         when it is being started through an Intent matching this filter. -->\n    <declare-styleable name=\"AndroidManifestIntentFilter\"\n         parent=\"AndroidManifestActivity AndroidManifestReceiver AndroidManifestService\">\n        <attr name=\"label\" />\n        <attr name=\"icon\" />\n        <attr name=\"roundIcon\" />\n        <attr name=\"banner\" />\n        <attr name=\"logo\" />\n        <attr name=\"priority\" />\n        <attr name=\"autoVerify\" />\n        <!-- Within an application, multiple intent filters may match a particular\n             intent. This allows the app author to specify the order filters should\n             be considered. We don't want to use priority because that is global\n             across applications.\n             <p>Only use if you really need to forcibly set the order in which\n             filters are evaluated. It is preferred to target an activity with a\n             directed intent instead.\n             <p>The value is a single integer, with higher numbers considered to\n             be better. If not specified, the default order is 0. -->\n        <attr name=\"order\" />\n    </declare-styleable>\n\n    <!-- Attributes that can be supplied in an AndroidManifest.xml\n         <code>action</code> tag, a child of the\n         {@link #AndroidManifestIntentFilter intent-filter} tag.\n         See {@link android.content.IntentFilter#addAction} for\n         more information. -->\n    <declare-styleable name=\"AndroidManifestAction\" parent=\"AndroidManifestIntentFilter\">\n        <!-- The name of an action that is handled, using the Java-style\n             naming convention.  For example, to support\n             {@link android.content.Intent#ACTION_VIEW Intent.ACTION_VIEW}\n             you would put <code>android.intent.action.VIEW</code> here.\n             Custom actions should generally use a prefix matching the\n             package name. -->\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- Attributes that can be supplied in an AndroidManifest.xml\n         <code>data</code> tag, a child of the\n         {@link #AndroidManifestIntentFilter intent-filter} tag, describing\n         the types of data that match.  This tag can be specified multiple\n         times to supply multiple data options, as described in the\n         {@link android.content.IntentFilter} class.  Note that all such\n         tags are adding options to the same IntentFilter so that, for example,\n         <code>&lt;data android:scheme=\"myscheme\" android:host=\"me.com\" /&gt;</code>\n         is equivalent to <code>&lt;data android:scheme=\"myscheme\" /&gt;\n         &lt;data android:host=\"me.com\" /&gt;</code>. -->\n    <declare-styleable name=\"AndroidManifestData\" parent=\"AndroidManifestIntentFilter\">\n        <!-- Specify a MIME type that is handled, as per\n             {@link android.content.IntentFilter#addDataType\n             IntentFilter.addDataType()}.\n             <p><em>Note: MIME type matching in the Android framework is\n             case-sensitive, unlike formal RFC MIME types.  As a result,\n             MIME types here should always use lower case letters.</em></p> -->\n        <attr name=\"mimeType\" format=\"string\" />\n        <!-- Specify a group of MIME types that are handled. MIME types can be added and\n             removed to a package's MIME group via the PackageManager. -->\n        <attr name=\"mimeGroup\" format=\"string\" />\n        <!-- Specify a URI scheme that is handled, as per\n             {@link android.content.IntentFilter#addDataScheme\n             IntentFilter.addDataScheme()}.\n             <p><em>Note: scheme matching in the Android framework is\n             case-sensitive, unlike the formal RFC.  As a result,\n             schemes here should always use lower case letters.</em></p> -->\n        <attr name=\"scheme\" format=\"string\" />\n        <!-- Specify a URI scheme specific part that must exactly match, as per\n             {@link android.content.IntentFilter#addDataSchemeSpecificPart\n             IntentFilter.addDataSchemeSpecificPart()} with\n             {@link android.os.PatternMatcher#PATTERN_LITERAL}. -->\n        <attr name=\"ssp\" format=\"string\" />\n        <!-- Specify a URI scheme specific part that must be a prefix to match, as per\n             {@link android.content.IntentFilter#addDataSchemeSpecificPart\n             IntentFilter.addDataSchemeSpecificPart()} with\n             {@link android.os.PatternMatcher#PATTERN_PREFIX}. -->\n        <attr name=\"sspPrefix\" format=\"string\" />\n        <!-- Specify a URI scheme specific part that matches a simple pattern, as per\n             {@link android.content.IntentFilter#addDataSchemeSpecificPart\n             IntentFilter.addDataSchemeSpecificPart()} with\n             {@link android.os.PatternMatcher#PATTERN_SIMPLE_GLOB}.\n             Note that because '\\' is used as an escape character when\n             reading the string from XML (before it is parsed as a pattern),\n             you will need to double-escape: for example a literal \"*\" would\n             be written as \"\\\\*\" and a literal \"\\\" would be written as\n             \"\\\\\\\\\".  This is basically the same as what you would need to\n             write if constructing the string in Java code. -->\n        <attr name=\"sspPattern\" format=\"string\" />\n        <!-- Specify a URI scheme specific part that matches an advanced pattern, as per\n             {@link android.content.IntentFilter#addDataSchemeSpecificPart\n             IntentFilter.addDataSchemeSpecificPart()} with\n             {@link android.os.PatternMatcher#PATTERN_ADVANCED_GLOB}.\n             Note that because '\\' is used as an escape character when\n             reading the string from XML (before it is parsed as a pattern),\n             you will need to double-escape: for example a literal \"*\" would\n             be written as \"\\\\*\" and a literal \"\\\" would be written as\n             \"\\\\\\\\\".  This is basically the same as what you would need to\n             write if constructing the string in Java code. -->\n        <attr name=\"sspAdvancedPattern\" format=\"string\" />\n        <!-- Specify a URI scheme specific part that must be a suffix to match, as per\n             {@link android.content.IntentFilter#addDataSchemeSpecificPart\n             IntentFilter.addDataSchemeSpecificPart()} with\n             {@link android.os.PatternMatcher#PATTERN_SUFFIX}. -->\n        <attr name=\"sspSuffix\" format=\"string\" />\n        <!-- Specify a URI authority host that is handled, as per\n             {@link android.content.IntentFilter#addDataAuthority\n             IntentFilter.addDataAuthority()}.\n             <p><em>Note: host name matching in the Android framework is\n             case-sensitive, unlike the formal RFC.  As a result,\n             host names here should always use lower case letters.</em></p> -->\n        <attr name=\"host\" format=\"string\" />\n        <!-- Specify a URI authority port that is handled, as per\n             {@link android.content.IntentFilter#addDataAuthority\n             IntentFilter.addDataAuthority()}.  If a host is supplied\n             but not a port, any port is matched. -->\n        <attr name=\"port\" format=\"string\" />\n        <!-- Specify a URI path that must exactly match, as per\n             {@link android.content.IntentFilter#addDataPath\n             IntentFilter.addDataPath()} with\n             {@link android.os.PatternMatcher#PATTERN_LITERAL}. -->\n        <attr name=\"path\" />\n        <!-- Specify a URI path that must be a prefix to match, as per\n             {@link android.content.IntentFilter#addDataPath\n             IntentFilter.addDataPath()} with\n             {@link android.os.PatternMatcher#PATTERN_PREFIX}. -->\n        <attr name=\"pathPrefix\" />\n        <!-- Specify a URI path that matches a simple pattern, as per\n             {@link android.content.IntentFilter#addDataPath\n             IntentFilter.addDataPath()} with\n             {@link android.os.PatternMatcher#PATTERN_SIMPLE_GLOB}.\n             Note that because '\\' is used as an escape character when\n             reading the string from XML (before it is parsed as a pattern),\n             you will need to double-escape: for example a literal \"*\" would\n             be written as \"\\\\*\" and a literal \"\\\" would be written as\n             \"\\\\\\\\\".  This is basically the same as what you would need to\n             write if constructing the string in Java code. -->\n        <attr name=\"pathPattern\" />\n        <!-- Specify a URI path that matches an advanced pattern, as per\n             {@link android.content.IntentFilter#addDataPath\n             IntentFilter.addDataPath()} with\n             {@link android.os.PatternMatcher#PATTERN_ADVANCED_GLOB}.\n             Note that because '\\' is used as an escape character when\n             reading the string from XML (before it is parsed as a pattern),\n             you will need to double-escape: for example a literal \"*\" would\n             be written as \"\\\\*\" and a literal \"\\\" would be written as\n             \"\\\\\\\\\".  This is basically the same as what you would need to\n             write if constructing the string in Java code. -->\n        <attr name=\"pathAdvancedPattern\" />\n        <!-- Specify a URI path that must be a suffix to match, as per\n             {@link android.content.IntentFilter#addDataPath\n             IntentFilter.addDataPath()} with\n             {@link android.os.PatternMatcher#PATTERN_SUFFIX}. -->\n        <attr name=\"pathSuffix\" />\n    </declare-styleable>\n\n    <!-- Attributes that can be supplied in an AndroidManifest.xml\n         <code>category</code> tag, a child of the\n         {@link #AndroidManifestIntentFilter intent-filter} tag.\n         See {@link android.content.IntentFilter#addCategory} for\n         more information. -->\n    <declare-styleable name=\"AndroidManifestCategory\" parent=\"AndroidManifestIntentFilter\">\n        <!-- The name of category that is handled, using the Java-style\n             naming convention.  For example, to support\n             {@link android.content.Intent#CATEGORY_LAUNCHER Intent.CATEGORY_LAUNCHER}\n             you would put <code>android.intent.category.LAUNCHER</code> here.\n             Custom actions should generally use a prefix matching the\n             package name. -->\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- Attributes that can be supplied in an AndroidManifest.xml\n         <code>instrumentation</code> tag, a child of the root\n         {@link #AndroidManifest manifest} tag. -->\n    <declare-styleable name=\"AndroidManifestInstrumentation\" parent=\"AndroidManifest\">\n        <!-- Required name of the class implementing the instrumentation, deriving from\n            {@link android.app.Instrumentation}.  This is a fully\n            qualified class name (for example, com.mycompany.myapp.MyActivity); as a\n            short-hand if the first character of the class\n            is a period then it is appended to your package name. -->\n        <attr name=\"name\" />\n        <attr name=\"targetPackage\" />\n        <attr name=\"targetProcesses\" />\n        <attr name=\"label\" />\n        <attr name=\"icon\" />\n        <attr name=\"roundIcon\" />\n        <attr name=\"banner\" />\n        <attr name=\"logo\" />\n        <attr name=\"handleProfiling\" />\n        <attr name=\"functionalTest\" />\n    </declare-styleable>\n\n    <!-- Attributes that can be supplied in an AndroidManifest.xml\n         <code>screen</code> tag, a child of <code>compatible-screens</code>,\n         which is itself a child of the root\n         {@link #AndroidManifest manifest} tag. -->\n    <declare-styleable name=\"AndroidManifestCompatibleScreensScreen\"\n                       parent=\"AndroidManifest.AndroidManifestCompatibleScreens\">\n        <!-- Specifies a compatible screen size, as per the device\n             configuration screen size bins. -->\n        <attr name=\"screenSize\">\n            <!-- A small screen configuration, at least 240x320dp. -->\n            <enum name=\"small\" value=\"200\" />\n            <!-- A normal screen configuration, at least 320x480dp. -->\n            <enum name=\"normal\" value=\"300\" />\n            <!-- A large screen configuration, at least 400x530dp. -->\n            <enum name=\"large\" value=\"400\" />\n            <!-- An extra large screen configuration, at least 600x800dp. -->\n            <enum name=\"xlarge\" value=\"500\" />\n        </attr>\n        <!-- Specifies a compatible screen density, as per the device\n             configuration screen density bins. -->\n        <attr name=\"screenDensity\" format=\"integer\">\n            <!-- A low density screen, approximately 120dpi. -->\n            <enum name=\"ldpi\" value=\"120\" />\n            <!-- A medium density screen, approximately 160dpi. -->\n            <enum name=\"mdpi\" value=\"160\" />\n            <!-- A high density screen, approximately 240dpi. -->\n            <enum name=\"hdpi\" value=\"240\" />\n            <!-- An extra high density screen, approximately 320dpi. -->\n            <enum name=\"xhdpi\" value=\"320\" />\n            <!-- An extra extra high density screen, approximately 480dpi. -->\n            <enum name=\"xxhdpi\" value=\"480\" />\n            <!-- An extra extra extra high density screen, approximately 640dpi. -->\n            <enum name=\"xxxhdpi\" value=\"640\" />\n        </attr>\n    </declare-styleable>\n\n    <!-- The <code>input-type</code> tag is a child of the <code>supports-input</code> tag, which\n         is itself a child of the root {@link #AndroidManifest manifest} tag. Each\n         <code>input-type</code> tag specifices the name of a specific input device type. When\n         grouped with the other elements of the parent <code>supports-input</code> tag it defines\n         a collection of input devices, which when all used together, are considered a supported\n         input mechanism for the application. There may be multiple <code>supports-input</code>\n         tags defined, each containing a different combination of input device types. -->\n    <declare-styleable name=\"AndroidManifestSupportsInputInputType\"\n                       parent=\"AndroidManifest.AndroidManifestSupportsInput\">\n        <!-- Specifices the name of the input device type -->\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- The attribute that holds a Base64-encoded public key. -->\n    <attr name=\"publicKey\" format=\"string\" />\n\n    <!-- Attributes relating to a package verifier. -->\n    <declare-styleable name=\"AndroidManifestPackageVerifier\" parent=\"AndroidManifest\">\n        <!-- Specifies the Java-style package name that defines this\n             package verifier. -->\n        <attr name=\"name\" />\n\n        <!-- The Base64 encoded public key of the package verifier's\n             signature. -->\n        <attr name=\"publicKey\" />\n    </declare-styleable>\n\n    <!-- Attributes relating to resource overlay packages. -->\n    <declare-styleable name=\"AndroidManifestResourceOverlay\" parent=\"AndroidManifest\">\n        <!-- Package name of base package whose resources will be overlaid. -->\n        <attr name=\"targetPackage\" />\n\n        <!-- Category of the resource overlay. -->\n        <attr name=\"category\" format=\"string\"/>\n\n        <!-- Load order of overlay package. -->\n        <attr name=\"priority\" />\n\n        <!-- Whether the given RRO is static or not. -->\n        <attr name=\"isStatic\" format=\"boolean\" />\n\n        <!-- Required property name/value pair used to enable this overlay.\n             e.g. name=ro.oem.sku value=MKT210.\n             Overlay will be ignored unless system property exists and is\n             set to specified value -->\n        <!-- @hide This shouldn't be public. -->\n        <attr name=\"requiredSystemPropertyName\" format=\"string\" />\n        <!-- @hide This shouldn't be public. -->\n        <attr name=\"requiredSystemPropertyValue\" format=\"string\" />\n\n        <!-- The name of the overlayable whose resources will be overlaid. -->\n        <attr name=\"targetName\" />\n\n        <!-- The xml file that defines the target id to overlay value mappings. -->\n        <attr name=\"resourcesMap\" format=\"reference\" />\n    </declare-styleable>\n\n    <!-- Declaration of an {@link android.content.Intent} object in XML.  May\n         also include zero or more {@link #IntentCategory <category>} and\n         {@link #Extra <extra>} tags. -->\n    <declare-styleable name=\"Intent\">\n        <!-- The action name to assign to the Intent, as per\n            {@link android.content.Intent#setAction Intent.setAction()}. -->\n        <attr name=\"action\" format=\"string\" />\n        <!-- The data URI to assign to the Intent, as per\n            {@link android.content.Intent#setData Intent.setData()}.\n            <p><em>Note: scheme and host name matching in the Android framework is\n            case-sensitive, unlike the formal RFC.  As a result,\n            URIs here should always be normalized to use lower case letters\n            for these elements (as well as other proper Uri normalization).</em></p> -->\n        <attr name=\"data\" format=\"string\" />\n        <!-- The MIME type name to assign to the Intent, as per\n            {@link android.content.Intent#setType Intent.setType()}.\n            <p><em>Note: MIME type matching in the Android framework is\n            case-sensitive, unlike formal RFC MIME types.  As a result,\n            MIME types here should always use lower case letters.</em></p> -->\n        <attr name=\"mimeType\" />\n        <!-- The identifier to assign to the intent, as per\n            {@link android.content.Intent#setIdentifier Intent.setIdentifier()}. -->\n        <attr name=\"identifier\" format=\"string\" />\n        <!-- The package part of the ComponentName to assign to the Intent, as per\n            {@link android.content.Intent#setComponent Intent.setComponent()}. -->\n        <attr name=\"targetPackage\" />\n        <!-- The class part of the ComponentName to assign to the Intent, as per\n            {@link android.content.Intent#setComponent Intent.setComponent()}. -->\n        <attr name=\"targetClass\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- A category to add to an Intent, as per\n            {@link android.content.Intent#addCategory Intent.addCategory()}. -->\n    <declare-styleable name=\"IntentCategory\" parent=\"Intent\">\n        <!-- Required name of the category. -->\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- An extra data value to place into a an extra/name value pair held\n            in a Bundle, as per {@link android.os.Bundle}. -->\n    <declare-styleable name=\"Extra\" parent=\"Intent\">\n        <!-- Required name of the extra data. -->\n        <attr name=\"name\" />\n        <!-- Concrete value to put for this named extra data. -->\n        <attr name=\"value\" />\n    </declare-styleable>\n\n    <!-- Groups signing keys into a {@code KeySet} for easier reference in\n            other APIs. However, currently no APIs use this. -->\n    <attr name=\"keySet\" />\n    <declare-styleable name=\"AndroidManifestPublicKey\">\n        <attr name=\"name\" />\n        <attr name=\"value\" />\n    </declare-styleable>\n    <declare-styleable name=\"AndroidManifestKeySet\">\n        <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- Associate declared KeySets with upgrading capability. -->\n    <declare-styleable name=\"AndroidManifestUpgradeKeySet\" parent=\"AndroidManifest\">\n      <attr name=\"name\" />\n    </declare-styleable>\n\n    <!-- <code>layout</code> tag allows configuring the layout for the activity within multi-window\n         environment. -->\n    <declare-styleable name=\"AndroidManifestLayout\" parent=\"AndroidManifestActivity\">\n        <!-- Default width of the activity. Can be either a fixed value or fraction, in which case\n             the width will be constructed as a fraction of the total available width. -->\n        <attr name=\"defaultWidth\" format=\"dimension|fraction\" />\n        <!-- Default height of the activity. Can be either a fixed value or fraction, in which case\n             the height will be constructed as a fraction of the total available height. -->\n        <attr name=\"defaultHeight\" format=\"dimension|fraction\" />\n        <!-- Where to initially position the activity inside the available space. Uses constants\n             defined in {@link android.view.Gravity}. -->\n        <attr name=\"gravity\" />\n        <!-- Minimal width of the activity.\n\n         <p><strong>NOTE:</strong> A task's root activity value is applied to all additional\n         activities launched in the task. That is if the root activity of a task set minimal width,\n         then the system will set the same minimal width on all other activities in the task. It\n         will also ignore any other minimal width attributes of non-root activities. -->\n        <attr name=\"minWidth\" />\n        <!-- Minimal height of the activity.\n\n         <p><strong>NOTE:</strong> A task's root activity value is applied to all additional\n         activities launched in the task. That is if the root activity of a task set minimal height,\n         then the system will set the same minimal height on all other activities in the task. It\n         will also ignore any other minimal height attributes of non-root activities. -->\n        <attr name=\"minHeight\" />\n\n        <!-- Window layout affinity of this activity. Activities with the same window layout\n          affinity will share the same layout record. That is, if a user is opening an activity in\n          a new task on a display that can host freeform windows, and the user had opened a task\n          before and that task had a root activity who had the same window layout affinity, the\n          new task's window will be created in the same window mode and around the location which\n          the previously opened task was in.\n\n          <p>For example, if a user maximizes a task with root activity A and opens another\n          activity B that has the same window layout affinity as activity A has, activity B will\n          be created in fullscreen window mode. Similarly, if they move/resize a task with root\n          activity C and open another activity D that has the same window layout affinity as\n          activity C has, activity D will be in freeform window mode and as close to the position\n          of activity C as conditions permit. It doesn't require the user to keep the task with\n          activity A or activity C open. It won't, however, put any task into split-screen or PIP\n          window mode on launch.\n\n          <p>If the user is opening an activity with its window layout affinity for the first time,\n          the window mode and position is OEM defined.\n\n          <p>By default activity doesn't share any affinity with other activities. -->\n        <attr name=\"windowLayoutAffinity\" format=\"string\" />\n    </declare-styleable>\n\n    <!-- <code>restrict-update</code> tag restricts system apps from being updated unless the\n        SHA-512 hash equals the specified value.\n        @hide -->\n    <declare-styleable name=\"AndroidManifestRestrictUpdate\" parent=\"AndroidManifest\">\n        <!-- The SHA-512 hash of the only APK that can be used to update a package.\n             <p>NOTE: This is only applicable to system packages.\n             @hide -->\n        <attr name=\"hash\" format=\"string\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"AndroidManifestUsesSplit\" parent=\"AndroidManifest\">\n        <attr name=\"name\" format=\"string\" />\n    </declare-styleable>\n\n\n    <declare-styleable name=\"AndroidManifestProfileable\" parent=\"AndroidManifestApplication\">\n        <!-- Flag indicating whether the application can be profiled by the shell user,\n             even when running on a device that is running in user mode. -->\n        <attr name=\"shell\" format=\"boolean\" />\n        <!-- Flag indicating whether the application can be profiled by system services, but not\n             necessarily via shell tools (for which also android:shell=\"true\" must be set). If\n             false, the application cannot be profiled at all. Defaults to true. -->\n        <attr name=\"enabled\" format=\"boolean\" />\n    </declare-styleable>\n</resources>\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/TestBase.java",
    "content": "package software.coley.recaf.test;\n\nimport org.jboss.weld.proxy.WeldClientProxy;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.parallel.Isolated;\nimport software.coley.recaf.Bootstrap;\nimport software.coley.recaf.Recaf;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.util.TestEnvironment;\n\nimport java.lang.annotation.Annotation;\n\n/**\n * Common base for test classes using the recaf application and ensures child classes\n * are marked as test env via {@link TestEnvironment#isTestEnv()}.\n */\n@Isolated\npublic class TestBase {\n\tprotected static final Recaf recaf;\n\tprotected static WorkspaceManager workspaceManager;\n\n\tstatic {\n\t\tBootstrap.setWeldConsumer(w -> w.addPackage(true, TestConfigSetup.class));\n\t\trecaf = Bootstrap.get();\n\n\t\t// Trigger the test-default bean to load\n\t\trecaf.get(TestConfigSetup.class).configure();\n\t}\n\n\t@BeforeAll\n\tpublic static void setupWorkspaceManager() {\n\t\tTestEnvironment.initTestEnv();\n\n\t\t// We'll use this a lot so may as well grab it\n\t\tworkspaceManager = recaf.get(WorkspaceManager.class);\n\t\tworkspaceManager.setCurrent(null);\n\t}\n\n\t/**\n\t * CDI will wrap beans in proxies and update the backing value when appropriate.\n\t * If you want to verify that the initialized value has changed you cannot compare\n\t * the proxy instances, you must compare the values they delegate to.\n\t *\n\t * @param proxy\n\t * \t\tProxy, assumed to be from {@link Recaf#get(Class)} or {@link Recaf#get(Class, Annotation...)}.\n\t * @param <T>\n\t * \t\tBean type.\n\t *\n\t * @return Unwrapped value, without the proxy.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static <T> T unwrapProxy(T proxy) {\n\t\tif (proxy instanceof WeldClientProxy weldProxy) {\n\t\t\treturn (T) weldProxy.getMetadata().getContextualInstance();\n\t\t}\n\t\treturn proxy;\n\t}\n}"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/TestClassUtils.java",
    "content": "package software.coley.recaf.test;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.tree.ClassNode;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.BasicFileBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder;\n\nimport java.io.IOException;\nimport java.util.function.Consumer;\n\n/**\n * Various test utils for {@link Class} and {@link ClassInfo} usage.\n *\n * @author Matt Coley\n */\npublic class TestClassUtils {\n\t/**\n\t * @param c\n\t * \t\tClass ref.\n\t *\n\t * @return Info of class.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the class cannot be found at runtime.\n\t */\n\t@Nonnull\n\tpublic static JvmClassInfo fromRuntimeClass(@Nonnull Class<?> c) throws IOException {\n\t\treturn new JvmClassInfoBuilder(new ClassReader(c.getName())).build();\n\t}\n\n\t/**\n\t * @param classes\n\t * \t\tClasses to put into the bundle.\n\t *\n\t * @return Class bundle of classes.\n\t *\n\t * @throws IOException\n\t * \t\tWhen a class could not be found at runtime.\n\t */\n\t@Nonnull\n\tpublic static BasicJvmClassBundle fromClasses(Class<?>... classes) throws IOException {\n\t\tBasicJvmClassBundle bundle = new BasicJvmClassBundle();\n\t\tfor (Class<?> cls : classes)\n\t\t\tbundle.initialPut(fromRuntimeClass(cls));\n\t\treturn bundle;\n\t}\n\n\t/**\n\t * @param classes\n\t * \t\tClasses to put into the bundle.\n\t *\n\t * @return Class bundle of classes.\n\t */\n\t@Nonnull\n\tpublic static BasicJvmClassBundle fromClasses(JvmClassInfo... classes) {\n\t\tBasicJvmClassBundle bundle = new BasicJvmClassBundle();\n\t\tfor (JvmClassInfo cls : classes)\n\t\t\tbundle.initialPut(cls);\n\t\treturn bundle;\n\t}\n\n\t/**\n\t * @param files\n\t * \t\tFiles to put into the bundle.\n\t *\n\t * @return File bundle contianing the files.\n\t */\n\t@Nonnull\n\tpublic static BasicFileBundle fromFiles(FileInfo... files) {\n\t\tBasicFileBundle bundle = new BasicFileBundle();\n\t\tfor (FileInfo file : files)\n\t\t\tbundle.initialPut(file);\n\t\treturn bundle;\n\t}\n\n\t/**\n\t * @param classes\n\t * \t\tClasses to put into the workspace.\n\t *\n\t * @return Workspace containing classes in single resource.\n\t */\n\t@Nonnull\n\tpublic static Workspace fromBundle(@Nonnull JvmClassBundle classes) {\n\t\tWorkspaceResource resource = new WorkspaceResourceBuilder()\n\t\t\t\t.withJvmClassBundle(classes)\n\t\t\t\t.build();\n\t\treturn new BasicWorkspace(resource);\n\t}\n\n\t/**\n\t * @param files\n\t * \t\tFiles to put into the workspace.\n\t *\n\t * @return Workspace containing files in single resource.\n\t */\n\t@Nonnull\n\tpublic static Workspace fromBundle(@Nonnull FileBundle files) {\n\t\tWorkspaceResource resource = new WorkspaceResourceBuilder()\n\t\t\t\t.withFileBundle(files)\n\t\t\t\t.build();\n\t\treturn new BasicWorkspace(resource);\n\t}\n\n\t/**\n\t * @param classes\n\t * \t\tClasses to put into the workspace.\n\t * @param files\n\t * \t\tFiles to put into the workspace.\n\t *\n\t * @return Workspace containing classes and files in single resource.\n\t */\n\t@Nonnull\n\tpublic static Workspace fromBundles(@Nonnull JvmClassBundle classes, @Nonnull FileBundle files) {\n\t\tWorkspaceResource resource = new WorkspaceResourceBuilder()\n\t\t\t\t.withJvmClassBundle(classes)\n\t\t\t\t.withFileBundle(files)\n\t\t\t\t.build();\n\t\treturn new BasicWorkspace(resource);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName of class to create.\n\t *\n\t * @return Info of generated class.\n\t */\n\t@Nonnull\n\tpublic static JvmClassInfo createEmptyClass(@Nonnull String name) {\n\t\treturn createClass(name, null);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName of class to create.\n\t * @param consumer\n\t * \t\tOptional post-processing to add content to the generated class.\n\t *\n\t * @return Info of generated class.\n\t */\n\t@Nonnull\n\tpublic static JvmClassInfo createClass(@Nonnull String name, @Nullable Consumer<ClassNode> consumer) {\n\t\tClassWriter cw = new ClassWriter(0);\n\t\tClassNode node = new ClassNode();\n\t\tnode.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name, null, \"java/lang/Object\", null);\n\t\tif (consumer != null) consumer.accept(node);\n\t\tnode.accept(cw);\n\t\treturn new JvmClassInfoBuilder(cw.toByteArray()).build();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/TestConfigSetup.java",
    "content": "package software.coley.recaf.test;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.services.compile.JavacCompilerConfig;\n\n/**\n * Configures default values for the test environment. Some of our values that are best enabled by default\n * for users aren't the best for testing the 'base case' of things during test development.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class TestConfigSetup {\n\tprivate final JavacCompilerConfig javac;\n\n\t@Inject\n\tpublic TestConfigSetup(JavacCompilerConfig javac) {\n\t\tthis.javac = javac;\n\t}\n\t\n\tpublic void configure() {\n\t\t// Do not generate phantoms by default when using the compiler service\n\t\tjavac.getGeneratePhantoms().setValue(false);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/AccessibleFields.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class to test existence of fields with varing degrees of access.\n */\n@SuppressWarnings(\"all\")\npublic class AccessibleFields {\n\tpublic static final int CONSTANT_FIELD = 16;\n\tprivate final int privateFinalField = 8;\n\tprotected final int protectedField = 4;\n\tpublic final int publicField = 2;\n\tfinal int packageField = 1;\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tAccessibleFields other = (AccessibleFields) o;\n\n\t\tif (privateFinalField != other.privateFinalField) return false;\n\t\tif (protectedField != other.protectedField) return false;\n\t\tif (publicField != other.publicField) return false;\n\t\tif (packageField != other.packageField) return false;\n\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = privateFinalField;\n\t\tresult = 31 * result + protectedField;\n\t\tresult = 31 * result + publicField;\n\t\tresult = 31 * result + packageField;\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/AccessibleMethods.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class to test existence of methods with varing degrees of access.\n */\n@SuppressWarnings(\"all\")\npublic class AccessibleMethods {\n\tpublic void publicMethod() {\n\t}\n\n\tprivate void privateMethod() {\n\t}\n\n\tprotected void protectedMethod() {\n\t}\n\n\tvoid packageMethod() {\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/AccessibleMethodsChild.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class to test method overrides with varing degrees of access.\n */\n@SuppressWarnings(\"all\")\npublic class AccessibleMethodsChild extends AccessibleMethods{\n\t@Override\n\tpublic void publicMethod() {\n\t\tsuper.publicMethod();\n\t}\n\n\t@Override\n\tprotected void protectedMethod() {\n\t\tsuper.protectedMethod();\n\t}\n\n\t@Override\n\tvoid packageMethod() {\n\t\tsuper.packageMethod();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/AnnotationImpl.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Dummy annotation to test annotations with.\n */\n@SuppressWarnings(\"all\")\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})\npublic @interface AnnotationImpl {\n\tString value();\n\tRetention policy();\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/AnonymousLambda.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.util.function.Supplier;\n\n/**\n * Dummy class to test lambda replacing usage of anonymous classes are not broken in remapping.\n */\n@SuppressWarnings(\"all\")\npublic class AnonymousLambda {\n\tpublic static void main(String[] args) {\n\t\trun();\n\t}\n\n\tpublic static String run() {\n\t\tSupplier<String> supplierA = () -> \"One: \" + Supplier.class.getName();\n\t\tStringSupplier supplierB = () -> \"Two: \" + StringSupplier.class.getName();\n\n\t\tString a = supplierA.get();\n\t\tString b = supplierB.get();\n\t\tSystem.out.println(a);\n\t\tSystem.out.println(b);\n\t\treturn a + b;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ArrayTypeAnno.java",
    "content": "package software.coley.recaf.test.dummy;\n\npublic class ArrayTypeAnno {\n\tpublic static void foo(String[][]@TypeAnnotationImpl(\"the foo\") [][] foo) {\n\t\tSystem.out.println(foo.length);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithAnnotation.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n/**\n * Test for recursive annotation usage.\n */\n@AnnotationImpl(\n\t\tvalue = \"Hello\",\n\t\tpolicy = @Retention(\n\t\t\t\tvalue = RetentionPolicy.CLASS\n\t\t)\n)\n@SuppressWarnings(\"all\")\npublic class ClassWithAnnotation<@TypeAnnotationImpl(\"arg\") S> {\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithAnonymousInner.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.util.TimerTask;\n\n/**\n * Class to test basic inner-outer relation, for an anonymous class.\n */\n@SuppressWarnings(\"all\")\npublic class ClassWithAnonymousInner {\n\tvoid foo() {\n\t\t// Use timer-task so IntelliJ doesn't suggest lambda-izing a runnable\n\t\tnew Thread(new TimerTask() {\n\t\t\t@Override\n\t\t\tpublic void run() {\n\t\t\t\tSystem.out.println(\"Hello\");\n\t\t\t}\n\t\t}).start();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithConstructor.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class with some constructors.\n */\npublic class ClassWithConstructor {\n\tpublic ClassWithConstructor() {}\n\tpublic ClassWithConstructor(int i) {}\n\tpublic ClassWithConstructor(int i, int j) {}\n\tpublic ClassWithConstructor(DummyEnum dummyEnum, StringSupplier supplier) {}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithEmbeddedInners.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Class to test embedded inner-outer relation, with regular inner classes.\n */\n@SuppressWarnings(\"all\")\npublic class ClassWithEmbeddedInners {\n\tpublic class A {\n\t\tpublic class B {\n\t\t\tpublic class C {\n\t\t\t\tpublic class D {\n\t\t\t\t\tpublic class E {\n\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithExceptions.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class to test exceptions / catch blocks.\n */\n@SuppressWarnings(\"all\")\npublic class ClassWithExceptions {\n\tstatic int readInt(Object input) throws NumberFormatException {\n\t\ttry {\n\t\t\treturn Integer.parseInt(input.toString());\n\t\t} catch (NullPointerException ex) {\n\t\t\tthrow new NumberFormatException(\"input was null\");\n\t\t} catch (NumberFormatException ex) {\n\t\t\tthrow ex;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithFieldsAndMethods.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.io.PrintStream;\nimport java.util.List;\nimport java.util.UUID;\nimport java.util.function.Supplier;\n\n/**\n * Class with a variety of stuff in it.\n */\n@SuppressWarnings(\"all\")\npublic abstract class ClassWithFieldsAndMethods implements Supplier<Integer> {\n\tprivate static final int CONST_INT = 12345567;\n\n\tprivate final int finalInt;\n\n\tpublic ClassWithFieldsAndMethods(int finalInt) {this.finalInt = finalInt;}\n\n\tpublic void methodWithParameters(String foo, long wide, float decimal, double d, List<String> strings) {\n\t\t// no-op\n\t}\n\n\tpublic void methodWithLocalVariables() {\n\t\tPrintStream out = System.out;\n\t\tString message = UUID.randomUUID().toString();\n\t\tout.println(message);\n\t}\n\n\tpublic int plusTwo() {\n\t\treturn finalInt + 2;\n\t}\n\n\tprivate int minusOne() {\n\t\treturn finalInt - 1;\n\t}\n\n\tpublic static int getConstInt() {\n\t\treturn CONST_INT;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithInner.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Class to test basic inner-outer relation, for a regular inner class.\n */\n@SuppressWarnings(\"all\")\npublic class ClassWithInner {\n\tpublic class TheInner {\n\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithInnerAndMembers.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Class to test basic inner-outer relation, for a regular inner class with fields.\n */\n@SuppressWarnings(\"all\")\npublic class ClassWithInnerAndMembers {\n\tprivate int foo = 10;\n\n\tvoid outer() {\n\t\tSystem.out.println(foo);\n\t}\n\n\tvoid outerToInner(TheInner inner) {\n\t\tinner.inner();\n\t\tSystem.out.println(inner.bar);\n\t\tSystem.out.println(inner.strings.getFirst());\n\t}\n\n\tpublic class TheInner {\n\t\tprivate final List<String> strings = new ArrayList<>();\n\t\tprivate int bar = 10;\n\n\t\tvoid inner() {\n\t\t\tSystem.out.println(foo);\n\t\t\tSystem.out.println(bar);\n\t\t}\n\n\t\tvoid innerToOuter() {\n\t\t\touter();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithInvisAnnotation.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class with an 'invisible' annotation.\n * Meaning it is not visible at runtime but exists in the class.\n */\n@InvisAnnotationImpl\n@SuppressWarnings(\"all\")\npublic class ClassWithInvisAnnotation {\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithLambda.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.util.Collections;\nimport java.util.Map;\n\n/**\n * Class to test basic existence of lambdas with.\n */\n@SuppressWarnings(\"all\")\npublic class ClassWithLambda {\n\tstatic final Map<String, String> map = Collections.emptyMap();\n\n\tstatic void runnable() {\n\t\tnew Thread(() -> {\n\t\t\tSystem.out.println(\"foo\");\n\t\t});\n\t}\n\n\tstatic void consumer() {\n\t\tmap.values().stream().findFirst()\n\t\t\t\t.ifPresent(s -> System.out.println(s));\n\t}\n\n\tstatic void predicate() {\n\t\tmap.values().stream().findFirst()\n\t\t\t\t.filter(s -> s.isBlank());\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithMethodReference.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.util.function.Supplier;\n\npublic class ClassWithMethodReference {\n\tstatic {\n\t\tSupplier<String> fooSupplier = ClassWithMethodReference::foo;\n\n\t\tSystem.out.println(foo());\n\t\tSystem.out.println(fooSupplier.get());\n\t}\n\n\tstatic String foo() {\n\t\treturn \"foo\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithMultipleMethods.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class with multiple methods.\n */\npublic class ClassWithMultipleMethods {\n\tpublic static String append(String one, String two) {\n\t\treturn one + two;\n\t}\n\n\tpublic static String append(String one, String two, String three) {\n\t\treturn append(append(one, two), three);\n\t}\n\n\tpublic static int add(int a, int b) {\n\t\treturn a + b;\n\t}\n\n\tpublic static int add(int a, int b, int c) {\n\t\treturn add(add(a, b), c);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithRequiredConstructor.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.collections.delegate.DelegatingSortedSet;\n\nimport java.util.Arrays;\nimport java.util.SortedSet;\nimport java.util.TreeSet;\n\n/**\n * Dummy class which has a constructor it must implement from its parent type.\n */\npublic class ClassWithRequiredConstructor extends DelegatingSortedSet<Object> {\n\tpublic ClassWithRequiredConstructor(@Nonnull SortedSet<Object> delegate) {\n\t\tsuper(delegate);\n\t}\n\n\tpublic ClassWithRequiredConstructor(@Nonnull Object[] array) {\n\t\tsuper(new TreeSet<>(Arrays.asList(array)));\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithStaticInit.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class with a static initializer.\n */\npublic class ClassWithStaticInit {\n\tpublic static int i;\n\tstatic { i = 42; }\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/ClassWithToString.java",
    "content": "package software.coley.recaf.test.dummy;\n\npublic class ClassWithToString {\n\t@Override\n\tpublic String toString() {\n\t\treturn \"hello\";\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/DiamondA.java",
    "content": "package software.coley.recaf.test.dummy;\n\npublic interface DiamondA {\n\tvoid diamond();\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/DiamondB.java",
    "content": "package software.coley.recaf.test.dummy;\n\npublic class DiamondB {\n\tpublic void diamond() {}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/DiamondC.java",
    "content": "package software.coley.recaf.test.dummy;\n\npublic class DiamondC extends DiamondB implements DiamondA {\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/DummyEmptyMap.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class DummyEmptyMap<K, V> implements Map<K, V> {\n\n\t@Override\n\tpublic int size() {\n\t\treturn 0;\n\t}\n\n\t@Override\n\tpublic boolean isEmpty() {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic boolean containsKey(Object key) {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean containsValue(Object value) {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic V get(Object key) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic V put(K key, V value) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic V remove(Object key) {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void putAll(Map<? extends K, ? extends V> m) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void clear() {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic Set<K> keySet() {\n\t\treturn Collections.emptySet();\n\t}\n\n\t@Override\n\tpublic Collection<V> values() {\n\t\treturn Collections.emptySet();\n\t}\n\n\t@Override\n\tpublic Set<Entry<K, V>> entrySet() {\n\t\treturn Collections.emptySet();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/DummyEnum.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy enum for {@link DummyEnumPrinter}\n */\n@SuppressWarnings(\"all\")\npublic enum DummyEnum {\n\tONE,\n\tTWO,\n\tTHREE\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/DummyEnumPrinter.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class to show that enum's {@code $VALUES} field and {@code values()} methods are not remapped.\n */\n@SuppressWarnings(\"all\")\npublic class DummyEnumPrinter {\n\tpublic static void main(String[] args) {\n\t\trun1();\n\t\trun2();\n\t}\n\n\tpublic static String run1() {\n\t\tStringBuilder sb = new StringBuilder(DummyEnum.class.getName() + \":\");\n\t\tfor (DummyEnum enumConstant : DummyEnum.values()) {\n\t\t\tString name = enumConstant.name();\n\t\t\tSystem.out.println(name);\n\t\t\tsb.append(\"[\" + name + \"]\");\n\t\t\tDummyEnum valueOf = Enum.valueOf(DummyEnum.class, name);\n\t\t\tif (enumConstant != valueOf) {\n\t\t\t\tSystem.err.println(\"Mismatch: \" + name);\n\t\t\t}\n\t\t}\n\t\treturn sb.toString();\n\t}\n\n\tpublic static String run2() {\n\t\tStringBuilder sb = new StringBuilder(DummyEnum.class.getName() + \":\");\n\t\tfor (DummyEnum enumConstant : DummyEnum.class.getEnumConstants()) {\n\t\t\tString name = enumConstant.name();\n\t\t\tSystem.out.println(name);\n\t\t\tsb.append(\"[\" + name + \"]\");\n\t\t}\n\t\treturn sb.toString();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/DummyRecord.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport jakarta.annotation.Nonnull;\n\npublic record DummyRecord(int foo, @Nonnull String bar) {\n\t@Nonnull\n\tpublic String fooPlus(int other) {\n\t\treturn String.valueOf(foo + other);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/HelloWorld.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class with a main entry-point.\n */\n@SuppressWarnings(\"all\")\npublic class HelloWorld {\n\tpublic static void main(String[] args) {\n\t\tSystem.out.println(\"Hello world\");\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/Inheritance.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class with a inheritance examples.\n */\n@SuppressWarnings(\"all\")\npublic class Inheritance {\n\tpublic interface Edible {\n\t}\n\n\tpublic interface Red {\n\t}\n\n\tpublic class Apple implements Edible, Red {\n\t}\n\n\tpublic class AppleWithWorm extends Apple {\n\t}\n\n\tpublic class Grape implements Edible {\n\t}\n\n\tpublic class NotFoodException extends Exception {\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/InvisAnnotationImpl.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Dummy annotation to test invisible annotations with.\n */\n@SuppressWarnings(\"all\")\n@Retention(RetentionPolicy.CLASS)\n@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})\npublic @interface InvisAnnotationImpl {\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/MethodWithTypeAnno.java",
    "content": "package software.coley.recaf.test.dummy;\n\npublic class MethodWithTypeAnno {\n\tpublic static void foo(@TypeAnnotationImpl(\"the foo\") String foo) {\n\t\tfoo.toString();\n\t}\n\n\tpublic static void bar(int x, long j, @TypeAnnotationImpl(\"the bar\") String bar) {\n\t\tbar.toString();\n\t}\n\n\tpublic static void multi(@TypeAnnotationImpl(\"a\") int a, @TypeAnnotationImpl(\"b\") int b, @TypeAnnotationImpl(\"c\") int c) {\n\t\tMath.clamp(a, b, c);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/MultipleInterfacesClass.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.util.Comparator;\n\n/**\n * Dummy class to test for multiple interfaces on.\n */\n@SuppressWarnings(\"all\")\npublic class MultipleInterfacesClass implements AutoCloseable, Comparator<String> {\n\t@Override\n\tpublic void close() throws Exception {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic int compare(String o1, String o2) {\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/OverlapCaller.java",
    "content": "package software.coley.recaf.test.dummy;\n\n\n/**\n * Dummy class to test for renaming with overlapping definitions from interfaces.\n *\n * @see OverlapClassAB\n */\n@SuppressWarnings(\"all\")\npublic class OverlapCaller {\n\tpublic static void main(String[] args) {\n\t\trun();\n\t}\n\n\tpublic static void run() {\n\t\tObject object = new OverlapClassAB();\n\n\t\tOverlapInterfaceA a = (OverlapInterfaceA) object;\n\t\ta.methodA();\n\n\t\tOverlapInterfaceB b = (OverlapInterfaceB) object;\n\t\tb.methodA();\n\t\tb.methodB();\n\n\t\tOverlapClassAB ab = (OverlapClassAB) object;\n\t\tab.methodA();\n\t\tab.methodB();\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/OverlapClassAB.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class to test for renaming with overlapping definitions from interfaces.\n */\n@SuppressWarnings(\"all\")\npublic class OverlapClassAB implements OverlapInterfaceA, OverlapInterfaceB {\n\t@Override\n\tpublic void methodA() {\n\t\t// Both interfaces declare this\n\t}\n\n\t@Override\n\tpublic void methodB() {\n\t\t// Only B declares this\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/OverlapInterfaceA.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class to test for renaming with overlapping definitions from interfaces.\n *\n * @see OverlapClassAB\n */\n@SuppressWarnings(\"all\")\npublic interface OverlapInterfaceA {\n\tvoid methodA();\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/OverlapInterfaceB.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class to test for renaming with overlapping definitions from interfaces.\n *\n * @see OverlapClassAB\n */\n@SuppressWarnings(\"all\")\npublic interface OverlapInterfaceB {\n\tvoid methodA();\n\n\tvoid methodB();\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/SealedCircle.java",
    "content": "package software.coley.recaf.test.dummy;\n\npublic record SealedCircle(double radius) implements SealedShape {\n\t@Override\n\tpublic double area() {\n\t\treturn Math.PI * radius * radius;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/SealedOtherShape.java",
    "content": "package software.coley.recaf.test.dummy;\n\npublic abstract non-sealed class SealedOtherShape implements SealedShape {\n\t@Override\n\tpublic abstract double area();\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/SealedShape.java",
    "content": "package software.coley.recaf.test.dummy;\n\npublic sealed interface SealedShape permits SealedCircle, SealedOtherShape, SealedSquare {\n\tdouble area();\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/SealedSquare.java",
    "content": "package software.coley.recaf.test.dummy;\n\npublic record SealedSquare(double side) implements SealedShape {\n\t@Override\n\tpublic double area() {\n\t\treturn side * side;\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/StringConsumer.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.util.function.Consumer;\n\n/**\n * Class to test things pertaining to implementing methods from JDK classes <i>(as opposed to our own interfaces)</i>\n */\n@SuppressWarnings(\"all\")\npublic class StringConsumer implements Consumer<String> {\n\t@Override\n\tpublic void accept(String s) {\n\t\tSystem.out.println(\"Consumed: \" + s);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/StringConsumerUser.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Class to test interactions with a class that depends on another <i>(In the same package in this case)</i>.\n *\n * @see StringConsumer\n */\n@SuppressWarnings(\"all\")\npublic class StringConsumerUser {\n\tpublic static void main(String[] args) {\n\t\tfor (String arg : args) {\n\t\t\tnew StringConsumer().accept(arg);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/StringList.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\n\n/**\n * Dummy class that's a list of strings.\n */\npublic class StringList extends ArrayList<String> {\n\tpublic static StringList of(String... args) {\n\t\tStringList strings = new StringList();\n\t\tstrings.addAll(Arrays.asList(args));\n\t\treturn strings;\n\t}\n\n\tpublic Set<String> unique() {\n\t\treturn new LinkedHashSet<>(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/StringListUser.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class that uses {@link StringList}\n */\npublic class StringListUser {\n\tpublic static void main(String[] args) {\n\t\tStringList list = StringList.of(\"foo\");\n\t\tfor (String string : list.unique()) {\n\t\t\tSystem.out.println(string);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/StringSupplier.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.util.function.Supplier;\n\n/**\n * Dummy class for testing anonymous lambdas in remapping\n *\n * @see AnonymousLambda\n */\n@SuppressWarnings(\"all\")\npublic interface StringSupplier extends Supplier<String> {\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/TypeAnnotationImpl.java",
    "content": "package software.coley.recaf.test.dummy;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Dummy annotation to test type annotations with.\n */\n@SuppressWarnings(\"all\")\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.TYPE, ElementType.TYPE_PARAMETER, ElementType.TYPE_USE, ElementType.PARAMETER})\npublic @interface TypeAnnotationImpl {\n\tString value();\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/VariedModifierFields.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class to test existence of fields with varing modifiers.\n */\n@SuppressWarnings(\"all\")\npublic class VariedModifierFields {\n\tstatic int staticField = 0;\n\tvolatile int volatileField;\n\ttransient int transientField;\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/java/software/coley/recaf/test/dummy/VariedModifierMethods.java",
    "content": "package software.coley.recaf.test.dummy;\n\n/**\n * Dummy class to test existence of methods with varing modifiers.\n */\n@SuppressWarnings(\"all\")\npublic abstract class VariedModifierMethods {\n\tstatic void staticMethod(){}\n\tfinal void finalMethod(){}\n\tsynchronized void synchronizedMethod(){}\n\tnative void nativeMethod();\n\tabstract void abstractMethod();\n\tstrictfp void strictfpMethod(){} // As of Java 17, the 'strictfp' modifier is stripped in compilation\n\tvoid varargsMethod(String...varargs){}\n}\n"
  },
  {
    "path": "recaf-core/src/testFixtures/resources/lorem-long-ascii.txt",
    "content": "Lorem ipsum dolor sit amet. Ut modi accusantium et cumque tempore et dolorem consequatur vel aperiam laborum sit quam enim ea accusantium illum! Ab dolores commodi 33 omnis ipsa est quam maxime ab numquam optio non modi omnis in deleniti nesciunt et necessitatibus nihil. Ut deleniti quasi ut quia delectus quo fugit incidunt et beatae quia rem repellat dolorum id omnis inventore a rerum sapiente. Sit consequatur pariatur cum voluptatibus quia in voluptas velit nam magnam numquam ut commodi voluptatum. Id accusamus earum in magni molestiae vel odit facere. Ea fuga dolorem id sapiente possimus et galisum velit ut distinctio eveniet vel iure pariatur sit soluta numquam ut velit doloribus? Aut sunt quis id reprehenderit voluptas non minus porro qui ipsa officiis qui nesciunt architecto aut dolorem velit est vero illo. Quo placeat laboriosam est omnis voluptate id Quis accusantium ad quas voluptatem. Et voluptas possimus quo necessitatibus rerum vel voluptatibus enim. Et modi quasi et voluptas quibusdam et nulla omnis qui consequuntur ipsum? Non reprehenderit cumque aut animi deserunt sed nisi error! Et voluptatem dolorum et odio aperiam ea numquam quia ut consequatur fugit eos dolorem voluptatum qui dolor sunt At soluta molestiae. At aliquam deleniti ab temporibus laborum ea natus amet. Non enim necessitatibus qui corrupti repellendus eum aperiam perspiciatis.\n\nIn autem quam qui quaerat corrupti eum recusandae recusandae. Et numquam mollitia ab magni repellendus et nulla officiis non voluptatem iure ea eaque corrupti. Est magnam excepturi non corporis voluptates aut velit nemo sed sapiente Quis ut suscipit debitis ex sapiente accusamus? Et minus quia ut quidem aliquam aut corporis autem est doloremque obcaecati aut dolores laboriosam aut excepturi omnis. Qui illum voluptatem sit iusto eveniet sit corrupti odio aut impedit quia ut assumenda nesciunt est galisum sapiente. Sit animi officia qui iusto iusto ut eveniet esse? Ut placeat enim et culpa quam ad ratione facere qui quasi consequatur et suscipit dolor. Aut perferendis galisum ut quaerat voluptatum ex assumenda sint a molestiae fugiat. Et maiores quia quo impedit dolorum ut magni ratione non quam deserunt eos quia cumque! Eum tempore perspiciatis et praesentium quidem et nulla beatae ea doloribus nemo et sunt perspiciatis est aliquid fugiat? Aut ipsa voluptatem qui adipisci repudiandae ut omnis esse ea tempore laborum. Qui consequatur nulla et repudiandae voluptas et veritatis dolor. Ut consequuntur praesentium aut vero reprehenderit eum similique eveniet. Est officia molestias ut laborum fugit qui eligendi tempora!\n\nAut recusandae tempora est recusandae consequuntur et excepturi architecto est natus necessitatibus. Aut dolorem pariatur hic dolorum iusto et optio sequi ad odio modi et voluptatem esse ut magni maiores est quam sint. Rem quod temporibus non quia beatae aut exercitationem blanditiis ut rerum sequi qui labore accusamus vel omnis dolorem. Aut odit molestiae qui sunt necessitatibus et beatae ratione aut voluptatem esse. Et reprehenderit rerum ab incidunt iste qui quis internos aut voluptatibus laudantium. Id iure placeat ad quibusdam itaque ad voluptas possimus sed culpa optio. Sit amet necessitatibus eos autem dolor non omnis repellat qui laboriosam labore nam fuga omnis et inventore nisi. Sed sequi rerum et autem porro 33 quas deserunt sed aliquam enim est quae distinctio.\n\nAut itaque incidunt sed perspiciatis omnis nam nemo tenetur. Aut galisum ipsam sit facere quam eum quia dolores et voluptas laudantium ut vero doloribus qui velit beatae ut nisi velit. Et voluptatem officia qui eius natus ut doloremque minus rem consequatur sunt id soluta porro sit officia harum. Ut cupiditate ipsa ut vitae voluptate ut facilis fugiat et velit maiores aut modi consequatur. Vel beatae minima est dolore eligendi et omnis consequatur et error nostrum et nihil tenetur et Quis esse. Aut accusantium sint qui consectetur dignissimos qui nulla possimus et quidem esse. Hic dolor doloremque hic quis officia est voluptatem excepturi aut iusto blanditiis hic similique voluptatem. 33 temporibus neque et quae doloribus et quia accusamus vel tempora delectus? Aut neque illo non suscipit assumenda eum fugiat dolore qui corrupti recusandae. Ut placeat ducimus eos iusto ducimus sed nisi eveniet qui iure nihil. Est dolorem aspernatur et praesentium soluta et optio perferendis. Qui velit enim eos deserunt aliquid cum repudiandae rerum ut libero debitis sed minima voluptas ut sunt veniam. Eum earum libero ea doloremque sequi 33 deserunt omnis hic incidunt ipsum et illum nulla et officiis repellendus. Ex dolorem nisi ab quia neque ea commodi tempore.\n\nUt possimus illum quo ipsam porro est omnis reprehenderit 33 asperiores autem aut illo assumenda aut exercitationem recusandae ut esse neque! Et fugit amet eos maxime dolorem eum deserunt neque hic autem quibusdam aut dolorem magni hic architecto eligendi. Ea quam perspiciatis ab molestias soluta eos sint modi ut sunt adipisci. Hic iste voluptate eos perferendis omnis a dolor quod non quidem laborum et quia sequi hic sunt neque. Id sint voluptate qui quae dolorem ut earum eaque cum fugit quisquam in laudantium nihil. Sed voluptatem minima At quae voluptas ex recusandae consequatur aut exercitationem voluptate. Cum autem alias nam error alias ut reprehenderit dolores qui atque voluptatibus et asperiores consectetur ea recusandae commodi. Rem architecto dolores aut voluptate minima et reiciendis quisquam ut laborum quas sed autem harum et delectus voluptatibus. Eum reiciendis saepe non dignissimos recusandae sit error autem non provident magni. Ad aliquam voluptate et itaque vitae qui maiores illum ut quia error! Ut asperiores iure sit internos repellat ut doloremque cumque sit eligendi enim eum voluptatem quae 33 iure sint eum pariatur voluptates. Nam nihil provident eum modi cumque nam sunt voluptate 33 architecto placeat qui enim sunt est iste maiores. Ea obcaecati quia in saepe minima sit dolores dicta sit possimus fuga eum quos minima non doloribus dolorem. Cum consequatur rerum est reprehenderit illum ad nemo aperiam At iure quaerat quo amet autem.\n\nUt harum adipisci cum vitae tempore est quae doloremque qui quasi incidunt ea laborum libero eum delectus fugit qui animi nostrum. Aut enim natus sit corrupti debitis aut consequatur neque. Et voluptatibus maiores rem sapiente fugiat et blanditiis aliquam. Aut repellat molestiae aut omnis quos id omnis aliquid ut dicta architecto et sapiente dolorem sed repellat dolore vel rerum culpa. In nesciunt optio et Quis animi sit unde accusamus nam fugit explicabo in animi sunt. Et nihil sint ut omnis eligendi ut enim asperiores. Nam beatae porro sit fugit illo aut rerum libero et galisum repudiandae ut harum quas. Hic Quis reprehenderit eos temporibus quos ut ullam possimus ut eaque consequatur eum dolorem blanditiis in natus culpa sit reprehenderit obcaecati. Ut impedit rerum non laborum possimus non quisquam omnis id repellat dolor. Aut aliquam praesentium aut fugiat aperiam et necessitatibus nesciunt eos blanditiis ducimus vel alias inventore! Ad quia fugiat ad consequatur totam qui quis temporibus aut praesentium officiis? Est voluptatem culpa ut internos minima aut accusamus quisquam ea labore inventore quo eligendi voluptas. Qui autem veniam a molestiae dolorem cum dolor amet! Eos sapiente nulla qui vero magni At voluptatem dolorem.\n\nUt voluptas voluptatem et laudantium obcaecati cum galisum pariatur? Et harum autem ut debitis quis id ullam reiciendis. A iusto quis qui illum voluptatem vel quibusdam explicabo. Aut nesciunt repellat sit cumque omnis et voluptas eaque vel obcaecati soluta. Et vitae fuga hic eveniet veniam ut illum veritatis ut asperiores sunt non nihil quia et itaque commodi non voluptatem mollitia. Ex omnis omnis ut perferendis exercitationem rem consequatur voluptate cum saepe autem sit rerum accusantium eum optio labore in maiores architecto. Qui omnis voluptate aut odio consequatur hic ipsum sunt sit porro pariatur aut aliquam veniam qui maiores Quis aut amet doloremque! Quo galisum consectetur ut voluptates nostrum est quae quas aut saepe ratione sit sapiente cumque et optio dolor!\n\nEst rerum aspernatur et consequatur eius est deserunt quae qui internos ipsa est quod odit et minima deserunt. Vel quis earum quo quod unde id illum quam et dignissimos natus et natus tempore. Ut ullam assumenda qui incidunt deleniti ut voluptate nihil id distinctio quae in sapiente inventore et nemo quam. Ut vero voluptas cum quam tempore ex excepturi debitis et enim aliquam est rerum tempore ea atque deserunt! Ad optio nemo est illum vero et magni ipsam qui obcaecati libero ex dolores molestias? Id quas dolore sit perspiciatis tempore et fugiat dolor et nulla maiores hic deleniti eveniet sed dolorem explicabo. Aut Quis similique aut expedita rerum ut eligendi nulla sed velit repellendus non officiis corrupti vel similique reiciendis. Ut culpa enim rem iusto aliquid aut sapiente quaerat! Aut nisi praesentium cum quod placeat ab voluptatem repellat. Non saepe nihil ad modi perspiciatis vel odio voluptatem. Est vitae quia ea consequatur dolores et ratione velit est unde maxime est sequi atque aut fugit modi eum quae labore. Et maxime saepe hic sequi voluptatem nam dolores vero cum modi voluptate est laboriosam tempore. Qui velit aspernatur id vero quia sit aliquam cumque ex dolores dolore cum sunt illum. Qui quibusdam necessitatibus et doloribus accusamus et dolorem libero 33 necessitatibus autem?\n\nEt soluta molestias ut velit quidem ut quasi alias sit mollitia ipsa! Et similique rerum ea porro esse qui doloribus quaerat et voluptas asperiores et repudiandae Quis qui optio eius et reprehenderit fuga. Sed repellat sint ea possimus autem quo veniam odio cum assumenda eaque. Est labore veniam At dolor laboriosam aut numquam sequi. Aut nesciunt tempore quo harum earum ea porro deleniti cum sint inventore ut commodi nihil et veniam fuga. Sed voluptas laudantium qui maxime velit At molestiae blanditiis qui omnis labore. Qui obcaecati dolores eum mollitia laboriosam qui modi similique ea cupiditate dolores et voluptatem necessitatibus et voluptate autem. Eum expedita obcaecati et iste assumenda At provident quae ut quae inventore aut reiciendis repellendus aut fugiat ducimus! Non sint ipsa in enim expedita et internos ipsum id perspiciatis repudiandae sit quia odit? Ut quam sunt ut veniam internos nam ipsam vitae ea voluptatem quas ut quia laborum id animi galisum ut consequatur deleniti. Vel dolorem nobis eum fuga repellendus vel ducimus nulla qui omnis vitae sed incidunt mollitia eos mollitia facilis ut maiores architecto.\n\nSit iusto porro ut sapiente maiores ut omnis Quis At voluptate accusantium qui sint quidem ut delectus ipsum et explicabo iste? Sit quam exercitationem in corporis iste et veniam ullam. Et enim earum est dignissimos animi ad veniam ipsa At officia consequatur a praesentium porro et voluptas voluptas et quibusdam molestias. Et omnis vitae et fuga vitae cum libero atque qui ducimus consequuntur est quibusdam mollitia non dolores possimus. In consequatur laborum 33 Quis architecto aut dolor autem et explicabo autem. Non minima odit vel quasi Quis rem omnis modi. Sit culpa totam aut Quis molestiae et quibusdam velit eos libero iste sed numquam dolor non reprehenderit enim eum esse delectus. Est cupiditate natus sed odio officia et rerum animi sed quae natus. Sed rerum nihil aut fuga quos ut necessitatibus nemo est consectetur voluptatem qui aspernatur doloremque est esse omnis et repellat autem! Ab nihil quis sed minima galisum ab officia quis. Et provident quod eos tenetur amet ut delectus mollitia."
  },
  {
    "path": "recaf-core/src/testFixtures/resources/lorem-long-cn.txt",
    "content": "顾客非常高兴。控告者的举止，无论它可能导致什么时间和痛苦，或工作的开始，都可能适合那些控告他的人！从方便的痛苦中 33 所有她都尽可能地从不选择不以温和的方式进行的一切，他们一无所知，也不需要任何东西。可以说，他们是有福的，因为被选中的人远离了他们；他们是幸福的，因为每一个发现者都远离了事物的智者，所以他们是幸福的。让后果与快乐相匹配，因为他渴望快乐，因为他从不想要巨大的快乐作为优势。我们指责他们遇到了很大麻烦或不愿意这样做。逃离痛苦可能是明智的，高卢人希望这种区别发生，或者出生，永远不被释放，因为他想受苦，这是正确的吗？或者有些人批评它是为了快乐，就像那些想要承担建筑师的职责的人一样，他们不知道，或者痛苦对他来说是真实的。随心所欲地与谁在一起，一切快乐都是费力的。我们可以根据事物的需要或快乐来获得快乐。还有那些追随他的人的举止，好像有些人感到高兴，而所有的人却没有人高兴？他不会批评任何放弃自己思想的人，只会批评一个错误！我永远不会让他们接受痛苦和仇恨的快乐，因为结果他们会逃避快乐的痛苦，快乐是痛苦的，但通过不适来解决。但她生来就因困难时期而变得有些软弱。因为我不会让他满足那些腐败和排斥的人的需要。\n\n但提出请求的人以何种方式被败坏而拒绝拒绝。永远不要被伟人的温柔所排斥，也不要被任何官职所排斥，不要为这些人和那些腐败的人的权利而高兴。我们不应该接受肉体的快乐，或者说除了智者之外没有人愿意接受，这是一件伟大的事情。更不那么重要，因为确实有某个身体，而痛苦是盲目的，或者痛苦是费力的，或者所有人都会接受它们。那些只是快乐的人会碰巧被仇恨所腐蚀或受到阻碍，因为他们不知道如何假设这是一个智者。应该是灵魂的职责才刚刚发生吧？为了讨好而犯下过错，这似乎是受苦受难的结果。或者通过背负鸡翅去寻求应有的快乐，他可以逃避麻烦。而长辈们，因为是什么阻止他们受苦是一个伟大的理由，而不是因为他们来了才离开他们！如果你当时看到他并且在场，没有人受到这些痛苦的祝福，并且他们被看到，还有什么可以逃避的吗？或拒绝那些获得它的人的快乐，因此这一切都在困难时期。他不会因否认而感到快乐，也不会因真相而感到痛苦。作为现在的后果，或者说他确实会批评他，类似的事情就会发生。逃避选择合适时间的烦恼真是太麻烦了！\n\n或者说拒绝时代就是拒绝后果并接受建筑师是为需要而生的。或者让痛苦在这里由痛苦的规律性来承担，并选择遵循憎恨的方式和快乐，以至于它们比它们本身更伟大。事实上，时代并不是因为他们快乐或奉承奉承的事情而跟随我们指责劳动或所有的痛苦。要么他讨厌烦恼是必要的和快乐的理由，要么它们是快乐。他会斥责事物的发生，赞扬内在的事物或快乐。这可能会让某些人感到高兴，因此我们可能会感到高兴，但这是一种内疚的选择。但并不是所有的痛苦都会让那些被辛苦劳作所驱使的人排斥，每一次飞行和发现。但追随他们留下的东西和进一步的33，却有一定的区别。\n\n或者，因此，它们会倒下，但必须看到一切，因为没有人被抓住。否则，这应该是高卢人而不是他自己做的，因为那些赞美他的人的痛苦和快乐确实是由他愿意的痛苦所祝福的，就好像他不愿意一样。为了减少痛苦和磨难，他所承担的快乐义务是他必须进一步履行的义务。作为欲望本身，作为生活的乐趣，因此很容易逃避并想要实现更大或更好的事情。或者最不幸运的是选择的痛苦和所有的后果以及我们的错误，没有什么是束缚的，谁是束缚的。或者他们可能是追求最值得的人的指控者，我们不能也确实不存在。这里有痛苦和磨难，这里有谁能从责任中获得快乐，或者仅仅从奉承中获得快乐，在这里还有类似的快乐。 33次不，有什么痛苦，因为我们指责或选择时间？或者他并没有认为自己应该逃避拒绝腐败的痛苦。为了取悦他们，我们公正地引导他们，但除非事实证明没有什么是正确的。它是一种止痛药和当前的解决方案，也是一种忍受的选择。对于那些希望给他们留下一些东西的人，虽然他们是无偿的债务，但最少的快乐，我会原谅他们。 33 他们让他摆脱他们，跟随他们和他们的痛苦。来自痛苦，除了因为现在不方便。\n\n为了使我们能够抓住他所属的人，每个人都会批评他， 33 但更严厉的人必须要么被他接受，要么拒绝被运用。他逃离了他们最重要的痛苦，即离开他而不是这里的一些人，或者在这里选择一位伟大建筑师的痛苦。你看到自己从烦恼中解脱出来的方法就是接受它们本来的样子。在这里我\n\n他们带着这种快乐承受了所有的痛苦，这实际上并不是劳动，因为他们不是来追随的。让那些处于痛苦中的人感到快乐，因为他们本来就是这样，当任何人逃避一切而没有得到任何赞扬时。但最小的快乐。但当他在其他方面犯了错误时，他会以其他方式归咎于快乐带来的痛苦，以及拒绝快乐带来的更严厉的好处。问题的建筑师是痛苦或最小的快乐，并拒绝任何人作为劳动，除了这些和选择的快乐。拒绝他的人往往会被拒绝，但他们却没有预见到伟人的错误。为了一些快乐，也因此为了生命，那些比他年长的人，因为这是一个错误！为了使他在正义上更加严厉，他排斥内心，因此，无论选择他时可能有什么痛苦和磨难，正确的快乐33都应该由快乐来免除他。因为他们没有为他提供任何东西，因为他们追求享乐33，因为建筑师是最伟大的人。她之所以盲目，是因为人们常说，没有遭受最小痛苦的人能够逃避最小的痛苦。当事情的结果归咎于他时，我不会向任何人公开。\n\n为了在有生之年获得这些东西，可以说，正是这些东西所遭受的痛苦和磨难，才将他从我们灵魂的劳作中解放出来。因为他要么生来就负债累累，要么就不会成功。让他明智地避免更大的快乐和一些奉承。正如建筑师和智者所说，它要么驱除麻烦，要么驱除所有那些对它感兴趣的人，但它驱除痛苦或事物的错误。他们不知道选择，也不知道我们从何而来指责谁，因为我将解释逃避是在头脑中。而且他们并不是所有人都可以选择的，至于更粗糙的人。因为让他庆幸的是，他逃跑了，或者他拒绝了事物的自由和英勇，就像那些在这里，谁会批评那些尽我们所能以任何方式实现这一目标的时代，指责他被天生的错误中的奉承痛苦蒙蔽了双眼。所以我们可以预防一些并不困难的事情，但并不是每个人都能击退那种痛苦。要么我打开一些礼物，要么逃跑，他们不知道我们的需求，我们通过奉承引导他们或寻找其他人！为什么有人要逃避时代的后果或当前的责任？快乐是最不内在的，这是一种错误，或者我们指责任何人通过劳动来选择快乐来找到快乐。而那些从痛苦中走出来的烦恼时也会痛苦！除了巨大的快乐和痛苦之外，没有一个智者能够了解它们。\n\n当加利斯出生时，被欢乐和赞美者的喜悦蒙蔽了双眼？其中，你应该拒绝其中任何一个。一个会向某些人解释这种快乐的人。他们要么不知道它排斥一切和快乐，并且它是盲目的。在这里，生命的飞翔将被视为真理的飞翔，因为它们更加严酷，而不是毫无意义，因为因此舒适不是快乐，而是柔软。从一切事物中，每个人为了承受锻炼，都会愉快地获得事物，但往往对指责他的事物的选择是伟大建筑师的劳动。那些因所有快乐或仇恨而产生的人就在这里，让他们得到进一步的宽恕，或者对那些喜欢痛苦的年长者给予一些宽恕！我们怎样才能追求我们的快乐，这往往是明智的，而痛苦是一种选择！\n\n这是对事物的渴望，其结果就是抛弃内心所憎恨的东西，而且是最小的抛弃。或者他们中的哪一个出生，出生在什么时间，从什么地方，从哪里那个那个。任何人都应该假设，那些碰巧被软化为一种快乐的人与明智的发明家而不是其他人的区别无关。确实，你应该在什么时候享受它，确实，事情到了一定的时间，他们就会离开它！难道真的没有人可以选择他和这位没有痛苦和烦恼的伟大人物吗？这就是你随着时间的推移会看到的疼痛，疼痛会消失，这里不会有更大的缓解，但我会解释疼痛。或任何人，诸如此类，或权宜之计，因为没有选择，但会被排斥，而不是被腐败的办公室，或被拒绝等。对于错误应该寻求正义或明智的东西！或者，除非他在场，他喜欢什么，他就会排斥快乐。通常与洞察力或仇恨快乐无关。这就是生活，因为它会带来痛苦，而正是由于选择，遵循或逃离有效的方式才是最重要的。在这里，大多数时候都是为了追随快乐，因为在享受快乐的过程中，痛苦在当时是很费力的。他想要因此受到惩罚，因为当他们和他在一起时，会有某种痛苦中的痛苦。谁指责我们的某些需求和痛苦以及无痛的33个需求？\n\n而解决了他想要的麻烦之后，就仿佛恢复力本身就不一样了！那些类似的事情更进一步，那些寻求痛苦和快乐的人更严厉和被拒绝。但愿他们被击退；宽恕是很困难的，但痛苦是很困难的，或者永远不会随之而来。或者他们不知道这些东西什么时候被进一步软化，当它们被发现没有用处而逃走时。但最赞美他的人的快乐却是阿谀奉承"
  },
  {
    "path": "recaf-core/src/testFixtures/resources/lorem-long-ru.txt",
    "content": "Клиент очень доволен. Чтобы манеры обвинителей, и какое бы время и боль это ни повлекло за собой, или начало работ, были для тех, кто обвиняет его! Из соображений удобства 33 все, что она делает, насколько это возможно, никогда не имеет возможности, все в нежности ничего не знают и ни в чем не нуждаются. Они как бы блаженны, потому что избранный убегает от них, и счастливы, потому что дело скорбей отталкивается всяким нашедшим от мудреца вещей. Пусть последствия сочетаются с удовольствиями, потому что он желает удовольствий, ибо он никогда не хочет больших удовольствий как преимуществ. Мы обвиняем их в том, что у них большие проблемы или что мы ненавидим это делать. Этот полет боли может быть мудрым, и Галлик хочет, чтобы это различие произошло, или правильно ли родиться, никогда не освобождаясь, поскольку он хочет страдать? Или есть такие, которые критикуют его ради удовольствия не меньше, чем те, которые желают самих обязанностей архитектора, которые не знают, или боль истинна для него. С кем пожелает, тому всякое удовольствие трудоёмко. И мы можем получать удовольствие в соответствии с потребностями вещей или удовольствий. А манеры такие, будто некоторым доставляет удовольствие, а всем, кто следует за ним, нет удовольствия? Он не будет критиковать никого, кто откажется от своего ума, а только ошибку! И я никогда не открою их для удовольствия боли и ненависти, потому что, как следствие, они бегут от боли удовольствий, которые являются болью, но разрешаются дискомфортом. Но она родилась немного смягченной трудными временами. Ибо Я не открою его для нужд развращенных и отталкивающих.\n\nНо каким образом развратен тот, кто просит, чтобы отказаться отказать? И никогда не отталкиваться ни от кротости великих, ни от каких-либо должностей, не радоваться праву тех и тех, кто развращен. Прекрасно, что нам не следует принимать телесные удовольствия или что никто, кроме мудрых, не принимает их. И тем более, потому что действительно есть какое-то тело, и боль ослепляет, или боли трудны, или все примут их. Тот, кто является именно этим удовольствием, будет испорчен ненавистью или затруднен, потому что он не знает, как предположить, что это мудрый человек. Должны ли быть обязанности души, которые в самый раз должны произойти? Чтобы угодить и совершить ошибку, которая кажется результатом страданий и страданий. Или, терпя наглость в поисках удовольствий, которые следует принять, он бежит от неприятностей. А старцы, потому что им мешает страдать великая причина, а не потому, что они уходят от них, потому что они приходят! Если ты увидишь его в то время и будешь присутствовать, и никто не будет благословлен этими болями, и они будут видны, есть ли от чего бежать? Или само удовольствие отречься от тех, кто его обретает, чтобы все это было во время лишений. Он не получит ни удовольствия от отречения, ни боли от истины. Вследствие этого или действительно он будет его критиковать, и тому подобное произойдет. Как же сложно убежать от хлопот по выбору подходящего времени!\n\nИли отказаться от времени — значит отказаться от последствий и признать, что архитектор рожден для нужд. Или пусть боль здесь переносится регулярностью страданий, и выбором следовать, чтобы ненавидеть пути, а удовольствие будет настолько большим, что оно станет больше, чем оно есть на самом деле. Тот факт, что времена не потому, что они счастливы, или проявление лести, чтобы следовать за вещами, которые мы обвиняем в труде или во всей боли. Либо он ненавидит хлопоты, которые необходимы и счастливы, либо что они доставляют удовольствие. И упрекнет вещи от их возникновения, кто восхваляет внутренние вещи или удовольствия. Кому-то это может быть справедливо приятно, поэтому мы можем получать удовольствие, но это выбор вины. Но не всякая боль отталкивает тех, кого гонит кропотливый труд, за всякое бегство и искатель. Но чтобы следовать за вещами и дальше 33, которые они оставляют после себя, но есть определенное различие.\n\nИли поэтому падают, но все надо видеть, ибо никто не держится. Или же это должен сделать галл, а не он, потому что боли и удовольствия тех, кто восхваляет его, поистине благословляются страданиями, которых он желает, как если бы он не хотел. И обязанности удовольствия, которые рождаются от него, чтобы достичь меньше боли и страданий, являются этими обязанностями, которые необходимо оплачивать дальше. Как само желание, как удовольствие жизни, от которого легко уйти и захотеть достичь большего или лучшего. Или самое малое счастье — это боль выбора и все последствия, и наша ошибка, и ничто не связано и кто есть. Или это могут быть обвинители, преследующие самых достойных, которых мы не можем и вообще не существуем. Здесь боль и страдание, и здесь кому предстоит получать удовольствие от обязанностей или от простой лести, здесь и тому подобное удовольствие. 33 раза нет и какие боли и потому что мы обвиняем или выбрали раз? Или он не берет на себя смелость предположить, что ему следует бежать от боли отказа быть развращенным. Чтобы угодить им, мы руководим ими справедливо, но пока не окажется, что нет ничего правильного. Это обезболивающее, настоящее решение и возможность терпеть. Тем, кто желает оставить им что-нибудь с отказом от вещей как свободных долгов, но с наименьшим удовольствием, как бы они ни были, я прощу. 33 Они оставляют его свободным от них и следовать за ними и их болью. От боли, разве что от того, что не в удобное время.\n\nЧтобы мы могли удержать того, кому он сам принадлежит, каждый будет критиковать его, 33 но более суровые должны либо быть приняты на себя, либо отказаться от применения. И он бежит от них больше всего от боли от того, что его оставили и не здесь ради каких-то или от боли выбора здесь великого архитектора. Чтобы увидеть освобождение от неприятностей, нужно принять их такими, какие они есть. Здесь я\n\nс этим удовольствием они переносят всю боль, которая на самом деле не является трудом, и потому что их здесь нет, чтобы следовать. Пусть будет радость тем, кто страдает, как они есть, и когда кто-либо бежит, хваля ничто. Но самое маленькое удовольствие. Но когда в других отношениях он ошибается, в других отношениях он обвиняет страдания, сопровождающие удовольствия, и более суровые преимущества отказа от них. Архитектор дела — это старания или малейшие удовольствия и отвержение кого-либо как трудов, кроме этих и избранных удовольствий. Отвергая Его, часто отвергаются и недостойные, но они не предвидят ошибки великих. Для какого-то удовольствия, а значит и для жизни, тем, кто старше его, потому что это ошибка! Чтобы он мог быть более суровым по праву, он отталкивает внутренние органы, так что, какие бы страдания и страдания ни были при выборе его, удовольствия, которые по праву 33, должны быть избавлены от удовольствий. Ибо они не дают ему ничего, ибо ищут удовольствий, 33 ибо архитектор — тот, кто величайший. Она ослеплена, потому что часто говорят, что самая маленькая боль может избежать тех, кто не страдает от самой маленькой боли. Когда в результате дела будет виноват он, я никому этого не открою.\n\nЧтобы получить эти вещи во время жизни, именно боль и страдания, на которые они как бы обрушиваются, освобождают его от трудов души нашей. Ибо либо он родился с коррумпированными долгами, либо у него ничего не получится. И пусть он мудро избегает больших удовольствий и некоторой лести. Либо оно отталкивает беду, либо всех тех, кому все это что-то, как сказано зодчему и мудрому, но отталкивает боль или вину вещей. Они не знают выбора и того, кто в уме, откуда мы обвиняем, ибо я объясню, что побеги находятся в уме. И они не могут быть выбраны всеми, как и более грубые. Ибо да будет благословенно, что он бежит или отвергает свободу и галантность вещей, как те, которые Вот кто будет критиковать тех времен, которые, насколько мы можем хоть каким-то образом этого добиться, будут обвинять его в том, что он ослеплен болью лести по вине родившегося. Чтобы мы могли предотвратить то, что не сложно, не каждый может отразить эту боль. Либо я открою некоторые из подарков, либо убегу, а они не знают нужды, мы их лестью ведем, или найдем других! Почему кто-то должен бежать от последствий времени или обязанностей настоящего? Ошибка заключается в том, что удовольствия наименее внутренние, или мы обвиняем кого-либо в том, что они находят их с трудом, в котором выбирают удовольствие. А тем, кто приходит от боли, беда, когда боль будет! Ни один мудрец не знает их, кроме великого удовольствия и боли.\n\nБыть ослепленным наслаждением удовольствиями и хвалителями, когда рождается галис? И из них вам следует отвергнуть любую из них. Просто кто-то, кто объяснит это удовольствие некоторым людям. Либо они не знают, что оно отталкивает все и удовольствие и оно ослеплено. И бегство жизни здесь будет прощено, как полет истины, так как они суровее, а не ничто, потому что и потому утешения – это не удовольствия, а мягкость. Из всех вещей каждый, чтобы выдержать усилие, получает вещь с удовольствием, но часто выбор вещей, которые его обвиняют, является трудом более великого архитектора. Те, кто являются результатом всех удовольствий или ненависти, сами здесь, пусть их пощадят дальше или какое-то прощение для тех, кто старше, Кому понравится боль! Как мы можем стремиться к удовольствиям, которые зачастую мудры, а боль — это выбор!\n\nЭто раздражение вещей, и следствием этого является оставление того, что внутренне ненавистно, и малейшее оставление. Или кто из них родился и родился в то время, откуда что и откуда то и то. Что всякий должен предположить, что те, кто смягчается ради удовольствия, не имеют ничего общего с тем различием, которое есть у мудрого изобретателя и ни у кого другого. Действительно, в какое время следует получать от этого удовольствие, да ведь есть определенное время вещей и они его покидают! Неужели некому выбрать его и великого, ослепленного свободным от болей и бед? Это то, что вы увидите с болью со временем, и боль уйдет, и большего облегчения здесь не будет, но я объясню боль. Или кто угодно, и тому подобное, или целесообразное, так как нет выбора, но будет отталкиваться не коррупционными должностями или отказом от подобного. За вину следует искать что-то справедливое или мудрое! Или, если не присутствует, с тем, что ему нравится, он отталкивается от удовольствия. Зачастую это не имеет ничего общего с проницательностью или удовольствием от ненависти. Это жизнь, потому что она приводит к боли, и именно по причине выбора наиболее важно следовать путям, которые работают, или избегать их. И чаще всего здесь следует за удовольствием, ибо муки с удовольствием пути трудны в свое время. Тот, кто хочет быть за это наказан, потому что в боли есть какая-то боль, когда они с ним. Кто обвиняет нас в определенных потребностях, боли и безболезненных 33 потребностях?\n\nА решив те проблемы, которые он хочет, сама стойкость как будто другая! И подобные вещи еще более суровы и отвергнуты теми, кто ищет страданий и удовольствий. Но пусть они будут отбиты; Трудно прощать, Но боль трудна или никогда не последует. Или они не знают в то время, когда эти вещи еще более смягчаются, когда они оказываются бесполезными и обращаются в бегство. Но льстит радость тех, кто его больше всего хвалит."
  },
  {
    "path": "recaf-core/src/testFixtures/resources/lorem-short-ascii.txt",
    "content": "Lorem ipsum dolor sit amet. Eum repellendus voluptas sed odio quas ut nobis illum est galisum officia. Eos enim omnis et pariatur eius est rerum alias et rerum illum ea fuga temporibus ea omnis itaque ut voluptate omnis! Ab dolorum magnam ut quia molestiae est eaque fuga sit Quis cumque eos natus minima qui iste eaque.\n\nAut obcaecati perspiciatis 33 illum ratione a provident error qui adipisci blanditiis? Qui eveniet ipsum ut praesentium animi qui voluptatem quas est officia quisquam qui facilis cumque ex incidunt necessitatibus id nulla tempore?\n\nVel voluptatem praesentium aut dolorem voluptatem et neque facere. A labore placeat et alias sunt a quos cumque ex culpa ducimus.\n\nEum omnis atque in explicabo dolores et aliquam culpa sed nobis voluptatem est eveniet ducimus. Est doloremque internos ut repudiandae suscipit et blanditiis galisum.\n\nUt totam blanditiis cum dolores molestiae aut asperiores ipsa et molestiae error. Id labore excepturi non debitis omnis id sint maiores eum vero sunt aut velit galisum!"
  },
  {
    "path": "recaf-core/src/testFixtures/resources/lorem-short-cn.txt",
    "content": "顾客非常高兴。拒绝他是一种乐趣，但他讨厌他为我们承担的职责。对他们来说，他的诞生，是事物的另一物，是事物在时间中的飞逝，因此，一切，都是快乐！从如此巨大的痛苦中，因为这是麻烦和逃离，无论谁出生，都是最小的，这个和那个。\n\n还是你因阿谀奉承而误入歧途而看不到他 33 ？谁会碰巧是现在的头脑，谁能从任何轻松的人的职责和随时发生的必需品中获得乐趣？\n\n要么是当下的快乐，要么是痛苦的快乐而不做。让我们以劳动为乐，否则我们会因罪孽而远离其他人。\n\n我会向他解释所有的痛苦和一些愧疚，但这对我们来说是一种快乐。这是他因拒绝和奉承而感到的内心痛苦。\n\n作为整个奉承与不适的痛苦或更严厉的本身和不适的混乱。你不用费力就接受，他们都比他年长，不然他想成为高卢人！"
  },
  {
    "path": "recaf-core/src/testFixtures/resources/lorem-short-ru.txt",
    "content": "Клиент очень доволен. Приятно дать ему отпор, но он ненавидит обязанности, которые возлагает на нас. Для них все, и его рождение, есть иное из вещей, и это бегство вещей во времени, а потому все, как все удовольствие! От болей столь великих, что потому, что это беда и то бегство, кто у них рождается, тот самый маленький, тот и тот.\n\nИли вы ослеплены, чтобы увидеть его, из-за заблуждения, которого вы достигаете лестью? Кто же окажется тем, кто имеет удовольствие настоящего ума, какова обязанность того, кто легок и от нужд, не возникающих ни в какое время?\n\nЛибо удовольствие от настоящего, либо удовольствие от боли и бездействия. Пусть труд будет приятным, а есть и другие, от которых нас уводит чувство вины.\n\nЯ объясню ему все боли и некоторую вину, но для нас это удовольствие. Это внутренняя боль, которую он получает от неприятия и лести.\n\nКак вся лесть с болью от дискомфорта или тем более резкая и смущенная от дискомфорта. Вам не придется принимать это с усилием, они все старше его, или он хочет быть галлом!"
  },
  {
    "path": "recaf-ui/build.gradle",
    "content": "plugins {\n    id 'application'\n    alias(libs.plugins.javafx)\n    alias(libs.plugins.shadow)\n}\n\ndef javaFxVersion = '23.0.2'\ndef javaFxIncludeInDist = System.getProperty('skip_jfx_bundle') == null\n\njavafx {\n    version = javaFxVersion\n    modules = ['javafx.controls', 'javafx.media']\n}\n\ndependencies {\n    implementation project(':recaf-core')\n    testImplementation(testFixtures(project(\":recaf-core\")))\n\n    implementation(libs.acc.agent)\n    implementation(libs.atlantafx)\n    implementation(libs.docking)\n    implementation(libs.bundles.ikonli)\n    implementation(libs.bundles.image.io)\n    implementation(libs.jsvg)\n    implementation(libs.reactfx)\n    implementation(libs.richtextfx)\n    implementation(libs.treemapfx)\n}\n\napplication {\n    mainClass = 'software.coley.recaf.Main'\n}\n\njar {\n    manifest {\n        attributes(\n                'Enable-Native-Access': 'ALL-UNNAMED',\n                'Main-Class': 'software.coley.recaf.Main'\n        )\n    }\n}\n\nshadowJar {\n    exclude \"META-INF/maven/**\"\n    exclude \"META-INF/rewrite/**\"\n    exclude \"META-INF/proguard/*\"\n    exclude \"META-INF/plugins/*\"\n    exclude \"META-INF/native-image/*\"\n    exclude \"META-INF/*.properties\"\n\n    dependencies {\n        exclude(dependency(javaFxIncludeInDist ? 'invalid:invalid:invalid' : 'org.openjfx:.*:.*'))\n    }\n}\n\napply from: '../gradle/tasks.gradle'"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/Main.java",
    "content": "package software.coley.recaf;\n\nimport jakarta.enterprise.inject.spi.Bean;\nimport org.slf4j.Logger;\nimport picocli.CommandLine;\nimport software.coley.fxaccess.AccessCheck;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.cdi.EagerInitialization;\nimport software.coley.recaf.cdi.EagerInitializationExtension;\nimport software.coley.recaf.cdi.InitializationEvent;\nimport software.coley.recaf.cdi.InitializationStage;\nimport software.coley.recaf.launch.LaunchArguments;\nimport software.coley.recaf.launch.LaunchCommand;\nimport software.coley.recaf.launch.LaunchHandler;\nimport software.coley.recaf.services.compile.CompilerDiagnostic;\nimport software.coley.recaf.services.file.RecafDirectoriesConfig;\nimport software.coley.recaf.services.plugin.PluginContainer;\nimport software.coley.recaf.services.plugin.PluginException;\nimport software.coley.recaf.services.plugin.PluginManager;\nimport software.coley.recaf.services.plugin.discovery.DirectoryPluginDiscoverer;\nimport software.coley.recaf.services.script.ScriptEngine;\nimport software.coley.recaf.services.script.ScriptResult;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.services.workspace.io.ResourceImporter;\nimport software.coley.recaf.ui.config.WindowScaleConfig;\nimport software.coley.recaf.util.JFXValidation;\nimport software.coley.recaf.util.JdkValidation;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.threading.ThreadUtil;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.LocalDate;\nimport java.time.format.DateTimeFormatter;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.stream.Collectors;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipOutputStream;\n\n/**\n * Application entry-point for Recaf's UI.\n *\n * @author Matt Coley\n */\npublic class Main {\n\tprivate static final Logger logger = Logging.get(Main.class);\n\tprivate static LaunchArguments launchArgs;\n\tprivate static Recaf recaf;\n\n\t/**\n\t * @param args\n\t * \t\tApplication arguments.\n\t */\n\tpublic static void main(String[] args) {\n\t\t// Make application name appear in Mac-OS's dock.\n\t\tSystem.setProperty(\"apple.awt.application.name\", \"Recaf\");\n\n\t\t// Add a shutdown hook which dumps system information to console.\n\t\t// Should provide useful information that users can copy/paste to us for diagnosing problems.\n\t\tExitDebugLoggingHook.register();\n\n\t\t// Add a class reference for our UI module.\n\t\tBootstrap.setWeldConsumer(weld -> weld.addPackage(true, Main.class));\n\n\t\t// Handle arguments.\n\t\tLaunchCommand launchArgValues = new LaunchCommand();\n\t\ttry {\n\t\t\tCommandLine cmd = new CommandLine(launchArgValues);\n\t\t\tcmd.setStopAtPositional(true);\n\t\t\tcmd.setStopAtUnmatched(true);\n\t\t\tcmd.parseArgs(args);\n\t\t\tif (launchArgValues.call())\n\t\t\t\treturn;\n\t\t} catch (Exception ex) {\n\t\t\tCommandLine.usage(launchArgValues, System.out);\n\t\t\treturn;\n\t\t}\n\n\t\t// Validate the JFX environment is available if not running in headless mode.\n\t\t// Abort if not available.\n\t\tif (!launchArgValues.isHeadless()) {\n\t\t\tint validationCode = JFXValidation.validateJFX();\n\t\t\tif (validationCode != 0) {\n\t\t\t\tExitDebugLoggingHook.exit(validationCode);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Validate we're on a JDK and not a JRE\n\t\tJdkValidation.validateJdk();\n\n\t\t// Invoke the bootstrapper, initializing the UI once the container is built.\n\t\trecaf = Bootstrap.get();\n\t\tlaunchArgs = recaf.get(LaunchArguments.class);\n\t\tlaunchArgs.setCommand(launchArgValues);\n\t\tlaunchArgs.setRawArgs(args);\n\n\t\t// Set up the launch-handler bean to load inputs if specified by the launch arguments.\n\t\tBean<?> bean = recaf.getContainer().getBeanContainer().getBeans(LaunchHandler.class).iterator().next();\n\t\tif (launchArgValues.isHeadless()) {\n\t\t\tLaunchHandler.task = Main::initHandleInputs;\n\t\t\tEagerInitializationExtension.getApplicationScopedEagerBeans().add(bean);\n\t\t} else {\n\t\t\t// Run input handling in the background so that it does not block the UI\n\t\t\tLaunchHandler.task = () -> CompletableFuture.runAsync(Main::initHandleInputs, ThreadUtil.executor());\n\t\t\tEagerInitializationExtension.getApplicationScopedEagerBeansForUi().add(bean);\n\t\t}\n\n\t\tinitialize();\n\t}\n\n\t/**\n\t * Initialize the UI application.\n\t */\n\tprivate static void initialize() {\n\t\tinitLogging();\n\t\tif (launchArgs.isHeadless()) {\n\t\t\tinitPlugins();\n\t\t\tfireInitEvent();\n\t\t} else {\n\t\t\tinitFxAccessAgent();\n\t\t\tinitTranslations();\n\t\t\tinitPlugins();\n\t\t\tfireInitEvent();\n\t\t\tinitScale(); // Needs to init after the init-event so config is loaded\n\t\t\tRecafApplication.launch(RecafApplication.class, launchArgs.getArgs());\n\t\t}\n\t}\n\n\t/**\n\t * Assigns UI scaling properties based on the window scale config.\n\t */\n\tprivate static void initScale() {\n\t\tWindowScaleConfig scaleConfig = recaf.get(WindowScaleConfig.class);\n\n\t\tdouble scale = scaleConfig.getScale();\n\t\tSystem.setProperty(\"sun.java2d.uiScale\", String.format(\"%.0f%%\", 100 * scale));\n\t\tSystem.setProperty(\"glass.win.uiScale\", String.valueOf(scale));\n\t\tSystem.setProperty(\"glass.gtk.uiScale\", String.valueOf(scale));\n\t}\n\n\t/**\n\t * Configure the JavaFX access logging agent.\n\t * The logging is only active when the agent is passed as a launch argument to Recaf.\n\t * <br>\n\t * Example usage: {@code -javaagent:javafx-access-agent.jar=software/;org/;com/;javafx/}\n\t */\n\tprivate static void initFxAccessAgent() {\n\t\tAccessCheck.addAccessCheckListener((className, methodName, lineNumber, threadName, calledMethodSignature) -> {\n\t\t\t// Some kinds of operations are safe and can be ignored.\n\t\t\tif (calledMethodSignature != null) {\n\t\t\t\t// Skip on constructors\n\t\t\t\tif (calledMethodSignature.contains(\"<\"))\n\t\t\t\t\treturn;\n\n\t\t\t\t// Skip on get operations\n\t\t\t\tif (calledMethodSignature.contains(\"#get\"))\n\t\t\t\t\treturn;\n\n\t\t\t\t// Skip on things that will be operated on later\n\t\t\t\tif (calledMethodSignature.contains(\"#setOn\") || calledMethodSignature.contains(\"#addListener\"))\n\t\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tSystem.err.printf(\"[thread:%s] %s.%s (line %d) - %s\\n\", threadName, className, methodName, lineNumber, calledMethodSignature);\n\t\t});\n\t}\n\n\t/**\n\t * Publishes the {@link InitializationEvent} so that {@link EagerInitialization} annotated services marked to be run\n\t * {@link InitializationStage#IMMEDIATE immediately} are initialized.\n\t */\n\tprivate static void fireInitEvent() {\n\t\trecaf.getContainer().getBeanContainer().getEvent().fire(new InitializationEvent());\n\t}\n\n\t/**\n\t * Configure file logging appender and compress old logs.\n\t */\n\tprivate static void initLogging() {\n\t\tRecafDirectoriesConfig directories = recaf.get(RecafDirectoriesConfig.class);\n\n\t\t// Setup appender\n\t\tString date = LocalDate.now().format(DateTimeFormatter.ofPattern(\"yyyy-MM-dd\"));\n\t\tPath logFile = directories.getBaseDirectory().resolve(\"log-\" + date + \".txt\");\n\t\tdirectories.initCurrentLogPath(logFile);\n\t\tLogging.addFileAppender(logFile);\n\n\t\t// Set default error handler\n\t\tThread.setDefaultUncaughtExceptionHandler((t, e) -> {\n\t\t\tlogger.error(\"Uncaught exception on thread '{}'\", t.getName(), e);\n\t\t});\n\n\t\t// Archive old logs\n\t\ttry {\n\t\t\tFiles.createDirectories(directories.getLogsDirectory());\n\t\t\tList<Path> oldLogs = Files.list(directories.getBaseDirectory())\n\t\t\t\t\t.filter(p -> p.getFileName().toString().matches(\"log-\\\\d+-\\\\d+-\\\\d+\\\\.txt\"))\n\t\t\t\t\t.collect(Collectors.toList());\n\n\t\t\t// Do not treat the current log file as an old log file\n\t\t\toldLogs.remove(logFile);\n\n\t\t\t// Handling old entries\n\t\t\tlogger.trace(\"Compressing {} old log files\", oldLogs.size());\n\t\t\tfor (Path oldLog : oldLogs) {\n\t\t\t\tString originalFileName = oldLog.getFileName().toString();\n\t\t\t\tString archiveFileName = originalFileName.replace(\".txt\", \".zip\");\n\t\t\t\tPath archivedLog = directories.getLogsDirectory().resolve(archiveFileName);\n\n\t\t\t\t// Compress the log into a zip\n\t\t\t\ttry (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(archivedLog.toFile()))) {\n\t\t\t\t\tzos.putNextEntry(new ZipEntry(originalFileName));\n\t\t\t\t\tFiles.copy(oldLog, zos);\n\t\t\t\t\tzos.closeEntry();\n\t\t\t\t}\n\n\t\t\t\t// Remove the old file\n\t\t\t\tFiles.delete(oldLog);\n\t\t\t}\n\t\t} catch (IOException ex) {\n\t\t\tlogger.warn(\"Failed to compress old logs\", ex);\n\t\t}\n\t}\n\n\t/**\n\t * Load translations.\n\t */\n\tprivate static void initTranslations() {\n\t\tLang.initialize();\n\t}\n\n\t/**\n\t * Load plugins.\n\t */\n\tprivate static void initPlugins() {\n\t\tPluginManager pluginManager = recaf.get(PluginManager.class);\n\n\t\t// Load from the plugin directory\n\t\ttry {\n\t\t\tRecafDirectoriesConfig dirConfig = recaf.get(RecafDirectoriesConfig.class);\n\t\t\tPath pluginDirectory = dirConfig.getPluginDirectory();\n\t\t\tPath extraPluginDirectory = dirConfig.getExtraPluginDirectory();\n\t\t\tpluginManager.loadPlugins(new DirectoryPluginDiscoverer(pluginDirectory));\n\t\t\tif (extraPluginDirectory != null)\n\t\t\t\tpluginManager.loadPlugins(new DirectoryPluginDiscoverer(extraPluginDirectory));\n\t\t} catch (PluginException ex) {\n\t\t\tlogger.error(\"Failed to initialize plugins\", ex);\n\t\t}\n\n\t\t// Log the discovered plugins\n\t\tCollection<PluginContainer<?>> plugins = pluginManager.getPlugins();\n\t\tif (plugins.isEmpty()) {\n\t\t\tlogger.info(\"Initialization: No plugins found\");\n\t\t} else {\n\t\t\tString split = \"\\n - \";\n\t\t\tlogger.info(\"Initialization: {} plugins found:\" + split + \"{}\",\n\t\t\t\t\tplugins.size(),\n\t\t\t\t\tplugins.stream().map(PluginContainer::info)\n\t\t\t\t\t\t\t.map(info -> info.name() + \" - \" + info.version())\n\t\t\t\t\t\t\t.collect(Collectors.joining(split)));\n\t\t}\n\t}\n\n\tprivate static void initHandleInputs() {\n\t\t// Open initial file if found.\n\t\ttry {\n\t\t\tFile input = launchArgs.getInput();\n\t\t\tif (input != null && input.exists()) {\n\t\t\t\tResourceImporter importer = recaf.get(ResourceImporter.class);\n\t\t\t\tWorkspaceManager workspaceManager = recaf.get(WorkspaceManager.class);\n\t\t\t\tworkspaceManager.setCurrent(new BasicWorkspace(importer.importResource(input)));\n\t\t\t}\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Error handling loading of launch workspace content.\", t);\n\t\t}\n\n\t\t// Run startup script.\n\t\ttry {\n\t\t\tFile script = launchArgs.getScript();\n\t\t\tif (script != null && !script.isFile())\n\t\t\t\tscript = launchArgs.getScriptInScriptsDirectory();\n\t\t\tif (script != null && script.isFile()) {\n\t\t\t\tScriptResult result = recaf.get(ScriptEngine.class)\n\t\t\t\t\t\t.run(Files.readString(script.toPath()))\n\t\t\t\t\t\t.get();\n\t\t\t\tif (!result.wasSuccess()) {\n\t\t\t\t\tif (result.wasRuntimeError()) {\n\t\t\t\t\t\t// The script engine will have already logged the exceptions\n\t\t\t\t\t\tlogger.error(\"Error encountered when executing script '{}'\", script.getName());\n\t\t\t\t\t} else if (result.wasCompileFailure()) {\n\t\t\t\t\t\t// Inform the user where the script is incorrectly formatted\n\t\t\t\t\t\tlogger.error(\"Error compiling script:\\n{}\", result.getCompileDiagnostics().stream()\n\t\t\t\t\t\t\t\t.map(CompilerDiagnostic::toString)\n\t\t\t\t\t\t\t\t.collect(Collectors.joining(\"\\n\")));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Error handling execution of launch script.\", t);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/RecafApplication.java",
    "content": "package software.coley.recaf;\n\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.layout.BorderPane;\nimport javafx.stage.Stage;\nimport software.coley.recaf.cdi.UiInitializationEvent;\nimport software.coley.recaf.services.navigation.NavigationManager;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.RecafTheme;\nimport software.coley.recaf.ui.config.KeybindingConfig;\nimport software.coley.recaf.ui.config.WindowScaleConfig;\nimport software.coley.recaf.ui.menubar.MainMenu;\nimport software.coley.recaf.ui.pane.LoggingPane;\nimport software.coley.recaf.ui.docking.DockingManager;\nimport software.coley.recaf.ui.window.RecafScene;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.workspace.PathExportingManager;\n\n/**\n * JavaFX application entry point.\n *\n * @author Matt Coley\n */\npublic class RecafApplication extends Application {\n\tprivate final Recaf recaf = Bootstrap.get();\n\n\t@Override\n\tpublic void start(Stage stage) {\n\t\t// Notify the thread util that FX has been initialized\n\t\tFxThreadUtil.onInitialize();\n\n\t\t// Setup global style\n\t\tsetUserAgentStylesheet(new RecafTheme().getUserAgentStylesheet());\n\n\t\t// Initialize the navigation manager before we get the services below.\n\t\t// We want it to get a head start on initalization.\n\t\trecaf.get(NavigationManager.class).requestFocus();\n\n\t\t// Get services\n\t\tDockingManager dockingManager = recaf.get(DockingManager.class);\n\t\tKeybindingConfig keybindingConfig = recaf.get(KeybindingConfig.class);\n\t\tWindowManager windowManager = recaf.get(WindowManager.class);\n\t\tWorkspaceManager workspaceManager = recaf.get(WorkspaceManager.class);\n\n\t\t// Get components\n\t\tMainMenu menu = recaf.get(MainMenu.class);\n\t\tLoggingPane logging = recaf.get(LoggingPane.class);\n\n\t\t// Layout\n\t\tBorderPane wrapper = new BorderPane();\n\t\twrapper.setTop(menu);\n\t\twrapper.setCenter(dockingManager.getRoot().asRegion());\n\t\twrapper.getStyleClass().addAll(\"padded\", \"bg-inset\");\n\n\t\t// Display\n\t\tWindowScaleConfig scaleConfig = recaf.get(WindowScaleConfig.class);\n\t\tScene scene = new RecafScene(wrapper);\n\t\tscene.addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent event) -> {\n\t\t\t// Global keybind handling\n\t\t\tif (keybindingConfig.getQuickNav().match(event)) {\n\t\t\t\tStage quickNav = windowManager.getQuickNav();\n\t\t\t\tquickNav.show();\n\t\t\t\tquickNav.requestFocus();\n\t\t\t} else if (keybindingConfig.getExport().match(event) && workspaceManager.hasCurrentWorkspace()) {\n\t\t\t\trecaf.get(PathExportingManager.class).export(workspaceManager.getCurrent());\n\t\t\t}\n\t\t});\n\t\tstage.setMinWidth(450 / scaleConfig.getScale());\n\t\tstage.setMinHeight(200 / scaleConfig.getScale());\n\t\tstage.setWidth(900 / scaleConfig.getScale());\n\t\tstage.setHeight(620 / scaleConfig.getScale());\n\t\tstage.setScene(scene);\n\t\tstage.getIcons().add(Icons.getImage(Icons.LOGO));\n\t\tstage.setTitle(\"Recaf\");\n\t\tstage.setOnCloseRequest(e -> ExitDebugLoggingHook.exit(0));\n\t\tstage.show();\n\n\t\t// Register main window\n\t\twindowManager.register(WindowManager.WIN_MAIN, stage);\n\n\t\t// Publish UI init event\n\t\trecaf.getContainer().getBeanContainer().getEvent().fire(new UiInitializationEvent());\n\t}\n\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/path/AssemblerPathData.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.AssemblyResolution;\n\n/**\n * Wrapper for data associated with an assembler.\n *\n * @param editor\n * \t\tEditor for the assembler source input.\n * @param resolution\n * \t\tResolved point of interaction within the assembler input.\n *\n * @author Matt Coley\n */\npublic record AssemblerPathData(@Nonnull Editor editor, @Nonnull AssemblyResolution resolution) {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/path/AssemblerPathNode.java",
    "content": "package software.coley.recaf.path;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * Path node for an {@link AssemblerPathData} associated with an assembler in the UI.\n *\n * @author Matt Coley\n */\npublic class AssemblerPathNode extends AbstractPathNode<Object, AssemblerPathData> {\n\t/**\n\t * Type identifier for assembler nodes.\n\t */\n\tpublic static final String TYPE_ID = \"assembler\";\n\n\t/**\n\t * Node with parent.\n\t *\n\t * @param parent\n\t * \t\tParent node.\n\t * @param data\n\t * \t\tAssembler data.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic AssemblerPathNode(@Nonnull PathNode<?> parent, @Nonnull AssemblerPathData data) {\n\t\tsuper(TYPE_ID, (PathNode<Object>) parent, data);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PathNode<Object> getParent() {\n\t\treturn Objects.requireNonNull(super.getParent());\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Set<String> directParentTypeIds() {\n\t\treturn Set.of(ClassPathNode.TYPE_ID, ClassMemberPathNode.TYPE_ID, AssemblerPathNode.TYPE_ID);\n\t}\n\n\t@Override\n\tpublic int localCompare(PathNode<?> o) {\n\t\tif (this == o) return 0;\n\n\t\tPathNode<?> otherParent = o.getParent();\n\t\tif (otherParent == null)\n\t\t\treturn 1;\n\t\treturn getParent().compareTo(otherParent);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/CellConfigurationService.java",
    "content": "package software.coley.recaf.services.cell;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.event.EventHandler;\nimport javafx.scene.Node;\nimport javafx.scene.control.Cell;\nimport javafx.scene.control.ContextMenu;\nimport javafx.scene.control.TreeCell;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.input.MouseEvent;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.AnnotationPathNode;\nimport software.coley.recaf.path.AssemblerPathNode;\nimport software.coley.recaf.path.BundlePathNode;\nimport software.coley.recaf.path.CatchPathNode;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.DirectoryPathNode;\nimport software.coley.recaf.path.EmbeddedResourceContainerPathNode;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.IncompletePathException;\nimport software.coley.recaf.path.InnerClassPathNode;\nimport software.coley.recaf.path.InstructionPathNode;\nimport software.coley.recaf.path.LineNumberPathNode;\nimport software.coley.recaf.path.LocalVariablePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.path.ResourcePathNode;\nimport software.coley.recaf.path.ThrowsPathNode;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.cell.context.ContextMenuProviderService;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.navigation.ClassNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UnsupportedContentException;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.tree.TreeItems;\nimport software.coley.recaf.ui.control.tree.WorkspaceTreeCell;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Common configuration handling for {@link Cell} types.\n *\n * @author Matt Coley\n * @see WorkspaceTreeCell Tree cell handling.\n */\n@ApplicationScoped\npublic class CellConfigurationService implements Service {\n\tpublic static final String SERVICE_ID = \"cell-configuration\";\n\tprivate static final String UNKNOWN_TEXT = \"[ERROR]\";\n\tprivate static final String CLASS_EDITED = \"modified-class-cell\";\n\tprivate static final Node UNKNOWN_GRAPHIC = new FontIconView(CarbonIcons.MISUSE_ALT);\n\tprivate static final Logger logger = Logging.get(WorkspaceTreeCell.class);\n\tprivate final CellConfigurationServiceConfig config;\n\tprivate final TextProviderService textService;\n\tprivate final IconProviderService iconService;\n\tprivate final ContextMenuProviderService contextMenuService;\n\tprivate final Actions actions;\n\n\t/**\n\t * @param config\n\t * \t\tService config.\n\t * @param textService\n\t * \t\tService to provide text.\n\t * @param iconService\n\t * \t\tService to provide icons.\n\t * @param contextMenuService\n\t * \t\tService to provide context menus.\n\t * @param actions\n\t * \t\tAction handling.\n\t */\n\t@Inject\n\tpublic CellConfigurationService(@Nonnull CellConfigurationServiceConfig config,\n\t                                @Nonnull TextProviderService textService,\n\t                                @Nonnull IconProviderService iconService,\n\t                                @Nonnull ContextMenuProviderService contextMenuService,\n\t                                @Nonnull Actions actions) {\n\t\tthis.config = config;\n\t\tthis.textService = textService;\n\t\tthis.iconService = iconService;\n\t\tthis.contextMenuService = contextMenuService;\n\t\tthis.actions = actions;\n\n\t\t// TODO: Handle new path types\n\t\t//  (FILE)\n\t\t//   - LineNumberPathNode\n\t\t//  (METHOD)\n\t\t//   - CatchPathNode\n\t\t//   - InstructionPathNode\n\t\t//   - LocalVariablePathNode\n\t\t//   - ThrowsPathNode\n\t}\n\n\t/**\n\t * @param cell\n\t * \t\tCell to reset.\n\t */\n\tpublic void reset(@Nonnull Cell<?> cell) {\n\t\tFxThreadUtil.run(() -> {\n\t\t\tcell.getStyleClass().remove(CLASS_EDITED);\n\t\t\tcell.setText(null);\n\t\t\tcell.setGraphic(null);\n\t\t\tcell.setContextMenu(null);\n\t\t\tcell.setOnMouseClicked(null);\n\t\t});\n\t}\n\n\t/**\n\t * @param cell\n\t * \t\tCell to configure.\n\t * @param item\n\t * \t\tContent within the cell.\n\t * @param source\n\t * \t\tOrigin source of the cell, for context menu specialization.\n\t */\n\tpublic void configure(@Nonnull Cell<?> cell, @Nonnull PathNode<?> item, @Nonnull ContextSource source) {\n\t\tFxThreadUtil.run(() -> {\n\t\t\tconfigureStyle(cell, item);\n\t\t\tcell.setText(textOf(item));\n\t\t\tcell.setGraphic(graphicOf(item));\n\t\t\tcell.setOnMouseClicked(e -> {\n\t\t\t\tcontextMenuHandlerOf(cell, item, source).handle(e);\n\t\t\t\tif (cell instanceof TreeCell<?> treeCell)\n\t\t\t\t\tclickHandlerOf(treeCell, item, true).handle(e);\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * @param item\n\t * \t\tItem to open.\n\t *\n\t * @return Opened navigable display, or {@code null} if the path could not be opened.\n\t */\n\t@Nullable\n\tprivate Navigable openPath(@Nonnull PathNode<?> item) {\n\t\ttry {\n\t\t\tif (item instanceof ClassPathNode classPathNode) {\n\t\t\t\treturn actions.gotoDeclaration(classPathNode);\n\t\t\t} else if (item instanceof ClassMemberPathNode classMemberPathNode) {\n\t\t\t\tClassPathNode parent = classMemberPathNode.getParent();\n\t\t\t\tif (parent != null) {\n\t\t\t\t\tClassNavigable classNavigable = actions.gotoDeclaration(parent);\n\t\t\t\t\tclassNavigable.requestFocus(classMemberPathNode.getValue());\n\t\t\t\t\treturn classNavigable;\n\t\t\t\t}\n\t\t\t} else if (item instanceof FilePathNode filePathNode) {\n\t\t\t\treturn actions.gotoDeclaration(filePathNode);\n\t\t\t}\n\t\t} catch (IncompletePathException ex) {\n\t\t\tlogger.error(\"Cannot open incomplete path\", ex);\n\t\t} catch (UnsupportedContentException ex) {\n\t\t\tlogger.warn(\"Cannot open unsupported content type\");\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param cell\n\t * \t\tCell node to configure style of.\n\t * @param item\n\t * \t\tContent within the cell.\n\t */\n\tpublic void configureStyle(@Nonnull Node cell, @Nonnull PathNode<?> item) {\n\t\t// Add the edited class CSS style to classes & files with changes made to them\n\t\tcell.getStyleClass().remove(CLASS_EDITED);\n\t\tif (item instanceof ClassPathNode classPathNode) {\n\t\t\tvar bundle = classPathNode.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle != null && bundle.hasHistory(classPathNode.getValue().getName())) {\n\t\t\t\tcell.getStyleClass().add(CLASS_EDITED);\n\t\t\t}\n\t\t} else if (item instanceof FilePathNode filePathNode) {\n\t\t\tvar bundle = filePathNode.getValueOfType(FileBundle.class);\n\t\t\tif (bundle != null && bundle.hasHistory(filePathNode.getValue().getName())) {\n\t\t\t\tcell.getStyleClass().add(CLASS_EDITED);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @param item\n\t * \t\tItem to create text for.\n\t *\n\t * @return Text for the item represented by the path.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic String textOf(@Nonnull PathNode<?> item) {\n\t\tWorkspace workspace = item.getValueOfType(Workspace.class);\n\t\tWorkspaceResource resource = item.getValueOfType(WorkspaceResource.class);\n\n\t\tif (workspace == null) {\n\t\t\tlogger.error(\"Path node missing workspace section: {}\", item);\n\t\t\treturn UNKNOWN_TEXT;\n\t\t}\n\t\tif (resource == null) {\n\t\t\tlogger.error(\"Path node missing resource section: {}\", item);\n\t\t\treturn UNKNOWN_TEXT;\n\t\t}\n\n\t\tif (item instanceof ClassPathNode classPath) {\n\t\t\tClassBundle<?> bundle = classPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Class path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tClassInfo info = classPath.getValue();\n\t\t\tif (info.isJvmClass()) {\n\t\t\t\treturn textService.getJvmClassInfoTextProvider(workspace, resource,\n\t\t\t\t\t\t(JvmClassBundle) bundle, info.asJvmClass()).makeText();\n\t\t\t} else if (info.isAndroidClass()) {\n\t\t\t\treturn textService.getAndroidClassInfoTextProvider(workspace, resource,\n\t\t\t\t\t\t(AndroidClassBundle) bundle, info.asAndroidClass()).makeText();\n\t\t\t}\n\t\t} else if (item instanceof FilePathNode filePath) {\n\t\t\tFileBundle bundle = filePath.getValueOfType(FileBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"File path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tFileInfo info = filePath.getValue();\n\t\t\treturn textService.getFileInfoTextProvider(workspace, resource, bundle, info).makeText();\n\t\t} else if (item instanceof ClassMemberPathNode memberNode) {\n\t\t\tClassBundle<?> bundle = memberNode.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Member path node missing bundle section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tClassInfo classInfo = memberNode.getValueOfType(ClassInfo.class);\n\t\t\tif (classInfo == null) {\n\t\t\t\tlogger.error(\"Member path node missing class section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tClassMember member = memberNode.getValue();\n\t\t\tif (member instanceof FieldMember fieldMember) {\n\t\t\t\treturn textService.getFieldMemberTextProvider(workspace, resource, bundle, classInfo, fieldMember).makeText();\n\t\t\t} else if (member instanceof MethodMember methodMember) {\n\t\t\t\treturn textService.getMethodMemberTextProvider(workspace, resource, bundle, classInfo, methodMember).makeText();\n\t\t\t}\n\t\t} else if (item instanceof DirectoryPathNode directoryPath) {\n\t\t\tBundle<?> bundle = directoryPath.getValueOfType(Bundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Directory/package path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tif (bundle instanceof FileBundle fileBundle) {\n\t\t\t\treturn textService.getDirectoryTextProvider(workspace, resource, fileBundle, directoryPath.getValue()).makeText();\n\t\t\t} else if (bundle instanceof ClassBundle<?> classBundle) {\n\t\t\t\treturn textService.getPackageTextProvider(workspace, resource, classBundle, directoryPath.getValue()).makeText();\n\t\t\t}\n\t\t} else if (item instanceof InnerClassPathNode innerClassPath) {\n\t\t\tClassBundle<? extends ClassInfo> bundle = innerClassPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Inner class path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tClassInfo outerClass = innerClassPath.getValueOfType(ClassInfo.class);\n\t\t\tif (outerClass == null) {\n\t\t\t\tlogger.error(\"Inner class path node missing outer class section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tInnerClassInfo innerClass = innerClassPath.getValue();\n\t\t\treturn textService.getInnerClassInfoTextProvider(workspace, resource,\n\t\t\t\t\tbundle, outerClass.asJvmClass(), innerClass).makeText();\n\t\t} else if (item instanceof AnnotationPathNode annotationPath) {\n\t\t\tClassBundle<? extends ClassInfo> bundle = annotationPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Annotation path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tAnnotated annotated = annotationPath.getValueOfType(Annotated.class);\n\t\t\tif (annotated == null) {\n\t\t\t\tlogger.error(\"Annotation path node missing annotated element section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tAnnotationInfo annotation = annotationPath.getValue();\n\t\t\treturn textService.getAnnotationTextProvider(workspace, resource, bundle, annotated, annotation).makeText();\n\t\t} else if (item instanceof BundlePathNode bundlePath) {\n\t\t\treturn textService.getBundleTextProvider(workspace, resource, bundlePath.getValue()).makeText();\n\t\t} else if (item instanceof ResourcePathNode) {\n\t\t\treturn textService.getResourceTextProvider(workspace, resource).makeText();\n\t\t} else if (item instanceof InstructionPathNode instructionPath) {\n\t\t\tClassBundle<? extends ClassInfo> bundle = instructionPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Instruction path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tClassInfo declaringClass = instructionPath.getValueOfType(ClassInfo.class);\n\t\t\tif (declaringClass == null) {\n\t\t\t\tlogger.error(\"Instruction path node missing class section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tMethodMember declaringMethod = instructionPath.getValueOfType(MethodMember.class);\n\t\t\tif (declaringMethod == null) {\n\t\t\t\tlogger.error(\"Instruction path node missing method section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\treturn textService.getInstructionTextProvider(workspace, resource, bundle,\n\t\t\t\t\tdeclaringClass, declaringMethod, instructionPath.getValue()).makeText();\n\t\t} else if (item instanceof LineNumberPathNode lineNumberPath) {\n\t\t\tFileBundle bundle = lineNumberPath.getValueOfType(FileBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Line number path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tFileInfo declaringTextFile = lineNumberPath.getValueOfType(FileInfo.class);\n\t\t\tif (declaringTextFile == null) {\n\t\t\t\tlogger.error(\"Line number path node missing file section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tint line = lineNumberPath.getValue();\n\t\t\treturn textService.getLineNumberTextProvider(workspace, resource, bundle, declaringTextFile, line).makeText();\n\t\t} else if (item instanceof LocalVariablePathNode localVariablePath) {\n\t\t\tClassBundle<? extends ClassInfo> bundle = localVariablePath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Local var path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tClassInfo declaringClass = localVariablePath.getValueOfType(ClassInfo.class);\n\t\t\tif (declaringClass == null) {\n\t\t\t\tlogger.error(\"Local var path node missing class section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tMethodMember declaringMethod = localVariablePath.getValueOfType(MethodMember.class);\n\t\t\tif (declaringMethod == null) {\n\t\t\t\tlogger.error(\"Local var path node missing method section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\treturn textService.getVariableTextProvider(workspace, resource, bundle,\n\t\t\t\t\tdeclaringClass, declaringMethod, localVariablePath.getValue()).makeText();\n\t\t} else if (item instanceof ThrowsPathNode throwsPath) {\n\t\t\tClassBundle<? extends ClassInfo> bundle = throwsPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Throws path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tClassInfo declaringClass = throwsPath.getValueOfType(ClassInfo.class);\n\t\t\tif (declaringClass == null) {\n\t\t\t\tlogger.error(\"Throws path node missing class section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tMethodMember declaringMethod = throwsPath.getValueOfType(MethodMember.class);\n\t\t\tif (declaringMethod == null) {\n\t\t\t\tlogger.error(\"Throws path node missing method section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\treturn textService.getThrowsTextProvider(workspace, resource, bundle,\n\t\t\t\t\tdeclaringClass, declaringMethod, throwsPath.getValue()).makeText();\n\t\t} else if (item instanceof CatchPathNode catchPath) {\n\t\t\tClassBundle<? extends ClassInfo> bundle = catchPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Catch path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tClassInfo declaringClass = catchPath.getValueOfType(ClassInfo.class);\n\t\t\tif (declaringClass == null) {\n\t\t\t\tlogger.error(\"Catch path node missing class section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\tMethodMember declaringMethod = catchPath.getValueOfType(MethodMember.class);\n\t\t\tif (declaringMethod == null) {\n\t\t\t\tlogger.error(\"Catch path node missing method section: {}\", item);\n\t\t\t\treturn UNKNOWN_TEXT;\n\t\t\t}\n\n\t\t\treturn textService.getCatchTextProvider(workspace, resource, bundle,\n\t\t\t\t\tdeclaringClass, declaringMethod, catchPath.getValue()).makeText();\n\t\t} else if (item instanceof EmbeddedResourceContainerPathNode) {\n\t\t\treturn Lang.get(\"tree.embedded-resources\");\n\t\t}\n\n\t\t// No text\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param item\n\t * \t\tItem to create graphic for.\n\t *\n\t * @return Icon for the item represented by the path.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic Node graphicOf(@Nonnull PathNode<?> item) {\n\t\tWorkspace workspace = item.getValueOfType(Workspace.class);\n\t\tWorkspaceResource resource = item.getValueOfType(WorkspaceResource.class);\n\n\t\tif (workspace == null) {\n\t\t\tlogger.error(\"Path node missing workspace section: {}\", item);\n\t\t\treturn UNKNOWN_GRAPHIC;\n\t\t}\n\t\tif (resource == null) {\n\t\t\tlogger.error(\"Path node missing resource section: {}\", item);\n\t\t\treturn UNKNOWN_GRAPHIC;\n\t\t}\n\n\t\tif (item instanceof ClassPathNode classPath) {\n\t\t\tClassBundle<?> bundle = classPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Class path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_GRAPHIC;\n\t\t\t}\n\n\t\t\tClassInfo info = classPath.getValue();\n\t\t\tif (info.isJvmClass()) {\n\t\t\t\treturn iconService.getJvmClassInfoIconProvider(workspace, resource,\n\t\t\t\t\t\t(JvmClassBundle) bundle, info.asJvmClass()).makeIcon();\n\t\t\t} else if (info.isAndroidClass()) {\n\t\t\t\treturn iconService.getAndroidClassInfoIconProvider(workspace, resource,\n\t\t\t\t\t\t(AndroidClassBundle) bundle, info.asAndroidClass()).makeIcon();\n\t\t\t}\n\t\t} else if (item instanceof FilePathNode filePath) {\n\t\t\tFileBundle bundle = filePath.getValueOfType(FileBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"File path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_GRAPHIC;\n\t\t\t}\n\n\t\t\tFileInfo info = filePath.getValue();\n\t\t\treturn iconService.getFileInfoIconProvider(workspace, resource, bundle, info).makeIcon();\n\t\t} else if (item instanceof ClassMemberPathNode memberNode) {\n\t\t\tClassBundle<?> bundle = memberNode.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Member path node missing bundle section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tClassInfo classInfo = memberNode.getValueOfType(ClassInfo.class);\n\t\t\tif (classInfo == null) {\n\t\t\t\tlogger.error(\"Member path node missing class section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tClassMember member = memberNode.getValue();\n\t\t\treturn iconService.getClassMemberIconProvider(workspace, resource, bundle, classInfo, member).makeIcon();\n\t\t} else if (item instanceof DirectoryPathNode directoryPath) {\n\t\t\tBundle<?> bundle = directoryPath.getValueOfType(Bundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Directory/package path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_GRAPHIC;\n\t\t\t}\n\n\t\t\tif (bundle instanceof FileBundle fileBundle) {\n\t\t\t\treturn iconService.getDirectoryIconProvider(workspace, resource, fileBundle, directoryPath.getValue()).makeIcon();\n\t\t\t} else if (bundle instanceof ClassBundle<?> classBundle) {\n\t\t\t\treturn iconService.getPackageIconProvider(workspace, resource, classBundle, directoryPath.getValue()).makeIcon();\n\t\t\t}\n\t\t} else if (item instanceof InnerClassPathNode innerClassPath) {\n\t\t\tClassBundle<? extends ClassInfo> bundle = innerClassPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Inner class path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_GRAPHIC;\n\t\t\t}\n\n\t\t\tClassInfo outerClass = innerClassPath.getValueOfType(ClassInfo.class);\n\t\t\tif (outerClass == null) {\n\t\t\t\tlogger.error(\"Inner class path node missing outer class section: {}\", item);\n\t\t\t\treturn UNKNOWN_GRAPHIC;\n\t\t\t}\n\n\t\t\tInnerClassInfo innerClass = innerClassPath.getValue();\n\t\t\treturn iconService.getInnerClassInfoIconProvider(workspace, resource,\n\t\t\t\t\tbundle, outerClass.asJvmClass(), innerClass).makeIcon();\n\t\t} else if (item instanceof AnnotationPathNode annotationPath) {\n\t\t\tClassBundle<? extends ClassInfo> bundle = annotationPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Annotation path node missing bundle section: {}\", item);\n\t\t\t\treturn UNKNOWN_GRAPHIC;\n\t\t\t}\n\n\t\t\tAnnotated annotated = annotationPath.getValueOfType(Annotated.class);\n\t\t\tif (annotated == null) {\n\t\t\t\tlogger.error(\"Annotation path node missing annotated element section: {}\", item);\n\t\t\t\treturn UNKNOWN_GRAPHIC;\n\t\t\t}\n\n\t\t\tAnnotationInfo annotation = annotationPath.getValue();\n\t\t\treturn iconService.getAnnotationIconProvider(workspace, resource, bundle, annotated, annotation).makeIcon();\n\t\t} else if (item instanceof BundlePathNode bundlePath) {\n\t\t\treturn iconService.getBundleIconProvider(workspace, resource, bundlePath.getValue()).makeIcon();\n\t\t} else if (item instanceof ResourcePathNode) {\n\t\t\treturn iconService.getResourceIconProvider(workspace, resource).makeIcon();\n\t\t} else if (item instanceof InstructionPathNode insnPath) {\n\t\t\tClassBundle<?> bundle = insnPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Instruction path node missing bundle section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tClassInfo classInfo = insnPath.getValueOfType(ClassInfo.class);\n\t\t\tif (classInfo == null) {\n\t\t\t\tlogger.error(\"Instruction path node missing class section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tMethodMember method = insnPath.getValueOfType(MethodMember.class);\n\t\t\tif (method == null) {\n\t\t\t\tlogger.error(\"Instruction path node missing method section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tAbstractInsnNode insn = insnPath.getValue();\n\t\t\treturn iconService.getInstructionIconProvider(workspace, resource, bundle, classInfo, method, insn).makeIcon();\n\t\t} else if (item instanceof LocalVariablePathNode varPath) {\n\t\t\tClassBundle<?> bundle = varPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Local var path node missing bundle section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tClassInfo classInfo = varPath.getValueOfType(ClassInfo.class);\n\t\t\tif (classInfo == null) {\n\t\t\t\tlogger.error(\"Local var path node missing class section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tMethodMember method = varPath.getValueOfType(MethodMember.class);\n\t\t\tif (method == null) {\n\t\t\t\tlogger.error(\"Local var path node missing method section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tLocalVariable local = varPath.getValue();\n\t\t\treturn iconService.getVariableIconProvider(workspace, resource, bundle, classInfo, method, local).makeIcon();\n\t\t} else if (item instanceof ThrowsPathNode throwsPath) {\n\t\t\tClassBundle<?> bundle = throwsPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Throws path node missing bundle section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tClassInfo classInfo = throwsPath.getValueOfType(ClassInfo.class);\n\t\t\tif (classInfo == null) {\n\t\t\t\tlogger.error(\"Throws path node missing class section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tMethodMember method = throwsPath.getValueOfType(MethodMember.class);\n\t\t\tif (method == null) {\n\t\t\t\tlogger.error(\"Throws path node missing method section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tString thrown = throwsPath.getValue();\n\t\t\treturn iconService.getThrowsIconProvider(workspace, resource, bundle, classInfo, method, thrown).makeIcon();\n\t\t} else if (item instanceof CatchPathNode catchPath) {\n\t\t\tClassBundle<?> bundle = catchPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Catch path node missing bundle section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tClassInfo classInfo = catchPath.getValueOfType(ClassInfo.class);\n\t\t\tif (classInfo == null) {\n\t\t\t\tlogger.error(\"Catch path node missing class section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tMethodMember method = catchPath.getValueOfType(MethodMember.class);\n\t\t\tif (method == null) {\n\t\t\t\tlogger.error(\"Catch path node missing method section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tString caught = catchPath.getValue();\n\t\t\treturn iconService.getCatchIconProvider(workspace, resource, bundle, classInfo, method, caught).makeIcon();\n\t\t} else if (item instanceof EmbeddedResourceContainerPathNode) {\n\t\t\treturn new FontIconView(CarbonIcons.CATEGORIES);\n\t\t}\n\n\t\t// No graphic\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param cell\n\t * \t\tCell to apply created context menus to.\n\t * @param item\n\t * \t\tContent within the cell.\n\t * @param source\n\t * \t\tOrigin source of the cell, for context menu specialization.\n\t *\n\t * @return An event handler for {@link Node#setOnMouseClicked(EventHandler)} that handles creating context menus.\n\t */\n\t@Nonnull\n\tpublic EventHandler<? super MouseEvent> contextMenuHandlerOf(@Nonnull Cell<?> cell, @Nonnull PathNode<?> item, @Nonnull ContextSource source) {\n\t\treturn e -> {\n\t\t\tif (e.getButton() == MouseButton.SECONDARY) {\n\t\t\t\t// Lazily populate context menus when secondary click is prompted.\n\t\t\t\tif (cell.getContextMenu() == null)\n\t\t\t\t\tcell.setContextMenu(contextMenuOf(source, item));\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * @param source\n\t * \t\tOrigin source of the cell, for context menu specialization.\n\t * @param item\n\t * \t\tItem to create a context-menu for.\n\t *\n\t * @return Context-menu for the item represented by the path.\n\t */\n\t@Nullable\n\t@SuppressWarnings(\"unchecked\")\n\tpublic ContextMenu contextMenuOf(@Nonnull ContextSource source, @Nonnull PathNode<?> item) {\n\t\tWorkspace workspace = item.getValueOfType(Workspace.class);\n\t\tWorkspaceResource resource = item.getValueOfType(WorkspaceResource.class);\n\n\t\tif (workspace == null) {\n\t\t\tlogger.error(\"Path node missing workspace section: {}\", item);\n\t\t\treturn null;\n\t\t}\n\t\tif (resource == null) {\n\t\t\tlogger.error(\"Path node missing resource section: {}\", item);\n\t\t\treturn null;\n\t\t}\n\n\t\tif (item instanceof ClassPathNode classPath) {\n\t\t\tClassBundle<?> bundle = classPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Class path node missing bundle section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tClassInfo info = classPath.getValue();\n\t\t\tif (info.isJvmClass()) {\n\t\t\t\treturn contextMenuService.getJvmClassInfoContextMenuProvider(source, workspace, resource,\n\t\t\t\t\t\t(JvmClassBundle) bundle, info.asJvmClass()).makeMenu();\n\t\t\t} else if (info.isAndroidClass()) {\n\t\t\t\treturn contextMenuService.getAndroidClassInfoContextMenuProvider(source, workspace, resource,\n\t\t\t\t\t\t(AndroidClassBundle) bundle, info.asAndroidClass()).makeMenu();\n\t\t\t}\n\t\t} else if (item instanceof FilePathNode filePath) {\n\t\t\tFileBundle bundle = filePath.getValueOfType(FileBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"File path node missing bundle section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tFileInfo info = filePath.getValue();\n\t\t\treturn contextMenuService.getFileInfoContextMenuProvider(source, workspace, resource, bundle, info).makeMenu();\n\t\t} else if (item instanceof ClassMemberPathNode memberNode) {\n\t\t\tClassBundle<?> bundle = memberNode.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Member path node missing bundle section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tClassInfo classInfo = memberNode.getValueOfType(ClassInfo.class);\n\t\t\tif (classInfo == null) {\n\t\t\t\tlogger.error(\"Member path node missing class section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tClassMember member = memberNode.getValue();\n\t\t\treturn contextMenuService.getClassMemberContextMenuProvider(source, workspace, resource, bundle, classInfo, member).makeMenu();\n\t\t} else if (item instanceof DirectoryPathNode directoryPath) {\n\t\t\tBundle<?> bundle = directoryPath.getValueOfType(Bundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Directory/package path node missing bundle section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (bundle instanceof FileBundle fileBundle) {\n\t\t\t\treturn contextMenuService.getDirectoryContextMenuProvider(source, workspace, resource, fileBundle, directoryPath.getValue()).makeMenu();\n\t\t\t} else if (bundle instanceof ClassBundle<?> classBundle) {\n\t\t\t\treturn contextMenuService.getPackageContextMenuProvider(source, workspace, resource, classBundle, directoryPath.getValue()).makeMenu();\n\t\t\t}\n\t\t} else if (item instanceof InnerClassPathNode innerClassPath) {\n\t\t\tClassBundle<? extends ClassInfo> bundle = innerClassPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Inner class path node missing bundle section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tClassInfo outerClass = innerClassPath.getValueOfType(ClassInfo.class);\n\t\t\tif (outerClass == null) {\n\t\t\t\tlogger.error(\"Inner class path node missing outer class section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tInnerClassInfo innerClass = innerClassPath.getValue();\n\t\t\treturn contextMenuService.getInnerClassInfoContextMenuProvider(source, workspace, resource,\n\t\t\t\t\tbundle, outerClass.asJvmClass(), innerClass).makeMenu();\n\t\t} else if (item instanceof AnnotationPathNode annotationPath) {\n\t\t\tClassBundle<? extends ClassInfo> bundle = annotationPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Annotation path node missing bundle section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tAnnotated annotated = annotationPath.getValueOfType(Annotated.class);\n\t\t\tif (annotated == null) {\n\t\t\t\tlogger.error(\"Annotation path node missing annotated element section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tAnnotationInfo annotation = annotationPath.getValue();\n\t\t\treturn contextMenuService.getAnnotationContextMenuProvider(source, workspace, resource, bundle, annotated, annotation).makeMenu();\n\t\t} else if (item instanceof BundlePathNode bundlePath) {\n\t\t\treturn contextMenuService.getBundleContextMenuProvider(source, workspace, resource, bundlePath.getValue()).makeMenu();\n\t\t} else if (item instanceof ResourcePathNode) {\n\t\t\treturn contextMenuService.getResourceContextMenuProvider(source, workspace, resource).makeMenu();\n\t\t} else if (item instanceof AssemblerPathNode assemblerPath) {\n\t\t\tClassBundle<?> bundle = assemblerPath.getValueOfType(ClassBundle.class);\n\t\t\tif (bundle == null) {\n\t\t\t\tlogger.error(\"Assembler path node missing bundle section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tClassInfo declaring = assemblerPath.getValueOfType(ClassInfo.class);\n\t\t\tif (declaring == null) {\n\t\t\t\tlogger.error(\"Assembler path node missing declaring class section: {}\", item);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn contextMenuService.getAssemblerContextMenuProvider(source, workspace, resource, bundle, declaring, assemblerPath.getValue()).makeMenu();\n\t\t}\n\n\t\t// No menu\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param cell\n\t * \t\tCell to apply click handling to.\n\t * @param item\n\t * \t\tContent within the cell.\n\t * @param gotoNavigation\n\t * \t\tFlag to include {@link Actions#gotoDeclaration(PathNode)} behavior on double-click.\n\t *\n\t * @return An event handler for {@link Node#setOnMouseClicked(EventHandler)}\n\t * that handles generic click interactions with tree cells.\n\t */\n\t@Nonnull\n\tpublic EventHandler<? super MouseEvent> clickHandlerOf(@Nonnull TreeCell<?> cell, @Nonnull PathNode<?> item, boolean gotoNavigation) {\n\t\treturn e -> {\n\t\t\tif (e.getButton() == MouseButton.PRIMARY) {\n\t\t\t\t// Double-clicking leafs should 'open' their content.\n\t\t\t\t// Branches should recursively open.\n\t\t\t\tTreeItem<?> treeItem = cell.getTreeItem();\n\t\t\t\tif (e.getClickCount() == 2 && treeItem != null)\n\t\t\t\t\tif (treeItem.isLeaf()) {\n\t\t\t\t\t\tif (gotoNavigation) openPath(item);\n\t\t\t\t\t}\n\t\t\t\t\t// I know that it looks odd, that we recurse-open when its already expanded\n\t\t\t\t\t// but in-practice this results in a more consistent desired behavior and less rapid re-closures.\n\t\t\t\t\telse if (treeItem.isExpanded()) {\n\t\t\t\t\t\tTreeItems.recurseOpen(treeItem);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tTreeItems.recurseClose(cell.getTreeView(), treeItem);\n\t\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic CellConfigurationServiceConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/CellConfigurationServiceConfig.java",
    "content": "package software.coley.recaf.services.cell;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link CellConfigurationService}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class CellConfigurationServiceConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic CellConfigurationServiceConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, CellConfigurationService.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/AbstractContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.navigation.Actions;\n\n/**\n * Common base to context menu provider factories.\n *\n * @author Matt Coley\n */\npublic abstract class AbstractContextMenuProviderFactory implements ContextMenuProviderFactory {\n\tprotected final TextProviderService textService;\n\tprotected final IconProviderService iconService;\n\tprotected final Actions actions;\n\n\tprotected AbstractContextMenuProviderFactory(@Nonnull TextProviderService textService,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull IconProviderService iconService,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull Actions actions) {\n\t\tthis.textService = textService;\n\t\tthis.iconService = iconService;\n\t\tthis.actions = actions;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/AnnotationContextMenuAdapter.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu adapter for {@link AnnotationInfo} types.\n *\n * @author Matt Coley\n */\npublic interface AnnotationContextMenuAdapter extends ContextMenuAdapter {\n\t/**\n\t * @param menu\n\t * \t\tThe menu to adapt.\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param annotated\n\t * \t\tThe annotated item.\n\t * @param annotation\n\t * \t\tThe annotation the menu is for.\n\t */\n\tvoid adaptAnnotationContextMenu(@Nonnull ContextMenu menu,\n\t                                @Nonnull ContextSource source,\n\t                                @Nonnull Workspace workspace,\n\t                                @Nonnull WorkspaceResource resource,\n\t                                @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                @Nonnull Annotated annotated,\n\t                                @Nonnull AnnotationInfo annotation);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/AnnotationContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu provider for {@link AnnotationInfo} types.\n *\n * @author Matt Coley\n */\npublic interface AnnotationContextMenuProviderFactory extends ContextMenuProviderFactory {\n\t/**\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param annotated\n\t * \t\tThe annotated item.\n\t * @param annotation\n\t * \t\tThe annotation to create a menu for.\n\t *\n\t * @return Menu provider for the annotation.\n\t */\n\t@Nonnull\n\tdefault ContextMenuProvider getAnnotationContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull Annotated annotated,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull AnnotationInfo annotation) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/AssemblerContextMenuAdapter.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.AssemblerPathData;\nimport software.coley.recaf.ui.pane.editing.assembler.AssemblerPane;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu adapter for contents within an {@link AssemblerPane}.\n *\n * @author Matt Coley\n */\npublic interface AssemblerContextMenuAdapter extends ContextMenuAdapter {\n\t/**\n\t * @param menu\n\t * \t\tThe menu to adapt.\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param assemblerData\n\t * \t\tThe assembler data the menu is for.\n\t */\n\tvoid adaptAssemblerMenu(@Nonnull ContextMenu menu,\n\t                        @Nonnull ContextSource source,\n\t                        @Nonnull Workspace workspace,\n\t                        @Nonnull WorkspaceResource resource,\n\t                        @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                        @Nonnull ClassInfo declaringClass,\n\t                        @Nonnull AssemblerPathData assemblerData);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/AssemblerContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.AssemblerPathData;\nimport software.coley.recaf.ui.pane.editing.assembler.AssemblerPane;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu provider for contents within an {@link AssemblerPane}.\n *\n * @author Matt Coley\n */\npublic interface AssemblerContextMenuProviderFactory extends ContextMenuProviderFactory {\n\t/**\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param assemblerData\n\t * \t\tThe assembler data to create a menu for.\n\t *\n\t * @return Menu provider for the selected contents in the assembler.\n\t */\n\t@Nonnull\n\tdefault ContextMenuProvider getAssemblerMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull AssemblerPathData assemblerData) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicAnnotationContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ContextMenu;\nimport org.objectweb.asm.Type;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.cell.icon.IconProvider;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProvider;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.ui.contextmenu.ContextMenuBuilder;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.ARROW_RIGHT;\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.TRASH_CAN;\nimport static software.coley.collections.Unchecked.runnable;\n\n/**\n * Basic implementation for {@link AnnotationContextMenuProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicAnnotationContextMenuProviderFactory extends AbstractContextMenuProviderFactory\n\t\timplements AnnotationContextMenuProviderFactory {\n\tprivate static final Logger logger = Logging.get(BasicAnnotationContextMenuProviderFactory.class);\n\n\t@Inject\n\tpublic BasicAnnotationContextMenuProviderFactory(@Nonnull TextProviderService textService,\n\t                                                 @Nonnull IconProviderService iconService,\n\t                                                 @Nonnull Actions actions) {\n\t\tsuper(textService, iconService, actions);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ContextMenuProvider getAnnotationContextMenuProvider(@Nonnull ContextSource source,\n\t                                                            @Nonnull Workspace workspace,\n\t                                                            @Nonnull WorkspaceResource resource,\n\t                                                            @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                                            @Nonnull Annotated annotated,\n\t                                                            @Nonnull AnnotationInfo annotation) {\n\t\treturn () -> {\n\t\t\tTextProvider nameProvider = textService.getAnnotationTextProvider(workspace, resource, bundle, annotated, annotation);\n\t\t\tIconProvider iconProvider = iconService.getAnnotationIconProvider(workspace, resource, bundle, annotated, annotation);\n\t\t\tContextMenu menu = new ContextMenu();\n\t\t\taddHeader(menu, nameProvider.makeText(), iconProvider.makeIcon());\n\t\t\tvar builder = new ContextMenuBuilder(menu, source).forAnnotation(workspace, resource, bundle, annotated, annotation);\n\n\t\t\tString annotationType = Type.getType(annotation.getDescriptor()).getInternalName();\n\n\t\t\tClassPathNode annotationDecPath = workspace.findClass(annotationType);\n\t\t\tif (annotationDecPath != null)\n\t\t\t\tbuilder.item(\"menu.goto.class\", ARROW_RIGHT, runnable(() -> actions.gotoDeclaration(annotationDecPath)));\n\n\t\t\tbuilder.item(\"menu.edit.remove.annotation\", TRASH_CAN, () -> actions.immediateDeleteAnnotations(bundle, annotated, annotationType));\n\n\t\t\treturn menu;\n\t\t};\n\t}\n\n\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicAssemblerContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ContextMenu;\nimport me.darknet.assembler.ast.primitive.ASTIdentifier;\nimport me.darknet.assembler.ast.primitive.ASTInstruction;\nimport me.darknet.assembler.ast.primitive.ASTLabel;\nimport me.darknet.assembler.ast.specific.ASTMethod;\nimport me.darknet.assembler.util.BlwOpcodes;\nimport me.darknet.assembler.util.Location;\nimport me.darknet.assembler.util.Range;\nimport org.fxmisc.richtext.CodeArea;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.AssemblerPathData;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.IncompletePathException;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.ui.control.ActionMenuItem;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.AssemblyResolution;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.ClassAnnotationResolution;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.ClassExtends;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.ClassImplements;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.FieldAnnotationResolution;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.FieldResolution;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.IndependentAnnotationResolution;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.InnerClassResolution;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.InstructionResolution;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.LabelDeclarationResolution;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.LabelReferenceResolution;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.MethodAnnotationResolution;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.MethodResolution;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.TypeReferenceResolution;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.VariableDeclarationResolution;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.ARROW_RIGHT;\nimport static org.objectweb.asm.Opcodes.*;\nimport static software.coley.collections.Unchecked.runnable;\nimport static software.coley.recaf.util.Menus.action;\n\n/**\n * Basic implementation for {@link AssemblerContextMenuProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicAssemblerContextMenuProviderFactory extends AbstractContextMenuProviderFactory implements AssemblerContextMenuProviderFactory {\n\tprivate static final Logger logger = Logging.get(BasicAssemblerContextMenuProviderFactory.class);\n\tprivate static final Map<Class<? extends AssemblyResolution>, ResolutionMenuFiller<AssemblyResolution>> fillers = new IdentityHashMap<>();\n\n\t@Inject\n\tpublic BasicAssemblerContextMenuProviderFactory(@Nonnull TextProviderService textService,\n\t                                                @Nonnull IconProviderService iconService,\n\t                                                @Nonnull Actions actions) {\n\t\tsuper(textService, iconService, actions);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ContextMenuProvider getAssemblerMenuProvider(@Nonnull ContextSource source,\n\t                                                    @Nonnull Workspace workspace,\n\t                                                    @Nonnull WorkspaceResource resource,\n\t                                                    @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                                    @Nonnull ClassInfo declaringClass,\n\t                                                    @Nonnull AssemblerPathData assemblerData) {\n\t\treturn () -> {\n\t\t\tContextMenu menu = new ContextMenu();\n\t\t\tAssemblyResolution resolution = assemblerData.resolution();\n\t\t\tEditor editor = assemblerData.editor();\n\t\t\tResolutionMenuFiller<AssemblyResolution> filler = fillers.get(resolution.getClass());\n\t\t\tif (filler != null) filler.accept(this, menu, editor, workspace, resolution);\n\t\t\tif (menu.getItems().isEmpty()) return null;\n\t\t\treturn menu;\n\t\t};\n\t}\n\n\t/**\n\t * Helper to register {@link ResolutionMenuFiller} instances.\n\t *\n\t * @param type\n\t * \t\tSubtype of {@link AssemblyResolution}.\n\t * @param filler\n\t * \t\tFiller for the given subtype.\n\t * @param <T>\n\t * \t\tSubtype of {@link AssemblyResolution}.\n\t */\n\tprivate static <T extends AssemblyResolution> void register(@Nonnull Class<T> type, @Nonnull ResolutionMenuFiller<T> filler) {\n\t\tfillers.put(type, Unchecked.cast(filler));\n\t}\n\n\tstatic {\n\t\tregister(ClassImplements.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\tClassPathNode classPath = workspace.findClass(resolution.implemented().literal());\n\t\t\tActionMenuItem action = action(\"menu.goto.class\", ARROW_RIGHT, () -> {\n\t\t\t\ttry {\n\t\t\t\t\tprovider.actions.gotoDeclaration(Objects.requireNonNull(classPath));\n\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\tlogger.error(\"Cannot go to class due to incomplete path\", ex);\n\t\t\t\t}\n\t\t\t});\n\t\t\tif (classPath == null) action.setDisable(true);\n\t\t\tmenu.getItems().add(action);\n\n\t\t\t// TODO:\n\t\t\t//  - Implement methods (for methods not already present in the ASTClass)\n\t\t});\n\t\tregister(ClassExtends.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\tClassPathNode classPath = workspace.findClass(resolution.superName().literal());\n\t\t\tActionMenuItem action = action(\"menu.goto.class\", ARROW_RIGHT, () -> {\n\t\t\t\ttry {\n\t\t\t\t\tprovider.actions.gotoDeclaration(Objects.requireNonNull(classPath));\n\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\tlogger.error(\"Cannot go to class due to incomplete path\", ex);\n\t\t\t\t}\n\t\t\t});\n\t\t\tif (classPath == null) action.setDisable(true);\n\t\t\tmenu.getItems().add(action);\n\n\t\t\t// TODO:\n\t\t\t//  - Override methods (for methods not already present in the ASTClass)\n\t\t});\n\t\tregister(ClassAnnotationResolution.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\t// No items\n\t\t});\n\t\tregister(FieldAnnotationResolution.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\t// No items\n\t\t});\n\t\tregister(MethodAnnotationResolution.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\t// No items\n\t\t});\n\t\tregister(IndependentAnnotationResolution.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\t// No items\n\t\t});\n\t\tregister(InnerClassResolution.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\t// No items\n\t\t});\n\t\tregister(FieldResolution.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\t// No items\n\t\t});\n\t\tregister(MethodResolution.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\t// No items\n\t\t});\n\t\tregister(InstructionResolution.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\t// TODO:\n\t\t\t//  - Different actions for different instructions (only differentiable by instruction name string atm)\n\t\t\t//  - Convert integer representations (Hex, binary, decimal)\n\t\t\t//  - Goto declaration for type/field/method references\n\t\t\t//  - Goto declaration for jump instructions (with case hint)\n\t\t\tASTInstruction instruction = resolution.instruction();\n\t\t\tASTIdentifier identifier = instruction.identifier();\n\t\t\tif (identifier != null && identifier.content() != null) {\n\t\t\t\tint opcode = BlwOpcodes.opcode(identifier.content());\n\t\t\t\tif (opcode >= IFEQ && opcode <= JSR && !instruction.arguments().isEmpty()) {\n\t\t\t\t\tString labelName = instruction.arguments().getLast().content();\n\t\t\t\t\tif (labelName != null)\n\t\t\t\t\t\tmenu.getItems().add(action(\"menu.goto.label\", ARROW_RIGHT, () -> {\n\t\t\t\t\t\t\tCodeArea area = editor.getCodeArea();\n\t\t\t\t\t\t\tgotoLabelDeclaration(resolution.method(), editor.getCodeArea(), labelName);\n\t\t\t\t\t\t}));\n\t\t\t\t} else if (opcode == NEW || opcode == CHECKCAST || opcode == INSTANCEOF) {\n\t\t\t\t\tif (instruction.arguments().getLast() instanceof ASTIdentifier typeIdentifier) {\n\t\t\t\t\t\tString typeName = typeIdentifier.content();\n\t\t\t\t\t\tif (typeName != null) {\n\t\t\t\t\t\t\tClassPathNode typePath = workspace.findClass(typeName);\n\t\t\t\t\t\t\tif (typePath != null) {\n\t\t\t\t\t\t\t\t// TODO: We want to show the type that is targeted with this, similar to how we have in\n\t\t\t\t\t\t\t\t//  other context menus. This code is also messy and there's probably some abstractions\n\t\t\t\t\t\t\t\t//  we can make to reduce the indentation hell.\n\t\t\t\t\t\t\t\tmenu.getItems().add(action(\"menu.goto.class\", ARROW_RIGHT, runnable(() -> provider.actions.gotoDeclaration(typePath))));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tregister(VariableDeclarationResolution.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\t// TODO:\n\t\t\t//  - Goto usage(s)\n\t\t});\n\t\tregister(LabelDeclarationResolution.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\t// TODO:\n\t\t\t//  - Goto usage(s)\n\t\t});\n\t\tregister(LabelReferenceResolution.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\tString target = resolution.labelName().content();\n\t\t\tif (target != null)\n\t\t\t\tmenu.getItems().add(action(\"menu.goto.label\", ARROW_RIGHT, () -> {\n\t\t\t\t\tCodeArea area = editor.getCodeArea();\n\t\t\t\t\tgotoLabelDeclaration(resolution.method(), area, target);\n\t\t\t\t}));\n\t\t});\n\t\tregister(TypeReferenceResolution.class, (provider, menu, editor, workspace, resolution) -> {\n\t\t\tClassPathNode classPath = workspace.findClass(resolution.typeName().literal());\n\t\t\tActionMenuItem action = action(\"menu.goto.class\", ARROW_RIGHT, () -> {\n\t\t\t\ttry {\n\t\t\t\t\tprovider.actions.gotoDeclaration(Objects.requireNonNull(classPath));\n\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\tlogger.error(\"Cannot go to class due to incomplete path\", ex);\n\t\t\t\t}\n\t\t\t});\n\t\t\tif (classPath == null) action.setDisable(true);\n\t\t\tmenu.getItems().add(action);\n\t\t});\n\t}\n\n\tprivate static void gotoLabelDeclaration(@Nonnull ASTMethod method, @Nonnull CodeArea area, @Nonnull String target) {\n\t\tList<ASTInstruction> instructions = method.code().instructions();\n\t\tfor (ASTInstruction instruction : instructions) {\n\t\t\tif (instruction instanceof ASTLabel label && Objects.equals(label.identifier().content(), target)) {\n\t\t\t\tRange range = Objects.requireNonNull(label.range());\n\t\t\t\tLocation location = Objects.requireNonNull(label.location());\n\t\t\t\tarea.selectRange(range.start(), range.end());\n\t\t\t\tarea.showParagraphAtCenter(location.line());\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @param <R>\n\t * \t\tResolution impl type.\n\t */\n\tprivate interface ResolutionMenuFiller<R extends AssemblyResolution> {\n\t\tvoid accept(@Nonnull AbstractContextMenuProviderFactory provider, @Nonnull ContextMenu menu, @Nonnull Editor editor,\n\t\t            @Nonnull Workspace workspace, @Nonnull R resolution);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicBlacklistingContextSource.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.function.Predicate;\n\n/**\n * Basic context source with whitelisting for {@link #allow(String)}.\n *\n * @author Matt Coley\n */\npublic class BasicBlacklistingContextSource extends BasicWhitelistingContextSource {\n\t/**\n\t * @param isDeclaration\n\t *        {@code true} for the source to model a declaration.\n\t *        {@code false} for the source to model a reference.\n\t * @param whitelist\n\t * \t\tBlacklist function, where {@code false} is allowed.\n\t */\n\tpublic BasicBlacklistingContextSource(boolean isDeclaration, @Nonnull Predicate<String> whitelist) {\n\t\tsuper(isDeclaration, whitelist.negate());\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicBundleContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ContextMenu;\nimport javafx.stage.WindowEvent;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.services.cell.icon.IconProvider;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProvider;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.ui.contextmenu.ContextMenuBuilder;\nimport software.coley.recaf.ui.control.popup.ChangeClassVersionPopup;\nimport software.coley.recaf.ui.control.popup.DecompileAllPopup;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.*;\n\n/**\n * Basic implementation for {@link BundleContextMenuProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicBundleContextMenuProviderFactory extends AbstractContextMenuProviderFactory\n\t\timplements BundleContextMenuProviderFactory {\n\tprivate final Instance<DecompileAllPopup> decompileAllPaneProvider;\n\n\t@Inject\n\tpublic BasicBundleContextMenuProviderFactory(@Nonnull TextProviderService textService,\n\t                                             @Nonnull IconProviderService iconService,\n\t                                             @Nonnull Instance<DecompileAllPopup> decompileAllPaneProvider,\n\t                                             @Nonnull Actions actions) {\n\t\tsuper(textService, iconService, actions);\n\t\tthis.decompileAllPaneProvider = decompileAllPaneProvider;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ContextMenuProvider getBundleContextMenuProvider(@Nonnull ContextSource source,\n\t                                                        @Nonnull Workspace workspace,\n\t                                                        @Nonnull WorkspaceResource resource,\n\t                                                        @Nonnull Bundle<? extends Info> bundle) {\n\t\treturn () -> {\n\t\t\tTextProvider nameProvider = textService.getBundleTextProvider(workspace, resource, bundle);\n\t\t\tIconProvider iconProvider = iconService.getBundleIconProvider(workspace, resource, bundle);\n\t\t\tContextMenu menu = new ContextMenu();\n\t\t\taddHeader(menu, nameProvider.makeText(), iconProvider.makeIcon());\n\t\t\tvar builder = new ContextMenuBuilder(menu, source).forBundle(workspace, resource, bundle);\n\t\t\tvar edit = builder.submenu(\"menu.edit\", EDIT);\n\t\t\tedit.item(\"misc.clear\", TRASH_CAN, bundle::clear);\n\n\t\t\tif (bundle instanceof JvmClassBundle jvmBundle) {\n\t\t\t\tedit.item(\"menu.edit.changeversion\", ARROWS_VERTICAL, () -> {\n\t\t\t\t\tChangeClassVersionPopup popup = new ChangeClassVersionPopup();\n\t\t\t\t\tpopup.setTargetBundle(jvmBundle);\n\t\t\t\t\tpopup.show();\n\t\t\t\t});\n\n\t\t\t\tbuilder.item(\"menu.export.classes\", DOCUMENT_EXPORT, () -> actions.exportClasses(workspace, resource, jvmBundle));\n\t\t\t\tbuilder.item(\"menu.file.decompileall\", DOCUMENT_EXPORT, () -> {\n\t\t\t\t\tDecompileAllPopup popup = decompileAllPaneProvider.get();\n\t\t\t\t\tpopup.addEventFilter(WindowEvent.WINDOW_HIDDEN, e -> decompileAllPaneProvider.destroy(popup));\n\t\t\t\t\tpopup.setTargetBundle(jvmBundle);\n\t\t\t\t\tpopup.show();\n\t\t\t\t});\n\t\t\t} else if (bundle instanceof FileBundle fileBundle) {\n\t\t\t\tbuilder.item(\"menu.export.files\", DOCUMENT_EXPORT, () -> actions.exportFiles(workspace, resource, fileBundle));\n\t\t\t}\n\t\t\treturn menu;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicClassContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.collections.ObservableList;\nimport javafx.scene.control.ContextMenu;\nimport javafx.scene.control.MenuItem;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.services.cell.icon.IconProvider;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProvider;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.search.match.StringPredicateProvider;\nimport software.coley.recaf.ui.contextmenu.ContextMenuBuilder;\nimport software.coley.recaf.ui.control.popup.ChangeClassVersionPopup;\nimport software.coley.recaf.ui.pane.search.ClassReferenceSearchPane;\nimport software.coley.recaf.ui.pane.search.MemberReferenceSearchPane;\nimport software.coley.recaf.ui.pane.search.SearchContextSource;\nimport software.coley.recaf.util.ClipboardUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.*;\nimport static software.coley.recaf.util.Menus.action;\n\n/**\n * Basic implementation for {@link ClassContextMenuProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicClassContextMenuProviderFactory extends AbstractContextMenuProviderFactory\n\t\timplements ClassContextMenuProviderFactory {\n\tprivate static final Logger logger = Logging.get(BasicClassContextMenuProviderFactory.class);\n\n\t@Inject\n\tpublic BasicClassContextMenuProviderFactory(@Nonnull TextProviderService textService,\n\t                                            @Nonnull IconProviderService iconService,\n\t                                            @Nonnull Actions actions) {\n\t\tsuper(textService, iconService, actions);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ContextMenuProvider getJvmClassInfoContextMenuProvider(@Nonnull ContextSource source,\n\t                                                              @Nonnull Workspace workspace,\n\t                                                              @Nonnull WorkspaceResource resource,\n\t                                                              @Nonnull JvmClassBundle bundle,\n\t                                                              @Nonnull JvmClassInfo info) {\n\t\treturn () -> {\n\t\t\tContextMenu menu = createMenu(source, workspace, resource, bundle, info);\n\t\t\tpopulateJvmMenu(menu, source, workspace, resource, bundle, info);\n\t\t\treturn menu;\n\t\t};\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ContextMenuProvider getAndroidClassInfoContextMenuProvider(@Nonnull ContextSource source,\n\t                                                                  @Nonnull Workspace workspace,\n\t                                                                  @Nonnull WorkspaceResource resource,\n\t                                                                  @Nonnull AndroidClassBundle bundle,\n\t                                                                  @Nonnull AndroidClassInfo info) {\n\t\treturn () -> {\n\t\t\tContextMenu menu = createMenu(source, workspace, resource, bundle, info);\n\t\t\tpopulateAndroidMenu(menu, source, workspace, resource, bundle, info);\n\t\t\treturn menu;\n\t\t};\n\t}\n\n\t/**\n\t * @param source\n\t * \t\tContext source.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create a menu for.\n\t *\n\t * @return Initial menu header for the class.\n\t */\n\tprivate ContextMenu createMenu(@Nonnull ContextSource source,\n\t                               @Nonnull Workspace workspace,\n\t                               @Nonnull WorkspaceResource resource,\n\t                               @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                               @Nonnull ClassInfo info) {\n\t\tTextProvider nameProvider;\n\t\tIconProvider iconProvider;\n\t\tif (info.isJvmClass()) {\n\t\t\tnameProvider = textService.getClassInfoTextProvider(workspace, resource, bundle, info.asJvmClass());\n\t\t\ticonProvider = iconService.getJvmClassInfoIconProvider(workspace, resource,\n\t\t\t\t\t(JvmClassBundle) bundle, info.asJvmClass());\n\t\t} else if (info.isAndroidClass()) {\n\t\t\tnameProvider = textService.getAndroidClassInfoTextProvider(workspace, resource,\n\t\t\t\t\t(AndroidClassBundle) bundle, info.asAndroidClass());\n\t\t\ticonProvider = iconService.getAndroidClassInfoIconProvider(workspace, resource,\n\t\t\t\t\t(AndroidClassBundle) bundle, info.asAndroidClass());\n\t\t} else {\n\t\t\tthrow new IllegalStateException(\"Unknown class type: \" + info.getClass().getName());\n\t\t}\n\t\tContextMenu menu = new ContextMenu();\n\t\taddHeader(menu, nameProvider.makeText(), iconProvider.makeIcon());\n\t\treturn menu;\n\t}\n\n\n\t/**\n\t * Append JVM specific operations to the given menu.\n\t *\n\t * @param menu\n\t * \t\tMenu to append content to.\n\t * @param source\n\t * \t\tContext source.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create a menu for.\n\t */\n\tprivate void populateJvmMenu(@Nonnull ContextMenu menu,\n\t                             @Nonnull ContextSource source,\n\t                             @Nonnull Workspace workspace,\n\t                             @Nonnull WorkspaceResource resource,\n\t                             @Nonnull JvmClassBundle bundle,\n\t                             @Nonnull JvmClassInfo info) {\n\t\tvar builder = new ContextMenuBuilder(menu, source).forInfo(workspace, resource, bundle, info);\n\t\tif (source.isReference()) {\n\t\t\tbuilder.infoItem(\"menu.goto.class\", ARROW_RIGHT, actions::gotoDeclaration);\n\n\t\t\t// Convenience for search results\n\t\t\tif (source instanceof SearchContextSource) {\n\t\t\t\tbuilder.item(\"menu.edit.assemble.class\", EDIT, Unchecked.runnable(() ->\n\t\t\t\t\t\tactions.openAssembler(PathNodes.classPath(workspace, resource, bundle, info))\n\t\t\t\t));\n\t\t\t}\n\t\t} else if (source.isDeclaration()) {\n\t\t\t// Edit menu\n\t\t\tvar edit = builder.submenu(\"menu.edit\", EDIT);\n\t\t\tedit.item(\"menu.edit.assemble.class\", EDIT, Unchecked.runnable(() ->\n\t\t\t\t\tactions.openAssembler(PathNodes.classPath(workspace, resource, bundle, info))\n\t\t\t));\n\t\t\tedit.infoItem(\"menu.edit.add.field\", ADD_ALT, actions::addClassField);\n\t\t\tedit.infoItem(\"menu.edit.add.method\", ADD_ALT, actions::addClassMethod);\n\t\t\tedit.infoItem(\"menu.edit.override.method\", HEALTH_CROSS, actions::overrideClassMethod);\n\t\t\tedit.item(\"menu.edit.changeversion\", ARROWS_VERTICAL, () -> {\n\t\t\t\tChangeClassVersionPopup popup = new ChangeClassVersionPopup();\n\t\t\t\tpopup.setTargetClass(bundle, info);\n\t\t\t\tpopup.show();\n\t\t\t});\n\t\t\tedit.infoItem(\"menu.edit.remove.field\", CLOSE, actions::deleteClassFields).disableWhen(info.getFields().isEmpty());\n\t\t\tedit.infoItem(\"menu.edit.remove.method\", CLOSE, actions::deleteClassMethods).disableWhen(info.getMethods().isEmpty());\n\t\t\tedit.infoItem(\"menu.edit.remove.annotation\", CLOSE, actions::deleteClassAnnotations).disableWhen(info.getAnnotations().isEmpty());\n\t\t\tedit.infoItem(\"menu.edit.copy\", COPY_FILE, actions::copyClass);\n\t\t\tedit.infoItem(\"menu.edit.delete\", COPY_FILE, actions::deleteClass);\n\t\t}\n\n\t\t// Search actions\n\t\tvar search = builder.submenu(\"menu.search\", SEARCH);\n\t\tsearch.item(\"menu.search.class.member-references\", CODE_REFERENCE, () -> {\n\t\t\tMemberReferenceSearchPane pane = actions.openNewMemberReferenceSearch();\n\t\t\tpane.ownerPredicateIdProperty().setValue(StringPredicateProvider.KEY_EQUALS);\n\t\t\tpane.ownerValueProperty().setValue(info.getName());\n\t\t});\n\t\tsearch.item(\"menu.search.class.type-references\", CODE_REFERENCE, () -> {\n\t\t\tClassReferenceSearchPane pane = actions.openNewClassReferenceSearch();\n\t\t\tpane.typePredicateIdProperty().setValue(StringPredicateProvider.KEY_EQUALS);\n\t\t\tpane.typeValueProperty().setValue(info.getName());\n\t\t});\n\n\t\t// Refactor actions\n\t\tif (!resource.isInternal()) {\n\t\t\tvar refactor = builder.submenu(\"menu.refactor\", PAINT_BRUSH);\n\t\t\trefactor.infoItem(\"menu.refactor.rename\", TAG_EDIT, actions::renameClass);\n\t\t\trefactor.infoItem(\"menu.refactor.move\", STACKED_MOVE, actions::moveClass);\n\t\t}\n\n\t\t// Copy path\n\t\tbuilder.item(\"menu.tab.copypath\", COPY_LINK, () -> ClipboardUtil.copyString(info));\n\n\t\t// Documentation actions\n\t\tbuilder.infoItem(\"menu.analysis.comment\", ADD_COMMENT, actions::openCommentEditing);\n\n\t\t// Export actions\n\t\tbuilder.infoItem(\"menu.export.class\", EXPORT, actions::exportClass);\n\n\t\t// TODO: implement operations\n\t\t//  - View\n\t\t//    - Class hierarchy\n\t\t//  - Deobfuscate\n\t\t//    - Suggest class name / purpose\n\t\t//    - Suggest method names / purposes (get/set)\n\t\t//    - Organize fields (constants -> finals -> non-finals\n\t}\n\n\t/**\n\t * Append Android specific operations to the given menu.\n\t *\n\t * @param menu\n\t * \t\tMenu to append content to.\n\t * @param source\n\t * \t\tContext source.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create a menu for.\n\t */\n\tprivate void populateAndroidMenu(@Nonnull ContextMenu menu,\n\t                                 @Nonnull ContextSource source,\n\t                                 @Nonnull Workspace workspace,\n\t                                 @Nonnull WorkspaceResource resource,\n\t                                 @Nonnull AndroidClassBundle bundle,\n\t                                 @Nonnull AndroidClassInfo info) {\n\t\t// TODO: implement operations\n\t\t//  - Edit\n\t\t//    - (class assembler)\n\t\t//    - Add field\n\t\t//    - Add method\n\t\t//    - Add annotation\n\t\t//    - Remove fields\n\t\t//    - Remove methods\n\t\t//    - Remove annotations\n\t\t//  - Copy\n\t\t//  - Delete\n\t\t//  - Refactor\n\t\t//    - Rename\n\t\t//    - Move\n\t\t//  - Search references\n\t\t//  - View\n\t\t//    - Class hierarchy\n\t\t//  - Deobfuscate\n\t\t//    - Suggest class name / purpose\n\t\t//    - Suggest method names / purposes (get/set)\n\t\tObservableList<MenuItem> items = menu.getItems();\n\t\titems.add(action(\"menu.goto.class\", CarbonIcons.ARROW_RIGHT,\n\t\t\t\t() -> actions.gotoDeclaration(workspace, resource, bundle, info)));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicContextSource.java",
    "content": "package software.coley.recaf.services.cell.context;\n\n/**\n * Basic context source. Either a declaration or a reference.\n *\n * @see BasicBlacklistingContextSource Simple blacklist filtering source.\n * @see BasicWhitelistingContextSource Simple whitelist filtering source.\n *\n * @author Matt Coley\n */\npublic class BasicContextSource implements ContextSource {\n\tprivate final boolean isDeclaration;\n\n\t/**\n\t * @param isDeclaration\n\t *        {@code true} for the source to model a declaration.\n\t *        {@code false} for the source to model a reference.\n\t */\n\tpublic BasicContextSource(boolean isDeclaration) {\n\t\tthis.isDeclaration = isDeclaration;\n\t}\n\n\t@Override\n\tpublic boolean isDeclaration() {\n\t\treturn isDeclaration;\n\t}\n\n\t@Override\n\tpublic boolean isReference() {\n\t\treturn !isDeclaration;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tBasicContextSource that = (BasicContextSource) o;\n\n\t\treturn isDeclaration == that.isDeclaration;\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn (isDeclaration ? 1 : 0);\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"BasicContextSource{\" +\n\t\t\t\t\"isDeclaration=\" + isDeclaration +\n\t\t\t\t'}';\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicDirectoryContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.services.cell.icon.IconProvider;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProvider;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.ui.contextmenu.ContextMenuBuilder;\nimport software.coley.recaf.util.ClipboardUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.*;\n\n/**\n * Basic implementation for {@link DirectoryContextMenuProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicDirectoryContextMenuProviderFactory extends AbstractContextMenuProviderFactory\n\t\timplements DirectoryContextMenuProviderFactory {\n\n\t@Inject\n\tpublic BasicDirectoryContextMenuProviderFactory(@Nonnull TextProviderService textService,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull IconProviderService iconService,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull Actions actions) {\n\t\tsuper(textService, iconService, actions);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ContextMenuProvider getDirectoryContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull FileBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull String directoryName) {\n\t\treturn () -> {\n\t\t\tTextProvider nameProvider = textService.getDirectoryTextProvider(workspace, resource, bundle, directoryName);\n\t\t\tIconProvider iconProvider = iconService.getDirectoryIconProvider(workspace, resource, bundle, directoryName);\n\t\t\tContextMenu menu = new ContextMenu();\n\t\t\taddHeader(menu, nameProvider.makeText(), iconProvider.makeIcon());\n\t\t\tvar builder = new ContextMenuBuilder(menu, source).forDirectory(workspace, resource, bundle, directoryName);\n\n\t\t\tif (source.isDeclaration()) {\n\t\t\t\tbuilder.directoryItem(\"menu.edit.copy\", COPY_FILE, actions::copyDirectory);\n\t\t\t\tbuilder.directoryItem(\"menu.edit.delete\", TRASH_CAN, actions::deleteDirectory);\n\n\t\t\t\tvar refactor = builder.submenu(\"menu.refactor\", PAINT_BRUSH);\n\t\t\t\trefactor.directoryItem(\"menu.refactor.move\", STACKED_MOVE, actions::moveDirectory);\n\t\t\t\trefactor.directoryItem(\"menu.refactor.rename\", TAG_EDIT, actions::renameDirectory);\n\n\t\t\t\tbuilder.directoryItem(\"menu.export.directory\", EXPORT, actions::exportDirectory);\n\n\t\t\t\t// TODO: implement operations\n\t\t\t\t//  - Search references\n\t\t\t}\n\n\t\t\t// Copy path\n\t\t\tbuilder.item(\"menu.tab.copypath\", COPY_LINK, () -> ClipboardUtil.copyString(directoryName));\n\n\t\t\treturn menu;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicFieldContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ContextMenu;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.IncompletePathException;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.services.cell.icon.IconProvider;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProvider;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.search.match.StringPredicateProvider;\nimport software.coley.recaf.ui.contextmenu.ContextMenuBuilder;\nimport software.coley.recaf.ui.pane.search.MemberReferenceSearchPane;\nimport software.coley.recaf.ui.pane.search.SearchContextSource;\nimport software.coley.recaf.util.ClipboardUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.List;\n\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.*;\n\n/**\n * Basic implementation for {@link FieldContextMenuProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicFieldContextMenuProviderFactory extends AbstractContextMenuProviderFactory\n\t\timplements FieldContextMenuProviderFactory {\n\tprivate static final Logger logger = Logging.get(BasicFieldContextMenuProviderFactory.class);\n\n\t@Inject\n\tpublic BasicFieldContextMenuProviderFactory(@Nonnull TextProviderService textService,\n\t                                            @Nonnull IconProviderService iconService,\n\t                                            @Nonnull Actions actions) {\n\t\tsuper(textService, iconService, actions);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ContextMenuProvider getFieldContextMenuProvider(@Nonnull ContextSource source,\n\t                                                       @Nonnull Workspace workspace,\n\t                                                       @Nonnull WorkspaceResource resource,\n\t                                                       @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                                       @Nonnull ClassInfo declaringClass,\n\t                                                       @Nonnull FieldMember field) {\n\t\treturn () -> {\n\t\t\tTextProvider nameProvider = textService.getFieldMemberTextProvider(workspace, resource, bundle, declaringClass, field);\n\t\t\tIconProvider iconProvider = iconService.getClassMemberIconProvider(workspace, resource, bundle, declaringClass, field);\n\t\t\tContextMenu menu = new ContextMenu();\n\t\t\taddHeader(menu, nameProvider.makeText(), iconProvider.makeIcon());\n\t\t\tvar builder = new ContextMenuBuilder(menu, source).forMember(workspace, resource, bundle, declaringClass, field);\n\n\t\t\tif (source.isReference()) {\n\t\t\t\tbuilder.item(\"menu.goto.field\", ARROW_RIGHT, () -> {\n\t\t\t\t\tClassPathNode classPath = PathNodes.classPath(workspace, resource, bundle, declaringClass);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tactions.gotoDeclaration(classPath)\n\t\t\t\t\t\t\t\t.requestFocus(field);\n\t\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\t\tlogger.error(\"Cannot go to field due to incomplete path\", ex);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// Convenience for search results\n\t\t\t\tif (source instanceof SearchContextSource) {\n\t\t\t\t\tbuilder.item(\"menu.edit.assemble.field\", EDIT, () -> Unchecked.runnable(() -> actions.openAssembler(PathNodes.memberPath(workspace, resource, bundle, declaringClass, field))).run());\n\t\t\t\t}\n\t\t\t} else if (!resource.isInternal()) {\n\t\t\t\t// Edit menu\n\t\t\t\tvar edit = builder.submenu(\"menu.edit\", EDIT);\n\t\t\t\tedit.item(\"menu.edit.assemble.field\", EDIT, () -> Unchecked.runnable(() -> actions.openAssembler(PathNodes.memberPath(workspace, resource, bundle, declaringClass, field))).run());\n\t\t\t\tif (declaringClass.isJvmClass()) {\n\t\t\t\t\tJvmClassBundle jvmBundle = (JvmClassBundle) bundle;\n\t\t\t\t\tJvmClassInfo declaringJvmClass = declaringClass.asJvmClass();\n\n\t\t\t\t\tedit.item(\"menu.edit.copy\", COPY_FILE, () -> actions.copyMember(workspace, resource, jvmBundle, declaringJvmClass, field));\n\t\t\t\t\tedit.item(\"menu.edit.delete\", TRASH_CAN, () -> actions.deleteClassFields(workspace, resource, jvmBundle, declaringJvmClass, List.of(field)));\n\t\t\t\t\tedit.item(\"menu.edit.remove.annotation\", CLOSE, () -> actions.deleteMemberAnnotations(workspace, resource, jvmBundle, declaringJvmClass, field))\n\t\t\t\t\t\t\t.disableWhen(field.getAnnotations().isEmpty());\n\t\t\t\t}\n\n\t\t\t\t// TODO: implement operations\n\t\t\t\t//  - Edit\n\t\t\t\t//    - Add annotation\n\t\t\t}\n\n\t\t\t// Search actions\n\t\t\tbuilder.item(\"menu.search.field-references\", CODE_REFERENCE, () -> {\n\t\t\t\tMemberReferenceSearchPane pane = actions.openNewMemberReferenceSearch();\n\t\t\t\tpane.ownerPredicateIdProperty().setValue(StringPredicateProvider.KEY_EQUALS);\n\t\t\t\tpane.namePredicateIdProperty().setValue(StringPredicateProvider.KEY_EQUALS);\n\t\t\t\tpane.descPredicateIdProperty().setValue(StringPredicateProvider.KEY_EQUALS);\n\t\t\t\tpane.ownerValueProperty().setValue(declaringClass.getName());\n\t\t\t\tpane.nameValueProperty().setValue(field.getName());\n\t\t\t\tpane.descValueProperty().setValue(field.getDescriptor());\n\t\t\t});\n\n\t\t\t// Copy path\n\t\t\tbuilder.item(\"menu.tab.copypath\", COPY_LINK, () -> ClipboardUtil.copyString(declaringClass, field));\n\n\t\t\t// Documentation actions\n\t\t\tbuilder.memberItem(\"menu.analysis.comment\", ADD_COMMENT, actions::openCommentEditing);\n\n\t\t\t// Refactor actions\n\t\t\tif (!resource.isInternal())\n\t\t\t\tbuilder.memberItem(\"menu.refactor.rename\", TAG_EDIT, actions::renameField);\n\n\t\t\treturn menu;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicFileContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.services.cell.icon.IconProvider;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProvider;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.ui.contextmenu.ContextMenuBuilder;\nimport software.coley.recaf.util.ClipboardUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.*;\n\n/**\n * Basic implementation for {@link FileContextMenuProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicFileContextMenuProviderFactory extends AbstractContextMenuProviderFactory\n\t\timplements FileContextMenuProviderFactory {\n\t@Inject\n\tpublic BasicFileContextMenuProviderFactory(@Nonnull TextProviderService textService,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull IconProviderService iconService,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull Actions actions) {\n\t\tsuper(textService, iconService, actions);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ContextMenuProvider getFileInfoContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull FileBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull FileInfo info) {\n\t\treturn () -> {\n\t\t\tTextProvider nameProvider = textService.getFileInfoTextProvider(workspace, resource, bundle, info);\n\t\t\tIconProvider iconProvider = iconService.getFileInfoIconProvider(workspace, resource, bundle, info);\n\t\t\tContextMenu menu = new ContextMenu();\n\t\t\taddHeader(menu, nameProvider.makeText(), iconProvider.makeIcon());\n\t\t\tvar builder = new ContextMenuBuilder(menu, source).forInfo(workspace, resource, bundle, info);\n\n\t\t\tFilePathNode filePath = PathNodes.filePath(workspace, resource, bundle, info);\n\t\t\tif (source.isReference()) {\n\t\t\t\tbuilder.item(\"menu.goto.file\", ARROW_RIGHT, Unchecked.runnable(() -> actions.gotoDeclaration(filePath)));\n\t\t\t} else if (source.isDeclaration()) {\n\t\t\t\tbuilder.item(\"menu.tab.copypath\", COPY_LINK, () -> ClipboardUtil.copyString(info));\n\t\t\t\tbuilder.infoItem(\"menu.edit.copy\", COPY_FILE, actions::copyFile);\n\t\t\t\tbuilder.infoItem(\"menu.edit.delete\", TRASH_CAN, actions::deleteFile);\n\n\t\t\t\t// Refactor actions\n\t\t\t\tvar refactor = builder.submenu(\"menu.refactor\", PAINT_BRUSH);\n\t\t\t\trefactor.infoItem(\"menu.refactor.move\", STACKED_MOVE, actions::moveFile);\n\t\t\t\trefactor.infoItem(\"menu.refactor.rename\", TAG_EDIT, actions::renameFile);\n\n\t\t\t\t// TODO: implement operations\n\t\t\t\t//  - Search references\n\t\t\t\t//  - Override text-view language (FileTypeAssociationService)\n\n\t\t\t\t// Export actions\n\t\t\t\tbuilder.infoItem(\"menu.export.file\", EXPORT, actions::exportClass);\n\t\t\t}\n\t\t\treturn menu;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicInnerClassContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.services.cell.context.ClassContextMenuProviderFactory;\nimport software.coley.recaf.services.cell.context.ContextMenuProvider;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.services.cell.context.InnerClassContextMenuProviderFactory;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link InnerClassContextMenuProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicInnerClassContextMenuProviderFactory implements InnerClassContextMenuProviderFactory {\n\tprivate final ClassContextMenuProviderFactory classContextFactory;\n\n\t@Inject\n\tpublic BasicInnerClassContextMenuProviderFactory(@Nonnull ClassContextMenuProviderFactory classContextFactory) {\n\t\tthis.classContextFactory = classContextFactory;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ContextMenuProvider getInnerClassInfoContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassInfo outerClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull InnerClassInfo inner) {\n\t\treturn () -> {\n\t\t\t// While the inner class attribute gives us access flags, we want to grab the REAL\n\t\t\t// class-info instance for the inner class. This allows us to check for properties and such.\n\t\t\tClassInfo innerClass = bundle.get(inner.getInnerClassName());\n\t\t\tif (innerClass == null)\n\t\t\t\treturn null;\n\n\t\t\t// Delegate initial menu creation to standard class menu.\n\t\t\tContextMenu menu;\n\t\t\tif (innerClass.isJvmClass()) {\n\t\t\t\tmenu = classContextFactory.getJvmClassInfoContextMenuProvider(source, workspace, resource,\n\t\t\t\t\t\t(JvmClassBundle) bundle, innerClass.asJvmClass()).makeMenu();\n\t\t\t} else if (innerClass.isAndroidClass()) {\n\t\t\t\tmenu = classContextFactory.getAndroidClassInfoContextMenuProvider(source, workspace, resource,\n\t\t\t\t\t\t(AndroidClassBundle) bundle, innerClass.asAndroidClass()).makeMenu();\n\t\t\t} else {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn menu;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicMethodContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ContextMenu;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.IncompletePathException;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.services.cell.icon.IconProvider;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProvider;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.search.match.StringPredicateProvider;\nimport software.coley.recaf.ui.contextmenu.ContextMenuBuilder;\nimport software.coley.recaf.ui.pane.search.MemberReferenceSearchPane;\nimport software.coley.recaf.ui.pane.search.SearchContextSource;\nimport software.coley.recaf.util.ClipboardUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.List;\n\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.*;\n\n/**\n * Basic implementation for {@link MethodContextMenuProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicMethodContextMenuProviderFactory extends AbstractContextMenuProviderFactory implements MethodContextMenuProviderFactory {\n\tprivate static final Logger logger = Logging.get(BasicMethodContextMenuProviderFactory.class);\n\n\tprivate final InheritanceGraphService graphService;\n\n\t@Inject\n\tpublic BasicMethodContextMenuProviderFactory(@Nonnull InheritanceGraphService graphService,\n\t\t\t@Nonnull TextProviderService textService,\n\t                                             @Nonnull IconProviderService iconService,\n\t                                             @Nonnull Actions actions) {\n\t\tsuper(textService, iconService, actions);\n\t\tthis.graphService = graphService;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ContextMenuProvider getMethodContextMenuProvider(@Nonnull ContextSource source,\n\t                                                        @Nonnull Workspace workspace,\n\t                                                        @Nonnull WorkspaceResource resource,\n\t                                                        @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                                        @Nonnull ClassInfo declaringClass,\n\t                                                        @Nonnull MethodMember method) {\n\t\treturn () -> {\n\t\t\tInheritanceGraph inheritGraph = graphService.getCurrentWorkspaceInheritanceGraph();\n\t\t\tboolean isLibrary = inheritGraph == null || inheritGraph.isLibraryMethod(declaringClass.getName(), method.getName(), method.getDescriptor());\n\n\t\t\tTextProvider nameProvider = textService.getMethodMemberTextProvider(workspace, resource, bundle, declaringClass, method);\n\t\t\tIconProvider iconProvider = iconService.getClassMemberIconProvider(workspace, resource, bundle, declaringClass, method);\n\t\t\tContextMenu menu = new ContextMenu();\n\t\t\taddHeader(menu, nameProvider.makeText(), iconProvider.makeIcon());\n\t\t\tvar builder = new ContextMenuBuilder(menu, source).forMember(workspace, resource, bundle, declaringClass, method);\n\n\t\t\tif (source.isReference()) {\n\t\t\t\tbuilder.item(\"menu.goto.method\", ARROW_RIGHT, () -> {\n\t\t\t\t\tClassPathNode classPath = PathNodes.classPath(workspace, resource, bundle, declaringClass);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tactions.gotoDeclaration(classPath)\n\t\t\t\t\t\t\t\t.requestFocus(method);\n\t\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\t\tlogger.error(\"Cannot go to method due to incomplete path\", ex);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// Convenience for search results\n\t\t\t\tif (source instanceof SearchContextSource) {\n\t\t\t\t\tbuilder.item(\"menu.edit.assemble.method\", EDIT, Unchecked.runnable(() -> actions.openAssembler(PathNodes.memberPath(workspace, resource, bundle, declaringClass, method))));\n\t\t\t\t}\n\t\t\t} else if (!resource.isInternal()) {\n\t\t\t\t// Edit menu\n\t\t\t\tvar edit = builder.submenu(\"menu.edit\", EDIT);\n\t\t\t\tedit.item(\"menu.edit.assemble.method\", EDIT, Unchecked.runnable(() -> actions.openAssembler(PathNodes.memberPath(workspace, resource, bundle, declaringClass, method))));\n\t\t\t\tif (declaringClass.isJvmClass()) {\n\t\t\t\t\tJvmClassBundle jvmBundle = (JvmClassBundle) bundle;\n\t\t\t\t\tJvmClassInfo declaringJvmClass = declaringClass.asJvmClass();\n\n\t\t\t\t\tedit.item(\"menu.edit.copy\", COPY_FILE, () -> actions.copyMember(workspace, resource, jvmBundle, declaringJvmClass, method));\n\t\t\t\t\tedit.item(\"menu.edit.removevars\", CIRCLE_DASH, () -> actions.removeMethodVariables(workspace, resource, jvmBundle, declaringJvmClass, List.of(method)));\n\t\t\t\t\tif (!method.getName().equals(\"<init>\")) // The conditions for optimally no-op'ing a constructor are a bit tricky, we'll just skip those for now.\n\t\t\t\t\t\tedit.item(\"menu.edit.noop\", CIRCLE_DASH, () -> actions.makeMethodsNoop(workspace, resource, jvmBundle, declaringJvmClass, List.of(method)));\n\t\t\t\t\tedit.item(\"menu.edit.delete\", TRASH_CAN, () -> actions.deleteClassMethods(workspace, resource, jvmBundle, declaringJvmClass, List.of(method)));\n\t\t\t\t\tedit.item(\"menu.edit.remove.annotation\", CLOSE, () -> actions.deleteMemberAnnotations(workspace, resource, jvmBundle, declaringJvmClass, method))\n\t\t\t\t\t\t\t.disableWhen(method.getAnnotations().isEmpty());\n\t\t\t\t}\n\n\t\t\t\t// TODO: implement additional operations\n\t\t\t\t//  - Edit\n\t\t\t\t//    - Add annotation\n\t\t\t}\n\n\t\t\t// TODO: implement additional operations\n\t\t\t//  - View\n\t\t\t//    - Control flow graph\n\t\t\t//    - Application flow graph\n\t\t\tvar view = builder.submenu(\"menu.view\", VIEW);\n\t\t\tif (declaringClass.isJvmClass()) {\n\t\t\t\tJvmClassBundle jvmBundle = (JvmClassBundle) bundle;\n\t\t\t\tJvmClassInfo declaringJvmClass = declaringClass.asJvmClass();\n\t\t\t\tview.item(\"menu.view.methodcallgraph\", FLOW, () -> actions.openMethodCallGraph(workspace, resource, jvmBundle, declaringJvmClass, method));\n\t\t\t}\n\n\t\t\t// TODO: implement additional operations\n\t\t\t//  - Deobfuscate\n\t\t\t//    - Regenerate variable names\n\t\t\t//    - Optimize with pattern matchers\n\t\t\t//    - Optimize with SSVM\n\t\t\t//  - Simulate with SSVM (Virtualize > Run)\n\n\t\t\t// Search actions\n\t\t\tbuilder.item(\"menu.search.method-references\", CODE_REFERENCE, () -> {\n\t\t\t\tMemberReferenceSearchPane pane = actions.openNewMemberReferenceSearch();\n\t\t\t\tpane.ownerPredicateIdProperty().setValue(StringPredicateProvider.KEY_EQUALS);\n\t\t\t\tpane.namePredicateIdProperty().setValue(StringPredicateProvider.KEY_EQUALS);\n\t\t\t\tpane.descPredicateIdProperty().setValue(StringPredicateProvider.KEY_EQUALS);\n\t\t\t\tpane.ownerValueProperty().setValue(declaringClass.getName());\n\t\t\t\tpane.nameValueProperty().setValue(method.getName());\n\t\t\t\tpane.descValueProperty().setValue(method.getDescriptor());\n\t\t\t});\n\n\t\t\t// Copy path\n\t\t\tbuilder.item(\"menu.tab.copypath\", COPY_LINK, () -> ClipboardUtil.copyString(declaringClass, method));\n\n\t\t\t// Documentation actions\n\t\t\tbuilder.memberItem(\"menu.analysis.comment\", ADD_COMMENT, actions::openCommentEditing);\n\n\t\t\t// Refactor actions\n\t\t\tif (!resource.isInternal() && !isLibrary)\n\t\t\t\tbuilder.memberItem(\"menu.refactor.rename\", TAG_EDIT, actions::renameMethod);\n\n\t\t\treturn menu;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicPackageContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ContextMenu;\nimport javafx.stage.WindowEvent;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.services.cell.icon.IconProvider;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProvider;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.search.match.StringPredicateProvider;\nimport software.coley.recaf.ui.contextmenu.ContextMenuBuilder;\nimport software.coley.recaf.ui.control.popup.ChangeClassVersionPopup;\nimport software.coley.recaf.ui.control.popup.DecompileAllPopup;\nimport software.coley.recaf.ui.pane.search.ClassReferenceSearchPane;\nimport software.coley.recaf.ui.pane.search.MemberReferenceSearchPane;\nimport software.coley.recaf.util.ClipboardUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.*;\n\n/**\n * Basic implementation for {@link PackageContextMenuProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicPackageContextMenuProviderFactory extends AbstractContextMenuProviderFactory\n\t\timplements PackageContextMenuProviderFactory {\n\tprivate final Instance<DecompileAllPopup> decompileAllPaneProvider;\n\n\t@Inject\n\tpublic BasicPackageContextMenuProviderFactory(@Nonnull Instance<DecompileAllPopup> decompileAllPaneProvider,\n\t                                              @Nonnull TextProviderService textService,\n\t                                              @Nonnull IconProviderService iconService,\n\t                                              @Nonnull Actions actions) {\n\t\tsuper(textService, iconService, actions);\n\t\tthis.decompileAllPaneProvider = decompileAllPaneProvider;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ContextMenuProvider getPackageContextMenuProvider(@Nonnull ContextSource source,\n\t                                                         @Nonnull Workspace workspace,\n\t                                                         @Nonnull WorkspaceResource resource,\n\t                                                         @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                                         @Nonnull String packageName) {\n\t\treturn () -> {\n\t\t\tTextProvider nameProvider = textService.getPackageTextProvider(workspace, resource, bundle, packageName);\n\t\t\tIconProvider iconProvider = iconService.getPackageIconProvider(workspace, resource, bundle, packageName);\n\t\t\tContextMenu menu = new ContextMenu();\n\t\t\taddHeader(menu, nameProvider.makeText(), iconProvider.makeIcon());\n\t\t\tvar builder = new ContextMenuBuilder(menu, source).forDirectory(workspace, resource, bundle, packageName);\n\n\t\t\tif (source.isDeclaration()) {\n\t\t\t\tif (bundle instanceof JvmClassBundle jvmBundle) {\n\t\t\t\t\tvar jvmBuilder = builder.cast(JvmClassBundle.class);\n\t\t\t\t\tvar edit = jvmBuilder.submenu(\"menu.edit\", EDIT);\n\t\t\t\t\tedit.directoryItem(\"menu.edit.newclass\", ADD_ALT, actions::newClass);\n\t\t\t\t\tedit.directoryItem(\"menu.edit.copy\", COPY_FILE, actions::copyPackage);\n\t\t\t\t\tedit.directoryItem(\"menu.edit.delete\", TRASH_CAN, actions::deletePackage);\n\t\t\t\t\tedit.item(\"menu.edit.changeversion\", ARROWS_VERTICAL, () -> {\n\t\t\t\t\t\tChangeClassVersionPopup popup = new ChangeClassVersionPopup();\n\t\t\t\t\t\tpopup.setTargetPackage(jvmBundle, packageName);\n\t\t\t\t\t\tpopup.show();\n\t\t\t\t\t});\n\n\t\t\t\t\tvar refactor = jvmBuilder.submenu(\"menu.refactor\", PAINT_BRUSH);\n\t\t\t\t\trefactor.directoryItem(\"menu.refactor.move\", STACKED_MOVE, actions::movePackage);\n\t\t\t\t\trefactor.directoryItem(\"menu.refactor.rename\", TAG_EDIT, actions::renamePackage);\n\n\t\t\t\t\tjvmBuilder.directoryItem(\"menu.export.package\", EXPORT, actions::exportPackage);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Search actions\n\t\t\tvar search = builder.submenu(\"menu.search\", SEARCH);\n\t\t\tsearch.item(\"menu.search.class.member-references\", CODE_REFERENCE, () -> {\n\t\t\t\tMemberReferenceSearchPane pane = actions.openNewMemberReferenceSearch();\n\t\t\t\tpane.ownerPredicateIdProperty().setValue(StringPredicateProvider.KEY_STARTS_WITH);\n\t\t\t\tpane.ownerValueProperty().setValue(packageName + \"/\");\n\t\t\t});\n\t\t\tsearch.item(\"menu.search.class.type-references\", CODE_REFERENCE, () -> {\n\t\t\t\tClassReferenceSearchPane pane = actions.openNewClassReferenceSearch();\n\t\t\t\tpane.typePredicateIdProperty().setValue(StringPredicateProvider.KEY_STARTS_WITH);\n\t\t\t\tpane.typeValueProperty().setValue(packageName + \"/\");\n\t\t\t});\n\n\t\t\t// Misc\n\t\t\tif (bundle instanceof JvmClassBundle jvmBundle) {\n\t\t\t\tbuilder.item(\"menu.file.decompileall\", DOCUMENT_EXPORT, () -> {\n\t\t\t\t\tDecompileAllPopup popup = decompileAllPaneProvider.get();\n\t\t\t\t\tpopup.addEventFilter(WindowEvent.WINDOW_HIDDEN, e -> decompileAllPaneProvider.destroy(popup));\n\t\t\t\t\tpopup.setTargetBundle(jvmBundle);\n\t\t\t\t\tpopup.setNamePredicate(name -> name.startsWith(packageName));\n\t\t\t\t\tpopup.show();\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Copy path\n\t\t\tbuilder.item(\"menu.tab.copypath\", COPY_LINK, () -> ClipboardUtil.copyString(packageName));\n\n\t\t\treturn menu;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicResourceContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.services.cell.icon.IconProvider;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.ui.contextmenu.ContextMenuBuilder;\nimport software.coley.recaf.util.ClipboardUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.COPY_LINK;\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.TRASH_CAN;\n\n/**\n * Basic implementation for {@link ResourceContextMenuProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicResourceContextMenuProviderFactory extends AbstractContextMenuProviderFactory implements ResourceContextMenuProviderFactory {\n\t@Inject\n\tpublic BasicResourceContextMenuProviderFactory(@Nonnull TextProviderService textService,\n\t                                               @Nonnull IconProviderService iconService,\n\t                                               @Nonnull Actions actions) {\n\t\tsuper(textService, iconService, actions);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ContextMenuProvider getResourceContextMenuProvider(@Nonnull ContextSource source,\n\t                                                          @Nonnull Workspace workspace,\n\t                                                          @Nonnull WorkspaceResource resource) {\n\t\treturn () -> {\n\t\t\tString name = textService.getResourceTextProvider(workspace, resource).makeText();\n\t\t\tIconProvider iconProvider = iconService.getResourceIconProvider(workspace, resource);\n\t\t\tContextMenu menu = new ContextMenu();\n\t\t\taddHeader(menu, name, iconProvider.makeIcon());\n\n\t\t\tvar builder = new ContextMenuBuilder(menu, source).forResource(workspace, resource);\n\t\t\tif (resource.isEmbeddedResource()) {\n\t\t\t\tif (resource instanceof WorkspaceFileResource fileResource) {\n\t\t\t\t\tbuilder.item(\"menu.tab.copypath\", COPY_LINK, () -> ClipboardUtil.copyString(fileResource.getFileInfo()));\n\t\t\t\t}\n\t\t\t\tbuilder.item(\"misc.remove\", TRASH_CAN, () -> workspace.removeSupportingResource(resource));\n\t\t\t}\n\n\t\t\treturn menu;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicWhitelistingContextSource.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.util.function.Predicate;\n\n/**\n * Basic context source with whitelisting for {@link #allow(String)}.\n *\n * @author Matt Coley\n */\npublic class BasicWhitelistingContextSource extends BasicContextSource {\n\tprivate final Predicate<String> allowed;\n\n\t/**\n\t * @param isDeclaration\n\t *        {@code true} for the source to model a declaration.\n\t *        {@code false} for the source to model a reference.\n\t * @param whitelist\n\t * \t\tWhitelist function, where {@code true} is allowed.\n\t */\n\tpublic BasicWhitelistingContextSource(boolean isDeclaration, @Nonnull Predicate<String> whitelist) {\n\t\tsuper(isDeclaration);\n\t\tthis.allowed = whitelist;\n\t}\n\n\t@Override\n\tpublic boolean allow(@Nonnull String key) {\n\t\treturn allowed.test(key);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BundleContextMenuAdapter.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu adapter for {@link Bundle} types.\n *\n * @author Matt Coley\n */\npublic interface BundleContextMenuAdapter extends ContextMenuAdapter {\n\t/**\n\t * @param menu\n\t * \t\tThe menu to adapt.\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tThe bundle the menu is for.\n\t */\n\tvoid adaptBundleContextMenu(@Nonnull ContextMenu menu,\n\t                            @Nonnull ContextSource source,\n\t                            @Nonnull Workspace workspace,\n\t                            @Nonnull WorkspaceResource resource,\n\t                            @Nonnull Bundle<? extends Info> bundle);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BundleContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu provider for {@link Bundle} types.\n *\n * @author Matt Coley\n */\npublic interface BundleContextMenuProviderFactory extends ContextMenuProviderFactory {\n\t/**\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tThe bundle to create a menu for.\n\t *\n\t * @return Menu provider for the bundle.\n\t */\n\t@Nonnull\n\tdefault ContextMenuProvider getBundleContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull Bundle<? extends Info> bundle) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/ClassContextMenuAdapter.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu adapter for {@link ClassInfo} types.\n *\n * @author Matt Coley\n */\npublic interface ClassContextMenuAdapter extends ContextMenuAdapter {\n\t/**\n\t * @param menu\n\t * \t\tThe menu to adapt.\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class the menu is for.\n\t */\n\tdefault void adaptJvmClassMenu(@Nonnull ContextMenu menu,\n\t\t\t\t\t\t\t\t   @Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t   @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t   @Nonnull JvmClassBundle bundle,\n\t\t\t\t\t\t\t\t   @Nonnull JvmClassInfo info) {}\n\n\t/**\n\t * @param menu\n\t * \t\tThe menu to adapt.\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class the menu is for.\n\t */\n\tdefault void adaptAndroidClassMenu(@Nonnull ContextMenu menu,\n\t\t\t\t\t\t\t\t\t   @Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t   @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t   @Nonnull AndroidClassBundle bundle,\n\t\t\t\t\t\t\t\t\t   @Nonnull AndroidClassInfo info) {}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/ClassContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu provider for {@link ClassInfo} types.\n *\n * @author Matt Coley\n */\npublic interface ClassContextMenuProviderFactory extends ContextMenuProviderFactory {\n\t/**\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create a menu for.\n\t *\n\t * @return Menu provider for the class.\n\t */\n\t@Nonnull\n\tdefault ContextMenuProvider getJvmClassInfoContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull JvmClassBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull JvmClassInfo info) {\n\t\treturn emptyProvider();\n\t}\n\n\t/**\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create a menu for.\n\t *\n\t * @return Menu provider for the class.\n\t */\n\t@Nonnull\n\tdefault ContextMenuProvider getAndroidClassInfoContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull AndroidClassBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull AndroidClassInfo info) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/ContextMenuAdapter.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.behavior.PrioritySortable;\n\n/**\n * Adapts an existing {@link ContextMenu}. Used to customize content provided by {@link ContextMenuProvider} instances.\n *\n * @author Matt Coley\n */\npublic interface ContextMenuAdapter extends PrioritySortable {}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/ContextMenuProvider.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nullable;\nimport javafx.scene.control.ContextMenu;\n\n/**\n * Provides a {@link ContextMenu}. Primarily used when wanting to provide an expected set of actions on a type lazily.\n *\n * @author Matt Coley\n */\npublic interface ContextMenuProvider {\n\t/**\n\t * @return Provided menu. May be {@code null}.\n\t */\n\t@Nullable\n\tContextMenu makeMenu();\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/ContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.Node;\nimport javafx.scene.control.ContextMenu;\nimport javafx.scene.control.MenuItem;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.AssemblyResolution;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Base context menu provider factory.\n *\n * @author Matt Coley\n * @see AnnotationContextMenuProviderFactory For annotations on classes and their members.\n * @see AssemblerContextMenuProviderFactory For {@link AssemblyResolution} data in an assembler UI.\n * @see ClassContextMenuProviderFactory For {@link JvmClassInfo} and {@link AndroidClassBundle} entries.\n * @see InnerClassContextMenuProviderFactory For {@link InnerClassInfo} entries.\n * @see FieldContextMenuProviderFactory For {@link FieldMember} entries.\n * @see MethodContextMenuProviderFactory For {@link MethodMember} entries.\n * @see FileContextMenuProviderFactory For {@link FileInfo} entries.\n * @see DirectoryContextMenuProviderFactory For directory entries, not linked to a specific {@link FileInfo}.\n * @see PackageContextMenuProviderFactory  For package entries, not linked to a specific {@link ClassInfo}.\n * @see BundleContextMenuProviderFactory For {@link Bundle} entries.\n * @see ResourceContextMenuProviderFactory For {@link WorkspaceResource} entries.\n */\npublic interface ContextMenuProviderFactory {\n\t/**\n\t * @return Context menu provider that provides {@code null}.\n\t */\n\t@Nonnull\n\tdefault ContextMenuProvider emptyProvider() {\n\t\treturn () -> null;\n\t}\n\n\t/**\n\t * Add a header item to the given menu.\n\t *\n\t * @param menu\n\t * \t\tMenu to add to.\n\t * @param title\n\t * \t\tHeader text content.\n\t * @param graphic\n\t * \t\tHeader graphic.\n\t */\n\tdefault void addHeader(@Nonnull ContextMenu menu, @Nullable String title, @Nullable Node graphic) {\n\t\tMenuItem header = new MenuItem(title, graphic);\n\t\tmenu.getItems().add(header);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/ContextMenuProviderService.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.AssemblerPathData;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.ui.control.tree.WorkspaceTreeCell;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.function.BiConsumer;\n\n/**\n * Provides support for providing contextual right click menus for a variety of item types.\n * For instance, the menus of {@link WorkspaceTreeCell} instances.\n * <br>\n * The menus displayed in the UI can be adapted out by supplying your own\n * {@link ContextMenuAdapter} instances via:\n * <ul>\n *     <li>{@link #addClassContextMenuAdapter(ClassContextMenuAdapter)}</li>\n *     <li>{@link #addFileContextMenuAdapter(FileContextMenuAdapter)}</li>\n *     <li>{@link #addInnerClassContextMenuAdapter(InnerClassContextMenuAdapter)}</li>\n *     <li>{@link #addFieldContextMenuAdapter(FieldContextMenuAdapter)}</li>\n *     <li>{@link #addMethodContextMenuAdapter(MethodContextMenuAdapter)}</li>\n *     <li>{@link #addAnnotationContextMenuAdapter(AnnotationContextMenuAdapter)}</li>\n *     <li>{@link #addPackageContextMenuAdapter(PackageContextMenuAdapter)}</li>\n *     <li>{@link #addDirectoryContextMenuAdapter(DirectoryContextMenuAdapter)}</li>\n *     <li>{@link #addBundleContextMenuAdapter(BundleContextMenuAdapter)}</li>\n *     <li>{@link #addResourceContextMenuAdapter(ResourceContextMenuAdapter)}</li>\n *     <li>{@link #addAssemblerContextMenuAdapter(AssemblerContextMenuAdapter)}</li>\n * </ul>\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ContextMenuProviderService implements Service {\n\tpublic static final String SERVICE_ID = \"cell-menus\";\n\tprivate final ContextMenuProviderServiceConfig config;\n\t// Adapters\n\tprivate final List<ClassContextMenuAdapter> classContextMenuAdapters = new ArrayList<>();\n\tprivate final List<FileContextMenuAdapter> fileContextMenuAdapters = new ArrayList<>();\n\tprivate final List<InnerClassContextMenuAdapter> innerClassContextMenuAdapters = new ArrayList<>();\n\tprivate final List<FieldContextMenuAdapter> fieldContextMenuAdapters = new ArrayList<>();\n\tprivate final List<MethodContextMenuAdapter> methodContextMenuAdapters = new ArrayList<>();\n\tprivate final List<AnnotationContextMenuAdapter> annotationContextMenuAdapters = new ArrayList<>();\n\tprivate final List<PackageContextMenuAdapter> packageContextMenuAdapters = new ArrayList<>();\n\tprivate final List<DirectoryContextMenuAdapter> directoryContextMenuAdapters = new ArrayList<>();\n\tprivate final List<BundleContextMenuAdapter> bundleContextMenuAdapters = new ArrayList<>();\n\tprivate final List<ResourceContextMenuAdapter> resourceContextMenuAdapters = new ArrayList<>();\n\tprivate final List<AssemblerContextMenuAdapter> assemblerContextMenuAdapters = new ArrayList<>();\n\t// Defaults\n\tprivate final ClassContextMenuProviderFactory classContextMenuDefault;\n\tprivate final FileContextMenuProviderFactory fileContextMenuDefault;\n\tprivate final InnerClassContextMenuProviderFactory innerClassContextMenuDefault;\n\tprivate final FieldContextMenuProviderFactory fieldContextMenuDefault;\n\tprivate final MethodContextMenuProviderFactory methodContextMenuDefault;\n\tprivate final AnnotationContextMenuProviderFactory annotationContextMenuDefault;\n\tprivate final PackageContextMenuProviderFactory packageContextMenuDefault;\n\tprivate final DirectoryContextMenuProviderFactory directoryContextMenuDefault;\n\tprivate final BundleContextMenuProviderFactory bundleContextMenuDefault;\n\tprivate final ResourceContextMenuProviderFactory resourceContextMenuDefault;\n\tprivate final AssemblerContextMenuProviderFactory assemblerContextMenuDefault;\n\n\t@Inject\n\tpublic ContextMenuProviderService(@Nonnull ContextMenuProviderServiceConfig config,\n\t                                  @Nonnull ClassContextMenuProviderFactory classContextMenuDefault,\n\t                                  @Nonnull FileContextMenuProviderFactory fileContextMenuDefault,\n\t                                  @Nonnull InnerClassContextMenuProviderFactory innerClassContextMenuDefault,\n\t                                  @Nonnull FieldContextMenuProviderFactory fieldContextMenuDefault,\n\t                                  @Nonnull MethodContextMenuProviderFactory methodContextMenuDefault,\n\t                                  @Nonnull AnnotationContextMenuProviderFactory annotationContextMenuDefault,\n\t                                  @Nonnull PackageContextMenuProviderFactory packageContextMenuDefault,\n\t                                  @Nonnull DirectoryContextMenuProviderFactory directoryContextMenuDefault,\n\t                                  @Nonnull BundleContextMenuProviderFactory bundleContextMenuDefault,\n\t                                  @Nonnull ResourceContextMenuProviderFactory resourceContextMenuDefault,\n\t                                  @Nonnull AssemblerContextMenuProviderFactory assemblerContextMenuDefault) {\n\t\tthis.config = config;\n\n\t\t// Default factories\n\t\tthis.classContextMenuDefault = classContextMenuDefault;\n\t\tthis.fileContextMenuDefault = fileContextMenuDefault;\n\t\tthis.innerClassContextMenuDefault = innerClassContextMenuDefault;\n\t\tthis.fieldContextMenuDefault = fieldContextMenuDefault;\n\t\tthis.methodContextMenuDefault = methodContextMenuDefault;\n\t\tthis.annotationContextMenuDefault = annotationContextMenuDefault;\n\t\tthis.packageContextMenuDefault = packageContextMenuDefault;\n\t\tthis.directoryContextMenuDefault = directoryContextMenuDefault;\n\t\tthis.bundleContextMenuDefault = bundleContextMenuDefault;\n\t\tthis.resourceContextMenuDefault = resourceContextMenuDefault;\n\t\tthis.assemblerContextMenuDefault = assemblerContextMenuDefault;\n\t}\n\n\t/**\n\t * Delegates to {@link ClassContextMenuProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create a menu for.\n\t *\n\t * @return Menu provider for the class.\n\t */\n\t@Nonnull\n\tpublic ContextMenuProvider getJvmClassInfoContextMenuProvider(@Nonnull ContextSource source,\n\t                                                              @Nonnull Workspace workspace,\n\t                                                              @Nonnull WorkspaceResource resource,\n\t                                                              @Nonnull JvmClassBundle bundle,\n\t                                                              @Nonnull JvmClassInfo info) {\n\t\tContextMenuProvider provider = classContextMenuDefault.getJvmClassInfoContextMenuProvider(source, workspace, resource, bundle, info);\n\t\tprovider = adapt(provider, classContextMenuAdapters, (adapter, menu) -> adapter.adaptJvmClassMenu(menu, source, workspace, resource, bundle, info));\n\t\treturn provider;\n\t}\n\n\t/**\n\t * Delegates to {@link ClassContextMenuProviderFactory}.\n\t *\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create a menu for.\n\t *\n\t * @return Menu provider for the class.\n\t */\n\t@Nonnull\n\tpublic ContextMenuProvider getAndroidClassInfoContextMenuProvider(@Nonnull ContextSource source,\n\t                                                                  @Nonnull Workspace workspace,\n\t                                                                  @Nonnull WorkspaceResource resource,\n\t                                                                  @Nonnull AndroidClassBundle bundle,\n\t                                                                  @Nonnull AndroidClassInfo info) {\n\t\tContextMenuProvider provider = classContextMenuDefault.getAndroidClassInfoContextMenuProvider(source, workspace, resource, bundle, info);\n\t\tprovider = adapt(provider, classContextMenuAdapters, (adapter, menu) -> adapter.adaptAndroidClassMenu(menu, source, workspace, resource, bundle, info));\n\t\treturn provider;\n\t}\n\n\t/**\n\t * Delegates to {@link InnerClassContextMenuProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param outerClass\n\t * \t\tOuter class.\n\t * @param inner\n\t * \t\tThe inner class to create a menu for.\n\t *\n\t * @return Menu provider for the class.\n\t */\n\t@Nonnull\n\tpublic ContextMenuProvider getInnerClassInfoContextMenuProvider(@Nonnull ContextSource source,\n\t                                                                @Nonnull Workspace workspace,\n\t                                                                @Nonnull WorkspaceResource resource,\n\t                                                                @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                                                @Nonnull ClassInfo outerClass,\n\t                                                                @Nonnull InnerClassInfo inner) {\n\t\tContextMenuProvider provider = innerClassContextMenuDefault.getInnerClassInfoContextMenuProvider(source, workspace, resource, bundle, outerClass, inner);\n\t\tprovider = adapt(provider, innerClassContextMenuAdapters, (adapter, menu) -> adapter.adaptInnerClassInfoContextMenu(menu, source, workspace, resource, bundle, outerClass, inner));\n\t\treturn provider;\n\t}\n\n\t/**\n\t * Delegates to {@link FieldContextMenuProviderFactory} and {@link MethodContextMenuProviderFactory}.\n\t *\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param member\n\t * \t\tThe member to create a menu for.\n\t *\n\t * @return Menu provider for the class member.\n\t */\n\t@Nonnull\n\tpublic ContextMenuProvider getClassMemberContextMenuProvider(@Nonnull ContextSource source,\n\t                                                             @Nonnull Workspace workspace,\n\t                                                             @Nonnull WorkspaceResource resource,\n\t                                                             @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                                             @Nonnull ClassInfo declaringClass,\n\t                                                             @Nonnull ClassMember member) {\n\t\tif (member.isField()) {\n\t\t\tFieldMember field = (FieldMember) member;\n\t\t\tContextMenuProvider provider = fieldContextMenuDefault.getFieldContextMenuProvider(source, workspace, resource, bundle, declaringClass, field);\n\t\t\tprovider = adapt(provider, fieldContextMenuAdapters, (adapter, menu) -> adapter.adaptFieldContextMenu(menu, source, workspace, resource, bundle, declaringClass, field));\n\t\t\treturn provider;\n\t\t} else if (member.isMethod()) {\n\t\t\tMethodMember method = (MethodMember) member;\n\t\t\tContextMenuProvider provider = methodContextMenuDefault.getMethodContextMenuProvider(source, workspace, resource, bundle, declaringClass, method);\n\t\t\tprovider = adapt(provider, methodContextMenuAdapters, (adapter, menu) -> adapter.adaptMethodContextMenu(menu, source, workspace, resource, bundle, declaringClass, method));\n\t\t\treturn provider;\n\t\t} else {\n\t\t\tthrow new IllegalStateException(\"Unsupported member: \" + member.getClass().getName());\n\t\t}\n\t}\n\n\t/**\n\t * Delegates to {@link AnnotationContextMenuProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param annotated\n\t * \t\tThe annotated item.\n\t * @param annotation\n\t * \t\tThe annotation to create an icon for.\n\t *\n\t * @return Text provider for the annotation.\n\t */\n\t@Nonnull\n\tpublic ContextMenuProvider getAnnotationContextMenuProvider(@Nonnull ContextSource source,\n\t                                                            @Nonnull Workspace workspace,\n\t                                                            @Nonnull WorkspaceResource resource,\n\t                                                            @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                                            @Nonnull Annotated annotated,\n\t                                                            @Nonnull AnnotationInfo annotation) {\n\t\tContextMenuProvider provider = annotationContextMenuDefault.getAnnotationContextMenuProvider(source, workspace, resource, bundle, annotated, annotation);\n\t\tprovider = adapt(provider, annotationContextMenuAdapters, (adapter, menu) -> adapter.adaptAnnotationContextMenu(menu, source, workspace, resource, bundle, annotated, annotation));\n\t\treturn provider;\n\t}\n\n\t/**\n\t * Delegates to {@link FileContextMenuProviderFactory}.\n\t *\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe file to create a menu for.\n\t *\n\t * @return Menu provider for the file.\n\t */\n\t@Nonnull\n\tpublic ContextMenuProvider getFileInfoContextMenuProvider(@Nonnull ContextSource source,\n\t                                                          @Nonnull Workspace workspace,\n\t                                                          @Nonnull WorkspaceResource resource,\n\t                                                          @Nonnull FileBundle bundle,\n\t                                                          @Nonnull FileInfo info) {\n\t\tContextMenuProvider provider = fileContextMenuDefault.getFileInfoContextMenuProvider(source, workspace, resource, bundle, info);\n\t\tprovider = adapt(provider, fileContextMenuAdapters, (adapter, menu) -> adapter.adaptFileInfoContextMenu(menu, source, workspace, resource, bundle, info));\n\t\treturn provider;\n\t}\n\n\t/**\n\t * Delegates to {@link PackageContextMenuProviderFactory}.\n\t *\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param packageName\n\t * \t\tThe full package name, separated by {@code /}.\n\t *\n\t * @return Menu provider for the package.\n\t */\n\t@Nonnull\n\tpublic ContextMenuProvider getPackageContextMenuProvider(@Nonnull ContextSource source,\n\t                                                         @Nonnull Workspace workspace,\n\t                                                         @Nonnull WorkspaceResource resource,\n\t                                                         @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                                         @Nonnull String packageName) {\n\t\tContextMenuProvider provider = packageContextMenuDefault.getPackageContextMenuProvider(source, workspace, resource, bundle, packageName);\n\t\tprovider = adapt(provider, packageContextMenuAdapters, (adapter, menu) -> adapter.adaptPackageContextMenu(menu, source, workspace, resource, bundle, packageName));\n\t\treturn provider;\n\t}\n\n\t/**\n\t * Delegates to {@link DirectoryContextMenuProviderFactory}.\n\t *\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tThe full path of the directory.\n\t *\n\t * @return Menu provider for the directory.\n\t */\n\t@Nonnull\n\tpublic ContextMenuProvider getDirectoryContextMenuProvider(@Nonnull ContextSource source,\n\t                                                           @Nonnull Workspace workspace,\n\t                                                           @Nonnull WorkspaceResource resource,\n\t                                                           @Nonnull FileBundle bundle,\n\t                                                           @Nonnull String directoryName) {\n\t\tContextMenuProvider provider = directoryContextMenuDefault.getDirectoryContextMenuProvider(source, workspace, resource, bundle, directoryName);\n\t\tprovider = adapt(provider, directoryContextMenuAdapters, (adapter, menu) -> adapter.adaptDirectoryContextMenu(menu, source, workspace, resource, bundle, directoryName));\n\t\treturn provider;\n\t}\n\n\t/**\n\t * Delegates to {@link BundleContextMenuProviderFactory}.\n\t *\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tThe bundle to create a menu for.\n\t *\n\t * @return Menu provider for the bundle.\n\t */\n\t@Nonnull\n\tpublic ContextMenuProvider getBundleContextMenuProvider(@Nonnull ContextSource source,\n\t                                                        @Nonnull Workspace workspace,\n\t                                                        @Nonnull WorkspaceResource resource,\n\t                                                        @Nonnull Bundle<? extends Info> bundle) {\n\t\tContextMenuProvider provider = bundleContextMenuDefault.getBundleContextMenuProvider(source, workspace, resource, bundle);\n\t\tprovider = adapt(provider, bundleContextMenuAdapters, (adapter, menu) -> adapter.adaptBundleContextMenu(menu, source, workspace, resource, bundle));\n\t\treturn provider;\n\t}\n\n\t/**\n\t * Delegates to {@link ResourceContextMenuProviderFactory}.\n\t *\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tThe resource to create a menu for.\n\t *\n\t * @return Menu provider for the resource.\n\t */\n\t@Nonnull\n\tpublic ContextMenuProvider getResourceContextMenuProvider(@Nonnull ContextSource source,\n\t                                                          @Nonnull Workspace workspace,\n\t                                                          @Nonnull WorkspaceResource resource) {\n\t\tContextMenuProvider provider = resourceContextMenuDefault.getResourceContextMenuProvider(source, workspace, resource);\n\t\tprovider = adapt(provider, resourceContextMenuAdapters, (adapter, menu) -> adapter.adaptResourceContextMenu(menu, source, workspace, resource));\n\t\treturn provider;\n\t}\n\n\t/**\n\t * Delegates to {@link AssemblerContextMenuProviderFactory}.\n\t *\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param assemblerData\n\t * \t\tThe assembler data to create a menu for.\n\t *\n\t * @return Menu provider for the resource.\n\t */\n\t@Nonnull\n\tpublic ContextMenuProvider getAssemblerContextMenuProvider(@Nonnull ContextSource source,\n\t                                                           @Nonnull Workspace workspace,\n\t                                                           @Nonnull WorkspaceResource resource,\n\t                                                           @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                                           @Nonnull ClassInfo declaringClass,\n\t                                                           @Nonnull AssemblerPathData assemblerData) {\n\t\tContextMenuProvider provider = assemblerContextMenuDefault.getAssemblerMenuProvider(source, workspace, resource, bundle, declaringClass, assemblerData);\n\t\tprovider = adapt(provider, assemblerContextMenuAdapters, (adapter, menu) -> adapter.adaptAssemblerMenu(menu, source, workspace, resource, bundle, declaringClass, assemblerData));\n\t\treturn provider;\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to register for modifying class context menus.\n\t *\n\t * @return {@code true} when the adapter was added. {@link false} when the adapter has already been added.\n\t */\n\tpublic boolean addClassContextMenuAdapter(@Nonnull ClassContextMenuAdapter adapter) {\n\t\treturn PrioritySortable.add(classContextMenuAdapters, adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to remove.\n\t *\n\t * @return {@code true} when the adapter was removed. {@link false} when the adapter was not previously registered.\n\t */\n\tpublic boolean removeClassContextMenuAdapter(@Nonnull ClassContextMenuAdapter adapter) {\n\t\treturn classContextMenuAdapters.remove(adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to register for modifying file context menus.\n\t *\n\t * @return {@code true} when the adapter was added. {@link false} when the adapter has already been added.\n\t */\n\tpublic boolean addFileContextMenuAdapter(@Nonnull FileContextMenuAdapter adapter) {\n\t\treturn PrioritySortable.add(fileContextMenuAdapters, adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to remove.\n\t *\n\t * @return {@code true} when the adapter was removed. {@link false} when the adapter was not previously registered.\n\t */\n\tpublic boolean removeFileContextMenuAdapter(@Nonnull FileContextMenuAdapter adapter) {\n\t\treturn fileContextMenuAdapters.remove(adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to register for modifying inner-class context menus.\n\t *\n\t * @return {@code true} when the adapter was added. {@link false} when the adapter has already been added.\n\t */\n\tpublic boolean addInnerClassContextMenuAdapter(@Nonnull InnerClassContextMenuAdapter adapter) {\n\t\treturn PrioritySortable.add(innerClassContextMenuAdapters, adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to remove.\n\t *\n\t * @return {@code true} when the adapter was removed. {@link false} when the adapter was not previously registered.\n\t */\n\tpublic boolean removeInnerClassContextMenuAdapter(@Nonnull InnerClassContextMenuAdapter adapter) {\n\t\treturn innerClassContextMenuAdapters.remove(adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to register for modifying field context menus.\n\t *\n\t * @return {@code true} when the adapter was added. {@link false} when the adapter has already been added.\n\t */\n\tpublic boolean addFieldContextMenuAdapter(@Nonnull FieldContextMenuAdapter adapter) {\n\t\treturn PrioritySortable.add(fieldContextMenuAdapters, adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to remove.\n\t *\n\t * @return {@code true} when the adapter was removed. {@link false} when the adapter was not previously registered.\n\t */\n\tpublic boolean removeFieldContextMenuAdapter(@Nonnull FieldContextMenuAdapter adapter) {\n\t\treturn fieldContextMenuAdapters.remove(adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to register for modifying method context menus.\n\t *\n\t * @return {@code true} when the adapter was added. {@link false} when the adapter has already been added.\n\t */\n\tpublic boolean addMethodContextMenuAdapter(@Nonnull MethodContextMenuAdapter adapter) {\n\t\treturn PrioritySortable.add(methodContextMenuAdapters, adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to remove.\n\t *\n\t * @return {@code true} when the adapter was removed. {@link false} when the adapter was not previously registered.\n\t */\n\tpublic boolean removeMethodContextMenuAdapter(@Nonnull MethodContextMenuAdapter adapter) {\n\t\treturn methodContextMenuAdapters.remove(adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to register for modifying annotation context menus.\n\t *\n\t * @return {@code true} when the adapter was added. {@link false} when the adapter has already been added.\n\t */\n\tpublic boolean addAnnotationContextMenuAdapter(@Nonnull AnnotationContextMenuAdapter adapter) {\n\t\treturn PrioritySortable.add(annotationContextMenuAdapters, adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to remove.\n\t *\n\t * @return {@code true} when the adapter was removed. {@link false} when the adapter was not previously registered.\n\t */\n\tpublic boolean removeAnnotationContextMenuAdapter(@Nonnull AnnotationContextMenuAdapter adapter) {\n\t\treturn annotationContextMenuAdapters.remove(adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to register for modifying package context menus.\n\t *\n\t * @return {@code true} when the adapter was added. {@link false} when the adapter has already been added.\n\t */\n\tpublic boolean addPackageContextMenuAdapter(@Nonnull PackageContextMenuAdapter adapter) {\n\t\treturn PrioritySortable.add(packageContextMenuAdapters, adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to remove.\n\t *\n\t * @return {@code true} when the adapter was removed. {@link false} when the adapter was not previously registered.\n\t */\n\tpublic boolean removePackageContextMenuAdapter(@Nonnull PackageContextMenuAdapter adapter) {\n\t\treturn packageContextMenuAdapters.remove(adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to register for modifying directory context menus.\n\t *\n\t * @return {@code true} when the adapter was added. {@link false} when the adapter has already been added.\n\t */\n\tpublic boolean addDirectoryContextMenuAdapter(@Nonnull DirectoryContextMenuAdapter adapter) {\n\t\treturn PrioritySortable.add(directoryContextMenuAdapters, adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to remove.\n\t *\n\t * @return {@code true} when the adapter was removed. {@link false} when the adapter was not previously registered.\n\t */\n\tpublic boolean removeDirectoryContextMenuAdapter(@Nonnull DirectoryContextMenuAdapter adapter) {\n\t\treturn directoryContextMenuAdapters.remove(adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to register for modifying bundle context menus.\n\t *\n\t * @return {@code true} when the adapter was added. {@link false} when the adapter has already been added.\n\t */\n\tpublic boolean addBundleContextMenuAdapter(@Nonnull BundleContextMenuAdapter adapter) {\n\t\treturn PrioritySortable.add(bundleContextMenuAdapters, adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to remove.\n\t *\n\t * @return {@code true} when the adapter was removed. {@link false} when the adapter was not previously registered.\n\t */\n\tpublic boolean removeBundleContextMenuAdapter(@Nonnull BundleContextMenuAdapter adapter) {\n\t\treturn bundleContextMenuAdapters.remove(adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to register for modifying resource context menus.\n\t *\n\t * @return {@code true} when the adapter was added. {@link false} when the adapter has already been added.\n\t */\n\tpublic boolean addResourceContextMenuAdapter(@Nonnull ResourceContextMenuAdapter adapter) {\n\t\treturn PrioritySortable.add(resourceContextMenuAdapters, adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to remove.\n\t *\n\t * @return {@code true} when the adapter was removed. {@link false} when the adapter was not previously registered.\n\t */\n\tpublic boolean removeResourceContextMenuAdapter(@Nonnull ResourceContextMenuAdapter adapter) {\n\t\treturn resourceContextMenuAdapters.remove(adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to register for modifying assembler context menus.\n\t *\n\t * @return {@code true} when the adapter was added. {@link false} when the adapter has already been added.\n\t */\n\tpublic boolean addAssemblerContextMenuAdapter(@Nonnull AssemblerContextMenuAdapter adapter) {\n\t\treturn PrioritySortable.add(assemblerContextMenuAdapters, adapter);\n\t}\n\n\t/**\n\t * @param adapter\n\t * \t\tAdapter to remove.\n\t *\n\t * @return {@code true} when the adapter was removed. {@link false} when the adapter was not previously registered.\n\t */\n\tpublic boolean removeAssemblerContextMenuAdapter(@Nonnull AssemblerContextMenuAdapter adapter) {\n\t\treturn assemblerContextMenuAdapters.remove(adapter);\n\t}\n\n\t/**\n\t * @return Default menu provider for classes.\n\t */\n\t@Nonnull\n\tpublic ClassContextMenuProviderFactory getClassContextMenuDefault() {\n\t\treturn classContextMenuDefault;\n\t}\n\n\t/**\n\t * @return Default menu provider for files.\n\t */\n\t@Nonnull\n\tpublic FileContextMenuProviderFactory getFileContextMenuDefault() {\n\t\treturn fileContextMenuDefault;\n\t}\n\n\t/**\n\t * @return Default menu provider for packages.\n\t */\n\t@Nonnull\n\tpublic PackageContextMenuProviderFactory getPackageContextMenuDefault() {\n\t\treturn packageContextMenuDefault;\n\t}\n\n\t/**\n\t * @return Default menu provider for directories.\n\t */\n\t@Nonnull\n\tpublic DirectoryContextMenuProviderFactory getDirectoryContextMenuDefault() {\n\t\treturn directoryContextMenuDefault;\n\t}\n\n\t/**\n\t * @return Default menu provider for bundles.\n\t */\n\t@Nonnull\n\tpublic BundleContextMenuProviderFactory getBundleContextMenuDefault() {\n\t\treturn bundleContextMenuDefault;\n\t}\n\n\t/**\n\t * @return Default menu provider for resources.\n\t */\n\t@Nonnull\n\tpublic ResourceContextMenuProviderFactory getResourceContextMenuDefault() {\n\t\treturn resourceContextMenuDefault;\n\t}\n\n\t/**\n\t * @return Default menu provider for inner classes.\n\t */\n\t@Nonnull\n\tpublic InnerClassContextMenuProviderFactory getInnerClassContextMenuDefault() {\n\t\treturn innerClassContextMenuDefault;\n\t}\n\n\t/**\n\t * @return Default menu provider for fields.\n\t */\n\t@Nonnull\n\tpublic FieldContextMenuProviderFactory getFieldContextMenuDefault() {\n\t\treturn fieldContextMenuDefault;\n\t}\n\n\t/**\n\t * @return Default menu provider for methods.\n\t */\n\t@Nonnull\n\tpublic MethodContextMenuProviderFactory getMethodContextMenuDefault() {\n\t\treturn methodContextMenuDefault;\n\t}\n\n\t/**\n\t * @return Default menu provider for annotations.\n\t */\n\t@Nonnull\n\tpublic AnnotationContextMenuProviderFactory getAnnotationContextMenuDefault() {\n\t\treturn annotationContextMenuDefault;\n\t}\n\n\t/**\n\t * @return Default menu provider for assembler data.\n\t */\n\t@Nonnull\n\tpublic AssemblerContextMenuProviderFactory getAssemblerContextMenuDefault() {\n\t\treturn assemblerContextMenuDefault;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ContextMenuProviderServiceConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\t@Nonnull\n\tprivate static <T extends ContextMenuAdapter> ContextMenuProvider adapt(@Nonnull ContextMenuProvider provider,\n\t                                                                        @Nonnull Collection<T> adapters,\n\t                                                                        @Nonnull BiConsumer<T, ContextMenu> adapterConsumer) {\n\t\tfor (T adapter : adapters) {\n\t\t\tContextMenuProvider currentProvider = provider;\n\t\t\tprovider = () -> {\n\t\t\t\tContextMenu menu = currentProvider.makeMenu();\n\t\t\t\tif (menu != null)\n\t\t\t\t\tadapterConsumer.accept(adapter, menu);\n\t\t\t\treturn menu;\n\t\t\t};\n\t\t}\n\t\treturn provider;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/ContextMenuProviderServiceConfig.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link ContextMenuProviderService}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ContextMenuProviderServiceConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic ContextMenuProviderServiceConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, ContextMenuProviderService.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/ContextSource.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Allows the {@link ContextMenuProviderFactory} types to know additional information about the context of the inputs.\n * For instance, if the request to provide a context menu for some data is based on <b>the declaration</b> of the data\n * or <b>a reference to</b> the data.\n *\n * @author Matt Coley\n */\npublic interface ContextSource {\n\t/**\n\t * Constant describing declaration context sources.\n\t */\n\tContextSource DECLARATION = new BasicContextSource(true);\n\n\t/**\n\t * Constant describing reference context sources.\n\t */\n\tContextSource REFERENCE = new BasicContextSource(false);\n\n\t/**\n\t * @return {@code true} if the context is of a declaration.\n\t */\n\tboolean isDeclaration();\n\n\t/**\n\t * @return {@code true} if the context is of a reference.\n\t */\n\tboolean isReference();\n\n\t/**\n\t * @param key\n\t * \t\tA key denoting a context menu capability.\n\t *\n\t * @return {@code true} if the context action associated with the key should be allowed to be shown.\n\t */\n\tdefault boolean allow(@Nonnull String key) {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/DirectoryContextMenuAdapter.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu adapter for directories <i>(paths in {@link FileBundle})</i>.\n *\n * @author Matt Coley\n */\npublic interface DirectoryContextMenuAdapter extends ContextMenuAdapter {\n\t/**\n\t * @param menu\n\t * \t\tThe menu to adapt.\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tThe full path of the directory the menu is for.\n\t */\n\tvoid adaptDirectoryContextMenu(@Nonnull ContextMenu menu,\n\t                               @Nonnull ContextSource source,\n\t                               @Nonnull Workspace workspace,\n\t                               @Nonnull WorkspaceResource resource,\n\t                               @Nonnull FileBundle bundle,\n\t                               @Nonnull String directoryName);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/DirectoryContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu provider for directories <i>(paths in {@link FileBundle})</i>.\n *\n * @author Matt Coley\n */\npublic interface DirectoryContextMenuProviderFactory extends ContextMenuProviderFactory {\n\t/**\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tThe full path of the directory.\n\t *\n\t * @return Menu provider for the directory.\n\t */\n\t@Nonnull\n\tdefault ContextMenuProvider getDirectoryContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull FileBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull String directoryName) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/FieldContextMenuAdapter.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu adapter for {@link FieldMember} types.\n *\n * @author Matt Coley\n */\npublic interface FieldContextMenuAdapter extends ContextMenuAdapter {\n\t/**\n\t * @param menu\n\t * \t\tThe menu to adapt.\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param field\n\t * \t\tThe field the menu is for.\n\t */\n\tvoid adaptFieldContextMenu(@Nonnull ContextMenu menu,\n\t                           @Nonnull ContextSource source,\n\t                           @Nonnull Workspace workspace,\n\t                           @Nonnull WorkspaceResource resource,\n\t                           @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                           @Nonnull ClassInfo declaringClass,\n\t                           @Nonnull FieldMember field);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/FieldContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu provider for {@link FieldMember} types.\n *\n * @author Matt Coley\n */\npublic interface FieldContextMenuProviderFactory extends ContextMenuProviderFactory {\n\t/**\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param field\n\t * \t\tThe field to create a menu for.\n\t *\n\t * @return Menu provider for the field.\n\t */\n\t@Nonnull\n\tdefault ContextMenuProvider getFieldContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull FieldMember field) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/FileContextMenuAdapter.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu adapter for {@link FileInfo} types.\n *\n * @author Matt Coley\n */\npublic interface FileContextMenuAdapter extends ContextMenuAdapter {\n\t/**\n\t * @param menu\n\t * \t\tThe menu to adapt.\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe file the menu is for.\n\t */\n\tvoid adaptFileInfoContextMenu(@Nonnull ContextMenu menu,\n\t                              @Nonnull ContextSource source,\n\t                              @Nonnull Workspace workspace,\n\t                              @Nonnull WorkspaceResource resource,\n\t                              @Nonnull FileBundle bundle,\n\t                              @Nonnull FileInfo info);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/FileContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu provider for {@link FileInfo} types.\n *\n * @author Matt Coley\n */\npublic interface FileContextMenuProviderFactory extends ContextMenuProviderFactory {\n\t/**\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe file to create a menu for.\n\t *\n\t * @return Menu provider for the file.\n\t */\n\t@Nonnull\n\tdefault ContextMenuProvider getFileInfoContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull FileBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull FileInfo info) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/InnerClassContextMenuAdapter.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu adapter for {@link InnerClassInfo} types.\n *\n * @author Matt Coley\n */\npublic interface InnerClassContextMenuAdapter extends ContextMenuAdapter {\n\t/**\n\t * @param menu\n\t * \t\tThe menu to adapt.\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param outerClass\n\t * \t\tOuter class.\n\t * @param inner\n\t * \t\tThe inner class the menu is for.\n\t */\n\tvoid adaptInnerClassInfoContextMenu(@Nonnull ContextMenu menu,\n\t                                    @Nonnull ContextSource source,\n\t                                    @Nonnull Workspace workspace,\n\t                                    @Nonnull WorkspaceResource resource,\n\t                                    @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                    @Nonnull ClassInfo outerClass,\n\t                                    @Nonnull InnerClassInfo inner);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/InnerClassContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu provider for {@link InnerClassInfo} types.\n *\n * @author Matt Coley\n */\npublic interface InnerClassContextMenuProviderFactory extends ContextMenuProviderFactory {\n\t/**\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param outerClass\n\t * \t\tOuter class.\n\t * @param inner\n\t * \t\tThe inner class to create a menu for.\n\t *\n\t * @return Menu provider for the inner class.\n\t */\n\t@Nonnull\n\tdefault ContextMenuProvider getInnerClassInfoContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassInfo outerClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull InnerClassInfo inner) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/MethodContextMenuAdapter.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu adapter for {@link MethodMember} types.\n *\n * @author Matt Coley\n */\npublic interface MethodContextMenuAdapter extends ContextMenuAdapter {\n\t/**\n\t * @param menu\n\t * \t\tThe menu to adapt.\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param method\n\t * \t\tThe method the menu is for.\n\t */\n\tvoid adaptMethodContextMenu(@Nonnull ContextMenu menu,\n\t                            @Nonnull ContextSource source,\n\t                            @Nonnull Workspace workspace,\n\t                            @Nonnull WorkspaceResource resource,\n\t                            @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                            @Nonnull ClassInfo declaringClass,\n\t                            @Nonnull MethodMember method);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/MethodContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu provider for {@link MethodMember} types.\n *\n * @author Matt Coley\n */\npublic interface MethodContextMenuProviderFactory extends ContextMenuProviderFactory {\n\t/**\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param method\n\t * \t\tThe method to create a menu for.\n\t *\n\t * @return Menu provider for the method.\n\t */\n\t@Nonnull\n\tdefault ContextMenuProvider getMethodContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull MethodMember method) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/PackageContextMenuAdapter.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu adapter for packages <i>(paths in {@link JvmClassBundle} and {@link AndroidClassBundle})</i>.\n *\n * @author Matt Coley\n */\npublic interface PackageContextMenuAdapter extends ContextMenuAdapter {\n\t/**\n\t * @param menu\n\t * \t\tThe menu to adapt.\n\t * @param source\n\t * \t\tContext request source.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param packageName\n\t * \t\tThe full package name, separated by {@code /}.\n\t */\n\tvoid adaptPackageContextMenu(@Nonnull ContextMenu menu,\n\t                             @Nonnull ContextSource source,\n\t                             @Nonnull Workspace workspace,\n\t                             @Nonnull WorkspaceResource resource,\n\t                             @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                             @Nonnull String packageName);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/PackageContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu provider for packages <i>(paths in {@link JvmClassBundle} and {@link AndroidClassBundle})</i>.\n *\n * @author Matt Coley\n */\npublic interface PackageContextMenuProviderFactory extends ContextMenuProviderFactory {\n\t/**\n\t * @param source\n\t * \t\tContext request source.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param packageName\n\t * \t\tThe full package name, separated by {@code /}.\n\t *\n\t * @return Menu provider for the package.\n\t */\n\t@Nonnull\n\tdefault ContextMenuProvider getPackageContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull String packageName) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/ResourceContextMenuAdapter.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu adapter for {@link WorkspaceResource} types.\n *\n * @author Matt Coley\n */\npublic interface ResourceContextMenuAdapter extends ContextMenuAdapter {\n\t/**\n\t * @param menu\n\t * \t\tThe menu to adapt.\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tThe resource the menu is for.\n\t */\n\tvoid adaptResourceContextMenu(@Nonnull ContextMenu menu,\n\t                              @Nonnull ContextSource source,\n\t                              @Nonnull Workspace workspace,\n\t                              @Nonnull WorkspaceResource resource);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/context/ResourceContextMenuProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.context;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Context menu provider for {@link WorkspaceResource} types.\n *\n * @author Matt Coley\n */\npublic interface ResourceContextMenuProviderFactory extends ContextMenuProviderFactory {\n\t/**\n\t * @param source\n\t * \t\tContext request origin.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tThe resource to create a menu for.\n\t *\n\t * @return Menu provider for the resource.\n\t */\n\t@Nonnull\n\tdefault ContextMenuProvider getResourceContextMenuProvider(@Nonnull ContextSource source,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/AnnotationIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for {@link AnnotationInfo annotations}, to be plugged into {@link IconProviderService}\n * to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface AnnotationIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param annotated\n\t * \t\tThe annotated item.\n\t * @param annotation\n\t * \t\tThe annotation to create an icon for.\n\t *\n\t * @return Icon provider for the annotation.\n\t */\n\t@Nonnull\n\tdefault IconProvider getAnnotationIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull Annotated annotated,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull AnnotationInfo annotation) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicAnnotationIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link AnnotationIconProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicAnnotationIconProviderFactory implements AnnotationIconProviderFactory {\n\tprivate static final IconProvider ANNOTATION = Icons.createProvider(Icons.ANNOTATION);\n\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getAnnotationIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull Annotated annotated,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull AnnotationInfo annotation) {\n\t\treturn ANNOTATION;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicBundleIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link BundleIconProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicBundleIconProviderFactory implements BundleIconProviderFactory {\n\tprivate static final IconProvider CLASS_BUNDLE = Icons.createProvider(Icons.FOLDER_SRC);\n\tprivate static final IconProvider FILE_BUNDLE = Icons.createProvider(Icons.FOLDER_RES);\n\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getBundleIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull Bundle<? extends Info> bundle) {\n\t\tif (bundle instanceof ClassBundle) {\n\t\t\treturn CLASS_BUNDLE;\n\t\t} else {\n\t\t\treturn FILE_BUNDLE;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicCatchIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport javafx.scene.control.Label;\nimport javafx.scene.paint.Color;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link CatchIconProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicCatchIconProviderFactory implements CatchIconProviderFactory {\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getCatchIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull String caughtType) {\n\t\treturn () -> {\n\t\t\tLabel label = new Label(\"catch\");\n\t\t\tlabel.setTextFill(Color.web(\"rgb(0, 175, 255)\"));\n\t\t\treturn label;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicClassIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.properties.builtin.ThrowableProperty;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link ClassIconProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicClassIconProviderFactory implements ClassIconProviderFactory {\n\tprivate static final IconProvider CLASS = Icons.createProvider(Icons.CLASS);\n\tprivate static final IconProvider INTERFACE = Icons.createProvider(Icons.INTERFACE);\n\tprivate static final IconProvider ANNO = Icons.createProvider(Icons.ANNOTATION);\n\tprivate static final IconProvider ENUM = Icons.createProvider(Icons.ENUM);\n\tprivate static final IconProvider ANONYMOUS = Icons.createProvider(Icons.CLASS_ANONYMOUS);\n\tprivate static final IconProvider ABSTRACT = Icons.createProvider(Icons.CLASS_ABSTRACT);\n\tprivate static final IconProvider EXCEPTION = Icons.createProvider(Icons.CLASS_EXCEPTION);\n\tprivate static final IconProvider ABSTRACT_EXCEPTION = Icons.createProvider(Icons.CLASS_ABSTRACT_EXCEPTION);\n\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getJvmClassInfoIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull JvmClassBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull JvmClassInfo info) {\n\t\treturn classIconProvider(info);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getAndroidClassInfoIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull AndroidClassBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull AndroidClassInfo info) {\n\t\treturn classIconProvider(info);\n\t}\n\n\t@Nonnull\n\tprivate static IconProvider classIconProvider(@Nonnull ClassInfo info) {\n\t\t// Special class cases\n\t\tif (info.hasEnumModifier()) return ENUM;\n\t\tif (info.hasAnnotationModifier()) return ANNO;\n\t\tif (info.hasInterfaceModifier()) return INTERFACE;\n\n\t\t// Normal class, consider other edge cases\n\t\tif (ThrowableProperty.get(info)) {\n\t\t\tif (info.hasAnnotationModifier()) {\n\t\t\t\treturn ABSTRACT_EXCEPTION;\n\t\t\t} else {\n\t\t\t\treturn EXCEPTION;\n\t\t\t}\n\t\t} else if (info.isAnonymousInnerClass()) {\n\t\t\treturn ANONYMOUS;\n\t\t} else if (info.hasAbstractModifier()) {\n\t\t\treturn ABSTRACT;\n\t\t}\n\t\treturn CLASS;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicDirectoryIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link DirectoryIconProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicDirectoryIconProviderFactory implements DirectoryIconProviderFactory {\n\tprivate static final IconProvider PROVIDER = Icons.createProvider(Icons.FOLDER);\n\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getDirectoryIconProvider(@Nonnull Workspace workspace, @Nonnull WorkspaceResource resource, @Nonnull FileBundle bundle, @Nonnull String directoryName) {\n\t\treturn PROVIDER;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicFieldIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport javafx.collections.ObservableList;\nimport javafx.scene.Node;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link FieldIconProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicFieldIconProviderFactory implements FieldIconProviderFactory {\n\tprivate static final IconProvider FIELD = Icons.createProvider(Icons.FIELD);\n\tprivate static final IconProvider ACCESS_FINAL = Icons.createProvider(Icons.ACCESS_FINAL);\n\tprivate static final IconProvider ACCESS_STATIC = Icons.createProvider(Icons.ACCESS_STATIC);\n\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getFieldMemberIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull FieldMember field) {\n\t\treturn () -> fieldIconProvider(field);\n\t}\n\n\tprivate static Node fieldIconProvider(FieldMember field) {\n\t\t// Base\n\t\tStackPane stack = new StackPane();\n\t\tObservableList<Node> children = stack.getChildren();\n\t\tchildren.add(FIELD.makeIcon());\n\n\t\t// Add overlay for certain flags.\n\t\tif (field.hasFinalModifier())\n\t\t\tchildren.add(ACCESS_FINAL.makeIcon());\n\t\tif (field.hasStaticModifier())\n\t\t\tchildren.add(ACCESS_STATIC.makeIcon());\n\n\t\t// Wrap with visibility.\n\t\treturn new HBox(stack, Icons.getVisibilityIcon(field.getAccess()));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicFileIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.util.ByteHeaderUtil;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Set;\n\n/**\n * Basic implementation for {@link FileIconProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicFileIconProviderFactory implements FileIconProviderFactory {\n\tprivate static final Set<String> CODE_EXTENSIONS = Set.of(\"java\", \"groovy\", \"kt\", \"css\", \"xml\", \"html\", \"json\");\n\tprivate static final IconProvider TEXT = Icons.createProvider(Icons.FILE_TEXT);\n\tprivate static final IconProvider TEXT_CODE = Icons.createProvider(Icons.FILE_CODE);\n\tprivate static final IconProvider AUDIO = Icons.createProvider(Icons.FILE_AUDIO);\n\tprivate static final IconProvider VIDEO = Icons.createProvider(Icons.FILE_VIDEO);\n\tprivate static final IconProvider IMAGE = Icons.createProvider(Icons.FILE_IMAGE);\n\tprivate static final IconProvider EXECUTABLE = Icons.createProvider(Icons.FILE_PROGRAM);\n\tprivate static final IconProvider ZIP = Icons.createProvider(Icons.FILE_ZIP);\n\tprivate static final IconProvider JAR = Icons.createProvider(Icons.FILE_JAR);\n\tprivate static final IconProvider ANDROID = Icons.createProvider(Icons.ANDROID);\n\tprivate static final IconProvider UNKNOWN = Icons.createProvider(Icons.FILE_BINARY);\n\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getFileInfoIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull FileBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull FileInfo info) {\n\t\t// Built-in info match\n\t\tif (info.isTextFile()) {\n\t\t\tString name = info.getName();\n\t\t\tString ext = name.substring(name.lastIndexOf('.') + 1).toLowerCase();\n\t\t\treturn (CODE_EXTENSIONS.contains(ext)) ? TEXT_CODE : TEXT;\n\t\t} else if (info.isZipFile()) {\n\t\t\tString name = info.getName();\n\t\t\tString ext = name.substring(name.lastIndexOf('.') + 1).toLowerCase();\n\t\t\tif (ext.equals(\"jar\") || ext.equals(\"jmod\") || ext.equals(\"war\"))\n\t\t\t\treturn JAR;\n\t\t\tif (ext.equals(\"apk\"))\n\t\t\t\treturn ANDROID;\n\t\t\treturn ZIP;\n\t\t}\n\n\t\t// Content match\n\t\tbyte[] content = info.getRawContent();\n\t\tif (ByteHeaderUtil.matchAny(content, ByteHeaderUtil.IMAGE_HEADERS))\n\t\t\treturn IMAGE;\n\t\tif (ByteHeaderUtil.match(content, ByteHeaderUtil.CLASS))\n\t\t\treturn JAR;\n\t\tif (ByteHeaderUtil.matchAny(content, ByteHeaderUtil.PROGRAM_HEADERS))\n\t\t\treturn EXECUTABLE;\n\t\tif (ByteHeaderUtil.matchAny(content, ByteHeaderUtil.AUDIO_HEADERS))\n\t\t\treturn AUDIO;\n\t\tif (ByteHeaderUtil.matchAny(content, ByteHeaderUtil.VIDEO_HEADERS))\n\t\t\treturn VIDEO;\n\t\tif (ByteHeaderUtil.match(content, ByteHeaderUtil.DEX))\n\t\t\treturn ANDROID;\n\n\t\t// No obvious association\n\t\treturn UNKNOWN;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicInnerClassIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport javafx.scene.Node;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.properties.builtin.ThrowableProperty;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link InnerClassIconProviderFactory}.\n * <br>\n * Not used by the default implementation for context-menus on {@link InnerClassInfo}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicInnerClassIconProviderFactory implements InnerClassIconProviderFactory {\n\tprivate static final IconProvider MISSING = () -> new FontIconView(CarbonIcons.MISUSE_ALT);\n\tprivate static final IconProvider CLASS = Icons.createProvider(Icons.CLASS);\n\tprivate static final IconProvider INTERFACE = Icons.createProvider(Icons.INTERFACE);\n\tprivate static final IconProvider ANNO = Icons.createProvider(Icons.ANNOTATION);\n\tprivate static final IconProvider ENUM = Icons.createProvider(Icons.ENUM);\n\tprivate static final IconProvider ANONYMOUS = Icons.createProvider(Icons.CLASS_ANONYMOUS);\n\tprivate static final IconProvider ABSTRACT = Icons.createProvider(Icons.CLASS_ABSTRACT);\n\tprivate static final IconProvider EXCEPTION = Icons.createProvider(Icons.CLASS_EXCEPTION);\n\tprivate static final IconProvider ABSTRACT_EXCEPTION = Icons.createProvider(Icons.CLASS_ABSTRACT_EXCEPTION);\n\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getInnerClassInfoIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassInfo outerClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull InnerClassInfo inner) {\n\t\treturn () -> {\n\t\t\t// While the inner class attribute gives us access flags, we want to grab the REAL\n\t\t\t// class-info instance for the inner class. This allows us to check for properties and such.\n\t\t\tClassInfo innerClass = bundle.get(inner.getInnerClassName());\n\t\t\tif (innerClass == null)\n\t\t\t\treturn MISSING.makeIcon();\n\t\t\treturn classIconProvider(innerClass);\n\t\t};\n\t}\n\n\tprivate static Node classIconProvider(ClassInfo info) {\n\t\t// Special class cases\n\t\tif (info.hasEnumModifier()) return ENUM.makeIcon();\n\t\tif (info.hasAnnotationModifier()) return ANNO.makeIcon();\n\t\tif (info.hasInterfaceModifier()) return INTERFACE.makeIcon();\n\n\t\t// Normal class, consider other edge cases\n\t\tif (ThrowableProperty.get(info)) {\n\t\t\tif (info.hasAnnotationModifier()) {\n\t\t\t\treturn ABSTRACT_EXCEPTION.makeIcon();\n\t\t\t} else {\n\t\t\t\treturn EXCEPTION.makeIcon();\n\t\t\t}\n\t\t} else if (info.isAnonymousInnerClass()) {\n\t\t\treturn ANONYMOUS.makeIcon();\n\t\t} else if (info.hasAbstractModifier()) {\n\t\t\treturn ABSTRACT.makeIcon();\n\t\t}\n\t\treturn CLASS.makeIcon();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicInstructionIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport dev.xdark.blw.code.Instruction;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport javafx.scene.paint.Color;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link InstructionIconProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicInstructionIconProviderFactory implements InstructionIconProviderFactory {\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getInstructionIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull Instruction instruction) {\n\t\treturn () -> new FontIconView(CarbonIcons.CODE, Color.web(\"rgb(0, 175, 255)\"));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicMethodIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport javafx.collections.ObservableList;\nimport javafx.scene.Node;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link MethodIconProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicMethodIconProviderFactory implements MethodIconProviderFactory {\n\tprivate static final IconProvider METHOD = Icons.createProvider(Icons.METHOD);\n\tprivate static final IconProvider METHOD_ABSTRACT = Icons.createProvider(Icons.METHOD_ABSTRACT);\n\tprivate static final IconProvider ACCESS_FINAL = Icons.createProvider(Icons.ACCESS_FINAL);\n\tprivate static final IconProvider ACCESS_STATIC = Icons.createProvider(Icons.ACCESS_STATIC);\n\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getMethodMemberIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull MethodMember method) {\n\t\treturn () -> methodIconProvider(method);\n\t}\n\n\tprivate static Node methodIconProvider(MethodMember method) {\n\t\t// Base\n\t\tStackPane stack = new StackPane();\n\t\tObservableList<Node> children = stack.getChildren();\n\t\tif (method.hasAbstractModifier())\n\t\t\tchildren.add(METHOD_ABSTRACT.makeIcon());\n\t\telse\n\t\t\tchildren.add(METHOD.makeIcon());\n\n\t\t// Add overlay for certain flags.\n\t\tif (method.hasFinalModifier())\n\t\t\tchildren.add(ACCESS_FINAL.makeIcon());\n\t\tif (method.hasStaticModifier())\n\t\t\tchildren.add(ACCESS_STATIC.makeIcon());\n\n\t\t// Wrap with visibility.\n\t\treturn new HBox(stack, Icons.getVisibilityIcon(method.getAccess()));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicPackageIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link PackageIconProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicPackageIconProviderFactory implements PackageIconProviderFactory {\n\tprivate static final IconProvider PROVIDER = Icons.createProvider(Icons.FOLDER_PACKAGE);\n\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getPackageIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull String packageName) {\n\t\treturn PROVIDER;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicResourceIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport software.coley.recaf.info.*;\nimport software.coley.recaf.services.phantom.GeneratedPhantomWorkspaceResource;\nimport software.coley.recaf.util.ByteHeaderUtil;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceDirectoryResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link PackageIconProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicResourceIconProviderFactory implements ResourceIconProviderFactory {\n\tprivate static final IconProvider PROVIDER_DIR = Icons.createProvider(Icons.FOLDER);\n\tprivate static final IconProvider PROVIDER_ANDROID = Icons.createProvider(Icons.ANDROID);\n\tprivate static final IconProvider PROVIDER_TEXT = Icons.createProvider(Icons.FILE_TEXT);\n\tprivate static final IconProvider PROVIDER_ZIP = Icons.createProvider(Icons.FILE_ZIP);\n\tprivate static final IconProvider PROVIDER_JAR = Icons.createProvider(Icons.FILE_JAR);\n\tprivate static final IconProvider PROVIDER_CLASS = Icons.createProvider(Icons.FILE_CLASS);\n\tprivate static final IconProvider PROVIDER_IMAGE = Icons.createProvider(Icons.FILE_IMAGE);\n\tprivate static final IconProvider PROVIDER_AUDIO = Icons.createProvider(Icons.FILE_AUDIO);\n\tprivate static final IconProvider PROVIDER_PROGRAM = Icons.createProvider(Icons.FILE_PROGRAM);\n\tprivate static final IconProvider PROVIDER_PHANTOM = Icons.createProvider(Icons.PHANTOM);\n\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getResourceIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource) {\n\t\tif (resource instanceof WorkspaceDirectoryResource)\n\t\t\treturn PROVIDER_DIR;\n\t\tif (resource instanceof WorkspaceFileResource fileResource) {\n\t\t\tFileInfo file = fileResource.getFileInfo();\n\t\t\tif (file instanceof ApkFileInfo || file instanceof DexFileInfo || file instanceof ArscFileInfo)\n\t\t\t\treturn PROVIDER_ANDROID;\n\t\t\tif (file instanceof JarFileInfo || file instanceof WarFileInfo || file instanceof JModFileInfo)\n\t\t\t\treturn PROVIDER_JAR;\n\t\t\tif (file instanceof TextFileInfo)\n\t\t\t\treturn PROVIDER_TEXT;\n\t\t\tif (ByteHeaderUtil.match(file.getRawContent(), ByteHeaderUtil.CLASS))\n\t\t\t\treturn PROVIDER_CLASS;\n\t\t\tif (ByteHeaderUtil.matchAny(file.getRawContent(), ByteHeaderUtil.PROGRAM_HEADERS))\n\t\t\t\treturn PROVIDER_PROGRAM;\n\t\t\tif (ByteHeaderUtil.matchAny(file.getRawContent(), ByteHeaderUtil.IMAGE_HEADERS))\n\t\t\t\treturn PROVIDER_IMAGE;\n\t\t\tif (ByteHeaderUtil.matchAny(file.getRawContent(), ByteHeaderUtil.AUDIO_HEADERS))\n\t\t\t\treturn PROVIDER_AUDIO;\n\t\t}\n\t\tif (resource instanceof GeneratedPhantomWorkspaceResource)\n\t\t\treturn PROVIDER_PHANTOM;\n\t\treturn PROVIDER_ZIP;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicThrowsProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport javafx.scene.control.Label;\nimport javafx.scene.paint.Color;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link ThrowsIconProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicThrowsProviderFactory implements ThrowsIconProviderFactory {\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getThrowsIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull String thrownType) {\n\t\treturn () -> {\n\t\t\tLabel label = new Label(\"throws\");\n\t\t\tlabel.setTextFill(Color.web(\"rgb(0, 175, 255)\"));\n\t\t\treturn label;\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BasicVariableIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport javafx.scene.paint.Color;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Basic implementation for {@link VariableIconProviderFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BasicVariableIconProviderFactory implements VariableIconProviderFactory {\n\t@Nonnull\n\t@Override\n\tpublic IconProvider getVariableIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull LocalVariable variable) {\n\t\treturn () -> new FontIconView(CarbonIcons.CODE, Color.web(\"rgb(0, 175, 255)\"));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/BundleIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for {@link Bundle bundles}, to be plugged into {@link IconProviderService}\n * to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface BundleIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tThe bundle to create an icon for.\n\t *\n\t * @return Icon provider for the bundle.\n\t */\n\t@Nonnull\n\tdefault IconProvider getBundleIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull Bundle<? extends Info> bundle) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/CatchIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.CatchPathNode;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for {@link CatchPathNode catch T}, to be plugged into {@link IconProviderService}\n * to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface CatchIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param declaringMethod\n\t * \t\tContaining method.\n\t * @param caughtType\n\t * \t\tThe caught type to create an icon for.\n\t *\n\t * @return Icon provider for the method.\n\t */\n\t@Nonnull\n\tdefault IconProvider getCatchIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull String caughtType) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/ClassIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for {@link ClassInfo classes}, to be plugged into {@link IconProviderService}\n * to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface ClassIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create an icon for.\n\t *\n\t * @return Icon provider for the class.\n\t */\n\t@Nonnull\n\tdefault IconProvider getJvmClassInfoIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull JvmClassBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull JvmClassInfo info) {\n\t\treturn emptyProvider();\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create an icon for.\n\t *\n\t * @return Icon provider for the class.\n\t */\n\t@Nonnull\n\tdefault IconProvider getAndroidClassInfoIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull AndroidClassBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull AndroidClassInfo info) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/DirectoryIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for directories <i>(paths in {@link FileBundle})</i>, to be plugged into {@link IconProviderService}\n * to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface DirectoryIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tThe full path of the directory.\n\t *\n\t * @return Icon provider for the directory.\n\t */\n\t@Nonnull\n\tdefault IconProvider getDirectoryIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull FileBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull String directoryName) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/FieldIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for {@link FieldMember fields}, to be plugged into {@link IconProviderService}\n * to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface FieldIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param field\n\t * \t\tThe field to create an icon for.\n\t *\n\t * @return Icon provider for the field.\n\t */\n\t@Nonnull\n\tdefault IconProvider getFieldMemberIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull FieldMember field) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/FileIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for {@link FileInfo files}, to be plugged into {@link IconProviderService}\n * to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface FileIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe file to create an icon for.\n\t *\n\t * @return Icon provider for the file.\n\t */\n\t@Nonnull\n\tdefault IconProvider getFileInfoIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull FileBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull FileInfo info) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/IconProvider.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nullable;\nimport javafx.scene.Node;\n\n/**\n * Provides an icon. Primarily used when wanting to provide an expected icon\n * <i>(typically requiring some computation to assemble)</i> lazily.\n *\n * @author xDark\n */\n@FunctionalInterface\npublic interface IconProvider {\n\t/**\n\t * @return Provided icon. May be {@code null}.\n\t */\n\t@Nullable\n\tNode makeIcon();\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/IconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Base icon provider factory.\n *\n * @author Matt Coley\n * @see ClassIconProviderFactory For {@link JvmClassInfo} and {@link AndroidClassBundle} entries.\n * @see InnerClassIconProviderFactory For {@link InnerClassInfo} entries.\n * @see FieldIconProviderFactory For {@link FieldMember} entries.\n * @see MethodIconProviderFactory For {@link MethodMember} entries.\n * @see AnnotationIconProviderFactory Fpr {@link AnnotationInfo} entries.\n * @see FileIconProviderFactory For {@link FileInfo} entries.\n * @see DirectoryIconProviderFactory For directory entries, not linked to a specific {@link FileInfo}.\n * @see PackageIconProviderFactory  For package entries, not linked to a specific {@link ClassInfo}.\n * @see BundleIconProviderFactory For {@link Bundle} entries.\n * @see ResourceIconProviderFactory For {@link WorkspaceResource} entries.\n */\npublic interface IconProviderFactory {\n\t/**\n\t * @return Icon provider that provides {@code null}.\n\t */\n\t@Nonnull\n\tdefault IconProvider emptyProvider() {\n\t\treturn () -> null;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/IconProviderService.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport dev.xdark.blw.code.Instruction;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport software.coley.recaf.info.*;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.cell.context.FieldContextMenuProviderFactory;\nimport software.coley.recaf.services.cell.context.MethodContextMenuProviderFactory;\nimport software.coley.recaf.ui.control.tree.WorkspaceTreeCell;\nimport software.coley.recaf.util.BlwUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.*;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Provides support for providing icons for a variety of item types.\n * For instance, the graphics of {@link WorkspaceTreeCell} instances.\n * <br>\n * The icons displayed in the UI can be swapped out by supplying your own\n * {@link IconProviderFactory} instances to the overrides:\n * <ul>\n *     <li>{@link #setClassIconProviderOverride(ClassIconProviderFactory)}</li>\n *     <li>{@link #setFileIconProviderOverride(FileIconProviderFactory)}</li>\n *     <li>{@link #setInnerClassIconProviderOverride(InnerClassIconProviderFactory)}</li>\n *     <li>{@link #setFieldIconProviderOverride(FieldIconProviderFactory)}</li>\n *     <li>{@link #setMethodIconProviderOverride(MethodIconProviderFactory)}</li>\n *     <li>{@link #setPackageIconProviderOverride(PackageIconProviderFactory)}</li>\n *     <li>{@link #setDirectoryIconProviderOverride(DirectoryIconProviderFactory)}</li>\n *     <li>{@link #setBundleIconProviderOverride(BundleIconProviderFactory)}</li>\n *     <li>{@link #setResourceIconProviderOverride(ResourceIconProviderFactory)}</li>\n * </ul>\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class IconProviderService implements Service {\n\tpublic static final String SERVICE_ID = \"cell-icons\";\n\tprivate final IconProviderServiceConfig config;\n\t// Defaults\n\tprivate final ClassIconProviderFactory classIconDefault;\n\tprivate final FileIconProviderFactory fileIconDefault;\n\tprivate final InnerClassIconProviderFactory innerClassIconDefault;\n\tprivate final FieldIconProviderFactory fieldIconDefault;\n\tprivate final MethodIconProviderFactory methodIconDefault;\n\tprivate final InstructionIconProviderFactory instructionIconDefault;\n\tprivate final ThrowsIconProviderFactory throwsIconDefault;\n\tprivate final CatchIconProviderFactory catchIconDefault;\n\tprivate final VariableIconProviderFactory variableIconDefault;\n\tprivate final AnnotationIconProviderFactory annotationIconDefault;\n\tprivate final PackageIconProviderFactory packageIconDefault;\n\tprivate final DirectoryIconProviderFactory directoryIconDefault;\n\tprivate final BundleIconProviderFactory bundleIconDefault;\n\tprivate final ResourceIconProviderFactory resourceIconDefault;\n\t// Overrides\n\tprivate ClassIconProviderFactory classIconOverride;\n\tprivate FileIconProviderFactory fileIconOverride;\n\tprivate InnerClassIconProviderFactory innerClassIconOverride;\n\tprivate FieldIconProviderFactory fieldIconOverride;\n\tprivate MethodIconProviderFactory methodIconOverride;\n\tprivate InstructionIconProviderFactory instructionIconOverride;\n\tprivate ThrowsIconProviderFactory throwsIconOverride;\n\tprivate CatchIconProviderFactory catchIconOverride;\n\tprivate VariableIconProviderFactory variableIconOverride;\n\tprivate AnnotationIconProviderFactory annotationIconOverride;\n\tprivate PackageIconProviderFactory packageIconOverride;\n\tprivate DirectoryIconProviderFactory directoryIconOverride;\n\tprivate BundleIconProviderFactory bundleIconOverride;\n\tprivate ResourceIconProviderFactory resourceIconOverride;\n\n\t@Inject\n\tpublic IconProviderService(@Nonnull IconProviderServiceConfig config,\n\t\t\t\t\t\t\t   @Nonnull ClassIconProviderFactory classIconDefault,\n\t\t\t\t\t\t\t   @Nonnull FileIconProviderFactory fileIconDefault,\n\t\t\t\t\t\t\t   @Nonnull InnerClassIconProviderFactory innerClassIconDefault,\n\t\t\t\t\t\t\t   @Nonnull FieldIconProviderFactory fieldIconDefault,\n\t\t\t\t\t\t\t   @Nonnull MethodIconProviderFactory methodIconDefault,\n\t\t\t\t\t\t\t   @Nonnull InstructionIconProviderFactory instructionIconDefault,\n\t\t\t\t\t\t\t   @Nonnull ThrowsIconProviderFactory throwsIconDefault,\n\t\t\t\t\t\t\t   @Nonnull CatchIconProviderFactory catchIconDefault,\n\t\t\t\t\t\t\t   @Nonnull VariableIconProviderFactory variableIconDefault,\n\t\t\t\t\t\t\t   @Nonnull AnnotationIconProviderFactory annotationIconDefault,\n\t\t\t\t\t\t\t   @Nonnull PackageIconProviderFactory packageIconDefault,\n\t\t\t\t\t\t\t   @Nonnull DirectoryIconProviderFactory directoryIconDefault,\n\t\t\t\t\t\t\t   @Nonnull BundleIconProviderFactory bundleIconDefault,\n\t\t\t\t\t\t\t   @Nonnull ResourceIconProviderFactory resourceIconDefault) {\n\t\tthis.config = config;\n\n\t\t// Default factories\n\t\tthis.classIconDefault = classIconDefault;\n\t\tthis.fileIconDefault = fileIconDefault;\n\t\tthis.innerClassIconDefault = innerClassIconDefault;\n\t\tthis.fieldIconDefault = fieldIconDefault;\n\t\tthis.methodIconDefault = methodIconDefault;\n\t\tthis.instructionIconDefault = instructionIconDefault;\n\t\tthis.throwsIconDefault = throwsIconDefault;\n\t\tthis.catchIconDefault = catchIconDefault;\n\t\tthis.variableIconDefault = variableIconDefault;\n\t\tthis.annotationIconDefault = annotationIconDefault;\n\t\tthis.packageIconDefault = packageIconDefault;\n\t\tthis.directoryIconDefault = directoryIconDefault;\n\t\tthis.bundleIconDefault = bundleIconDefault;\n\t\tthis.resourceIconDefault = resourceIconDefault;\n\t}\n\n\t/**\n\t * Delegates to {@link ClassIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create an icon for.\n\t *\n\t * @return Icon provider for the class.\n\t */\n\t@Nonnull\n\tpublic IconProvider getJvmClassInfoIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull JvmClassBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull JvmClassInfo info) {\n\t\tClassIconProviderFactory factory = classIconOverride != null ? classIconOverride : classIconDefault;\n\t\treturn factory.getJvmClassInfoIconProvider(workspace, resource, bundle, info);\n\t}\n\n\t/**\n\t * Delegates to {@link ClassIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create an icon for.\n\t *\n\t * @return Icon provider for the class.\n\t */\n\t@Nonnull\n\tpublic IconProvider getAndroidClassInfoIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull AndroidClassBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull AndroidClassInfo info) {\n\t\tClassIconProviderFactory factory = classIconOverride != null ? classIconOverride : classIconDefault;\n\t\treturn factory.getAndroidClassInfoIconProvider(workspace, resource, bundle, info);\n\t}\n\n\t/**\n\t * Delegates to {@link FileIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe file to create an icon for.\n\t *\n\t * @return Icon provider for the file.\n\t */\n\t@Nonnull\n\tpublic IconProvider getFileInfoIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull FileBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull FileInfo info) {\n\t\tFileIconProviderFactory factory = fileIconOverride != null ? fileIconOverride : fileIconDefault;\n\t\treturn factory.getFileInfoIconProvider(workspace, resource, bundle, info);\n\t}\n\n\t/**\n\t * Delegates to {@link InnerClassIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param outerClass\n\t * \t\tOuter class.\n\t * @param inner\n\t * \t\tThe inner class to create an icon for.\n\t *\n\t * @return Icon provider for the inner class.\n\t */\n\t@Nonnull\n\tpublic IconProvider getInnerClassInfoIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassInfo outerClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull InnerClassInfo inner) {\n\t\tInnerClassIconProviderFactory factory = innerClassIconOverride != null ? innerClassIconOverride : innerClassIconDefault;\n\t\treturn factory.getInnerClassInfoIconProvider(workspace, resource, bundle, outerClass, inner);\n\t}\n\n\t/**\n\t * Delegates to {@link FieldContextMenuProviderFactory} and {@link MethodContextMenuProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param member\n\t * \t\tThe member to create an icon for.\n\t *\n\t * @return Icon provider for the class member.\n\t */\n\t@Nonnull\n\tpublic IconProvider getClassMemberIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassMember member) {\n\t\tif (member.isField()) {\n\t\t\tFieldIconProviderFactory factory = fieldIconOverride != null ? fieldIconOverride : fieldIconDefault;\n\t\t\treturn factory.getFieldMemberIconProvider(workspace, resource, bundle, declaringClass, (FieldMember) member);\n\t\t} else if (member.isMethod()) {\n\t\t\tMethodIconProviderFactory factory = methodIconOverride != null ? methodIconOverride : methodIconDefault;\n\t\t\treturn factory.getMethodMemberIconProvider(workspace, resource, bundle, declaringClass, (MethodMember) member);\n\t\t} else {\n\t\t\tthrow new IllegalStateException(\"Unsupported member: \" + member.getClass().getName());\n\t\t}\n\t}\n\n\t/**\n\t * Delegates to {@link InstructionIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param declaringMethod\n\t * \t\tContaining method.\n\t * @param instruction\n\t * \t\tThe instruction to create an icon for.\n\t *\n\t * @return Icon provider for the class member.\n\t */\n\t@Nonnull\n\tpublic IconProvider getInstructionIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassBundle<?> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull AbstractInsnNode instruction) {\n\t\treturn getInstructionIconProvider(workspace, resource, bundle, declaringClass, declaringMethod, BlwUtil.convert(instruction));\n\t}\n\n\t/**\n\t * Delegates to {@link InstructionIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param declaringMethod\n\t * \t\tContaining method.\n\t * @param instruction\n\t * \t\tThe instruction to create an icon for.\n\t *\n\t * @return Icon provider for the instruction.\n\t */\n\t@Nonnull\n\tpublic IconProvider getInstructionIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassBundle<?> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull Instruction instruction) {\n\t\tInstructionIconProviderFactory factory = instructionIconOverride != null ? instructionIconOverride : instructionIconDefault;\n\t\treturn factory.getInstructionIconProvider(workspace, resource, bundle, declaringClass, declaringMethod, instruction);\n\t}\n\n\t/**\n\t * Delegates to {@link VariableIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param declaringMethod\n\t * \t\tContaining method.\n\t * @param variable\n\t * \t\tThe local variable to create an icon for.\n\t *\n\t * @return Icon provider for the local variable.\n\t */\n\t@Nonnull\n\tpublic IconProvider getVariableIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassBundle<?> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull LocalVariable variable) {\n\t\tVariableIconProviderFactory factory = variableIconOverride != null ? variableIconOverride : variableIconDefault;\n\t\treturn factory.getVariableIconProvider(workspace, resource, bundle, declaringClass, declaringMethod, variable);\n\t}\n\n\t/**\n\t * Delegates to {@link InstructionIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param declaringMethod\n\t * \t\tContaining method.\n\t * @param thrown\n\t * \t\tThe thrown type to create an icon for.\n\t *\n\t * @return Icon provider for the thrown type.\n\t */\n\t@Nonnull\n\tpublic IconProvider getThrowsIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassBundle<?> bundle,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull String thrown) {\n\t\tThrowsIconProviderFactory factory = throwsIconOverride != null ? throwsIconOverride : throwsIconDefault;\n\t\treturn factory.getThrowsIconProvider(workspace, resource, bundle, declaringClass, declaringMethod, thrown);\n\t}\n\n\t/**\n\t * Delegates to {@link InstructionIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param declaringMethod\n\t * \t\tContaining method.\n\t * @param caught\n\t * \t\tThe caught type to create an icon for.\n\t *\n\t * @return Icon provider for the caught type.\n\t */\n\t@Nonnull\n\tpublic IconProvider getCatchIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassBundle<?> bundle,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull String caught) {\n\t\tCatchIconProviderFactory factory = catchIconOverride != null ? catchIconOverride : catchIconDefault;\n\t\treturn factory.getCatchIconProvider(workspace, resource, bundle, declaringClass, declaringMethod, caught);\n\t}\n\n\t/**\n\t * Delegates to {@link AnnotationIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param annotated\n\t * \t\tThe annotated item.\n\t * @param annotation\n\t * \t\tThe annotation to create an icon for.\n\t *\n\t * @return Text provider for the annotation.\n\t */\n\t@Nonnull\n\tpublic IconProvider getAnnotationIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull Annotated annotated,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull AnnotationInfo annotation) {\n\t\tAnnotationIconProviderFactory factory = annotationIconOverride != null ? annotationIconOverride : annotationIconDefault;\n\t\treturn factory.getAnnotationIconProvider(workspace, resource, bundle, annotated, annotation);\n\t}\n\n\t/**\n\t * Delegates to {@link PackageIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param packageName\n\t * \t\tThe full package name, separated by {@code /}.\n\t *\n\t * @return Icon provider for the package.\n\t */\n\t@Nonnull\n\tpublic IconProvider getPackageIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull String packageName) {\n\t\tPackageIconProviderFactory factory = packageIconOverride != null ? packageIconOverride : packageIconDefault;\n\t\treturn factory.getPackageIconProvider(workspace, resource, bundle, packageName);\n\t}\n\n\t/**\n\t * Delegates to {@link DirectoryIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tThe full path of the directory.\n\t *\n\t * @return Icon provider for the directory.\n\t */\n\t@Nonnull\n\tpublic IconProvider getDirectoryIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull FileBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull String directoryName) {\n\t\tDirectoryIconProviderFactory factory = directoryIconOverride != null ? directoryIconOverride : directoryIconDefault;\n\t\treturn factory.getDirectoryIconProvider(workspace, resource, bundle, directoryName);\n\t}\n\n\t/**\n\t * Delegates to {@link BundleIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tThe bundle to create an icon for.\n\t *\n\t * @return Icon provider for the bundle.\n\t */\n\t@Nonnull\n\tpublic IconProvider getBundleIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull Bundle<? extends Info> bundle) {\n\t\tBundleIconProviderFactory factory = bundleIconOverride != null ? bundleIconOverride : bundleIconDefault;\n\t\treturn factory.getBundleIconProvider(workspace, resource, bundle);\n\t}\n\n\t/**\n\t * Delegates to {@link ResourceIconProviderFactory}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tThe resource to create an icon for.\n\t *\n\t * @return Icon provider for the resource.\n\t */\n\t@Nonnull\n\tpublic IconProvider getResourceIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource) {\n\t\tResourceIconProviderFactory factory = resourceIconOverride != null ? resourceIconOverride : resourceIconDefault;\n\t\treturn factory.getResourceIconProvider(workspace, resource);\n\t}\n\n\t/**\n\t * @return Default icon provider for classes.\n\t */\n\t@Nonnull\n\tpublic ClassIconProviderFactory getClassIconDefault() {\n\t\treturn classIconDefault;\n\t}\n\n\t/**\n\t * @return Default icon provider for methods.\n\t */\n\t@Nonnull\n\tpublic MethodIconProviderFactory getMethodIconDefault() {\n\t\treturn methodIconDefault;\n\t}\n\n\t/**\n\t * @return Default icon provider for fields.\n\t */\n\t@Nonnull\n\tpublic FieldIconProviderFactory getFieldIconDefault() {\n\t\treturn fieldIconDefault;\n\t}\n\n\t/**\n\t * @return Default icon provider for annotations.\n\t */\n\t@Nonnull\n\tpublic AnnotationIconProviderFactory getAnnotationIconDefault() {\n\t\treturn annotationIconDefault;\n\t}\n\n\t/**\n\t * @return Default icon provider for inner classes.\n\t */\n\t@Nonnull\n\tpublic InnerClassIconProviderFactory getInnerClassIconDefault() {\n\t\treturn innerClassIconDefault;\n\t}\n\n\t/**\n\t * @return Default icon provider for instructions.\n\t */\n\t@Nonnull\n\tpublic InstructionIconProviderFactory getInstructionIconDefault() {\n\t\treturn instructionIconDefault;\n\t}\n\n\t/**\n\t * @return Default icon provider for thrown types.\n\t */\n\t@Nonnull\n\tpublic ThrowsIconProviderFactory getThrowsIconDefault() {\n\t\treturn throwsIconDefault;\n\t}\n\n\t/**\n\t * @return Default icon provider for catch blocks.\n\t */\n\t@Nonnull\n\tpublic CatchIconProviderFactory getCatchIconDefault() {\n\t\treturn catchIconDefault;\n\t}\n\n\t/**\n\t * @return Default icon provider for variables.\n\t */\n\t@Nonnull\n\tpublic VariableIconProviderFactory getVariableIconDefault() {\n\t\treturn variableIconDefault;\n\t}\n\n\t/**\n\t * @return Default icon provider for files.\n\t */\n\t@Nonnull\n\tpublic FileIconProviderFactory getFileIconDefault() {\n\t\treturn fileIconDefault;\n\t}\n\n\t/**\n\t * @return Default icon provider for packages.\n\t */\n\t@Nonnull\n\tpublic PackageIconProviderFactory getPackageIconDefault() {\n\t\treturn packageIconDefault;\n\t}\n\n\t/**\n\t * @return Default icon provider for directories.\n\t */\n\t@Nonnull\n\tpublic DirectoryIconProviderFactory getDirectoryIconDefault() {\n\t\treturn directoryIconDefault;\n\t}\n\n\t/**\n\t * @return Default icon provider for bundles.\n\t */\n\t@Nonnull\n\tpublic BundleIconProviderFactory getBundleIconDefault() {\n\t\treturn bundleIconDefault;\n\t}\n\n\t/**\n\t * @return Default icon provider for resources.\n\t */\n\t@Nonnull\n\tpublic ResourceIconProviderFactory getResourceIconDefault() {\n\t\treturn resourceIconDefault;\n\t}\n\n\t/**\n\t * @return Override factory for supplying class icon providers.\n\t */\n\t@Nullable\n\tpublic ClassIconProviderFactory getClassIconProviderOverride() {\n\t\treturn classIconOverride;\n\t}\n\n\t/**\n\t * @param classIconOverride\n\t * \t\tOverride factory for supplying class icon providers.\n\t */\n\tpublic void setClassIconProviderOverride(@Nullable ClassIconProviderFactory classIconOverride) {\n\t\tthis.classIconOverride = classIconOverride;\n\t}\n\n\t/**\n\t * @return Override factory for supplying file icon providers.\n\t */\n\t@Nullable\n\tpublic FileIconProviderFactory getFileIconProviderOverride() {\n\t\treturn fileIconOverride;\n\t}\n\n\t/**\n\t * @param fileIconOverride\n\t * \t\tOverride factory for supplying file icon providers.\n\t */\n\tpublic void setFileIconProviderOverride(@Nullable FileIconProviderFactory fileIconOverride) {\n\t\tthis.fileIconOverride = fileIconOverride;\n\t}\n\n\t/**\n\t * @return Override factory for supplying inner class icon providers.\n\t */\n\t@Nullable\n\tpublic InnerClassIconProviderFactory getInnerClassIconProviderOverride() {\n\t\treturn innerClassIconOverride;\n\t}\n\n\t/**\n\t * @param innerClassIconOverride\n\t * \t\tOverride factory for supplying inner class icon providers.\n\t */\n\tpublic void setInnerClassIconProviderOverride(@Nullable InnerClassIconProviderFactory innerClassIconOverride) {\n\t\tthis.innerClassIconOverride = innerClassIconOverride;\n\t}\n\n\t/**\n\t * @return Override factory for supplying field icon providers.\n\t */\n\t@Nullable\n\tpublic FieldIconProviderFactory getFieldIconProviderOverride() {\n\t\treturn fieldIconOverride;\n\t}\n\n\t/**\n\t * @param fieldIconOverride\n\t * \t\tOverride factory for supplying field icon providers.\n\t */\n\tpublic void setFieldIconProviderOverride(@Nullable FieldIconProviderFactory fieldIconOverride) {\n\t\tthis.fieldIconOverride = fieldIconOverride;\n\t}\n\n\t/**\n\t * @return Override factory for supplying method icon providers.\n\t */\n\t@Nullable\n\tpublic MethodIconProviderFactory getMethodIconProviderOverride() {\n\t\treturn methodIconOverride;\n\t}\n\n\t/**\n\t * @param methodIconOverride\n\t * \t\tOverride factory for supplying method icon providers.\n\t */\n\tpublic void setMethodIconProviderOverride(@Nullable MethodIconProviderFactory methodIconOverride) {\n\t\tthis.methodIconOverride = methodIconOverride;\n\t}\n\n\t/**\n\t * @return Override factory for supplying instruction icon providers.\n\t */\n\t@Nullable\n\tpublic InstructionIconProviderFactory getInstructionIconOverride() {\n\t\treturn instructionIconOverride;\n\t}\n\n\t/**\n\t * @param instructionIconOverride\n\t * \t\tOverride factory for supplying instruction icon providers.\n\t */\n\tpublic void setInstructionIconOverride(@Nullable InstructionIconProviderFactory instructionIconOverride) {\n\t\tthis.instructionIconOverride = instructionIconOverride;\n\t}\n\n\t/**\n\t * @return Override factory for supplying {@code throws} icon providers.\n\t */\n\t@Nullable\n\tpublic ThrowsIconProviderFactory getThrowsIconOverride() {\n\t\treturn throwsIconOverride;\n\t}\n\n\t/**\n\t * @param throwsIconOverride\n\t * \t\tOverride factory for supplying {@code throws} icon providers.\n\t */\n\tpublic void setThrowsIconOverride(@Nullable ThrowsIconProviderFactory throwsIconOverride) {\n\t\tthis.throwsIconOverride = throwsIconOverride;\n\t}\n\n\t/**\n\t * @return Override factory for supplying {@code catch} icon providers.\n\t */\n\t@Nullable\n\tpublic CatchIconProviderFactory getCatchIconOverride() {\n\t\treturn catchIconOverride;\n\t}\n\n\t/**\n\t * @param catchIconOverride\n\t * \t\tOverride factory for supplying {@code catch} icon providers.\n\t */\n\tpublic void setCatchIconOverride(@Nullable CatchIconProviderFactory catchIconOverride) {\n\t\tthis.catchIconOverride = catchIconOverride;\n\t}\n\n\t/**\n\t * @return Override factory for supplying {@code var} icon providers.\n\t */\n\t@Nullable\n\tpublic VariableIconProviderFactory getVariableIconOverride() {\n\t\treturn variableIconOverride;\n\t}\n\n\t/**\n\t * @param variableIconOverride\n\t * \t\tOverride factory for supplying {@code var} icon providers.\n\t */\n\tpublic void setVariableIconOverride(@Nullable VariableIconProviderFactory variableIconOverride) {\n\t\tthis.variableIconOverride = variableIconOverride;\n\t}\n\n\t/**\n\t * @return Override factory for supplying annotation icon providers.\n\t */\n\t@Nullable\n\tpublic AnnotationIconProviderFactory getAnnotationIconProviderOverride() {\n\t\treturn annotationIconOverride;\n\t}\n\n\t/**\n\t * @param annotationIconOverride\n\t * \t\tOverride factory for supplying annotation icon providers.\n\t */\n\tpublic void setAnnotationIconProviderOverride(@Nullable AnnotationIconProviderFactory annotationIconOverride) {\n\t\tthis.annotationIconOverride = annotationIconOverride;\n\t}\n\n\t/**\n\t * @return Override factory for supplying package icon providers.\n\t */\n\t@Nullable\n\tpublic PackageIconProviderFactory getPackageIconProviderOverride() {\n\t\treturn packageIconOverride;\n\t}\n\n\t/**\n\t * @param packageIconOverride\n\t * \t\tOverride factory for supplying package icon providers.\n\t */\n\tpublic void setPackageIconProviderOverride(@Nullable PackageIconProviderFactory packageIconOverride) {\n\t\tthis.packageIconOverride = packageIconOverride;\n\t}\n\n\t/**\n\t * @return Override factory for supplying directory icon providers.\n\t */\n\t@Nullable\n\tpublic DirectoryIconProviderFactory getDirectoryIconProviderOverride() {\n\t\treturn directoryIconOverride;\n\t}\n\n\t/**\n\t * @param directoryIconOverride\n\t * \t\tOverride factory for supplying directory icon providers.\n\t */\n\tpublic void setDirectoryIconProviderOverride(@Nullable DirectoryIconProviderFactory directoryIconOverride) {\n\t\tthis.directoryIconOverride = directoryIconOverride;\n\t}\n\n\t/**\n\t * @return Override factory for supplying bundle icon providers.\n\t */\n\t@Nullable\n\tpublic BundleIconProviderFactory getBundleIconProviderOverride() {\n\t\treturn bundleIconOverride;\n\t}\n\n\t/**\n\t * @param bundleIconOverride\n\t * \t\tOverride factory for supplying bundle icon providers.\n\t */\n\tpublic void setBundleIconProviderOverride(@Nullable BundleIconProviderFactory bundleIconOverride) {\n\t\tthis.bundleIconOverride = bundleIconOverride;\n\t}\n\n\t/**\n\t * @return Override factory for supplying resource icon providers.\n\t */\n\t@Nullable\n\tpublic ResourceIconProviderFactory getResourceIconProviderOverride() {\n\t\treturn resourceIconOverride;\n\t}\n\n\t/**\n\t * @param resourceIconOverride\n\t * \t\tOverride factory for supplying resource icon providers.\n\t */\n\tpublic void setResourceIconProviderOverride(@Nullable ResourceIconProviderFactory resourceIconOverride) {\n\t\tthis.resourceIconOverride = resourceIconOverride;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IconProviderServiceConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/IconProviderServiceConfig.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link IconProviderService}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class IconProviderServiceConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic IconProviderServiceConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, IconProviderService.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/InnerClassIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for {@link InnerClassInfo inner classes}, to be plugged into {@link IconProviderService}\n * to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface InnerClassIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param outerClass\n\t * \t\tOuter class.\n\t * @param inner\n\t * \t\tThe inner class to create an icon for.\n\t *\n\t * @return Icon provider for the inner class.\n\t */\n\t@Nonnull\n\tdefault IconProvider getInnerClassInfoIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassInfo outerClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull InnerClassInfo inner) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/InstructionIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport dev.xdark.blw.code.Instruction;\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for {@link Instruction instructions}, to be plugged into {@link IconProviderService}\n * to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface InstructionIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param declaringMethod\n\t * \t\tContaining method.\n\t * @param instruction\n\t * \t\tThe instruction to create an icon for.\n\t *\n\t * @return Icon provider for the method.\n\t */\n\t@Nonnull\n\tdefault IconProvider getInstructionIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull Instruction instruction) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/MethodIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for {@link MethodMember method}, to be plugged into {@link IconProviderService}\n * to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface MethodIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param method\n\t * \t\tThe method to create an icon for.\n\t *\n\t * @return Icon provider for the method.\n\t */\n\t@Nonnull\n\tdefault IconProvider getMethodMemberIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull MethodMember method) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/PackageIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for packages <i>(paths in {@link JvmClassBundle} and {@link AndroidClassBundle})</i>,\n * to be plugged into {@link IconProviderService} to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface PackageIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param packageName\n\t * \t\tThe full package name, separated by {@code /}.\n\t *\n\t * @return Icon provider for the package.\n\t */\n\t@Nonnull\n\tdefault IconProvider getPackageIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull String packageName) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/ResourceIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for {@link WorkspaceResource resources}, to be plugged into {@link IconProviderService}\n * to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface ResourceIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tThe resource to create an icon for.\n\t *\n\t * @return Icon provider for the resource.\n\t */\n\t@Nonnull\n\tdefault IconProvider getResourceIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/ThrowsIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ThrowsPathNode;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for {@link ThrowsPathNode throws T}, to be plugged into {@link IconProviderService}\n * to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface ThrowsIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param declaringMethod\n\t * \t\tContaining method.\n\t * @param thrownType\n\t * \t\tThe thrown type to create an icon for.\n\t *\n\t * @return Icon provider for the method.\n\t */\n\t@Nonnull\n\tdefault IconProvider getThrowsIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull String thrownType) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/icon/VariableIconProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.icon;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Icon provider for {@link LocalVariable variables}, to be plugged into {@link IconProviderService}\n * to allow for third party icon customization.\n *\n * @author Matt Coley\n */\npublic interface VariableIconProviderFactory extends IconProviderFactory {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param declaringMethod\n\t * \t\tContaining method.\n\t * @param variable\n\t * \t\tThe variable to create an icon for.\n\t *\n\t * @return Icon provider for the method.\n\t */\n\t@Nonnull\n\tdefault IconProvider getVariableIconProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull LocalVariable variable) {\n\t\treturn emptyProvider();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/text/TextProvider.java",
    "content": "package software.coley.recaf.services.cell.text;\n\nimport jakarta.annotation.Nullable;\n\n/**\n * Provides some text. Primarily used when wanting to provide an expected text pattern lazily.\n *\n * @author Matt Coley\n */\npublic interface TextProvider {\n\t/**\n\t * @return Provided text. May be {@code null}.\n\t */\n\t@Nullable\n\tString makeText();\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/text/TextProviderFactory.java",
    "content": "package software.coley.recaf.services.cell.text;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Base text provider factory.\n *\n * @author Matt Coley\n */\npublic interface TextProviderFactory {\n\t/**\n\t * @return Text provider that provides {@code null}.\n\t */\n\t@Nonnull\n\tdefault TextProvider emptyProvider() {\n\t\treturn () -> null;\n\t}\n\n\t/**\n\t * @return Text provider that provides {@code \"\"}.\n\t */\n\t@Nonnull\n\tdefault TextProvider blankProvider() {\n\t\treturn () -> \"\";\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/text/TextProviderService.java",
    "content": "package software.coley.recaf.services.cell.text;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.benf.cfr.reader.entities.annotations.ElementValue;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.AbstractInsnNode;\nimport software.coley.recaf.info.*;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationElement;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.LocalVariable;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.phantom.GeneratedPhantomWorkspaceResource;\nimport software.coley.recaf.ui.config.MemberDisplayFormatConfig;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.ui.control.tree.WorkspaceTreeCell;\nimport software.coley.recaf.util.BlwUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.*;\nimport software.coley.recaf.workspace.model.resource.*;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Provides support for providing text for a variety of item types.\n * For instance, the text of {@link WorkspaceTreeCell} instances.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class TextProviderService implements Service {\n\tpublic static final String SERVICE_ID = \"cell-text\";\n\tprivate final TextProviderServiceConfig config;\n\tprivate final TextFormatConfig formatConfig;\n\tprivate final MemberDisplayFormatConfig memberFormatConfig;\n\n\t@Inject\n\tpublic TextProviderService(@Nonnull TextProviderServiceConfig config,\n\t                           @Nonnull TextFormatConfig formatConfig,\n\t                           @Nonnull MemberDisplayFormatConfig memberFormatConfig) {\n\t\tthis.config = config;\n\t\tthis.formatConfig = formatConfig;\n\t\tthis.memberFormatConfig = memberFormatConfig;\n\t\t// Unlike the other services for graphics/menus, I don't see a use-case for text customization...\n\t\t// Will keep the model similar to them though just in case so that it is easy to add in the future.\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create a text for.\n\t *\n\t * @return Text provider for the class.\n\t */\n\t@Nonnull\n\tpublic TextProvider getClassInfoTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassInfo info) {\n\t\treturn () -> formatConfig.filter(info.getName());\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create a text for.\n\t *\n\t * @return Text provider for the class.\n\t */\n\t@Nonnull\n\tpublic TextProvider getJvmClassInfoTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull JvmClassBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull JvmClassInfo info) {\n\t\treturn () -> formatConfig.filter(info.getName());\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe class to create text for.\n\t *\n\t * @return Text provider for the class.\n\t */\n\t@Nonnull\n\tpublic TextProvider getAndroidClassInfoTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull AndroidClassBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull AndroidClassInfo info) {\n\t\treturn () -> formatConfig.filter(info.getName());\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param outerClass\n\t * \t\tOuter class.\n\t * @param inner\n\t * \t\tThe inner class to create text for.\n\t *\n\t * @return Text provider for the inner class.\n\t */\n\t@Nonnull\n\tpublic TextProvider getInnerClassInfoTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassInfo outerClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull InnerClassInfo inner) {\n\t\treturn () -> formatConfig.filter(inner.getSimpleName());\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param fieldOrMethod\n\t * \t\tThe field or method to create text for.\n\t *\n\t * @return Text provider for the field or method.\n\t */\n\t@Nonnull\n\tpublic TextProvider getMemberTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassMember fieldOrMethod) {\n\t\treturn fieldOrMethod.isField() ?\n\t\t\t\tgetFieldMemberTextProvider(workspace, resource, bundle, declaringClass, (FieldMember) fieldOrMethod) :\n\t\t\t\tgetMethodMemberTextProvider(workspace, resource, bundle, declaringClass, (MethodMember) fieldOrMethod);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param field\n\t * \t\tThe field to create text for.\n\t *\n\t * @return Text provider for the field.\n\t */\n\t@Nonnull\n\tpublic TextProvider getFieldMemberTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull FieldMember field) {\n\t\treturn () -> formatConfig.filter(memberFormatConfig.getDisplay(field), false, true, true);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param method\n\t * \t\tThe method to create text for.\n\t *\n\t * @return Text provider for the method.\n\t */\n\t@Nonnull\n\tpublic TextProvider getMethodMemberTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull MethodMember method) {\n\t\treturn () -> formatConfig.filter(memberFormatConfig.getDisplay(method), false, true, true);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param declaringMethod\n\t * \t\tContaining method.\n\t * @param insn\n\t * \t\tVariable to get the text of.\n\t *\n\t * @return Text provider for the instruction.\n\t */\n\t@Nonnull\n\tpublic TextProvider getInstructionTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull AbstractInsnNode insn) {\n\t\treturn () -> formatConfig.filterMaxLength(BlwUtil.toString(insn));\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param declaringMethod\n\t * \t\tContaining method.\n\t * @param variable\n\t * \t\tVariable to get the text of.\n\t *\n\t * @return Text provider for the instruction.\n\t */\n\t@Nonnull\n\tpublic TextProvider getVariableTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull LocalVariable variable) {\n\t\treturn () -> {\n\t\t\tString text;\n\t\t\tType type = Type.getType(variable.getDescriptor());\n\t\t\tString name = formatConfig.filter(variable.getName());\n\t\t\tif (Types.isPrimitive(type)) {\n\t\t\t\ttext = type.getClassName() + \" \" + name;\n\t\t\t} else {\n\t\t\t\ttext = formatConfig.filterShorten(type.getClassName()) + \" \" + name;\n\t\t\t}\n\t\t\treturn formatConfig.filterMaxLength(text);\n\t\t};\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param declaringMethod\n\t * \t\tContaining method.\n\t * @param thrownType\n\t * \t\tThrown type to get the text of.\n\t *\n\t * @return Text provider for the instruction.\n\t */\n\t@Nonnull\n\tpublic TextProvider getThrowsTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull String thrownType) {\n\t\treturn () -> formatConfig.filter(thrownType);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param declaringMethod\n\t * \t\tContaining method.\n\t * @param caughtType\n\t * \t\tCaught type to get the text of.\n\t *\n\t * @return Text provider for the instruction.\n\t */\n\t@Nonnull\n\tpublic TextProvider getCatchTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull ClassInfo declaringClass,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull MethodMember declaringMethod,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull String caughtType) {\n\t\treturn () -> formatConfig.filter(caughtType);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringFile\n\t * \t\tContaining file.\n\t * @param line\n\t * \t\tThe line in the file to get the text of.\n\t *\n\t * @return Text provider for the line number.\n\t */\n\t@Nonnull\n\tpublic TextProvider getLineNumberTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull FileBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull FileInfo declaringFile,\n\t\t\t\t\t\t\t\t\t\t\t\t  int line) {\n\t\treturn () -> {\n\t\t\tif (declaringFile.isTextFile()) {\n\t\t\t\tint index = line - 1;\n\t\t\t\tString[] lines = declaringFile.asTextFile().getTextLines();\n\t\t\t\tif (index >= 0 && index < lines.length)\n\t\t\t\t\treturn formatConfig.filterMaxLength(lines[index]);\n\t\t\t}\n\t\t\treturn \"???\";\n\t\t};\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param annotated\n\t * \t\tThe annotated item.\n\t * @param annotation\n\t * \t\tThe annotation to create an icon for.\n\t *\n\t * @return Text provider for the annotation.\n\t */\n\t@Nonnull\n\tpublic TextProvider getAnnotationTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull Annotated annotated,\n\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull AnnotationInfo annotation) {\n\t\treturn () -> {\n\t\t\tString desc = annotation.getDescriptor();\n\t\t\tString type = formatConfig.filter(desc.substring(1, desc.length() - 1));\n\n\t\t\tStringBuilder sb = new StringBuilder(type);\n\t\t\tMap<String, AnnotationElement> elements = annotation.getElements();\n\t\t\tif (!elements.isEmpty()) {\n\t\t\t\tsb.append('(');\n\t\t\t\telements.forEach((key, element) -> {\n\t\t\t\t\tObject value = element.getElementValue();\n\t\t\t\t\tsb.append(key).append(\" = \");\n\t\t\t\t\tswitch (value) {\n\t\t\t\t\t\tcase String s -> sb.append('\"').append(formatConfig.filter(s)).append('\"');\n\t\t\t\t\t\tcase ElementValue elementValue -> sb.append(\"{...}\");\n\t\t\t\t\t\tcase List list -> sb.append(\"[...]\");\n\t\t\t\t\t\tdefault -> sb.append('\"').append(formatConfig.filter(String.valueOf(value))).append('\"');\n\t\t\t\t\t}\n\t\t\t\t\tsb.append(\", \");\n\t\t\t\t});\n\t\t\t\tsb.setLength(sb.length() - 2);\n\t\t\t\tsb.append(')');\n\t\t\t}\n\n\t\t\treturn formatConfig.filterMaxLength(sb.toString());\n\t\t};\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tThe file to create text for.\n\t *\n\t * @return Text provider for the file.\n\t */\n\t@Nonnull\n\tpublic TextProvider getFileInfoTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull FileBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull FileInfo info) {\n\t\treturn () -> formatConfig.filter(info.getName());\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param packageName\n\t * \t\tThe full package name, separated by {@code /}.\n\t *\n\t * @return Text provider for the package.\n\t */\n\t@Nonnull\n\tpublic TextProvider getPackageTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t\t\t\t   @Nonnull String packageName) {\n\t\treturn () -> {\n\t\t\tif (packageName.isEmpty())\n\t\t\t\treturn Lang.get(\"tree.defaultpackage\");\n\t\t\treturn formatConfig.filter(packageName);\n\t\t};\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tThe full path of the directory.\n\t *\n\t * @return Text provider for the directory.\n\t */\n\t@Nonnull\n\tpublic TextProvider getDirectoryTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull FileBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull String directoryName) {\n\t\treturn () -> {\n\t\t\tif (directoryName.isEmpty())\n\t\t\t\treturn Lang.get(\"tree.defaultdirectory\");\n\t\t\treturn formatConfig.filter(directoryName);\n\t\t};\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tThe bundle to create text for.\n\t *\n\t * @return Text provider for the bundle.\n\t */\n\t@Nonnull\n\tpublic TextProvider getBundleTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull Bundle<? extends Info> bundle) {\n\t\treturn () -> {\n\t\t\tif (bundle instanceof AndroidClassBundle) {\n\t\t\t\tString dexName = resource.getAndroidClassBundles().entrySet().stream()\n\t\t\t\t\t\t.filter(e -> e.getValue() == bundle)\n\t\t\t\t\t\t.map(Map.Entry::getKey)\n\t\t\t\t\t\t.findFirst()\n\t\t\t\t\t\t.orElse(null);\n\t\t\t\tif (dexName != null)\n\t\t\t\t\treturn dexName;\n\t\t\t} else if (bundle instanceof AgentServerRemoteVmResource.RemoteJvmClassBundle remoteBundle) {\n\t\t\t\treturn formatConfig.filter(remoteBundle.getLoaderInfo().getName());\n\t\t\t} else if (bundle instanceof VersionedJvmClassBundle versionedClassBundle) {\n\t\t\t\treturn Lang.get(\"tree.classes\") + \" (Java \" + versionedClassBundle.version() + \")\";\n\t\t\t}\n\n\t\t\tif (bundle instanceof ClassBundle)\n\t\t\t\treturn Lang.get(\"tree.classes\");\n\t\t\telse\n\t\t\t\treturn Lang.get(\"tree.files\");\n\t\t};\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tThe resource to create text for.\n\t *\n\t * @return Text provider for the resource.\n\t */\n\t@Nonnull\n\tpublic TextProvider getResourceTextProvider(@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource) {\n\t\treturn () -> switch (resource) {\n\t\t\tcase WorkspaceFileResource fileResource -> {\n\t\t\t\tString name = fileResource.getFileInfo().getName();\n\t\t\t\tyield name.substring(name.lastIndexOf('/') + 1);\n\t\t\t}\n\t\t\tcase WorkspaceDirectoryResource directoryResource -> StringUtil.pathToNameString(directoryResource.getDirectoryPath());\n\t\t\tcase WorkspaceRemoteVmResource remoteVmResource -> remoteVmResource.getVirtualMachine().id();\n\t\t\tcase GeneratedPhantomWorkspaceResource generatedPhantomWorkspaceResource -> Lang.get(\"tree.phantoms\");\n\t\t\tdefault -> resource.getClass().getSimpleName();\n\t\t};\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic TextProviderServiceConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/cell/text/TextProviderServiceConfig.java",
    "content": "package software.coley.recaf.services.cell.text;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link TextProviderService}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class TextProviderServiceConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic TextProviderServiceConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, TextProviderService.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigComponentFactory.java",
    "content": "package software.coley.recaf.services.config;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.Node;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.ui.pane.ConfigPane;\n\n/**\n * Instances of these factories determine how {@link ConfigValue} instances are represented in the {@link ConfigPane}.\n * These factories should be registered in {@link ConfigComponentManager}.\n *\n * @param <T>\n * \t\tValue type of {@link ConfigValue} to create a component for.\n *\n * @author Matt Coley\n * @see ConfigPane UI where these are used.\n * @see ConfigComponentManager Manager for registering these factories.\n * @see KeyedConfigComponentFactory Factory impl for targeting a specific value by its {@link ConfigValue#getId()}.\n * @see TypedConfigComponentFactory Factory impl for targeting any value by its {@link ConfigValue#getType()}.\n */\npublic abstract class ConfigComponentFactory<T> {\n\tprivate final boolean standAlone;\n\n\t/**\n\t * @param standAlone\n\t * \t\tSee {@link #isStandAlone()}. Determines if label is automatically added.\n\t */\n\tprotected ConfigComponentFactory(boolean standAlone) {\n\t\tthis.standAlone = standAlone;\n\t}\n\n\t/**\n\t * @return {@code true} when the created component should span the full page in {@link ConfigPane}.\n\t * {@code false} will place a label next to the created component based on the {@link ConfigValue#getId()}.\n\t */\n\tpublic boolean isStandAlone() {\n\t\treturn standAlone;\n\t}\n\n\t/**\n\t * @param container\n\t * \t\tContainer of the value.\n\t * @param value\n\t * \t\tValue wrapper.\n\t *\n\t * @return Control to represent the value.\n\t */\n\t@Nonnull\n\tpublic abstract Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue<T> value);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigComponentManager.java",
    "content": "package software.coley.recaf.services.config;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.ui.pane.ConfigPane;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.BiFunction;\n\n/**\n * Manages which {@link ConfigComponentFactory} instances to use to create editor components in the {@link ConfigPane}\n * for {@link ConfigValue} instances.\n *\n * @author Matt Coley\n * @see ConfigPane UI where this manager is used.\n * @see ConfigComponentFactory Factory base.\n */\n@ApplicationScoped\npublic class ConfigComponentManager implements Service {\n\tpublic static final String ID = \"config-components\";\n\tprivate final ConfigComponentFactory<Object> DEFAULT_FACTORY = new ConfigComponentFactory<>(false) {\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue<Object> value) {\n\t\t\tLabel label = new Label(\"Unsupported: \" + value.getType().getName());\n\t\t\tlabel.getStyleClass().add(Styles.WARNING);\n\t\t\treturn label;\n\t\t}\n\t};\n\tprivate final Map<String, ConfigComponentFactory<?>> keyToConfigurator = new HashMap<>();\n\tprivate final Map<Class<?>, ConfigComponentFactory<?>> typeToConfigurator = new HashMap<>();\n\tprivate final ConfigComponentManagerConfig config;\n\n\t@Inject\n\tpublic ConfigComponentManager(@Nonnull ConfigComponentManagerConfig config,\n\t                              @Nonnull Instance<KeyedConfigComponentFactory<?>> keyedFactories,\n\t                              @Nonnull Instance<TypedConfigComponentFactory<?>> typedFactories) {\n\t\tthis.config = config;\n\n\t\t// Register implementations\n\t\tfor (KeyedConfigComponentFactory<?> factory : keyedFactories)\n\t\t\tregister(factory.getId(), factory);\n\t\tfor (TypedConfigComponentFactory<?> factory : typedFactories)\n\t\t\tregister(factory.getType(), factory);\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tThe {@link ConfigContainer#getGroupAndId()} + {@link ConfigValue#getId()}, used to create factories to generate components for a specific value.\n\t * @param factory\n\t * \t\tFactory to generate components to support the given type.\n\t *\n\t * @see #register(ConfigContainer, String, boolean, BiFunction) Alternative for generating a {@link KeyedConfigComponentFactory}.\n\t */\n\tpublic void register(@Nonnull String id, @Nonnull KeyedConfigComponentFactory<?> factory) {\n\t\tkeyToConfigurator.put(id, factory);\n\t}\n\n\t/**\n\t * @param registeringContainer\n\t * \t\tThe container holding the config value.\n\t * @param valueId\n\t * \t\tThe {@link ConfigValue#getId() config value id} to register for.\n\t * @param standAlone\n\t * \t\tSee {@link ConfigComponentFactory#isStandAlone()}.\n\t * @param factory\n\t * \t\tFunction for the implementation of {@link KeyedConfigComponentFactory}.\n\t * @param <T>\n\t * \t\tConfig value type.\n\t */\n\tpublic <T> void register(@Nonnull ConfigContainer registeringContainer, @Nonnull String valueId, boolean standAlone,\n\t                         @Nonnull BiFunction<ConfigContainer, ConfigValue<T>, Node> factory) {\n\t\tString id = registeringContainer.getGroupAndId() + ConfigGroups.PACKAGE_SPLIT + valueId;\n\t\tkeyToConfigurator.put(id, new KeyedConfigComponentFactory<T>(standAlone, id) {\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue<T> value) {\n\t\t\t\treturn factory.apply(container, value);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * @param type\n\t * \t\tClass type.\n\t * @param factory\n\t * \t\tFactory to generate components to support the given type.\n\t */\n\tpublic void register(@Nonnull Class<?> type, @Nonnull TypedConfigComponentFactory<?> factory) {\n\t\ttypeToConfigurator.put(type, factory);\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to get factory for.\n\t * @param <T>\n\t * \t\tValue type.\n\t *\n\t * @return Component factory for value.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic <T> ConfigComponentFactory<T> getFactory(@Nonnull ConfigContainer container, @Nonnull ConfigValue<T> value) {\n\t\t// Get factory for config value ID.\n\t\tString id = container.getGroupAndId() + ConfigGroups.PACKAGE_SPLIT + value.getId();\n\t\tConfigComponentFactory<?> factory = keyToConfigurator.get(id);\n\t\tif (factory != null)\n\t\t\treturn (ConfigComponentFactory<T>) factory;\n\n\t\t// Get factory for config value type.\n\t\tClass<T> type = value.getType();\n\t\tfactory = typeToConfigurator.get(type);\n\t\tif (factory != null)\n\t\t\treturn (ConfigComponentFactory<T>) factory;\n\n\t\t// Check for common generic types.\n\t\tif (type.isEnum())\n\t\t\tfactory = typeToConfigurator.get(Enum.class);\n\t\tif (factory != null)\n\t\t\treturn (ConfigComponentFactory<T>) factory;\n\n\t\t// Fallback factory.\n\t\treturn (ConfigComponentFactory<T>) DEFAULT_FACTORY;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ConfigComponentManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigComponentManagerConfig.java",
    "content": "package software.coley.recaf.services.config;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link ConfigComponentManager}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ConfigComponentManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic ConfigComponentManagerConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, ConfigComponentManager.ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigIconManager.java",
    "content": "package software.coley.recaf.services.config;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.kordamp.ikonli.Ikon;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.services.Service;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static software.coley.recaf.config.ConfigGroups.*;\n\n/**\n * Manages icons to display for {@link ConfigContainer} and {@link ConfigValue} entries when displayed in the UI.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ConfigIconManager implements Service {\n\tpublic static final String ID = \"config-icons\";\n\tprivate final Map<String, Ikon> valueIcons = new HashMap<>();\n\tprivate final Map<String, Ikon> containerIcons = new HashMap<>();\n\tprivate final Map<String, Ikon> groupIcons = new HashMap<>();\n\tprivate final ConfigIconManagerConfig config;\n\n\t@Inject\n\tpublic ConfigIconManager(@Nonnull ConfigIconManagerConfig config) {\n\t\tthis.config = config;\n\n\t\t// Add defaults\n\t\tregisterGroup(SERVICE, CarbonIcons.DATA_CLASS);\n\t\tregisterGroup(SERVICE_ANALYSIS, CarbonIcons.COGNITIVE);\n\t\tregisterGroup(SERVICE_ASSEMBLER, CarbonIcons.CODE);\n\t\tregisterGroup(SERVICE_COMPILE, CarbonIcons.CODE);\n\t\tregisterGroup(SERVICE_DEBUG, CarbonIcons.DEBUG);\n\t\tregisterGroup(SERVICE_DECOMPILE, CarbonIcons.CODE);\n\t\tregisterGroup(SERVICE_IO, CarbonIcons.ARROWS_HORIZONTAL);\n\t\tregisterGroup(SERVICE_MAPPING, CarbonIcons.MAP_BOUNDARY);\n\t\tregisterGroup(SERVICE_PLUGIN, CarbonIcons.PLUG);\n\t\tregisterGroup(SERVICE_UI, CarbonIcons.DICOM_OVERLAY);\n\t}\n\n\t/**\n\t * @param id\n\t *        {@link ConfigValue#getId()}.\n\t * @param icon\n\t * \t\tIcon to set.\n\t */\n\tpublic void registerValue(@Nonnull String id, @Nonnull Ikon icon) {\n\t\tvalueIcons.putIfAbsent(id, icon);\n\t}\n\n\t/**\n\t * @param id\n\t *        {@link ConfigContainer#getId()}.\n\t * @param icon\n\t * \t\tIcon to set.\n\t */\n\tpublic void registerContainer(@Nonnull String id, @Nonnull Ikon icon) {\n\t\tcontainerIcons.putIfAbsent(id, icon);\n\t}\n\n\t/**\n\t * @param group\n\t *        {@link ConfigContainer#getGroup()}.\n\t * @param icon\n\t * \t\tIcon to set.\n\t */\n\tpublic void registerGroup(@Nonnull String group, @Nonnull Ikon icon) {\n\t\tgroupIcons.putIfAbsent(group, icon);\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tValue to get icon of.\n\t *\n\t * @return Associated icon. May be {@code null} for no association.\n\t */\n\t@Nullable\n\tpublic Ikon getValueIcon(@Nonnull ConfigValue<?> value) {\n\t\treturn getValueIcon(value.getId());\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tValue of a {@link ConfigValue#getId()}.\n\t *\n\t * @return Associated icon. May be {@code null} for no association.\n\t */\n\t@Nullable\n\tpublic Ikon getValueIcon(@Nonnull String id) {\n\t\treturn valueIcons.get(id);\n\t}\n\n\t/**\n\t * @param container\n\t * \t\tContainer to get icon of for it's {@link ConfigContainer#getId()}.\n\t *\n\t * @return Associated icon. May be {@code null} for no association.\n\t */\n\t@Nullable\n\tpublic Ikon getContainerIcon(@Nonnull ConfigContainer container) {\n\t\treturn getContainerIcon(container.getId());\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tValue of a {@link ConfigContainer#getId()}.\n\t *\n\t * @return Associated icon. May be {@code null} for no association.\n\t */\n\t@Nullable\n\tpublic Ikon getContainerIcon(@Nonnull String id) {\n\t\treturn containerIcons.get(id);\n\t}\n\n\t/**\n\t * @param container\n\t * \t\tContainer to get icon of for it's {@link ConfigContainer#getGroup()}.\n\t *\n\t * @return Associated icon. May be {@code null} for no association.\n\t */\n\t@Nullable\n\tpublic Ikon getGroupIcon(@Nonnull ConfigContainer container) {\n\t\treturn getGroupIcon(container.getGroup());\n\t}\n\n\t/**\n\t * @param group\n\t * \t\tValue of a {@link ConfigContainer#getGroup()}.\n\t *\n\t * @return Associated icon. May be {@code null} for no association.\n\t */\n\t@Nullable\n\tpublic Ikon getGroupIcon(@Nonnull String group) {\n\t\treturn groupIcons.get(group);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ConfigIconManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigIconManagerConfig.java",
    "content": "package software.coley.recaf.services.config;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link ConfigIconManager}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ConfigIconManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic ConfigIconManagerConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, ConfigIconManager.ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/KeyedConfigComponentFactory.java",
    "content": "package software.coley.recaf.services.config;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.config.ConfigValue;\n\n/**\n * Factory for a specific {@link ConfigValue} by its {@link ConfigValue#getId()}.\n *\n * @param <T>\n * \t\tValue type of {@link ConfigValue} to create a component for.\n *\n * @author Matt Coley\n */\npublic abstract class KeyedConfigComponentFactory<T> extends ConfigComponentFactory<T> {\n\tprivate final String id;\n\n\t/**\n\t * @param standAlone\n\t * \t\tSee {@link #isStandAlone()}. Determines if label is automatically added.\n\t * @param id\n\t * \t\tValue of a {@link ConfigValue#getId()} to associate with.\n\t */\n\tprotected KeyedConfigComponentFactory(boolean standAlone, @Nonnull String id) {\n\t\tsuper(standAlone);\n\t\tthis.id = id;\n\t}\n\n\t/**\n\t * @return The {@link ConfigValue#getId()} this factory is for.\n\t */\n\tpublic String getId() {\n\t\treturn id;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/TypedConfigComponentFactory.java",
    "content": "package software.coley.recaf.services.config;\n\nimport software.coley.recaf.config.ConfigValue;\n\n/**\n * Factory for any {@link ConfigValue} of a specific {@link ConfigValue#getType()}.\n *\n * @param <T>\n * \t\tValue type of {@link ConfigValue} to create a component for.\n *\n * @author Matt Coley\n */\npublic abstract class TypedConfigComponentFactory<T> extends ConfigComponentFactory<T> {\n\tprivate final Class<T> type;\n\n\t/**\n\t * @param isStandAlone\n\t * \t\tSee {@link #isStandAlone()}. Creates an adjacent label for the config value name if {@code true}.\n\t * @param type\n\t * \t\tThe {@link ConfigValue#getType()} to support.\n\t */\n\tprotected TypedConfigComponentFactory(boolean isStandAlone, Class<T> type) {\n\t\tsuper(isStandAlone);\n\t\tthis.type = type;\n\t}\n\n\t/**\n\t * @return The {@link ConfigValue#getType()} this factory is for.\n\t */\n\tpublic Class<T> getType() {\n\t\treturn type;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/factories/AndroidDecompilerComponentFactory.java",
    "content": "package software.coley.recaf.services.config.factories;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.Node;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.services.decompile.Decompiler;\nimport software.coley.recaf.services.decompile.DecompilerManager;\nimport software.coley.recaf.services.decompile.DecompilerManagerConfig;\nimport software.coley.recaf.services.config.KeyedConfigComponentFactory;\nimport software.coley.recaf.ui.control.ObservableComboBox;\n\nimport java.util.List;\n\n/**\n * Factory for general {@link DecompilerManagerConfig#getPreferredAndroidDecompiler()}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class AndroidDecompilerComponentFactory extends KeyedConfigComponentFactory<String> {\n\tprivate final List<String> decompilers;\n\n\t@Inject\n\tpublic AndroidDecompilerComponentFactory(DecompilerManager decompilerManager) {\n\t\tsuper(false, id(decompilerManager.getServiceConfig()));\n\t\tdecompilers = decompilerManager.getAndroidDecompilers().stream()\n\t\t\t\t.map(Decompiler::getName)\n\t\t\t\t.toList();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue<String> value) {\n\t\treturn new ObservableComboBox<>(value.getObservable(), decompilers);\n\t}\n\n\t@Nonnull\n\tprivate static String id(@Nonnull ConfigContainer container) {\n\t\treturn container.getGroupAndId() + ConfigGroups.PACKAGE_SPLIT + DecompilerManagerConfig.KEY_PREF_ANDROID_DECOMPILER;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/factories/BooleanComponentFactory.java",
    "content": "package software.coley.recaf.services.config.factories;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.Node;\nimport javafx.scene.control.CheckBox;\nimport software.coley.observables.Observable;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.services.config.TypedConfigComponentFactory;\nimport software.coley.recaf.util.Lang;\n\n/**\n * Factory for general {@code boolean} values.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class BooleanComponentFactory extends TypedConfigComponentFactory<Boolean> {\n\t@Inject\n\tpublic BooleanComponentFactory() {\n\t\tsuper(true, boolean.class);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue<Boolean> value) {\n\t\tObservable<Boolean> observable = value.getObservable();\n\t\tString translationKey = container.getScopedId(value);\n\n\t\t// Create the component.\n\t\tCheckBox check = new CheckBox();\n\t\tcheck.setSelected(observable.getValue());\n\t\tcheck.textProperty().bind(Lang.getBinding(translationKey));\n\t\tobservable.addChangeListener((ob, old, cur) -> {\n\t\t\tif (check.isSelected() != cur)\n\t\t\t\tcheck.setSelected(cur);\n\t\t});\n\t\tcheck.selectedProperty().addListener((ob, old, cur) -> {\n\t\t\tif (observable.getValue() != cur)\n\t\t\t\tobservable.setValue(cur);\n\t\t});\n\t\treturn check;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/factories/EnumComponentFactory.java",
    "content": "package software.coley.recaf.services.config.factories;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.Node;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.services.config.TypedConfigComponentFactory;\nimport software.coley.recaf.ui.control.ObservableComboBox;\n\nimport java.util.Arrays;\n\n/**\n * Factory for general {@link Enum} values.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\n@SuppressWarnings(\"rawtypes\")\npublic class EnumComponentFactory extends TypedConfigComponentFactory<Enum> {\n\t@Inject\n\tpublic EnumComponentFactory() {\n\t\tsuper(false, Enum.class);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue<Enum> value) {\n\t\tEnum[] enumConstants = value.getType().getEnumConstants();\n\t\treturn new ObservableComboBox<>(value.getObservable(), Arrays.asList(enumConstants));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/factories/IntegerComponentFactory.java",
    "content": "package software.coley.recaf.services.config.factories;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.Node;\nimport javafx.scene.control.TextField;\nimport javafx.scene.effect.*;\nimport javafx.scene.paint.Color;\nimport software.coley.observables.Observable;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.services.config.TypedConfigComponentFactory;\nimport software.coley.recaf.util.Effects;\n\n/**\n * Factory for general {@code int} values.\n *\n * @author pvpb0t\n */\n@ApplicationScoped\npublic class IntegerComponentFactory extends TypedConfigComponentFactory<Integer> {\n\n    @Inject\n    protected IntegerComponentFactory() {\n        super(false, int.class);\n    }\n\n    @Nonnull\n    @Override\n    public Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue<Integer> value) {\n        Observable<Integer> observable = value.getObservable();\n        TextField textField = new TextField();\n        textField.setText(Integer.toString(observable.getValue()));\n        textField.textProperty().addListener((observableValue, oldValue, newValue) -> {\n            try {\n                int newValueAsInt = Integer.parseInt(newValue);\n                observable.setValue(newValueAsInt);\n                textField.setEffect(null);\n            } catch (NumberFormatException e) {\n                textField.setEffect(Effects.ERROR_BORDER);\n            }\n        });\n        textField.focusedProperty().addListener((ob, old, isFocused) -> {\n            if (!isFocused && textField.getEffect() != null) {\n                textField.setText(Integer.toString(observable.getValue()));\n                textField.setEffect(null);\n            }\n        });\n\n        return textField;\n    }\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/factories/JvmDecompilerComponentFactory.java",
    "content": "package software.coley.recaf.services.config.factories;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.Node;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.services.config.KeyedConfigComponentFactory;\nimport software.coley.recaf.services.decompile.Decompiler;\nimport software.coley.recaf.services.decompile.DecompilerManager;\nimport software.coley.recaf.services.decompile.DecompilerManagerConfig;\nimport software.coley.recaf.ui.control.ObservableComboBox;\n\nimport java.util.List;\n\n/**\n * Factory for general {@link DecompilerManagerConfig#getPreferredJvmDecompiler()}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class JvmDecompilerComponentFactory extends KeyedConfigComponentFactory<String> {\n\tprivate final List<String> decompilers;\n\n\t@Inject\n\tpublic JvmDecompilerComponentFactory(@Nonnull DecompilerManager decompilerManager) {\n\t\tsuper(false, id(decompilerManager.getServiceConfig()));\n\t\tdecompilers = decompilerManager.getJvmDecompilers().stream()\n\t\t\t\t.map(Decompiler::getName)\n\t\t\t\t.toList();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue<String> value) {\n\t\treturn new ObservableComboBox<>(value.getObservable(), decompilers);\n\t}\n\n\t@Nonnull\n\tprivate static String id(@Nonnull ConfigContainer container) {\n\t\treturn container.getGroupAndId() + ConfigGroups.PACKAGE_SPLIT + DecompilerManagerConfig.KEY_PREF_JVM_DECOMPILER;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/factories/ProcyonLanguageComponentFactory.java",
    "content": "package software.coley.recaf.services.config.factories;\n\nimport com.strobel.decompiler.languages.Language;\nimport com.strobel.decompiler.languages.Languages;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.Node;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.ListCell;\nimport software.coley.observables.Observable;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.services.config.TypedConfigComponentFactory;\nimport software.coley.recaf.util.ToStringConverter;\n\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * Factory for general {@link Language} values.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ProcyonLanguageComponentFactory extends TypedConfigComponentFactory<Language> {\n\t@Inject\n\tprotected ProcyonLanguageComponentFactory() {\n\t\tsuper(false, Language.class);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue<Language> value) {\n\t\tMap<String, Language> languageMap = Languages.all().stream()\n\t\t\t\t.collect(Collectors.toMap(Language::getName, Function.identity()));\n\t\tObservable<Language> observable = value.getObservable();\n\t\tComboBox<Language> comboBox = new ComboBox<>();\n\t\tcomboBox.setConverter(new ToStringConverter<>() {\n\t\t\t@Override\n\t\t\tpublic String toString(Language language) {\n\t\t\t\treturn language.getName();\n\t\t\t}\n\t\t});\n\t\tcomboBox.setCellFactory(c -> new ListCell<>() {\n\t\t\t@Override\n\t\t\tprotected void updateItem(Language item, boolean empty) {\n\t\t\t\tsuper.updateItem(item, empty);\n\t\t\t\tif (empty || item == null) {\n\t\t\t\t\tsetText(null);\n\t\t\t\t} else {\n\t\t\t\t\tsetText(item.getName());\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tcomboBox.getItems().addAll(Languages.all());\n\t\tcomboBox.setValue(observable.getValue());\n\t\tcomboBox.valueProperty().addListener((observableValue, oldValue, newValue) -> {\n\t\t\tobservable.setValue(newValue);\n\t\t});\n\t\treturn comboBox;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/config/factories/StringComponentFactory.java",
    "content": "package software.coley.recaf.services.config.factories;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.Node;\nimport javafx.scene.control.TextField;\nimport software.coley.observables.Observable;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.services.config.TypedConfigComponentFactory;\n/**\n * Factory for general {@link String} values.\n *\n * @author pvpb0t\n */\n@ApplicationScoped\npublic class StringComponentFactory extends TypedConfigComponentFactory<String> {\n\n    @Inject\n    protected StringComponentFactory() {\n        super(false, String.class);\n    }\n\n    @Nonnull\n    @Override\n    public Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue<String> value) {\n        Observable<String> observable = value.getObservable();\n        TextField textField = new TextField();\n        textField.setText(observable.getValue());\n        textField.textProperty().addListener((observableValue, oldValue, newValue) -> {\n            observable.setValue(newValue);\n        });\n        return textField;\n    }\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/info/association/FileTypeSyntaxAssociationService.java",
    "content": "package software.coley.recaf.services.info.association;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.collections.ObservableList;\nimport software.coley.recaf.info.BinaryXmlFileInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.ui.LanguageStylesheets;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.syntax.RegexLanguages;\nimport software.coley.recaf.ui.control.richtext.syntax.RegexRule;\nimport software.coley.recaf.ui.control.richtext.syntax.RegexSyntaxHighlighter;\n\nimport static software.coley.recaf.util.StringUtil.*;\n\n/**\n * Provides mapping of {@link FileInfo#getName() file extensions} to {@link RegexRule language patterns}\n * for use by {@link RegexSyntaxHighlighter}.\n * <p>\n * Users can change which highlighter is used for different file extensions by updating the\n * {@link FileTypeSyntaxAssociationServiceConfig#getExtensionsToLangKeys() extensions map config}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class FileTypeSyntaxAssociationService implements Service {\n\tpublic static final String SERVICE_ID = \"file-type-syntax-association\";\n\tprivate final FileTypeSyntaxAssociationServiceConfig config;\n\n\t@Inject\n\tpublic FileTypeSyntaxAssociationService(@Nonnull FileTypeSyntaxAssociationServiceConfig config) {\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Updates the syntax highlighter and stylesheet of the given editor to match the file contents of the given info object.\n\t * <br>\n\t * {@link JvmClassInfo} will use {@code java} syntax highlighting. {@link FileInfo} will use the file extension in their names\n\t * to determine which syntax to use.\n\t *\n\t * @param info\n\t * \t\tInfo to target.\n\t * @param editor\n\t * \t\tEditor to update.\n\t */\n\tpublic void configureEditorSyntax(@Nonnull Info info, @Nonnull Editor editor) {\n\t\tString fileExtension;\n\t\tif (info.isClass()) fileExtension = \"java\";\n\t\telse if (info instanceof BinaryXmlFileInfo) fileExtension = \"xml\";\n\t\telse fileExtension = getAfter(info.getName(), \".\");\n\t\tconfigureEditorSyntax(fileExtension, editor);\n\t}\n\n\t/**\n\t * Updates the syntax highlighter and stylesheet of the given editor to match the file contents of the given file type.\n\t *\n\t * @param fileExtension\n\t * \t\tFile extension to target.\n\t * @param editor\n\t * \t\tEditor to update.\n\t */\n\tpublic void configureEditorSyntax(@Nonnull String fileExtension, @Nonnull Editor editor) {\n\t\tString lowerExtension = fileExtension.toLowerCase();\n\t\tString languageKey = config.getExtensionsToLangKeys().getOrDefault(lowerExtension, lowerExtension);\n\n\t\tString sheet = LanguageStylesheets.getLanguageStylesheet(languageKey);\n\t\tRegexRule language = RegexLanguages.getLanguage(languageKey);\n\n\t\tObservableList<String> stylesheets = editor.getStylesheets();\n\t\tif (sheet != null && !stylesheets.contains(sheet))\n\t\t\tstylesheets.add(sheet);\n\t\tif (language != null)\n\t\t\teditor.setSyntaxHighlighter(new RegexSyntaxHighlighter(language));\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic FileTypeSyntaxAssociationServiceConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/info/association/FileTypeSyntaxAssociationServiceConfig.java",
    "content": "package software.coley.recaf.services.info.association;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.collections.tuple.Pair;\nimport software.coley.observables.ObservableMap;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicMapConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\nimport software.coley.recaf.ui.LanguageStylesheets;\nimport software.coley.recaf.ui.control.richtext.syntax.RegexLanguages;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * Config for {@link FileTypeSyntaxAssociationService}.\n * <p>\n * Retains mapping of file extensions to language keys used by:\n * <ul>\n *     <li>{@link RegexLanguages#getLanguage(String)}</li>\n *     <li>{@link LanguageStylesheets#getLanguageStylesheet(String)}</li>\n * </ul>\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class FileTypeSyntaxAssociationServiceConfig extends BasicConfigContainer implements ServiceConfig {\n\tprivate final ExtensionMapping extensionsToLangKeys;\n\n\t@Inject\n\tpublic FileTypeSyntaxAssociationServiceConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, FileTypeSyntaxAssociationService.SERVICE_ID + CONFIG_SUFFIX);\n\n\t\textensionsToLangKeys = new ExtensionMapping(List.of(\n\t\t\t\tnew Pair<>(\"java\", \"java\"),\n\t\t\t\tnew Pair<>(\"jasm\", \"jasm\"),\n\t\t\t\tnew Pair<>(\"xml\", \"xml\"),\n\t\t\t\tnew Pair<>(\"html\", \"xml\"),\n\t\t\t\tnew Pair<>(\"svg\", \"xml\"),\n\t\t\t\tnew Pair<>(\"enigma\", \"enigma\")\n\t\t));\n\t\taddValue(new BasicMapConfigValue<>(\"extensions-to-langs\", Map.class, String.class, String.class, extensionsToLangKeys));\n\t}\n\n\t/**\n\t * @return Map of file extensions to {@link RegexLanguages} name keys.\n\t *\n\t * @see RegexLanguages#getLanguage(String)\n\t */\n\t@Nonnull\n\tpublic ExtensionMapping getExtensionsToLangKeys() {\n\t\treturn extensionsToLangKeys;\n\t}\n\n\t/**\n\t * Maps file extensions to {@link RegexLanguages} entries.\n\t */\n\tpublic static class ExtensionMapping extends ObservableMap<String, String, Map<String, String>> {\n\t\tpublic ExtensionMapping(@Nonnull List<Pair<String, String>> extensions) {\n\t\t\tthis(extensions.stream().collect(Collectors.toMap(Pair::getLeft, Pair::getRight)));\n\t\t}\n\n\t\tpublic ExtensionMapping(@Nonnull Map<String, String> extensions) {\n\t\t\tsuper(extensions, HashMap::new);\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/info/summary/ResourceSummarizer.java",
    "content": "package software.coley.recaf.services.info.summary;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Provides summary information about a {@link WorkspaceResource} to the user.\n *\n * @author Matt Coley\n */\npublic interface ResourceSummarizer extends Comparable<ResourceSummarizer> {\n\t/**\n\t * Computes summary information about the given workspace.\n\t * <p>\n\t * When implementations of this method need to display output\n\t * all UI creation should be wrapped in {@link FxThreadUtil#run(Runnable)}.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tResource to summarize.\n\t * @param consumer\n\t * \t\tConsumer of summary data.\n\t *\n\t * @return {@code true} when data was summarized.\n\t * {@code false} when summarization was skipped.\n\t */\n\tboolean summarize(@Nonnull Workspace workspace,\n\t                  @Nonnull WorkspaceResource resource,\n\t                  @Nonnull SummaryConsumer consumer);\n\n\t/**\n\t * @return Summarizer identity.\n\t */\n\t@Nonnull\n\tdefault String id() {\n\t\treturn getClass().getSimpleName();\n\t}\n\n\t@Override\n\tdefault int compareTo(ResourceSummarizer o) {\n\t\treturn id().compareTo(o.id());\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/info/summary/ResourceSummaryService.java",
    "content": "package software.coley.recaf.services.info.summary;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.Separator;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.ui.pane.WorkspaceInformationPane;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.Map;\nimport java.util.TreeSet;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutorService;\n\n/**\n * Provides {@link ResourceSummarizer} content to the {@link WorkspaceInformationPane}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ResourceSummaryService implements Service {\n\tpublic static final String SERVICE_ID = \"info-summary\";\n\tprivate static final Logger logger = Logging.get(ResourceSummaryService.class);\n\tprivate static final ExecutorService threadPool = ThreadPoolFactory.newSingleThreadExecutor(\"resource-summary\");\n\tprivate final Map<String, ResourceSummarizer> summarizers = new ConcurrentHashMap<>();\n\tprivate final ResourceSummaryServiceConfig config;\n\n\t@Inject\n\tpublic ResourceSummaryService(@Nonnull ResourceSummaryServiceConfig config,\n\t                              @Nonnull Instance<ResourceSummarizer> summarizers) {\n\t\tthis.config = config;\n\n\t\t// TODO: Summarizer for android\n\t\t//  - Manifest\n\t\t//    - Entry points (can update existing entry-point summarizer)\n\t\t//    - Permissions (and their descriptions, level of concern perhaps?)\n\n\t\t// Add discovered summarizers from classpath.\n\t\tfor (ResourceSummarizer summarizer : summarizers)\n\t\t\taddSummarizer(summarizer);\n\t}\n\n\t/**\n\t * Run all {@link ResourceSummarizer} instances, appending to the given consumer.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tResource to summarize.\n\t * @param consumer\n\t * \t\tConsumer of summary data.\n\t *\n\t * @return Future of summarization completion.\n\t */\n\t@Nonnull\n\tpublic CompletableFuture<Void> summarizeTo(@Nonnull Workspace workspace,\n\t                                           @Nonnull WorkspaceResource resource,\n\t                                           @Nonnull SummaryConsumer consumer) {\n\t\t// Run async so we do not block the UI thread\n\t\treturn CompletableFuture.runAsync(() -> {\n\t\t\tboolean lastSummarizerAppended = false;\n\t\t\tfor (ResourceSummarizer summarizer : new TreeSet<>(summarizers.values())) {\n\t\t\t\tif (lastSummarizerAppended)\n\t\t\t\t\tFxThreadUtil.run(() -> consumer.appendSummary(new Separator()));\n\t\t\t\ttry {\n\t\t\t\t\tlastSummarizerAppended = summarizer.summarize(workspace, resource, consumer);\n\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\tlogger.error(\"Summarizer '{}' encountered an error\", summarizer.getClass().getName(), t);\n\t\t\t\t\tlastSummarizerAppended = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}, threadPool);\n\t}\n\n\t/**\n\t * @param summarizer\n\t * \t\tSummarizer to add.\n\t */\n\tpublic void addSummarizer(@Nonnull ResourceSummarizer summarizer) {\n\t\tsummarizers.put(summarizer.id(), summarizer);\n\t}\n\n\t/**\n\t * @param summarizer\n\t * \t\tSummarizer to remove.\n\t *\n\t * @return {@code true} on removal. {@code false} when it did not exist prior.\n\t */\n\tpublic boolean removeSummarizer(@Nonnull ResourceSummarizer summarizer) {\n\t\treturn summarizers.remove(summarizer.id()) != null;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ResourceSummaryServiceConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/info/summary/ResourceSummaryServiceConfig.java",
    "content": "package software.coley.recaf.services.info.summary;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link ResourceSummaryService}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ResourceSummaryServiceConfig extends BasicConfigContainer implements ServiceConfig {\n\tprivate final ObservableBoolean summarizeOnOpen = new ObservableBoolean(true);\n\n\t@Inject\n\tpublic ResourceSummaryServiceConfig() {\n\t\tsuper(ConfigGroups.SERVICE_ANALYSIS, ResourceSummaryService.SERVICE_ID + CONFIG_SUFFIX);\n\t\t// Add values\n\t\taddValue(new BasicConfigValue<>(\"summarize-on-open\", boolean.class, summarizeOnOpen));\n\t}\n\n\t/**\n\t * @return {@code true} when workspace contents should be summarized on opening a new workspace.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getSummarizeOnOpen() {\n\t\treturn summarizeOnOpen;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/info/summary/SummaryConsumer.java",
    "content": "package software.coley.recaf.services.info.summary;\n\nimport javafx.scene.Node;\n\n/**\n * Interface for consuming generated summaries.\n *\n * @author Matt Coley\n */\npublic interface SummaryConsumer {\n\t/**\n\t * Appends a single line.\n\t *\n\t * @param node\n\t * \t\tSingle line content.\n\t */\n\tvoid appendSummary(Node node);\n\n\t/**\n\t * Appends a single line, constructed from two parts.\n\t *\n\t * @param left\n\t * \t\tLeft portion of content.\n\t * @param right\n\t * \t\tRight portion of content.\n\t */\n\tvoid appendSummary(Node left, Node right);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/info/summary/builtin/AntiDecompilationSummarizer.java",
    "content": "package software.coley.recaf.services.info.summary.builtin;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.HBox;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.deobfuscation.transform.generic.CycleClassRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.DuplicateAnnotationRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.IllegalAnnotationRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.IllegalSignatureRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.LongAnnotationRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.LongExceptionRemovingTransformer;\nimport software.coley.recaf.services.info.summary.ResourceSummarizer;\nimport software.coley.recaf.services.info.summary.SummaryConsumer;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeKeywordNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeNonAsciiNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeNonJavaIdentifierNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeWhitespaceNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.NameGeneratorFilter;\nimport software.coley.recaf.services.transform.JvmTransformResult;\nimport software.coley.recaf.services.transform.TransformationApplierService;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.services.window.WindowFactory;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.pane.MappingGeneratorPane;\nimport software.coley.recaf.ui.window.MappingGeneratorWindow;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\n\n/**\n * Summarizer that allows patching of common anti-decompilation tricks.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class AntiDecompilationSummarizer implements ResourceSummarizer {\n\tprivate static final int BUTTON_WIDTH = 210;\n\tprivate static final NameGeneratorFilter ILLEGAL_NAME_FILTER =\n\t\t\tnew IncludeWhitespaceNameFilter(new IncludeNonAsciiNameFilter(new IncludeKeywordNameFilter(new IncludeNonJavaIdentifierNameFilter(null))));\n\tprivate static final Logger logger = Logging.get(AntiDecompilationSummarizer.class);\n\tprivate final TransformationApplierService transformationApplierService;\n\tprivate final Instance<MappingGeneratorWindow> generatorWindowProvider;\n\tprivate final WindowFactory windowFactory;\n\n\t@Inject\n\tpublic AntiDecompilationSummarizer(@Nonnull TransformationApplierService transformationApplierService,\n\t                                   @Nonnull Instance<MappingGeneratorWindow> generatorWindowProvider,\n\t                                   @Nonnull WindowFactory windowFactory) {\n\t\tthis.generatorWindowProvider = generatorWindowProvider;\n\t\tthis.transformationApplierService = transformationApplierService;\n\t\tthis.windowFactory = windowFactory;\n\t}\n\n\t@Override\n\tpublic boolean summarize(@Nonnull Workspace workspace,\n\t                         @Nonnull WorkspaceResource resource,\n\t                         @Nonnull SummaryConsumer consumer) {\n\t\t// Transform workspace with a number of strictly anti-crasher transformers.\n\t\t// Let the user determine if they want to apply the results.\n\t\tJvmTransformResult transformResult = computeTransformations(workspace);\n\n\t\t// Detect if we should show the prompt to show the pre-populated mapping generator panel for illegally named classes.\n\t\tint illegalNameCount = computeIllegalNames(resource);\n\n\t\t// Skip if there is no anti-decompilation work to be done.\n\t\tboolean hasIllegalNames = illegalNameCount > 0;\n\t\tboolean hasTransformations = (transformResult != null &&\n\t\t\t\t(!transformResult.getClassesToRemove().isEmpty() || !transformResult.getTransformedClasses().isEmpty()));\n\t\tif (!hasIllegalNames && !hasTransformations)\n\t\t\treturn false;\n\n\t\t// We have actions to take, create UI to apply patches.\n\t\tFxThreadUtil.run(() -> {\n\t\t\tExecutorService service = ThreadPoolFactory.newSingleThreadExecutor(\"anti-decompile-patching\");\n\t\t\tLabel title = new BoundLabel(Lang.getBinding(\"service.analysis.anti-decompile\"));\n\t\t\ttitle.getStyleClass().addAll(Styles.TITLE_4);\n\t\t\tconsumer.appendSummary(title);\n\t\t\tif (hasTransformations) {\n\t\t\t\tint transformCount = transformResult.getTransformedClasses().size() + transformResult.getClassesToRemove().size();\n\t\t\t\tLabel label = new BoundLabel(Lang.format(\"service.analysis.anti-decompile.label-patch\", transformCount));\n\t\t\t\tButton action = new ActionButton(CarbonIcons.CLEAN, Lang.format(\"service.analysis.anti-decompile.illegal-attr\", transformCount), transformResult::apply)\n\t\t\t\t\t\t.width(BUTTON_WIDTH).once().async(service);\n\t\t\t\tconsumer.appendSummary(box(action, label));\n\t\t\t}\n\t\t\tif (hasIllegalNames) {\n\t\t\t\tButton action = new ActionButton(CarbonIcons.LICENSE_MAINTENANCE, Lang.getBinding(\"service.analysis.anti-decompile.illegal-name\"), () -> {\n\t\t\t\t\tCompletableFuture.runAsync(() -> {\n\t\t\t\t\t\tMappingGeneratorWindow window = generatorWindowProvider.get();\n\n\t\t\t\t\t\tMappingGeneratorPane mappingGeneratorPane = window.getGeneratorPane();\n\t\t\t\t\t\tmappingGeneratorPane.addConfiguredFilter(new MappingGeneratorPane.IncludeNonAsciiNames());\n\t\t\t\t\t\tmappingGeneratorPane.addConfiguredFilter(new MappingGeneratorPane.IncludeKeywordNames());\n\t\t\t\t\t\tmappingGeneratorPane.addConfiguredFilter(new MappingGeneratorPane.IncludeWhitespaceNames());\n\t\t\t\t\t\tmappingGeneratorPane.addConfiguredFilter(new MappingGeneratorPane.IncludeNonJavaIdentifierNames());\n\t\t\t\t\t\tmappingGeneratorPane.addConfiguredFilter(new MappingGeneratorPane.IncludeLongName(400));\n\t\t\t\t\t\tmappingGeneratorPane.generate();\n\n\t\t\t\t\t\twindow.setOnCloseRequest(e -> generatorWindowProvider.destroy(window));\n\t\t\t\t\t\twindow.show();\n\t\t\t\t\t\twindow.requestFocus();\n\t\t\t\t\t}, FxThreadUtil.executor()).exceptionally(t -> {\n\t\t\t\t\t\tlogger.error(\"Failed to open mapping viewer\", t);\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t});\n\t\t\t\t}).width(BUTTON_WIDTH);\n\t\t\t\tLabel label = new BoundLabel(Lang.format(\"service.analysis.anti-decompile.label-patch\", illegalNameCount));\n\t\t\t\tconsumer.appendSummary(box(action, label));\n\t\t\t}\n\t\t});\n\t\treturn true;\n\t}\n\n\t@Nullable\n\tprivate JvmTransformResult computeTransformations(@Nonnull Workspace workspace) {\n\t\tJvmTransformResult transformResult;\n\t\ttry {\n\t\t\treturn transformationApplierService.newApplier(workspace).transformJvm(List.of(\n\t\t\t\t\t// Remove classes with cycles in inheritance\n\t\t\t\t\tCycleClassRemovingTransformer.class,\n\n\t\t\t\t\t// Remove illegally formed annotations\n\t\t\t\t\tIllegalAnnotationRemovingTransformer.class,\n\n\t\t\t\t\t// Remove illegally formed generic signatures\n\t\t\t\t\tIllegalSignatureRemovingTransformer.class,\n\n\t\t\t\t\t// Remove bogus duplicate annotations\n\t\t\t\t\tDuplicateAnnotationRemovingTransformer.class,\n\n\t\t\t\t\t// Remove annoying long annotations\n\t\t\t\t\tLongAnnotationRemovingTransformer.class,\n\n\t\t\t\t\t// Remove annoying long exceptions\n\t\t\t\t\tLongExceptionRemovingTransformer.class\n\t\t\t));\n\t\t} catch (TransformationException ex) {\n\t\t\tlogger.error(\"Failed applying anti-decompilation transformers\", ex);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprivate int computeIllegalNames(@Nonnull WorkspaceResource resource) {\n\t\tSet<JvmClassInfo> classesWithIllegalNames = Collections.newSetFromMap(new IdentityHashMap<>());\n\t\ttry (ExecutorService service = ThreadPoolFactory.newFixedThreadPool(\"illegal-name-summary\")) {\n\t\t\tList<Callable<Void>> tasks = new ArrayList<>();\n\t\t\tresource.jvmAllClassBundleStreamRecursive().forEach(bundle -> {\n\t\t\t\tbundle.forEach(cls -> {\n\t\t\t\t\t// While this task is 'simple' it can be a lot of work for workspaces with 1000's of classes\n\t\t\t\t\t// hence why each class is checked in parallel.\n\t\t\t\t\ttasks.add(() -> {\n\t\t\t\t\t\tif (ILLEGAL_NAME_FILTER.shouldMapClass(cls)) {\n\t\t\t\t\t\t\tsynchronized (classesWithIllegalNames) {\n\t\t\t\t\t\t\t\tclassesWithIllegalNames.add(cls);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (FieldMember field : cls.getFields()) {\n\t\t\t\t\t\t\tif (ILLEGAL_NAME_FILTER.shouldMapField(cls, field)) {\n\t\t\t\t\t\t\t\tsynchronized (classesWithIllegalNames) {\n\t\t\t\t\t\t\t\t\tclassesWithIllegalNames.add(cls);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (MethodMember method : cls.getMethods()) {\n\t\t\t\t\t\t\tif (ILLEGAL_NAME_FILTER.shouldMapMethod(cls, method)) {\n\t\t\t\t\t\t\t\tsynchronized (classesWithIllegalNames) {\n\t\t\t\t\t\t\t\t\tclassesWithIllegalNames.add(cls);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\n\t\t\t// Wait for all classes to be processed\n\t\t\ttry {\n\t\t\t\tservice.invokeAll(tasks);\n\t\t\t} catch (InterruptedException ex) {\n\t\t\t\tlogger.error(\"Interrupted illegal name computation\", ex);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t\treturn classesWithIllegalNames.size();\n\t}\n\n\t@Nonnull\n\tprivate static Node box(@Nonnull Node left, @Nonnull Node right) {\n\t\tHBox box = new HBox(left, right);\n\t\tbox.setSpacing(10);\n\t\tbox.setAlignment(Pos.CENTER_LEFT);\n\t\treturn box;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/info/summary/builtin/EntryPointSummarizer.java",
    "content": "package software.coley.recaf.services.info.summary.builtin;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.geometry.Insets;\nimport javafx.scene.Cursor;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.info.summary.ResourceSummarizer;\nimport software.coley.recaf.services.info.summary.SummaryConsumer;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.threading.Batch;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayDeque;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Queue;\nimport java.util.function.Supplier;\n\nimport static java.lang.reflect.Modifier.PUBLIC;\nimport static java.lang.reflect.Modifier.STATIC;\n\n/**\n * Summarizer that shows entry-points.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class EntryPointSummarizer implements ResourceSummarizer {\n\tprivate final TextProviderService textService;\n\tprivate final IconProviderService iconService;\n\tprivate final Actions actions;\n\n\t@Inject\n\tpublic EntryPointSummarizer(@Nonnull TextProviderService textService,\n\t                            @Nonnull IconProviderService iconService,\n\t                            @Nonnull Actions actions) {\n\t\tthis.textService = textService;\n\t\tthis.iconService = iconService;\n\t\tthis.actions = actions;\n\t}\n\n\t@Override\n\tpublic boolean summarize(@Nonnull Workspace workspace,\n\t                         @Nonnull WorkspaceResource resource,\n\t                         @Nonnull SummaryConsumer consumer) {\n\t\tBatch batch = FxThreadUtil.batch();\n\t\tbatch.add(() -> {\n\t\t\tLabel title = new BoundLabel(Lang.getBinding(\"service.analysis.entry-points\"));\n\t\t\ttitle.getStyleClass().addAll(Styles.TITLE_4);\n\t\t\tconsumer.appendSummary(title);\n\t\t});\n\n\t\t// Visit JVM classes\n\t\tint[] found = {0};\n\t\tQueue<WorkspaceResource> resourceQueue = new ArrayDeque<>();\n\t\tresourceQueue.add(resource);\n\t\twhile (!resourceQueue.isEmpty()) {\n\t\t\t// Doing this queue because embedded resources need to properly know which resource they belong to.\n\t\t\tWorkspaceResource currentResource = resourceQueue.remove();\n\t\t\tcurrentResource.jvmAllClassBundleStream().forEach(bundle -> {\n\t\t\t\tbundle.forEach(cls -> {\n\t\t\t\t\tList<MethodMember> entryMethods = cls.getMethods().stream()\n\t\t\t\t\t\t\t.filter(this::isJvmEntry)\n\t\t\t\t\t\t\t.toList();\n\t\t\t\t\tif (!entryMethods.isEmpty()) {\n\t\t\t\t\t\tfound[0]++;\n\t\t\t\t\t\tbatch.add(() -> {\n\t\t\t\t\t\t\tSupplier<JvmClassInfo> classLookup = () -> Objects.requireNonNullElse(bundle.get(cls.getName()), cls);\n\n\t\t\t\t\t\t\t// Add entry for class\n\t\t\t\t\t\t\tString classDisplay = textService.getJvmClassInfoTextProvider(workspace, currentResource, bundle, cls).makeText();\n\t\t\t\t\t\t\tNode classIcon = iconService.getJvmClassInfoIconProvider(workspace, currentResource, bundle, cls).makeIcon();\n\t\t\t\t\t\t\tLabel classLabel = new Label(classDisplay, classIcon);\n\t\t\t\t\t\t\tclassLabel.setCursor(Cursor.HAND);\n\t\t\t\t\t\t\tclassLabel.setOnMouseEntered(e -> classLabel.getStyleClass().add(Styles.TEXT_UNDERLINED));\n\t\t\t\t\t\t\tclassLabel.setOnMouseExited(e -> classLabel.getStyleClass().remove(Styles.TEXT_UNDERLINED));\n\t\t\t\t\t\t\tclassLabel.setOnMouseClicked(e -> actions.gotoDeclaration(workspace, currentResource, bundle, classLookup.get()));\n\t\t\t\t\t\t\tconsumer.appendSummary(classLabel);\n\n\t\t\t\t\t\t\t// Add entries for methods\n\t\t\t\t\t\t\tfor (MethodMember method : entryMethods) {\n\t\t\t\t\t\t\t\tString methodDisplay = textService.getMethodMemberTextProvider(workspace, currentResource, bundle, cls, method).makeText();\n\t\t\t\t\t\t\t\tNode methodIcon = iconService.getClassMemberIconProvider(workspace, currentResource, bundle, cls, method).makeIcon();\n\t\t\t\t\t\t\t\tLabel methodLabel = new Label(methodDisplay);\n\t\t\t\t\t\t\t\tmethodLabel.setCursor(Cursor.HAND);\n\t\t\t\t\t\t\t\tmethodLabel.setGraphic(methodIcon);\n\t\t\t\t\t\t\t\tmethodLabel.setPadding(new Insets(2, 2, 2, 15));\n\t\t\t\t\t\t\t\tmethodLabel.setOnMouseEntered(e -> methodLabel.getStyleClass().add(Styles.TEXT_UNDERLINED));\n\t\t\t\t\t\t\t\tmethodLabel.setOnMouseExited(e -> methodLabel.getStyleClass().remove(Styles.TEXT_UNDERLINED));\n\t\t\t\t\t\t\t\tmethodLabel.setOnMouseClicked(e -> {\n\t\t\t\t\t\t\t\t\tactions.gotoDeclaration(workspace, currentResource, bundle, classLookup.get())\n\t\t\t\t\t\t\t\t\t\t\t.requestFocus(method);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tconsumer.appendSummary(methodLabel);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\t\t\tresourceQueue.addAll(currentResource.getEmbeddedResources().values());\n\t\t}\n\n\t\tif (found[0] == 0)\n\t\t\tbatch.add(() -> consumer.appendSummary(new BoundLabel(Lang.getBinding(\"service.analysis.entry-points.none\"))));\n\n\t\tbatch.execute();\n\n\t\treturn true;\n\t}\n\n\tprivate boolean isJvmEntry(MethodMember method) {\n\t\treturn method.hasModifierMask(PUBLIC | STATIC) &&\n\t\t\t\tmethod.getName().equals(\"main\") &&\n\t\t\t\tmethod.getDescriptor().equals(\"([Ljava/lang/String;)V\");\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/info/summary/builtin/HashSummarizer.java",
    "content": "package software.coley.recaf.services.info.summary.builtin;\n\nimport atlantafx.base.theme.Styles;\nimport com.google.common.hash.Hashing;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TableCell;\nimport javafx.scene.control.TableColumn;\nimport javafx.scene.control.TableView;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.info.summary.ResourceSummarizer;\nimport software.coley.recaf.services.info.summary.SummaryConsumer;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.util.Animations;\nimport software.coley.recaf.util.ClipboardUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Summarizer that shows input file hash information.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class HashSummarizer implements ResourceSummarizer {\n\tprivate final CellConfigurationService configurationService;\n\n\t@Inject\n\tpublic HashSummarizer(@Nonnull CellConfigurationService configurationService) {\n\t\tthis.configurationService = configurationService;\n\t}\n\n\t@Override\n\tpublic boolean summarize(@Nonnull Workspace workspace,\n\t                         @Nonnull WorkspaceResource resource,\n\t                         @Nonnull SummaryConsumer consumer) {\n\t\tList<Node> grids = new ArrayList<>();\n\t\tif (resource instanceof WorkspaceFileResource fileResource)\n\t\t\tgrids.add(generateHashDisplay(workspace, fileResource));\n\t\tfor (WorkspaceFileResource embedded : resource.getEmbeddedResources().values())\n\t\t\tgrids.add(generateHashDisplay(workspace, embedded));\n\n\t\t// Skip if no file resources were found.\n\t\tif (grids.isEmpty())\n\t\t\treturn false;\n\n\t\t// Add title then all hash displays.\n\t\tBoundLabel title = new BoundLabel(Lang.getBinding(\"service.analysis.hashing\"));\n\t\ttitle.getStyleClass().add(Styles.TITLE_4);\n\t\tconsumer.appendSummary(title);\n\t\tfor (Node grid : grids)\n\t\t\tconsumer.appendSummary(grid);\n\n\t\treturn true;\n\t}\n\n\t@Nonnull\n\t@SuppressWarnings(\"deprecation\")\n\tprivate Node generateHashDisplay(@Nonnull Workspace workspace, @Nonnull WorkspaceFileResource fileResource) {\n\t\tFileInfo fileInfo = fileResource.getFileInfo();\n\t\tString name = fileInfo.getName();\n\t\tbyte[] content = fileInfo.getRawContent();\n\t\tString md5 = Hashing.md5().hashBytes(content).toString();\n\t\tString sha1 = Hashing.sha1().hashBytes(content).toString();\n\t\tString sha256 = Hashing.sha256().hashBytes(content).toString();\n\t\tString sha512 = Hashing.sha512().hashBytes(content).toString();\n\n\t\t// Generate a TableView of the hash information.\n\t\tFilePathNode path = PathNodes.filePath(workspace, fileResource, fileResource.getFileBundle(), fileInfo);\n\t\tVBox container = new VBox();\n\t\tcontainer.setSpacing(10);\n\n\t\t// Skip file title when the resource is the primary resource of the workspace, as that is already shown in the header.\n\t\tif (fileResource != workspace.getPrimaryResource()) {\n\t\t\tHBox fileTitle = new HBox(configurationService.graphicOf(path), new Label(configurationService.textOf(path)));\n\t\t\tfileTitle.setAlignment(Pos.CENTER_LEFT);\n\t\t\tfileTitle.setSpacing(10);\n\t\t\tfileTitle.setPadding(new Insets(8, 8, 0, 0));\n\t\t\tfileTitle.getStyleClass().add(Styles.TEXT_BOLD);\n\t\t\tcontainer.getChildren().add(fileTitle);\n\t\t}\n\n\t\t// Create TableView\n\t\tTableView<HashEntry> tableView = new TableView<>();\n\t\ttableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);\n\t\ttableView.setFixedCellSize(40);\n\n\t\t// Create columns\n\t\tTableColumn<HashEntry, String> typeColumn = new TableColumn<>();\n\t\tTableColumn<HashEntry, String> hashColumn = new TableColumn<>();\n\t\tTableColumn<HashEntry, Void> actionColumn = new TableColumn<>();\n\t\ttypeColumn.setMinWidth(70);\n\t\ttypeColumn.setMaxWidth(75);\n\t\ttypeColumn.setReorderable(false);\n\t\ttypeColumn.setResizable(false);\n\t\ttypeColumn.textProperty().bind(Lang.getBinding(\"service.analysis.hashing.type\"));\n\t\ttypeColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().type()));\n\t\thashColumn.setReorderable(false);\n\t\thashColumn.textProperty().bind(Lang.getBinding(\"service.analysis.hashing.value\"));\n\t\thashColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().value()));\n\t\thashColumn.setCellFactory(col -> new TableCell<>() {\n\t\t\t@Override\n\t\t\tprotected void updateItem(String item, boolean empty) {\n\t\t\t\tsuper.updateItem(item, empty);\n\t\t\t\tif (empty || item == null) {\n\t\t\t\t\tsetText(null);\n\t\t\t\t\tsetGraphic(null);\n\t\t\t\t} else {\n\t\t\t\t\tsetText(item);\n\t\t\t\t\tgetStyleClass().add(\"mono-text\");\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tactionColumn.setPrefWidth(50);\n\t\tactionColumn.setMaxWidth(50);\n\t\tactionColumn.setMinWidth(50);\n\t\tactionColumn.setReorderable(false);\n\t\tactionColumn.setResizable(false);\n\t\tactionColumn.setCellFactory(col -> new TableCell<>() {\n\t\t\tprivate final ActionButton copyButton = new ActionButton(CarbonIcons.COPY, this::runAction);\n\t\t\tprivate Runnable action;\n\n\t\t\t@Override\n\t\t\tprotected void updateItem(Void item, boolean empty) {\n\t\t\t\tsuper.updateItem(item, empty);\n\t\t\t\tif (empty) {\n\t\t\t\t\tsetGraphic(null);\n\t\t\t\t\taction = null;\n\t\t\t\t} else {\n\t\t\t\t\tHashEntry hashEntry = getTableView().getItems().get(getIndex());\n\t\t\t\t\taction = () -> {\n\t\t\t\t\t\tClipboardUtil.copyString(hashEntry.value());\n\t\t\t\t\t\tAnimations.animateSuccess(this, 500);\n\t\t\t\t\t};\n\t\t\t\t\tsetGraphic(copyButton);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprivate void runAction() {\n\t\t\t\tif (action != null) action.run();\n\t\t\t}\n\t\t});\n\t\ttableView.getColumns().addAll(typeColumn, hashColumn, actionColumn);\n\n\t\t// Add hash entries\n\t\tObservableList<HashEntry> hashEntries = FXCollections.observableArrayList(\n\t\t\t\tnew HashEntry(\"MD5\", md5),\n\t\t\t\tnew HashEntry(\"SHA-1\", sha1),\n\t\t\t\tnew HashEntry(\"SHA-256\", sha256),\n\t\t\t\tnew HashEntry(\"SHA-512\", sha512)\n\t\t);\n\t\ttableView.setItems(hashEntries);\n\t\ttableView.prefHeightProperty().bind(tableView.fixedCellSizeProperty().multiply(Bindings.size(tableView.getItems()).add(1.05)));\n\t\ttableView.minHeightProperty().bind(tableView.prefHeightProperty());\n\t\ttableView.maxHeightProperty().bind(tableView.prefHeightProperty());\n\n\t\tcontainer.getChildren().add(tableView);\n\t\treturn container;\n\t}\n\n\tprivate record HashEntry(@Nonnull String type, @Nonnull String value) {}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/info/summary/builtin/JarSigningSummarizer.java",
    "content": "package software.coley.recaf.services.info.summary.builtin;\n\nimport atlantafx.base.controls.Spacer;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport javafx.geometry.Insets;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ContentDisplay;\nimport javafx.scene.control.Label;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.layout.Background;\nimport javafx.scene.layout.BackgroundImage;\nimport javafx.scene.layout.BackgroundRepeat;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Region;\nimport javafx.scene.layout.VBox;\nimport org.fxmisc.flowless.VirtualizedScrollPane;\nimport org.fxmisc.richtext.CodeArea;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.collections.box.Box;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.TextFileInfo;\nimport software.coley.recaf.services.info.summary.ResourceSummarizer;\nimport software.coley.recaf.services.info.summary.SummaryConsumer;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.io.ByteArrayInputStream;\nimport java.security.cert.Certificate;\nimport java.security.cert.CertificateException;\nimport java.security.cert.CertificateFactory;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * Summarizer that shows jar signature information, with the option to remove it in a single click.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class JarSigningSummarizer implements ResourceSummarizer {\n\tprivate static final String MANIFEST_PATCH_PATTERN = \"^Name: [\\\\S\\\\s]+?[\\\\w-]+-Digest: .+\\\\s+?\";\n\tprivate static final CertificateFactory CERTIFICATE_FACTORY;\n\n\tstatic {\n\t\tCertificateFactory factory;\n\t\ttry {\n\t\t\tfactory = CertificateFactory.getInstance(\"X.509\");\n\t\t} catch (Throwable t) {\n\t\t\tfactory = null;\n\t\t}\n\t\tCERTIFICATE_FACTORY = factory;\n\t}\n\n\t@Override\n\tpublic boolean summarize(@Nonnull Workspace workspace,\n\t                         @Nonnull WorkspaceResource resource,\n\t                         @Nonnull SummaryConsumer consumer) {\n\t\tFileBundle bundle = resource.getFileBundle();\n\t\tFileInfo manifest = bundle.get(\"META-INF/MANIFEST.MF\");\n\n\t\t// Must have a manifest file\n\t\tif (manifest == null || !manifest.isTextFile())\n\t\t\treturn false;\n\n\t\t// Must list at least one file signed\n\t\tTextFileInfo manifestText = manifest.asTextFile();\n\t\tif (!manifestText.getText().contains(\"-Digest: \"))\n\t\t\treturn false;\n\n\t\t// Collect signature files to remove\n\t\tList<FileInfo> sfFiles = new ArrayList<>();\n\t\tList<FileInfo> rsaFiles = new ArrayList<>();\n\t\tfor (FileInfo file : bundle) {\n\t\t\tString name = file.getName();\n\t\t\tif (name.matches(\"META-INF/[\\\\w-]+\\\\.(?:SF|RSA|DSA)\")) {\n\t\t\t\tif (name.endsWith(\".SF\")) {\n\t\t\t\t\tsfFiles.add(file);\n\t\t\t\t} else {\n\t\t\t\t\trsaFiles.add(file);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Layout output\n\t\tVBox box = new VBox();\n\t\tbox.setSpacing(15);\n\t\tbox.setFillWidth(true);\n\t\tInsets rightPadding = new Insets(0, 30, 0, 0);\n\t\tLabel title = new BoundLabel(Lang.getBinding(\"service.analysis.signature-info\"));\n\t\ttitle.getStyleClass().addAll(Styles.TITLE_4);\n\t\tfor (FileInfo rsaFile : rsaFiles) {\n\t\t\ttry {\n\t\t\t\tCodeArea area = new CodeArea();\n\t\t\t\tarea.getStyleClass().addAll(\"background-dark\", \"border-muted\");\n\t\t\t\tCollection<? extends Certificate> certificates = CERTIFICATE_FACTORY.generateCertificates(new ByteArrayInputStream(rsaFile.getRawContent()));\n\t\t\t\tfor (Certificate certificate : certificates) {\n\t\t\t\t\tarea.appendText(certificate.toString() + \"\\n\");\n\t\t\t\t\tarea.appendText(\"=\".repeat(73) + \"\\n\");\n\t\t\t\t}\n\t\t\t\tarea.showParagraphAtTop(0);\n\t\t\t\tarea.setPrefHeight(300);\n\t\t\t\tarea.setEditable(false);\n\t\t\t\tBorderPane pane = new BorderPane(new VirtualizedScrollPane<>(area));\n\t\t\t\tpane.setPadding(rightPadding);\n\t\t\t\tbox.getChildren().add(pane);\n\t\t\t} catch (CertificateException e) {\n\t\t\t\tbox.getChildren().add(new Label(\"Error parsing certificate: \" + rsaFile.getName()));\n\t\t\t}\n\t\t}\n\t\tBox<Runnable> boxedFixRunnable = new Box<>();\n\t\tButton button = new ActionButton(new FontIconView(CarbonIcons.TRASH_CAN), Lang.getBinding(\"menu.edit.remove\"), () -> boxedFixRunnable.get().run());\n\t\tHBox titleWrapper = new HBox(title, new Spacer(), button);\n\t\ttitleWrapper.setPadding(rightPadding);\n\t\tbox.getChildren().addFirst(titleWrapper);\n\t\tconsumer.appendSummary(box);\n\n\t\t// Create task to remove signing information\n\t\tRunnable fix = () -> {\n\t\t\t// Patch out the entries from the 'MANIFEST.MF'\n\t\t\tString patchedManifest = RegexUtil.getMatcher(MANIFEST_PATCH_PATTERN, manifestText.getText()).replaceAll(\"\");\n\t\t\tbundle.put(manifestText.toTextBuilder().withText(patchedManifest).build());\n\n\t\t\t// Remove '.SF' and '.RSA' files\n\t\t\tfor (FileInfo file : sfFiles)\n\t\t\t\tbundle.remove(file.getName());\n\t\t\tfor (FileInfo file : rsaFiles)\n\t\t\t\tbundle.remove(file.getName());\n\n\t\t\t// Indicate we're done\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tbox.getChildren().clear();\n\t\t\t\tbox.getChildren().add(title);\n\t\t\t\ttitle.setContentDisplay(ContentDisplay.RIGHT);\n\t\t\t\ttitle.setGraphic(new BoundLabel(Lang.getBinding(\"misc.removed\").map(s -> \"(\" + s + \")\")));\n\t\t\t});\n\t\t};\n\t\tboxedFixRunnable.set(fix);\n\n\t\t// Indicate we have emitted our summary\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/mapping/MappingHelper.java",
    "content": "package software.coley.recaf.services.mapping;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.mapping.aggregate.AggregateMappingManager;\nimport software.coley.recaf.services.mapping.aggregate.AggregatedMappings;\nimport software.coley.recaf.services.mapping.format.InvalidMappingException;\nimport software.coley.recaf.services.mapping.format.MappingFileFormat;\nimport software.coley.recaf.ui.menubar.MappingMenu;\nimport software.coley.recaf.ui.pane.MappingApplicationPane;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Objects;\nimport java.util.concurrent.ExecutorService;\n\n/**\n * Shared mapping helper utility for UI logic.\n *\n * @author Matt Coley\n * @see MappingMenu\n * @see MappingApplicationPane\n */\n@ApplicationScoped\npublic class MappingHelper {\n\tprivate static final Logger logger = Logging.get(MappingHelper.class);\n\tprivate final ExecutorService exportPool = ThreadPoolFactory.newSingleThreadExecutor(\"mapping-export\");\n\tprivate final ExecutorService importPool = ThreadPoolFactory.newSingleThreadExecutor(\"mapping-import\");\n\tprivate final MappingApplierService applierService;\n\tprivate final AggregateMappingManager aggregateMappingManager;\n\n\t@Inject\n\tpublic MappingHelper(@Nonnull MappingApplierService applierService, @Nonnull AggregateMappingManager aggregateMappingManager) {\n\t\tthis.applierService = applierService;\n\t\tthis.aggregateMappingManager = aggregateMappingManager;\n\t}\n\n\t@Nonnull\n\tpublic IntermediateMappings parse(@Nonnull MappingFileFormat format, @Nonnull Path mappingFile) throws IOException, InvalidMappingException {\n\t\tString mappingsText = Files.readString(mappingFile);\n\t\tIntermediateMappings parsedMappings = format.parse(mappingsText);\n\t\tlogger.info(\"Loaded mappings from {} in {} format\", mappingFile.getFileName(), format.implementationName());\n\t\treturn parsedMappings;\n\t}\n\n\tpublic void applyMappings(@Nonnull MappingFileFormat format, @Nonnull Mappings mappings) {\n\t\timportPool.submit(() -> {\n\t\t\ttry {\n\t\t\t\tMappingResults results = Objects.requireNonNull(applierService.inCurrentWorkspace()).applyToPrimaryResource(mappings);\n\t\t\t\tresults.apply();\n\t\t\t\tlogger.info(\"Applied mappings - Updated {} classes\", results.getPostMappingPaths().size());\n\t\t\t} catch (Exception ex) {\n\t\t\t\tlogger.error(\"Failed to read {} mappings\", format.implementationName(), ex);\n\t\t\t}\n\t\t});\n\t}\n\n\tpublic void exportMappingsFile(@Nonnull MappingFileFormat format, @Nonnull Path mappingFile) {\n\t\texportPool.submit(() -> {\n\t\t\ttry {\n\t\t\t\tAggregatedMappings mappings = Objects.requireNonNull(aggregateMappingManager.getAggregatedMappings());\n\t\t\t\tString mappingsText = format.exportText(mappings);\n\t\t\t\tif (mappingsText != null) {\n\t\t\t\t\tFiles.writeString(mappingFile, mappingsText);\n\t\t\t\t\tlogger.info(\"Exporting mappings to {} in {} format\", mappingFile.getFileName(), format.implementationName());\n\t\t\t\t} else {\n\t\t\t\t\t// We already checked for export support, so this should never happen\n\t\t\t\t\tthrow new IllegalStateException(\"Mapping export shouldn't be null for format: \" + format.implementationName());\n\t\t\t\t}\n\t\t\t} catch (Exception ex) {\n\t\t\t\tlogger.error(\"Failed to write mappings in {} format to {}\", format.implementationName(), mappingFile.getFileName(), ex);\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/navigation/Actions.java",
    "content": "package software.coley.recaf.services.navigation;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.beans.value.ObservableValue;\nimport javafx.collections.ObservableList;\nimport javafx.scene.Node;\nimport javafx.scene.Scene;\nimport javafx.scene.control.ContextMenu;\nimport javafx.scene.control.Menu;\nimport javafx.scene.control.MenuItem;\nimport javafx.scene.control.Tab;\nimport javafx.scene.input.KeyEvent;\nimport javafx.stage.Stage;\nimport org.kordamp.ikonli.Ikon;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.Opcodes;\nimport org.slf4j.Logger;\nimport software.coley.bentofx.dockable.Dockable;\nimport software.coley.bentofx.dockable.DockableIconFactory;\nimport software.coley.bentofx.layout.container.DockContainerLeaf;\nimport software.coley.bentofx.path.DockablePath;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.DirectoryPathNode;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.IncompletePathException;\nimport software.coley.recaf.path.LineNumberPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.cell.icon.IconProvider;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.MappingApplier;\nimport software.coley.recaf.services.mapping.MappingApplierService;\nimport software.coley.recaf.services.mapping.MappingResults;\nimport software.coley.recaf.services.window.WindowFactory;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.config.KeybindingConfig;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.graph.MethodCallGraphsPane;\nimport software.coley.recaf.ui.control.popup.AddMemberPopup;\nimport software.coley.recaf.ui.control.popup.ItemListSelectionPopup;\nimport software.coley.recaf.ui.control.popup.ItemTreeSelectionPopup;\nimport software.coley.recaf.ui.control.popup.NamePopup;\nimport software.coley.recaf.ui.control.popup.OverrideMethodPopup;\nimport software.coley.recaf.ui.docking.DockingManager;\nimport software.coley.recaf.ui.pane.CommentEditPane;\nimport software.coley.recaf.ui.pane.CommentListPane;\nimport software.coley.recaf.ui.pane.DocumentationPane;\nimport software.coley.recaf.ui.pane.WorkspaceInformationPane;\nimport software.coley.recaf.ui.pane.editing.AbstractContentPane;\nimport software.coley.recaf.ui.pane.editing.FileDisplayMode;\nimport software.coley.recaf.ui.pane.editing.FilePane;\nimport software.coley.recaf.ui.pane.editing.android.AndroidClassEditorType;\nimport software.coley.recaf.ui.pane.editing.android.AndroidClassPane;\nimport software.coley.recaf.ui.pane.editing.assembler.AssemblerPane;\nimport software.coley.recaf.ui.pane.editing.jvm.JvmClassEditorType;\nimport software.coley.recaf.ui.pane.editing.jvm.JvmClassPane;\nimport software.coley.recaf.ui.pane.search.AbstractSearchPane;\nimport software.coley.recaf.ui.pane.search.ClassReferenceSearchPane;\nimport software.coley.recaf.ui.pane.search.InstructionSearchPane;\nimport software.coley.recaf.ui.pane.search.MemberDeclarationSearchPane;\nimport software.coley.recaf.ui.pane.search.MemberReferenceSearchPane;\nimport software.coley.recaf.ui.pane.search.NumberSearchPane;\nimport software.coley.recaf.ui.pane.search.StringSearchPane;\nimport software.coley.recaf.util.Animations;\nimport software.coley.recaf.util.ClipboardUtil;\nimport software.coley.recaf.util.EscapeUtil;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.visitors.ClassAnnotationRemovingVisitor;\nimport software.coley.recaf.util.visitors.FieldAnnotationRemovingVisitor;\nimport software.coley.recaf.util.visitors.FieldPredicate;\nimport software.coley.recaf.util.visitors.MemberCopyingVisitor;\nimport software.coley.recaf.util.visitors.MemberRemovingVisitor;\nimport software.coley.recaf.util.visitors.MemberStubAddingVisitor;\nimport software.coley.recaf.util.visitors.MethodAnnotationRemovingVisitor;\nimport software.coley.recaf.util.visitors.MethodNoopingVisitor;\nimport software.coley.recaf.util.visitors.MethodPredicate;\nimport software.coley.recaf.util.visitors.MethodVariableRemovingVisitor;\nimport software.coley.recaf.workspace.PathExportingManager;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicFileBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\n\nimport static software.coley.collections.Unchecked.cast;\nimport static software.coley.recaf.util.Lang.getBinding;\nimport static software.coley.recaf.util.Menus.*;\nimport static software.coley.recaf.util.StringUtil.*;\n\n/**\n * Common actions integration.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class Actions implements Service {\n\tpublic static final String ID = \"actions\";\n\tprivate static final Logger logger = Logging.get(Actions.class);\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final NavigationManager navigationManager;\n\tprivate final DockingManager dockingManager;\n\tprivate final WindowFactory windowFactory;\n\tprivate final TextProviderService textService;\n\tprivate final IconProviderService iconService;\n\tprivate final CellConfigurationService cellConfigurationService;\n\tprivate final PathExportingManager pathExportingManager;\n\tprivate final MappingApplierService mappingApplierService;\n\tprivate final InheritanceGraphService inheritanceGraphService;\n\tprivate final Instance<JvmClassPane> jvmPaneProvider;\n\tprivate final Instance<AndroidClassPane> androidPaneProvider;\n\tprivate final Instance<FilePane> filePaneProvider;\n\tprivate final Instance<AssemblerPane> assemblerPaneProvider;\n\tprivate final Instance<WorkspaceInformationPane> infoPaneProvider;\n\tprivate final Instance<CommentEditPane> commentPaneProvider;\n\tprivate final Instance<CommentListPane> commentListPaneProvider;\n\tprivate final Instance<MethodCallGraphsPane> callGraphsPaneProvider;\n\tprivate final Instance<StringSearchPane> stringSearchPaneProvider;\n\tprivate final Instance<NumberSearchPane> numberSearchPaneProvider;\n\tprivate final Instance<ClassReferenceSearchPane> classReferenceSearchPaneProvider;\n\tprivate final Instance<MemberReferenceSearchPane> memberReferenceSearchPaneProvider;\n\tprivate final Instance<MemberDeclarationSearchPane> memberDeclarationSearchPaneProvider;\n\tprivate final Instance<InstructionSearchPane> instructionSearchPaneProvider;\n\tprivate final KeybindingConfig keybindingConfig;\n\tprivate final ActionsConfig config;\n\n\t@Inject\n\tpublic Actions(@Nonnull ActionsConfig config,\n\t               @Nonnull KeybindingConfig keybindingConfig,\n\t               @Nonnull WorkspaceManager workspaceManager,\n\t               @Nonnull NavigationManager navigationManager,\n\t               @Nonnull DockingManager dockingManager,\n\t               @Nonnull WindowFactory windowFactory,\n\t               @Nonnull TextProviderService textService,\n\t               @Nonnull IconProviderService iconService,\n\t               @Nonnull CellConfigurationService cellConfigurationService,\n\t               @Nonnull PathExportingManager pathExportingManager,\n\t               @Nonnull MappingApplierService mappingApplierService,\n\t               @Nonnull InheritanceGraphService inheritanceGraphService,\n\t               @Nonnull Instance<MappingApplier> applierProvider,\n\t               @Nonnull Instance<JvmClassPane> jvmPaneProvider,\n\t               @Nonnull Instance<AndroidClassPane> androidPaneProvider,\n\t               @Nonnull Instance<FilePane> filePaneProvider,\n\t               @Nonnull Instance<AssemblerPane> assemblerPaneProvider,\n\t               @Nonnull Instance<WorkspaceInformationPane> infoPaneProvider,\n\t               @Nonnull Instance<CommentEditPane> commentPaneProvider,\n\t               @Nonnull Instance<CommentListPane> commentListPaneProvider,\n\t               @Nonnull Instance<StringSearchPane> stringSearchPaneProvider,\n\t               @Nonnull Instance<NumberSearchPane> numberSearchPaneProvider,\n\t               @Nonnull Instance<MethodCallGraphsPane> callGraphsPaneProvider,\n\t               @Nonnull Instance<ClassReferenceSearchPane> classReferenceSearchPaneProvider,\n\t               @Nonnull Instance<MemberReferenceSearchPane> memberReferenceSearchPaneProvider,\n\t               @Nonnull Instance<MemberDeclarationSearchPane> memberDeclarationSearchPaneProvider,\n\t               @Nonnull Instance<InstructionSearchPane> instructionSearchPaneProvider) {\n\t\tthis.config = config;\n\t\tthis.keybindingConfig = keybindingConfig;\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.navigationManager = navigationManager;\n\t\tthis.dockingManager = dockingManager;\n\t\tthis.windowFactory = windowFactory;\n\t\tthis.textService = textService;\n\t\tthis.iconService = iconService;\n\t\tthis.cellConfigurationService = cellConfigurationService;\n\t\tthis.pathExportingManager = pathExportingManager;\n\t\tthis.mappingApplierService = mappingApplierService;\n\t\tthis.inheritanceGraphService = inheritanceGraphService;\n\t\tthis.jvmPaneProvider = jvmPaneProvider;\n\t\tthis.androidPaneProvider = androidPaneProvider;\n\t\tthis.filePaneProvider = filePaneProvider;\n\t\tthis.assemblerPaneProvider = assemblerPaneProvider;\n\t\tthis.infoPaneProvider = infoPaneProvider;\n\t\tthis.commentPaneProvider = commentPaneProvider;\n\t\tthis.commentListPaneProvider = commentListPaneProvider;\n\t\tthis.stringSearchPaneProvider = stringSearchPaneProvider;\n\t\tthis.numberSearchPaneProvider = numberSearchPaneProvider;\n\t\tthis.callGraphsPaneProvider = callGraphsPaneProvider;\n\t\tthis.classReferenceSearchPaneProvider = classReferenceSearchPaneProvider;\n\t\tthis.memberReferenceSearchPaneProvider = memberReferenceSearchPaneProvider;\n\t\tthis.memberDeclarationSearchPaneProvider = memberDeclarationSearchPaneProvider;\n\t\tthis.instructionSearchPaneProvider = instructionSearchPaneProvider;\n\t}\n\n\t/**\n\t * Brings a {@link Navigable} component representing a class/file into focus.\n\t * If no such component exists, one is created.\n\t * <br>\n\t * Automatically calls the type-specific goto-declaration handling.\n\t *\n\t * @param path\n\t * \t\tPath to class or file to open.\n\t *\n\t * @return Navigable content representing content of the path.\n\t *\n\t * @throws IncompletePathException\n\t * \t\tWhen the path is missing parent elements.\n\t */\n\t@Nonnull\n\tpublic Navigable gotoDeclaration(@Nonnull PathNode<?> path) throws IncompletePathException {\n\t\tif (path instanceof ClassPathNode classPath) return gotoDeclaration(classPath);\n\t\telse if (path instanceof FilePathNode filePath) return gotoDeclaration(filePath);\n\t\telse if (path instanceof ClassMemberPathNode classMemberPath) {\n\t\t\tClassPathNode parent = classMemberPath.getParent();\n\t\t\tif (parent == null)\n\t\t\t\tthrow new IncompletePathException(ClassInfo.class);\n\t\t\tClassNavigable navigable = gotoDeclaration(parent);\n\t\t\tnavigable.requestFocus(classMemberPath.getValue());\n\t\t\treturn navigable;\n\t\t} else if (path instanceof LineNumberPathNode lineNumberPath) {\n\t\t\tFilePathNode parent = lineNumberPath.getParent();\n\t\t\tif (parent == null)\n\t\t\t\tthrow new IncompletePathException(FileInfo.class);\n\t\t\treturn gotoDeclaration(parent);\n\t\t}\n\t\tthrow new IncompletePathException(path.getValueType());\n\t}\n\n\t/**\n\t * Brings a {@link ClassNavigable} component representing the given class into focus.\n\t * If no such component exists, one is created.\n\t * <br>\n\t * Automatically calls the type-specific goto-declaration handling.\n\t *\n\t * @param path\n\t * \t\tPath containing a class to open.\n\t *\n\t * @return Navigable content representing class content of the path.\n\t *\n\t * @throws IncompletePathException\n\t * \t\tWhen the path is missing parent elements.\n\t */\n\t@Nonnull\n\tpublic ClassNavigable gotoDeclaration(@Nonnull ClassPathNode path) throws IncompletePathException {\n\t\tWorkspace workspace = path.getValueOfType(Workspace.class);\n\t\tWorkspaceResource resource = path.getValueOfType(WorkspaceResource.class);\n\t\tClassBundle<?> bundle = path.getValueOfType(ClassBundle.class);\n\t\tClassInfo info = path.getValue();\n\t\tif (workspace == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for class '{}', missing workspace in path\", info.getName());\n\t\t\tthrow new IncompletePathException(Workspace.class);\n\t\t}\n\t\tif (resource == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for class '{}', missing resource in path\", info.getName());\n\t\t\tthrow new IncompletePathException(WorkspaceResource.class);\n\t\t}\n\t\tif (bundle == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for class '{}', missing bundle in path\", info.getName());\n\t\t\tthrow new IncompletePathException(ClassBundle.class);\n\t\t}\n\n\t\t// Handle JVM vs Android\n\t\tif (info.isJvmClass()) {\n\t\t\treturn gotoDeclaration(workspace, resource, (JvmClassBundle) bundle, info.asJvmClass());\n\t\t} else if (info.isAndroidClass()) {\n\t\t\treturn gotoDeclaration(workspace, resource, (AndroidClassBundle) bundle, info.asAndroidClass());\n\t\t}\n\t\tthrow new UnsupportedContentException(\"Unsupported class type: \" + info.getClass().getName());\n\t}\n\n\t/**\n\t * Brings a {@link ClassNavigable} component representing the given class into focus.\n\t * If no such component exists, one is created.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to go to.\n\t *\n\t * @return Navigable content representing class content of the path.\n\t */\n\t@Nonnull\n\tpublic ClassNavigable gotoDeclaration(@Nonnull Workspace workspace,\n\t                                      @Nonnull WorkspaceResource resource,\n\t                                      @Nonnull JvmClassBundle bundle,\n\t                                      @Nonnull JvmClassInfo info) {\n\t\tClassPathNode path = PathNodes.classPath(workspace, resource, bundle, info);\n\t\treturn (ClassNavigable) getOrCreatePathContent(path, () -> {\n\t\t\t// Create text/graphic for the tab to create.\n\t\t\tString title = textService.getJvmClassInfoTextProvider(workspace, resource, bundle, info).makeText();\n\t\t\tIconProvider iconProvider = iconService.getJvmClassInfoIconProvider(workspace, resource, bundle, info);\n\t\t\tDockableIconFactory graphicFactory = d -> Objects.requireNonNull(iconProvider.makeIcon(), \"Missing graphic\");\n\t\t\tif (title == null) throw new IllegalStateException(\"Missing title\");\n\n\t\t\t// Create content for the tab.\n\t\t\tJvmClassPane content = jvmPaneProvider.get();\n\t\t\tcontent.onUpdatePath(path);\n\n\t\t\t// Build the tab.\n\t\t\tDockable dockable = createDockable(dockingManager.getPrimaryDockingContainer(), title, graphicFactory, content);\n\t\t\tdockable.addCloseListener((_, _) -> jvmPaneProvider.destroy(content));\n\t\t\tcontent.addPathUpdateListener(updatedPath -> {\n\t\t\t\t// Update tab graphic in case backing class details change.\n\t\t\t\tJvmClassInfo updatedInfo = updatedPath.getValue().asJvmClass();\n\t\t\t\tString updatedTitle = textService.getJvmClassInfoTextProvider(workspace, resource, bundle, updatedInfo).makeText();\n\t\t\t\tIconProvider updatedIconProvider = iconService.getJvmClassInfoIconProvider(workspace, resource, bundle, updatedInfo);\n\t\t\t\tDockableIconFactory updatedGraphicFactory = d -> Objects.requireNonNull(updatedIconProvider.makeIcon(), \"Missing graphic\");\n\t\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\t\tdockable.setTitle(updatedTitle);\n\t\t\t\t\tdockable.setIconFactory(updatedGraphicFactory);\n\t\t\t\t});\n\t\t\t});\n\t\t\tsetupInfoContextMenu(info, content, dockable);\n\t\t\treturn dockable;\n\t\t});\n\t}\n\n\t/**\n\t * Brings a {@link ClassNavigable} component representing the given class into focus.\n\t * If no such component exists, one is created.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to go to.\n\t *\n\t * @return Navigable content representing class content of the path.\n\t */\n\t@Nonnull\n\tpublic ClassNavigable gotoDeclaration(@Nonnull Workspace workspace,\n\t                                      @Nonnull WorkspaceResource resource,\n\t                                      @Nonnull AndroidClassBundle bundle,\n\t                                      @Nonnull AndroidClassInfo info) {\n\t\tClassPathNode path = PathNodes.classPath(workspace, resource, bundle, info);\n\t\treturn (ClassNavigable) getOrCreatePathContent(path, () -> {\n\t\t\t// Create text/graphic for the tab to create.\n\t\t\tString title = textService.getAndroidClassInfoTextProvider(workspace, resource, bundle, info).makeText();\n\t\t\tIconProvider iconProvider = iconService.getAndroidClassInfoIconProvider(workspace, resource, bundle, info);\n\t\t\tDockableIconFactory graphicFactory = d -> Objects.requireNonNull(iconProvider.makeIcon(), \"Missing graphic\");\n\t\t\tif (title == null) throw new IllegalStateException(\"Missing title\");\n\n\t\t\t// Create content for the tab.\n\t\t\tAndroidClassPane content = androidPaneProvider.get();\n\t\t\tcontent.onUpdatePath(path);\n\n\t\t\t// Build the tab.\n\t\t\tDockable dockable = createDockable(dockingManager.getPrimaryDockingContainer(), title, graphicFactory, content);\n\t\t\tdockable.addCloseListener((_, _) -> androidPaneProvider.destroy(content));\n\t\t\tcontent.addPathUpdateListener(updatedPath -> {\n\t\t\t\t// Update tab graphic in case backing class details change.\n\t\t\t\tAndroidClassInfo updatedInfo = updatedPath.getValue().asAndroidClass();\n\t\t\t\tString updatedTitle = textService.getAndroidClassInfoTextProvider(workspace, resource, bundle, updatedInfo).makeText();\n\t\t\t\tIconProvider updatedIconProvider = iconService.getAndroidClassInfoIconProvider(workspace, resource, bundle, updatedInfo);\n\t\t\t\tDockableIconFactory updatedGraphicFactory = d -> Objects.requireNonNull(updatedIconProvider.makeIcon(), \"Missing graphic\");\n\t\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\t\tdockable.setTitle(updatedTitle);\n\t\t\t\t\tdockable.setIconFactory(updatedGraphicFactory);\n\t\t\t\t});\n\t\t\t});\n\t\t\tsetupInfoContextMenu(info, content, dockable);\n\t\t\treturn dockable;\n\t\t});\n\t}\n\n\t/**\n\t * Brings a {@link FileNavigable} component representing the given file into focus.\n\t * If no such component exists, one is created.\n\t * <br>\n\t * Automatically calls the type-specific goto-declaration handling.\n\t *\n\t * @param path\n\t * \t\tPath containing a file to open.\n\t *\n\t * @return Navigable content representing file content of the path.\n\t *\n\t * @throws IncompletePathException\n\t * \t\tWhen the path is missing parent elements.\n\t */\n\t@Nonnull\n\tpublic FileNavigable gotoDeclaration(@Nonnull FilePathNode path) throws IncompletePathException {\n\t\tWorkspace workspace = path.getValueOfType(Workspace.class);\n\t\tWorkspaceResource resource = path.getValueOfType(WorkspaceResource.class);\n\t\tFileBundle bundle = path.getValueOfType(FileBundle.class);\n\t\tFileInfo info = path.getValue();\n\t\tif (workspace == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for file '{}', missing workspace in path\", info.getName());\n\t\t\tthrow new IncompletePathException(Workspace.class);\n\t\t}\n\t\tif (resource == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for file '{}', missing resource in path\", info.getName());\n\t\t\tthrow new IncompletePathException(WorkspaceResource.class);\n\t\t}\n\t\tif (bundle == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for file '{}', missing bundle in path\", info.getName());\n\t\t\tthrow new IncompletePathException(ClassBundle.class);\n\t\t}\n\n\t\treturn gotoDeclaration(workspace, resource, bundle, info.asFile());\n\t}\n\n\t/**\n\t * Brings a {@link FileNavigable} component representing the given file into focus.\n\t * If no such component exists, one is created.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tFile to go to.\n\t *\n\t * @return Navigable content representing file content of the path.\n\t */\n\t@Nonnull\n\tpublic FileNavigable gotoDeclaration(@Nonnull Workspace workspace,\n\t                                     @Nonnull WorkspaceResource resource,\n\t                                     @Nonnull FileBundle bundle,\n\t                                     @Nonnull FileInfo info) {\n\t\tFilePathNode path = PathNodes.filePath(workspace, resource, bundle, info);\n\t\treturn (FileNavigable) getOrCreatePathContent(path, () -> {\n\t\t\t// Create text/graphic for the tab to create.\n\t\t\tString title = textService.getFileInfoTextProvider(workspace, resource, bundle, info).makeText();\n\t\t\tIconProvider iconProvider = iconService.getFileInfoIconProvider(workspace, resource, bundle, info);\n\t\t\tDockableIconFactory graphicFactory = d -> Objects.requireNonNull(iconProvider.makeIcon(), \"Missing graphic\");\n\t\t\tif (title == null) throw new IllegalStateException(\"Missing title\");\n\n\t\t\t// Create content for the tab.\n\t\t\tFilePane content = filePaneProvider.get();\n\t\t\tcontent.setupForFileType(info);\n\t\t\tcontent.onUpdatePath(path);\n\n\t\t\t// Build the tab.\n\t\t\tDockable dockable = createDockable(dockingManager.getPrimaryDockingContainer(), title, graphicFactory, content);\n\t\t\tdockable.addCloseListener((_, _) -> filePaneProvider.destroy(content));\n\t\t\tsetupInfoContextMenu(info, content, dockable, menu -> addFilePaneOptions(menu, content));\n\t\t\treturn dockable;\n\t\t});\n\t}\n\n\t/**\n\t * Prompts the user to document the given class into.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to document.\n\t */\n\tpublic void openCommentEditing(@Nonnull Workspace workspace,\n\t                               @Nonnull WorkspaceResource resource,\n\t                               @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                               @Nonnull ClassInfo info) {\n\t\tcreateContent(() -> {\n\t\t\tClassPathNode path = PathNodes.classPath(workspace, resource, bundle, info);\n\n\t\t\t// Create text/graphic for the tab to create.\n\t\t\tString title = textService.getClassInfoTextProvider(workspace, resource, bundle, info).makeText();\n\t\t\tDockableIconFactory graphicFactory = d -> new FontIconView(CarbonIcons.BOOKMARK_FILLED);\n\t\t\tif (title == null) throw new IllegalStateException(\"Missing title\");\n\t\t\treturn createCommentEditDockable(path, title, graphicFactory, info);\n\t\t});\n\t}\n\n\t/**\n\t * Prompts the user to document the given class into.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param member\n\t * \t\tMember to document.\n\t */\n\tpublic void openCommentEditing(@Nonnull Workspace workspace,\n\t                               @Nonnull WorkspaceResource resource,\n\t                               @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                               @Nonnull ClassInfo declaringClass,\n\t                               @Nonnull ClassMember member) {\n\t\tcreateContent(() -> {\n\t\t\tClassMemberPathNode path = PathNodes.memberPath(workspace, resource, bundle, declaringClass, member);\n\n\t\t\t// Create text/graphic for the tab to create.\n\t\t\tClassInfo classInfo = path.getParent().getValue();\n\t\t\tString title = textService.getMemberTextProvider(workspace, resource, bundle, classInfo, member).makeText();\n\t\t\tDockableIconFactory graphicFactory = d -> new FontIconView(CarbonIcons.BOOKMARK_FILLED);\n\t\t\tif (title == null) throw new IllegalStateException(\"Missing title\");\n\t\t\treturn createCommentEditDockable(path, title, graphicFactory, classInfo);\n\t\t});\n\t}\n\n\t@Nonnull\n\tprivate Dockable createCommentEditDockable(@Nonnull PathNode<?> path, @Nonnull String title,\n\t                                           @Nonnull DockableIconFactory graphicFactory, @Nonnull ClassInfo classInfo) {\n\t\t// Create content for the dockable.\n\t\tCommentEditPane content = commentPaneProvider.get();\n\t\tcontent.onUpdatePath(path);\n\n\t\t// Place the tab in a region with other comments if possible.\n\t\tDockablePath searchPath = dockingManager.getBento()\n\t\t\t\t.search().dockable(d -> d.getNode() instanceof DocumentationPane);\n\t\tDockContainerLeaf container = searchPath == null ? dockingManager.getPrimaryDockingContainer() : searchPath.leafContainer();\n\n\t\t// Build the dockable.\n\t\tDockable dockable = createDockable(container, title, graphicFactory, content);\n\t\tdockable.addCloseListener((_, _) -> commentPaneProvider.destroy(content));\n\t\tcontainer.addDockable(dockable);\n\t\tdockable.setContextMenuFactory(d -> {\n\t\t\tContextMenu menu = new ContextMenu();\n\t\t\taddCloseActions(menu, dockable);\n\t\t\treturn menu;\n\t\t});\n\n\t\tselectTab(content);\n\t\tcontent.requestFocus();\n\n\t\treturn dockable;\n\t}\n\n\tpublic void openCommentList() {\n\t\t// Check for tabs with the panel already open.\n\t\tDockablePath docPanePath = null;\n\t\tfor (DockablePath path : dockingManager.getBento().search().allDockables()) {\n\t\t\tDockable dockable = path.dockable();\n\t\t\tNode node = dockable.nodeProperty().get();\n\t\t\tif (node instanceof CommentListPane) {\n\t\t\t\tpath.leafContainer().selectDockable(dockable);\n\t\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\t\tnode.requestFocus();\n\t\t\t\t\tAnimations.animateNotice(node, 1000);\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t} else if (node instanceof DocumentationPane) {\n\t\t\t\tdocPanePath = path;\n\t\t\t}\n\t\t}\n\n\t\t// Not already open, gotta open a new one.\n\t\tDockContainerLeaf container = docPanePath != null ? docPanePath.leafContainer() : dockingManager.getPrimaryDockingContainer();\n\t\tCommentListPane content = commentListPaneProvider.get();\n\t\tDockable dockable = dockingManager.newTranslatableDockable(\"menu.analysis.list-comments\", CarbonIcons.CHAT, content);\n\t\tdockable.addCloseListener((_, _) -> commentListPaneProvider.destroy(content));\n\t\tcontainer.addDockable(dockable);\n\n\t\tcontainer.selectDockable(dockable);\n\t\tcontent.requestFocus();\n\t}\n\n\t/**\n\t * Display the workspace summary / current information.\n\t */\n\tpublic void openSummary() {\n\t\tWorkspaceInformationPane content = infoPaneProvider.get();\n\t\tDockable dockable = createDockable(dockingManager.getPrimaryDockingContainer(), getBinding(\"workspace.info\"),\n\t\t\t\td -> new FontIconView(CarbonIcons.INFORMATION), content);\n\t\tdockable.addCloseListener((_, _) -> infoPaneProvider.destroy(content));\n\t\tdockable.setContextMenuFactory(d -> {\n\t\t\tContextMenu menu = new ContextMenu();\n\t\t\taddCloseActions(menu, d);\n\t\t\treturn menu;\n\t\t});\n\t}\n\n\t/**\n\t * Prompts the user to select a package to move the given class into.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to move into a different package.\n\t */\n\tpublic void moveClass(@Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource,\n\t                      @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo info) {\n\t\tboolean isRootDirectory = isNullOrEmpty(info.getPackageName());\n\t\tItemTreeSelectionPopup.forPackageNames(bundle, packages -> {\n\t\t\t\t\t// We only allow a single package, so the list should contain just one item.\n\t\t\t\t\tString oldPackage = isRootDirectory ? \"\" : info.getPackageName() + \"/\";\n\t\t\t\t\tString newPackage = packages.get(0);\n\t\t\t\t\tif (Objects.equals(oldPackage, newPackage)) return;\n\t\t\t\t\tif (!newPackage.isEmpty()) newPackage += \"/\";\n\n\t\t\t\t\t// Create mapping for the class and any inner classes.\n\t\t\t\t\tString originalName = info.getName();\n\t\t\t\t\tString newName = replacePrefix(originalName, oldPackage, newPackage);\n\t\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\t\tfor (InnerClassInfo inner : info.getInnerClasses()) {\n\t\t\t\t\t\tif (inner.isExternalReference()) continue;\n\t\t\t\t\t\tString innerClassName = inner.getInnerClassName();\n\t\t\t\t\t\tmappings.addClass(innerClassName, newName + innerClassName.substring(originalName.length()));\n\t\t\t\t\t}\n\n\t\t\t\t\t// Apply the mappings.\n\t\t\t\t\tMappingApplier applier = mappingApplierService.inWorkspace(workspace);\n\t\t\t\t\tMappingResults results = applier.applyToPrimaryResource(mappings);\n\t\t\t\t\tresults.apply();\n\t\t\t\t})\n\t\t\t\t.withTitle(Lang.getBinding(\"dialog.title.move-class\"))\n\t\t\t\t.withTextMapping(name -> textService.getPackageTextProvider(workspace, resource, bundle, name).makeText())\n\t\t\t\t.withGraphicMapping(name -> iconService.getPackageIconProvider(workspace, resource, bundle, name).makeIcon())\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to select a directory to move the given file into.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tFile to move into a different directory.\n\t */\n\tpublic void moveFile(@Nonnull Workspace workspace,\n\t                     @Nonnull WorkspaceResource resource,\n\t                     @Nonnull FileBundle bundle,\n\t                     @Nonnull FileInfo info) {\n\t\tboolean isRootDirectory = isNullOrEmpty(info.getDirectoryName());\n\t\tItemTreeSelectionPopup.forDirectoryNames(bundle, chosenDirectories -> {\n\t\t\t\t\t// We only allow a single directory, so the list should contain just one item.\n\t\t\t\t\tif (chosenDirectories.isEmpty()) return;\n\t\t\t\t\tString oldDirectoryName = isRootDirectory ? \"\" : info.getDirectoryName() + \"/\";\n\t\t\t\t\tString newDirectoryName = chosenDirectories.get(0);\n\t\t\t\t\tif (Objects.equals(oldDirectoryName, newDirectoryName)) return;\n\t\t\t\t\tif (!newDirectoryName.isEmpty()) newDirectoryName += \"/\";\n\n\t\t\t\t\tString newName = replacePrefix(info.getName(), oldDirectoryName, newDirectoryName);\n\n\t\t\t\t\tbundle.remove(info.getName());\n\t\t\t\t\tbundle.put(info.toFileBuilder().withName(newName).build());\n\t\t\t\t}).withTitle(Lang.getBinding(\"dialog.title.move-file\"))\n\t\t\t\t.withTextMapping(name -> textService.getDirectoryTextProvider(workspace, resource, bundle, name).makeText())\n\t\t\t\t.withGraphicMapping(name -> iconService.getDirectoryIconProvider(workspace, resource, bundle, name).makeIcon())\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to select a package to move the given package into.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param packageName\n\t * \t\tPackage to go move into another package as a sub-package.\n\t */\n\tpublic void movePackage(@Nonnull Workspace workspace,\n\t                        @Nonnull WorkspaceResource resource,\n\t                        @Nonnull JvmClassBundle bundle,\n\t                        @Nonnull String packageName) {\n\t\tboolean isRootDirectory = packageName.isEmpty();\n\t\tItemTreeSelectionPopup.forPackageNames(bundle, chosenPackages -> {\n\t\t\t\t\tif (chosenPackages.isEmpty()) return;\n\t\t\t\t\tString newPackageName = chosenPackages.get(0);\n\t\t\t\t\tif (packageName.equals(newPackageName)) return;\n\n\t\t\t\t\t// Create mappings for classes in the given package.\n\t\t\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\t\t\tString newPrefix = (newPackageName.isEmpty() ? \"\" : newPackageName + \"/\") + shortenPath(packageName) + \"/\";\n\t\t\t\t\tif (isRootDirectory) {\n\t\t\t\t\t\t// Source is default package\n\t\t\t\t\t\tfor (JvmClassInfo info : bundle.values()) {\n\t\t\t\t\t\t\tString name = info.getName();\n\t\t\t\t\t\t\tif (name.indexOf('/') != -1) {\n\t\t\t\t\t\t\t\tmappings.addClass(name, newPrefix + name);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Source is another package\n\t\t\t\t\t\tString oldPrefix = packageName + \"/\";\n\t\t\t\t\t\tfor (JvmClassInfo info : bundle.values()) {\n\t\t\t\t\t\t\tString name = info.getName();\n\t\t\t\t\t\t\tif (newPackageName.isEmpty() && name.indexOf('/') == -1) {\n\t\t\t\t\t\t\t\t// Target is default package\n\t\t\t\t\t\t\t\tmappings.addClass(name, shortenPath(name));\n\t\t\t\t\t\t\t} else if (name.startsWith(oldPrefix)) {\n\t\t\t\t\t\t\t\t// Target is some package, replace prefix\n\t\t\t\t\t\t\t\tmappings.addClass(name, replacePrefix(name, oldPrefix, newPrefix));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Apply the mappings.\n\t\t\t\t\tMappingApplier applier = mappingApplierService.inWorkspace(workspace);\n\t\t\t\t\tMappingResults results = applier.applyToPrimaryResource(mappings);\n\t\t\t\t\tresults.apply();\n\t\t\t\t}).withTitle(Lang.getBinding(\"dialog.title.move-package\"))\n\t\t\t\t.withTextMapping(name -> textService.getPackageTextProvider(workspace, resource, bundle, name).makeText())\n\t\t\t\t.withGraphicMapping(name -> iconService.getPackageIconProvider(workspace, resource, bundle, name).makeIcon())\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to select a directory to move the given directory into.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tDirectory to go move into another directory as a sub-directory.\n\t */\n\tpublic void moveDirectory(@Nonnull Workspace workspace,\n\t                          @Nonnull WorkspaceResource resource,\n\t                          @Nonnull FileBundle bundle,\n\t                          @Nonnull String directoryName) {\n\t\tboolean isRootDirectory = directoryName.isEmpty();\n\t\tString localDirectoryName = shortenPath(directoryName);\n\t\tItemTreeSelectionPopup.forDirectoryNames(bundle, chosenDirectories -> {\n\t\t\t\t\tif (chosenDirectories.isEmpty()) return;\n\t\t\t\t\tString newDirectoryName = chosenDirectories.get(0);\n\t\t\t\t\tif (directoryName.equals(newDirectoryName)) return;\n\n\t\t\t\t\tString prefix = directoryName + \"/\";\n\t\t\t\t\tfor (FileInfo value : bundle.valuesAsCopy()) {\n\t\t\t\t\t\tString filePath = value.getName();\n\t\t\t\t\t\tString fileName = shortenPath(filePath);\n\n\t\t\t\t\t\t// Source is root directory, this file is also in the root directory\n\t\t\t\t\t\tif (isRootDirectory && filePath.indexOf('/') == -1) {\n\t\t\t\t\t\t\tString name = newDirectoryName + \"/\" + fileName;\n\t\t\t\t\t\t\tbundle.remove(filePath);\n\t\t\t\t\t\t\tbundle.put(value.toFileBuilder().withName(name).build());\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Source is another package, this file matches that package\n\t\t\t\t\t\t\tif (filePath.startsWith(prefix)) {\n\t\t\t\t\t\t\t\tString name;\n\t\t\t\t\t\t\t\tif (newDirectoryName.isEmpty()) {\n\t\t\t\t\t\t\t\t\t// Target is root directory\n\t\t\t\t\t\t\t\t\tname = localDirectoryName + \"/\" + fileName;\n\t\t\t\t\t\t\t\t} else if (filePath.startsWith(directoryName)) {\n\t\t\t\t\t\t\t\t\t// Target is another directory\n\t\t\t\t\t\t\t\t\tname = replacePrefix(filePath, directoryName, newDirectoryName + \"/\" + localDirectoryName);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbundle.remove(filePath);\n\t\t\t\t\t\t\t\tbundle.put(value.toFileBuilder().withName(name).build());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}).withTitle(Lang.getBinding(\"dialog.title.move-directory\"))\n\t\t\t\t.withTextMapping(name -> textService.getDirectoryTextProvider(workspace, resource, bundle, name).makeText())\n\t\t\t\t.withGraphicMapping(name -> iconService.getDirectoryIconProvider(workspace, resource, bundle, name).makeIcon())\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to rename whatever sort of content is contained within the given path.\n\t *\n\t * @param path\n\t * \t\tItem to rename. Can be a number of values.\n\t */\n\tpublic void rename(@Nonnull PathNode<?> path) {\n\t\t// Handle renaming based on the different resolved content type.\n\t\tif (path instanceof ClassPathNode classPath)\n\t\t\tUnchecked.run(() -> renameClass(classPath));\n\t\telse if (path instanceof ClassMemberPathNode memberPathNode)\n\t\t\tif (memberPathNode.isField())\n\t\t\t\tUnchecked.run(() -> renameField(memberPathNode));\n\t\t\telse\n\t\t\t\tUnchecked.run(() -> renameMethod(memberPathNode));\n\t\telse if (path instanceof FilePathNode filePath)\n\t\t\tUnchecked.run(() -> renameFile(filePath));\n\t\telse if (path instanceof DirectoryPathNode directoryPath)\n\t\t\tUnchecked.run(() -> renamePackageOrDirectory(directoryPath));\n\t}\n\n\t/**\n\t * Prompts the user to rename the given class.\n\t *\n\t * @param path\n\t * \t\tPath to class.\n\t *\n\t * @throws IncompletePathException\n\t * \t\tWhen the path is missing parent elements.\n\t */\n\tpublic void renameClass(@Nonnull ClassPathNode path) throws IncompletePathException {\n\t\tWorkspace workspace = path.getValueOfType(Workspace.class);\n\t\tWorkspaceResource resource = path.getValueOfType(WorkspaceResource.class);\n\t\tClassBundle<?> bundle = path.getValueOfType(ClassBundle.class);\n\t\tClassInfo info = path.getValue();\n\t\tif (workspace == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for class '{}', missing workspace in path\", info.getName());\n\t\t\tthrow new IncompletePathException(Workspace.class);\n\t\t}\n\t\tif (resource == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for class '{}', missing resource in path\", info.getName());\n\t\t\tthrow new IncompletePathException(WorkspaceResource.class);\n\t\t}\n\t\tif (bundle == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for class '{}', missing bundle in path\", info.getName());\n\t\t\tthrow new IncompletePathException(ClassBundle.class);\n\t\t}\n\t\trenameClass(workspace, resource, bundle, info);\n\t}\n\n\t/**\n\t * Prompts the user to rename the given class.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to rename.\n\t */\n\tpublic void renameClass(@Nonnull Workspace workspace,\n\t                        @Nonnull WorkspaceResource resource,\n\t                        @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                        @Nonnull ClassInfo info) {\n\t\tString originalName = info.getName();\n\t\tConsumer<String> renameTask = newName -> {\n\t\t\t// Create mapping for the class and any inner classes.\n\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\tmappings.addClass(originalName, newName);\n\t\t\tfor (InnerClassInfo inner : info.getInnerClasses()) {\n\t\t\t\tif (inner.isExternalReference()) continue;\n\t\t\t\tString innerClassName = inner.getInnerClassName();\n\t\t\t\tmappings.addClass(innerClassName, newName + innerClassName.substring(originalName.length()));\n\t\t\t}\n\n\t\t\t// Apply the mappings.\n\t\t\tMappingApplier applier = mappingApplierService.inWorkspace(workspace);\n\t\t\tMappingResults results = applier.applyToPrimaryResource(mappings);\n\t\t\tresults.apply();\n\t\t};\n\t\tnew NamePopup(renameTask)\n\t\t\t\t.withInitialPathName(originalName)\n\t\t\t\t.forClassRename(bundle)\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to rename the given field.\n\t *\n\t * @param path\n\t * \t\tPath to field.\n\t *\n\t * @throws IncompletePathException\n\t * \t\tWhen the path is missing parent elements.\n\t */\n\tpublic void renameField(@Nonnull ClassMemberPathNode path) throws IncompletePathException {\n\t\tWorkspace workspace = path.getValueOfType(Workspace.class);\n\t\tWorkspaceResource resource = path.getValueOfType(WorkspaceResource.class);\n\t\tClassBundle<?> bundle = path.getValueOfType(ClassBundle.class);\n\t\tClassInfo declaringClass = path.getValueOfType(ClassInfo.class);\n\t\tFieldMember fieldMember = (FieldMember) path.getValue();\n\t\tif (workspace == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for field '{}', missing workspace in path\", fieldMember.getName());\n\t\t\tthrow new IncompletePathException(Workspace.class);\n\t\t}\n\t\tif (resource == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for field '{}', missing resource in path\", fieldMember.getName());\n\t\t\tthrow new IncompletePathException(WorkspaceResource.class);\n\t\t}\n\t\tif (bundle == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for field '{}', missing bundle in path\", fieldMember.getName());\n\t\t\tthrow new IncompletePathException(ClassBundle.class);\n\t\t}\n\t\tif (declaringClass == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for field '{}', missing class in path\", fieldMember.getName());\n\t\t\tthrow new IncompletePathException(ClassBundle.class);\n\t\t}\n\t\trenameField(workspace, resource, bundle, declaringClass, fieldMember);\n\t}\n\n\t/**\n\t * Prompts the user to rename the given field.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tClass containing the field.\n\t * @param field\n\t * \t\tField to rename.\n\t */\n\tpublic void renameField(@Nonnull Workspace workspace,\n\t                        @Nonnull WorkspaceResource resource,\n\t                        @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                        @Nonnull ClassInfo declaringClass,\n\t                        @Nonnull FieldMember field) {\n\t\tString originalName = field.getName();\n\t\tConsumer<String> renameTask = newName -> {\n\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\tmappings.addField(declaringClass.getName(), field.getDescriptor(), originalName, newName);\n\n\t\t\t// Apply the mappings.\n\t\t\tMappingApplier applier = mappingApplierService.inWorkspace(workspace);\n\t\t\tMappingResults results = applier.applyToPrimaryResource(mappings);\n\t\t\tresults.apply();\n\t\t};\n\t\tnew NamePopup(renameTask)\n\t\t\t\t.withInitialName(originalName)\n\t\t\t\t.forFieldRename(declaringClass, field)\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to rename the given method.\n\t *\n\t * @param path\n\t * \t\tPath to method.\n\t *\n\t * @throws IncompletePathException\n\t * \t\tWhen the path is missing parent elements.\n\t */\n\tpublic void renameMethod(@Nonnull ClassMemberPathNode path) throws IncompletePathException {\n\t\tWorkspace workspace = path.getValueOfType(Workspace.class);\n\t\tWorkspaceResource resource = path.getValueOfType(WorkspaceResource.class);\n\t\tClassBundle<?> bundle = path.getValueOfType(ClassBundle.class);\n\t\tClassInfo declaringClass = path.getValueOfType(ClassInfo.class);\n\t\tMethodMember methodMember = (MethodMember) path.getValue();\n\t\tif (workspace == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for method '{}', missing workspace in path\", methodMember.getName());\n\t\t\tthrow new IncompletePathException(Workspace.class);\n\t\t}\n\t\tif (resource == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for method '{}', missing resource in path\", methodMember.getName());\n\t\t\tthrow new IncompletePathException(WorkspaceResource.class);\n\t\t}\n\t\tif (bundle == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for method '{}', missing bundle in path\", methodMember.getName());\n\t\t\tthrow new IncompletePathException(ClassBundle.class);\n\t\t}\n\t\tif (declaringClass == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for method '{}', missing class in path\", methodMember.getName());\n\t\t\tthrow new IncompletePathException(ClassBundle.class);\n\t\t}\n\t\trenameMethod(workspace, resource, bundle, declaringClass, methodMember);\n\t}\n\n\t/**\n\t * Prompts the user to rename the given method.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tClass containing the method.\n\t * @param method\n\t * \t\tMethod to rename.\n\t */\n\tpublic void renameMethod(@Nonnull Workspace workspace,\n\t                         @Nonnull WorkspaceResource resource,\n\t                         @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                         @Nonnull ClassInfo declaringClass,\n\t                         @Nonnull MethodMember method) {\n\t\tString originalName = method.getName();\n\t\tConsumer<String> renameTask = newName -> {\n\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\tmappings.addMethod(declaringClass.getName(), method.getDescriptor(), originalName, newName);\n\n\t\t\t// Apply the mappings.\n\t\t\tMappingApplier applier = mappingApplierService.inWorkspace(workspace);\n\t\t\tMappingResults results = applier.applyToPrimaryResource(mappings);\n\t\t\tresults.apply();\n\t\t};\n\t\tnew NamePopup(renameTask)\n\t\t\t\t.withInitialName(originalName)\n\t\t\t\t.forMethodRename(declaringClass, method)\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to rename the given field.\n\t *\n\t * @param path\n\t * \t\tPath to file.\n\t *\n\t * @throws IncompletePathException\n\t * \t\tWhen the path is missing parent elements.\n\t */\n\tpublic void renameFile(@Nonnull FilePathNode path) throws IncompletePathException {\n\t\tWorkspace workspace = path.getValueOfType(Workspace.class);\n\t\tWorkspaceResource resource = path.getValueOfType(WorkspaceResource.class);\n\t\tFileBundle bundle = path.getValueOfType(FileBundle.class);\n\t\tFileInfo info = path.getValue();\n\t\tif (workspace == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for file '{}', missing workspace in path\", info.getName());\n\t\t\tthrow new IncompletePathException(Workspace.class);\n\t\t}\n\t\tif (resource == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for file '{}', missing resource in path\", info.getName());\n\t\t\tthrow new IncompletePathException(WorkspaceResource.class);\n\t\t}\n\t\tif (bundle == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for file '{}', missing bundle in path\", info.getName());\n\t\t\tthrow new IncompletePathException(ClassBundle.class);\n\t\t}\n\t\trenameFile(workspace, resource, bundle, info);\n\t}\n\n\t/**\n\t * Prompts the user to rename the given file.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tFile to rename.\n\t */\n\tpublic void renameFile(@Nonnull Workspace workspace,\n\t                       @Nonnull WorkspaceResource resource,\n\t                       @Nonnull FileBundle bundle,\n\t                       @Nonnull FileInfo info) {\n\t\tString name = info.getName();\n\t\tnew NamePopup(newFileName -> {\n\t\t\tif (name.equals(newFileName)) return;\n\t\t\tbundle.remove(name);\n\t\t\tbundle.put(info.toFileBuilder().withName(newFileName).build());\n\t\t}).withInitialPathName(name)\n\t\t\t\t.forFileRename(bundle)\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to rename the given directory.\n\t *\n\t * @param path\n\t * \t\tPath to directory.\n\t *\n\t * @throws IncompletePathException\n\t * \t\tWhen the path is missing parent elements.\n\t */\n\tpublic void renamePackageOrDirectory(@Nonnull DirectoryPathNode path) throws IncompletePathException {\n\t\tString directoryName = path.getValue();\n\n\t\tWorkspace workspace = path.getValueOfType(Workspace.class);\n\t\tWorkspaceResource resource = path.getValueOfType(WorkspaceResource.class);\n\t\tBundle<?> bundle = path.getValueOfType(Bundle.class);\n\t\tif (workspace == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for directory '{}', missing workspace in path\", directoryName);\n\t\t\tthrow new IncompletePathException(Workspace.class);\n\t\t}\n\t\tif (resource == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for directory '{}', missing resource in path\", directoryName);\n\t\t\tthrow new IncompletePathException(WorkspaceResource.class);\n\t\t}\n\t\tif (bundle == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for directory '{}', missing bundle in path\", directoryName);\n\t\t\tthrow new IncompletePathException(ClassBundle.class);\n\t\t}\n\n\t\tif (bundle instanceof FileBundle fileBundle)\n\t\t\trenameDirectory(workspace, resource, fileBundle, directoryName);\n\t\telse if (bundle instanceof JvmClassBundle classBundle)\n\t\t\trenamePackage(workspace, resource, classBundle, directoryName);\n\t}\n\n\t/**\n\t * Prompts the user to rename the given directory.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tName of directory to rename.\n\t */\n\tpublic void renameDirectory(@Nonnull Workspace workspace,\n\t                            @Nonnull WorkspaceResource resource,\n\t                            @Nonnull FileBundle bundle,\n\t                            @Nonnull String directoryName) {\n\t\tboolean isRootDirectory = directoryName.isEmpty();\n\t\tnew NamePopup(newDirectoryName -> {\n\t\t\tif (directoryName.equals(newDirectoryName)) return;\n\n\t\t\tString prefix = directoryName + \"/\";\n\t\t\tfor (FileInfo value : bundle.valuesAsCopy()) {\n\t\t\t\tString filePath = value.getName();\n\t\t\t\tString fileName = shortenPath(filePath);\n\n\t\t\t\t// Source is root directory, this file is also in the root directory\n\t\t\t\tif (isRootDirectory && filePath.indexOf('/') == -1) {\n\t\t\t\t\tString name = newDirectoryName + \"/\" + fileName;\n\t\t\t\t\tbundle.remove(filePath);\n\t\t\t\t\tbundle.put(value.toFileBuilder().withName(name).build());\n\t\t\t\t} else {\n\t\t\t\t\t// Source is another package, this file matches that package\n\t\t\t\t\tif (filePath.startsWith(prefix)) {\n\t\t\t\t\t\tString name;\n\t\t\t\t\t\tif (newDirectoryName.isEmpty()) {\n\t\t\t\t\t\t\t// Target is root directory\n\t\t\t\t\t\t\tname = fileName;\n\t\t\t\t\t\t} else if (filePath.startsWith(directoryName)) {\n\t\t\t\t\t\t\t// Target is another directory\n\t\t\t\t\t\t\tname = replacePrefix(filePath, directoryName, newDirectoryName);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbundle.remove(filePath);\n\t\t\t\t\t\tbundle.put(value.toFileBuilder().withName(name).build());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}).withInitialPathName(directoryName)\n\t\t\t\t.forDirectoryRename(bundle)\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to give a new name for the copied package.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param packageName\n\t * \t\tName of directory to copy.\n\t */\n\tpublic void renamePackage(@Nonnull Workspace workspace,\n\t                          @Nonnull WorkspaceResource resource,\n\t                          @Nonnull JvmClassBundle bundle,\n\t                          @Nonnull String packageName) {\n\t\tboolean isRootDirectory = packageName.isEmpty();\n\t\tnew NamePopup(newPackageName -> {\n\t\t\t// Create mappings.\n\t\t\tString oldPrefix = isRootDirectory ? \"\" : packageName + \"/\";\n\t\t\tString newPrefix = newPackageName + \"/\";\n\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\tfor (JvmClassInfo info : bundle.valuesAsCopy()) {\n\t\t\t\tString className = info.getName();\n\t\t\t\tif (isRootDirectory) {\n\t\t\t\t\t// Source is the default package\n\t\t\t\t\tif (className.indexOf('/') == -1)\n\t\t\t\t\t\t// Class is in the default package\n\t\t\t\t\t\tmappings.addClass(className, newPackageName + '/' + className);\n\t\t\t\t} else if (className.startsWith(oldPrefix))\n\t\t\t\t\t// Class starts with the package prefix\n\t\t\t\t\tmappings.addClass(className, replacePrefix(className, oldPrefix, newPrefix));\n\t\t\t}\n\n\t\t\t// Apply mappings to create copies of the affected classes, using the provided name.\n\t\t\t// Then dump the mapped classes into bundle.\n\t\t\tMappingApplier applier = mappingApplierService.inWorkspace(workspace);\n\t\t\tMappingResults results = applier.applyToPrimaryResource(mappings);\n\t\t\tresults.apply();\n\t\t}).withInitialPathName(packageName)\n\t\t\t\t.forPackageRename(bundle)\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to give a name for the new class.\n\t * Creates the class in the workspace and then opens it.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param packageName\n\t * \t\tPackage to place the class in initially.\n\t */\n\tpublic void newClass(@Nonnull Workspace workspace,\n\t                     @Nonnull WorkspaceResource resource,\n\t                     @Nonnull JvmClassBundle bundle,\n\t                     @Nonnull String packageName) {\n\t\tnew NamePopup(name -> {\n\t\t\t// TODO: We probably also want to configure the version\n\t\t\t//  - There are ways the user can do this themselves atm, but it would be nice to offer providing it\n\t\t\tClassWriter cw = new ClassWriter(0);\n\t\t\tcw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name, null, \"java/lang/Object\", null);\n\t\t\tcw.visitEnd();\n\n\t\t\tJvmClassInfo info = new JvmClassInfoBuilder(cw.toByteArray()).build();\n\t\t\tbundle.put(info);\n\n\t\t\tFxThreadUtil.run(() -> gotoDeclaration(workspace, resource, bundle, info));\n\t\t}).withInitialName(packageName.isBlank() ? \"ClassName\" : packageName + \"/ClassName\")\n\t\t\t\t.forClassCreation(bundle)\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to give a new name for the copied class.\n\t * Inner classes also get copied.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to copy.\n\t */\n\tpublic void copyClass(@Nonnull Workspace workspace,\n\t                      @Nonnull WorkspaceResource resource,\n\t                      @Nonnull JvmClassBundle bundle,\n\t                      @Nonnull JvmClassInfo info) {\n\t\tString originalName = info.getName();\n\t\tConsumer<String> copyTask = newName -> {\n\t\t\t// Create mappings.\n\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\tmappings.addClass(originalName, newName);\n\n\t\t\t// Collect inner classes, we need to copy these as well.\n\t\t\tList<JvmClassInfo> classesToCopy = new ArrayList<>();\n\t\t\tclassesToCopy.add(info);\n\t\t\tfor (InnerClassInfo inner : info.getInnerClasses()) {\n\t\t\t\tif (inner.isExternalReference()) continue;\n\t\t\t\tString innerClassName = inner.getInnerClassName();\n\t\t\t\tmappings.addClass(innerClassName, newName + innerClassName.substring(originalName.length()));\n\t\t\t\tJvmClassInfo innerClassInfo = bundle.get(innerClassName);\n\t\t\t\tif (innerClassInfo != null)\n\t\t\t\t\tclassesToCopy.add(innerClassInfo);\n\t\t\t\telse\n\t\t\t\t\tlogger.warn(\"Could not find inner class for copy-operation: {}\", EscapeUtil.escapeStandard(innerClassName));\n\t\t\t}\n\n\t\t\t// Apply mappings to create copies of the affected classes, using the provided name.\n\t\t\t// Then dump the mapped classes into bundle.\n\t\t\tMappingApplier applier = mappingApplierService.inWorkspace(workspace);\n\t\t\tMappingResults results = applier.applyToClasses(mappings, resource, bundle, classesToCopy);\n\t\t\tfor (ClassPathNode mappedClassPath : results.getPostMappingPaths().values()) {\n\t\t\t\tJvmClassInfo mappedClass = mappedClassPath.getValue().asJvmClass();\n\t\t\t\tbundle.put(mappedClass);\n\t\t\t}\n\t\t};\n\t\tnew NamePopup(copyTask)\n\t\t\t\t.withInitialPathName(originalName)\n\t\t\t\t.forClassCopy(bundle)\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to give a new name for the copied member.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tContaining class.\n\t * @param member\n\t * \t\tmember to copy.\n\t */\n\tpublic void copyMember(@Nonnull Workspace workspace,\n\t                       @Nonnull WorkspaceResource resource,\n\t                       @Nonnull JvmClassBundle bundle,\n\t                       @Nonnull JvmClassInfo declaringClass,\n\t                       @Nonnull ClassMember member) {\n\t\tString originalName = member.getName();\n\t\tConsumer<String> copyTask = newName -> {\n\t\t\tClassReader reader = declaringClass.getClassReader();\n\t\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\t\tMemberCopyingVisitor copier = new MemberCopyingVisitor(writer, member, newName);\n\t\t\treader.accept(copier, declaringClass.getClassReaderFlags());\n\t\t\tbundle.put(new JvmClassInfoBuilder(writer.toByteArray()).build());\n\t\t};\n\t\tNamePopup popup = new NamePopup(copyTask).withInitialName(originalName);\n\t\tif (member.isField())\n\t\t\tpopup.forFieldCopy(declaringClass, member);\n\t\telse if (member.isMethod())\n\t\t\tpopup.forMethodCopy(declaringClass, member);\n\t\tpopup.show();\n\t}\n\n\t/**\n\t * Prompts the user to give a new name for the copied file.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tFile to copy.\n\t */\n\tpublic void copyFile(@Nonnull Workspace workspace,\n\t                     @Nonnull WorkspaceResource resource,\n\t                     @Nonnull FileBundle bundle,\n\t                     @Nonnull FileInfo info) {\n\t\tnew NamePopup(newName -> {\n\t\t\tif (info.getName().equals(newName)) return;\n\t\t\tbundle.put(info.toFileBuilder().withName(newName).build());\n\t\t}).withInitialPathName(info.getName())\n\t\t\t\t.forDirectoryCopy(bundle)\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to give a new name for the copied directory.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tName of directory to copy.\n\t */\n\tpublic void copyDirectory(@Nonnull Workspace workspace,\n\t                          @Nonnull WorkspaceResource resource,\n\t                          @Nonnull FileBundle bundle,\n\t                          @Nonnull String directoryName) {\n\t\tboolean isRootDirectory = directoryName.isEmpty();\n\t\tnew NamePopup(newDirectoryName -> {\n\t\t\tif (directoryName.equals(newDirectoryName)) return;\n\t\t\tfor (FileInfo value : bundle.valuesAsCopy()) {\n\t\t\t\tString path = value.getName();\n\t\t\t\tif (isRootDirectory) {\n\t\t\t\t\tif (path.indexOf('/') == -1) {\n\t\t\t\t\t\tString name = newDirectoryName + \"/\" + path;\n\t\t\t\t\t\tbundle.put(value.toFileBuilder().withName(name).build());\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (path.startsWith(directoryName)) {\n\t\t\t\t\t\tString name = replacePrefix(path, directoryName, newDirectoryName);\n\t\t\t\t\t\tbundle.put(value.toFileBuilder().withName(name).build());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}).withInitialPathName(directoryName)\n\t\t\t\t.forDirectoryCopy(bundle)\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to give a new name for the copied package.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param packageName\n\t * \t\tName of directory to copy.\n\t */\n\tpublic void copyPackage(@Nonnull Workspace workspace,\n\t                        @Nonnull WorkspaceResource resource,\n\t                        @Nonnull JvmClassBundle bundle,\n\t                        @Nonnull String packageName) {\n\t\tboolean isRootDirectory = packageName.isEmpty();\n\t\tnew NamePopup(newPackageName -> {\n\t\t\t// Create mappings.\n\t\t\tString oldPrefix = isRootDirectory ? \"\" : packageName + \"/\";\n\t\t\tString newPrefix = newPackageName + \"/\";\n\t\t\tIntermediateMappings mappings = new IntermediateMappings();\n\t\t\tList<JvmClassInfo> classesToCopy = new ArrayList<>();\n\t\t\tfor (JvmClassInfo info : bundle.valuesAsCopy()) {\n\t\t\t\tString className = info.getName();\n\t\t\t\tif (isRootDirectory) {\n\t\t\t\t\tif (className.indexOf('/') == -1) {\n\t\t\t\t\t\tmappings.addClass(className, newPrefix + className);\n\t\t\t\t\t\tclassesToCopy.add(info);\n\t\t\t\t\t}\n\t\t\t\t} else if (className.startsWith(oldPrefix)) {\n\t\t\t\t\tmappings.addClass(className, replacePrefix(className, oldPrefix, newPrefix));\n\t\t\t\t\tclassesToCopy.add(info);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Apply mappings to create copies of the affected classes, using the provided name.\n\t\t\t// Then dump the mapped classes into bundle.\n\t\t\tMappingApplier applier = mappingApplierService.inWorkspace(workspace);\n\t\t\tMappingResults results = applier.applyToClasses(mappings, resource, bundle, classesToCopy);\n\t\t\tfor (ClassPathNode mappedClassPath : results.getPostMappingPaths().values()) {\n\t\t\t\tJvmClassInfo mappedClass = mappedClassPath.getValue().asJvmClass();\n\t\t\t\tbundle.put(mappedClass);\n\t\t\t}\n\t\t}).withInitialPathName(packageName)\n\t\t\t\t.forPackageCopy(bundle)\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Opens an {@link AssemblerPane} for a class, field, or method at the given path.\n\t *\n\t * @param path\n\t * \t\tPath containing a class, field, or method to open.\n\t *\n\t * @return Navigable content with an assembler for the given class, field, or method.\n\t *\n\t * @throws IncompletePathException\n\t * \t\tWhen the path is missing parent elements.\n\t */\n\t@Nonnull\n\tpublic Navigable openAssembler(@Nonnull PathNode<?> path) throws IncompletePathException {\n\t\tWorkspace workspace = path.getValueOfType(Workspace.class);\n\t\tWorkspaceResource resource = path.getValueOfType(WorkspaceResource.class);\n\t\tClassBundle<?> bundle = path.getValueOfType(ClassBundle.class);\n\t\tClassInfo info = path.getValueOfType(ClassInfo.class);\n\t\tif (info == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes, missing class in path\");\n\t\t\tthrow new IncompletePathException(ClassInfo.class);\n\t\t}\n\t\tif (workspace == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for class '{}', missing workspace in path\", info.getName());\n\t\t\tthrow new IncompletePathException(Workspace.class);\n\t\t}\n\t\tif (resource == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for class '{}', missing resource in path\", info.getName());\n\t\t\tthrow new IncompletePathException(WorkspaceResource.class);\n\t\t}\n\t\tif (bundle == null) {\n\t\t\tlogger.error(\"Cannot resolve required path nodes for class '{}', missing bundle in path\", info.getName());\n\t\t\tthrow new IncompletePathException(ClassBundle.class);\n\t\t}\n\n\t\treturn createContent(() -> {\n\t\t\t// Create text/graphic for the tab to create.\n\t\t\tString name = \"?\";\n\t\t\tif (path instanceof ClassPathNode classPathNode)\n\t\t\t\tname = StringUtil.shortenPath(classPathNode.getValue().getName());\n\t\t\telse if (path instanceof ClassMemberPathNode classMemberPathNode)\n\t\t\t\tname = classMemberPathNode.getValue().getName();\n\t\t\tString title = \"Assembler: \" + EscapeUtil.escapeStandard(StringUtil.cutOff(name, 60));\n\t\t\tDockableIconFactory graphicFactory = d -> new FontIconView(CarbonIcons.CODE);\n\n\t\t\t// Create content for the tab.\n\t\t\tAssemblerPane content = assemblerPaneProvider.get();\n\t\t\tcontent.onUpdatePath(path);\n\n\t\t\t// Build the tab.\n\t\t\tDockable dockable = createDockable(dockingManager.getPrimaryDockingContainer(), title, graphicFactory, content);\n\t\t\tdockable.addCloseListener((_, _) -> assemblerPaneProvider.destroy(content));\n\n\t\t\t// Class assemblers should have full context menus of a class.\n\t\t\t// Member assemblers should the basic close actions.\n\t\t\tif (path instanceof ClassPathNode)\n\t\t\t\tsetupInfoContextMenu(info, content, dockable);\n\t\t\telse {\n\t\t\t\tdockable.setContextMenuFactory(d -> {\n\t\t\t\t\tContextMenu menu = new ContextMenu();\n\t\t\t\t\taddCloseActions(menu, d);\n\t\t\t\t\treturn menu;\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn dockable;\n\t\t});\n\t}\n\n\n\t/**\n\t * Exports a class, prompting the user to select a location to save the class to.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tClass declaring the method\n\t * @param method\n\t * \t\tMethod to show the incoming/outgoing calls of.\n\t *\n\t * @return Navigable reference to the call graph pane.\n\t */\n\t@Nonnull\n\tpublic Navigable openMethodCallGraph(@Nonnull Workspace workspace,\n\t                                     @Nonnull WorkspaceResource resource,\n\t                                     @Nonnull JvmClassBundle bundle,\n\t                                     @Nonnull JvmClassInfo declaringClass,\n\t                                     @Nonnull MethodMember method) {\n\t\treturn createContent(() -> {\n\t\t\t// Create text/graphic for the tab to create.\n\t\t\tString title = Lang.get(\"menu.view.methodcallgraph\") + \": \" + method.getName();\n\t\t\tDockableIconFactory graphicFactory = d -> new FontIconView(CarbonIcons.FLOW);\n\n\t\t\t// Create content for the tab.\n\t\t\tMethodCallGraphsPane content = callGraphsPaneProvider.get();\n\t\t\tcontent.onUpdatePath(PathNodes.memberPath(workspace, resource, bundle, declaringClass, method));\n\n\t\t\t// Build the tab.\n\t\t\tDockable dockable = createDockable(dockingManager.getPrimaryDockingContainer(), title, graphicFactory, content);\n\t\t\tdockable.addCloseListener((_, _) -> callGraphsPaneProvider.destroy(content));\n\t\t\treturn dockable;\n\t\t});\n\t}\n\n\t/**\n\t * Exports a class, prompting the user to select a location to save the class to.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to export.\n\t */\n\tpublic void exportClass(@Nonnull Workspace workspace,\n\t                        @Nonnull WorkspaceResource resource,\n\t                        @Nonnull JvmClassBundle bundle,\n\t                        @Nonnull JvmClassInfo info) {\n\t\tpathExportingManager.export(info);\n\t}\n\n\t/**\n\t * Exports a file, prompting the user to select a location to save the file to.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tFile to export.\n\t */\n\tpublic void exportClass(@Nonnull Workspace workspace,\n\t                        @Nonnull WorkspaceResource resource,\n\t                        @Nonnull FileBundle bundle,\n\t                        @Nonnull FileInfo info) {\n\t\tpathExportingManager.export(info);\n\t}\n\n\t/**\n\t * Exports a package, prompting the user to select a location to save the file to.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param packageName\n\t * \t\tName of package to export.\n\t */\n\tpublic void exportPackage(@Nonnull Workspace workspace,\n\t                          @Nonnull WorkspaceResource resource,\n\t                          @Nonnull JvmClassBundle bundle,\n\t                          @Nullable String packageName) {\n\t\tJvmClassBundle bundleCopy = new BasicJvmClassBundle();\n\t\tbundle.valuesAsCopy().forEach(cls -> {\n\t\t\tif (packageName == null && cls.getPackageName() == null)\n\t\t\t\tbundleCopy.put(cls);\n\t\t\telse if (cls.getName().startsWith(packageName + \"/\"))\n\t\t\t\tbundleCopy.put(cls);\n\t\t});\n\t\tWorkspaceResource resourceCopy = new WorkspaceResourceBuilder().withJvmClassBundle(bundleCopy).build();\n\t\tWorkspace workspaceCopy = new BasicWorkspace(resourceCopy);\n\t\tpathExportingManager.export(workspaceCopy, \"package\", false);\n\t}\n\n\t/**\n\t * Exports all classes in a bundle, prompting the user to select a location to save the file to.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tBundle with contents to export.\n\t */\n\tpublic void exportClasses(@Nonnull Workspace workspace,\n\t                          @Nonnull WorkspaceResource resource,\n\t                          @Nonnull JvmClassBundle bundle) {\n\t\tBasicJvmClassBundle bundleCopy = new BasicJvmClassBundle();\n\t\tbundle.valuesAsCopy().forEach(bundleCopy::initialPut);\n\t\tWorkspaceResource resourceCopy = new WorkspaceResourceBuilder().withJvmClassBundle(bundleCopy).build();\n\t\tWorkspace workspaceCopy = new BasicWorkspace(resourceCopy);\n\t\tpathExportingManager.export(workspaceCopy, \"bundle\", false);\n\t}\n\n\t/**\n\t * Exports a directory, prompting the user to select a location to save the file to.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tName of directory to export.\n\t */\n\tpublic void exportDirectory(@Nonnull Workspace workspace,\n\t                            @Nonnull WorkspaceResource resource,\n\t                            @Nonnull FileBundle bundle,\n\t                            @Nullable String directoryName) {\n\t\tFileBundle bundleCopy = new BasicFileBundle();\n\t\tbundle.valuesAsCopy().forEach(cls -> {\n\t\t\tif (directoryName == null && cls.getDirectoryName() == null)\n\t\t\t\tbundleCopy.put(cls);\n\t\t\telse if (cls.getName().startsWith(directoryName + \"/\"))\n\t\t\t\tbundleCopy.put(cls);\n\t\t});\n\t\tWorkspaceResource resourceCopy = new WorkspaceResourceBuilder().withFileBundle(bundleCopy).build();\n\t\tWorkspace workspaceCopy = new BasicWorkspace(resourceCopy);\n\t\tpathExportingManager.export(workspaceCopy, \"directory\", false);\n\t}\n\n\t/**\n\t * Exports all files in a bundle, prompting the user to select a location to save the file to.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tBundle with contents to export.\n\t */\n\tpublic void exportFiles(@Nonnull Workspace workspace,\n\t                        @Nonnull WorkspaceResource resource,\n\t                        @Nonnull FileBundle bundle) {\n\t\tBasicFileBundle bundleCopy = new BasicFileBundle();\n\t\tbundle.valuesAsCopy().forEach(bundleCopy::initialPut);\n\t\tWorkspaceResource resourceCopy = new WorkspaceResourceBuilder().withFileBundle(bundleCopy).build();\n\t\tWorkspace workspaceCopy = new BasicWorkspace(resourceCopy);\n\t\tpathExportingManager.export(workspaceCopy, \"bundle\", false);\n\t}\n\n\t/**\n\t * Prompts the user <i>(if configured, otherwise prompt is skipped)</i> to delete the class.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to delete.\n\t */\n\tpublic void deleteClass(@Nonnull Workspace workspace,\n\t                        @Nonnull WorkspaceResource resource,\n\t                        @Nonnull JvmClassBundle bundle,\n\t                        @Nonnull JvmClassInfo info) {\n\t\t// TODO: Ask user if they are sure\n\t\t//  - Use config to check if \"are you sure\" prompts should be bypassed\n\t\tbundle.remove(info.getName());\n\t}\n\n\t/**\n\t * Prompts the user <i>(if configured, otherwise prompt is skipped)</i> to delete the file.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tFile to delete.\n\t */\n\tpublic void deleteFile(@Nonnull Workspace workspace,\n\t                       @Nonnull WorkspaceResource resource,\n\t                       @Nonnull FileBundle bundle,\n\t                       @Nonnull FileInfo info) {\n\t\t// TODO: Ask user if they are sure\n\t\t//  - Use config to check if \"are you sure\" prompts should be bypassed\n\t\tbundle.remove(info.getName());\n\t}\n\n\t/**\n\t * Prompts the user <i>(if configured, otherwise prompt is skipped)</i> to delete the package.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param packageName\n\t * \t\tName of package to delete.\n\t */\n\tpublic void deletePackage(@Nonnull Workspace workspace,\n\t                          @Nonnull WorkspaceResource resource,\n\t                          @Nonnull ClassBundle<?> bundle,\n\t                          @Nonnull String packageName) {\n\t\t// TODO: Ask user if they are sure\n\t\t//  - Use config to check if \"are you sure\" prompts should be bypassed\n\t\tboolean isRootDirectory = packageName.isEmpty();\n\t\tString packageNamePrefix = packageName + \"/\";\n\t\tfor (ClassInfo value : bundle.valuesAsCopy()) {\n\t\t\tString path = value.getName();\n\t\t\tif (isRootDirectory) {\n\t\t\t\t// Source is in the default package, and the current class is also in the default package.\n\t\t\t\tif (path.indexOf('/') == -1) {\n\t\t\t\t\tbundle.remove(path);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Source is in a package, and the current class is in the same package.\n\t\t\t\tif (path.startsWith(packageNamePrefix)) {\n\t\t\t\t\tbundle.remove(path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Prompts the user <i>(if configured, otherwise prompt is skipped)</i> to delete the directory.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tName of directory to delete.\n\t */\n\tpublic void deleteDirectory(@Nonnull Workspace workspace,\n\t                            @Nonnull WorkspaceResource resource,\n\t                            @Nonnull FileBundle bundle,\n\t                            @Nonnull String directoryName) {\n\t\t// TODO: Ask user if they are sure\n\t\t//  - Use config to check if \"are you sure\" prompts should be bypassed\n\t\tboolean isRootDirectory = directoryName.isEmpty();\n\t\tString directoryNamePrefix = directoryName + \"/\";\n\t\tfor (FileInfo value : bundle.valuesAsCopy()) {\n\t\t\tString path = value.getName();\n\t\t\tif (isRootDirectory) {\n\t\t\t\t// Source is in the root directory, and the current file is also in the root directory.\n\t\t\t\tif (path.indexOf('/') == -1) {\n\t\t\t\t\tbundle.remove(path);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Source is in a directory, and the current file is in the same directory.\n\t\t\t\tif (path.startsWith(directoryNamePrefix)) {\n\t\t\t\t\tbundle.remove(path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Prompts the user to select fields within the class to remove.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to update.\n\t */\n\tpublic void deleteClassFields(@Nonnull Workspace workspace,\n\t                              @Nonnull WorkspaceResource resource,\n\t                              @Nonnull JvmClassBundle bundle,\n\t                              @Nonnull JvmClassInfo info) {\n\t\tItemListSelectionPopup.forFields(info, fields -> deleteClassFields(workspace, resource, bundle, info, fields))\n\t\t\t\t.withMultipleSelection()\n\t\t\t\t.withTitle(Lang.getBinding(\"menu.edit.remove.field\"))\n\t\t\t\t.withTextMapping(field -> textService.getFieldMemberTextProvider(workspace, resource, bundle, info, field).makeText())\n\t\t\t\t.withGraphicMapping(field -> iconService.getClassMemberIconProvider(workspace, resource, bundle, info, field).makeIcon())\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Removes the given fields from the given class.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tClass declaring the methods.\n\t * @param fields\n\t * \t\tFields to delete.\n\t */\n\tpublic void deleteClassFields(@Nonnull Workspace workspace,\n\t                              @Nonnull WorkspaceResource resource,\n\t                              @Nonnull JvmClassBundle bundle,\n\t                              @Nonnull JvmClassInfo declaringClass,\n\t                              @Nonnull Collection<FieldMember> fields) {\n\t\tClassReader reader = declaringClass.getClassReader();\n\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\tMemberRemovingVisitor visitor = new MemberRemovingVisitor(writer, FieldPredicate.of(fields));\n\t\treader.accept(visitor, declaringClass.getClassReaderFlags());\n\t\tbundle.put(declaringClass.toJvmClassBuilder()\n\t\t\t\t.adaptFrom(writer.toByteArray())\n\t\t\t\t.build());\n\t}\n\n\t/**\n\t * Prompts the user to select methods within the class to remove.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to update.\n\t */\n\tpublic void deleteClassMethods(@Nonnull Workspace workspace,\n\t                               @Nonnull WorkspaceResource resource,\n\t                               @Nonnull JvmClassBundle bundle,\n\t                               @Nonnull JvmClassInfo info) {\n\t\tItemListSelectionPopup.forMethods(info, methods -> deleteClassMethods(workspace, resource, bundle, info, methods))\n\t\t\t\t.withMultipleSelection()\n\t\t\t\t.withTitle(Lang.getBinding(\"menu.edit.remove.method\"))\n\t\t\t\t.withTextMapping(method -> textService.getMethodMemberTextProvider(workspace, resource, bundle, info, method).makeText())\n\t\t\t\t.withGraphicMapping(method -> iconService.getClassMemberIconProvider(workspace, resource, bundle, info, method).makeIcon())\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Removes the given methods from the given class.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tClass declaring the methods.\n\t * @param methods\n\t * \t\tMethods to delete.\n\t */\n\tpublic void deleteClassMethods(@Nonnull Workspace workspace,\n\t                               @Nonnull WorkspaceResource resource,\n\t                               @Nonnull JvmClassBundle bundle,\n\t                               @Nonnull JvmClassInfo declaringClass,\n\t                               @Nonnull Collection<MethodMember> methods) {\n\t\tClassReader reader = declaringClass.getClassReader();\n\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\tMemberRemovingVisitor visitor = new MemberRemovingVisitor(writer, MethodPredicate.of(methods));\n\t\treader.accept(visitor, declaringClass.getClassReaderFlags());\n\t\tbundle.put(declaringClass.toJvmClassBuilder()\n\t\t\t\t.adaptFrom(writer.toByteArray())\n\t\t\t\t.build());\n\t}\n\n\t/**\n\t * Prompts the user to select annotations on the class to remove.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to update.\n\t */\n\tpublic void deleteClassAnnotations(@Nonnull Workspace workspace,\n\t                                   @Nonnull WorkspaceResource resource,\n\t                                   @Nonnull JvmClassBundle bundle,\n\t                                   @Nonnull JvmClassInfo info) {\n\t\tItemListSelectionPopup.forAnnotationRemoval(info, annotations -> {\n\t\t\t\t\tList<String> names = annotations.stream()\n\t\t\t\t\t\t\t.map(AnnotationInfo::getDescriptor)\n\t\t\t\t\t\t\t.map(desc -> desc.substring(1, desc.length() - 1))\n\t\t\t\t\t\t\t.collect(Collectors.toList());\n\t\t\t\t\timmediateDeleteAnnotations(bundle, info, names);\n\t\t\t\t})\n\t\t\t\t.withMultipleSelection()\n\t\t\t\t.withTitle(Lang.getBinding(\"menu.edit.remove.annotation\"))\n\t\t\t\t.withTextMapping(anno -> textService.getAnnotationTextProvider(workspace, resource, bundle, info, anno).makeText())\n\t\t\t\t.withGraphicMapping(anno -> iconService.getAnnotationIconProvider(workspace, resource, bundle, info, anno).makeIcon())\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user to select annotations on the field or method to remove.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to update.\n\t * @param member\n\t * \t\tField or method to remove annotations from.\n\t */\n\tpublic void deleteMemberAnnotations(@Nonnull Workspace workspace,\n\t                                    @Nonnull WorkspaceResource resource,\n\t                                    @Nonnull JvmClassBundle bundle,\n\t                                    @Nonnull JvmClassInfo info,\n\t                                    @Nonnull ClassMember member) {\n\t\tItemListSelectionPopup.forAnnotationRemoval(member, annotations -> {\n\t\t\t\t\tList<String> names = annotations.stream()\n\t\t\t\t\t\t\t.map(AnnotationInfo::getDescriptor)\n\t\t\t\t\t\t\t.map(desc -> desc.substring(1, desc.length() - 1))\n\t\t\t\t\t\t\t.collect(Collectors.toList());\n\t\t\t\t\timmediateDeleteAnnotations(bundle, member, names);\n\t\t\t\t})\n\t\t\t\t.withMultipleSelection()\n\t\t\t\t.withTitle(Lang.getBinding(\"menu.edit.remove.annotation\"))\n\t\t\t\t.withTextMapping(anno -> textService.getAnnotationTextProvider(workspace, resource, bundle, info, anno).makeText())\n\t\t\t\t.withGraphicMapping(anno -> iconService.getAnnotationIconProvider(workspace, resource, bundle, info, anno).makeIcon())\n\t\t\t\t.show();\n\t}\n\n\t/**\n\t * Prompts the user for field declaration info, to add it to the given class.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to update.\n\t */\n\tpublic void addClassField(@Nonnull Workspace workspace,\n\t                          @Nonnull WorkspaceResource resource,\n\t                          @Nonnull JvmClassBundle bundle,\n\t                          @Nonnull JvmClassInfo info) {\n\t\tnew AddMemberPopup(member -> {\n\t\t\tClassReader reader = info.getClassReader();\n\t\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\t\treader.accept(new MemberStubAddingVisitor(writer, member), info.getClassReaderFlags());\n\t\t\tJvmClassInfo updatedInfo = info.toJvmClassBuilder()\n\t\t\t\t\t.adaptFrom(writer.toByteArray())\n\t\t\t\t\t.build();\n\t\t\tbundle.put(updatedInfo);\n\n\t\t\t// Open the assembler with the new field\n\t\t\ttry {\n\t\t\t\topenAssembler(PathNodes.memberPath(workspace, resource, bundle, updatedInfo, member));\n\t\t\t} catch (IncompletePathException e) {\n\t\t\t\tlogger.error(\"Failed to open assembler for new field\", e);\n\t\t\t}\n\t\t}).forField(info).show();\n\t}\n\n\t/**\n\t * Prompts the user for method declaration info, to add it to the given class.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to update.\n\t */\n\tpublic void addClassMethod(@Nonnull Workspace workspace,\n\t                           @Nonnull WorkspaceResource resource,\n\t                           @Nonnull JvmClassBundle bundle,\n\t                           @Nonnull JvmClassInfo info) {\n\t\tnew AddMemberPopup(member -> {\n\t\t\tClassReader reader = info.getClassReader();\n\t\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\t\treader.accept(new MemberStubAddingVisitor(writer, member), info.getClassReaderFlags());\n\t\t\tJvmClassInfo updatedInfo = info.toJvmClassBuilder()\n\t\t\t\t\t.adaptFrom(writer.toByteArray())\n\t\t\t\t\t.build();\n\t\t\tbundle.put(updatedInfo);\n\n\t\t\t// Open the assembler with the new method\n\t\t\ttry {\n\t\t\t\topenAssembler(PathNodes.memberPath(workspace, resource, bundle, updatedInfo, member));\n\t\t\t} catch (IncompletePathException e) {\n\t\t\t\tlogger.error(\"Failed to open assembler for new method\", e);\n\t\t\t}\n\t\t}).forMethod(info).show();\n\t}\n\n\t/**\n\t * Prompts the user to select a method to override.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tClass to update.\n\t */\n\tpublic void overrideClassMethod(@Nonnull Workspace workspace,\n\t                                @Nonnull WorkspaceResource resource,\n\t                                @Nonnull JvmClassBundle bundle,\n\t                                @Nonnull JvmClassInfo info) {\n\t\tInheritanceGraph inheritanceGraph = inheritanceGraphService.getOrCreateInheritanceGraph(workspace);\n\t\tnew OverrideMethodPopup(this, cellConfigurationService, inheritanceGraph, workspace, info, (methodOwner, method) -> {\n\t\t\tClassReader reader = info.getClassReader();\n\t\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\t\treader.accept(new MemberStubAddingVisitor(writer, method), info.getClassReaderFlags());\n\t\t\tJvmClassInfo updatedInfo = info.toJvmClassBuilder()\n\t\t\t\t\t.adaptFrom(writer.toByteArray())\n\t\t\t\t\t.build();\n\t\t\tbundle.put(updatedInfo);\n\n\t\t\t// Open the assembler with the new method\n\t\t\ttry {\n\t\t\t\topenAssembler(PathNodes.memberPath(workspace, resource, bundle, updatedInfo, method));\n\t\t\t} catch (IncompletePathException e) {\n\t\t\t\tlogger.error(\"Failed to open assembler for new method\", e);\n\t\t\t}\n\t\t}).show();\n\t}\n\n\t/**\n\t * Makes the given methods no-op.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tClass declaring the methods.\n\t * @param methods\n\t * \t\tMethods to noop.\n\t */\n\tpublic void makeMethodsNoop(@Nonnull Workspace workspace,\n\t                            @Nonnull WorkspaceResource resource,\n\t                            @Nonnull JvmClassBundle bundle,\n\t                            @Nonnull JvmClassInfo declaringClass,\n\t                            @Nonnull Collection<MethodMember> methods) {\n\t\tClassReader reader = declaringClass.getClassReader();\n\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\tMethodNoopingVisitor visitor = new MethodNoopingVisitor(writer, MethodPredicate.of(methods));\n\t\treader.accept(visitor, declaringClass.getClassReaderFlags());\n\t\tbundle.put(declaringClass.toJvmClassBuilder()\n\t\t\t\t.adaptFrom(writer.toByteArray())\n\t\t\t\t.build());\n\t}\n\n\t/**\n\t * Removes variable debug info in the given methods.\n\t *\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param declaringClass\n\t * \t\tClass declaring the methods.\n\t * @param methods\n\t * \t\tMethods to clean.\n\t */\n\tpublic void removeMethodVariables(@Nonnull Workspace workspace,\n\t                                  @Nonnull WorkspaceResource resource,\n\t                                  @Nonnull JvmClassBundle bundle,\n\t                                  @Nonnull JvmClassInfo declaringClass,\n\t                                  @Nonnull Collection<MethodMember> methods) {\n\t\tClassReader reader = declaringClass.getClassReader();\n\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\tMethodVariableRemovingVisitor visitor = new MethodVariableRemovingVisitor(writer, MethodPredicate.of(methods));\n\t\treader.accept(visitor, declaringClass.getClassReaderFlags());\n\t\tbundle.put(declaringClass.toJvmClassBuilder()\n\t\t\t\t.adaptFrom(writer.toByteArray())\n\t\t\t\t.build());\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param annotated\n\t * \t\tAnnotated class or member.\n\t * @param annotationType\n\t * \t\tAnnotation type to remove.\n\t */\n\tpublic void immediateDeleteAnnotations(@Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                       @Nonnull Annotated annotated,\n\t                                       @Nonnull String annotationType) {\n\t\timmediateDeleteAnnotations(bundle, annotated, Collections.singleton(annotationType));\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param annotated\n\t * \t\tAnnotated class or member.\n\t * @param annotationTypes\n\t * \t\tAnnotation types to remove.\n\t */\n\tpublic void immediateDeleteAnnotations(@Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t                                       @Nonnull Annotated annotated,\n\t                                       @Nonnull Collection<String> annotationTypes) {\n\t\ttry {\n\t\t\tif (annotated instanceof JvmClassInfo target) {\n\t\t\t\tClassReader reader = target.getClassReader();\n\t\t\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\t\t\treader.accept(new ClassAnnotationRemovingVisitor(writer, annotationTypes), target.getClassReaderFlags());\n\t\t\t\tJvmClassInfo updatedClass = new JvmClassInfoBuilder(writer.toByteArray()).build();\n\t\t\t\tbundle.put(cast(updatedClass));\n\t\t\t} else if (annotated instanceof ClassMember member && member.getDeclaringClass() instanceof JvmClassInfo target) {\n\t\t\t\tClassReader reader = target.getClassReader();\n\t\t\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\t\t\tif (member.isField()) {\n\t\t\t\t\tFieldMember field = (FieldMember) member;\n\t\t\t\t\treader.accept(FieldAnnotationRemovingVisitor.forClass(writer, annotationTypes, field), target.getClassReaderFlags());\n\t\t\t\t} else {\n\t\t\t\t\tMethodMember method = (MethodMember) member;\n\t\t\t\t\treader.accept(MethodAnnotationRemovingVisitor.forClass(writer, annotationTypes, method), target.getClassReaderFlags());\n\t\t\t\t}\n\t\t\t\tJvmClassInfo updatedClass = new JvmClassInfoBuilder(writer.toByteArray()).build();\n\t\t\t\tbundle.put(cast(updatedClass));\n\t\t\t} else {\n\t\t\t\tlogger.warn(\"Cannot remove annotations on unsupported annotated type: {}\", annotated.getClass().getSimpleName());\n\t\t\t}\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Failed removing annotation\", t);\n\t\t}\n\t}\n\n\t/**\n\t * @return New string search pane, opened in a new docking tab.\n\t */\n\t@Nonnull\n\tpublic StringSearchPane openNewStringSearch() {\n\t\treturn openSearchPane(\"menu.search.string\", CarbonIcons.QUOTES, stringSearchPaneProvider);\n\t}\n\n\t/**\n\t * @return New number search pane, opened in a new docking tab.\n\t */\n\t@Nonnull\n\tpublic NumberSearchPane openNewNumberSearch() {\n\t\treturn openSearchPane(\"menu.search.number\", CarbonIcons.NUMBER_0, numberSearchPaneProvider);\n\t}\n\n\t/**\n\t * @return New class-reference search pane, opened in a new docking tab.\n\t */\n\t@Nonnull\n\tpublic ClassReferenceSearchPane openNewClassReferenceSearch() {\n\t\treturn openSearchPane(\"menu.search.class.type-references\", CarbonIcons.CODE_REFERENCE, classReferenceSearchPaneProvider);\n\t}\n\n\t/**\n\t * @return New member-reference search pane, opened in a new docking tab.\n\t */\n\t@Nonnull\n\tpublic MemberReferenceSearchPane openNewMemberReferenceSearch() {\n\t\treturn openSearchPane(\"menu.search.class.member-references\", CarbonIcons.CODE_REFERENCE, memberReferenceSearchPaneProvider);\n\t}\n\n\t/**\n\t * @return New member-declaration search pane, opened in a new docking tab.\n\t */\n\t@Nonnull\n\tpublic MemberDeclarationSearchPane openNewMemberDeclarationSearch() {\n\t\treturn openSearchPane(\"menu.search.class.member-declarations\", CarbonIcons.CODE, memberDeclarationSearchPaneProvider);\n\t}\n\n\t/**\n\t * @return New instruction search pane, opened in a new docking tab.\n\t */\n\t@Nonnull\n\tpublic InstructionSearchPane openNewInstructionSearch() {\n\t\treturn openSearchPane(\"menu.search.class.instruction\", CarbonIcons.CODE, instructionSearchPaneProvider);\n\t}\n\n\t@Nonnull\n\tprivate <T extends AbstractSearchPane> T openSearchPane(@Nonnull String titleId, @Nonnull Ikon icon, @Nonnull Instance<T> paneProvider) {\n\t\t// Place the tab in a region with other searches if possible.\n\t\tDockablePath searchPath = dockingManager.getBento()\n\t\t\t\t.search().dockable(d -> d.getNode() instanceof AbstractSearchPane);\n\t\tDockContainerLeaf container = searchPath == null ? null : searchPath.leafContainer();\n\n\t\tT content = paneProvider.get();\n\t\tDockable dockable;\n\t\tif (container != null) {\n\t\t\tdockable = createDockable(container, getBinding(titleId), d -> new FontIconView(icon), content);\n\t\t} else {\n\t\t\tdockable = createDockable(null, getBinding(titleId), d -> new FontIconView(icon), content);\n\t\t\tScene originScene = dockingManager.getPrimaryDockingContainer().asRegion().getScene();\n\t\t\tStage stage = dockingManager.getBento().stageBuilding().newStageForDockable(originScene, dockable, 800, 400);\n\t\t\tstage.show();\n\t\t\tstage.requestFocus();\n\t\t}\n\t\tdockable.setDragGroupMask(DockingManager.GROUP_ANYWHERE);\n\t\tdockable.addCloseListener((_, _) -> paneProvider.destroy(content));\n\t\tdockable.setContextMenuFactory(d -> {\n\t\t\tContextMenu menu = new ContextMenu();\n\t\t\taddCloseActions(menu, d);\n\t\t\treturn menu;\n\t\t});\n\t\treturn content;\n\t}\n\n\t/**\n\t * Looks for the {@link Navigable} component representing the path and returns it if found.\n\t * If no such component exists, it should be generated by the passed supplier, which then gets returned.\n\t * <br>\n\t * The dockable containing the {@link Navigable} component is selected when returned.\n\t *\n\t * @param path\n\t * \t\tPath to navigate to.\n\t * @param factory\n\t * \t\tFactory to create a dockable for displaying content located at the given path,\n\t * \t\tshould a dockable for the content not already exist.\n\t * \t\t<br>\n\t * \t\t<b>NOTE:</b> It is required/assumed that the {@link Tab#getContent()} is a\n\t * \t\tcomponent implementing {@link Navigable}.\n\t *\n\t * @return Navigable content representing content of the path.\n\t */\n\t@Nonnull\n\tpublic Navigable getOrCreatePathContent(@Nonnull PathNode<?> path, @Nonnull Supplier<Dockable> factory) {\n\t\tList<Navigable> children = navigationManager.getNavigableChildrenByPath(path);\n\t\tNavigable navigable = children.isEmpty() ? createContent(factory) : children.getFirst();\n\t\tselectTab(navigable);\n\t\tnavigable.requestFocus();\n\t\treturn navigable;\n\t}\n\n\t@Nonnull\n\tprivate Navigable createContent(@Nonnull Supplier<Dockable> factory) {\n\t\t// Create the dockable for the content, then display it.\n\t\tDockable dockable = factory.get();\n\t\tNavigable navigable = (Navigable) Objects.requireNonNull(dockable.getNode());\n\t\tselectTab(navigable);\n\t\tnavigable.requestFocus();\n\t\treturn navigable;\n\t}\n\n\tprivate void setupInfoContextMenu(@Nonnull Info info,\n\t                                  @Nonnull AbstractContentPane<?> contentPane,\n\t                                  @Nonnull Dockable dockable) {\n\t\tsetupInfoContextMenu(info, contentPane, dockable, null);\n\t}\n\n\tprivate void setupInfoContextMenu(@Nonnull Info info,\n\t                                  @Nonnull AbstractContentPane<?> contentPane,\n\t                                  @Nonnull Dockable dockable,\n\t                                  @Nullable Consumer<ContextMenu> menuAdapter) {\n\t\tdockable.setContextMenuFactory(d -> {\n\t\t\tContextMenu menu = new ContextMenu();\n\n\t\t\tif (menuAdapter != null)\n\t\t\t\tmenuAdapter.accept(menu);\n\n\t\t\tObservableList<MenuItem> items = menu.getItems();\n\t\t\tif (info instanceof JvmClassInfo classInfo && contentPane instanceof JvmClassPane content) {\n\t\t\t\tMenu mode = menu(\"menu.mode\", CarbonIcons.VIEW);\n\t\t\t\tmode.getItems().addAll(\n\t\t\t\t\t\taction(\"menu.mode.class.decompile\", CarbonIcons.CODE,\n\t\t\t\t\t\t\t\t() -> content.setEditorType(JvmClassEditorType.DECOMPILE)),\n\t\t\t\t\t\taction(\"menu.mode.class.low-level\", CarbonIcons.MICROSCOPE,\n\t\t\t\t\t\t\t\t() -> content.setEditorType(JvmClassEditorType.LOW_LEVEL)),\n\t\t\t\t\t\taction(\"menu.mode.file.hex\", CarbonIcons.NUMBER_0,\n\t\t\t\t\t\t\t\t() -> content.setEditorType(JvmClassEditorType.HEX))\n\t\t\t\t);\n\t\t\t\titems.add(mode);\n\t\t\t} else if (info instanceof AndroidClassInfo classInfo && contentPane instanceof AndroidClassPane content) {\n\t\t\t\tMenu mode = menu(\"menu.mode\", CarbonIcons.VIEW);\n\t\t\t\tmode.getItems().addAll(\n\t\t\t\t\t\taction(\"menu.mode.class.decompile\", CarbonIcons.CODE,\n\t\t\t\t\t\t\t\t() -> content.setEditorType(AndroidClassEditorType.DECOMPILE)),\n\t\t\t\t\t\taction(\"menu.mode.file.smali\", CarbonIcons.NUMBER_0,\n\t\t\t\t\t\t\t\t() -> content.setEditorType(AndroidClassEditorType.SMALI))\n\t\t\t\t);\n\t\t\t\titems.add(mode);\n\t\t\t}\n\n\t\t\taddCopyPathAction(menu, info);\n\t\t\taddCloseActions(menu, d);\n\n\t\t\treturn menu;\n\t\t});\n\t}\n\n\n\t/**\n\t * Selects the containing {@link Dockable} that contains the navigable content.\n\t *\n\t * @param navigable\n\t * \t\tNavigable content to select in its containing {@link DockContainerLeaf}.\n\t */\n\tprivate void selectTab(@Nullable Navigable navigable) {\n\t\tif (navigable == null)\n\t\t\treturn;\n\t\tDockable dockable = navigationManager.lookupDockable(navigable);\n\t\tif (dockable != null)\n\t\t\tdockable.inContainer(DockContainerLeaf::selectDockable);\n\t}\n\n\t/**\n\t * Shorthand for dockable-creation + graphic setting.\n\t *\n\t * @param container\n\t * \t\tParent container to spawn in.\n\t * @param title\n\t * \t\tDockable title.\n\t * @param graphicFactory\n\t * \t\tDockable graphic factory.\n\t * @param node\n\t * \t\tDockable content.\n\t *\n\t * @return Created dockable.\n\t */\n\t@Nonnull\n\tprivate Dockable createDockable(@Nullable DockContainerLeaf container,\n\t                                @Nonnull String title,\n\t                                @Nonnull DockableIconFactory graphicFactory,\n\t                                @Nonnull Node node) {\n\t\tDockable dockable = dockingManager.newDockable(title, graphicFactory, node);\n\t\tnode.addEventFilter(KeyEvent.KEY_PRESSED, e -> {\n\t\t\tif (keybindingConfig.getCloseTab().match(e))\n\t\t\t\tcloseDockable(dockable);\n\t\t});\n\t\tif (container != null) {\n\t\t\tcontainer.addDockable(dockable);\n\t\t\tcontainer.selectDockable(dockable);\n\t\t\tnode.requestFocus();\n\t\t}\n\t\treturn dockable;\n\t}\n\n\t/**\n\t * Shorthand for dockable-creation + graphic setting.\n\t *\n\t * @param container\n\t * \t\tParent container to spawn in.\n\t * @param title\n\t * \t\tDockable title.\n\t * @param graphicFactory\n\t * \t\tDockable graphic factory.\n\t * @param node\n\t * \t\tDockable content.\n\t *\n\t * @return Created dockable.\n\t */\n\t@Nonnull\n\tprivate Dockable createDockable(@Nullable DockContainerLeaf container,\n\t                                @Nonnull ObservableValue<String> title,\n\t                                @Nonnull DockableIconFactory graphicFactory,\n\t                                @Nonnull Node node) {\n\t\tDockable dockable = dockingManager.newTranslatableDockable(title, graphicFactory, node);\n\t\tnode.addEventFilter(KeyEvent.KEY_PRESSED, e -> {\n\t\t\tif (keybindingConfig.getCloseTab().match(e))\n\t\t\t\tcloseDockable(dockable);\n\t\t});\n\t\tif (container != null) {\n\t\t\tcontainer.addDockable(dockable);\n\t\t\tcontainer.selectDockable(dockable);\n\t\t\tfocusSelectedDockableContent(container);\n\t\t}\n\t\treturn dockable;\n\t}\n\n\t/**\n\t * Close the given dockable.\n\t *\n\t * @param dockable\n\t * \t\tDockable to close.\n\t */\n\tprivate void closeDockable(@Nonnull Dockable dockable) {\n\t\tdockable.inContainer(container -> {\n\t\t\tcontainer.closeDockable(dockable);\n\t\t\tfocusSelectedDockableContent(container);\n\t\t});\n\t}\n\n\t/**\n\t * Focuses the content of the selected dockable in the given container.\n\t *\n\t * @param container\n\t * \t\tContainer to focus selected dockable content in.\n\t */\n\tprivate void focusSelectedDockableContent(@Nonnull DockContainerLeaf container) {\n\t\tDockable selectedDockable = container.getSelectedDockable();\n\t\tif (selectedDockable == null)\n\t\t\treturn;\n\n\t\tNode node = selectedDockable.getNode();\n\t\tif (node == null)\n\t\t\treturn;\n\n\t\tScene scene = container.getScene();\n\t\tif (scene != null && scene.getWindow() != null)\n\t\t\tscene.getWindow().requestFocus();\n\n\t\tnode.requestFocus();\n\t}\n\n\t/**\n\t * Adds 'mode' switches for supported file display modes in the given file pane.\n\t *\n\t * @param menu\n\t * \t\tMenu to add items to.\n\t * @param content\n\t * \t\tFile pane to pull supported display modes from.\n\t */\n\tprivate static void addFilePaneOptions(@Nonnull ContextMenu menu, @Nonnull FilePane content) {\n\t\tList<FileDisplayMode> fileDisplayModes = content.getFileDisplayModes();\n\t\tif (fileDisplayModes.size() > 1) {\n\t\t\tMenu modeMenu = menu(\"menu.mode\", CarbonIcons.VIEW);\n\t\t\tfor (FileDisplayMode mode : fileDisplayModes)\n\t\t\t\tmodeMenu.getItems().add(action(mode.getKey(), mode.newIcon(), () -> content.setFileDisplayMode(mode)));\n\t\t\tmenu.getItems().add(modeMenu);\n\t\t}\n\t}\n\n\t/**\n\t * Adds 'copy path' action to the given menu, with a following separator assuming other items will be added next.\n\t *\n\t * @param menu\n\t * \t\tMenu to add to.\n\t * @param info\n\t * \t\tInfo with path to copy.\n\t */\n\tprivate static void addCopyPathAction(@Nonnull ContextMenu menu, @Nonnull Info info) {\n\t\tObservableList<MenuItem> items = menu.getItems();\n\t\titems.add(action(\"menu.tab.copypath\", CarbonIcons.COPY_LINK, () -> ClipboardUtil.copyString(info)));\n\t\titems.add(separator());\n\t}\n\n\t/**\n\t * Adds close actions to the given menu.\n\t * <ul>\n\t *     <li>Close</li>\n\t *     <li>Close others</li>\n\t *     <li>Close all</li>\n\t * </ul>\n\t *\n\t * @param menu\n\t * \t\tMenu to add to.\n\t * @param dockable\n\t * \t\tDockable reference.\n\t */\n\tprivate void addCloseActions(@Nonnull ContextMenu menu, @Nonnull Dockable dockable) {\n\t\tmenu.getItems().addAll(\n\t\t\t\taction(\"menu.tab.close\", CarbonIcons.CLOSE, () -> closeDockable(dockable)),\n\t\t\t\taction(\"menu.tab.closeothers\", CarbonIcons.CLOSE, () -> {\n\t\t\t\t\tdockable.inContainer(container -> {\n\t\t\t\t\t\tUnchecked.checkedForEach(container.getDockables(), d -> {\n\t\t\t\t\t\t\tif (d != dockable)\n\t\t\t\t\t\t\t\tcontainer.closeDockable(d);\n\t\t\t\t\t\t}, (d, error) -> {\n\t\t\t\t\t\t\tlogger.error(\"Failed to close tab '{}'\", d.getTitle(), error);\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t}),\n\t\t\t\taction(\"menu.tab.closeall\", CarbonIcons.CLOSE, () -> {\n\t\t\t\t\tdockable.inContainer(container -> {\n\t\t\t\t\t\tUnchecked.checkedForEach(container.getDockables(), container::closeDockable, (d, error) -> {\n\t\t\t\t\t\t\tlogger.error(\"Failed to close tab '{}'\", d.getTitle(), error);\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t})\n\t\t);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ActionsConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/navigation/ActionsConfig.java",
    "content": "package software.coley.recaf.services.navigation;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link Actions}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ActionsConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic ActionsConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, Actions.ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/navigation/ClassNavigable.java",
    "content": "package software.coley.recaf.services.navigation;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.path.ClassPathNode;\n\n/**\n * Outline of navigable content representing {@link ClassInfo} values.\n *\n * @author Matt Coley\n */\npublic interface ClassNavigable extends Navigable {\n\t/**\n\t * @return Class path of this navigable component.\n\t */\n\t@Nonnull\n\tClassPathNode getClassPath();\n\n\t/**\n\t * Requests focus of the class member contained within this navigable representation of a class.\n\t *\n\t * @param member\n\t * \t\tMember to focus.\n\t */\n\tvoid requestFocus(@Nonnull ClassMember member);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/navigation/FileNavigable.java",
    "content": "package software.coley.recaf.services.navigation;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.path.FilePathNode;\n\n/**\n * Outline of navigable content representing {@link FileInfo} values.\n *\n * @author Matt Coley\n */\npublic interface FileNavigable extends Navigable {\n\t@Nonnull\n\t@Override\n\tFilePathNode getPath(); // Force child types to implement getter as FilePathNode\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/navigation/Navigable.java",
    "content": "package software.coley.recaf.services.navigation;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport javafx.scene.layout.Pane;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.ui.docking.DockingManager;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Queue;\n\n/**\n * Outline of navigable content <i>(IE, content in the {@link Workspace} such as classes and files)</i>.\n * UI components implement this type can be discovered via {@link PathNode} look-ups.\n *\n * @author Matt Coley\n * @see NavigationManager Tracker of all open {@link Navigable} content.\n * @see ClassNavigable\n * @see FileNavigable\n */\npublic interface Navigable {\n\t/**\n\t * @return The path layout pointing to the content <i>(Such as a {@link ClassInfo}, {@link ClassMember}, etc)</i>\n\t * that this {@link Navigable} class is representing. Can be {@code null} if this content is populated dynamically\n\t * <i>(Common for {@link UpdatableNavigable})</i>.\n\t */\n\t@Nullable\n\tPathNode<?> getPath();\n\n\t/**\n\t * @return Child navigable nodes.\n\t */\n\t@Nonnull\n\tCollection<Navigable> getNavigableChildren();\n\n\t/**\n\t * Search for a given navigable type in the {@link #getNavigableChildren()}.\n\t *\n\t * @param childType\n\t * \t\tChild class to search for.\n\t * @param <N>\n\t * \t\tInferred child type.\n\t *\n\t * @return Instance of child.\n\t */\n\t@Nullable\n\t@SuppressWarnings(\"unchecked\")\n\tdefault <N extends Navigable> N getNavigableChildOfType(@Nonnull Class<N> childType) {\n\t\tQueue<Navigable> queue = new ArrayDeque<>(getNavigableChildren());\n\t\twhile (!queue.isEmpty()) {\n\t\t\tNavigable child = queue.remove();\n\t\t\tif (childType.isAssignableFrom(child.getClass()))\n\t\t\t\treturn (N) child;\n\t\t\tqueue.addAll(child.getNavigableChildren());\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * Search for a given navigable type in the {@link #getNavigableChildren()}.\n\t *\n\t * @param childType\n\t * \t\tChild class to search for.\n\t * @param <N>\n\t * \t\tInferred child type.\n\t *\n\t * @return All child instances of the given type.\n\t */\n\t@Nonnull\n\t@SuppressWarnings(\"unchecked\")\n\tdefault <N extends Navigable> List<N> getNavigableChildrenOfType(@Nonnull Class<N> childType) {\n\t\tList<N> children = new ArrayList<>();\n\t\tQueue<Navigable> queue = new ArrayDeque<>(getNavigableChildren());\n\t\twhile (!queue.isEmpty()) {\n\t\t\tNavigable child = queue.remove();\n\t\t\tif (childType.isAssignableFrom(child.getClass()))\n\t\t\t\tchildren.add((N) child);\n\t\t\tqueue.addAll(child.getNavigableChildren());\n\t\t}\n\t\treturn children;\n\t}\n\n\t/**\n\t * Requests focus of this navigable component.\n\t */\n\tvoid requestFocus();\n\n\t/**\n\t * Disables this navigable component.\n\t * <p>\n\t * Called when:\n\t * <ul>\n\t *     <li>A {@link Dependent} {@link Navigable} content within a tab tracked by {@link DockingManager} is closed.</li>\n\t *     <li>An associated {@link ClassInfo} or {@link FileInfo} in the workspace is removed.</li>\n\t * </ul>\n\t * <b>Must be called on the FX thread.</b>\n\t */\n\tvoid disable();\n\n\t/**\n\t * Searches for {@link Navigable} child components in the component model.\n\t * The model can be thought of as a tree, where child nodes are represented by {@link #getNavigableChildren()}.\n\t * <br>\n\t * For UI navigable components, this typically would be implemented by filtering the UI children list\n\t * <i>(Such as {@link Pane#getChildren()})</i> that implement {@link Navigable}.\n\t * <br>\n\t * However, the children are not limited to strictly components that exist akin to {@link Pane#getChildren()}.\n\t * a UI can wrap a {@link ClassInfo} but then declare its members as {@link Navigable} children. Then each child\n\t * can implement {@link #requestFocus()} in such a way that is handled in the parent representation of the\n\t * {@link ClassInfo}.\n\t *\n\t * @param path\n\t * \t\tPath associated with node to look for in the component model.\n\t *\n\t * @return {@link Navigable} components matching the path in the component model.\n\t */\n\t@Nonnull\n\tdefault List<Navigable> getNavigableChildrenByPath(@Nonnull PathNode<?> path) {\n\t\tPathNode<?> value = getPath();\n\t\tif (value == null || path.equals(value))\n\t\t\treturn Collections.singletonList(this);\n\n\t\tList<Navigable> list = null;\n\t\tfor (Navigable child : getNavigableChildren()) {\n\t\t\tPathNode<?> childPath = child.getPath();\n\t\t\tif (childPath == null) continue;\n\n\t\t\tif (path.isDescendantOf(childPath)) {\n\t\t\t\tList<Navigable> childM = child.getNavigableChildrenByPath(path);\n\t\t\t\tif (!childM.isEmpty()) {\n\t\t\t\t\tif (list == null)\n\t\t\t\t\t\tlist = new ArrayList<>(childM);\n\t\t\t\t\telse\n\t\t\t\t\t\tlist.addAll(childM);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn list == null ? Collections.emptyList() : list;\n\t}\n\n\t/**\n\t * @return {@code true} to enable tracking of this component within {@link NavigationManager}.\n\t */\n\tdefault boolean isTrackable() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/navigation/NavigableAddListener.java",
    "content": "package software.coley.recaf.services.navigation;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\n\n/**\n * Listener to observe {@link Navigable} components being added to the UI.\n *\n * @author Matt Coley\n */\npublic interface NavigableAddListener extends PrioritySortable {\n\t/**\n\t * Called when the {@link NavigationManager} observes a {@link Navigable} is added to the UI.\n\t * <p>\n\t * Be wary that the same {@link Navigable} can technically be removed and added to the UI multiple times.\n\t *\n\t * @param navigable\n\t * \t\tAdded navigable component.\n\t */\n\tvoid onAdd(@Nonnull Navigable navigable);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/navigation/NavigableRemoveListener.java",
    "content": "package software.coley.recaf.services.navigation;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\n\n/**\n * Listener to observe {@link Navigable} components being removed from the UI.\n *\n * @author Matt Coley\n */\npublic interface NavigableRemoveListener extends PrioritySortable {\n\t/**\n\t * Called when the {@link NavigationManager} observes a {@link Navigable} is removed from the UI.\n\t * <p>\n\t * Be wary that the same {@link Navigable} can technically be removed and added to the UI multiple times.\n\t *\n\t * @param navigable\n\t * \t\tRemoved navigable component.\n\t */\n\tvoid onRemove(@Nonnull Navigable navigable);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/navigation/NavigationManager.java",
    "content": "package software.coley.recaf.services.navigation;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.Node;\nimport org.slf4j.Logger;\nimport software.coley.bentofx.dockable.Dockable;\nimport software.coley.bentofx.path.DockablePath;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.properties.builtin.RemapOriginTaskProperty;\nimport software.coley.recaf.path.AbstractPathNode;\nimport software.coley.recaf.path.BundlePathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.path.WorkspacePathNode;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.mapping.MappingResults;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.docking.DockingManager;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.WorkspaceModificationListener;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.ResourceAndroidClassListener;\nimport software.coley.recaf.workspace.model.resource.ResourceFileListener;\nimport software.coley.recaf.workspace.model.resource.ResourceJvmClassListener;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * Tracks available {@link Navigable} content currently open in the UI.\n * <br>\n * This is done by tracking the content of {@link Dockable} instances when they are {@link Navigable}.\n * This component is itself {@link Navigable} which means if we use these tracked instances as our\n * {@link #getNavigableChildren()} we can do dynamic look-ups with {@link #getNavigableChildrenByPath(PathNode)}\n * to discover any currently open content.\n *\n * @author Matt Coley\n * @see DockingManager\n */\n@ApplicationScoped\npublic class NavigationManager implements Navigable, Service {\n\tpublic static final String ID = \"navigation\";\n\tprivate static final Logger logger = Logging.get(NavigationManager.class);\n\tprivate final List<NavigableAddListener> addListeners = new CopyOnWriteArrayList<>();\n\tprivate final List<NavigableRemoveListener> removeListeners = new CopyOnWriteArrayList<>();\n\tprivate final Map<Navigable, Dockable> childrenToDockable = Collections.synchronizedMap(new IdentityHashMap<>());\n\tprivate final Map<Dockable, NavigableSpy> dockableToSpy = Collections.synchronizedMap(new IdentityHashMap<>());\n\tprivate final Forwarding forwarding = new Forwarding();\n\tprivate final NavigationManagerConfig config;\n\tprivate PathNode<?> path = new DummyInitialNode();\n\n\t@Inject\n\tpublic NavigationManager(@Nonnull NavigationManagerConfig config,\n\t                         @Nonnull DockingManager dockingManager,\n\t                         @Nonnull WorkspaceManager workspaceManager) {\n\t\tthis.config = config;\n\n\t\t// Track what navigable content is available.\n\t\tdockingManager.getBento().events().addDockableOpenListener((path, dockable) -> {\n\t\t\tObjectProperty<Node> contentProperty = dockable.nodeProperty();\n\n\t\t\t// Create spy for the tab.\n\t\t\tNavigableSpy spy = new NavigableSpy(dockable);\n\t\t\tdockableToSpy.put(dockable, spy);\n\n\t\t\t// Add listener, so if content changes we are made aware of the changes.\n\t\t\tcontentProperty.addListener(spy);\n\n\t\t\t// Record initial value.\n\t\t\tspy.changed(contentProperty, null, contentProperty.getValue());\n\t\t});\n\t\tdockingManager.getBento().events().addDockableCloseListener((path, dockable) -> {\n\t\t\t// The tab is closed, remove its spy lookup.\n\t\t\tNavigableSpy spy = dockableToSpy.remove(dockable);\n\t\t\tif (spy == null) {\n\t\t\t\tlogger.warn(\"Tab {} was closed, but had no associated content spy instance\", dockable.getTitle());\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Remove content from navigation tracking.\n\t\t\tspy.remove(dockable.nodeProperty().get());\n\n\t\t\t// Remove the listener from the tab.\n\t\t\tdockable.nodeProperty().removeListener(spy);\n\t\t});\n\n\t\t// When the workspace closes, close all associated children.\n\t\tworkspaceManager.addWorkspaceCloseListener(workspace -> FxThreadUtil.run(() -> {\n\t\t\tfor (Navigable child : new ArrayList<>(childrenToDockable.keySet())) {\n\t\t\t\tchild.disable();\n\n\t\t\t\tDockable dockable = childrenToDockable.get(child);\n\t\t\t\tif (dockable == null) {\n\t\t\t\t\tlogger.warn(\"Navigation manager children-to-dockable map did not have value for key: {}\", child.getPath());\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tDockablePath path = dockable.getPath();\n\t\t\t\tif (path == null) {\n\t\t\t\t\tlogger.warn(\"Navigation manager couldn't invoke dockable.onClose() since dockable path could not be resolved for: {}\", dockable.getTitle());\n\n\t\t\t\t\t// Still need to remove it from the map to prevent leakage.\n\t\t\t\t\tdockableToSpy.remove(dockable);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Trigger on-close handler\n\t\t\t\tif (!dockable.isClosable())\n\t\t\t\t\tdockable.setClosable(true);\n\t\t\t\tpath.leafContainer().closeDockable(dockable);\n\t\t\t}\n\n\t\t\t// Validate all child references have been removed.\n\t\t\tif (!childrenToDockable.isEmpty()) {\n\t\t\t\tlogger.warn(\"Navigation manager children-to-dockable map was not empty after workspace closure\");\n\t\t\t\tchildrenToDockable.clear();\n\t\t\t}\n\n\t\t\t// Remove the path reference to the old workspace.\n\t\t\tforwarding.workspacePath = null;\n\t\t\tpath = new DummyInitialNode();\n\n\t\t\t// Force close any remaining tabs that hold navigable content.\n\t\t\tfor (DockablePath path : dockingManager.getBento().search().allDockables()) {\n\t\t\t\tDockable dockable = path.dockable();\n\t\t\t\tif (dockable.getNode() instanceof Navigable navigable) {\n\t\t\t\t\tnavigable.disable();\n\t\t\t\t\tpath.leafContainer().closeDockable(dockable);\n\t\t\t\t}\n\t\t\t}\n\t\t}));\n\n\t\t// Track current workspace so that we are navigable ourselves.\n\t\t// Nothing here *needs* to be on the FX thread per-say, but because the closer handling logic is on the FX\n\t\t// thread making this also on the FX thread will help prevent race conditions on assigning the 'path' field.\n\t\tworkspaceManager.addWorkspaceOpenListener(workspace -> FxThreadUtil.run(() -> {\n\t\t\tWorkspacePathNode workspacePath = PathNodes.workspacePath(workspace);\n\t\t\tpath = workspacePath;\n\n\t\t\t// Update forwarding's path.\n\t\t\tforwarding.workspacePath = workspacePath;\n\t\t}));\n\n\t\t// Setup forwarding workspace updates to children.\n\t\tworkspaceManager.addDefaultWorkspaceModificationListeners(new WorkspaceModificationListener() {\n\t\t\t@Override\n\t\t\tpublic void onAddLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library) {\n\t\t\t\tlibrary.addListener(forwarding);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onRemoveLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library) {\n\t\t\t\tlibrary.removeListener(forwarding);\n\t\t\t}\n\t\t});\n\t\tworkspaceManager.addWorkspaceOpenListener(workspace -> {\n\t\t\tfor (WorkspaceResource resource : workspace.getAllResources(false))\n\t\t\t\tresource.addListener(forwarding);\n\t\t});\n\t}\n\n\t/**\n\t * Looks up which {@link Dockable} has the given navigable as its {@link Dockable#nodeProperty()}.\n\t *\n\t * @param navigable\n\t * \t\tSome navigable UI element.\n\t *\n\t * @return The dockable holding the navigable element, if any exists.\n\t */\n\t@Nullable\n\tpublic Dockable lookupDockable(@Nonnull Navigable navigable) {\n\t\treturn childrenToDockable.get(navigable);\n\t}\n\n\t/**\n\t * Add a listener that observes the addition of {@link Navigable} components to the UI.\n\t *\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tpublic void addNavigableAddListener(@Nonnull NavigableAddListener listener) {\n\t\tPrioritySortable.add(addListeners, listener);\n\t}\n\n\t/**\n\t * Remove a listener that observes the addition of {@link Navigable} components to the UI.\n\t *\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tpublic void removeNavigableAddListener(@Nonnull NavigableAddListener listener) {\n\t\taddListeners.remove(listener);\n\t}\n\n\t/**\n\t * Add a listener that observes the removal of {@link Navigable} components to the UI.\n\t *\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tpublic void addNavigableRemoveListener(@Nonnull NavigableRemoveListener listener) {\n\t\tPrioritySortable.add(removeListeners, listener);\n\t}\n\n\t/**\n\t * Remove a listener that observes the removal of {@link Navigable} components to the UI.\n\t *\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tpublic void removeNavigableRemoveListener(@Nonnull NavigableRemoveListener listener) {\n\t\tremoveListeners.remove(listener);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.unmodifiableCollection(childrenToDockable.keySet());\n\t}\n\n\t@Override\n\tpublic void requestFocus() {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\t// no-op\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic NavigationManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n\n\t/**\n\t * Listener to update {@link #childrenToDockable}.\n\t */\n\tprivate class NavigableSpy implements ChangeListener<Node> {\n\t\tprivate final Dockable dockable;\n\n\t\tpublic NavigableSpy(@Nonnull Dockable dockable) {\n\t\t\tthis.dockable = dockable;\n\t\t}\n\n\t\t@Override\n\t\tpublic void changed(ObservableValue<? extends Node> observable, Node oldValue, Node newValue) {\n\t\t\tremove(oldValue);\n\t\t\tadd(newValue);\n\t\t}\n\n\t\tvoid add(@Nullable Node value) {\n\t\t\tif (value instanceof Navigable navigable && navigable.isTrackable()) {\n\t\t\t\tchildrenToDockable.put(navigable, dockable);\n\t\t\t\tUnchecked.checkedForEach(addListeners, listener -> listener.onAdd(navigable),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when handling navigable added '{}'\", navigable.getClass().getName(), t));\n\t\t\t}\n\t\t}\n\n\t\tvoid remove(@Nullable Node value) {\n\t\t\tif (value instanceof Navigable navigable) {\n\t\t\t\tchildrenToDockable.remove(navigable);\n\t\t\t\tUnchecked.checkedForEach(removeListeners, listener -> listener.onRemove(navigable),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when handling navigable removed '{}'\", navigable.getClass().getName(), t));\n\n\t\t\t\t// For dependent beans, we are likely not going to see them ever again.\n\t\t\t\t// Call disable to clean them up.\n\t\t\t\tif (value.getClass().getDeclaredAnnotation(Dependent.class) != null)\n\t\t\t\t\tnavigable.disable();\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Listener to forward updates in the workspace to {@link #getNavigableChildren() navigable components}.\n\t */\n\tprivate class Forwarding implements ResourceJvmClassListener, ResourceAndroidClassListener, ResourceFileListener {\n\t\tprivate WorkspacePathNode workspacePath;\n\n\t\t@Override\n\t\tpublic void onNewClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls) {\n\t\t\t// Handle forwarding updates to remapped classes\n\t\t\tMappingResults mappingResults = cls.getPropertyValueOrNull(RemapOriginTaskProperty.KEY);\n\t\t\tif (mappingResults != null) {\n\t\t\t\tClassPathNode preMappingPath = mappingResults.getPreMappingPath(cls.getName());\n\t\t\t\tif (preMappingPath != null) {\n\t\t\t\t\tClassPathNode postMappingPath = workspacePath.child(resource).child(bundle).child(cls.getPackageName()).child(cls);\n\t\t\t\t\tfor (Navigable navigable : getNavigableChildrenByPath(preMappingPath))\n\t\t\t\t\t\tif (navigable instanceof UpdatableNavigable updatable)\n\t\t\t\t\t\t\tupdatable.onUpdatePath(postMappingPath);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo oldCls, @Nonnull JvmClassInfo newCls) {\n\t\t\tBundlePathNode bundlePath = workspacePath.child(resource).child(bundle);\n\t\t\tClassPathNode path = bundlePath.child(oldCls.getPackageName()).child(oldCls);\n\t\t\tfor (Navigable navigable : getNavigableChildrenByPath(path))\n\t\t\t\tif (navigable instanceof UpdatableNavigable updatable)\n\t\t\t\t\tupdatable.onUpdatePath(bundlePath.child(newCls.getPackageName()).child(newCls));\n\t\t}\n\n\t\t@Override\n\t\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls) {\n\t\t\tClassPathNode path = workspacePath.child(resource).child(bundle).child(cls.getPackageName()).child(cls);\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tfor (Navigable navigable : getNavigableChildrenByPath(path))\n\t\t\t\t\tnavigable.disable();\n\t\t\t});\n\t\t}\n\n\t\t@Override\n\t\tpublic void onNewClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo cls) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo oldCls, @Nonnull AndroidClassInfo newCls) {\n\t\t\tBundlePathNode bundlePath = workspacePath.child(resource).child(bundle);\n\t\t\tClassPathNode path = bundlePath.child(oldCls.getPackageName()).child(oldCls);\n\t\t\tfor (Navigable navigable : getNavigableChildrenByPath(path))\n\t\t\t\tif (navigable instanceof UpdatableNavigable updatable)\n\t\t\t\t\tupdatable.onUpdatePath(bundlePath.child(newCls.getPackageName()).child(newCls));\n\t\t}\n\n\t\t@Override\n\t\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo cls) {\n\t\t\tClassPathNode path = workspacePath.child(resource).child(bundle).child(cls.getPackageName()).child(cls);\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tfor (Navigable navigable : getNavigableChildrenByPath(path))\n\t\t\t\t\tnavigable.disable();\n\t\t\t});\n\t\t}\n\n\t\t@Override\n\t\tpublic void onNewFile(@Nonnull WorkspaceResource resource, @Nonnull FileBundle bundle, @Nonnull FileInfo file) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic void onUpdateFile(@Nonnull WorkspaceResource resource, @Nonnull FileBundle bundle, @Nonnull FileInfo oldFile, @Nonnull FileInfo newFile) {\n\t\t\tBundlePathNode bundlePath = workspacePath.child(resource).child(bundle);\n\t\t\tFilePathNode path = bundlePath.child(oldFile.getDirectoryName()).child(oldFile);\n\t\t\tfor (Navigable navigable : getNavigableChildrenByPath(path))\n\t\t\t\tif (navigable instanceof UpdatableNavigable updatable)\n\t\t\t\t\tupdatable.onUpdatePath(bundlePath.child(newFile.getDirectoryName()).child(newFile));\n\t\t}\n\n\t\t@Override\n\t\tpublic void onRemoveFile(@Nonnull WorkspaceResource resource, @Nonnull FileBundle bundle, @Nonnull FileInfo file) {\n\t\t\tFilePathNode path = workspacePath.child(resource).child(bundle).child(file.getDirectoryName()).child(file);\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tfor (Navigable navigable : getNavigableChildrenByPath(path))\n\t\t\t\t\tnavigable.disable();\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Dummy node for initial state of {@link #path}.\n\t */\n\tprivate static class DummyInitialNode extends AbstractPathNode<Object, Object> {\n\t\tprivate DummyInitialNode() {\n\t\t\tsuper(\"dummy\", null, new Object());\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Set<String> directParentTypeIds() {\n\t\t\treturn Collections.emptySet();\n\t\t}\n\n\t\t@Override\n\t\tpublic int localCompare(PathNode<?> o) {\n\t\t\treturn -1;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/navigation/NavigationManagerConfig.java",
    "content": "package software.coley.recaf.services.navigation;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\nimport software.coley.recaf.services.config.ConfigIconManager;\n\n/**\n * Config for {@link NavigationManager}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class NavigationManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic NavigationManagerConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, NavigationManager.ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/navigation/UnsupportedContentException.java",
    "content": "package software.coley.recaf.services.navigation;\n\nimport software.coley.recaf.info.Info;\n\n/**\n * Exception used to denote a {@link Info} type couldn't be shown in the UI due to lack of support.\n *\n * @author Matt Coley\n */\npublic class UnsupportedContentException extends RuntimeException {\n\t/**\n\t * @param message\n\t * \t\tException message.\n\t */\n\tpublic UnsupportedContentException(String message) {\n\t\tsuper(message);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/navigation/UpdatableNavigable.java",
    "content": "package software.coley.recaf.services.navigation;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.ui.pane.editing.ClassPane;\nimport software.coley.recaf.path.PathNode;\n\n/**\n * Navigable subtype with intent for content to be updated.\n *\n * @author Matt Coley\n */\npublic interface UpdatableNavigable extends Navigable {\n\t/**\n\t * Called when the underlying content of the path is updated.\n\t * The new content is wrapped in a new path, which is passed to this method.\n\t * <br>\n\t * Implementations must update their internal {@link #getPath()} value\n\t * if the passed path type is the same as the one they represent.\n\t * For instance, a {@link ClassPane} will have this called when\n\t * a user modifies a class. It will then update its {@link ClassPane#getPath()} backing value.\n\t * It will then pass along the update message to its {@link ClassPane#getNavigableChildren()}.\n\t * Content displaying {@link ClassMember} values should re-fetch the member info from the new path\n\t * to maintain correctness.\n\t *\n\t * @param path\n\t * \t\tNew path value.\n\t */\n\tvoid onUpdatePath(@Nonnull PathNode<?> path);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/translation/LangConfig.java",
    "content": "package software.coley.recaf.services.translation;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.util.Lang;\n\n/**\n * Config to specify which language to use.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class LangConfig extends BasicConfigContainer {\n\tprivate final ObservableObject<SupportedLanguage> currentLanguage = new ObservableObject<>(SupportedLanguage.en_US);\n\n\t@Inject\n\tpublic LangConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, \"language\" + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"current\", SupportedLanguage.class, currentLanguage));\n\t\tcurrentLanguage.addChangeListener((ob, old, cur) -> Lang.setCurrentTranslations(cur.name()));\n\t}\n\n\t/**\n\t * @return Current translation language file name.\n\t */\n\t@Nonnull\n\tpublic ObservableObject<SupportedLanguage> getCurrentLanguage() {\n\t\treturn currentLanguage;\n\t}\n\n\t/**\n\t * Supported languages.\n\t */\n\tpublic enum SupportedLanguage {\n\t\tcs_CZ,\n\t\tde_DE,\n\t\ten_US,\n\t\tja_JP,\n\t\tru_RU,\n\t\tsv_SE,\n\t\tzh_CN\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/tutorial/TutorialWorkspaceBuilder.java",
    "content": "package software.coley.recaf.services.tutorial;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.commons.ClassRemapper;\nimport software.coley.cafedude.InvalidClassException;\nimport software.coley.cafedude.classfile.ClassFile;\nimport software.coley.cafedude.classfile.constant.CpUtf8;\nimport software.coley.cafedude.io.ClassFileReader;\nimport software.coley.cafedude.io.ClassFileWriter;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.IncompletePathException;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.services.comment.ClassComments;\nimport software.coley.recaf.services.comment.CommentManager;\nimport software.coley.recaf.services.comment.WorkspaceComments;\nimport software.coley.recaf.services.decompile.DecompilerManager;\nimport software.coley.recaf.services.decompile.filter.JvmBytecodeFilter;\nimport software.coley.recaf.services.mapping.BasicMappingsRemapper;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.tutorial.content.Chapter1;\nimport software.coley.recaf.services.tutorial.content.Chapter2;\nimport software.coley.recaf.services.tutorial.content.Chapter3;\nimport software.coley.recaf.services.tutorial.content.Chapter4;\nimport software.coley.recaf.services.tutorial.content.Chapter5;\nimport software.coley.recaf.services.tutorial.content.Chapter6;\nimport software.coley.recaf.services.tutorial.content.Chapter7;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.richtext.source.JavaContextActionManager;\nimport software.coley.recaf.util.ClassDefiner;\nimport software.coley.recaf.util.ExcludeFromJacocoGeneratedReport;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.threading.ThreadUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.ResourceJvmClassListener;\nimport software.coley.recaf.workspace.model.resource.RuntimeWorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder;\n\nimport java.awt.Toolkit;\nimport java.lang.reflect.Method;\nimport java.util.Objects;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Constructs instances of {@link TutorialWorkspace}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\n@ExcludeFromJacocoGeneratedReport(justification = \"Tutorial is for UI usage only, and is not testable in a meaningful way.\")\npublic class TutorialWorkspaceBuilder {\n\tprivate final CommentManager commentManager;\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final DecompilerManager decompilerManager;\n\tprivate final JavaContextActionManager javaContextActionManager;\n\tprivate final Actions actions;\n\tprivate final TutorialConfig tutorialConfig;\n\n\t@Inject\n\tpublic TutorialWorkspaceBuilder(@Nonnull CommentManager commentManager,\n\t                                @Nonnull WorkspaceManager workspaceManager,\n\t                                @Nonnull DecompilerManager decompilerManager,\n\t                                @Nonnull JavaContextActionManager javaContextActionManager,\n\t                                @Nonnull Actions actions,\n\t                                @Nonnull TutorialConfig tutorialConfig) {\n\t\tthis.commentManager = commentManager;\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.decompilerManager = decompilerManager;\n\t\tthis.javaContextActionManager = javaContextActionManager;\n\t\tthis.actions = actions;\n\t\tthis.tutorialConfig = tutorialConfig;\n\t}\n\n\t/**\n\t * @return New tutorial workspace instance.\n\t */\n\t@Nonnull\n\tpublic TutorialWorkspace generateWorkspace() {\n\t\tTutorialWorkspaceResource resource = new TutorialWorkspaceResource(new WorkspaceResourceBuilder());\n\t\tTutorialWorkspace workspace = new TutorialWorkspace(resource);\n\t\tJvmClassBundle bundle = resource.getJvmClassBundle();\n\n\t\t// TODO: Additional Chapters\n\t\t//  More docking and UI\n\t\t//     - Note mentioning how drag-drop works\n\t\t//        - Mention that it may be useful to have things side-by-side for this tutorial\n\t\t//     - Click tabs to collapse them if they are perpendicular (Accordion spanning)\n\t\t//  More searching\n\t\t//     - Local search\n\t\t//         - Control + F to open find\n\t\t//         - Control + R to open replace\n\t\t//            - Features of these search bars\n\t\t//     - Instruction disassembly search\n\t\t//  Mapping\n\t\t//     - Renaming junk into easy to understand names\n\t\t//     - Automated mass renaming\n\t\t//     - Advanced mapping\n\t\t//        - Minecraft mod example\n\t\t//           - Fabric because yarn mappings are easy to grab\n\t\t//           - Cover \"assume unique keys\"\n\t\t//  Deobfuscation\n\t\t//     - Different obfuscation examples\n\t\t//        - Showcase individual transformers\n\n\t\t// TODO: Only add content to workspace as user completes tutorial actions\n\t\tJvmClassInfo chapter1 = fromRuntimeClass(Chapter1.class);\n\t\tJvmClassInfo chapter2 = fromRuntimeClass(Chapter2.class);\n\t\tJvmClassInfo chapter3 = addSecretConstant(fromRuntimeClass(Chapter3.class));\n\t\tJvmClassInfo chapter4 = fromRuntimeClass(Chapter4.class);\n\t\tJvmClassInfo chapter5 = fromRuntimeClass(Chapter5.class);\n\t\tJvmClassInfo chapter6 = fromRuntimeClass(Chapter6.class);\n\t\tJvmClassInfo chapter7 = fromRuntimeClass(Chapter7.class);\n\t\tbundle.put(chapter1);\n\t\tbundle.put(chapter2);\n\t\tbundle.put(chapter3);\n\t\tbundle.put(chapter4);\n\t\tbundle.put(chapter5);\n\t\tbundle.put(chapter6);\n\t\tbundle.put(chapter7);\n\n\t\tClassPathNode chapter1path = PathNodes.classPath(workspace, resource, bundle, chapter1);\n\t\tClassPathNode chapter2path = PathNodes.classPath(workspace, resource, bundle, chapter2);\n\t\tClassPathNode chapter3path = PathNodes.classPath(workspace, resource, bundle, chapter3);\n\t\tClassPathNode chapter4path = PathNodes.classPath(workspace, resource, bundle, chapter4);\n\t\tClassPathNode chapter5path = PathNodes.classPath(workspace, resource, bundle, chapter5);\n\t\tClassPathNode chapter6path = PathNodes.classPath(workspace, resource, bundle, chapter6);\n\t\tClassPathNode chapter7path = PathNodes.classPath(workspace, resource, bundle, chapter7);\n\n\t\t// Fill in comments for all the classes so that the decompiler will show them.\n\t\tcommentManager.removeWorkspaceComments(workspace);\n\t\tWorkspaceComments workspaceComments = commentManager.getOrCreateWorkspaceComments(workspace);\n\n\t\tClassComments chapter1Comments = Objects.requireNonNull(workspaceComments.getOrCreateClassComments(chapter1path));\n\t\tchapter1Comments.setClassComment(Lang.get(\"tutorial.1.class\"));\n\t\tchapter1Comments.setFieldComment(\"message\", \"Ljava/lang/String;\", Lang.get(\"tutorial.1.field\"));\n\t\tchapter1Comments.setMethodComment(\"main\", \"([Ljava/lang/String;)V\", Lang.get(\"tutorial.1.main\"));\n\t\tchapter1Comments.setMethodComment(\"run\", \"()V\", Lang.get(\"tutorial.1.run\"));\n\n\t\tClassComments chapter2Comments = Objects.requireNonNull(workspaceComments.getOrCreateClassComments(chapter2path));\n\t\tchapter2Comments.setClassComment(Lang.get(\"tutorial.2.class\"));\n\t\tchapter2Comments.setFieldComment(\"findTheHiddenMessage\", \"I\", Lang.get(\"tutorial.2.field\"));\n\t\tchapter2Comments.setMethodComment(\"hiddenMethod\", \"()V\", Lang.get(\"tutorial.2.method\"));\n\n\t\tClassComments chapter3Comments = Objects.requireNonNull(workspaceComments.getOrCreateClassComments(chapter3path));\n\t\tchapter3Comments.setClassComment(Lang.get(\"tutorial.3.class\"));\n\t\tchapter3Comments.setFieldComment(\"answer\", \"Ljava/lang/String;\", Lang.get(\"tutorial.3.field\"));\n\t\tchapter3Comments.setMethodComment(\"impl\", \"()V\", Lang.get(\"tutorial.3.method\"));\n\n\t\tClassComments chapter4Comments = Objects.requireNonNull(workspaceComments.getOrCreateClassComments(chapter4path));\n\t\tchapter4Comments.setClassComment(Lang.get(\"tutorial.4.class\"));\n\t\tchapter4Comments.setFieldComment(\"answer\", \"I\", Lang.get(\"tutorial.4.field\"));\n\t\tchapter4Comments.setMethodComment(\"main\", \"([Ljava/lang/String;)V\", Lang.get(\"tutorial.4.method\"));\n\n\t\tClassComments chapter5Comments = Objects.requireNonNull(workspaceComments.getOrCreateClassComments(chapter5path));\n\t\tchapter5Comments.setClassComment(Lang.get(\"tutorial.5.class\"));\n\t\tchapter5Comments.setMethodComment(\"main\", \"([Ljava/lang/String;)V\", Lang.get(\"tutorial.5.main\"));\n\t\tchapter5Comments.setMethodComment(\"decrypt\", \"()Ljava/lang/String;\", Lang.get(\"tutorial.5.decrypt\"));\n\t\tchapter5Comments.setMethodComment(\"nice\", \"()L\" + chapter6.getName() + \";\", Lang.get(\"tutorial.5.finished\"));\n\n\t\tClassComments chapter6Comments = Objects.requireNonNull(workspaceComments.getOrCreateClassComments(chapter6path));\n\t\tchapter6Comments.setClassComment(Lang.get(\"tutorial.6.class\"));\n\t\tchapter6Comments.setMethodComment(\"whereIsThisUsed\", \"()Ljava/lang/String;\", Lang.get(\"tutorial.6.method\"));\n\n\t\tClassComments chapter7Comments = Objects.requireNonNull(workspaceComments.getOrCreateClassComments(chapter7path));\n\t\tchapter7Comments.setClassComment(Lang.get(\"tutorial.7.class\"));\n\n\t\tAtomicBoolean hiddenChapter2 = new AtomicBoolean(true);\n\t\tAtomicBoolean usedJavaToBytecode = new AtomicBoolean(false);\n\t\tJvmBytecodeFilter decompileFilter = new JvmBytecodeFilter() {\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic byte[] filter(@Nonnull Workspace workspace, @Nonnull JvmClassInfo initialClassInfo, @Nonnull byte[] bytecode) {\n\t\t\t\tif (isChapter(2, initialClassInfo) && hiddenChapter2.get()) {\n\t\t\t\t\t// Filter class to only show the field\n\t\t\t\t\tClassReader reader = new ClassReader(bytecode);\n\t\t\t\t\tClassWriter cw = new ClassWriter(reader, 0);\n\t\t\t\t\tClassVisitor cv = new ClassVisitor(RecafConstants.getAsmVersion(), cw) {\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tpublic void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {\n\t\t\t\t\t\t\t// Filter out parents\n\t\t\t\t\t\t\tsuper.visit(version, access, name, signature, \"java/lang/Object\", null);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\t\t\t\t\t\t// Filter out methods\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\treader.accept(cv, ClassReader.SKIP_CODE);\n\t\t\t\t\treturn cw.toByteArray();\n\t\t\t\t}\n\t\t\t\treturn bytecode;\n\t\t\t}\n\t\t};\n\t\tJavaContextActionManager.SelectListener selectListener = memberPath -> {\n\t\t\tif (memberPath.getValue().getName().equals(\"hiddenMethod\")) {\n\t\t\t\t// Force refresh\n\t\t\t\thiddenChapter2.set(false);\n\t\t\t\tbundle.put(chapter2.toJvmClassBuilder().build());\n\t\t\t}\n\t\t};\n\t\tresource.addResourceJvmClassListener(new ResourceJvmClassListener() {\n\t\t\tprivate static final int delayMs = 600;\n\n\t\t\t@Override\n\t\t\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo oldCls, @Nonnull JvmClassInfo newCls) {\n\t\t\t\t// Confirm user changed the message\n\t\t\t\tif (isChapter(1, newCls) && !newCls.getFields().isEmpty() && !Chapter1.message.equals(newCls.getFields().getFirst().getDefaultValue())) {\n\t\t\t\t\tFxThreadUtil.delayedRun(delayMs, () -> open(chapter2path));\n\t\t\t\t}\n\n\t\t\t\t// Confirm user found the correct value\n\t\t\t\telse if (isChapter(3, newCls) && !newCls.getFields().isEmpty()) {\n\t\t\t\t\tif (\"LowLevelView\".equals(newCls.getFields().getFirst().getDefaultValue()))\n\t\t\t\t\t\tFxThreadUtil.delayedRun(delayMs, () -> open(chapter4path));\n\t\t\t\t\telse\n\t\t\t\t\t\tToolkit.getDefaultToolkit().beep();\n\t\t\t\t}\n\n\t\t\t\t// Confirm user found the correct value\n\t\t\t\telse if (isChapter(4, newCls) && !newCls.getFields().isEmpty() && Integer.valueOf(25565).equals(newCls.getFields().getFirst().getDefaultValue())) {\n\t\t\t\t\tFxThreadUtil.delayedRun(delayMs, () -> open(chapter5path));\n\t\t\t\t}\n\n\t\t\t\t// Confirm user used the 'java to bytecode' feature and correctly implemented the method\n\t\t\t\telse if (isChapter(5, newCls) && !usedJavaToBytecode.get()) {\n\t\t\t\t\tThreadUtil.runDelayed(delayMs, () -> { // Need to dispatch async after bundle is updated (it is not at this point in the listener)\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tString name = newCls.getName();\n\t\t\t\t\t\t\tClassDefiner definer = new ClassDefiner(name, newCls.getBytecode());\n\t\t\t\t\t\t\tClass<?> cls = definer.findClass(name);\n\t\t\t\t\t\t\tMethod decrypt = cls.getDeclaredMethod(\"decrypt\");\n\t\t\t\t\t\t\tObject result = decrypt.invoke(null);\n\t\t\t\t\t\t\tif (\"converted-with-ease\".equals(result)) {\n\t\t\t\t\t\t\t\tusedJavaToBytecode.set(true);\n\n\t\t\t\t\t\t\t\t// Update class to reveal a new method that tells the user how to go to the next chapter.\n\t\t\t\t\t\t\t\tClassReader reader = newCls.getClassReader();\n\t\t\t\t\t\t\t\tClassWriter writer = new ClassWriter(0);\n\t\t\t\t\t\t\t\tClassVisitor modder = new ClassVisitor(RecafConstants.getAsmVersion(), writer) {\n\t\t\t\t\t\t\t\t\t@Override\n\t\t\t\t\t\t\t\t\tpublic void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {\n\t\t\t\t\t\t\t\t\t\tsuper.visit(version, access, name, signature, superName, interfaces);\n\n\t\t\t\t\t\t\t\t\t\t// Insert this method at the top\n\t\t\t\t\t\t\t\t\t\tMethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, \"nice\", \"()L\" + chapter6.getName() + \";\", null, null);\n\t\t\t\t\t\t\t\t\t\tmv.visitCode();\n\t\t\t\t\t\t\t\t\t\tmv.visitInsn(Opcodes.ACONST_NULL);\n\t\t\t\t\t\t\t\t\t\tmv.visitInsn(Opcodes.ARETURN);\n\t\t\t\t\t\t\t\t\t\tmv.visitMaxs(1, 1);\n\t\t\t\t\t\t\t\t\t\tmv.visitEnd();\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t@Override\n\t\t\t\t\t\t\t\t\tpublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {\n\t\t\t\t\t\t\t\t\t\t// Keep only our inserted method above, and the decrypt method the user just implemented\n\t\t\t\t\t\t\t\t\t\tif (name.indexOf(' ') < 0)\n\t\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t\treturn super.visitMethod(access, name, descriptor, signature, exceptions);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\treader.accept(modder, 0);\n\t\t\t\t\t\t\t\tbyte[] bytes = writer.toByteArray();\n\t\t\t\t\t\t\t\tbundle.put(name, new JvmClassInfoBuilder(bytes).build());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\t\tToolkit.getDefaultToolkit().beep();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// End the tutorial\n\t\t\t\telse if (isChapter(7, newCls)) {\n\t\t\t\t\ttutorialConfig.getFinishedTutorial().setValue(true);\n\t\t\t\t\tThreadUtil.runDelayed(delayMs, () -> workspaceManager.setCurrent(null));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void onNewClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls) {}\n\n\t\t\t@Override\n\t\t\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls) {}\n\t\t});\n\t\tdecompilerManager.addJvmBytecodeFilter(decompileFilter);\n\t\tworkspaceManager.addWorkspaceOpenListener(w -> {\n\t\t\tif (workspace == w) {\n\t\t\t\tdecompilerManager.addJvmBytecodeFilter(decompileFilter);\n\t\t\t\tjavaContextActionManager.addSelectListener(selectListener);\n\t\t\t\topen(chapter1path);\n\t\t\t} else {\n\t\t\t\tdecompilerManager.removeJvmBytecodeFilter(decompileFilter);\n\t\t\t\tjavaContextActionManager.removeSelectListener(selectListener);\n\t\t\t}\n\t\t});\n\t\treturn workspace;\n\t}\n\n\tprivate void open(@Nonnull ClassPathNode path) {\n\t\tFxThreadUtil.run(() -> {\n\t\t\ttry {\n\t\t\t\tactions.gotoDeclaration(path);\n\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\tthrow new RuntimeException(\"Failed navigating to class\", ex);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate static boolean isChapter(int chapter, @Nonnull ClassInfo cls) {\n\t\treturn cls.getName().endsWith(\"Chapter\" + chapter);\n\t}\n\n\t@Nonnull\n\tprivate static JvmClassInfo addSecretConstant(@Nonnull JvmClassInfo cls) {\n\t\tbyte[] modified;\n\t\ttry {\n\t\t\tClassFileReader reader = new ClassFileReader();\n\t\t\tClassFile file = reader.read(cls.getBytecode());\n\t\t\tfile.getPool().add(new CpUtf8(\"You found the secret! Change back to the 'Decompile' view and put it in the answer field.\"));\n\t\t\tfile.getPool().add(new CpUtf8(\"LowLevelView\"));\n\n\t\t\tClassFileWriter writer = new ClassFileWriter();\n\t\t\tmodified = writer.write(file);\n\t\t} catch (InvalidClassException ex) {\n\t\t\tthrow new RuntimeException(\"Failed to read class\", ex);\n\t\t}\n\t\treturn cls.toJvmClassBuilder()\n\t\t\t\t.withBytecode(modified)\n\t\t\t\t.build();\n\t}\n\n\t@Nonnull\n\tprivate static JvmClassInfo fromRuntimeClass(@Nonnull Class<?> c) {\n\t\tbyte[] classBytes = RuntimeWorkspaceResource.getRuntimeClass(c);\n\t\tif (classBytes == null)\n\t\t\tthrow new RuntimeException(\"Failed reading tutorial class: \" + c.getName());\n\n\t\tClassWriter cw = new ClassWriter(0);\n\t\tClassRemapper remapper = new ClassRemapper(cw, new BasicMappingsRemapper(chapterMappings));\n\t\tnew ClassReader(classBytes).accept(remapper, 0);\n\t\treturn new JvmClassInfoBuilder(cw.toByteArray()).build();\n\t}\n\n\tprivate static final Mappings chapterMappings = new Mappings() {\n\t\tprivate static final String chapterPackage = Chapter1.class.getPackageName().replace('.', '/');\n\n\t\t@Override\n\t\tpublic String getMappedClassName(@Nonnull String internalName) {\n\t\t\t// Move the classes to the default package\n\t\t\tif (internalName.startsWith(chapterPackage))\n\t\t\t\treturn internalName.substring(chapterPackage.length() + 1);\n\t\t\treturn internalName;\n\t\t}\n\n\t\t@Override\n\t\tpublic String getMappedFieldName(@Nonnull String ownerName, @Nonnull String fieldName, @Nonnull String fieldDesc) {\n\t\t\treturn fieldName;\n\t\t}\n\n\t\t@Override\n\t\tpublic String getMappedMethodName(@Nonnull String ownerName, @Nonnull String methodName, @Nonnull String methodDesc) {\n\t\t\t// Rename the chapter-5 decrypt method\n\t\t\tif (ownerName.endsWith(\"Chapter5\")\n\t\t\t\t\t&& methodName.equals(\"decrypt\")\n\t\t\t\t\t&& methodDesc.equals(\"(Ljava/lang/String;)Ljava/lang/String;\"))\n\t\t\t\treturn \"this whole sentence is the name of the method\";\n\t\t\treturn methodName;\n\t\t}\n\n\t\t@Override\n\t\tpublic String getMappedVariableName(@Nonnull String className, @Nonnull String methodName, @Nonnull String methodDesc, @Nullable String name, @Nullable String desc, int index) {\n\t\t\treturn name;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic IntermediateMappings exportIntermediate() {\n\t\t\tthrow new IllegalStateException();\n\t\t}\n\t};\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/tutorial/content/Chapter1.java",
    "content": "package software.coley.recaf.services.tutorial.content;\n\n@SuppressWarnings(\"all\")\npublic class Chapter1 {\n\t// Just ask the user to change this string and save the changes\n\tpublic static final String message = \"Hello World!\";\n\n\tpublic static void main(String[] args) {\n\t\trun();\n\t}\n\n\tprivate static void run() {\n\t\ttry {\n\t\t\t// The tutorial will use field 'DefaultValue' attributes in a few cases and\n\t\t\t// that means making the fields 'static final' which will inline primitives.\n\t\t\t// This has the unfortunate downside of inlining at compile time, which we\n\t\t\t// want to avoid to prevent confusion.\n\t\t\tObject theMessage = Chapter1.class.getDeclaredFields()[0].get(null);\n\t\t\tSystem.out.println(theMessage);\n\t\t} catch (IllegalAccessException e) {\n\t\t\tthrow new RuntimeException(e);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/tutorial/content/Chapter2.java",
    "content": "package software.coley.recaf.services.tutorial.content;\n\npublic class Chapter2 extends Chapter3 {\n\t// The class should be filtered to only show this field.\n\t//  - No super-type\n\t//  - No methods\n\t// We will tell the user to use the 'fields & methods' tab to find the method.\n\tprivate int findTheHiddenMessage;\n\n\tprivate static void hiddenMethod() {\n\t\t// Once this method is revealed, tell them to use the 'inheritance' tab to\n\t\t// see that this extends the class representing the next class\n\t\tSystem.out.println(\"You found me!\");\n\t\tSystem.out.println(\"Now go to the next chapter in the 'Inheritance' tab!\");\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/tutorial/content/Chapter3.java",
    "content": "package software.coley.recaf.services.tutorial.content;\n\n// A hidden value will be inserted into the constant pool of this class, but never referenced.\n// We will instruct the user to use the low level view to find it.\npublic class Chapter3 implements Runnable {\n\t// Answer is the 'LowLevelView'\n\t@SuppressWarnings(\"all\")\n\tprivate static final String answer = \"Put_Your_Answer_Here\";\n\n\t@Override\n\tpublic void run() {\n\t\timpl();\n\t}\n\n\tprivate static void impl() {\n\t\ttry {\n\t\t\tObject theAnswer = Chapter3.class.getDeclaredFields()[0].get(null);\n\t\t\tSystem.out.println(\"Finding secrets is easy with: \" + theAnswer);\n\t\t} catch (IllegalAccessException ex) {\n\t\t\tthrow new RuntimeException(ex);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/tutorial/content/Chapter4.java",
    "content": "package software.coley.recaf.services.tutorial.content;\n\n// This class shows some math operations.\n// The goal is to find the value of what gets passed to 'consume'.\n// The way this math is set up is such that it can be entirely simulated by the JASM analysis engine.\n//\n// When the user opens the 'Analysis' tab in the assembler and looks at the final assignment to 'c'\n// they should find the answer. This chapter teaches them how to open the assembler and use the analysis tab.\npublic class Chapter4 {\n\t// 25565\n\tprivate static final int answer = 0;\n\n\tpublic static void main(String[] args) {\n\t\tint a = \"a\".repeat(16).length();\n\t\tint b = Integer.parseInt(\"FF\", a);\n\t\tint c = Integer.parseInt(\"FF\", a << 1);\n\t\ta = Integer.min(a, b);\n\t\tb = (c / a) << 10;\n\t\ta = (int) Math.floor(b * 0.83);\n\t\tc = b - ((c * 10) - (a / 1000));\n\t\ta = -100 + (a - b) / 40;\n\t\tc += a;\n\t\tconsume(c);\n\t}\n\n\tprivate static void consume(int value) {\n\t\tSystem.out.println(value);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/tutorial/content/Chapter5.java",
    "content": "package software.coley.recaf.services.tutorial.content;\n\n// We will now ask the user to use the 'Java to bytecode' tool in the assembler.\n// The decrypt method is a stub, and they will need to implement it.\n// In the tutorial builder we will obfuscate this class so that they can't just recompile it.\npublic class Chapter5 {\n\t// This method will get replaced when the user succeeds and point the user to the next chapter.\n\tpublic static void main(String[] args) {\n\t\tSystem.out.println(decrypt());\n\t}\n\n\tpublic static String decrypt() {\n\t\treturn decrypt(\"쭴쭸쭹쭡쭲쭥쭣쭲쭳쬺쭠쭾쭣쭿쬺쭲쭶쭤쭲\"); // converted-with-ease\n\t}\n\n\t// This method will be renamed to 'this whole sentence is the name of the method'\n\t@SuppressWarnings(\"all\")\n\tprivate static String decrypt(String text) {\n//\t\t// \"this whole sentence is the name of the method\".hashCode();\n//\t\tint key = Thread.currentThread().getStackTrace()[1].getMethodName().hashCode();\n//\t\tchar[] chars = text.toCharArray();\n//\t\tfor (int i = 0; i < chars.length; i++)\n//\t\t\tchars[i] ^= key;\n//\t\treturn String.valueOf(chars);\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/tutorial/content/Chapter6.java",
    "content": "package software.coley.recaf.services.tutorial.content;\n\npublic class Chapter6 {\n\t// Tell the user about searching for references by right-clicking on it.\n\tpublic static String whereIsThisUsed() {\n\t\treturn \"Lets find out\";\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/tutorial/content/Chapter7.java",
    "content": "package software.coley.recaf.services.tutorial.content;\n\n// Whatever the last chapter is, when it is completed toggle a value in the Recaf config so that users aren't\n// always perma-prompted to redo the tutorial. It will remain in the help menu, but the message on the welcome\n// panel won't be in your face.\npublic class Chapter7 {\n\tpublic static void main(String[] args) {\n\t\tSystem.out.println(Chapter6.whereIsThisUsed());\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/window/WindowFactory.java",
    "content": "package software.coley.recaf.services.window;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.Scene;\nimport javafx.stage.Stage;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.ui.window.RecafStage;\n\n/**\n * Creates new windows.\n *\n * @author Matt Coley\n * @see WindowManager Manages which windows are active.\n */\n@ApplicationScoped\npublic class WindowFactory implements Service {\n\tpublic static final String SERVICE_ID = \"window-factory\";\n\tprivate final WindowFactoryConfig config;\n\tprivate final WindowManager windowManager;\n\n\t@Inject\n\tpublic WindowFactory(@Nonnull WindowFactoryConfig config, @Nonnull WindowManager windowManager) {\n\t\tthis.config = config;\n\t\tthis.windowManager = windowManager;\n\t}\n\n\t/**\n\t * Create a stage around the scene and register it.\n\t * <p>\n\t * <b>Must be on FX thread when calling.</b>\n\t *\n\t * @param scene\n\t * \t\tScene to pass to the created stage.\n\t * @param title\n\t * \t\tStage title binding.\n\t * @param minWidth\n\t * \t\tMinimum width of the stage.\n\t * @param minHeight\n\t * \t\tMinimum height of the stage.\n\t *\n\t * @return Created stage.\n\t */\n\t@Nonnull\n\tpublic Stage createAnonymousStage(@Nonnull Scene scene, @Nonnull ObservableValue<String> title, int minWidth, int minHeight) {\n\t\tStage stage = create(scene, minWidth, minHeight);\n\t\tstage.titleProperty().bind(title);\n\t\twindowManager.registerAnonymous(stage);\n\t\treturn stage;\n\t}\n\n\t/**\n\t * Create a stage around the scene and register it.\n\t * <p>\n\t * <b>Must be on FX thread when calling.</b>\n\t *\n\t * @param scene\n\t * \t\tScene to pass to the created stage.\n\t * @param title\n\t * \t\tStage title text.\n\t * @param minWidth\n\t * \t\tMinimum width of the stage.\n\t * @param minHeight\n\t * \t\tMinimum height of the stage.\n\t *\n\t * @return Created stage.\n\t */\n\t@Nonnull\n\tpublic Stage createAnonymousStage(@Nonnull Scene scene, @Nonnull String title, int minWidth, int minHeight) {\n\t\tStage stage = create(scene, minWidth, minHeight);\n\t\tstage.setTitle(title);\n\t\twindowManager.registerAnonymous(stage);\n\t\treturn stage;\n\t}\n\n\t@Nonnull\n\tprivate Stage create(@Nonnull Scene scene, int minWidth, int minHeight) {\n\t\tStage stage = new RecafStage();\n\t\tstage.setScene(scene);\n\t\tstage.setMinWidth(minWidth);\n\t\tstage.setMinHeight(minHeight);\n\t\treturn stage;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic WindowFactoryConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/window/WindowFactoryConfig.java",
    "content": "package software.coley.recaf.services.window;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link WindowFactory}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class WindowFactoryConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic WindowFactoryConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, WindowFactory.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/window/WindowManager.java",
    "content": "package software.coley.recaf.services.window;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.stage.Stage;\nimport javafx.stage.WindowEvent;\nimport org.slf4j.Logger;\nimport software.coley.collections.observable.ObservableList;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.ui.window.IdentifiableStage;\nimport software.coley.recaf.util.NodeEvents;\n\nimport java.util.Collection;\nimport java.util.ConcurrentModificationException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.UUID;\n\n/**\n * Manages active {@link Stage} windows.\n *\n * @author Matt Coley\n * @see IdentifiableStage Type added to children of {@link Stage} to support automatic discovery.\n * @see WindowFactory Service for creating new windows.\n */\n@ApplicationScoped\npublic class WindowManager implements Service {\n\tprivate static final Logger logger = Logging.get(WindowManager.class);\n\tpublic static final String SERVICE_ID = \"window-manager\";\n\tprivate static final String ANON_PREFIX = \"anon-\";\n\t// Built-in window keys\n\tpublic static final String WIN_MAIN = \"main\";\n\tpublic static final String WIN_REMOTE_VMS = \"remote-vms\";\n\tpublic static final String WIN_CONFIG = \"config\";\n\tpublic static final String WIN_INFO = \"system-information\";\n\tpublic static final String WIN_SCRIPTS = \"script-manager\";\n\tpublic static final String WIN_MAP_PROGRESS = \"mapping-progress\";\n\tpublic static final String WIN_QUICK_NAV = \"quick-nav\";\n\t// Manager instance data\n\tprivate final WindowStyling windowStyling;\n\tprivate final WindowManagerConfig config;\n\tprivate final ObservableList<Stage> activeWindows = new ObservableList<>();\n\tprivate final Map<String, Stage> windowMappings = new HashMap<>();\n\n\t@Inject\n\tpublic WindowManager(@Nonnull WindowStyling windowStyling, @Nonnull WindowManagerConfig config,\n\t                     @Nonnull Instance<IdentifiableStage> stages) {\n\t\tthis.windowStyling = windowStyling;\n\t\tthis.config = config;\n\n\t\t// Register identifiable stages.\n\t\t// These will be @Dependent scoped, so we need to be careful with their instances.\n\t\t// Interacting with named windows should always be done through this class to prevent duplicate allocations.\n\t\tfor (IdentifiableStage stage : stages)\n\t\t\tregister(stage.getId(), stage.asStage());\n\t}\n\n\t/**\n\t * Register listeners on the stage to monitor active state.\n\t *\n\t * @param stage\n\t * \t\tStage to register.\n\t */\n\tpublic void registerAnonymous(@Nonnull Stage stage) {\n\t\tregister(ANON_PREFIX + UUID.randomUUID(), stage);\n\t}\n\n\t/**\n\t * Registers listeners on the stage to monitor active state.\n\t *\n\t * @param identifiableStage\n\t * \t\tStage to register.\n\t */\n\tpublic void register(@Nonnull IdentifiableStage identifiableStage) {\n\t\tregister(identifiableStage.getId(), identifiableStage.asStage());\n\t}\n\n\t/**\n\t * Register listeners on the stage to monitor active state.\n\t *\n\t * @param id\n\t * \t\tUnique stage identifier.\n\t * @param stage\n\t * \t\tStage to register.\n\t */\n\tpublic void register(@Nonnull String id, @Nonnull Stage stage) {\n\t\t// Validate input, ensuring duplicate allocations are not allowed.\n\t\tif (windowMappings.containsKey(id))\n\t\t\tthrow new IllegalStateException(\"The stage ID was already registered: \" + id);\n\n\t\t// Add custom stylesheets if any are registered.\n\t\tif (!windowStyling.getStylesheetUris().isEmpty())\n\t\t\tNodeEvents.runOnceIfPresentOrOnChange(stage.sceneProperty(),\n\t\t\t\t\tscene -> scene.getStylesheets().addAll(windowStyling.getStylesheetUris()));\n\n\t\t// Record when windows are 'active' based on visibility.\n\t\t// We're using event filters so users can still do things like 'stage.setOnShown(...)' and not interfere with\n\t\t// our window tracking logic in here\n\t\tstage.addEventFilter(WindowEvent.WINDOW_SHOWN, e -> {\n\t\t\tlogger.trace(\"Stage showing: {}\", id);\n\t\t\tactiveWindows.add(stage);\n\t\t});\n\t\tstage.addEventFilter(WindowEvent.WINDOW_HIDDEN, e -> {\n\t\t\tlogger.trace(\"Stage hiding: {}\", id);\n\t\t\tactiveWindows.remove(stage);\n\n\t\t\t// Anonymous stages can be pruned from the id->stage map.\n\t\t\t// They are not meant to be persistent. But, we register them anyway for our duplicate register check above.\n\t\t\tif (id.startsWith(ANON_PREFIX)) {\n\t\t\t\tlogger.trace(\"Stage pruned: {} ({})\", id, stage.getTitle());\n\t\t\t\twindowMappings.remove(id);\n\t\t\t}\n\t\t});\n\n\t\t// If state is already visible, add it right away.\n\t\tif (stage.isShowing()) activeWindows.add(stage);\n\n\t\t// Register id --> stage\n\t\twindowMappings.put(id, stage);\n\t\tlogger.trace(\"Register stage: {}\", id);\n\t}\n\n\t/**\n\t * Do not use this list to iterate over if within your loop you will be closing/creating windows.\n\t * This will cause a {@link ConcurrentModificationException}. Wrap this result in a new collection\n\t * if you want to do that.\n\t *\n\t * @return Active windows.\n\t */\n\t@Nonnull\n\tpublic Collection<Stage> getActiveWindows() {\n\t\treturn activeWindows;\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tWindow identifier.\n\t *\n\t * @return Window by ID,\n\t * or {@code null} if no associated window for the ID exists.\n\t */\n\t@Nullable\n\tpublic Stage getWindow(@Nonnull String id) {\n\t\treturn windowMappings.get(id);\n\t}\n\n\t/**\n\t * @return Window for main display.\n\t */\n\t@Nonnull\n\tpublic Stage getMainWindow() {\n\t\treturn Objects.requireNonNull(getWindow(WIN_MAIN));\n\t}\n\n\t/**\n\t * @return Window for the remote VM list.\n\t */\n\t@Nonnull\n\tpublic Stage getRemoteVmWindow() {\n\t\treturn Objects.requireNonNull(getWindow(WIN_REMOTE_VMS));\n\t}\n\n\t/**\n\t * @return Window for the config display.\n\t */\n\t@Nonnull\n\tpublic Stage getConfigWindow() {\n\t\treturn Objects.requireNonNull(getWindow(WIN_CONFIG));\n\t}\n\n\t/**\n\t * @return Window for the system information display.\n\t */\n\t@Nonnull\n\tpublic Stage getSystemInfoWindow() {\n\t\treturn Objects.requireNonNull(getWindow(WIN_INFO));\n\t}\n\n\t/**\n\t * @return Window for the system information display.\n\t */\n\t@Nonnull\n\tpublic Stage getScriptManagerWindow() {\n\t\treturn Objects.requireNonNull(getWindow(WIN_SCRIPTS));\n\t}\n\n\t/**\n\t * @return Window for the current mapping preview display.\n\t */\n\t@Nonnull\n\tpublic Stage getMappingPreviewWindow() {\n\t\treturn Objects.requireNonNull(getWindow(WIN_MAP_PROGRESS));\n\t}\n\n\t/**\n\t * @return Window for quick navigation display.\n\t */\n\t@Nonnull\n\tpublic Stage getQuickNav() {\n\t\treturn Objects.requireNonNull(getWindow(WIN_QUICK_NAV));\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic WindowManagerConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/window/WindowManagerConfig.java",
    "content": "package software.coley.recaf.services.window;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link WindowManager}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class WindowManagerConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic WindowManagerConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, WindowManager.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/window/WindowStyling.java",
    "content": "package software.coley.recaf.services.window;\n\nimport com.google.common.hash.Hashing;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.instrument.util.Streams;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.Service;\nimport software.coley.recaf.services.file.RecafDirectoriesConfig;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Facilitates adding custom stylesheets to newly made windows.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class WindowStyling implements Service {\n\tpublic static final String SERVICE_ID = \"window-styling\";\n\tprivate static final Logger logger = Logging.get(WindowStyling.class);\n\tprivate final List<String> uris = new ArrayList<>();\n\tprivate final RecafDirectoriesConfig directoriesConfig;\n\tprivate final WindowStylingConfig config;\n\n\t@Inject\n\tpublic WindowStyling(@Nonnull RecafDirectoriesConfig directoriesConfig, @Nonnull WindowStylingConfig config) {\n\t\tthis.directoriesConfig = directoriesConfig;\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * Adds a stylesheet path.\n\t *\n\t * @param stylesheetPath\n\t * \t\tPath to a stylesheet file.\n\t */\n\tpublic void addStylesheet(@Nonnull Path stylesheetPath) {\n\t\turis.add(stylesheetPath.toUri().toString());\n\t}\n\n\t/**\n\t * Adds a stylesheet from the given url.\n\t *\n\t * @param stylesheetUrl\n\t * \t\tURL to the stylesheet.\n\t *\n\t * @return {@code false} when the content from the URL could not be loaded.\n\t * {@code true} the stylesheet was added successfully.\n\t */\n\tpublic boolean addStylesheet(@Nonnull URL stylesheetUrl) {\n\t\t// TODO: For 'file:/...' just add that url directly by mapping the file to a 'Path'\n\t\t//   String protocol = stylesheetUrl.getProtocol();\n\n\t\t// Extract content from URL\n\t\tbyte[] stylesheetContents;\n\t\tString tempStylesheetName;\n\t\ttry {\n\t\t\tstylesheetContents = Streams.readStream(stylesheetUrl.openStream());\n\t\t\ttempStylesheetName = Hashing.sha256().hashBytes(stylesheetContents) + \".css\";\n\t\t} catch (IOException ex) {\n\t\t\tlogger.error(\"Failed reading stylesheet from URL: {}\", stylesheetUrl, ex);\n\t\t\treturn false;\n\t\t}\n\n\t\t// Write to temp directory\n\t\tPath destinationPath;\n\t\ttry {\n\t\t\tdestinationPath = directoriesConfig.getTempDirectory().resolve(tempStylesheetName);\n\t\t\tFiles.write(destinationPath, stylesheetContents);\n\t\t} catch (IOException ex) {\n\t\t\tlogger.error(\"Failed reading stylesheet from URL: {}\", stylesheetUrl, ex);\n\t\t\treturn false;\n\t\t}\n\n\t\t// Add the temp path copy to the stylesheets URI list.\n\t\taddStylesheet(destinationPath);\n\t\treturn true;\n\t}\n\n\t/**\n\t * @return Registered stylesheet uri's.\n\t */\n\t@Nonnull\n\tpublic List<String> getStylesheetUris() {\n\t\treturn Collections.unmodifiableList(uris);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getServiceId() {\n\t\treturn SERVICE_ID;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic WindowStylingConfig getServiceConfig() {\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/services/window/WindowStylingConfig.java",
    "content": "package software.coley.recaf.services.window;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.ServiceConfig;\n\n/**\n * Config for {@link WindowStyling}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class WindowStylingConfig extends BasicConfigContainer implements ServiceConfig {\n\t@Inject\n\tpublic WindowStylingConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, WindowStyling.SERVICE_ID + CONFIG_SUFFIX);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/LanguageStylesheets.java",
    "content": "package software.coley.recaf.ui;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.ui.control.richtext.syntax.RegexLanguages;\n\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static software.coley.recaf.util.StringUtil.cutOffAtFirst;\nimport static software.coley.recaf.util.StringUtil.shortenPath;\n\n/**\n * Languages stylesheets for syntax highlighting.\n *\n * @author Matt Coley\n * @see RegexLanguages For retrieving matchers for the supported languages.\n */\npublic class LanguageStylesheets {\n\tprivate static final Map<String, String> NAME_TO_PATH = new HashMap<>();\n\tprivate static final String SHEET_JAVA;\n\tprivate static final String SHEET_JASM;\n\tprivate static final String SHEET_JSON;\n\tprivate static final String SHEET_XML;\n\tprivate static final String SHEET_ENIGMA;\n\n\t// Prevent construction\n\tprivate LanguageStylesheets() {\n\t}\n\n\tstatic {\n\t\tSHEET_JAVA = addLanguage(\"/syntax/java.css\");\n\t\tSHEET_JASM = addLanguage(\"/syntax/jasm.css\");\n\t\tSHEET_JSON = addLanguage(\"/syntax/json.css\");\n\t\tSHEET_XML = addLanguage(\"/syntax/xml.css\");\n\t\tSHEET_ENIGMA = addLanguage(\"/syntax/enigma.css\");\n\t}\n\n\t/**\n\t * Adds support for the language outlined by the contents of the given file.\n\t *\n\t * @param path\n\t * \t\tFull path to stylesheet file. The language name is the file name, without the extension.\n\t *\n\t * @return Full path to stylesheet file.\n\t */\n\t@Nonnull\n\tpublic static String addLanguage(@Nonnull String path) {\n\t\tString name = cutOffAtFirst(shortenPath(path), \".\");\n\t\treturn addLanguage(name, path);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tLanguage name to use as a key. Will be used in {@link #getLanguages()} and {@link #getLanguageStylesheet(String)}.\n\t * @param path\n\t * \t\tFull path to stylesheet file.\n\t *\n\t * @return Full path to stylesheet file.\n\t *\n\t * @see RegexLanguages#addLanguage(String, InputStream) You should assign a regex language model by the same language name.\n\t */\n\t@Nonnull\n\tpublic static String addLanguage(@Nonnull String name, @Nonnull String path) {\n\t\tNAME_TO_PATH.put(name, path);\n\t\treturn path;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName of the language, matching the file path inside Recaf's {@code /syntax/} directory,\n\t * \t\twithout the {@code .css} extension. To get {@link #getJavaStylesheet()} use {@code java}.\n\t *\n\t * @return Language stylesheet path, or {@code null} if unknown language.\n\t *\n\t * @see RegexLanguages#getLanguage(String) Language to match with.\n\t */\n\t@Nullable\n\tpublic static String getLanguageStylesheet(String name) {\n\t\treturn NAME_TO_PATH.get(name);\n\t}\n\n\t/**\n\t * @return Map of language names to stylesheet paths.\n\t */\n\t@Nonnull\n\tpublic static Map<String, String> getLanguages() {\n\t\treturn NAME_TO_PATH;\n\t}\n\n\t/**\n\t * @return Stylesheet for Java.\n\t *\n\t * @see RegexLanguages#getJavaLanguage()\n\t */\n\t@Nonnull\n\tpublic static String getJavaStylesheet() {\n\t\treturn SHEET_JAVA;\n\t}\n\n\t/**\n\t * @return Stylesheet for JASM.\n\t *\n\t * @see RegexLanguages#getJasmLanguage()\n\t */\n\t@Nonnull\n\tpublic static String getJasmStylesheet() {\n\t\treturn SHEET_JASM;\n\t}\n\n\t/**\n\t * @return Stylesheet for JSON.\n\t *\n\t * @see RegexLanguages#getJsonLanguage()\n\t */\n\t@Nonnull\n\tpublic static String getJsonStylesheet() {\n\t\treturn SHEET_JSON;\n\t}\n\n\t/**\n\t * @return Stylesheet for XML.\n\t *\n\t * @see RegexLanguages#getXmlLanguage()\n\t */\n\t@Nonnull\n\tpublic static String getXmlStylesheet() {\n\t\treturn SHEET_XML;\n\t}\n\n\t/**\n\t * @return Stylesheet for Engima.\n\t *\n\t * @see RegexLanguages#getLangEngimaMap()\n\t */\n\t@Nonnull\n\tpublic static String getEnigmaStylesheet() {\n\t\treturn SHEET_ENIGMA;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/RecafTheme.java",
    "content": "package software.coley.recaf.ui;\n\nimport atlantafx.base.theme.Theme;\nimport jakarta.annotation.Nullable;\n\n/**\n * AtlantaFX Recaf theme.\n *\n * @author Matt Coley\n */\npublic class RecafTheme implements Theme {\n\t@Override\n\tpublic String getName() {\n\t\treturn \"recaf\";\n\t}\n\n\t@Override\n\tpublic String getUserAgentStylesheet() {\n\t\treturn \"/style/recaf.css\";\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic String getUserAgentStylesheetBSS() {\n\t\t// Not used\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic boolean isDarkMode() {\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/config/Binding.java",
    "content": "package software.coley.recaf.ui.config;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * Wrapper to act as bind utility.\n *\n * @author Matt Coley\n */\npublic class Binding extends ArrayList<String> {\n\tprivate final String id;\n\n\t/**\n\t * @param id\n\t * \t\tUnique identifier of the binding.\n\t */\n\tpublic Binding(@Nonnull String id) {\n\t\tthis.id = id;\n\t}\n\n\t/**\n\t * @return Unique identifier of the binding.\n\t */\n\t@Nonnull\n\tpublic String getId() {\n\t\treturn id;\n\t}\n\n\t/**\n\t * @param original\n\t * \t\tOriginal key to replace.\n\t * @param replacement\n\t * \t\tReplacement key.\n\t *\n\t * @return Copy of the binding with the given key replaced.\n\t */\n\t@Nonnull\n\tpublic Binding withReplacement(@Nonnull KeyCode original, @Nonnull KeyCode replacement) {\n\t\tBinding copy = new Binding(id);\n\t\tString toMatch = original.getName().toLowerCase();\n\t\tString toReplace = replacement.getName().toLowerCase();\n\t\tfor (String item : this) {\n\t\t\tif (item.equals(toMatch))\n\t\t\t\tcopy.add(toReplace);\n\t\t\telse\n\t\t\t\tcopy.add(item);\n\t\t}\n\t\treturn copy;\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tUnique identifier of the binding.\n\t * @param codesString\n\t * \t\tSeries of keys for a keybind, split by '+'.\n\t * @param mask\n\t * \t\tOptional mask to include.\n\t *\n\t * @return Binding from keys + mask.\n\t */\n\t@Nonnull\n\tpublic static Binding newBind(@Nonnull String id, @Nonnull String codesString, @Nullable KeyCode mask) {\n\t\tString[] codes = codesString.split(\"\\\\+\");\n\t\tStream<String> stream = Arrays.stream(codes);\n\t\tif (mask != null)\n\t\t\tstream = Stream.concat(Stream.of(mask.getName()), stream);\n\t\treturn stream.map(String::toLowerCase).collect(Collectors.toCollection(() -> new Binding(id)));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tUnique identifier of the binding.\n\t * @param event\n\t * \t\tKey event.\n\t *\n\t * @return Binding from keys of the event.\n\t */\n\t@Nonnull\n\tpublic static Binding newBind(@Nonnull String id, @Nonnull KeyEvent event) {\n\t\treturn newBind(id, namesOf(event));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tUnique identifier of the binding.\n\t * @param codes\n\t * \t\tSeries of JFX {@link KeyCode} for a keybind. Unlike {@link #newBind(String, String, KeyCode)}\n\t * \t\tit is implied that the mask is given in this series, if one is intended.\n\t *\n\t * @return Binding from keys.\n\t */\n\t@Nonnull\n\tpublic static Binding newBind(@Nonnull String id, @Nonnull KeyCode... codes) {\n\t\treturn Arrays.stream(codes).map(KeyCode::getName)\n\t\t\t\t.map(String::toLowerCase)\n\t\t\t\t.collect(Collectors.toCollection(() -> new Binding(id)));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tUnique identifier of the binding.\n\t * @param codes\n\t * \t\tSeries of keys for a keybind, in string representation.\n\t *\n\t * @return Binding from keys.\n\t */\n\t@Nonnull\n\tpublic static Binding from(@Nonnull String id, @Nonnull Collection<String> codes) {\n\t\treturn codes.stream()\n\t\t\t\t.map(String::toLowerCase)\n\t\t\t\t.sorted((a, b) -> (a.length() > b.length()) ? -1 : a.compareTo(b))\n\t\t\t\t.collect(Collectors.toCollection(() -> new Binding(id)));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tUnique identifier of the binding.\n\t * @param codes\n\t * \t\tSeries of keys for a keybind.\n\t *\n\t * @return Binding from keys.\n\t */\n\t@Nonnull\n\tpublic static Binding newBind(@Nonnull String id, @Nonnull Collection<String> codes) {\n\t\treturn codes.stream()\n\t\t\t\t.map(String::toLowerCase)\n\t\t\t\t.sorted((a, b) -> (a.length() > b.length()) ? -1 : a.compareTo(b))\n\t\t\t\t.collect(Collectors.toCollection(() -> new Binding(id)));\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn String.join(\" + \", this);\n\t}\n\n\t/**\n\t * @param event\n\t * \t\tJFX key event.\n\t *\n\t * @return {@code true} if the event matches the current bind.\n\t */\n\tpublic boolean match(@Nonnull KeyEvent event) {\n\t\tif (size() == 1)\n\t\t\t// Simple 1-key check\n\t\t\treturn event.getCode().getName().equalsIgnoreCase(get(0));\n\t\telse if (size() > 1) {\n\t\t\t// Checking binds with masks\n\t\t\tSet<String> bindSet = new HashSet<>(this);\n\t\t\tSet<String> eventSet = namesOf(event);\n\t\t\treturn bindSet.equals(eventSet);\n\t\t} else\n\t\t\tthrow new IllegalStateException(\"Keybind must have have at least 1 key!\");\n\t}\n\n\t/**\n\t * @param event\n\t * \t\tEvent to get key names of <i>(Key name plus mask name)</i>.\n\t *\n\t * @return Key names of the event.\n\t */\n\t@Nonnull\n\tpublic static Set<String> namesOf(@Nonnull KeyEvent event) {\n\t\tSet<String> eventSet = new LinkedHashSet<>();\n\t\tif (event.isControlDown())\n\t\t\teventSet.add(nameOf(KeyCode.CONTROL));\n\t\tif (event.isMetaDown())\n\t\t\teventSet.add(nameOf(KeyCode.META));\n\t\tif (event.isAltDown())\n\t\t\teventSet.add(nameOf(KeyCode.ALT));\n\t\tif (event.isShiftDown())\n\t\t\teventSet.add(nameOf(KeyCode.SHIFT));\n\t\teventSet.add(nameOf(event.getCode()));\n\t\treturn eventSet;\n\t}\n\n\t/**\n\t * @param code\n\t * \t\tKey code to get name of.\n\t *\n\t * @return Key name of the event.\n\t */\n\t@Nonnull\n\tpublic static String nameOf(KeyCode code) {\n\t\treturn code.getName().toLowerCase();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/config/BindingCreator.java",
    "content": "package software.coley.recaf.ui.config;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.PlatformType;\n\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * Keybinding creator for creating different binding in different OS's.\n *\n * @author TimmyOVO\n */\npublic final class BindingCreator {\n\tprivate final Binding defaultBinding;\n\tprivate final Map<PlatformType, Binding> osBindings;\n\n\tprivate BindingCreator(@Nonnull Binding defaultBinding, OSBinding... osBindings) {\n\t\tthis.defaultBinding = defaultBinding;\n\t\tthis.osBindings = Arrays.stream(PlatformType.values())\n\t\t\t\t.collect(Collectors.toMap(Function.identity(), os -> defaultBinding));\n\t\tthis.osBindings.putAll(\n\t\t\t\tArrays.stream(osBindings)\n\t\t\t\t\t\t.collect(Collectors.toMap(\n\t\t\t\t\t\t\t\tosBinding -> osBinding.platform,\n\t\t\t\t\t\t\t\tosBinding -> osBinding.binding\n\t\t\t\t\t\t))\n\t\t);\n\t}\n\n\t/**\n\t * Build a KeybindingCreator to include all os specified keybinding.\n\t *\n\t * @param defaultBinding\n\t * \t\tIf osBindings is empty, all os's keybinding will be the same.\n\t * @param osBindings\n\t * \t\tOS specified keybinding.\n\t *\n\t * @return A KeybindingCreator instance.\n\t */\n\t@Nonnull\n\tpublic static BindingCreator bindings(@Nonnull Binding defaultBinding, OSBinding... osBindings) {\n\t\treturn new BindingCreator(defaultBinding, osBindings);\n\t}\n\n\t/**\n\t * Match keybinding for current using os.\n\t *\n\t * @return A Binding instance.\n\t */\n\t@Nonnull\n\tpublic Binding buildKeyBindingForCurrentOS() {\n\t\treturn osBindings.getOrDefault(PlatformType.get(), defaultBinding);\n\t}\n\n\t/**\n\t * OS specified keybinding wrapper.\n\t */\n\tpublic static class OSBinding {\n\t\tpublic PlatformType platform;\n\t\tpublic Binding binding;\n\n\t\tprivate OSBinding(@Nonnull PlatformType platform, @Nonnull Binding binding) {\n\t\t\tthis.platform = platform;\n\t\t\tthis.binding = binding;\n\t\t}\n\n\t\t/**\n\t\t * Build a key binding instance for specified os.\n\t\t *\n\t\t * @param platform\n\t\t * \t\tThe platform to be specified.\n\t\t * @param binding\n\t\t * \t\tKey binding.\n\t\t *\n\t\t * @return the instance of OSBinding.\n\t\t */\n\t\t@Nonnull\n\t\tpublic static OSBinding newOsBind(@Nonnull PlatformType platform, @Nonnull Binding binding) {\n\t\t\treturn new OSBinding(platform, binding);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/config/ClassEditingConfig.java",
    "content": "package software.coley.recaf.ui.config;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.ui.pane.editing.android.AndroidClassEditorType;\nimport software.coley.recaf.ui.pane.editing.ClassPane;\nimport software.coley.recaf.ui.pane.editing.jvm.JvmClassEditorType;\n\n/**\n * Config for {@link ClassPane} and its child types, plus any components they declare.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ClassEditingConfig extends BasicConfigContainer {\n\tpublic static final String ID = \"class-editing\";\n\tprivate final ObservableObject<AndroidClassEditorType> defaultAndroidEditor = new ObservableObject<>(AndroidClassEditorType.DECOMPILE);\n\tprivate final ObservableObject<JvmClassEditorType> defaultJvmEditor = new ObservableObject<>(JvmClassEditorType.DECOMPILE);\n\n\t@Inject\n\tpublic ClassEditingConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, ID + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"default-android-editor\", AndroidClassEditorType.class, defaultAndroidEditor));\n\t\taddValue(new BasicConfigValue<>(\"default-jvm-editor\", JvmClassEditorType.class, defaultJvmEditor));\n\t}\n\n\t/**\n\t * @return Default editor to display {@link AndroidClassInfo} content with.\n\t */\n\tpublic ObservableObject<AndroidClassEditorType> getDefaultAndroidEditor() {\n\t\treturn defaultAndroidEditor;\n\t}\n\n\t/**\n\t * @return Default editor to display {@link JvmClassInfo} content with.\n\t */\n\tpublic ObservableObject<JvmClassEditorType> getDefaultJvmEditor() {\n\t\treturn defaultJvmEditor;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/config/ExportConfig.java",
    "content": "package software.coley.recaf.ui.config;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.workspace.io.WorkspaceCompressType;\nimport software.coley.recaf.workspace.PathExportingManager;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Config for export options.\n *\n * @author Matt Coley\n * @see PathExportingManager\n */\n@ApplicationScoped\npublic class ExportConfig extends BasicConfigContainer {\n\tprivate final ObservableObject<WorkspaceCompressType> compression\n\t\t\t= new ObservableObject<>(WorkspaceCompressType.MATCH_ORIGINAL);\n\tprivate final ObservableBoolean bundleSupportingResources = new ObservableBoolean(false);\n\tprivate final ObservableBoolean createZipDirEntries = new ObservableBoolean(true);\n\tprivate final ObservableBoolean warnNoChanges = new ObservableBoolean(true);\n\n\t@Inject\n\tpublic ExportConfig() {\n\t\tsuper(ConfigGroups.SERVICE_IO, \"export\" + CONFIG_SUFFIX);\n\t\t// Add values\n\t\taddValue(new BasicConfigValue<>(\"compression\", WorkspaceCompressType.class, compression));\n\t\taddValue(new BasicConfigValue<>(\"bundle-supporting-resources\", boolean.class, bundleSupportingResources));\n\t\taddValue(new BasicConfigValue<>(\"create-zip-dir-entries\", boolean.class, createZipDirEntries));\n\t\taddValue(new BasicConfigValue<>(\"warn-no-changes\", boolean.class, warnNoChanges));\n\t}\n\n\t/**\n\t * @return Compression type to use when exporting workspaces.\n\t */\n\t@Nonnull\n\tpublic ObservableObject<WorkspaceCompressType> getCompression() {\n\t\treturn compression;\n\t}\n\n\t/**\n\t * @return {@code true} to include {@link Workspace#getSupportingResources()} in the output.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getBundleSupportingResources() {\n\t\treturn bundleSupportingResources;\n\t}\n\n\t/**\n\t * For classes, these entries would be their package names.\n\t * This data is not strictly required for a functional ZIP/JAR file, but some software implementations\n\t * may expect these entries to exist. For this reason we allow the user to choose if they want to include\n\t * this data in outputs.\n\t *\n\t * @return {@code true} to create ZIP entries for directory paths.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getCreateZipDirEntries() {\n\t\treturn createZipDirEntries;\n\t}\n\n\t/**\n\t * @return {@code true} to warn users when no changes were made prior to exporting.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getWarnNoChanges() {\n\t\treturn warnNoChanges;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/config/KeybindingConfig.java",
    "content": "package software.coley.recaf.ui.config;\n\nimport com.google.gson.JsonElement;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.event.Event;\nimport javafx.scene.Node;\nimport javafx.scene.control.TextField;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.GridPane;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableMap;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicMapConfigValue;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.services.config.ConfigComponentManager;\nimport software.coley.recaf.services.config.TypedConfigComponentFactory;\nimport software.coley.recaf.services.json.GsonProvider;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.ui.pane.editing.ClassPane;\nimport software.coley.recaf.ui.pane.editing.FilePane;\nimport software.coley.recaf.ui.pane.editing.jvm.JvmDecompilerPane;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.PlatformType;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.TreeMap;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\nimport static javafx.scene.input.KeyCode.*;\nimport static software.coley.recaf.ui.config.Binding.nameOf;\nimport static software.coley.recaf.ui.config.Binding.newBind;\nimport static software.coley.recaf.ui.config.BindingCreator.OSBinding.newOsBind;\nimport static software.coley.recaf.ui.config.BindingCreator.bindings;\n\n/**\n * Config for various keybindings.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class KeybindingConfig extends BasicConfigContainer {\n\tpublic static final String ID = \"bind\";\n\tprivate static final String ID_QUICK_NAV = \"quicknav\";\n\tprivate static final String ID_FIND = \"editor.find\";\n\tprivate static final String ID_REPLACE = \"editor.replace\";\n\tprivate static final String ID_SAVE = \"editor.save\";\n\tprivate static final String ID_UNDO = \"editor.undo\";\n\tprivate static final String ID_CLOSE_TAB = \"editor.closetab\";\n\tprivate static final String ID_RENAME = \"editor.rename\";\n\tprivate static final String ID_GOTO = \"editor.goto\";\n\tprivate static final String ID_EXPORT = \"workspace.export\";\n\n\tprivate final BindingBundle bundle;\n\n\t@Inject\n\tpublic KeybindingConfig(@Nonnull GsonProvider gsonProvider, @Nonnull ConfigComponentManager componentManager) {\n\t\tsuper(ConfigGroups.SERVICE_UI, ID + CONFIG_SUFFIX);\n\n\t\t// We will only be storing one 'value' so that the UI can treat it as a singular element.\n\t\tbundle = new BindingBundle(Arrays.asList(\n\t\t\t\tcreateBindForPlatform(ID_QUICK_NAV, CONTROL, G),\n\t\t\t\tcreateBindForPlatform(ID_FIND, CONTROL, F),\n\t\t\t\tcreateBindForPlatform(ID_REPLACE, CONTROL, R),\n\t\t\t\tcreateBindForPlatform(ID_SAVE, CONTROL, S),\n\t\t\t\tcreateBindForPlatform(ID_UNDO, CONTROL, U),\n\t\t\t\tcreateBindForPlatform(ID_CLOSE_TAB, CONTROL, W),\n\t\t\t\tcreateBindForPlatform(ID_RENAME, ALT, R),\n\t\t\t\tcreateBindForPlatform(ID_GOTO, F3),\n\t\t\t\tcreateBindForPlatform(ID_EXPORT, CONTROL, E)\n\t\t));\n\t\taddValue(new BasicMapConfigValue<>(\"bundle\", BindingBundle.class, String.class, Binding.class, bundle));\n\n\t\t// Register custom json adapter for the binding bundle type.\n\t\tgsonProvider.addTypeDeserializer(BindingBundle.class, (json, typeOfT, context) -> {\n\t\t\tSet<String> expected = new HashSet<>(bundle.keySet());\n\t\t\tMap<String, JsonElement> map = json.getAsJsonObject().asMap();\n\t\t\tList<Binding> bindings = new ArrayList<>(map.size());\n\t\t\tmap.forEach((id, keysElement) -> {\n\t\t\t\tList<String> keyNames = keysElement.getAsJsonArray().asList().stream()\n\t\t\t\t\t\t.map(JsonElement::getAsString)\n\t\t\t\t\t\t.toList();\n\t\t\t\tbindings.add(newBind(id, keyNames));\n\t\t\t\texpected.remove(id);\n\t\t\t});\n\n\t\t\t// Fill in values from default config that do not exist in the serialized model\n\t\t\texpected.forEach(missingId -> bindings.add(bundle.get(missingId)));\n\n\t\t\treturn new BindingBundle(bindings);\n\t\t});\n\n\t\t// Register custom config component display for the binding bundle.\n\t\tcomponentManager.register(BindingBundle.class, new TypedConfigComponentFactory<>(true, BindingBundle.class) {\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic Node create(@Nonnull ConfigContainer container, @Nonnull ConfigValue<BindingBundle> value) {\n\t\t\t\tGridPane grid = new GridPane();\n\t\t\t\tgrid.setVgap(10);\n\t\t\t\tgrid.setHgap(10);\n\n\t\t\t\t// Tree-map should show the items in grouped order by key.\n\t\t\t\tnew TreeMap<>(bundle).forEach((key, bind) -> {\n\t\t\t\t\tBoundLabel label = new BoundLabel(Lang.getBinding(\"bind.\" + key));\n\t\t\t\t\tBindingInputField inputField = new BindingInputField(bundle, key, bind);\n\t\t\t\t\tgrid.addRow(grid.getRowCount(), label, inputField);\n\t\t\t\t});\n\n\t\t\t\treturn grid;\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * @return Keybinding for opening the quick-nav stage.\n\t */\n\t@Nonnull\n\tpublic Binding getQuickNav() {\n\t\treturn Objects.requireNonNull(bundle.get(ID_QUICK_NAV));\n\t}\n\n\t/**\n\t * @return Keybinding for opening find operations.\n\t *\n\t * @see SearchBar Used for {@link Editor}.\n\t */\n\t@Nonnull\n\tpublic Binding getFind() {\n\t\treturn Objects.requireNonNull(bundle.get(ID_FIND));\n\t}\n\n\t/**\n\t * @return Keybinding for opening replace operations.\n\t *\n\t * @see SearchBar Used for {@link Editor}.\n\t */\n\t@Nonnull\n\tpublic Binding getReplace() {\n\t\treturn Objects.requireNonNull(bundle.get(ID_REPLACE));\n\t}\n\n\t/**\n\t * @return Keybinding to save within a {@link ClassPane} or {@link FilePane}.\n\t */\n\t@Nonnull\n\tpublic Binding getSave() {\n\t\treturn Objects.requireNonNull(bundle.get(ID_SAVE));\n\t}\n\n\t/**\n\t * @return Keybinding to undo the last saved state within a {@link ClassPane} or {@link FilePane}.\n\t */\n\t@Nonnull\n\tpublic Binding getUndo() {\n\t\treturn Objects.requireNonNull(bundle.get(ID_UNDO));\n\t}\n\n\t/**\n\t * @return Keybinding to close the current tab.\n\t */\n\t@Nonnull\n\tpublic Binding getCloseTab() {\n\t\treturn Objects.requireNonNull(bundle.get(ID_CLOSE_TAB));\n\t}\n\n\t/**\n\t * @return Keybinding for renaming whatever is found at the current caret position.\n\t *\n\t * @see JvmDecompilerPane Usage in decompiler.\n\t */\n\t@Nonnull\n\tpublic Binding getRename() {\n\t\treturn Objects.requireNonNull(bundle.get(ID_RENAME));\n\t}\n\n\t/**\n\t * @return Keybinding for renaming whatever is found at the current caret position.\n\t *\n\t * @see JvmDecompilerPane Usage in decompiler.\n\t */\n\t@Nonnull\n\tpublic Binding getGoto() {\n\t\treturn Objects.requireNonNull(bundle.get(ID_GOTO));\n\t}\n\n\t/**\n\t * @return Keybinding for exporting the current workspace.\n\t */\n\t@Nonnull\n\tpublic Binding getExport() {\n\t\treturn Objects.requireNonNull(bundle.get(ID_EXPORT));\n\t}\n\n\t/**\n\t * Wrapper around {@link Binding#newBind(String, KeyCode...)} and {@link BindingCreator}\n\t * to swap out {@link KeyCode#CONTROL} for {@link KeyCode#META} for Mac users.\n\t *\n\t * @param id\n\t * \t\tKeybinding ID.\n\t * @param codes\n\t * \t\tKey-codes to use.\n\t *\n\t * @return Binding for the current platform.\n\t */\n\t@Nonnull\n\tprivate static Binding createBindForPlatform(@Nonnull String id, KeyCode... codes) {\n\t\tBinding defaultBind = newBind(id, codes);\n\t\t// Swap out CONTROL for META on Mac.\n\t\tif (defaultBind.contains(nameOf(CONTROL))) {\n\t\t\tBinding macBinding = defaultBind.withReplacement(CONTROL, META);\n\t\t\treturn bindings(defaultBind, newOsBind(PlatformType.MAC, macBinding))\n\t\t\t\t\t.buildKeyBindingForCurrentOS();\n\t\t}\n\t\treturn defaultBind;\n\t}\n\n\t/**\n\t * Binding bundle containing all keys.\n\t */\n\tpublic static class BindingBundle extends ObservableMap<String, Binding, Map<String, Binding>> {\n\t\tprivate final ObservableBoolean isEditing = new ObservableBoolean(false);\n\n\t\tpublic BindingBundle(@Nonnull List<Binding> binds) {\n\t\t\tsuper(binds.stream().collect(Collectors.toMap(Binding::getId, Function.identity())), HashMap::new);\n\t\t}\n\n\t\t/**\n\t\t * Marks the bundle as being live-edited or not. Global key-binds should not be handled when editing.\n\t\t *\n\t\t * @param editing\n\t\t *        {@code true} to indicate the bundle is being edited by the user.\n\t\t *        {@code false} to indicate the user is not editing.\n\t\t */\n\t\tpublic void setIsEditing(boolean editing) {\n\t\t\tisEditing.setValue(editing);\n\t\t}\n\t}\n\n\t/**\n\t * Text field that updates a {@link Binding}.\n\t */\n\tprivate static class BindingInputField extends TextField {\n\t\tprivate Binding lastState;\n\n\t\tprivate BindingInputField(@Nonnull BindingBundle bundle, @Nonnull String id, @Nonnull Binding bind) {\n\t\t\tgetStyleClass().add(\"key-field\");\n\t\t\tsetText(bind.toString());\n\t\t\tsetPromptText(Lang.get(\"bind.inputprompt.initial\"));\n\n\t\t\t// Show prompt when focused, otherwise show current target binding text.\n\t\t\tfocusedProperty().addListener((v, old, focus) -> setText(focus ? Lang.get(\"bind.inputprompt.finish\") : bind.toString()));\n\t\t\tsetFocusTraversable(false);\n\n\t\t\t// Track binding state while typing (will remember last pressed key combo, before pressing enter)\n\t\t\tsetOnKeyPressed(e -> {\n\t\t\t\te.consume();\n\n\t\t\t\t// Skip if no-name support for the character\n\t\t\t\tif (e.getCode().getName().equalsIgnoreCase(\"undefined\"))\n\t\t\t\t\treturn;\n\n\t\t\t\t// Set target to last key-press combo\n\t\t\t\tif (e.getCode() == ENTER) {\n\t\t\t\t\tbundle.setIsEditing(false);\n\n\t\t\t\t\tbind.clear();\n\t\t\t\t\tbind.addAll(lastState);\n\t\t\t\t\tlastState = null;\n\n\t\t\t\t\t// Drop focus so the listener will update the display text.\n\t\t\t\t\tgetParent().requestFocus();\n\t\t\t\t\treturn;\n\t\t\t\t} else if (e.getCode() == ESCAPE) {\n\t\t\t\t\t// Drop focus so the listener will update the display text.\n\t\t\t\t\tlastState = null;\n\t\t\t\t\tgetParent().requestFocus();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Update key-press combo.\n\t\t\t\tlastState = newBind(id, e);\n\t\t\t\tbundle.setIsEditing(true);\n\t\t\t});\n\n\t\t\t// Disable text updating\n\t\t\tsetOnKeyTyped(Event::consume);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/config/MemberDisplayFormatConfig.java",
    "content": "package software.coley.recaf.ui.config;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.objectweb.asm.Type;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.util.Types;\n\n/**\n * Config for {@link ClassMember} display.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class MemberDisplayFormatConfig extends BasicConfigContainer {\n\tpublic static final String ID = \"member-format\";\n\tprivate final ObservableObject<Display> nameTypeDisplay = new ObservableObject<>(Display.NAME_ONLY);\n\n\t@Inject\n\tpublic MemberDisplayFormatConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, ID + CONFIG_SUFFIX);\n\t\t// Add values\n\t\taddValue(new BasicConfigValue<>(\"name-type-display\", Display.class, nameTypeDisplay));\n\t}\n\n\t@Nonnull\n\tpublic ObservableObject<Display> getNameTypeDisplay() {\n\t\treturn nameTypeDisplay;\n\t}\n\n\t@Nonnull\n\tpublic String getDisplay(@Nonnull ClassMember member) {\n\t\tif (member instanceof FieldMember field)\n\t\t\treturn getDisplay(field);\n\t\telse if (member instanceof MethodMember method)\n\t\t\treturn getDisplay(method);\n\t\tthrow new IllegalStateException(\"Member not field or method: \" + member);\n\t}\n\n\t@Nonnull\n\tpublic String getDisplay(@Nonnull FieldMember member) {\n\t\treturn getFieldDisplay(member.getName(), member.getDescriptor());\n\t}\n\n\t@Nonnull\n\tpublic String getDisplay(@Nonnull MethodMember member) {\n\t\treturn getMethodDisplay(member.getName(), member.getDescriptor());\n\t}\n\n\t@Nonnull\n\tpublic String getDisplay(@Nonnull String name, @Nonnull String desc) {\n\t\tif (desc.charAt(0) == '(')\n\t\t\treturn getMethodDisplay(name, desc);\n\t\treturn getFieldDisplay(name, desc);\n\t}\n\n\t@Nonnull\n\tpublic String getFieldDisplay(@Nonnull String name, @Nonnull String desc) {\n\t\treturn switch (nameTypeDisplay.getValue()) {\n\t\t\tcase NAME_ONLY -> name;\n\t\t\tcase NAME_AND_RAW_DESCRIPTOR -> name + \" \" + desc;\n\t\t\tcase NAME_AND_PRETTY_DESCRIPTOR -> name + \" \" + Types.pretty(desc);\n\t\t};\n\t}\n\n\t@Nonnull\n\tpublic String getMethodDisplay(@Nonnull String name, @Nonnull String desc) {\n\t\treturn switch (nameTypeDisplay.getValue()) {\n\t\t\tcase NAME_ONLY -> name;\n\t\t\tcase NAME_AND_RAW_DESCRIPTOR -> name + desc;\n\t\t\tcase NAME_AND_PRETTY_DESCRIPTOR -> name + \" \" + Types.pretty(desc);\n\t\t};\n\t}\n\n\t@Nonnull\n\tpublic String getDescriptorDisplay(@Nonnull String desc) {\n\t\treturn switch (nameTypeDisplay.getValue()) {\n\t\t\tcase NAME_ONLY, NAME_AND_RAW_DESCRIPTOR ->  desc;\n\t\t\tcase NAME_AND_PRETTY_DESCRIPTOR -> Types.pretty(desc);\n\t\t};\n\t}\n\n\tpublic enum Display {\n\t\tNAME_ONLY,\n\t\tNAME_AND_RAW_DESCRIPTOR,\n\t\tNAME_AND_PRETTY_DESCRIPTOR\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/config/RecentFilesConfig.java",
    "content": "package software.coley.recaf.ui.config;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableCollection;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.observables.ObservableObject;\nimport software.coley.observables.ObservableString;\nimport software.coley.recaf.config.BasicCollectionConfigValue;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.properties.builtin.InputFilePathProperty;\nimport software.coley.recaf.services.phantom.GeneratedPhantomWorkspaceResource;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceDirectoryResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * Config for tracking recent file interactions.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class RecentFilesConfig extends BasicConfigContainer {\n\tpublic static final String ID = \"recent-workspaces\";\n\tprivate final ObservableInteger maxRecentWorkspaces = new ObservableInteger(10);\n\tprivate final ObservableCollection<WorkspaceModel, List<WorkspaceModel>> recentWorkspaces = new ObservableCollection<>(ArrayList::new);\n\tprivate final ObservableString lastWorkspaceOpenDirectory = new ObservableString(System.getProperty(\"user.dir\"));\n\tprivate final ObservableString lastWorkspaceExportDirectory = new ObservableString(System.getProperty(\"user.dir\"));\n\tprivate final ObservableString lastClassExportDirectory = new ObservableString(System.getProperty(\"user.dir\"));\n\n\t@Inject\n\tpublic RecentFilesConfig() {\n\t\tsuper(ConfigGroups.SERVICE_IO, ID + CONFIG_SUFFIX);\n\t\t// Add values\n\t\taddValue(new BasicConfigValue<>(\"max-recent-workspaces\", int.class, maxRecentWorkspaces));\n\t\taddValue(new BasicCollectionConfigValue<>(\"recent-workspaces\", List.class, WorkspaceModel.class, recentWorkspaces, true));\n\t\taddValue(new BasicConfigValue<>(\"last-workspace-open-path\", String.class, lastWorkspaceOpenDirectory));\n\t\taddValue(new BasicConfigValue<>(\"last-workspace-export-path\", String.class, lastWorkspaceExportDirectory));\n\t\taddValue(new BasicConfigValue<>(\"last-class-export-path\", String.class, lastClassExportDirectory));\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to add to {@link #getRecentWorkspaces()}.\n\t */\n\tpublic void addWorkspace(@Nonnull Workspace workspace) {\n\t\t// Only allow serializable workspaces\n\t\tWorkspaceResource primaryResource = workspace.getPrimaryResource();\n\t\tif (!ResourceModel.isSupported(primaryResource))\n\t\t\treturn;\n\n\t\t// Wrap to model\n\t\tResourceModel primary = ResourceModel.from(primaryResource);\n\t\tList<ResourceModel> libraries = workspace.getSupportingResources().stream()\n\t\t\t\t.map(ResourceModel::from)\n\t\t\t\t.filter(Objects::nonNull)\n\t\t\t\t.toList();\n\t\tWorkspaceModel workspaceModel = new WorkspaceModel(primary, libraries);\n\n\t\t// Update recent workspace list, where new items are inserted at the beginning.\n\t\t// Old items are removed near the end.\n\t\tList<WorkspaceModel> updatedList = new ArrayList<>(recentWorkspaces.getValue());\n\t\tif (recentWorkspaces.contains(workspaceModel))\n\t\t\tupdatedList.remove(workspaceModel);\n\t\twhile (updatedList.size() >= maxRecentWorkspaces.getValue())\n\t\t\tupdatedList.remove(updatedList.size() - 1);\n\t\tupdatedList.add(0, workspaceModel);\n\t\trecentWorkspaces.setValue(updatedList);\n\t}\n\n\t/**\n\t * Refresh available workspaces, removing any items that cannot be loaded from the list.\n\t *\n\t * @see WorkspaceModel#canLoadWorkspace()\n\t */\n\tpublic void clearUnloadable() {\n\t\tList<WorkspaceModel> current = recentWorkspaces.getValue();\n\t\tList<WorkspaceModel> loadable = current.stream()\n\t\t\t\t.filter(WorkspaceModel::canLoadWorkspace)\n\t\t\t\t.toList();\n\t\tif (current.size() != loadable.size())\n\t\t\trecentWorkspaces.setValue(loadable);\n\t}\n\n\t/**\n\t * @return Number of recent items to track.\n\t */\n\t@Nonnull\n\tpublic ObservableInteger getMaxRecentWorkspaces() {\n\t\treturn maxRecentWorkspaces;\n\t}\n\n\t/**\n\t * @return Recent workspaces.\n\t */\n\t@Nonnull\n\tpublic ObservableObject<List<WorkspaceModel>> getRecentWorkspaces() {\n\t\treturn recentWorkspaces;\n\t}\n\n\t/**\n\t * @return Last path used to open a workspace with.\n\t */\n\t@Nonnull\n\tpublic ObservableString getLastWorkspaceOpenDirectory() {\n\t\treturn lastWorkspaceOpenDirectory;\n\t}\n\n\t/**\n\t * @return Last path used to export a workspace to.\n\t */\n\t@Nonnull\n\tpublic ObservableString getLastWorkspaceExportDirectory() {\n\t\treturn lastWorkspaceExportDirectory;\n\t}\n\n\t/**\n\t * @return Last path used to export a class to.\n\t */\n\t@Nonnull\n\tpublic ObservableString getLastClassExportDirectory() {\n\t\treturn lastClassExportDirectory;\n\t}\n\n\t/**\n\t * Basic wrapper for workspaces.\n\t *\n\t * @param primary\n\t * \t\tPrimary resource of the workspace.\n\t * @param libraries\n\t * \t\tWorkspace supporting libraries.\n\t *\n\t * @author Matt Coley\n\t * @see ResourceModel\n\t */\n\tpublic record WorkspaceModel(ResourceModel primary, List<ResourceModel> libraries) {\n\t\t/**\n\t\t * @return {@code true} when the files still exist at their expected locations.\n\t\t */\n\t\tpublic boolean canLoadWorkspace() {\n\t\t\tPath path = Paths.get(primary().path());\n\t\t\tif (!Files.exists(path))\n\t\t\t\treturn false;\n\t\t\tfor (ResourceModel model : libraries()) {\n\t\t\t\tpath = Paths.get(model.path());\n\t\t\t\tif (!Files.exists(path))\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/**\n\t * Wrapper for a resources content source path.\n\t *\n\t * @param path\n\t * \t\tPath to the resource content source.\n\t *\n\t * @author Matt Coley\n\t */\n\tpublic record ResourceModel(String path) {\n\t\t/**\n\t\t * @param resource\n\t\t * \t\tSome resource sourced from a file or directory.\n\t\t *\n\t\t * @return Representation of the content source.\n\t\t */\n\t\t@Nullable\n\t\tpublic static ResourceModel from(@Nonnull WorkspaceResource resource) {\n\t\t\tif (resource instanceof WorkspaceFileResource fileResource) {\n\t\t\t\tFileInfo fileInfo = fileResource.getFileInfo();\n\t\t\t\tPath inputPath = InputFilePathProperty.get(fileInfo);\n\t\t\t\tif (inputPath != null)\n\t\t\t\t\treturn new ResourceModel(StringUtil.pathToAbsoluteString(inputPath));\n\t\t\t\treturn new ResourceModel(fileInfo.getName());\n\t\t\t} else if (resource instanceof WorkspaceDirectoryResource fileResource) {\n\t\t\t\treturn new ResourceModel(StringUtil.pathToAbsoluteString(fileResource.getDirectoryPath()));\n\t\t\t} else if (resource instanceof GeneratedPhantomWorkspaceResource) {\n\t\t\t\t// Intentionally left out\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tthrow new UnsupportedOperationException(\"Cannot serialize content source of type: \" +\n\t\t\t\t\tresource.getClass().getName());\n\t\t}\n\n\t\t/**\n\t\t * @param resource\n\t\t * \t\tSome resource.\n\t\t *\n\t\t * @return {@code true} when it can be represented by this model.\n\t\t */\n\t\tpublic static boolean isSupported(@Nonnull WorkspaceResource resource) {\n\t\t\tif (resource instanceof WorkspaceFileResource fileResource)\n\t\t\t\treturn InputFilePathProperty.get(fileResource.getFileInfo()) != null;\n\t\t\treturn resource instanceof WorkspaceDirectoryResource;\n\t\t}\n\n\t\t/**\n\t\t * @return Shortened path of resource's content source.\n\t\t */\n\t\t@Nonnull\n\t\tpublic String getSimpleName() {\n\t\t\tString name = path;\n\t\t\tint slashIndex = name.lastIndexOf('/');\n\t\t\tif (slashIndex > 0)\n\t\t\t\tname = name.substring(slashIndex + 1);\n\t\t\treturn name;\n\t\t}\n\n\t\t/**\n\t\t * @return Path to the resource content source. Can be a file path, maven coordinates, or url.\n\t\t */\n\t\t@Override\n\t\tpublic String path() {\n\t\t\treturn path;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/config/WindowScaleConfig.java",
    "content": "package software.coley.recaf.ui.config;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.Node;\nimport javafx.scene.control.Slider;\nimport software.coley.observables.ObservableDouble;\nimport software.coley.recaf.config.*;\nimport software.coley.recaf.services.config.ConfigComponentManager;\nimport software.coley.recaf.services.config.KeyedConfigComponentFactory;\n\n/**\n * Config for window scaling.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class WindowScaleConfig extends BasicConfigContainer {\n\tpublic static final String ID = \"window-scale\";\n\tprivate final ObservableDouble scale = new ObservableDouble(1);\n\tprivate Slider slider;\n\n\t@Inject\n\tpublic WindowScaleConfig(@Nonnull ConfigComponentManager componentManager) {\n\t\tsuper(ConfigGroups.SERVICE_UI, ID + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"scale\", double.class, scale));\n\n\t\tcomponentManager.register(this, \"scale\", false, (container, value) -> {\n\t\t\tslider = new Slider(0.5, 2.0, getScale());\n\t\t\tslider.setSnapToTicks(true);\n\t\t\tslider.setShowTickLabels(true);\n\t\t\tslider.setBlockIncrement(0.5);\n\t\t\tslider.setMajorTickUnit(0.5);\n\t\t\tslider.setMinorTickCount(5);\n\t\t\tslider.valueProperty().addListener((ob, old, cur) -> scale.setValue(cur.doubleValue()));\n\t\t\treturn slider;\n\t\t});\n\t}\n\n\t/**\n\t * @return Window scale.\n\t */\n\tpublic double getScale() {\n\t\treturn Math.clamp(scale.getValue(), 0.5, 2);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/config/WorkspaceExplorerConfig.java",
    "content": "package software.coley.recaf.ui.config;\n\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.ui.control.tree.WorkspaceTree;\nimport software.coley.recaf.ui.pane.WorkspaceExplorerPane;\n\n/**\n * Config for workspace explorer.\n *\n * @author Matt Coley\n * @see WorkspaceExplorerPane\n */\n@ApplicationScoped\npublic class WorkspaceExplorerConfig extends BasicConfigContainer {\n\tprivate final ObservableObject<DragDropOption> dragDropAction = new ObservableObject<>(DragDropOption.CREATE_NEW_WORKSPACE);\n\tprivate final ObservableInteger maxTreeDirectoryDepth = new ObservableInteger(100);\n\n\t@Inject\n\tpublic WorkspaceExplorerConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, \"workspace-explorer\" + CONFIG_SUFFIX);\n\t\t// Add values\n\t\taddValue(new BasicConfigValue<>(\"drag-drop-action\", DragDropOption.class, dragDropAction));\n\t\taddValue(new BasicConfigValue<>(\"max-tree-dir-depth\", int.class, maxTreeDirectoryDepth));\n\t}\n\n\t/**\n\t * @return {@code true} when drag-drop behavior on {@link WorkspaceExplorerPane} should create new workspaces.\n\t */\n\tpublic boolean createOnDragDrop() {\n\t\treturn dragDropAction.getValue() == DragDropOption.CREATE_NEW_WORKSPACE;\n\t}\n\n\t/**\n\t * @return {@code true} when drag-drop behavior on {@link WorkspaceExplorerPane} should append files to the open workspace.\n\t */\n\tpublic boolean appendOnDragDrop() {\n\t\treturn dragDropAction.getValue() == DragDropOption.APPEND_TO_CURRENT;\n\t}\n\n\t/**\n\t * @return Number of directories deep to start compacting paths in the {@link WorkspaceTree}.\n\t */\n\tpublic int getMaxTreeDirectoryDepth() {\n\t\treturn Math.max(1, maxTreeDirectoryDepth.getValue());\n\t}\n\n\tpublic enum DragDropOption {\n\t\tCREATE_NEW_WORKSPACE,\n\t\tAPPEND_TO_CURRENT\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/AnnotationMenuBuilder.java",
    "content": "package software.coley.recaf.ui.contextmenu;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.control.MenuItem;\nimport org.kordamp.ikonli.Ikon;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.ui.contextmenu.actions.*;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Menu builder for {@link AnnotationInfo} content.\n *\n * @author Matt Coley\n */\npublic class AnnotationMenuBuilder extends MenuBuilder<AnnotationMenuBuilder> {\n\tprivate final Workspace workspace;\n\tprivate final WorkspaceResource resource;\n\tprivate final ClassBundle<? extends ClassInfo> bundle;\n\tprivate final Annotated annotated;\n\tprivate final AnnotationInfo annotation;\n\n\t/**\n\t * @param parent\n\t * \t\tOptional parent menu.\n\t * @param sink\n\t * \t\tSink to append menu items with.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param annotated\n\t * \t\tItem declared as the annotation target.\n\t * @param annotation\n\t * \t\tTarget annotation.\n\t */\n\tpublic AnnotationMenuBuilder(@Nullable AnnotationMenuBuilder parent,\n\t\t\t\t\t\t\t\t @Nonnull ItemSink sink,\n\t\t\t\t\t\t\t\t @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t\t\t\t\t @Nonnull Annotated annotated,\n\t\t\t\t\t\t\t\t @Nonnull AnnotationInfo annotation) {\n\t\tsuper(parent, sink);\n\t\tthis.workspace = workspace;\n\t\tthis.resource = resource;\n\t\tthis.bundle = bundle;\n\t\tthis.annotated = annotated;\n\t\tthis.annotation = annotation;\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> workspaceItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull WorkspaceAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> resourceItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull ResourceAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> bundleItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull BundleAction<?> action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource, Unchecked.cast(bundle)));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> infoItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull InfoAction<?, ?> action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource, Unchecked.cast(bundle), Unchecked.cast(annotated)));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> annoItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull AnnotationAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource, bundle, annotated, annotation));\n\t}\n\n\t@Override\n\tpublic AnnotationMenuBuilder submenu(@Nonnull String key, @Nonnull Ikon icon) {\n\t\treturn new AnnotationMenuBuilder(this, sink.withMenu(key, icon), workspace, resource, bundle, annotated, annotation);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/BundleMenuBuilder.java",
    "content": "package software.coley.recaf.ui.contextmenu;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.control.MenuItem;\nimport org.kordamp.ikonli.Ikon;\nimport software.coley.recaf.ui.contextmenu.actions.BundleAction;\nimport software.coley.recaf.ui.contextmenu.actions.ResourceAction;\nimport software.coley.recaf.ui.contextmenu.actions.WorkspaceAction;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Menu builder for {@link Bundle} content.\n *\n * @param <B>\n * \t\tBundle type.\n *\n * @author Matt Coley\n */\npublic class BundleMenuBuilder<B extends Bundle<?>> extends MenuBuilder<BundleMenuBuilder<B>> {\n\tprivate final Workspace workspace;\n\tprivate final WorkspaceResource resource;\n\tprivate final B bundle;\n\n\t/**\n\t * @param parent\n\t * \t\tOptional parent menu.\n\t * @param sink\n\t * \t\tSink to append menu items with.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tTarget bundle.\n\t */\n\tpublic BundleMenuBuilder(@Nullable BundleMenuBuilder<B> parent,\n\t\t\t\t\t\t\t @Nonnull ItemSink sink,\n\t\t\t\t\t\t\t @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t @Nonnull B bundle) {\n\t\tsuper(parent, sink);\n\t\tthis.workspace = workspace;\n\t\tthis.resource = resource;\n\t\tthis.bundle = bundle;\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> workspaceItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull WorkspaceAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> resourceItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull ResourceAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> bundleItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull BundleAction<B> action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource, bundle));\n\t}\n\n\t@Override\n\tpublic BundleMenuBuilder<B> submenu(@Nonnull String key, @Nonnull Ikon icon) {\n\t\treturn new BundleMenuBuilder<>(this, sink.withMenu(key, icon), workspace, resource, bundle);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/ContextMenuBuilder.java",
    "content": "package software.coley.recaf.ui.contextmenu;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Base to work off-of to create {@link MenuBuilder} types.\n *\n * @author Matt Coley\n */\npublic class ContextMenuBuilder {\n\tprivate final ItemSink sink;\n\tprivate final ContextMenu menu;\n\n\t/**\n\t * @param source\n\t * \t\tContext source to pass to menu builders.\n\t */\n\tpublic ContextMenuBuilder(@Nonnull ContextSource source) {\n\t\tthis(new ContextMenu(), source);\n\t}\n\n\t/**\n\t * @param menu\n\t * \t\tMenu to append content to.\n\t * @param source\n\t * \t\tContext source to pass to menu builders.\n\t */\n\tpublic ContextMenuBuilder(@Nonnull ContextMenu menu, @Nonnull ContextSource source) {\n\t\tthis.menu = menu;\n\t\tsink = new ItemSink(menu.getItems(), source);\n\t}\n\n\t/**\n\t * @return Menu target.\n\t */\n\t@Nonnull\n\tpublic ContextMenu getMenu() {\n\t\treturn menu;\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tTarget workspace.\n\t *\n\t * @return Menu builder.\n\t */\n\t@Nonnull\n\tpublic WorkspaceMenuBuilder\n\tforWorkspace(@Nonnull Workspace workspace) {\n\t\treturn new WorkspaceMenuBuilder(null, sink, workspace);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tTarget resource.\n\t *\n\t * @return Menu builder.\n\t */\n\t@Nonnull\n\tpublic ResourceMenuBuilder\n\tforResource(@Nonnull Workspace workspace,\n\t\t\t\t@Nonnull WorkspaceResource resource) {\n\t\treturn new ResourceMenuBuilder(null, sink, workspace, resource);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tTarget bundle.\n\t * @param <B>\n\t * \t\tBundle type.\n\t *\n\t * @return Menu builder.\n\t */\n\t@Nonnull\n\tpublic <B extends Bundle<?>> BundleMenuBuilder<B>\n\tforBundle(@Nonnull Workspace workspace,\n\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t  @Nonnull B bundle) {\n\t\treturn new BundleMenuBuilder<>(null, sink, workspace, resource, bundle);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tTarget directory path.\n\t * @param <B>\n\t * \t\tBundle type.\n\t *\n\t * @return Menu builder.\n\t */\n\t@Nonnull\n\tpublic <B extends Bundle<?>> DirectoryMenuBuilder<B>\n\tforDirectory(@Nonnull Workspace workspace,\n\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t @Nonnull B bundle,\n\t\t\t\t @Nonnull String directoryName) {\n\t\treturn new DirectoryMenuBuilder<>(null, sink, workspace, resource, bundle, directoryName);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tTarget info object.\n\t * @param <B>\n\t * \t\tBundle type.\n\t * @param <I>\n\t * \t\tInfo type.\n\t *\n\t * @return Menu builder.\n\t */\n\t@Nonnull\n\tpublic <B extends Bundle<?>, I extends Info> InfoMenuBuilder<B, I>\n\tforInfo(@Nonnull Workspace workspace,\n\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t@Nonnull B bundle,\n\t\t\t@Nonnull I info) {\n\t\treturn new InfoMenuBuilder<>(null, sink, workspace, resource, bundle, info);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tDeclaring class info object.\n\t * @param member\n\t * \t\tTarget member.\n\t * @param <B>\n\t * \t\tBundle type.\n\t * @param <I>\n\t * \t\tClass info type.\n\t * @param <M>\n\t * \t\tMember type.\n\t *\n\t * @return Menu builder.\n\t */\n\t@Nonnull\n\tpublic <B extends Bundle<?>, I extends ClassInfo, M extends ClassMember> MemberMenuBuilder<B, I, M>\n\tforMember(@Nonnull Workspace workspace,\n\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t  @Nonnull B bundle,\n\t\t\t  @Nonnull I info,\n\t\t\t  @Nonnull M member) {\n\t\treturn new MemberMenuBuilder<>(null, sink, workspace, resource, bundle, info, member);\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param annotated\n\t * \t\tItem declared as the annotation target.\n\t * @param annotation\n\t * \t\tTarget annotation.\n\t *\n\t * @return Menu builder.\n\t */\n\t@Nonnull\n\tpublic AnnotationMenuBuilder\n\tforAnnotation(@Nonnull Workspace workspace,\n\t\t\t\t  @Nonnull WorkspaceResource resource,\n\t\t\t\t  @Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t  @Nonnull Annotated annotated,\n\t\t\t\t  @Nonnull AnnotationInfo annotation) {\n\t\treturn new AnnotationMenuBuilder(null, sink, workspace, resource, bundle, annotated, annotation);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/DirectoryMenuBuilder.java",
    "content": "package software.coley.recaf.ui.contextmenu;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.control.MenuItem;\nimport org.kordamp.ikonli.Ikon;\nimport software.coley.recaf.ui.contextmenu.actions.BundleAction;\nimport software.coley.recaf.ui.contextmenu.actions.DirectoryAction;\nimport software.coley.recaf.ui.contextmenu.actions.ResourceAction;\nimport software.coley.recaf.ui.contextmenu.actions.WorkspaceAction;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Menu builder for directory paths in a {@link Bundle}.\n *\n * @param <B>\n * \t\tBundle type.\n *\n * @author Matt Coley\n */\npublic class DirectoryMenuBuilder<B extends Bundle<?>> extends MenuBuilder<DirectoryMenuBuilder<B>> {\n\tprivate final Workspace workspace;\n\tprivate final WorkspaceResource resource;\n\tprivate final B bundle;\n\tprivate final String directoryName;\n\n\t/**\n\t * @param parent\n\t * \t\tOptional parent menu.\n\t * @param sink\n\t * \t\tSink to append menu items with.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tTarget directory path.\n\t */\n\tpublic DirectoryMenuBuilder(@Nullable DirectoryMenuBuilder<B> parent,\n\t\t\t\t\t\t\t\t@Nonnull ItemSink sink,\n\t\t\t\t\t\t\t\t@Nonnull Workspace workspace,\n\t\t\t\t\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t\t@Nonnull B bundle,\n\t\t\t\t\t\t\t\t@Nonnull String directoryName) {\n\t\tsuper(parent, sink);\n\t\tthis.workspace = workspace;\n\t\tthis.resource = resource;\n\t\tthis.bundle = bundle;\n\t\tthis.directoryName = directoryName;\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> workspaceItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull WorkspaceAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> resourceItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull ResourceAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> bundleItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull BundleAction<B> action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource, bundle));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> directoryItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull DirectoryAction<B> action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource, bundle, directoryName));\n\t}\n\n\t@Override\n\tpublic DirectoryMenuBuilder<B> submenu(@Nonnull String key, @Nonnull Ikon icon) {\n\t\treturn new DirectoryMenuBuilder<>(this, sink.withMenu(key, icon), workspace, resource, bundle, directoryName);\n\t}\n\n\t/**\n\t * @param bundleType\n\t * \t\tTarget type.\n\t * @param <X>\n\t * \t\tTarget type.\n\t *\n\t * @return Cast self.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic <X extends Bundle<?>> DirectoryMenuBuilder<X> cast(@Nonnull Class<X> bundleType) {\n\t\treturn (DirectoryMenuBuilder<X>) this;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/InfoMenuBuilder.java",
    "content": "package software.coley.recaf.ui.contextmenu;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.control.MenuItem;\nimport org.kordamp.ikonli.Ikon;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.ui.contextmenu.actions.BundleAction;\nimport software.coley.recaf.ui.contextmenu.actions.InfoAction;\nimport software.coley.recaf.ui.contextmenu.actions.ResourceAction;\nimport software.coley.recaf.ui.contextmenu.actions.WorkspaceAction;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Menu builder for {@link Info} content.\n *\n * @param <B>\n * \t\tBundle type.\n * @param <I>\n * \t\tInfo type.\n *\n * @author Matt Coley\n */\npublic class InfoMenuBuilder<B extends Bundle<?>, I extends Info> extends MenuBuilder<InfoMenuBuilder<B, I>> {\n\tprivate final Workspace workspace;\n\tprivate final WorkspaceResource resource;\n\tprivate final B bundle;\n\tprivate final I info;\n\n\t/**\n\t * @param parent\n\t * \t\tOptional parent menu.\n\t * @param sink\n\t * \t\tSink to append menu items with.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tTarget info object.\n\t */\n\tpublic InfoMenuBuilder(@Nullable InfoMenuBuilder<B, I> parent,\n\t\t\t\t\t\t   @Nonnull ItemSink sink,\n\t\t\t\t\t\t   @Nonnull Workspace workspace,\n\t\t\t\t\t\t   @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t   @Nonnull B bundle,\n\t\t\t\t\t\t   @Nonnull I info) {\n\t\tsuper(parent, sink);\n\t\tthis.workspace = workspace;\n\t\tthis.resource = resource;\n\t\tthis.bundle = bundle;\n\t\tthis.info = info;\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> workspaceItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull WorkspaceAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> resourceItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull ResourceAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> bundleItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull BundleAction<B> action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource, bundle));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> infoItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull InfoAction<B, I> action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource, bundle, info));\n\t}\n\n\t@Override\n\tpublic InfoMenuBuilder<B, I> submenu(@Nonnull String key, @Nonnull Ikon icon) {\n\t\treturn new InfoMenuBuilder<>(this, sink.withMenu(key, icon), workspace, resource, bundle, info);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/ItemSink.java",
    "content": "package software.coley.recaf.ui.contextmenu;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.Menu;\nimport javafx.scene.control.MenuItem;\nimport org.kordamp.ikonli.Ikon;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.util.Lang;\n\nimport java.util.List;\n\nimport static software.coley.recaf.util.Menus.menu;\n\n/**\n * Delegate to adding {@link MenuItem} to a {@link Menu} with some condition checking.\n *\n * @param target\n * \t\tTarget menu items list.\n * @param source\n * \t\tContext source of the menu.\n *\n * @author Matt Coley\n */\npublic record ItemSink(@Nonnull List<MenuItem> target, @Nonnull ContextSource source) {\n\t/**\n\t * @param key\n\t * \t\tID of the item to add. Doubles as {@link Lang#get(String)} key.\n\t * @param item\n\t * \t\tMenu item to add.\n\t *\n\t * @return {@code true} when added. {@code false} when denied by the {@link #source()}.\n\t */\n\tpublic boolean add(@Nonnull String key, @Nonnull MenuItem item) {\n\t\tif (source.allow(key)) {\n\t\t\ttarget.add(item);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tID of the menu to add. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tIcon of the menu to add.\n\t *\n\t * @return New sink to target the newly made sub-menu.\n\t */\n\t@Nonnull\n\tpublic ItemSink withMenu(@Nonnull String key, @Nonnull Ikon icon) {\n\t\tif (source.allow(key)) {\n\t\t\tMenu menu = menu(key, icon);\n\t\t\ttarget.add(menu);\n\t\t\treturn new ItemSink(menu.getItems(), source);\n\t\t}\n\n\t\t// Not allowed, create dummy that never appends anything.\n\t\treturn new ItemSink(Lists.noopList(), source);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/MemberMenuBuilder.java",
    "content": "package software.coley.recaf.ui.contextmenu;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.control.MenuItem;\nimport org.kordamp.ikonli.Ikon;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.ui.contextmenu.actions.*;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Menu builder for {@link ClassMember} content.\n *\n * @param <B>\n * \t\tBundle type.\n * @param <I>\n * \t\tClass type.\n * @param <M>\n * \t\tMember type.\n *\n * @author Matt Coley\n */\npublic class MemberMenuBuilder<B extends Bundle<?>, I extends ClassInfo, M extends ClassMember> extends MenuBuilder<MemberMenuBuilder<B, I, M>> {\n\tprivate final Workspace workspace;\n\tprivate final WorkspaceResource resource;\n\tprivate final B bundle;\n\tprivate final I info;\n\tprivate final M member;\n\n\t/**\n\t * @param parent\n\t * \t\tOptional parent menu.\n\t * @param sink\n\t * \t\tSink to append menu items with.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tDeclaring class of member.\n\t * @param member\n\t * \t\tTarget member.\n\t */\n\tpublic MemberMenuBuilder(@Nullable MemberMenuBuilder<B, I, M> parent,\n\t\t\t\t\t\t\t @Nonnull ItemSink sink,\n\t\t\t\t\t\t\t @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t @Nonnull WorkspaceResource resource,\n\t\t\t\t\t\t\t @Nonnull B bundle,\n\t\t\t\t\t\t\t @Nonnull I info,\n\t\t\t\t\t\t\t @Nonnull M member) {\n\t\tsuper(parent, sink);\n\t\tthis.workspace = workspace;\n\t\tthis.resource = resource;\n\t\tthis.bundle = bundle;\n\t\tthis.info = info;\n\t\tthis.member = member;\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> workspaceItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull WorkspaceAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> resourceItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull ResourceAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> bundleItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull BundleAction<B> action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource, bundle));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> infoItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull InfoAction<B, I> action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource, bundle, info));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> memberItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull MemberAction<B, I, M> action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource, bundle, info, member));\n\t}\n\n\t@Override\n\tpublic MemberMenuBuilder<B, I, M> submenu(@Nonnull String key, @Nonnull Ikon icon) {\n\t\treturn new MemberMenuBuilder<>(this, sink.withMenu(key, icon), workspace, resource, bundle, info, member);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/MenuBuilder.java",
    "content": "package software.coley.recaf.ui.contextmenu;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.control.MenuItem;\nimport org.kordamp.ikonli.Ikon;\nimport software.coley.recaf.ui.control.ActionMenuItem;\nimport software.coley.recaf.util.Lang;\n\nimport static software.coley.recaf.util.Menus.action;\n\n/**\n * Base builder outline.\n *\n * @param <B>\n * \t\tRecursive self type.\n *\n * @author Matt Coley\n */\npublic abstract class MenuBuilder<B extends MenuBuilder<B>> {\n\tprotected final ItemSink sink;\n\tprivate final B parent;\n\n\t/**\n\t * @param parent\n\t * \t\tOptional parent menu.\n\t * @param sink\n\t * \t\tSink to append menu items with.\n\t */\n\tprotected MenuBuilder(@Nullable B parent, @Nonnull ItemSink sink) {\n\t\tthis.parent = parent;\n\t\tthis.sink = sink;\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> item(@Nonnull String id, @Nonnull Ikon icon, @Nonnull Runnable action) {\n\t\tActionMenuItem item = action(id, icon, action);\n\t\tif (sink.add(id, item))\n\t\t\treturn consumer -> consumer.accept(item);\n\t\treturn unused -> {};\n\t}\n\n\t/**\n\t * Used to escape from a {@link #submenu(String, Ikon)} back to the parent.\n\t *\n\t * @return Parent builder.\n\t */\n\t@Nullable\n\tpublic B close() {\n\t\treturn parent;\n\t}\n\n\t/**\n\t * Creates a new builder of the same type for a new sub-menu.\n\t *\n\t * @param key\n\t * \t\tMenu ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu graphic.\n\t *\n\t * @return New menu builder of the same type, targeting the newly created menu.\n\t */\n\tpublic abstract B submenu(@Nonnull String key, @Nonnull Ikon icon);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/MenuHandler.java",
    "content": "package software.coley.recaf.ui.contextmenu;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.control.MenuItem;\n\nimport java.util.Optional;\nimport java.util.function.Consumer;\n\n/**\n * Similar to {@link Optional} but with specific handling for {@link MenuItem} content.\n * Used as return types for additions to menus via {@link MenuBuilder} implementations.\n *\n * @param <M>\n * \t\tMenu content.\n *\n * @author Matt Coley\n */\npublic interface MenuHandler<M extends MenuItem> {\n\t/**\n\t * @param handlers\n\t * \t\tMultiple handlers.\n\t * @param <M>\n\t * \t\tMenu content.\n\t *\n\t * @return Handler to delegate an action to the given handlers.\n\t */\n\t@Nonnull\n\t@SafeVarargs\n\tstatic <M extends MenuItem> MenuHandler<M> each(MenuHandler<M>... handlers) {\n\t\treturn consumer -> {\n\t\t\tfor (MenuHandler<M> handler : handlers) {\n\t\t\t\thandler.configure(consumer);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * @param consumer\n\t * \t\tConsumer to act on the added menu item.\n\t */\n\tvoid configure(@Nonnull Consumer<M> consumer);\n\n\t/**\n\t * Disables the target menu item if the condition is met.\n\t *\n\t * @param condition\n\t * \t\tSet condition.\n\t */\n\tdefault void disableWhen(boolean condition) {\n\t\tconfigure(i -> i.setDisable(condition));\n\t}\n\n\t/**\n\t * Disables the target menu item if the condition is met.\n\t *\n\t * @param condition\n\t * \t\tSet condition.\n\t */\n\tdefault void disableWhen(@Nonnull ObservableValue<Boolean> condition) {\n\t\tconfigure(i -> i.disableProperty().bind(condition));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/ResourceMenuBuilder.java",
    "content": "package software.coley.recaf.ui.contextmenu;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.control.MenuItem;\nimport org.kordamp.ikonli.Ikon;\nimport software.coley.recaf.ui.contextmenu.actions.ResourceAction;\nimport software.coley.recaf.ui.contextmenu.actions.WorkspaceAction;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * Menu builder for {@link WorkspaceResource} content.\n *\n * @author Matt Coley\n */\npublic class ResourceMenuBuilder extends MenuBuilder<ResourceMenuBuilder> {\n\tprivate final Workspace workspace;\n\tprivate final WorkspaceResource resource;\n\n\t/**\n\t * @param parent\n\t * \t\tOptional parent menu.\n\t * @param sink\n\t * \t\tSink to append menu items with.\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tTarget resource.\n\t */\n\tpublic ResourceMenuBuilder(@Nullable ResourceMenuBuilder parent,\n\t\t\t\t\t\t\t   @Nonnull ItemSink sink,\n\t\t\t\t\t\t\t   @Nonnull Workspace workspace,\n\t\t\t\t\t\t\t   @Nonnull WorkspaceResource resource) {\n\t\tsuper(parent, sink);\n\t\tthis.workspace = workspace;\n\t\tthis.resource = resource;\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> workspaceItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull WorkspaceAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace));\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> resourceItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull ResourceAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace, resource));\n\t}\n\n\t@Override\n\tpublic ResourceMenuBuilder submenu(@Nonnull String key, @Nonnull Ikon icon) {\n\t\treturn new ResourceMenuBuilder(this, sink.withMenu(key, icon), workspace, resource);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/WorkspaceMenuBuilder.java",
    "content": "package software.coley.recaf.ui.contextmenu;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.control.MenuItem;\nimport org.kordamp.ikonli.Ikon;\nimport software.coley.recaf.ui.contextmenu.actions.WorkspaceAction;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Menu builder for {@link Workspace} content.\n *\n * @author Matt Coley\n */\npublic class WorkspaceMenuBuilder extends MenuBuilder<WorkspaceMenuBuilder> {\n\tprivate final Workspace workspace;\n\n\t/**\n\t * @param parent\n\t * \t\tOptional parent menu.\n\t * @param sink\n\t * \t\tSink to append menu items with.\n\t * @param workspace\n\t * \t\tTarget workspace.\n\t */\n\tpublic WorkspaceMenuBuilder(@Nullable WorkspaceMenuBuilder parent,\n\t\t\t\t\t\t\t\t@Nonnull ItemSink sink,\n\t\t\t\t\t\t\t\t@Nonnull Workspace workspace) {\n\t\tsuper(parent, sink);\n\t\tthis.workspace = workspace;\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tMenu item ID. Doubles as {@link Lang#get(String)} key.\n\t * @param icon\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tMenu item action.\n\t *\n\t * @return Handler for optional post-addition manipulations of the added item.\n\t */\n\t@Nonnull\n\tpublic MenuHandler<MenuItem> workspaceItem(@Nonnull String id, @Nonnull Ikon icon, @Nonnull WorkspaceAction action) {\n\t\treturn item(id, icon, () -> action.accept(workspace));\n\t}\n\n\t@Override\n\tpublic WorkspaceMenuBuilder submenu(@Nonnull String key, @Nonnull Ikon icon) {\n\t\treturn new WorkspaceMenuBuilder(this, sink.withMenu(key, icon), workspace);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/actions/AnnotationAction.java",
    "content": "package software.coley.recaf.ui.contextmenu.actions;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.services.cell.context.ContextMenuProviderFactory;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * For simplifying references to annotation entries in {@link ContextMenuProviderFactory} implementations.\n *\n * @author Matt Coley\n */\npublic interface AnnotationAction {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param annotated\n\t * \t\tItem declared as the annotation target.\n\t * @param annotation\n\t * \t\tTarget annotation.\n\t */\n\tvoid accept(@Nonnull Workspace workspace,\n\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t@Nonnull ClassBundle<? extends ClassInfo> bundle,\n\t\t\t\t@Nonnull Annotated annotated,\n\t\t\t\t@Nonnull AnnotationInfo annotation);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/actions/BundleAction.java",
    "content": "package software.coley.recaf.ui.contextmenu.actions;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.services.cell.context.ContextMenuProviderFactory;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * For simplifying references to methods in {@link ContextMenuProviderFactory} implementations.\n *\n * @param <B>\n * \t\tBundle type.\n *\n * @author Matt Coley\n */\npublic interface BundleAction<B extends Bundle<?>> {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tTarget bundle.\n\t */\n\tvoid accept(@Nonnull Workspace workspace,\n\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t@Nonnull B bundle);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/actions/DirectoryAction.java",
    "content": "package software.coley.recaf.ui.contextmenu.actions;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.services.cell.context.ContextMenuProviderFactory;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * For simplifying references to methods in {@link ContextMenuProviderFactory} implementations.\n *\n * @param <B>\n * \t\tBundle type.\n *\n * @author Matt Coley\n */\npublic interface DirectoryAction<B extends Bundle<?>> {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param directoryName\n\t * \t\tTarget directory.\n\t */\n\tvoid accept(@Nonnull Workspace workspace,\n\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t@Nonnull B bundle,\n\t\t\t\t@Nonnull String directoryName);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/actions/InfoAction.java",
    "content": "package software.coley.recaf.ui.contextmenu.actions;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.services.cell.context.ContextMenuProviderFactory;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * For simplifying references to methods in {@link ContextMenuProviderFactory} implementations.\n *\n * @param <B>\n * \t\tBundle type.\n * @param <I>\n * \t\tInfo type.\n *\n * @author Matt Coley\n */\npublic interface InfoAction<B extends Bundle<?>, I extends Info> {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tTarget info object.\n\t */\n\tvoid accept(@Nonnull Workspace workspace,\n\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t@Nonnull B bundle,\n\t\t\t\t@Nonnull I info);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/actions/MemberAction.java",
    "content": "package software.coley.recaf.ui.contextmenu.actions;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.services.cell.context.ContextMenuProviderFactory;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * For simplifying references to methods in {@link ContextMenuProviderFactory} implementations.\n *\n * @param <B>\n * \t\tBundle type.\n * @param <I>\n * \t\tDeclaring class type.\n * @param <M>\n * \t\tMember type.\n *\n * @author Matt Coley\n */\npublic interface MemberAction<B extends Bundle<?>, I extends ClassInfo, M extends ClassMember> {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tContaining resource.\n\t * @param bundle\n\t * \t\tContaining bundle.\n\t * @param info\n\t * \t\tDeclaring class info object.\n\t * @param member\n\t * \t\tTarget member.\n\t */\n\tvoid accept(@Nonnull Workspace workspace,\n\t\t\t\t@Nonnull WorkspaceResource resource,\n\t\t\t\t@Nonnull B bundle,\n\t\t\t\t@Nonnull I info,\n\t\t\t\t@Nonnull M member);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/actions/ResourceAction.java",
    "content": "package software.coley.recaf.ui.contextmenu.actions;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.services.cell.context.ContextMenuProviderFactory;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * For simplifying references to methods in {@link ContextMenuProviderFactory} implementations.\n *\n * @author Matt Coley\n */\npublic interface ResourceAction {\n\t/**\n\t * @param workspace\n\t * \t\tContaining workspace.\n\t * @param resource\n\t * \t\tTarget resource.\n\t */\n\tvoid accept(@Nonnull Workspace workspace, @Nonnull WorkspaceResource resource);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/contextmenu/actions/WorkspaceAction.java",
    "content": "package software.coley.recaf.ui.contextmenu.actions;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.services.cell.context.ContextMenuProviderFactory;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * For simplifying references to methods in {@link ContextMenuProviderFactory} implementations.\n *\n * @author Matt Coley\n */\npublic interface WorkspaceAction {\n\t/**\n\t * @param workspace\n\t * \t\tTarget workspace.\n\t */\n\tvoid accept(@Nonnull Workspace workspace);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/AbstractSearchBar.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport atlantafx.base.controls.CustomTextField;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ListChangeListener;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ContextMenu;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TextField;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.text.TextAlignment;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.util.Icons;\n\nimport java.util.List;\n\n/**\n * Common base for text-targeting search bar controls.\n * After constructing, call {@link #setup()}.\n *\n * @author Matt Coley\n */\npublic abstract class AbstractSearchBar extends VBox {\n\tprotected static final int MAX_HISTORY = 10;\n\tprotected final ObservableList<String> pastSearches = FXCollections.observableArrayList();\n\tprotected final SimpleBooleanProperty hasResults = new SimpleBooleanProperty();\n\tprotected final SimpleBooleanProperty caseSensitivity = new SimpleBooleanProperty();\n\tprotected final SimpleBooleanProperty regex = new SimpleBooleanProperty();\n\tprotected final CustomTextField searchInput = new CustomTextField();\n\tprotected final Button oldSearches = new Button();\n\tprotected final Label resultCount = new Label();\n\n\t/**\n\t * <b>Note</b>: The base {@link AbstractSearchBar} does not tie into any search systems,\n\t * so its up to implementors to ensure values are added to this list.\n\t *\n\t * @return Property holding list of prior searches.\n\t */\n\t@Nonnull\n\tpublic ObservableList<String> getPastSearches() {\n\t\treturn pastSearches;\n\t}\n\n\t/**\n\t * @return Property of the current search text.\n\t */\n\t@Nonnull\n\tpublic StringProperty getSearchTextProperty() {\n\t\treturn searchInput.textProperty();\n\t}\n\n\t/**\n\t * <b>Note</b>: The base {@link AbstractSearchBar} does not tie into any search systems,\n\t * so its up to implementors to ensure this is wired up properly.\n\t *\n\t * @return Property indicating if search results are found.\n\t */\n\t@Nonnull\n\tpublic SimpleBooleanProperty hasResultsProperty() {\n\t\treturn hasResults;\n\t}\n\n\t/**\n\t * @return Property indicating if searching is case-sensitive.\n\t */\n\t@Nonnull\n\tpublic SimpleBooleanProperty caseSensitivityProperty() {\n\t\treturn caseSensitivity;\n\t}\n\n\t/**\n\t * @return Property indicating if searching is regex-based.\n\t */\n\t@Nonnull\n\tpublic SimpleBooleanProperty regexProperty() {\n\t\treturn regex;\n\t}\n\n\t/**\n\t * Initializes controls for the search bar.\n\t */\n\tpublic void setup() {\n\t\tgetStyleClass().add(\"search-bar\");\n\n\t\t// Remove border from search/replace text fields.\n\t\tgetInputFields().forEach(field -> field.getStyleClass().addAll(Styles.ACCENT));\n\n\t\t// Create menu for search input left graphic (like IntelliJ) to display prior searches when clicked.\n\t\tsearchInput.setLeft(oldSearches);\n\t\tgetInputButtons().forEach(button -> {\n\t\t\tbutton.setDisable(true); // re-enabled when searches are populated.\n\t\t\tbutton.setGraphic(new FontIconView(CarbonIcons.SEARCH));\n\t\t\tbutton.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.ACCENT, Styles.FLAT, Styles.SMALL);\n\t\t});\n\n\t\t// Create toggles for search input query modes.\n\t\tBoundToggleIcon toggleSensitivity = new BoundToggleIcon(new FontIconView(CarbonIcons.LETTER_CC), caseSensitivity).withTooltip(\"misc.casesensitive\");\n\t\tBoundToggleIcon toggleRegex = new BoundToggleIcon(Icons.REGEX, regex).withTooltip(\"misc.regex\");\n\t\ttoggleSensitivity.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.ACCENT, Styles.FLAT, Styles.SMALL);\n\t\ttoggleRegex.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.ACCENT, Styles.FLAT, Styles.SMALL);\n\t\tHBox inputToggles = new HBox(\n\t\t\t\ttoggleSensitivity,\n\t\t\t\ttoggleRegex\n\t\t);\n\t\tinputToggles.setAlignment(Pos.CENTER_RIGHT);\n\t\tsearchInput.setRight(inputToggles);\n\t\tcaseSensitivity.addListener((ob, old, cur) -> refreshResults());\n\t\tregex.addListener((ob, old, cur) -> refreshResults());\n\n\t\t// Create label to display number of results.\n\t\tresultCount.setMinWidth(70);\n\t\tresultCount.setAlignment(Pos.CENTER);\n\t\tresultCount.setTextAlignment(TextAlignment.CENTER);\n\t\tbindResultCountDisplay(resultCount.textProperty());\n\t\tresultCount.styleProperty().bind(hasResults.map(has -> {\n\t\t\tif (has) {\n\t\t\t\treturn \"-fx-text-fill: -color-fg-default;\";\n\t\t\t} else {\n\t\t\t\treturn \"-fx-text-fill: red;\";\n\t\t\t}\n\t\t}));\n\n\t\t// Add to past searches when enter is pressed.\n\t\tsearchInput.setOnKeyPressed(this::onSearchInputKeyPress);\n\t\tsearchInput.setOnKeyReleased(e -> refreshResults());\n\n\t\t// Ensure typed keys are only handled when the input is focused.\n\t\t// If the user is tab-navigating to toggle the input buttons then we want to cancel the event.\n\t\tsearchInput.addEventFilter(KeyEvent.KEY_TYPED, e -> {\n\t\t\tif (!searchInput.isFocused() && e.getCode() != KeyCode.TAB)\n\t\t\t\te.consume();\n\t\t});\n\n\t\t// When past searches list is modified, update old search menu.\n\t\tupdatePastListing(oldSearches, pastSearches, searchInput);\n\n\t\t// Layout\n\t\tsetupLayout();\n\t}\n\n\t/**\n\t * Lays out search bar controls.\n\t * <p>\n\t * Called at the end of {@link #setup()}.\n\t */\n\tprotected void setupLayout() {\n\t\tHBox.setHgrow(searchInput, Priority.ALWAYS);\n\t\tHBox searchLine = new HBox(searchInput, resultCount);\n\t\tsearchLine.setAlignment(Pos.CENTER_LEFT);\n\t\tsearchLine.setSpacing(10);\n\t\tsearchLine.setPadding(new Insets(0, 5, 0, 0));\n\t\tgetChildren().addAll(searchLine);\n\t}\n\n\t/**\n\t * Focus the search input field, and select the text so users can quickly retype something new if desired.\n\t */\n\tpublic void requestSearchFocus() {\n\t\tsearchInput.requestFocus();\n\t\tsearchInput.selectAll();\n\t}\n\n\t/**\n\t * Called when {@link #searchInput} keys are pressed.\n\t *\n\t * @param e\n\t * \t\tInput key press event.\n\t */\n\tprotected void onSearchInputKeyPress(@Nonnull KeyEvent e) {\n\t\t// Clear old searches if there are too many\n\t\twhile (pastSearches.size() > MAX_HISTORY)\n\t\t\tpastSearches.removeLast();\n\t}\n\n\t/**\n\t * Implementations should configure how the result count is displayed here.\n\t *\n\t * @param resultTextProperty\n\t * \t\tProperty of the display count text.\n\t */\n\tprotected abstract void bindResultCountDisplay(@Nonnull StringProperty resultTextProperty);\n\n\t/**\n\t * Handle searching.\n\t */\n\tprotected abstract void refreshResults();\n\n\t/**\n\t * @return List of input fields\n\t */\n\t@Nonnull\n\tprotected List<TextField> getInputFields() {\n\t\treturn List.of(searchInput);\n\t}\n\n\t/**\n\t * @return List of input buttons for filling in old search inputs.\n\t */\n\t@Nonnull\n\tprotected List<Button> getInputButtons() {\n\t\treturn List.of(oldSearches);\n\t}\n\n\t/**\n\t * When the past items list is updated, update the menu that fills in the input with the selected old entry.\n\t *\n\t * @param button\n\t * \t\tButton to show the list.\n\t * @param list\n\t * \t\tSource list to pull values from.\n\t * @param input\n\t * \t\tInput to apply text to.\n\t */\n\tprotected void updatePastListing(@Nonnull Button button, @Nonnull ObservableList<String> list, @Nonnull CustomTextField input) {\n\t\tlist.addListener((ListChangeListener<String>) c -> {\n\t\t\tList<ActionMenuItem> items = list.stream()\n\t\t\t\t\t.map(text -> new ActionMenuItem(text, () -> {\n\t\t\t\t\t\tinput.setText(text);\n\t\t\t\t\t\trequestSearchFocus();\n\t\t\t\t\t}))\n\t\t\t\t\t.toList();\n\t\t\tif (items.isEmpty()) {\n\t\t\t\tbutton.setDisable(true);\n\t\t\t} else {\n\t\t\t\tbutton.setDisable(false);\n\t\t\t\tContextMenu contextMenu = new ContextMenu();\n\t\t\t\tcontextMenu.getItems().setAll(items);\n\t\t\t\tbutton.setOnMousePressed(e -> contextMenu.show(button, e.getScreenX(), e.getScreenY()));\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/ActionButton.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.value.ObservableValue;\nimport javafx.event.ActionEvent;\nimport javafx.event.EventHandler;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport org.kordamp.ikonli.Ikon;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\n\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\n\n/**\n * Button with an on-click runnable action.\n *\n * @author Matt Coley\n */\npublic class ActionButton extends Button implements Tooltipable {\n\tprivate static final ExecutorService service = ThreadPoolFactory.newSingleThreadExecutor(\"async-button-action\");\n\n\t/**\n\t * @param text\n\t * \t\tButton display text.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic ActionButton(@Nullable String text, @Nonnull Runnable action) {\n\t\tsuper(text);\n\t\tsetOnAction(e -> wrap(e, action));\n\t}\n\n\t/**\n\t * @param icon\n\t * \t\tButton display icon.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic ActionButton(@Nonnull Ikon icon, @Nonnull Runnable action) {\n\t\tthis(new FontIconView(icon), action);\n\t}\n\n\t/**\n\t * @param graphic\n\t * \t\tButton display graphic.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic ActionButton(@Nonnull Node graphic, @Nonnull Runnable action) {\n\t\tsetGraphic(graphic);\n\t\tsetOnAction(e -> wrap(e, action));\n\t}\n\n\t/**\n\t * @param graphic\n\t * \t\tButton display graphic property.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic ActionButton(@Nonnull ObjectProperty<Node> graphic, @Nonnull Runnable action) {\n\t\tgraphicProperty().bindBidirectional(graphic);\n\t\tsetOnAction(e -> wrap(e, action));\n\t}\n\n\t/**\n\t * @param icon\n\t * \t\tButton display icon.\n\t * @param text\n\t * \t\tButton display text.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic ActionButton(@Nonnull Ikon icon, @Nonnull ObservableValue<String> text, @Nonnull Runnable action) {\n\t\tsetGraphic(new FontIconView(icon));\n\t\ttextProperty().bind(text);\n\t\tsetOnAction(e -> wrap(e, action));\n\t}\n\n\t/**\n\t * @param graphic\n\t * \t\tButton display graphic.\n\t * @param text\n\t * \t\tButton display text.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic ActionButton(@Nonnull Node graphic, @Nonnull ObservableValue<String> text, @Nonnull Runnable action) {\n\t\tsetGraphic(graphic);\n\t\ttextProperty().bind(text);\n\t\tsetOnAction(e -> wrap(e, action));\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tButton display text.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic ActionButton(@Nonnull ObservableValue<String> text, @Nonnull Runnable action) {\n\t\ttextProperty().bind(text);\n\t\tsetOnAction(e -> wrap(e, action));\n\t}\n\n\t/**\n\t * Only allow the action to be run once.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic ActionButton once() {\n\t\tEventHandler<ActionEvent> onAction = getOnAction();\n\t\tif (onAction == null)\n\t\t\tthrow new IllegalArgumentException(\"No action set\");\n\t\tsetOnAction(e -> {\n\t\t\tonAction.handle(e);\n\t\t\tsetDisable(true);\n\t\t\tsetOnAction(null);\n\t\t});\n\t\treturn this;\n\t}\n\n\t/**\n\t * Run the provided action asynchronously.\n\t * Do note that this will no longer run the action on the FX thread.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic ActionButton async() {\n\t\treturn async(service);\n\t}\n\n\t/**\n\t * Run the provided action asynchronously.\n\t * Do note that this will no longer run the action on the FX thread.\n\t *\n\t * @param executor\n\t * \t\tExecutor to run the action on.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic ActionButton async(@Nonnull Executor executor) {\n\t\tEventHandler<ActionEvent> onAction = getOnAction();\n\t\tif (onAction == null)\n\t\t\tthrow new IllegalArgumentException(\"No action set\");\n\t\tsetOnAction(e -> CompletableFuture.runAsync(() -> onAction.handle(e), executor));\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param width\n\t * \t\tWidth to assign to button.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic ActionButton width(double width) {\n\t\tsetMinWidth(width);\n\t\tsetPrefWidth(width);\n\t\treturn this;\n\t}\n\n\tprivate static void wrap(ActionEvent e, Runnable action) {\n\t\t// This stops the input from 'bleeding' through to parent control handlers.\n\t\t//  - Useful for when the button is used in 'x.setGraphic(button)' scenarios\n\t\te.consume();\n\n\t\t// Then run the action.\n\t\taction.run();\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/ActionMenu.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Menu;\nimport javafx.scene.layout.HBox;\n\n/**\n * Menu with an on-click runnable action.\n *\n * @author Matt Coley\n */\npublic class ActionMenu extends Menu {\n\t/**\n\t * @param text\n\t * \t\tMenu display text translation key.\n\t * @param graphic\n\t * \t\tMenu graphic.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic ActionMenu(ObservableValue<String> text, Node graphic, Runnable action) {\n\t\tsuper();\n\t\t// This is a hack: https://stackoverflow.com/a/10317260\n\t\t// Works well enough without having to screw with CSS.\n\t\tHBox pane = new HBox();\n\t\tpane.setAlignment(Pos.CENTER);\n\t\tLabel label = new BoundLabel(text);\n\t\tpane.setStyle(\"-fx-background-insets: -8 -21 -8 -21;\" +\n\t\t\t\t\"-fx-background-color: rgba(0, 0, 0, 0.001);\");\n\n\t\tpane.getChildren().add(graphic);\n\t\tpane.getChildren().add(label);\n\n\t\tsetGraphic(pane);\n\t\tpane.setOnMousePressed(e -> action.run());\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tID to assign.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic ActionMenu withId(@Nullable String id) {\n\t\tsetId(id);\n\t\treturn this;\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/ActionMenuItem.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.Node;\nimport javafx.scene.control.MenuItem;\n\n/**\n * MenuItem with an on-click runnable action.\n *\n * @author Matt Coley\n */\npublic class ActionMenuItem extends MenuItem {\n\t/**\n\t * @param text\n\t * \t\tMenu item display text.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic ActionMenuItem(String text, Runnable action) {\n\t\tthis(text, null, action);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tMenu item display text.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic ActionMenuItem(StringBinding text, Runnable action) {\n\t\tthis(text, null, action);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tMenu item display text.\n\t * @param graphic\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic ActionMenuItem(String text, Node graphic, Runnable action) {\n\t\tsuper(text);\n\t\tsetGraphic(graphic);\n\t\tsetOnAction(e -> action.run());\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tMenu item display text.\n\t * @param graphic\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic ActionMenuItem(ObservableValue<String> text, Node graphic, Runnable action) {\n\t\ttextProperty().bind(text);\n\t\tsetGraphic(graphic);\n\t\tsetOnAction(e -> action.run());\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tID to assign.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic ActionMenuItem withId(@Nullable String id) {\n\t\tsetId(id);\n\t\treturn this;\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/AutoScrollPane.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.animation.AnimationTimer;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.scene.Cursor;\nimport javafx.scene.Node;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.layout.Region;\nimport software.coley.recaf.util.NodeEvents;\n\n/**\n * Scroll pane extension that adds support for middle-click auto-scrolling.\n *\n * @author Matt Coley\n * @see VirtualizedScrollPaneWrapper\n */\npublic class AutoScrollPane extends ScrollPane {\n\tprivate static final double AUTO_SCROLL_MULTIPLIER = 0.1;\n\tprivate static final double AUTO_SCROLL_BUFFER_PX = 5;\n\tprivate final BooleanProperty canAutoScroll = new SimpleBooleanProperty(true);\n\tprivate Cursor preAutoScrollCursor;\n\tprivate boolean isAutoScrolling;\n\tprivate double autoScrollStartY;\n\tprivate double autoScrollCurrentY;\n\tprivate final AnimationTimer autoScrollTimer = new AnimationTimer() {\n\t\t@Override\n\t\tpublic void handle(long now) {\n\t\t\tupdateAutoScroll();\n\t\t}\n\t};\n\n\tpublic AutoScrollPane(@Nonnull Node content) {\n\t\tsuper(content);\n\n\t\t// Handle middle mouse press to start auto-scrolling.\n\t\t// - Press initiates the auto-scroll\n\t\t// - Drag changes the auto-scroll speed\n\t\t// - Release stops the auto-scroll\n\t\tNodeEvents.addMousePressHandler(getContent(), e -> {\n\t\t\tif (canAutoScroll.get() && e.getButton() == MouseButton.MIDDLE) {\n\t\t\t\tpreAutoScrollCursor = getContent().getCursor();\n\t\t\t\tautoScrollStartY = e.getScreenY();\n\t\t\t\tautoScrollCurrentY = autoScrollStartY;\n\t\t\t}\n\t\t});\n\t\tNodeEvents.addMouseReleaseHandler(getContent(), e -> {\n\t\t\tif (e.getButton() == MouseButton.MIDDLE && isAutoScrolling()) {\n\t\t\t\tgetContent().setCursor(preAutoScrollCursor);\n\t\t\t\tautoScrollTimer.stop();\n\t\t\t\tautoScrollCurrentY = -1;\n\t\t\t\tisAutoScrolling = false;\n\t\t\t}\n\t\t});\n\t\tNodeEvents.addMouseDraggedHandler(getContent(), e -> {\n\t\t\tif (e.getButton() == MouseButton.MIDDLE) {\n\t\t\t\tautoScrollCurrentY = e.getScreenY();\n\n\t\t\t\t// Only begin the auto-scroll after the user has moved a couple of pixels away.\n\t\t\t\t//\n\t\t\t\t// We do this because the 'content' node may have middle click behavior similar\n\t\t\t\t// to how a browser opens/closes tabs when using the middle mouse button on links.\n\t\t\t\t// By not initiating until we're sure the user intends to move around via auto-scroll\n\t\t\t\t// we don't mess with the UX of the existing behavior in the 'content' node.\n\t\t\t\tif (!isAutoScrolling && Math.abs(autoScrollCurrentY - autoScrollStartY) > AUTO_SCROLL_BUFFER_PX) {\n\t\t\t\t\tisAutoScrolling = true;\n\t\t\t\t\tautoScrollTimer.start();\n\t\t\t\t\tgetContent().setCursor(Cursor.V_RESIZE);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate void updateAutoScroll() {\n\t\tdouble deltaY = autoScrollCurrentY - autoScrollStartY;\n\t\tdouble viewportHeight = getHeight();\n\n\t\t// Get current scroll values\n\t\tdouble contentSize = getContent() instanceof Region r ? r.getHeight() : viewportHeight;\n\t\tdouble value = getVvalue();\n\t\tdouble min = getVmin();\n\t\tdouble max = getVmax();\n\n\t\t// Calculate scroll amount based on viewport size\n\t\tdouble scrollAmount = (deltaY * AUTO_SCROLL_MULTIPLIER) / contentSize;\n\t\tif (Math.abs(scrollAmount) > 0) {\n\t\t\t// Calculate new scroll position\n\t\t\tdouble newValue = value + scrollAmount;\n\t\t\tnewValue = Math.max(min, Math.min(max, newValue));\n\n\t\t\t// Update scroll position\n\t\t\tsetVvalue(newValue);\n\t\t}\n\t}\n\n\t/**\n\t * @return {@code true} when this scroll pane is auto-scrolling.\n\t */\n\tpublic boolean isAutoScrolling() {\n\t\treturn isAutoScrolling;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/BoundBiDiComboBox.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.Property;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.SingleSelectionModel;\nimport javafx.util.StringConverter;\n\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * Combo box with two-way binding to a {@link ObjectProperty}.\n *\n * @param <T>\n * \t\tProperty value type.\n *\n * @author Matt Coley\n * @see BoundComboBox One-way bound combo-box.\n */\npublic class BoundBiDiComboBox<T> extends ComboBox<T> implements Tooltipable {\n\t/**\n\t * @param value\n\t * \t\tproperty.\n\t * @param values\n\t * \t\tAvailable options.\n\t * @param converter\n\t * \t\tValue to string conversion.\n\t */\n\tpublic BoundBiDiComboBox(@Nonnull Property<T> value, @Nonnull List<T> values, @Nonnull StringConverter<T> converter) {\n\t\t// Populate combo-model and select the initial value.\n\t\tgetItems().addAll(values);\n\t\tSingleSelectionModel<T> selectionModel = getSelectionModel();\n\t\tselectionModel.select(value.getValue());\n\n\t\t// Bind the property to the given selected item. We have this intermediate wrapper to mimic two-way binding\n\t\t// on the selected item property, which is declared as read-only.\n\t\tObjectProperty<T> currentItemWrapper = new SimpleObjectProperty<>();\n\t\tselectionModel.selectedItemProperty().addListener((ob, old, cur) -> {\n\t\t\tif (!Objects.equals(currentItemWrapper.getValue(), cur)) {\n\t\t\t\tcurrentItemWrapper.setValue(cur);\n\t\t\t\tvalue.setValue(cur);\n\t\t\t}\n\t\t});\n\t\tvalue.addListener((ob, old, cur) -> selectionModel.select(cur));\n\n\t\t// Allow horizontal expansion.\n\t\tsetMaxWidth(Double.MAX_VALUE);\n\n\t\t// T to String mapping.\n\t\tsetConverter(converter);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/BoundCheckBox.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.control.CheckBox;\n\n/**\n * Toggle checkbox for a {@link BooleanProperty}.\n *\n * @author Matt Coley\n */\npublic class BoundCheckBox extends CheckBox implements Tooltipable {\n\t/**\n\t * @param text\n\t * \t\tCheckbox text.\n\t * @param property\n\t * \t\tProperty to bind to.\n\t */\n\tpublic BoundCheckBox(@Nonnull ObservableValue<String> text, @Nonnull BooleanProperty property) {\n\t\ttextProperty().bind(text);\n\t\tselectedProperty().bindBidirectional(property);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/BoundComboBox.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.Property;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.SingleSelectionModel;\nimport javafx.util.StringConverter;\n\nimport java.util.List;\n\n/**\n * Combo box with one-way binding to a {@link ObjectProperty}.\n *\n * @param <T>\n * \t\tProperty value type.\n *\n * @author Matt Coley\n * @see BoundBiDiComboBox Two-way bound combo-box.\n */\npublic class BoundComboBox<T> extends ComboBox<T> implements Tooltipable {\n\t/**\n\t * @param value\n\t * \t\tproperty.\n\t * @param values\n\t * \t\tAvailable options.\n\t * @param converter\n\t * \t\tValue to string conversion.\n\t */\n\tpublic BoundComboBox(@Nonnull Property<T> value, @Nonnull List<T> values, @Nonnull StringConverter<T> converter) {\n\t\t// Populate combo-model and select the initial value.\n\t\tgetItems().addAll(values);\n\t\tSingleSelectionModel<T> selectionModel = getSelectionModel();\n\t\tselectionModel.select(value.getValue());\n\n\t\t// Bind the property to the given selected item.\n\t\tvalue.bind(selectionModel.selectedItemProperty());\n\n\t\t// Allow horizontal expansion.\n\t\tsetMaxWidth(Double.MAX_VALUE);\n\n\t\t// T to String mapping.\n\t\tsetConverter(converter);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/BoundHyperlink.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.Node;\nimport javafx.scene.control.Hyperlink;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.util.DesktopUtil;\n\nimport java.io.IOException;\nimport java.net.URI;\n\n/**\n * Utility hyperlink to quickly apply a {@link StringBinding}, commonly for UI translations.\n *\n * @author Matt Coley\n */\npublic class BoundHyperlink extends Hyperlink implements Tooltipable {\n\tprivate static final Logger logger = Logging.get(BoundHyperlink.class);\n\n\t/**\n\t * @param binding\n\t * \t\tText binding.\n\t * @param graphic\n\t * \t\tLabel display icon.\n\t * @param url\n\t * \t\tURL to navigate to on-click.\n\t */\n\tpublic BoundHyperlink(@Nonnull ObservableValue<String> binding, @Nullable Node graphic, @Nonnull String url) {\n\t\tthis(binding, graphic, () -> {\n\t\t\ttry {\n\t\t\t\tDesktopUtil.showDocument(URI.create(url));\n\t\t\t} catch (IOException ex) {\n\t\t\t\tlogger.error(\"Failed to browse to {}\", url, ex);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * @param binding\n\t * \t\tText binding.\n\t * @param graphic\n\t * \t\tLabel display icon.\n\t * @param onClick\n\t * \t\tOn-click action to run.\n\t */\n\tpublic BoundHyperlink(@Nonnull ObservableValue<String> binding, @Nullable Node graphic, @Nonnull Runnable onClick) {\n\t\ttextProperty().bind(binding);\n\t\tsetGraphic(graphic);\n\t\tsetOnAction(e -> onClick.run());\n\t}\n\n\t/**\n\t * Unbinds the old text property and rebinds to the given value.\n\t *\n\t * @param binding\n\t * \t\tNew value to bind to.\n\t */\n\tpublic void rebind(@Nonnull ObservableValue<String> binding) {\n\t\tvar property = textProperty();\n\t\tproperty.unbind();\n\t\tproperty.bind(binding);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/BoundIntSpinner.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.property.IntegerProperty;\nimport javafx.scene.control.Spinner;\nimport javafx.scene.control.SpinnerValueFactory;\nimport javafx.scene.control.TextFormatter;\nimport javafx.util.converter.IntegerStringConverter;\n\nimport java.text.NumberFormat;\nimport java.text.ParsePosition;\n\n/**\n * Integer spinner with binding to a {@link IntegerProperty}.\n *\n * @author Matt Coley\n */\npublic class BoundIntSpinner extends Spinner<Integer> implements Tooltipable {\n\tprivate static final NumberFormat NUMBER_FORMAT = NumberFormat.getIntegerInstance();\n\n\t/**\n\t * @param value\n\t * \t\tProperty.\n\t */\n\tpublic BoundIntSpinner(@Nonnull IntegerProperty value) {\n\t\tthis(value, Integer.MIN_VALUE, Integer.MAX_VALUE);\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tProperty.\n\t * @param min\n\t * \t\tProperty min value.\n\t * @param max\n\t * \t\tProperty max value.\n\t */\n\tpublic BoundIntSpinner(@Nonnull IntegerProperty value, int min, int max) {\n\t\tfinal int initialValue = value.get();\n\t\tfinal String initialValueStr = Integer.toString(initialValue);\n\n\t\tsetValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(min, max, initialValue));\n\t\tsetEditable(true);\n\t\tsetMaxWidth(Double.MAX_VALUE);\n\t\tgetEditor().setText(initialValueStr);\n\t\tgetEditor().setTextFormatter(new TextFormatter<>(new IntegerStringConverter(), initialValue, BoundIntSpinner::handleChange));\n\t\tvalue.bind(valueProperty());\n\t}\n\n\t@Nullable\n\tprivate static TextFormatter.Change handleChange(@Nonnull TextFormatter.Change c) {\n\t\tif (c.isContentChange()) {\n\t\t\tParsePosition pos = new ParsePosition(0);\n\t\t\t// NumberFormat evaluates the beginning of the text.\n\t\t\tBoundIntSpinner.NUMBER_FORMAT.parse(c.getControlNewText(), pos);\n\t\t\tif (pos.getIndex() == 0 || pos.getIndex() < c.getControlNewText().length()) {\n\t\t\t\t// Reject parsing the complete text failed.\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\treturn c;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/BoundLabel.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\n\n/**\n * Utility label to quickly apply a {@link StringBinding}, commonly for UI translations.\n *\n * @author Matt Coley\n */\npublic class BoundLabel extends Label implements Tooltipable {\n\t/**\n\t * @param binding\n\t * \t\tText binding.\n\t */\n\tpublic BoundLabel(@Nonnull ObservableValue<String> binding) {\n\t\ttextProperty().bind(binding);\n\t}\n\n\t/**\n\t * @param binding\n\t * \t\tText binding.\n\t * @param graphic\n\t * \t\tLabel display icon.\n\t */\n\tpublic BoundLabel(@Nonnull ObservableValue<String> binding, @Nonnull Node graphic) {\n\t\ttextProperty().bind(binding);\n\t\tsetGraphic(graphic);\n\t}\n\n\t/**\n\t * Unbinds the old text property and rebinds to the given value.\n\t *\n\t * @param binding\n\t * \t\tNew value to bind to.\n\t */\n\tpublic void rebind(@Nonnull ObservableValue<String> binding) {\n\t\tvar property = textProperty();\n\t\tproperty.unbind();\n\t\tproperty.bind(binding);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/BoundMultiToggleIcon.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.Property;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport software.coley.observables.ObservableObject;\n\nimport java.util.function.Function;\n\n/**\n * Toggle button for iterating through values of an {@link Enum} with icon mappings.\n *\n * @author Amejonah\n * @author Matt Coley\n */\npublic class BoundMultiToggleIcon<E extends Enum<?>> extends Button implements Tooltipable {\n\t/**\n\t * @param enumClass\n\t * \t\tEnum class.\n\t * @param enumObservable\n\t * \t\tObservable holding enum value.\n\t * @param iconProvider\n\t * \t\tIcon mapper from enum value to graphic.\n\t */\n\tpublic BoundMultiToggleIcon(@Nonnull Class<E> enumClass,\n\t\t\t\t\t\t\t\t@Nonnull ObservableObject<E> enumObservable,\n\t\t\t\t\t\t\t\t@Nonnull Function<E, Node> iconProvider) {\n\t\tenumObservable.addChangeListener((ob, old, cur) -> {\n\t\t\tsetGraphic(iconProvider.apply(enumObservable.getValue()));\n\t\t});\n\t\tsetOnAction(e -> {\n\t\t\tint index = (enumObservable.getValue().ordinal() + 1) % enumClass.getEnumConstants().length;\n\t\t\tenumObservable.setValue(enumClass.getEnumConstants()[index]);\n\t\t});\n\t}\n\n\t/**\n\t * @param enumClass\n\t * \t\tEnum class.\n\t * @param enumProperty\n\t * \t\tProperty holding enum value.\n\t * @param iconProvider\n\t * \t\tIcon mapper from enum value to graphic.\n\t */\n\tpublic BoundMultiToggleIcon(@Nonnull Class<E> enumClass,\n\t\t\t\t\t\t\t\t@Nonnull Property<E> enumProperty,\n\t\t\t\t\t\t\t\t@Nonnull Function<E, Node> iconProvider) {\n\t\tgraphicProperty().bind(Bindings.createObjectBinding(() ->\n\t\t\t\ticonProvider.apply(enumProperty.getValue()), enumProperty));\n\t\tsetOnAction(e -> {\n\t\t\tint index = (enumProperty.getValue().ordinal() + 1) % enumClass.getEnumConstants().length;\n\t\t\tenumProperty.setValue(enumClass.getEnumConstants()[index]);\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/BoundTab.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.Node;\nimport javafx.scene.control.Tab;\nimport org.kordamp.ikonli.Ikon;\n\n/**\n * Utility tab to quickly apply a {@link StringBinding}, commonly for UI translations.\n *\n * @author Matt Coley\n */\npublic class BoundTab extends Tab {\n\t/**\n\t * @param binding\n\t * \t\tText binding for tab title.\n\t * @param icon\n\t * \t\tTab graphic icon.\n\t * @param content\n\t * \t\tOptional tab content.\n\t */\n\tpublic BoundTab(@Nonnull ObservableValue<String> binding, @Nonnull Ikon icon, @Nullable Node content) {\n\t\tthis(binding, new FontIconView(icon), content);\n\t}\n\n\t/**\n\t * @param binding\n\t * \t\tText binding for tab title.\n\t * @param graphic\n\t * \t\tOptional tab graphic.\n\t * @param content\n\t * \t\tOptional tab content.\n\t */\n\tpublic BoundTab(@Nonnull ObservableValue<String> binding, @Nullable Node graphic, @Nullable Node content) {\n\t\ttextProperty().bind(binding);\n\t\tsetGraphic(graphic);\n\t\tsetContent(content);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/BoundTextField.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.property.StringProperty;\nimport javafx.scene.control.TextField;\n\n/**\n * Text field that forwards changes of the {@link #textProperty()} to a given {@link StringProperty}.\n *\n * @author Matt Coley\n */\npublic class BoundTextField extends TextField implements Tooltipable {\n\t/**\n\t * @param destination\n\t * \t\tProperty to sync with text field contents.\n\t */\n\tpublic BoundTextField(@Nonnull StringProperty destination) {\n\t\t// Initial state\n\t\ttextProperty().set(destination.get());\n\n\t\t// Sync changes to destination\n\t\tdestination.bind(textProperty());\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/BoundToggleIcon.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.recaf.util.Icons;\n\n/**\n * Toggle button for a {@link BooleanProperty}.\n *\n * @author Amejonah\n * @author Matt Coley\n */\npublic class BoundToggleIcon extends Button implements Tooltipable {\n\t/**\n\t * @param graphic\n\t * \t\tButton graphic path for {@link Icons#getIconView(String)}.\n\t * @param property\n\t * \t\tProperty to bind to.\n\t */\n\tpublic BoundToggleIcon(@Nonnull String graphic, @Nonnull BooleanProperty property) {\n\t\tthis(Icons.getIconView(graphic), property);\n\t}\n\n\t/**\n\t * @param graphic\n\t * \t\tButton graphic.\n\t * @param property\n\t * \t\tProperty to bind to.\n\t */\n\tpublic BoundToggleIcon(@Nonnull Node graphic, @Nonnull BooleanProperty property) {\n\t\tsetGraphic(graphic);\n\t\tsetOnAction(e -> property.set(!property.get()));\n\t\topacityProperty().bind(\n\t\t\t\tBindings.when(property)\n\t\t\t\t\t\t.then(1.0)\n\t\t\t\t\t\t.otherwise(0.4)\n\t\t);\n\t}\n\n\t/**\n\t * @param graphic\n\t * \t\tButton graphic path for {@link Icons#getIconView(String)}.\n\t * @param observable\n\t * \t\tObservable to bind to.\n\t */\n\tpublic BoundToggleIcon(@Nonnull String graphic, @Nonnull ObservableBoolean observable) {\n\t\tthis(Icons.getIconView(graphic), observable);\n\t}\n\n\t/**\n\t * @param graphic\n\t * \t\tButton graphic.\n\t * @param observable\n\t * \t\tObservable to bind to.\n\t */\n\tpublic BoundToggleIcon(@Nonnull Node graphic, @Nonnull ObservableBoolean observable) {\n\t\tsetGraphic(graphic);\n\t\tsetOnAction(e -> observable.setValue(!observable.getValue()));\n\t\tobservable.addChangeListener((ob, old, cur) -> setOpacity(cur ? 1.0 : 0.4));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/ClosableActionMenuItem.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport atlantafx.base.theme.Styles;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.CustomMenuItem;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Menu;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Region;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.NodeEvents;\nimport software.coley.recaf.util.threading.ThreadUtil;\n\n/**\n * MenuItem with an on-click and on-close runnable action.\n *\n * @author Wolfie / win32kbase\n */\npublic class ClosableActionMenuItem extends CustomMenuItem {\n\t/**\n\t * @param text\n\t * \t\tMenu item text.\n\t * @param graphic\n\t * \t\tMenu item graphic.\n\t * @param action\n\t * \t\tAction to run on menu item click.\n\t * @param onClose\n\t * \t\tAction to run to remove the item from the parent menu.\n\t */\n\tpublic ClosableActionMenuItem(String text, Node graphic, Runnable action, Runnable onClose) {\n\t\tLabel label = new Label(text);\n\t\tlabel.setPadding(new Insets(10, 5, 10, 0));\n\t\tButton closeButton = new ActionButton(new FontIconView(CarbonIcons.CLOSE), () -> {\n\t\t\tMenu parent = getParentMenu();\n\t\t\tif (parent != null)\n\t\t\t\tparent.getItems().remove(this);\n\t\t\tonClose.run();\n\n\t\t\t// Mark as disabled to show that the closure has been processed.\n\t\t\t// We can't instantly refresh the menu, so this is as good as we can do.\n\t\t\tsetDisable(true);\n\t\t});\n\t\tcloseButton.getStyleClass().addAll(Styles.RIGHT_PILL);\n\t\tcloseButton.prefWidthProperty().bind(closeButton.heightProperty());\n\t\tgetStyleClass().add(\"closable-menu-item\");\n\n\t\t// Layout\n\t\tHBox box = new HBox();\n\t\tbox.setSpacing(10);\n\t\tbox.setAlignment(Pos.CENTER_LEFT);\n\t\tbox.getChildren().addAll(closeButton, graphic, label);\n\t\tsetContent(box);\n\n\t\t// Hack to make the box fill the menu width.\n\t\t//  - When we are added to a menu...\n\t\t//    - And the menu is shown...\n\t\t//      - Initially show the precomputed size for items...\n\t\t//        - But then use those sizes of all items to figure the max width and set that for this (all) boxes\n\t\tNodeEvents.runOnceOnChange(parentMenuProperty(), parent -> {\n\t\t\tNodeEvents.dispatchAndRemoveIf(parent.showingProperty(), showing -> {\n\t\t\t\tif (showing) {\n\t\t\t\t\tbox.setPrefWidth(Region.USE_COMPUTED_SIZE);\n\t\t\t\t\tFxThreadUtil.delayedRun(1, () -> {\n\t\t\t\t\t\tdouble size = parent.getItems().stream()\n\t\t\t\t\t\t\t\t.filter(i -> i instanceof CustomMenuItem)\n\t\t\t\t\t\t\t\t.map(i -> ((CustomMenuItem) i).getContent())\n\t\t\t\t\t\t\t\t.mapToDouble(n -> n.getBoundsInParent().getWidth())\n\t\t\t\t\t\t\t\t.max().orElse(100);\n\t\t\t\t\t\tdouble max = Math.max(100, size);\n\t\t\t\t\t\tbox.setPrefWidth(max);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t});\n\t\t});\n\n\t\t// With 'setOnAction(...)' the action is run on the JFX thread.\n\t\t// We want the actions to be run on background threads so the UI does not hang on long-running tasks.\n\t\tsetOnAction(e -> ThreadUtil.run(action));\n\t}\n\n\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/DynamicNumericTextField.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport atlantafx.base.controls.CustomTextField;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.Property;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.scene.control.TextFormatter;\nimport software.coley.recaf.util.NumberUtil;\n\n/**\n * A numeric text field that can change the type of {@link Number} it holds.\n * Based on the text content, it can represent:\n * <ul>\n *     <li>{@link Integer}</li>\n *     <li>{@link Double}</li>\n *     <li>{@link Long}</li>\n *     <li>{@link Float}</li>\n * </ul>\n *\n * @author Matt Coley\n */\npublic class DynamicNumericTextField extends CustomTextField {\n\tprivate final Property<Number> numericValue = new SimpleObjectProperty<>();\n\tprivate final Property<Class<? extends Number>> numericType = new SimpleObjectProperty<>();\n\n\t/**\n\t * New text field with properties to bind to.\n\t *\n\t * @param numericValueProperty\n\t * \t\tNumber value bind target.\n\t * @param numericTypeProperty\n\t * \t\tNumber type bind target.\n\t */\n\tpublic DynamicNumericTextField(@Nonnull ObjectProperty<Number> numericValueProperty,\n\t\t\t\t\t\t\t\t   @Nonnull ObjectProperty<Class<? extends Number>> numericTypeProperty) {\n\t\tthis();\n\n\t\tnumericValueProperty.bindBidirectional(numericValue);\n\t\tnumericTypeProperty.bindBidirectional(numericType);\n\n\t\tsetText(NumberUtil.toString(numericValueProperty.get()));\n\t}\n\n\t/**\n\t * New empty text field.\n\t */\n\tpublic DynamicNumericTextField() {\n\t\ttextProperty().addListener((ob, old, cur) -> {\n\t\t\tif (cur.isEmpty()) {\n\t\t\t\tnumericValue.setValue(0);\n\t\t\t\tnumericType.setValue(Integer.class);\n\t\t\t} else {\n\t\t\t\tNumber value = NumberUtil.parse(cur);\n\t\t\t\tnumericValue.setValue(value);\n\t\t\t\tnumericType.setValue(value.getClass());\n\t\t\t}\n\t\t});\n\n\t\tsetTextFormatter(new TextFormatter<>(c -> {\n\t\t\tString newText = c.getControlNewText();\n\t\t\tif (newText.isEmpty())\n\t\t\t\treturn c;\n\t\t\ttry {\n\t\t\t\tNumberUtil.parse(newText);\n\t\t\t\treturn c;\n\t\t\t} catch (NumberFormatException ex) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}));\n\n\t\tBoundLabel typeLabel = new BoundLabel(numericType.map(Class::getSimpleName));\n\t\ttypeLabel.getStyleClass().add(Styles.TEXT_SUBTLE);\n\t\tsetRight(typeLabel);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/FontIconView.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.paint.Color;\nimport javafx.scene.text.Font;\nimport javafx.scene.text.Text;\nimport org.kordamp.ikonli.Ikon;\nimport org.kordamp.ikonli.IkonHandler;\nimport org.kordamp.ikonli.IkonResolver;\nimport org.kordamp.ikonli.IkonResolverProvider;\nimport org.kordamp.ikonli.javafx.FontIcon;\nimport org.kordamp.ikonli.javafx.JavaFXFontLoader;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Minimal reimplementation of {@link FontIcon} because its manual CSS writing instead of working with properties\n * prevents it from being used with AtlantaFX.\n *\n * @author Matt Coley\n */\npublic class FontIconView extends Text {\n\tprivate static final IkonResolver resolver = IkonResolverProvider.getInstance(JavaFXFontLoader.getInstance());\n\tprivate static final Map<Integer, IkonHandler> codeToHandler = new HashMap<>();\n\n\t/**\n\t * @param icon\n\t * \t\tIcon to use.\n\t */\n\tpublic FontIconView(@Nonnull Ikon icon) {\n\t\tthis(icon, IconView.DEFAULT_ICON_SIZE, null);\n\t}\n\n\t/**\n\t * @param icon\n\t * \t\tIcon to use.\n\t * @param color\n\t * \t\tColor to use.\n\t */\n\tpublic FontIconView(@Nonnull Ikon icon, @Nonnull Color color) {\n\t\tthis(icon, IconView.DEFAULT_ICON_SIZE, color);\n\t}\n\n\t/**\n\t * @param icon\n\t * \t\tIcon to use.\n\t * @param size\n\t * \t\tSize of icon in pixels.\n\t */\n\tpublic FontIconView(@Nonnull Ikon icon, int size) {\n\t\tthis(icon, size, null);\n\t}\n\n\t/**\n\t * @param icon\n\t * \t\tIcon to use.\n\t * @param size\n\t * \t\tSize of icon in pixels.\n\t * @param color\n\t * \t\tOptional color to use.\n\t */\n\tpublic FontIconView(@Nonnull Ikon icon, int size, @Nullable Color color) {\n\t\tint code = icon.getCode();\n\t\tIkonHandler ikonHandler = codeToHandler.computeIfAbsent(code, k -> resolver.resolve(icon.getDescription()));\n\n\t\t// Fetch expected font instance, configure size/color\n\t\tFont font = (Font) ikonHandler.getFont();\n\t\tFont sizedFont = new Font(font.getFamily(), size);\n\t\tsetFont(sizedFont);\n\t\tif (color != null) setFill(color);\n\t\telse setStyle(\"-fx-fill: -color-fg-default;\");\n\n\t\t// Set text to ikonli character\n\t\tif (code <= '\\uFFFF') {\n\t\t\tsetText(String.valueOf((char) code));\n\t\t} else {\n\t\t\tchar[] charPair = Character.toChars(code);\n\t\t\tString symbol = new String(charPair);\n\t\t\tsetText(symbol);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/GraphicActionButton.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.Node;\nimport org.kordamp.ikonli.Ikon;\n\n/**\n * Action button but with only a graphic.\n *\n * @author Matt Coley\n */\npublic class GraphicActionButton extends ActionButton {\n\t/**\n\t * @param graphic\n\t * \t\tButton graphic.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic GraphicActionButton(@Nonnull Node graphic, @Nonnull Runnable action) {\n\t\tsuper((String) null, action);\n\t\tsetGraphic(graphic);\n\t\tgetStyleClass().add(\"graphic-button\");\n\t}\n\n\t/**\n\t * @param icon\n\t * \t\tButton display icon.\n\t * @param action\n\t * \t\tAction to run on-click.\n\t */\n\tpublic GraphicActionButton(@Nonnull Ikon icon, @Nonnull Runnable action) {\n\t\tsuper(icon, action);\n\t\tgetStyleClass().add(\"graphic-button\");\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/IconView.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\nimport software.coley.recaf.util.Icons;\n\n/**\n * {@link ImageView} extension for icons. It is recommended to use use {@link Icons}\n * to fetch {@link Image} instances, and generally creating instances of this class.\n *\n * @author Matt Coley\n * @see Icons\n */\npublic class IconView extends ImageView {\n\t/**\n\t * Default icon size, 16x16.\n\t */\n\tpublic static final int DEFAULT_ICON_SIZE = 16;\n\n\t/**\n\t * @param image\n\t * \t\tImage resource.\n\t */\n\tpublic IconView(Image image) {\n\t\tthis(image, DEFAULT_ICON_SIZE);\n\t}\n\n\t/**\n\t * @param image\n\t * \t\tImage resource.\n\t * @param size\n\t * \t\tImage width/height.\n\t */\n\tpublic IconView(Image image, int size) {\n\t\tsuper(image);\n\t\tfitHeightProperty().set(size);\n\t\tfitWidthProperty().set(size);\n\t\t// Setting to false seems to make smaller images scale properly (like 10x10)\n\t\tsetPreserveRatio(false);\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/ImageCanvas.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.canvas.Canvas;\nimport javafx.scene.canvas.GraphicsContext;\nimport javafx.scene.effect.ColorAdjust;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\n\n/**\n * An alternative to {@link ImageView} which allows rendering lower res images with less aggressive linear filtering.\n *\n * @author Matt Coley\n */\npublic class ImageCanvas extends Canvas {\n\tprivate Image image;\n\tprivate ColorAdjust colorAdjust;\n\tprivate double saturation;\n\tprivate double brightness;\n\tprivate double contrast;\n\n\t/**\n\t * @param image\n\t * \t\tImage to draw.\n\t */\n\tpublic void setImage(@Nonnull Image image) {\n\t\tthis.image = image;\n\n\t\t// By default, the canvas is 0 by 0.\n\t\tsetWidth(image.getWidth());\n\t\tsetHeight(image.getHeight());\n\n\t\tdraw();\n\t}\n\n\t/**\n\t * @param brightness\n\t * \t\tNew relative brightness adjustment.\n\t */\n\tpublic void setBrightness(double brightness) {\n\t\tthis.brightness = brightness;\n\t\tcolorAdjust = new ColorAdjust(0, saturation, brightness, contrast);\n\t\tdraw();\n\t}\n\n\t/**\n\t * @param contrast\n\t * \t\tNew relative contrast adjustment.\n\t */\n\tpublic void setContrast(double contrast) {\n\t\tthis.contrast = contrast;\n\t\tcolorAdjust = new ColorAdjust(0, saturation, brightness, contrast);\n\t\tdraw();\n\t}\n\n\tprivate void draw() {\n\t\t// Draw the image with smoothing disabled.\n\t\t// With smoothing, it uses an aggressive linear filter which looks horrible on pixel-art/low-res images.\n\t\t// Without smoothing, it still uses a linear filter, but it is MUCH less aggressive.\n\t\tGraphicsContext gc = getGraphicsContext2D();\n\t\tgc.setEffect(null); // Clearing won't work if certain effects are applied, so reset it before clearing.\n\t\tgc.clearRect(0, 0, getWidth(), getHeight());\n\t\tif (image != null) {\n\t\t\tif (colorAdjust != null)\n\t\t\t\tgc.setEffect(colorAdjust);\n\t\t\tgc.setImageSmoothing(false);\n\t\t\tgc.drawImage(image, 0, 0);\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/ModalPaneComponent.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport atlantafx.base.controls.ModalPane;\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.EditorComponent;\n\n/**\n * Modal pane exposed as an {@link EditorComponent}.\n *\n * @author Matt Coley\n */\npublic class ModalPaneComponent extends ModalPane implements EditorComponent {\n\t{\n\t\tsetSkin(createDefaultSkin());\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\teditor.getPrimaryStack().getChildren().add(0, this);\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\teditor.getPrimaryStack().getChildren().remove(this);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/ObservableCheckBox.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.control.CheckBox;\nimport software.coley.observables.ObservableBoolean;\n\n/**\n * Checkbox with binding to an {@link ObservableBoolean}.\n *\n * @author Matt Coley\n */\npublic class ObservableCheckBox extends CheckBox implements Tooltipable {\n\t/**\n\t * @param observable\n\t * \t\tValue to bind to.\n\t * @param binding\n\t * \t\tText binding.\n\t */\n\tpublic ObservableCheckBox(@Nonnull ObservableBoolean observable, @Nonnull ObservableValue<String> binding) {\n\t\tsetSelected(observable.getValue());\n\t\tselectedProperty().addListener((ob, old, cur) -> observable.setValue(cur));\n\t\ttextProperty().bind(binding);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/ObservableComboBox.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.SingleSelectionModel;\nimport software.coley.observables.Observable;\nimport software.coley.observables.ObservableObject;\n\nimport java.util.Collection;\n\n/**\n * Combo box with binding to a {@link ObservableObject}.\n *\n * @param <T>\n * \t\tObservable value type.\n *\n * @author Matt Coley\n */\npublic class ObservableComboBox<T> extends ComboBox<T> implements Tooltipable {\n\t/**\n\t * @param value\n\t * \t\tObservable.\n\t * @param values\n\t * \t\tAvailable options.\n\t */\n\tpublic ObservableComboBox(@Nonnull Observable<T> value, @Nonnull Collection<? extends T> values) {\n\t\tgetItems().addAll(values);\n\t\tgetSelectionModel().select(value.getValue());\n\t\tSingleSelectionModel<T> model = getSelectionModel();\n\t\tvalue.addChangeListener((ob, old, cur) -> {\n\t\t\tif (model.getSelectedItem() != cur)\n\t\t\t\tmodel.select(cur);\n\t\t});\n\t\tmodel.selectedItemProperty().addListener((ob, old, cur) -> {\n\t\t\tif (value.getValue() != cur)\n\t\t\t\tvalue.setValue(cur);\n\t\t});\n\t\tsetMaxWidth(Double.MAX_VALUE);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/ObservableSpinner.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport atlantafx.base.util.IntegerStringConverter;\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.Spinner;\nimport javafx.scene.control.SpinnerValueFactory;\nimport software.coley.observables.Observable;\nimport software.coley.observables.ObservableInteger;\n\n/**\n * Spinner with binding to a numeric {@link Observable}.\n *\n * @param <T>\n * \t\tNumeric type.\n *\n * @author Matt Coley\n */\npublic class ObservableSpinner<T> extends Spinner<T> implements Tooltipable {\n\tprivate ObservableSpinner(@Nonnull Observable<T> observable) {\n\t\tvalueProperty().addListener((ob, old, cur) -> observable.setValue(cur));\n\t}\n\n\t/**\n\t * @param observable\n\t * \t\tObservable to wrap.\n\t * @param min\n\t * \t\tMin value.\n\t * @param max\n\t * \t\tMax value.\n\t *\n\t * @return Spinner instance.\n\t */\n\tpublic static ObservableSpinner<Integer> intSpinner(@Nonnull ObservableInteger observable, int min, int max) {\n\t\tint initial = observable.getValue();\n\t\tObservableSpinner<Integer> spinner = new ObservableSpinner<>(observable);\n\t\tspinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(min, max, initial));\n\t\tspinner.setEditable(true);\n\t\tIntegerStringConverter.createFor(spinner);\n\t\treturn spinner;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/PannableView.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.geometry.Bounds;\nimport javafx.scene.Cursor;\nimport javafx.scene.Node;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.input.ScrollEvent;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.Pane;\n\n/**\n * Wraps a {@link Node} providing panning and zoom capabilities.\n *\n * @author Matt Coley\n */\npublic class PannableView extends Pane {\n\tprivate static final double MIN_ZOOM = 0.001;\n\tprivate static final double MAX_ZOOM = 100;\n\tprivate final BorderPane nodeWrapper;\n\tprivate double startDragX;\n\tprivate double startDragY;\n\tprivate double initTranslateX;\n\tprivate double initTranslateY;\n\tprivate double translateX;\n\tprivate double translateY;\n\tprivate boolean allowZoom = true;\n\n\t/**\n\t * @param node\n\t * \t\tNode to wrap.\n\t */\n\tpublic PannableView(@Nonnull Node node) {\n\t\tnodeWrapper = new BorderPane(node);\n\t\tgetChildren().addAll(nodeWrapper);\n\t\tsetOnMousePressed(e -> {\n\t\t\tif (e.getButton() == MouseButton.PRIMARY) {\n\t\t\t\tstartDragX = translateX - e.getX();\n\t\t\t\tstartDragY = translateY - e.getY();\n\t\t\t\tsetCursor(Cursor.MOVE);\n\t\t\t}\n\t\t});\n\t\tsetOnMouseReleased(e -> {\n\t\t\tsetCursor(null);\n\t\t});\n\t\tsetOnMouseDragged(e -> {\n\t\t\tif (e.getButton() == MouseButton.PRIMARY) {\n\t\t\t\ttranslateX = startDragX + e.getX();\n\t\t\t\ttranslateY = startDragY + e.getY();\n\n\t\t\t\t// Translations get applied to our wrapper.\n\t\t\t\t// Managing the relationship between translation and zoom on one level is too much of a hassle.\n\t\t\t\tnodeWrapper.setTranslateX(translateX);\n\t\t\t\tnodeWrapper.setTranslateY(translateY);\n\t\t\t}\n\t\t});\n\t\tsetOnScroll(e -> {\n\t\t\t// Handle zoom on the actual node itself, rather than on our wrapper\n\t\t\tif (allowZoom)\n\t\t\t\tzoom(node, e);\n\t\t\te.consume();\n\t\t});\n\t}\n\n\t/**\n\t * @return {@code true} when scroll-wheel allows zooming in/out on the contained view.\n\t * {@code false} when zooming is disabled.\n\t */\n\tpublic boolean isAllowZoom() {\n\t\treturn allowZoom;\n\t}\n\n\t/**\n\t * @param allowZoom\n\t *        {@code true} to allow scroll-wheel to zoom in/out on the contained view.\n\t *        {@code false} to disable zooming.\n\t */\n\tpublic void setAllowZoom(boolean allowZoom) {\n\t\tthis.allowZoom = allowZoom;\n\t}\n\n\t/**\n\t * Sets the initial values to reset back to when calling {@link #resetTranslation()}.\n\t *\n\t * @param x\n\t * \t\tInitial translation X.\n\t * @param y\n\t * \t\tInitial translation Y.\n\t */\n\tpublic void setInitTranslation(double x, double y) {\n\t\tinitTranslateX = x;\n\t\tinitTranslateY = y;\n\t}\n\n\t/**\n\t * Reset translation offset to 0.\n\t */\n\tpublic void resetTranslation() {\n\t\ttranslateX = initTranslateX;\n\t\ttranslateY = initTranslateY;\n\n\t\tnodeWrapper.setTranslateX(initTranslateX);\n\t\tnodeWrapper.setTranslateY(initTranslateY);\n\n\t\tNode node = nodeWrapper.getCenter();\n\t\tnode.setTranslateX(0);\n\t\tnode.setTranslateY(0);\n\t}\n\n\t/**\n\t * Reset zoom level to 0.\n\t */\n\tpublic void resetZoom() {\n\t\tNode node = nodeWrapper.getCenter();\n\t\tnode.setScaleX(1);\n\t\tnode.setScaleY(1);\n\t\tnode.setTranslateX(0);\n\t\tnode.setTranslateY(0);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to zoom into.\n\t * @param event\n\t * \t\tScroll event to extract zoom info from.\n\t */\n\tprivate static void zoom(@Nonnull Node node, @Nonnull ScrollEvent event) {\n\t\tzoom(node, Math.pow(1.01, event.getDeltaY()), event.getSceneX(), event.getSceneY());\n\t}\n\n\t/**\n\t * See: <a href=\"https://stackoverflow.com/questions/27356577/scale-at-pivot-point-in-an-already-scaled-node\">\n\t * Jens-Peter Haack's zoom</a>\n\t *\n\t * @param node\n\t * \t\tNode to zoom into.\n\t * @param factor\n\t * \t\tFactor of zoom to apply.\n\t * @param x\n\t * \t\tPosition to zoom into.\n\t * @param y\n\t * \t\tPosition to zoom into.\n\t */\n\tprivate static void zoom(@Nonnull Node node, double factor, double x, double y) {\n\t\tdouble oldScale = node.getScaleX();\n\t\tdouble scale = oldScale * factor;\n\n\t\tif (scale < MIN_ZOOM) scale = MIN_ZOOM;\n\t\tif (scale > MAX_ZOOM) scale = MAX_ZOOM;\n\t\tnode.setScaleX(scale);\n\t\tnode.setScaleY(scale);\n\n\t\tdouble f = (scale / oldScale) - 1;\n\t\tBounds bounds = node.localToScene(node.getBoundsInLocal());\n\t\tdouble dx = (x - (bounds.getWidth() / 2 + bounds.getMinX()));\n\t\tdouble dy = (y - (bounds.getHeight() / 2 + bounds.getMinY()));\n\n\t\tnode.setTranslateX(node.getTranslateX() - f * dx);\n\t\tnode.setTranslateY(node.getTranslateY() - f * dy);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/PathNodeTree.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport atlantafx.base.theme.Styles;\nimport atlantafx.base.theme.Tweaks;\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\nimport javafx.scene.input.KeyCode;\nimport software.coley.recaf.path.IncompletePathException;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.ui.control.tree.TreeItems;\nimport software.coley.recaf.ui.control.tree.WorkspaceTreeCell;\n\n/**\n * A {@link TreeView} handling {@link PathNode} content. Pre-configures the following:\n * <ul>\n *     <li>{@link TreeView#cellFactoryProperty()} - Text / graphic / context menu</li>\n *     <li>{@link TreeView#onKeyPressedProperty()} - Recursive expansion/closure, open selected paths with enter</li>\n *     <li>{@link TreeView#getStyleClass()} - Some additional css styles</li>\n * </ul>\n *\n * @author Matt Coley\n */\npublic class PathNodeTree extends TreeView<PathNode<?>> {\n\tprotected final ObjectProperty<ContextSource> contextSourceObjectProperty = new SimpleObjectProperty<>(ContextSource.REFERENCE);\n\n\t/**\n\t * @param configurationService\n\t * \t\tCell service to configure tree cell rendering and population.\n\t * @param actions\n\t * \t\tActions service to handle opening {@link PathNode} items.\n\t */\n\tpublic PathNodeTree(@Nonnull CellConfigurationService configurationService, @Nonnull Actions actions) {\n\t\tsetShowRoot(false);\n\t\tsetCellFactory(param -> buildCell(configurationService));\n\t\tgetStyleClass().addAll(Tweaks.EDGE_TO_EDGE, Styles.DENSE);\n\t\tsetOnKeyPressed(e -> {\n\t\t\tKeyCode code = e.getCode();\n\t\t\tif (code == KeyCode.RIGHT || code == KeyCode.KP_RIGHT) {\n\t\t\t\tTreeItem<PathNode<?>> selected = getSelectionModel().getSelectedItem();\n\t\t\t\tif (selected != null)\n\t\t\t\t\tTreeItems.recurseOpen(selected);\n\t\t\t} else if (code == KeyCode.LEFT || code == KeyCode.KP_LEFT) {\n\t\t\t\tTreeItem<PathNode<?>> selected = getSelectionModel().getSelectedItem();\n\t\t\t\tif (selected != null)\n\t\t\t\t\tTreeItems.recurseClose(this, selected);\n\t\t\t} else if (code == KeyCode.ENTER) {\n\t\t\t\tTreeItem<PathNode<?>> selected = getSelectionModel().getSelectedItem();\n\t\t\t\tif (selected != null)\n\t\t\t\t\thandleEnter(actions, selected);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Called when the user presses {@link KeyCode#ENTER} on a given path.\n\t *\n\t * @param actions\n\t * \t\tActions service to handle opening {@link PathNode} items.\n\t * @param selected\n\t * \t\tSelected item holding a {@link PathNode}.\n\t */\n\tprotected void handleEnter(@Nonnull Actions actions, @Nonnull TreeItem<PathNode<?>> selected) {\n\t\tif (selected.isLeaf()) {\n\t\t\ttry {\n\t\t\t\tactions.gotoDeclaration(selected.getValue());\n\t\t\t} catch (IncompletePathException ignored) {\n\t\t\t\t// ignored\n\t\t\t}\n\t\t} else {\n\t\t\tif (selected.isExpanded())\n\t\t\t\tTreeItems.recurseClose(this, selected);\n\t\t\telse\n\t\t\t\tTreeItems.recurseOpen(selected);\n\t\t}\n\t}\n\n\t/**\n\t * @param configurationService\n\t * \t\tCell service to configure tree cell rendering and population.\n\t *\n\t * @return New tree cell to represent an item in this tree.\n\t */\n\t@Nonnull\n\tprotected WorkspaceTreeCell buildCell(@Nonnull CellConfigurationService configurationService) {\n\t\treturn new WorkspaceTreeCell(contextSourceObjectProperty.get(), configurationService);\n\t}\n\n\t/**\n\t * @return Property of the {@link ContextSource} passed to newly created {@link WorkspaceTreeCell} instances.\n\t */\n\t@Nonnull\n\tpublic ObjectProperty<ContextSource> contextSourceObjectPropertyProperty() {\n\t\treturn contextSourceObjectProperty;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/ReorderableListCell.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport com.google.common.collect.Iterables;\nimport jakarta.annotation.Nonnull;\nimport javafx.collections.ObservableList;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.ListView;\nimport javafx.scene.input.ClipboardContent;\nimport javafx.scene.input.DragEvent;\nimport javafx.scene.input.Dragboard;\nimport javafx.scene.input.TransferMode;\n\nimport java.util.Objects;\nimport java.util.function.Function;\n\n/**\n * A list cell that allows re-ordering within the parent {@link ListView} via drag-drop.\n *\n * @param <T>\n * \t\tCell content type.\n *\n * @author Matt Coley\n */\npublic class ReorderableListCell<T> extends ListCell<T> {\n\t/**\n\t * New list cell with {@code T} mapped via {@link Object#toString()} to {@link String} for drag-drop identification.\n\t */\n\tpublic ReorderableListCell() {\n\t\tthis(Objects::toString);\n\t}\n\n\t/**\n\t * New list cell with {@code T} mapped via a given function to {@link String} for drag-drop identification.\n\t *\n\t * @param converter\n\t * \t\tT instance to string converter.\n\t */\n\tpublic ReorderableListCell(@Nonnull Function<T, String> converter) {\n\t\tvar thisCell = this;\n\n\t\t// Begin by placing the item's string representation into the drag-board.\n\t\tsetOnDragDetected(event -> {\n\t\t\tif (getItem() == null)\n\t\t\t\treturn;\n\t\t\tDragboard dragboard = startDragAndDrop(TransferMode.MOVE);\n\t\t\tClipboardContent content = new ClipboardContent();\n\t\t\tcontent.putString(converter.apply(getItem()));\n\t\t\tdragboard.setDragView(thisCell.snapshot(null, null));\n\t\t\tdragboard.setContent(content);\n\t\t\tevent.consume();\n\t\t});\n\n\t\t// All reorderable list cells can accept others.\n\t\tsetOnDragOver(e -> {\n\t\t\tif (e.getGestureSource() != thisCell && e.getDragboard().hasString())\n\t\t\t\te.acceptTransferModes(TransferMode.MOVE);\n\t\t\te.consume();\n\t\t});\n\n\t\t// While dragging over this cell, set it to be transparent to indicate this\n\t\t// is the cell that will be swapped upon completion.\n\t\tsetOnDragEntered(e -> {\n\t\t\tif (e.getGestureSource() != thisCell && e.getDragboard().hasString())\n\t\t\t\tsetOpacity(0.3);\n\t\t});\n\t\tsetOnDragExited(e -> {\n\t\t\tif (e.getGestureSource() != thisCell && e.getDragboard().hasString())\n\t\t\t\tsetOpacity(1);\n\t\t});\n\n\t\t// Complete by swapping the cell indices.\n\t\tsetOnDragDropped(event -> {\n\t\t\tif (getItem() == null)\n\t\t\t\treturn;\n\n\t\t\tDragboard dragboard = event.getDragboard();\n\t\t\tif (dragboard.hasString()) {\n\t\t\t\tListView<T> parent = getListView();\n\t\t\t\tif (parent == null)\n\t\t\t\t\treturn;\n\n\t\t\t\t// Get the drag-drop initiator cell index, and the target cell index.\n\t\t\t\tObservableList<T> items = parent.getItems();\n\t\t\t\tString string = dragboard.getString();\n\t\t\t\tint draggedIdx = Iterables.indexOf(items, item -> string.equals(converter.apply(item)));\n\t\t\t\tint thisIdx = items.indexOf(getItem());\n\n\t\t\t\t// Swap and complete.\n\t\t\t\tT draggedItem = items.get(draggedIdx);\n\t\t\t\titems.set(draggedIdx, getItem());\n\t\t\t\titems.set(thisIdx, draggedItem);\n\n\t\t\t\tevent.setDropCompleted(true);\n\t\t\t} else {\n\t\t\t\tevent.setDropCompleted(false);\n\t\t\t}\n\n\t\t\tevent.consume();\n\t\t});\n\n\t\tsetOnDragDone(DragEvent::consume);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/ResizableCanvas.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport javafx.scene.canvas.Canvas;\n\n/**\n * Allows a canvas to be put into an auto-sizing container such as {@link javafx.scene.layout.BorderPane}\n * without any needed manual property binds.\n *\n * @author Matt Coley\n */\npublic class ResizableCanvas extends Canvas {\n\t@Override\n\tpublic boolean isResizable() {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic double maxHeight(double width) {\n\t\treturn Double.POSITIVE_INFINITY;\n\t}\n\n\t@Override\n\tpublic double maxWidth(double height) {\n\t\treturn Double.POSITIVE_INFINITY;\n\t}\n\n\t@Override\n\tpublic double minWidth(double height) {\n\t\treturn 1;\n\t}\n\n\t@Override\n\tpublic double minHeight(double width) {\n\t\treturn 1;\n\t}\n\n\t@Override\n\tpublic void resize(double width, double height) {\n\t\tsetWidth(width);\n\t\tsetHeight(height);\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/SubLabeled.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport atlantafx.base.theme.Styles;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.VBox;\n\n/**\n * A component that has a primary larger label, with a smaller label underneath it.\n *\n * @author Matt Coley\n */\npublic class SubLabeled extends VBox {\n\tprivate final ObservableValue<String> primary;\n\tprivate final ObservableValue<String> secondary;\n\n\t/**\n\t * @param primary\n\t * \t\tMain text, more prominent.\n\t * @param secondary\n\t * \t\tSecondary text, less prominent.\n\t */\n\tpublic SubLabeled(ObservableValue<String> primary, ObservableValue<String> secondary) {\n\t\tthis(primary, secondary, Styles.TITLE_3);\n\t}\n\n\t/**\n\t * @param primary\n\t * \t\tMain text, more prominent.\n\t * @param secondary\n\t * \t\tSecondary text, less prominent.\n\t * @param primaryClass\n\t * \t\tHeader style class.\n\t */\n\tpublic SubLabeled(ObservableValue<String> primary, ObservableValue<String> secondary, String primaryClass) {\n\t\tthis.primary = primary;\n\t\tthis.secondary = secondary;\n\t\tLabel lblPrimary = new BoundLabel(primary);\n\t\tLabel lblSecondary = new BoundLabel(secondary);\n\t\tlblPrimary.getStyleClass().add(primaryClass);\n\t\tlblSecondary.getStyleClass().add(Styles.TEXT_SUBTLE);\n\t\tgetChildren().addAll(lblPrimary, lblSecondary);\n\t}\n\n\t/**\n\t * @return Primary label text.\n\t */\n\tpublic String getPrimaryText() {\n\t\treturn primary.getValue();\n\t}\n\n\t/**\n\t * @return Secondary label text.\n\t */\n\tpublic String getSecondaryText() {\n\t\treturn secondary.getValue();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/Tooltipable.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Tooltip;\nimport software.coley.recaf.util.Lang;\n\n/**\n * Support for tooltips with bindings to {@link Lang} for any supported control type.\n *\n * @author Matt Coley\n */\npublic interface Tooltipable {\n\t/**\n\t * Implemented by {@link Control#setTooltip(Tooltip)}.\n\t *\n\t * @param tooltip\n\t * \t\tTooltip to assign.\n\t */\n\tvoid setTooltip(Tooltip tooltip);\n\n\t/**\n\t * @param tooltipKey\n\t * \t\tTranslation key for tooltip display.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tdefault <T extends Tooltipable> T withTooltip(@Nonnull String tooltipKey) {\n\t\treturn withTooltip(Lang.getBinding(tooltipKey));\n\t}\n\n\t/**\n\t * @param tooltipValue\n\t * \t\tText binding value for tooltip display.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\t@SuppressWarnings(\"unchecked\")\n\tdefault <T extends Tooltipable> T withTooltip(@Nonnull ObservableValue<String> tooltipValue) {\n\t\tTooltip tooltip = new Tooltip();\n\t\ttooltip.textProperty().bind(tooltipValue);\n\t\tsetTooltip(tooltip);\n\t\treturn (T) this;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/VirtualizedScrollPaneWrapper.java",
    "content": "package software.coley.recaf.ui.control;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.animation.AnimationTimer;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.DoubleProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleDoubleProperty;\nimport javafx.scene.Cursor;\nimport javafx.scene.control.ScrollBar;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.layout.Region;\nimport org.fxmisc.flowless.Virtualized;\nimport org.fxmisc.flowless.VirtualizedScrollPane;\nimport org.reactfx.value.Var;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.util.NodeEvents;\nimport software.coley.recaf.util.ReflectUtil;\n\n/**\n * Wrapper for {@link VirtualizedScrollPane} to properly expose properties with JavaFX's property types instead of\n * {@link Var} which cannot be used in a number of scenarios.\n *\n * @param <V>\n * \t\tNode type.\n *\n * @author Matt Coley\n * @see AutoScrollPane\n */\npublic class VirtualizedScrollPaneWrapper<V extends Region & Virtualized> extends VirtualizedScrollPane<V> {\n\tprivate static final double AUTO_SCROLL_MULTIPLIER = 0.1;\n\tprivate static final double AUTO_SCROLL_BUFFER_PX = 5;\n\tprivate final DoubleProperty xScrollProperty = new SimpleDoubleProperty(0);\n\tprivate final DoubleProperty yScrollProperty = new SimpleDoubleProperty(0);\n\tprivate final BooleanProperty canAutoScroll = new SimpleBooleanProperty(true);\n\tprivate final ScrollBar horizontalScrollbar;\n\tprivate final ScrollBar verticalScrollbar;\n\tprivate Cursor preAutoScrollCursor;\n\tprivate boolean isAutoScrolling;\n\tprivate double autoScrollStartY;\n\tprivate double autoScrollCurrentY;\n\tprivate final AnimationTimer autoScrollTimer = new AnimationTimer() {\n\t\t@Override\n\t\tpublic void handle(long now) {\n\t\t\tupdateAutoScroll();\n\t\t}\n\t};\n\n\t/**\n\t * @param content\n\t * \t\tVirtualized content.\n\t */\n\tpublic VirtualizedScrollPaneWrapper(V content) {\n\t\tsuper(content);\n\n\t\thorizontalScrollbar = Unchecked.get(() -> ReflectUtil.quietGet(this, VirtualizedScrollPane.class.getDeclaredField(\"hbar\")));\n\t\tverticalScrollbar = Unchecked.get(() -> ReflectUtil.quietGet(this, VirtualizedScrollPane.class.getDeclaredField(\"vbar\")));\n\n\t\tsetup();\n\t}\n\n\tprivate void setup() {\n\t\txScrollProperty.bind(estimatedScrollXProperty());\n\t\tyScrollProperty.bind(estimatedScrollYProperty());\n\n\t\t// Handle middle mouse press to start auto-scrolling.\n\t\t// - Press initiates the auto-scroll\n\t\t// - Drag changes the auto-scroll speed\n\t\t// - Release stops the auto-scroll\n\t\tNodeEvents.addMousePressHandler(getContent(), e -> {\n\t\t\tif (canAutoScroll.get() && e.getButton() == MouseButton.MIDDLE) {\n\t\t\t\tpreAutoScrollCursor = getContent().getCursor();\n\t\t\t\tautoScrollStartY = e.getScreenY();\n\t\t\t\tautoScrollCurrentY = autoScrollStartY;\n\t\t\t}\n\t\t});\n\t\tNodeEvents.addMouseReleaseHandler(getContent(), e -> {\n\t\t\tif (e.getButton() == MouseButton.MIDDLE && isAutoScrolling()) {\n\t\t\t\tgetContent().setCursor(preAutoScrollCursor);\n\t\t\t\tautoScrollTimer.stop();\n\t\t\t\tautoScrollCurrentY = -1;\n\t\t\t\tisAutoScrolling = false;\n\t\t\t}\n\t\t});\n\t\tNodeEvents.addMouseDraggedHandler(getContent(), e -> {\n\t\t\tif (e.getButton() == MouseButton.MIDDLE) {\n\t\t\t\tautoScrollCurrentY = e.getScreenY();\n\n\t\t\t\t// Only begin the auto-scroll after the user has moved a couple of pixels away.\n\t\t\t\t//\n\t\t\t\t// We do this because the 'content' node may have middle click behavior similar\n\t\t\t\t// to how a browser opens/closes tabs when using the middle mouse button on links.\n\t\t\t\t// By not initiating until we're sure the user intends to move around via auto-scroll\n\t\t\t\t// we don't mess with the UX of the existing behavior in the 'content' node.\n\t\t\t\tif (!isAutoScrolling && Math.abs(autoScrollCurrentY - autoScrollStartY) > AUTO_SCROLL_BUFFER_PX) {\n\t\t\t\t\tisAutoScrolling = true;\n\t\t\t\t\tautoScrollTimer.start();\n\t\t\t\t\tgetContent().setCursor(Cursor.V_RESIZE);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate void updateAutoScroll() {\n\t\tdouble deltaY = autoScrollCurrentY - autoScrollStartY;\n\n\t\t// Get current scroll values\n\t\tdouble value = verticalScrollbar.getValue();\n\t\tdouble min = verticalScrollbar.getMin();\n\t\tdouble max = verticalScrollbar.getMax();\n\n\t\t// Calculate scroll amount based on viewport size\n\t\tdouble viewportHeight = getHeight();\n\t\tdouble scrollAmount = (deltaY * AUTO_SCROLL_MULTIPLIER);\n\t\tif (Math.abs(scrollAmount) > 0.1) {\n\t\t\t// Calculate new scroll position\n\t\t\tdouble newValue = value + scrollAmount;\n\t\t\tnewValue = Math.max(min, Math.min(max, newValue));\n\n\t\t\t// Update scroll position\n\t\t\tverticalScrollbar.setValue(newValue);\n\t\t}\n\t}\n\n\t/**\n\t * @return {@code true} when this scroll pane is auto-scrolling.\n\t */\n\tpublic boolean isAutoScrolling() {\n\t\treturn isAutoScrolling;\n\t}\n\n\t/**\n\t * @return Horizontal scrollbar.\n\t */\n\t@Nonnull\n\tpublic ScrollBar getHorizontalScrollbar() {\n\t\treturn horizontalScrollbar;\n\t}\n\n\t/**\n\t * @return Vertical scrollbar.\n\t */\n\t@Nonnull\n\tpublic ScrollBar getVerticalScrollbar() {\n\t\treturn verticalScrollbar;\n\t}\n\n\t/**\n\t * @return Horizontal scroll property.\n\t */\n\t@Nonnull\n\tpublic DoubleProperty horizontalScrollProperty() {\n\t\treturn xScrollProperty;\n\t}\n\n\t/**\n\t * @return Vertical scroll property.\n\t */\n\t@Nonnull\n\tpublic DoubleProperty verticalScrollProperty() {\n\t\treturn yScrollProperty;\n\t}\n\n\t/**\n\t * @return Can auto-scroll property.\n\t */\n\t@Nonnull\n\tpublic BooleanProperty canAutoScrollProperty() {\n\t\treturn canAutoScroll;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/graph/MethodCallGraphPane.java",
    "content": "package software.coley.recaf.ui.control.graph;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.event.EventHandler;\nimport javafx.scene.control.ContextMenu;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.MenuItem;\nimport javafx.scene.control.TreeCell;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.input.MouseEvent;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.paint.Color;\nimport javafx.scene.text.Text;\nimport javafx.scene.text.TextFlow;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.collections.Lists;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.Named;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.IncompletePathException;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.callgraph.CallGraph;\nimport software.coley.recaf.services.callgraph.MethodVertex;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.navigation.ClassNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * Tree display of method calls.\n *\n * @author Amejonah\n */\npublic class MethodCallGraphPane extends BorderPane implements ClassNavigable, UpdatableNavigable {\n\tprivate static final Logger logger = Logging.get(MethodCallGraphPane.class);\n\tpublic static final int MAX_TREE_DEPTH = 20;\n\tprivate final ObjectProperty<MethodMember> currentMethod = new SimpleObjectProperty<>();\n\tprivate final CallGraphTreeView graphTreeView = new CallGraphTreeView();\n\tprivate final CellConfigurationService configurationService;\n\tprivate final TextFormatConfig format;\n\tprivate final CallGraph callGraph;\n\tprivate final CallGraphMode mode;\n\tprivate final Workspace workspace;\n\tprivate final Actions actions;\n\tprivate ClassPathNode path;\n\n\tpublic MethodCallGraphPane(@Nonnull Workspace workspace, @Nonnull CallGraph callGraph, @Nonnull CellConfigurationService configurationService,\n\t                           @Nonnull TextFormatConfig format, @Nonnull Actions actions, @Nonnull CallGraphMode mode,\n\t                           @Nullable ObjectProperty<MethodMember> methodInfoObservable) {\n\t\tthis.configurationService = configurationService;\n\t\tthis.workspace = workspace;\n\t\tthis.callGraph = callGraph;\n\t\tthis.actions = actions;\n\t\tthis.format = format;\n\t\tthis.mode = mode;\n\n\t\tcurrentMethod.addListener((ob, old, cur) -> graphTreeView.onUpdate());\n\t\tgraphTreeView.onUpdate();\n\n\t\tsetCenter(graphTreeView);\n\n\t\tif (methodInfoObservable != null) currentMethod.bindBidirectional(methodInfoObservable);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn getClassPath();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassPathNode getClassPath() {\n\t\treturn path;\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof ClassPathNode classPath)\n\t\t\tthis.path = classPath;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tgraphTreeView.setRoot(null);\n\t}\n\n\t@Override\n\tpublic void requestFocus(@Nonnull ClassMember member) {\n\t\t// no-op\n\t}\n\n\t/**\n\t * Mode of the call graph, determining the direction of the graph.\n\t */\n\tpublic enum CallGraphMode {\n\t\tCALLS(MethodVertex::getCalls),\n\t\tCALLERS(MethodVertex::getCallers);\n\n\t\tprivate final Function<MethodVertex, Collection<MethodVertex>> childrenGetter;\n\n\t\tCallGraphMode(@Nonnull Function<MethodVertex, Collection<MethodVertex>> getCallers) {\n\t\t\tchildrenGetter = getCallers;\n\t\t}\n\t}\n\n\t/**\n\t * Item of a class in the hierarchy.\n\t */\n\tprivate static class CallGraphItem extends TreeItem<MethodMember> implements Comparable<CallGraphItem> {\n\t\tboolean recursive;\n\n\t\tprivate CallGraphItem(@Nonnull MethodMember method, boolean recursive) {\n\t\t\tsuper(method);\n\t\t\tthis.recursive = recursive;\n\t\t}\n\n\t\t@Nullable\n\t\tprivate ClassInfo getDeclaringClass() {\n\t\t\treturn getValue().getDeclaringClass();\n\t\t}\n\n\t\t@Override\n\t\tpublic int compareTo(CallGraphItem o) {\n\t\t\t// We want the tree display to have items in sorted order by\n\t\t\t//    package > class > method-name > method-args\n\t\t\tint cmp = 0;\n\t\t\tMethodMember method = getValue();\n\t\t\tMethodMember otherMethod = o.getValue();\n\t\t\tClassInfo declaringClass = getDeclaringClass();\n\t\t\tClassInfo otherDeclaringClass = o.getDeclaringClass();\n\t\t\tif (declaringClass != null)\n\t\t\t\tcmp = Named.STRING_PATH_COMPARATOR.compare(declaringClass.getName(), otherDeclaringClass.getName());\n\t\t\tif (cmp == 0)\n\t\t\t\tcmp = Named.STRING_COMPARATOR.compare(method.getName(), otherMethod.getName());\n\t\t\tif (cmp == 0)\n\t\t\t\tcmp = Named.STRING_COMPARATOR.compare(method.getDescriptor(), otherMethod.getDescriptor());\n\t\t\treturn cmp;\n\t\t}\n\t}\n\n\t/**\n\t * Cell of a class in the hierarchy.\n\t */\n\tclass CallGraphCell extends TreeCell<MethodMember> {\n\t\tprivate EventHandler<MouseEvent> onClickFilter;\n\n\t\tprivate CallGraphCell() {\n\t\t\tgetStyleClass().addAll(\"code-area\", \"transparent-cell\");\n\t\t}\n\n\t\t@Override\n\t\tprotected void updateItem(MethodMember method, boolean empty) {\n\t\t\tsuper.updateItem(method, empty);\n\t\t\tif (empty || method == null) {\n\t\t\t\tsetText(null);\n\t\t\t\tsetGraphic(null);\n\t\t\t\tsetOnMouseClicked(null);\n\t\t\t\tsetContextMenu(null);\n\t\t\t\tif (onClickFilter != null)\n\t\t\t\t\tremoveEventFilter(MouseEvent.MOUSE_PRESSED, onClickFilter);\n\t\t\t\tsetOpacity(1);\n\t\t\t} else {\n\t\t\t\tonClickFilter = null;\n\n\t\t\t\tClassInfo declaringClass = method.getDeclaringClass();\n\t\t\t\tif (declaringClass == null)\n\t\t\t\t\treturn;\n\n\t\t\t\tClassPathNode ownerPath = workspace.findClass(declaringClass.getName());\n\t\t\t\tif (ownerPath == null)\n\t\t\t\t\treturn;\n\n\t\t\t\tClassMemberPathNode methodPath = ownerPath.child(method);\n\n\t\t\t\tString methodOwnerName = declaringClass.getName();\n\t\t\t\tText classText = new Text(format.filter(methodOwnerName, false, true, true));\n\t\t\t\tclassText.setFill(Color.CADETBLUE);\n\n\t\t\t\tText methodText = new Text(method.getName());\n\t\t\t\tif (method.hasStaticModifier()) methodText.setFill(Color.LIGHTGREEN);\n\t\t\t\telse methodText.setFill(Color.YELLOW);\n\n\t\t\t\t// Layout\n\t\t\t\tTextFlow textFlow = new TextFlow(classText, new Label(\"#\"), methodText, new Label(method.getDescriptor()));\n\t\t\t\tHBox box = new HBox(configurationService.graphicOf(methodPath), textFlow);\n\t\t\t\tbox.setSpacing(5);\n\t\t\t\tif (getTreeItem() instanceof CallGraphItem i && i.recursive) {\n\t\t\t\t\tbox.getChildren().add(new FontIconView(CarbonIcons.CODE_REFERENCE));\n\t\t\t\t\tbox.setOpacity(0.4);\n\t\t\t\t}\n\t\t\t\tsetGraphic(box);\n\n\t\t\t\t// Context menu support\n\t\t\t\tContextMenu contextMenu = configurationService.contextMenuOf(ContextSource.REFERENCE, methodPath);\n\t\t\t\tMenuItem focusItem = new MenuItem();\n\t\t\t\tfocusItem.setGraphic(new FontIconView(CarbonIcons.CI_3D_CURSOR_ALT));\n\t\t\t\tfocusItem.textProperty().bind(Lang.getBinding(\"menu.view.methodcallgraph.focus\"));\n\t\t\t\tfocusItem.setOnAction(e -> currentMethod.set(method));\n\t\t\t\tcontextMenu.getItems().add(1, focusItem);\n\t\t\t\tsetContextMenu(contextMenu);\n\n\t\t\t\t// Override the double click behavior to open the class. Doesn't work using the \"setOn...\" methods.\n\t\t\t\tonClickFilter = e -> {\n\t\t\t\t\tif (e.getButton().equals(MouseButton.PRIMARY) && e.getClickCount() >= 2) {\n\t\t\t\t\t\te.consume();\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tactions.gotoDeclaration(ownerPath).requestFocus(method);\n\t\t\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\t\t\tlogger.error(\"Cannot go to method due to incomplete path\", ex);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\taddEventFilter(MouseEvent.MOUSE_PRESSED, onClickFilter);\n\t\t\t}\n\t\t}\n\n\t\tpublic MethodMember getCurrentMethod() {\n\t\t\treturn currentMethod.get();\n\t\t}\n\n\t\tpublic ObjectProperty<MethodMember> currentMethodProperty() {\n\t\t\treturn currentMethod;\n\t\t}\n\t}\n\n\tprivate class CallGraphTreeView extends TreeView<MethodMember> {\n\t\tpublic CallGraphTreeView() {\n\t\t\tgetStyleClass().add(\"transparent-tree\");\n\t\t\tsetCellFactory(param -> new CallGraphCell());\n\t\t}\n\n\t\tpublic void onUpdate() {\n\t\t\tfinal MethodMember methodInfo = currentMethod.get();\n\t\t\tif (methodInfo == null) {\n\t\t\t\tsetRoot(null);\n\t\t\t} else {\n\t\t\t\tCompletableFuture.supplyAsync(() -> {\n\t\t\t\t\twhile (!callGraph.isReady().getValue()) Unchecked.run(() -> Thread.sleep(100));\n\t\t\t\t\treturn buildCallGraph(methodInfo, mode.childrenGetter);\n\t\t\t\t}).thenAcceptAsync(root -> {\n\t\t\t\t\troot.setExpanded(true);\n\t\t\t\t\tsetRoot(root);\n\t\t\t\t}, FxThreadUtil.executor());\n\t\t\t}\n\t\t}\n\n\t\t@Nonnull\n\t\tprivate CallGraphItem buildCallGraph(@Nonnull MethodMember rootMethod, @Nonnull Function<MethodVertex, Collection<MethodVertex>> childrenGetter) {\n\t\t\tArrayDeque<MethodMember> visitedMethods = new ArrayDeque<>();\n\t\t\tArrayDeque<List<CallGraphItem>> workingStack = new ArrayDeque<>();\n\t\t\tCallGraphItem root = new CallGraphItem(rootMethod, false);\n\t\t\tworkingStack.push(new ArrayList<>(Set.of(root)));\n\t\t\tint depth = 0;\n\t\t\twhile (!workingStack.isEmpty()) {\n\t\t\t\tList<CallGraphItem> todo = workingStack.peek();\n\t\t\t\tif (!todo.isEmpty()) {\n\t\t\t\t\tfinal CallGraphItem item = todo.removeLast();\n\t\t\t\t\tif (item.recursive)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tvisitedMethods.push(item.getValue());\n\t\t\t\t\tdepth++;\n\t\t\t\t\tfinal MethodVertex vertex = callGraph.getVertex(item.getValue());\n\t\t\t\t\tif (vertex != null) {\n\t\t\t\t\t\tfinal List<CallGraphItem> newTodo = childrenGetter.apply(vertex).stream()\n\t\t\t\t\t\t\t\t.filter(c -> c.getResolvedMethod() != null)\n\t\t\t\t\t\t\t\t.map(c -> {\n\t\t\t\t\t\t\t\t\tMethodMember cm = c.getResolvedMethod();\n\t\t\t\t\t\t\t\t\treturn new CallGraphItem(cm, visitedMethods.contains(cm));\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.filter(i -> {\n\t\t\t\t\t\t\t\t\tif (i.getValue() == null) return false;\n\t\t\t\t\t\t\t\t\tLists.sortedInsert(Unchecked.cast(item.getChildren()), i);\n\t\t\t\t\t\t\t\t\treturn !i.recursive;\n\t\t\t\t\t\t\t\t}).collect(Collectors.toList());\n\t\t\t\t\t\tif (!newTodo.isEmpty() && depth < MAX_TREE_DEPTH) {\n\t\t\t\t\t\t\tworkingStack.push(newTodo);\n\t\t\t\t\t\t} else visitedMethods.pop();\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tworkingStack.pop();\n\t\t\t\tif (!visitedMethods.isEmpty()) visitedMethods.pop();\n\t\t\t\tdepth--;\n\t\t\t}\n\t\t\treturn root;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/graph/MethodCallGraphsPane.java",
    "content": "package software.coley.recaf.ui.control.graph;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.scene.control.Tab;\nimport javafx.scene.control.TabPane;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.callgraph.CallGraph;\nimport software.coley.recaf.services.callgraph.CallGraphService;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.navigation.ClassNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Objects;\n\n/**\n * Container pane for two {@link MethodCallGraphPane} for inbound and outbound calls.\n *\n * @author Amejonah\n */\n@Dependent\npublic class MethodCallGraphsPane extends TabPane implements ClassNavigable, UpdatableNavigable {\n\tprivate final ObjectProperty<MethodMember> currentMethodInfo;\n\tprivate ClassPathNode path;\n\n\t@Inject\n\tpublic MethodCallGraphsPane(@Nonnull Workspace workspace, @Nonnull CallGraphService callGraphService,\n\t                            @Nonnull TextFormatConfig format, @Nonnull Actions actions,\n\t                            @Nonnull CellConfigurationService configurationService) {\n\t\tcurrentMethodInfo = new SimpleObjectProperty<>();\n\n\t\tCallGraph callGraph = Objects.requireNonNull(callGraphService.getCurrentWorkspaceCallGraph(), \"Graph not created\");\n\t\tgetTabs().add(creatTab(workspace, callGraph, configurationService, format, actions, MethodCallGraphPane.CallGraphMode.CALLS, currentMethodInfo));\n\t\tgetTabs().add(creatTab(workspace, callGraph, configurationService, format, actions, MethodCallGraphPane.CallGraphMode.CALLERS, currentMethodInfo));\n\n\t\t// Remove the standard tab-pane border.\n\t\tgetStyleClass().addAll(\"borderless\");\n\t}\n\n\t@Nonnull\n\tprivate Tab creatTab(@Nonnull Workspace workspace, @Nonnull CallGraph callGraph, @Nonnull CellConfigurationService configurationService,\n\t                     @Nonnull TextFormatConfig format, @Nonnull Actions actions, @Nonnull MethodCallGraphPane.CallGraphMode mode,\n\t                     @Nullable ObjectProperty<MethodMember> methodInfoObservable) {\n\t\tTab tab = new Tab();\n\t\ttab.setContent(new MethodCallGraphPane(workspace, callGraph, configurationService, format, actions, mode, methodInfoObservable));\n\t\ttab.textProperty().bind(Lang.getBinding(\"menu.view.methodcallgraph.\" + mode.name().toLowerCase()));\n\t\ttab.setGraphic(new FontIconView(mode == MethodCallGraphPane.CallGraphMode.CALLS ? CarbonIcons.LOGOUT : CarbonIcons.LOGIN));\n\t\ttab.setClosable(false);\n\t\treturn tab;\n\t}\n\n\t@Nonnull\n\tpublic ObjectProperty<MethodMember> currentMethodInfoProperty() {\n\t\treturn currentMethodInfo;\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof ClassMemberPathNode memberPathNode) {\n\t\t\tthis.path = memberPathNode.getParent();\n\t\t\tClassMember member = memberPathNode.getValue();\n\t\t\tif (member instanceof MethodMember method)\n\t\t\t\tcurrentMethodInfo.setValue(method);\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassPathNode getClassPath() {\n\t\treturn path;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn getClassPath();\n\t}\n\n\t@Override\n\tpublic boolean isTrackable() {\n\t\t// Disabling tracking allows other panels with the same path-node to be opened.\n\t\treturn false;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tgetTabs().clear();\n\t}\n\n\t@Override\n\tpublic void requestFocus(@Nonnull ClassMember member) {\n\t\t// no-op\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/popup/AddMemberPopup.java",
    "content": "package software.coley.recaf.ui.control.popup;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.binding.BooleanBinding;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.value.ChangeListener;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Group;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.CheckBox;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TextField;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.paint.Color;\nimport me.darknet.assembler.util.DescriptorUtil;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.*;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.IconView;\nimport software.coley.recaf.ui.window.RecafScene;\nimport software.coley.recaf.ui.window.RecafStage;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.util.Lang;\n\nimport java.lang.reflect.Modifier;\nimport java.util.Collections;\nimport java.util.function.Consumer;\n\nimport static atlantafx.base.theme.Styles.*;\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.CHECKMARK;\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.CLOSE;\n\n/**\n * Generic popup for adding a new field/method to a class.\n *\n * @author Justus Garbe\n */\npublic class AddMemberPopup extends RecafStage {\n\tprivate final TextField nameInput = new TextField();\n\tprivate final TextField descInput = new TextField();\n\tprivate final CheckBox publicCheck = new CheckBox(\"Public\");\n\tprivate final CheckBox privateCheck = new CheckBox(\"Private\");\n\tprivate final CheckBox protectedCheck = new CheckBox(\"Protected\");\n\tprivate final CheckBox staticCheck = new CheckBox(\"Static\");\n\tprivate final CheckBox finalCheck = new CheckBox(\"Final\");\n\tprivate final Label output = new Label();\n\tprivate final BooleanProperty isIllegalDescriptor = new SimpleBooleanProperty(false);\n\tprivate final BooleanProperty isIllegalName = new SimpleBooleanProperty(false);\n\tprivate final BooleanProperty nameConflict = new SimpleBooleanProperty(false);\n\tprivate boolean isMethod;\n\n\t/**\n\t * New generic member popup.\n\t *\n\t * @param memberConsumer\n\t * \t\tConsumer to add the member outline to a target class.\n\t *\n\t * @see #forField(ClassInfo)\n\t * @see #forMethod(ClassInfo)\n\t */\n\tpublic AddMemberPopup(@Nonnull Consumer<ClassMember> memberConsumer) {\n\t\tButton add = new ActionButton(new FontIconView(CHECKMARK, Color.LAWNGREEN), () -> accept(memberConsumer));\n\t\tButton cancel = new ActionButton(new FontIconView(CLOSE, Color.RED), this::hide);\n\t\tadd.getStyleClass().addAll(BUTTON_ICON, BUTTON_OUTLINED, SUCCESS);\n\t\tcancel.getStyleClass().addAll(BUTTON_ICON, BUTTON_OUTLINED, DANGER);\n\n\t\tadd.disableProperty().bind(isIllegalName.or(isIllegalDescriptor).or(nameConflict));\n\n\t\tpublicCheck.setGraphic(Icons.getIconView(Icons.ACCESS_PUBLIC));\n\t\tprivateCheck.setGraphic(Icons.getIconView(Icons.ACCESS_PRIVATE));\n\t\tprotectedCheck.setGraphic(Icons.getIconView(Icons.ACCESS_PROTECTED));\n\t\tstaticCheck.setGraphic(Icons.getIconView(Icons.ACCESS_STATIC));\n\t\tfinalCheck.setGraphic(Icons.getIconView(Icons.ACCESS_FINAL));\n\n\t\tBooleanBinding shouldShowOutput = isIllegalName.or(isIllegalDescriptor).or(nameConflict);\n\t\toutput.visibleProperty().bind(shouldShowOutput);\n\t\tisIllegalName.addListener((obs, ov, nv) -> {\n\t\t\tif (nv) output.setText(Lang.get(\"dialog.warn.illegal-name\"));\n\t\t});\n\t\tisIllegalDescriptor.addListener((obs, ov, nv) -> {\n\t\t\tif (nv) output.setText(Lang.get(\"dialog.warn.illegal-desc\"));\n\t\t});\n\t\tnameConflict.addListener((obs, ov, nv) -> {\n\t\t\tif (nv)\n\t\t\t\toutput.setText(Lang.get(isMethod ? \"dialog.warn.method-conflict\" : \"dialog.warn.field-conflict\"));\n\t\t});\n\n\t\tisIllegalName.bind(nameInput.textProperty().map(text -> {\n\t\t\tif (text.isBlank()) return true;\n\n\t\t\t// Cannot have ';', '[', '.' or '/' in name\n\t\t\treturn text.indexOf(';') >= 0 || text.indexOf('[') >= 0 ||\n\t\t\t\t\ttext.indexOf('.') >= 0 || text.indexOf('/') >= 0;\n\t\t}));\n\n\t\tnameInput.setPromptText(Lang.get(\"dialog.input.name\"));\n\t\tdescInput.setPromptText(Lang.get(\"dialog.input.desc\"));\n\n\t\tnameInput.setOnKeyPressed(e -> {\n\t\t\tif (e.getCode() == KeyCode.ENTER && !add.isDisable()) {\n\t\t\t \taccept(memberConsumer);\n\t\t\t} else if (e.getCode() == KeyCode.ESCAPE) {\n\t\t\t\thide();\n\t\t\t}\n\t\t});\n\t\tdescInput.setOnKeyPressed(e -> {\n\t\t\tif (e.getCode() == KeyCode.ENTER && !add.isDisable()) {\n\t\t\t\t accept(memberConsumer);\n\t\t\t} else if (e.getCode() == KeyCode.ESCAPE) {\n\t\t\t\thide();\n\t\t\t}\n\t\t});\n\n\t\t// Layout\n\t\tHBox controlButtons = new HBox(add, cancel);\n\t\tcontrolButtons.setSpacing(10);\n\t\tcontrolButtons.setPadding(new Insets(10, 0, 10, 0));\n\t\tcontrolButtons.setAlignment(Pos.CENTER_RIGHT);\n\n\t\t// Add a spacer\n\t\tHBox spacer = new HBox();\n\t\tspacer.setPadding(new Insets(10, 0, 10, 0));\n\t\tspacer.setAlignment(Pos.CENTER_LEFT);\n\n\t\t// Only one accessibility modifier can be selected at a time\n\t\tVBox accessibilityModifiers = new VBox(publicCheck, privateCheck, protectedCheck);\n\t\taccessibilityModifiers.setPadding(new Insets(0, 15, 0, 0));\n\t\tChangeListener<Boolean> singleAccessibilityEnforcement = (ov, old, cur) -> {\n\t\t\tif (cur)\n\t\t\t\taccessibilityModifiers.getChildren().forEach(child -> {\n\t\t\t\t\tif (child instanceof CheckBox check && check.selectedProperty() != ov)\n\t\t\t\t\t\tcheck.setSelected(false);\n\t\t\t\t});\n\t\t};\n\t\tpublicCheck.selectedProperty().addListener(singleAccessibilityEnforcement);\n\t\tprivateCheck.selectedProperty().addListener(singleAccessibilityEnforcement);\n\t\tprotectedCheck.selectedProperty().addListener(singleAccessibilityEnforcement);\n\t\tHBox modifiers = new HBox(accessibilityModifiers, staticCheck, finalCheck);\n\t\tmodifiers.setSpacing(10);\n\t\tmodifiers.setPadding(new Insets(10, 0, 10, 0));\n\t\tmodifiers.setAlignment(Pos.TOP_LEFT);\n\n\t\tHBox inputs = new HBox(nameInput, descInput);\n\t\tinputs.setSpacing(10);\n\t\tinputs.setAlignment(Pos.CENTER_LEFT);\n\n\t\t// Make it so the inputs change size with the window\n\t\tHBox.setHgrow(nameInput, Priority.ALWAYS);\n\t\tHBox.setHgrow(descInput, Priority.ALWAYS);\n\n\t\tVBox layout = new VBox(inputs, spacer, modifiers, controlButtons, new Group(output));\n\t\tlayout.setAlignment(Pos.TOP_CENTER);\n\t\tlayout.setPadding(new Insets(10));\n\t\tsetMinWidth(500);\n\t\tsetMinHeight(230);\n\t\tsetScene(new RecafScene(layout, 500, 230));\n\t}\n\n\tprivate void accept(@Nonnull Consumer<ClassMember> memberConsumer) {\n\t\tint access = 0;\n\t\tif (publicCheck.isSelected()) access |= Modifier.PUBLIC;\n\t\tif (privateCheck.isSelected()) access |= Modifier.PRIVATE;\n\t\tif (protectedCheck.isSelected()) access |= Modifier.PROTECTED;\n\t\tif (staticCheck.isSelected()) access |= Modifier.STATIC;\n\t\tif (finalCheck.isSelected()) access |= Modifier.FINAL;\n\n\t\tString name = nameInput.textProperty().get();\n\t\tString desc = descInput.textProperty().get();\n\t\tClassMember member;\n\t\tif (isMethod) {\n\t\t\tmember = new BasicMethodMember(name, desc, null, access, Collections.emptyList());\n\t\t} else {\n\t\t\tmember = new BasicFieldMember(name, desc, null, access, null);\n\t\t}\n\n\t\tmemberConsumer.accept(member);\n\n\t\thide();\n\t}\n\n\n\t/**\n\t * Configures the popup for adding a method.\n\t *\n\t * @param info\n\t * \t\tClass to add the method to.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic AddMemberPopup forMethod(@Nonnull ClassInfo info) {\n\t\tisMethod = true;\n\n\t\tnameConflict.bind(nameInput.textProperty().map(name -> {\n\t\t\tString desc = descInput.textProperty().get();\n\t\t\tfor (MethodMember method : info.getMethods()) {\n\t\t\t\tif (method.getName().equals(name) && method.getDescriptor().equals(desc)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t}));\n\n\t\tisIllegalDescriptor.bind(descInput.textProperty().map(text -> text.isBlank() ||\n\t\t\t\t!DescriptorUtil.isValidMethodDescriptor(text)));\n\n\t\tsetTitle(Lang.get(\"dialog.title.add-method\"));\n\n\t\tdescInput.setText(\"()V\");\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Configures the popup for adding a field.\n\t *\n\t * @param info\n\t * \t\tClass to add the field to.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic AddMemberPopup forField(@Nonnull ClassInfo info) {\n\t\tisMethod = false;\n\n\t\tnameConflict.bind(nameInput.textProperty().map(name -> {\n\t\t\tfor (FieldMember field : info.getFields()) {\n\t\t\t\tif (field.getName().equals(name)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t}));\n\n\t\tisIllegalDescriptor.bind(descInput.textProperty().map(text -> text.isBlank() ||\n\t\t\t\t!DescriptorUtil.isValidFieldDescriptor(text)));\n\n\t\tsetTitle(Lang.get(\"dialog.title.add-field\"));\n\n\t\tdescInput.setText(\"Ljava/lang/Object;\");\n\n\t\treturn this;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/popup/ChangeClassVersionPopup.java",
    "content": "package software.coley.recaf.ui.control.popup;\n\nimport atlantafx.base.theme.Styles;\nimport com.google.common.util.concurrent.AtomicDouble;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ProgressBar;\nimport javafx.scene.control.Tab;\nimport javafx.scene.control.TabPane;\nimport javafx.scene.layout.GridPane;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.ClassWriter;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.commons.JSRInlinerAdapter;\nimport org.slf4j.Logger;\nimport software.coley.recaf.RecafConstants;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.services.compile.JavacCompiler;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.BoundTab;\nimport software.coley.recaf.ui.window.RecafScene;\nimport software.coley.recaf.ui.window.RecafStage;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.JavaDowngraderUtil;\nimport software.coley.recaf.util.JavaVersion;\nimport software.coley.recaf.util.ToStringConverter;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\n\nimport java.io.IOException;\nimport java.time.Year;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\nimport java.util.function.DoubleConsumer;\n\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.*;\nimport static software.coley.recaf.util.Lang.get;\nimport static software.coley.recaf.util.Lang.getBinding;\n\n/**\n * Popup for initiating the upgrading or downgrading of all classes.\n *\n * @author Matt Coley\n */\npublic class ChangeClassVersionPopup extends RecafStage {\n\tprivate static final Logger logger = Logging.get(ChangeClassVersionPopup.class);\n\tprivate TargetClasses target;\n\n\t/**\n\t * New version change popup.\n\t */\n\tpublic ChangeClassVersionPopup() {\n\t\tTab tabUp = new BoundTab(getBinding(\"menu.edit.changeversion.up\"), ARROW_UP,\n\t\t\t\tcreatePane(\"up\", (version, progressSink) -> transform(true, version, progressSink)));\n\t\tTab tabDown = new BoundTab(getBinding(\"menu.edit.changeversion.down\"), ARROW_DOWN,\n\t\t\t\tcreatePane(\"down\", (version, progressSink) -> transform(false, version, progressSink)));\n\t\tTabPane tabs = new TabPane(tabUp, tabDown);\n\t\ttabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);\n\n\t\tsetMinWidth(450);\n\t\tsetMinHeight(200);\n\t\tsetTitle(get(\"menu.edit.changeversion\"));\n\t\tsetScene(new RecafScene(tabs, 400, 150));\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tBundle to target.\n\t * @param classInfo\n\t * \t\tClass in bundle to target.\n\t */\n\tpublic void setTargetClass(@Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo classInfo) {\n\t\tthis.target = new TargetClasses.JvmClassFile(bundle, classInfo);\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tBundle to target.\n\t * @param packageName\n\t * \t\tPackage in bundle to target.\n\t */\n\tpublic void setTargetPackage(@Nonnull JvmClassBundle bundle, @Nonnull String packageName) {\n\t\tthis.target = new TargetClasses.JvmPackage(bundle, packageName);\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tBundle to target.\n\t */\n\tpublic void setTargetBundle(@Nonnull JvmClassBundle bundle) {\n\t\tthis.target = new TargetClasses.JvmBundle(bundle);\n\t}\n\n\tprivate int transform(boolean upgrade, int targetJavaVersion, @Nonnull DoubleConsumer sink) {\n\t\tMap<String, ClassInfo> transformMap = new HashMap<>();\n\t\ttarget.each(c -> transformMap.put(c.getName(), c));\n\n\t\tint total = transformMap.size();\n\t\tAtomicDouble remaining = new AtomicDouble(total);\n\n\t\tExecutorService service = ThreadPoolFactory.newFixedThreadPool(\"class-version-transform\");\n\t\tif (upgrade) {\n\t\t\ttransformMap.forEach((name, classInfo) -> {\n\t\t\t\tCompletableFuture.supplyAsync(() -> {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (classInfo instanceof JvmClassInfo jvmClass) {\n\t\t\t\t\t\t\t// Convert class file version to Java version.\n\t\t\t\t\t\t\tint classVersion = jvmClass.getVersion() - JavaVersion.VERSION_OFFSET;\n\n\t\t\t\t\t\t\t// Upgrade or downgrade when appropriate.\n\t\t\t\t\t\t\tif (classVersion < targetJavaVersion)\n\t\t\t\t\t\t\t\treturn upgrade(targetJavaVersion, classInfo);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tsink.accept(1 - (remaining.addAndGet(-1) / total));\n\t\t\t\t\t}\n\t\t\t\t}, service).whenComplete((updated, error) -> {\n\t\t\t\t\t// Updated can be null when a class did not fit the upgrade criterion or if there was an error.\n\t\t\t\t\tif (updated != null)\n\t\t\t\t\t\ttarget.update(updated);\n\t\t\t\t});\n\t\t\t});\n\t\t} else {\n\t\t\tMap<String, byte[]> bytesMap = new HashMap<>(transformMap.size());\n\t\t\tMap<String, byte[]> resultsMap = new HashMap<>(transformMap.size());\n\t\t\ttransformMap.forEach((name, classInfo) -> bytesMap.put(name, classInfo.asJvmClass().getBytecode()));\n\t\t\tCompletableFuture.runAsync(() -> {\n\t\t\t\ttry {\n\t\t\t\t\tJavaDowngraderUtil.downgrade(targetJavaVersion, bytesMap, (name, downgradedBytes) -> {\n\t\t\t\t\t\tresultsMap.put(name, downgradedBytes);\n\t\t\t\t\t\tsink.accept(1 - (remaining.addAndGet(-1) / total));\n\t\t\t\t\t});\n\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\tlogger.error(\"Downgrade transformer failed to initialize\", ex);\n\t\t\t\t}\n\t\t\t}, service).whenComplete((_, error) -> {\n\t\t\t\tresultsMap.forEach((name, bytes) -> {\n\t\t\t\t\ttry {\n\t\t\t\t\t\ttarget.update(new JvmClassInfoBuilder(bytes).build());\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tlogger.error(\"Failed updating workspace with downgraded class '{}'\", name, t);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\ttry {\n\t\t\tservice.shutdown();\n\t\t\tservice.awaitTermination(1, TimeUnit.HOURS);\n\t\t} catch (InterruptedException ex) {\n\t\t\tlogger.info(\"Class version transformation interrupted\", ex);\n\t\t}\n\t\treturn total;\n\t}\n\n\t@Nullable\n\tprivate static JvmClassInfo upgrade(int javaVersion, @Nonnull ClassInfo classInfo) {\n\t\tif (classInfo instanceof JvmClassInfo jvmClassInfo) {\n\t\t\ttry {\n\t\t\t\t// Convert the target Java version to the intended class file version.\n\t\t\t\tint targetClassVersion = javaVersion + JavaVersion.VERSION_OFFSET;\n\n\t\t\t\tint baseClassVersion = jvmClassInfo.getVersion();\n\t\t\t\tif (baseClassVersion <= JavaVersion.VERSION_OFFSET + 7 && !classInfo.hasInterfaceModifier()) {\n\t\t\t\t\t// We need to use ASM's JSRInlinerAdapter to ensure JSR/RET instructions are removed from\n\t\t\t\t\t// the updated copy of the class file.\n\t\t\t\t\tClassReader reader = jvmClassInfo.getClassReader();\n\t\t\t\t\tClassWriter writer = new ClassWriter(reader, 0);\n\t\t\t\t\treader.accept(new ClassVisitor(RecafConstants.getAsmVersion(), writer) {\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tpublic void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {\n\t\t\t\t\t\t\tsuper.visit(targetClassVersion, access, name, signature, superName, interfaces);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tpublic MethodVisitor visitMethod(int access, String name, String descriptor,\n\t\t\t\t\t\t                                 String signature, String[] exceptions) {\n\t\t\t\t\t\t\tMethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);\n\t\t\t\t\t\t\treturn new JSRInlinerAdapter(mv, access, name, descriptor, signature, exceptions);\n\t\t\t\t\t\t}\n\t\t\t\t\t}, jvmClassInfo.getClassReaderFlags());\n\t\t\t\t\treturn new JvmClassInfoBuilder(writer.toByteArray()).build();\n\t\t\t\t} else {\n\t\t\t\t\t// No special handling needed, just update the version value.\n\t\t\t\t\tbyte[] original = jvmClassInfo.getBytecode();\n\t\t\t\t\tbyte[] copy = Arrays.copyOf(original, original.length);\n\t\t\t\t\tcopy[7] = (byte) targetClassVersion;\n\t\t\t\t\treturn jvmClassInfo.toJvmClassBuilder()\n\t\t\t\t\t\t\t.withBytecode(copy)\n\t\t\t\t\t\t\t.withVersion(targetClassVersion)\n\t\t\t\t\t\t\t.build();\n\t\t\t\t}\n\t\t\t} catch (Exception ex) {\n\t\t\t\tlogger.error(\"Failed downgrading class '{}'\", classInfo.getName(), ex);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Nonnull\n\tprivate GridPane createPane(@Nonnull String suffix, @Nonnull UpdateHandler versionAction) {\n\t\tGridPane layout = new GridPane(8, 8);\n\t\tLabel labelVersion = new BoundLabel(getBinding(\"java.targetversion\"));\n\t\tVersionComboBox versionCombo = new VersionComboBox();\n\t\tProgressBar progressBar = new ProgressBar(0);\n\t\tButton applyButton = new ActionButton(RUN, getBinding(\"menu.edit.changeversion.\" + suffix), () -> {\n\t\t\tversionCombo.setDisable(true);\n\t\t\tint targetVersion = versionCombo.getValue();\n\t\t\tCompletableFuture.supplyAsync(() -> versionAction.accept(targetVersion, progress -> {\n\t\t\t\t// Update progress bar when the handler reports progress\n\t\t\t\tFxThreadUtil.run(() -> progressBar.setProgress(progress));\n\t\t\t})).whenCompleteAsync((count, error) -> {\n\t\t\t\tif (error == null) {\n\t\t\t\t\t// Done with upgrade/downgrade, close the window\n\t\t\t\t\tlogger.info(\"Changed {} classes to target version {}\", count, targetVersion);\n\t\t\t\t\thide();\n\t\t\t\t} else {\n\t\t\t\t\tlogger.error(\"Failed to change class target version to {}\", targetVersion, error);\n\t\t\t\t}\n\t\t\t}, FxThreadUtil.executor());\n\t\t});\n\t\tapplyButton.disableProperty().bind(versionCombo.disabledProperty());\n\n\t\tversionCombo.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);\n\t\tapplyButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);\n\t\tprogressBar.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);\n\n\t\tlayout.add(labelVersion, 0, 0);\n\t\tlayout.add(versionCombo, 1, 0);\n\n\t\tif (suffix.equals(\"down\")) {\n\t\t\tLabel labelNotice = new BoundLabel(getBinding(\"java.targetversion.notice.\" + suffix));\n\t\t\tlabelNotice.getStyleClass().addAll(Styles.TEXT_SUBTLE, Styles.TEXT_ITALIC);\n\t\t\tlayout.add(labelNotice, 0, layout.getRowCount(), 2, 1);\n\t\t}\n\n\t\tlayout.add(applyButton, 0, layout.getRowCount(), 2, 1);\n\t\tlayout.add(progressBar, 0, layout.getRowCount(), 2, 1);\n\t\tlayout.setPadding(new Insets(10));\n\t\tlayout.setAlignment(Pos.TOP_CENTER);\n\t\treturn layout;\n\t}\n\n\t/**\n\t * Outline of upgrade/downgrade process.\n\t */\n\tprivate interface UpdateHandler {\n\t\t/**\n\t\t * @param version\n\t\t * \t\tVersion to upgrade/downgrade to.\n\t\t * @param progressSink\n\t\t * \t\tSink for progress reporting.\n\t\t *\n\t\t * @return Total number of classes updated.\n\t\t */\n\t\tint accept(int version, @Nonnull DoubleConsumer progressSink);\n\t}\n\n\t/**\n\t * Combo box to hold supported target versions.\n\t */\n\tprivate static class VersionComboBox extends ComboBox<Integer> {\n\t\tprivate VersionComboBox() {\n\t\t\tint max = releaseCycleAvailability();\n\t\t\tfor (int i = JavacCompiler.MIN_DOWNSAMPLE_VER; i <= max; i++)\n\t\t\t\tgetItems().add(i);\n\t\t\tsetValue(8);\n\t\t\tsetConverter(ToStringConverter.from(String::valueOf));\n\t\t}\n\n\t\t/**\n\t\t * @return Expected version of Java to exist for the current year, based on the 2-per-year release cycle.\n\t\t */\n\t\tprivate static int releaseCycleAvailability() {\n\t\t\t// Java is now on a release cycle of 2 versions every year.\n\t\t\t// The year is based on 2020 to accommodate for 'early-access'\n\t\t\tint year = Year.now().getValue();\n\t\t\treturn 17 + (year - 2020) * 2;\n\t\t}\n\t}\n\n\t/**\n\t * Model of what classes to change.\n\t */\n\tprivate sealed interface TargetClasses {\n\t\tvoid each(@Nonnull Consumer<ClassInfo> consumer);\n\n\t\tvoid update(@Nonnull ClassInfo classInfo);\n\n\t\trecord JvmClassFile(@Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo classInfo) implements TargetClasses {\n\n\t\t\t@Override\n\t\t\tpublic void each(@Nonnull Consumer<ClassInfo> consumer) {\n\t\t\t\tconsumer.accept(classInfo);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void update(@Nonnull ClassInfo classInfo) {\n\t\t\t\tbundle.put(classInfo.asJvmClass());\n\t\t\t}\n\t\t}\n\n\t\trecord JvmPackage(@Nonnull JvmClassBundle bundle, @Nullable String packageName) implements TargetClasses {\n\t\t\t@Override\n\t\t\tpublic void each(@Nonnull Consumer<ClassInfo> consumer) {\n\t\t\t\tif (packageName == null || packageName.isEmpty())\n\t\t\t\t\tbundle.stream()\n\t\t\t\t\t\t\t.filter(c -> c.getPackageName() == null)\n\t\t\t\t\t\t\t.forEach(consumer);\n\t\t\t\telse\n\t\t\t\t\tbundle.stream()\n\t\t\t\t\t\t\t.filter(c -> {\n\t\t\t\t\t\t\t\tString itPackageName = c.getPackageName();\n\t\t\t\t\t\t\t\treturn itPackageName != null && itPackageName.startsWith(packageName);\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.forEach(consumer);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void update(@Nonnull ClassInfo classInfo) {\n\t\t\t\tbundle.put(classInfo.asJvmClass());\n\t\t\t}\n\t\t}\n\n\t\trecord JvmBundle(@Nonnull JvmClassBundle bundle) implements TargetClasses {\n\t\t\t@Override\n\t\t\tpublic void each(@Nonnull Consumer<ClassInfo> consumer) {\n\t\t\t\tbundle.forEach(consumer);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void update(@Nonnull ClassInfo classInfo) {\n\t\t\t\tbundle.put(classInfo.asJvmClass());\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/popup/ClassSelectionPopup.java",
    "content": "package software.coley.recaf.ui.control.popup;\n\nimport atlantafx.base.controls.Spacer;\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.paint.Color;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.path.ResourcePathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.ui.config.WorkspaceExplorerConfig;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.PathNodeTree;\nimport software.coley.recaf.ui.control.tree.TreeItems;\nimport software.coley.recaf.ui.control.tree.WorkspaceRootTreeNode;\nimport software.coley.recaf.ui.control.tree.WorkspaceTreeCell;\nimport software.coley.recaf.ui.window.RecafScene;\nimport software.coley.recaf.ui.window.RecafStage;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\n\nimport java.util.function.Consumer;\n\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.CHECKMARK;\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.CLOSE;\n\n/**\n * Popup to show a selection of a class in a workspace.\n *\n * @author Matt Coley\n */\npublic class ClassSelectionPopup extends RecafStage {\n\tprivate final Consumer<ClassPathNode> classPathConsumer;\n\tprivate final PathNodeTree tree;\n\n\t/**\n\t * @param actions\n\t * @param configurationService\n\t * @param explorerConfig\n\t * @param workspace\n\t * @param classPathConsumer\n\t */\n\t@SuppressWarnings(\"all\")\n\tpublic ClassSelectionPopup(@Nonnull Actions actions, @Nonnull CellConfigurationService configurationService,\n\t                           @Nonnull WorkspaceExplorerConfig explorerConfig, @Nonnull Workspace workspace,\n\t                           @Nonnull Consumer<ClassPathNode> classPathConsumer) {\n\t\tthis.classPathConsumer = classPathConsumer;\n\n\t\ttree = new PathSelectionTree(configurationService, actions, classPathConsumer);\n\t\ttree.setShowRoot(false);\n\t\ttree.getStyleClass().add(\"border-muted\");\n\n\t\t// Keyboard accept/cancel\n\t\tObservableValue<Boolean> disable = tree.getSelectionModel().selectedItemProperty().map(i -> !(i.getValue() instanceof ClassPathNode));\n\t\ttree.setOnKeyPressed(e -> {\n\t\t\tif (e.getCode() == KeyCode.ENTER && !disable.getValue()) {\n\t\t\t\taccept();\n\t\t\t} else if (e.getCode() == KeyCode.ESCAPE) {\n\t\t\t\thide();\n\t\t\t}\n\t\t});\n\n\t\t// Setup tree contents\n\t\tWorkspaceRootTreeNode rootItem = new WorkspaceRootTreeNode(explorerConfig, PathNodes.workspacePath(workspace)) {\n\t\t\t@Override\n\t\t\tprotected void visitFiles(@Nonnull ResourcePathNode containingResourcePath, @Nonnull FileBundle bundle) {\n\t\t\t\t// Skip populating files in this tree.\n\t\t\t\treturn;\n\t\t\t}\n\t\t};\n\t\trootItem.build();\n\t\tTreeItems.recurseOpen(rootItem);\n\t\ttree.setRoot(rootItem);\n\n\t\tButton acceptButton = new ActionButton(new FontIconView(CHECKMARK, Color.LAWNGREEN), this::accept);\n\t\tButton cancelButton = new ActionButton(new FontIconView(CLOSE, Color.RED), this::hide);\n\t\tacceptButton.disableProperty().bind(disable);\n\t\tHBox buttons = new HBox(acceptButton, new Spacer(), cancelButton);\n\t\tVBox layout = new VBox(tree, buttons);\n\t\tlayout.setSpacing(10);\n\t\tlayout.setAlignment(Pos.TOP_CENTER);\n\t\tlayout.setPadding(new Insets(10));\n\t\tsetMinWidth(500);\n\t\tsetMinHeight(230);\n\t\tsetMaxHeight(460);\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.select-class\"));\n\t\tsetScene(new RecafScene(layout, 500, 350));\n\t}\n\n\tprivate void accept() {\n\t\tTreeItem<PathNode<?>> selectedItem = tree.getSelectionModel().getSelectedItem();\n\t\tif (selectedItem != null && selectedItem.getValue() instanceof ClassPathNode classPath)\n\t\t\tclassPathConsumer.accept(classPath);\n\t\thide();\n\t}\n\n\t/**\n\t * Path node tree with some behavior tweaks to improve its UX as the primary selection element in this popup.\n\t * Namely, changing the double click handling of cells to call {@link #accept()}.\n\t */\n\tprivate class PathSelectionTree extends PathNodeTree {\n\t\tpublic PathSelectionTree(@Nonnull CellConfigurationService configurationService, @Nonnull Actions actions,\n\t\t                         @Nonnull Consumer<ClassPathNode> classPathConsumer) {\n\t\t\tsuper(configurationService, actions);\n\t\t}\n\n\t\t@Override\n\t\tprotected void handleEnter(@Nonnull Actions actions, @Nonnull TreeItem<PathNode<?>> selected) {\n\t\t\taccept();\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected WorkspaceTreeCell buildCell(@Nonnull CellConfigurationService configurationService) {\n\t\t\treturn new WorkspaceTreeCell(contextSourceObjectPropertyProperty().get(), configurationService) {\n\t\t\t\t@Override\n\t\t\t\tprotected void populate(@Nonnull PathNode<?> path) {\n\t\t\t\t\t// We want to specify the text/graphic of the cell, but not some of the click behaviors.\n\t\t\t\t\t// For instance, we don't want context menus or double-click to open classes.\n\t\t\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\t\t\tconfigurationService.configureStyle(this, path);\n\t\t\t\t\t\tsetText(configurationService.textOf(path));\n\t\t\t\t\t\tsetGraphic(configurationService.graphicOf(path));\n\t\t\t\t\t\tsetOnMouseClicked(e -> {\n\t\t\t\t\t\t\t// We want tree expand/collapse handling, but not \"goto-declaration\" handling.\n\t\t\t\t\t\t\tconfigurationService.clickHandlerOf(this, path, false).handle(e);\n\n\t\t\t\t\t\t\t// Double click should fire our class path consumer.\n\t\t\t\t\t\t\tif (e.getClickCount() == 2\n\t\t\t\t\t\t\t\t\t&& e.getButton() == MouseButton.PRIMARY\n\t\t\t\t\t\t\t\t\t&& path instanceof ClassPathNode classPath)\n\t\t\t\t\t\t\t\taccept();\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/popup/DecompileAllPopup.java",
    "content": "package software.coley.recaf.ui.control.popup;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ProgressBar;\nimport javafx.scene.layout.GridPane;\nimport javafx.stage.FileChooser;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.decompile.DecompilerManager;\nimport software.coley.recaf.services.decompile.JvmDecompiler;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.config.RecentFilesConfig;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.ObservableComboBox;\nimport software.coley.recaf.ui.pane.editing.jvm.DecompilerPaneConfig;\nimport software.coley.recaf.ui.window.RecafScene;\nimport software.coley.recaf.ui.window.RecafStage;\nimport software.coley.recaf.util.FileChooserBuilder;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.ZipCreationUtils;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Predicate;\n\n/**\n * Popup for initiating decompilation of all classes, saved to a specified location.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class DecompileAllPopup extends RecafStage {\n\tprivate static final Logger logger = Logging.get(DecompileAllPopup.class);\n\tprivate final ObjectProperty<Path> pathProperty = new SimpleObjectProperty<>();\n\tprivate final ObservableObject<JvmDecompiler> decompilerProperty;\n\tprivate final BooleanProperty inProgressProperty = new SimpleBooleanProperty();\n\tprivate Predicate<String> namePredicate = name -> true;\n\tprivate JvmClassBundle targetBundle;\n\n\t@Inject\n\tpublic DecompileAllPopup(@Nonnull DecompilerManager decompilerManager,\n\t                         @Nonnull RecentFilesConfig recentFilesConfig,\n\t                         @Nonnull DecompilerPaneConfig decompilerPaneConfig,\n\t                         @Nonnull WorkspaceManager workspaceManager) {\n\t\tWorkspace workspace = workspaceManager.getCurrent();\n\t\tString defaultName = buildName(workspace);\n\n\t\ttargetBundle = workspace.getPrimaryResource().getJvmClassBundle();\n\t\tdecompilerProperty = new ObservableObject<>(decompilerManager.getTargetJvmDecompiler());\n\t\tpathProperty.setValue(Paths.get(recentFilesConfig.getLastWorkspaceExportDirectory().getValue()).resolve(defaultName));\n\n\t\tLabel decompilerLabel = new BoundLabel(Lang.getBinding(\"java.decompiler\"));\n\t\tLabel pathLabel = new BoundLabel(Lang.getBinding(\"menu.file.decompileall.path\"));\n\t\tObservableComboBox<JvmDecompiler> decompilerCombo = new ObservableComboBox<>(decompilerProperty, decompilerManager.getJvmDecompilers());\n\t\tProgressBar progress = new ProgressBar(0);\n\t\tButton pathButton = new ActionButton(CarbonIcons.EDIT, pathProperty.map(Path::toString), () -> {\n\t\t\tFileChooser chooser = new FileChooserBuilder()\n\t\t\t\t\t.setInitialFileName(defaultName)\n\t\t\t\t\t.setInitialDirectory(recentFilesConfig.getLastWorkspaceExportDirectory())\n\t\t\t\t\t.setFileExtensionFilter(\"Archives\", \"*.zip\", \"*.jar\")\n\t\t\t\t\t.setTitle(Lang.get(\"dialog.file.open\"))\n\t\t\t\t\t.build();\n\t\t\tFile file = chooser.showSaveDialog(getScene().getWindow());\n\t\t\tif (file != null) {\n\t\t\t\tString parent = file.getParent();\n\t\t\t\tif (parent != null) recentFilesConfig.getLastWorkspaceOpenDirectory().setValue(parent);\n\t\t\t\tpathProperty.set(file.toPath());\n\t\t\t}\n\t\t});\n\t\tButton decompileButton = new ActionButton(CarbonIcons.SAVE_SERIES, Lang.getBinding(\"menu.file.decompileall\"), () -> {\n\t\t\ttry {\n\t\t\t\tinProgressProperty.setValue(true);\n\t\t\t\tprogress.setProgress(0);\n\n\t\t\t\t// Determine which classes to decompile\n\t\t\t\tList<JvmClassInfo> targetClasses = targetBundle.stream().filter(cls -> {\n\t\t\t\t\t// Skip inner classes\n\t\t\t\t\tif (cls.isInnerClass())\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\t// Skip special case classes like 'module-info' and 'package-info'\n\t\t\t\t\tString name = cls.getName();\n\t\t\t\t\tif (cls.getSuperName() == null || name.equals(\"module-info\") || name.endsWith(\"package-info\"))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\t// Pass to name predicate for final say\n\t\t\t\t\treturn namePredicate.test(name);\n\t\t\t\t}).toList();\n\n\t\t\t\t// Determine delta of each decompilation\n\t\t\t\tint targetCount = targetClasses.size();\n\t\t\t\tAtomicInteger actionedClasses = new AtomicInteger(targetCount);\n\n\t\t\t\t// Decompile all classes\n\t\t\t\tJvmDecompiler decompiler = decompilerProperty.getValue();\n\t\t\t\tZipCreationUtils.ZipBuilder builder = ZipCreationUtils.builder();\n\t\t\t\ttargetClasses.forEach(cls -> {\n\t\t\t\t\tString name = cls.getName();\n\t\t\t\t\tdecompilerManager.decompile(decompiler, workspace, cls)\n\t\t\t\t\t\t\t.orTimeout(decompilerPaneConfig.getTimeoutSeconds().getValue(), TimeUnit.SECONDS)\n\t\t\t\t\t\t\t.whenComplete((result, error) -> {\n\t\t\t\t\t\t\t\tint remaining = actionedClasses.decrementAndGet();\n\t\t\t\t\t\t\t\tif (result != null) {\n\t\t\t\t\t\t\t\t\t// Handle errors\n\t\t\t\t\t\t\t\t\tif (result.getException() != null) {\n\t\t\t\t\t\t\t\t\t\tlogger.error(\"Failed to decompile '{}'\", name, result.getException());\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Write decompilation output\n\t\t\t\t\t\t\t\t\tString text = result.getText();\n\t\t\t\t\t\t\t\t\tif (text != null)\n\t\t\t\t\t\t\t\t\t\tbuilder.add(name + \".java\", text.getBytes(StandardCharsets.UTF_8));\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tlogger.error(\"Failed to decompile '{}'\", name, error);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// If done, write the zip file\n\t\t\t\t\t\t\t\tif (remaining <= 0) {\n\t\t\t\t\t\t\t\t\tinProgressProperty.setValue(false);\n\t\t\t\t\t\t\t\t\tPath path = pathProperty.get();\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tFiles.write(path, builder.bytes());\n\t\t\t\t\t\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\t\t\t\t\t\tlogger.error(\"Failed to write archive of decompiled classes to '{}'\", path, ex);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}).thenRunAsync(() -> progress.setProgress(1 - (actionedClasses.doubleValue() / targetCount)), FxThreadUtil.executor());\n\t\t\t\t});\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Failed to schedule all classes for decompilation\", t);\n\t\t\t\tinProgressProperty.setValue(false);\n\t\t\t}\n\t\t});\n\t\tdecompileButton.disableProperty().bind(pathProperty.isNull().or(inProgressProperty));\n\n\t\t// Hide when done\n\t\tprogress.progressProperty().addListener((ob, old, cur) -> {\n\t\t\tif (cur.doubleValue() >= 1)\n\t\t\t\thide();\n\t\t});\n\n\t\t// Layout\n\t\tGridPane layout = new GridPane(8, 8);\n\t\tGridPane.setFillWidth(progress, true);\n\t\tGridPane.setFillWidth(decompilerCombo, true);\n\t\tGridPane.setFillWidth(decompileButton, true);\n\t\tGridPane.setFillWidth(pathButton, true);\n\t\tpathButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);\n\t\tdecompileButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);\n\t\tdecompilerCombo.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);\n\t\tprogress.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);\n\t\tdecompilerLabel.setMinWidth(85); // Ensure the labels do not get squished when a large path is selected\n\t\tlayout.add(decompilerLabel, 0, 0);\n\t\tlayout.add(decompilerCombo, 1, 0);\n\t\tlayout.add(pathLabel, 0, 1);\n\t\tlayout.add(pathButton, 1, 1);\n\t\tlayout.add(decompileButton, 1, 2);\n\t\tlayout.add(progress, 0, 3, 2, 1);\n\t\tlayout.setPadding(new Insets(10));\n\t\tlayout.setAlignment(Pos.TOP_CENTER);\n\n\t\tsetMinWidth(450);\n\t\tsetMinHeight(200);\n\t\tsetTitle(Lang.get(\"menu.file.decompileall\"));\n\t\tsetScene(new RecafScene(layout, 400, 150));\n\t}\n\n\t@Nonnull\n\tprivate static String buildName(@Nonnull Workspace workspace) {\n\t\tString prefix = \"\";\n\t\tif (workspace.getPrimaryResource() instanceof WorkspaceFileResource fileResource)\n\t\t\tprefix = StringUtil.removeExtension(StringUtil.shortenPath(fileResource.getFileInfo().getName())) + \"-\";\n\t\treturn prefix + \"decompiled.zip\";\n\t}\n\n\t/**\n\t * @param targetBundle\n\t * \t\tBundle to target for decompilation.\n\t */\n\tpublic void setTargetBundle(@Nonnull JvmClassBundle targetBundle) {\n\t\tthis.targetBundle = targetBundle;\n\t}\n\n\t/**\n\t * @param namePredicate\n\t * \t\tName predicate for whitelisting names.\n\t */\n\tpublic void setNamePredicate(@Nonnull Predicate<String> namePredicate) {\n\t\tthis.namePredicate = namePredicate;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/popup/ItemListSelectionPopup.java",
    "content": "package software.coley.recaf.ui.control.popup;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.value.ObservableValue;\nimport javafx.collections.FXCollections;\nimport javafx.scene.Node;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.ListView;\nimport javafx.scene.control.SelectionMode;\nimport javafx.scene.input.KeyCode;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.annotation.Annotated;\nimport software.coley.recaf.info.annotation.AnnotationInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.function.Consumer;\n\n/**\n * Generic list item selection handling popup.\n *\n * @author Matt Coley\n */\n@SuppressWarnings(\"unchecked\")\npublic class ItemListSelectionPopup<T> extends SelectionPopup<T> {\n\tprivate final ListView<T> list = new ListView<>();\n\n\t/**\n\t * @param items\n\t * \t\tItems to show.\n\t * @param consumer\n\t * \t\tConsumer to run when user accepts selected items.\n\t */\n\tpublic ItemListSelectionPopup(@Nonnull Collection<T> items, @Nonnull Consumer<List<T>> consumer) {\n\t\tsetup(consumer);\n\n\t\t// Handle user accepting input\n\t\tlist.setOnKeyPressed(e -> {\n\t\t\tif (e.getCode() == KeyCode.ENTER) {\n\t\t\t\taccept(consumer);\n\t\t\t} else if (e.getCode() == KeyCode.ESCAPE) {\n\t\t\t\thide();\n\t\t\t}\n\t\t});\n\t\tlist.setOnMousePressed(e -> {\n\t\t\tif (e.getClickCount() >= 2) {\n\t\t\t\taccept(consumer);\n\t\t\t}\n\t\t});\n\t\tlist.setItems(FXCollections.observableArrayList(items));\n\t\tlist.setCellFactory(param -> new ListCell<>() {\n\t\t\t@Override\n\t\t\tprotected void updateItem(T item, boolean empty) {\n\t\t\t\tsuper.updateItem(item, empty);\n\t\t\t\tif (empty || item == null) {\n\t\t\t\t\tsetText(null);\n\t\t\t\t\tsetGraphic(null);\n\t\t\t\t} else {\n\t\t\t\t\tif (textMapper != null) setText(textMapper.apply(item));\n\t\t\t\t\tif (graphicMapper != null) setGraphic(graphicMapper.apply(item));\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// Initial selection if there is only one item.\n\t\t// Allows the user to jump straight to accept/cancel buttons.\n\t\tif (items.size() == 1) list.getSelectionModel().selectFirst();\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected Node getSelectionComponent() {\n\t\treturn list;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected List<T> adaptCurrentSelection() {\n\t\treturn list.getSelectionModel().getSelectedItems();\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObservableValue<Boolean> isNullSelection() {\n\t\treturn list.getSelectionModel().selectedItemProperty().isNull();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ItemListSelectionPopup<T> withMultipleSelection() {\n\t\tlist.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param cls\n\t * \t\tClass to pull fields from.\n\t * @param fieldConsumer\n\t * \t\tAction to run on accepted fields.\n\t *\n\t * @return Field selection popup.\n\t */\n\t@Nonnull\n\tpublic static ItemListSelectionPopup<FieldMember> forFields(@Nonnull ClassInfo cls,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@Nonnull Consumer<List<FieldMember>> fieldConsumer) {\n\t\treturn new ItemListSelectionPopup<>(cls.getFields(), fieldConsumer);\n\t}\n\n\t/**\n\t * @param cls\n\t * \t\tClass to pull methods from.\n\t * @param methodConsumer\n\t * \t\tAction to run on accepted methods.\n\t *\n\t * @return Method selection popup.\n\t */\n\t@Nonnull\n\tpublic static ItemListSelectionPopup<MethodMember> forMethods(@Nonnull ClassInfo cls,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull Consumer<List<MethodMember>> methodConsumer) {\n\t\treturn new ItemListSelectionPopup<>(cls.getMethods(), methodConsumer);\n\t}\n\n\t/**\n\t * @param annotated\n\t * \t\tAnnotated item to pull annotations from.\n\t * @param annotationConsumer\n\t * \t\tAction to run on accepted annotations.\n\t *\n\t * @return Annotation selection popup.\n\t */\n\t@Nonnull\n\tpublic static ItemListSelectionPopup<AnnotationInfo> forAnnotationRemoval(@Nonnull Annotated annotated,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  @Nonnull Consumer<List<AnnotationInfo>> annotationConsumer) {\n\t\treturn new ItemListSelectionPopup<>(annotated.getAnnotations(), annotationConsumer);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/popup/ItemTreeSelectionPopup.java",
    "content": "package software.coley.recaf.ui.control.popup;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.Node;\nimport javafx.scene.control.SelectionMode;\nimport javafx.scene.control.TreeCell;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\nimport javafx.scene.input.KeyCode;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\n\nimport java.util.List;\nimport java.util.Set;\nimport java.util.TreeSet;\nimport java.util.function.Consumer;\n\n/**\n * Generic tree item selection handling popup.\n *\n * @author Matt Coley\n */\n@SuppressWarnings(\"unchecked\")\npublic class ItemTreeSelectionPopup<T> extends SelectionPopup<T> {\n\tprivate final TreeView<T> tree = new TreeView<>();\n\n\t/**\n\t * @param consumer\n\t * \t\tConsumer to run when user accepts selected items.\n\t */\n\tpublic ItemTreeSelectionPopup(@Nonnull Consumer<List<T>> consumer) {\n\t\tsetup(consumer);\n\n\t\t// Handle user accepting input\n\t\ttree.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);\n\t\ttree.setOnKeyPressed(e -> {\n\t\t\tif (e.getCode() == KeyCode.ENTER) {\n\t\t\t\taccept(consumer);\n\t\t\t} else if (e.getCode() == KeyCode.ESCAPE) {\n\t\t\t\thide();\n\t\t\t}\n\t\t});\n\t\ttree.setCellFactory(param -> new TreeCell<>() {\n\t\t\t@Override\n\t\t\tprotected void updateItem(T item, boolean empty) {\n\t\t\t\tsuper.updateItem(item, empty);\n\t\t\t\tif (empty || item == null) {\n\t\t\t\t\tsetText(null);\n\t\t\t\t\tsetGraphic(null);\n\t\t\t\t} else {\n\t\t\t\t\tif (textMapper != null) setText(textMapper.apply(item));\n\t\t\t\t\tif (graphicMapper != null) setGraphic(graphicMapper.apply(item));\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected Node getSelectionComponent() {\n\t\treturn tree;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected List<T> adaptCurrentSelection() {\n\t\treturn tree.getSelectionModel().getSelectedItems().stream()\n\t\t\t\t.map(TreeItem::getValue)\n\t\t\t\t.toList();\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected ObservableValue<Boolean> isNullSelection() {\n\t\treturn tree.getSelectionModel().selectedItemProperty().isNull();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ItemTreeSelectionPopup<T> withMultipleSelection() {\n\t\ttree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tTarget bundle to pull packages from.\n\t * @param packageConsumer\n\t * \t\tAction to run on accepted packages.\n\t *\n\t * @return Popup for package names.\n\t */\n\t@Nonnull\n\tpublic static ItemTreeSelectionPopup<String> forPackageNames(@Nonnull ClassBundle<?> bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t @Nonnull Consumer<List<String>> packageConsumer) {\n\t\tSet<String> packages = new TreeSet<>();\n\t\tpackages.add(\"\"); // Empty package\n\t\tfor (String className : bundle.keySet()) {\n\t\t\tint slash = className.lastIndexOf('/');\n\t\t\tif (slash > 0) packages.add(className.substring(0, slash));\n\t\t}\n\t\tItemTreeSelectionPopup<String> popup = new ItemTreeSelectionPopup<>(packageConsumer);\n\t\tbuildTreeOfStringPaths(popup, packages);\n\t\treturn popup;\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tTarget bundle to pull directories from.\n\t * @param directoryConsumer\n\t * \t\tAction to run on accepted directories.\n\t *\n\t * @return Popup for directory names.\n\t */\n\t@Nonnull\n\tpublic static ItemTreeSelectionPopup<String> forDirectoryNames(@Nonnull FileBundle bundle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   @Nonnull Consumer<List<String>> directoryConsumer) {\n\t\tSet<String> directories = new TreeSet<>();\n\t\tdirectories.add(\"\"); // Empty directory\n\t\tfor (String fileName : bundle.keySet()) {\n\t\t\tint slash = fileName.lastIndexOf('/');\n\t\t\tif (slash > 0) directories.add(fileName.substring(0, slash));\n\t\t}\n\t\tItemTreeSelectionPopup<String> popup = new ItemTreeSelectionPopup<>(directoryConsumer);\n\t\tbuildTreeOfStringPaths(popup, directories);\n\t\treturn popup;\n\t}\n\n\t/**\n\t * Creates a tree model following a directory structure.\n\t *\n\t * @param popup\n\t * \t\tTree selection popup to build tree within.\n\t * @param paths\n\t * \t\tPaths making the tree, split by {@code '/'}.\n\t */\n\tprivate static void buildTreeOfStringPaths(@Nonnull ItemTreeSelectionPopup<String> popup, @Nonnull Set<String> paths) {\n\t\tTreeItem<String> rootItem = new TreeItem<>();\n\t\tfor (String pathName : paths) {\n\t\t\tTreeItem<String> path = rootItem;\n\t\t\tString[] parts = pathName.split(\"/\", -1);\n\t\t\tStringBuilder pathBuilder = new StringBuilder();\n\t\t\tfor (String part : parts) {\n\t\t\t\tpathBuilder.append(part).append('/');\n\t\t\t\tString currentPath = pathBuilder.substring(0, pathBuilder.length() - 1);\n\t\t\t\tpath = getOrCreateChild(path, currentPath);\n\t\t\t}\n\t\t}\n\t\tpopup.tree.setShowRoot(false);\n\t\tpopup.tree.setRoot(rootItem);\n\t}\n\n\t/**\n\t * @param parent\n\t * \t\tParent to look in, or place child into.\n\t * @param value\n\t * \t\tValue to look for in an associated child.\n\t * @param <T>\n\t * \t\tValue type.\n\t *\n\t * @return Tree item, a child of the parent, with the given value.\n\t */\n\t@Nonnull\n\tprivate static <T> TreeItem<T> getOrCreateChild(@Nonnull TreeItem<T> parent, @Nonnull T value) {\n\t\tfor (TreeItem<T> child : parent.getChildren())\n\t\t\tif (value.equals(child.getValue()))\n\t\t\t\treturn child;\n\t\tTreeItem<T> child = new TreeItem<>(value);\n\t\tparent.getChildren().add(child);\n\t\treturn child;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/popup/NamePopup.java",
    "content": "package software.coley.recaf.ui.control.popup;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.binding.BooleanBinding;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Group;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TextField;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.paint.Color;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.window.RecafScene;\nimport software.coley.recaf.ui.window.RecafStage;\nimport software.coley.recaf.util.EscapeUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\n\nimport java.awt.*;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.Consumer;\n\nimport static atlantafx.base.theme.Styles.*;\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.CHECKMARK;\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.CLOSE;\n\n/**\n * Generic name input popup.\n *\n * @author Matt Coley\n */\npublic class NamePopup extends RecafStage {\n\tprivate final BooleanProperty nameConflict = new SimpleBooleanProperty(false);\n\tprivate final BooleanProperty isIllegalValue = new SimpleBooleanProperty(false);\n\tprivate final Label output = new Label();\n\tprivate final TextField nameInput = new TextField();\n\tprivate final Button accept;\n\tprivate String initialText;\n\n\t/**\n\t * @param nameConsumer\n\t * \t\tConsumer to handle accepted inputs.\n\t */\n\tpublic NamePopup(@Nonnull Consumer<String> nameConsumer) {\n\t\t// Handle user accepting input\n\t\tisIllegalValue.bind(nameInput.textProperty().map(text -> {\n\t\t\t// Cannot be blank/empty\n\t\t\tif (text.isBlank()) return true;\n\n\t\t\t// Cannot end with a slash\n\t\t\tchar last = text.charAt(text.length() - 1);\n\t\t\treturn last == '/';\n\t\t}));\n\t\tnameInput.setOnKeyPressed(e -> {\n\t\t\tif (e.getCode() == KeyCode.ENTER) {\n\t\t\t\taccept(nameConsumer);\n\t\t\t} else if (e.getCode() == KeyCode.ESCAPE) {\n\t\t\t\thide();\n\t\t\t}\n\t\t});\n\t\taccept = new ActionButton(new FontIconView(CHECKMARK, Color.LAWNGREEN), () -> accept(nameConsumer));\n\t\tButton cancel = new ActionButton(new FontIconView(CLOSE, Color.RED), this::hide);\n\t\taccept.getStyleClass().addAll(BUTTON_ICON, BUTTON_OUTLINED, SUCCESS);\n\t\tcancel.getStyleClass().addAll(BUTTON_ICON, BUTTON_OUTLINED, DANGER);\n\n\t\t// Layout\n\t\tHBox buttons = new HBox(accept, cancel);\n\t\tbuttons.setSpacing(10);\n\t\tbuttons.setPadding(new Insets(10, 0, 10, 0));\n\t\tbuttons.setAlignment(Pos.CENTER_RIGHT);\n\t\tVBox layout = new VBox(nameInput, buttons, new Group(output));\n\t\tlayout.setAlignment(Pos.TOP_CENTER);\n\t\tlayout.setPadding(new Insets(10));\n\t\tsetScene(new RecafScene(layout, 400, 150));\n\t}\n\n\t/**\n\t * @param nameConsumer\n\t * \t\tAction to run on accept.\n\t */\n\tprivate void accept(@Nonnull Consumer<String> nameConsumer) {\n\t\t// Do nothing if conflict detected\n\t\tif (nameConflict.get() || isIllegalValue.get()) {\n\t\t\tToolkit.getDefaultToolkit().beep();\n\t\t\treturn;\n\t\t}\n\n\t\tString text = EscapeUtil.unescapeStandard(nameInput.getText());\n\t\tCompletableFuture.runAsync(() -> nameConsumer.accept(text));\n\t\thide();\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tTarget bundle the package will reside in.\n\t * \t\tUsed to check for name overlap.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic NamePopup forPackageCopy(@Nonnull Bundle<?> bundle) {\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.copy-package\"));\n\t\toutput.textProperty().bind(Lang.getBinding(\"dialog.header.rename-package-error\"));\n\n\t\t// Bind conflict property\n\t\tnameConflict.bind(nameInput.textProperty().map(dirName -> bundleHasDirectory(bundle, dirName)));\n\t\tBooleanBinding notContainingPackageOrEqualToInitial = nameConflict.or(nameInput.textProperty().isEqualTo(initialText));\n\t\taccept.disableProperty().bind(isIllegalValue.or(notContainingPackageOrEqualToInitial));\n\t\toutput.visibleProperty().bind(notContainingPackageOrEqualToInitial);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tTarget bundle the package will reside in.\n\t * \t\tUsed to check for name overlap.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic NamePopup forPackageRename(@Nonnull Bundle<?> bundle) {\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.rename-package\"));\n\t\toutput.textProperty().bind(Lang.getBinding(\"dialog.header.rename-package-error\"));\n\n\t\t// Bind conflict property\n\t\tnameConflict.bind(nameInput.textProperty().map(dirName -> bundleHasDirectory(bundle, dirName)));\n\t\tBooleanBinding notContainingPackageOrEqualToInitial = nameConflict.or(nameInput.textProperty().isEqualTo(initialText));\n\t\taccept.disableProperty().bind(isIllegalValue.or(notContainingPackageOrEqualToInitial));\n\t\toutput.visibleProperty().bind(notContainingPackageOrEqualToInitial);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tTarget bundle the class will reside in.\n\t * \t\tUsed to check for name overlap.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic NamePopup forClassCreation(@Nonnull ClassBundle<?> bundle) {\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.create-class\"));\n\t\toutput.textProperty().bind(Lang.getBinding(\"dialog.header.create-class-error\"));\n\n\t\t// Bind conflict property\n\t\tnameConflict.bind(nameInput.textProperty().map(bundle::containsKey));\n\t\taccept.disableProperty().bind(isIllegalValue.or(nameConflict));\n\t\toutput.visibleProperty().bind(nameConflict);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tTarget bundle the class will reside in.\n\t * \t\tUsed to check for name overlap.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic NamePopup forClassCopy(@Nonnull ClassBundle<?> bundle) {\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.copy-class\"));\n\t\toutput.textProperty().bind(Lang.getBinding(\"dialog.header.rename-class-error\"));\n\n\t\t// Bind conflict property\n\t\tnameConflict.bind(nameInput.textProperty().map(bundle::containsKey));\n\t\taccept.disableProperty().bind(isIllegalValue.or(nameConflict));\n\t\toutput.visibleProperty().bind(nameConflict);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tTarget bundle the class will reside in.\n\t * \t\tUsed to check for name overlap.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic NamePopup forClassRename(@Nonnull ClassBundle<?> bundle) {\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.rename-class\"));\n\t\toutput.textProperty().bind(Lang.getBinding(\"dialog.header.rename-class-error\"));\n\n\t\t// Bind conflict property\n\t\tnameConflict.bind(nameInput.textProperty().map(bundle::containsKey));\n\t\taccept.disableProperty().bind(isIllegalValue.or(nameConflict));\n\t\toutput.visibleProperty().bind(nameConflict);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tTarget bundle the file will reside in.\n\t * \t\tUsed to check for name overlap.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic NamePopup forFileRename(@Nonnull FileBundle bundle) {\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.rename-file\"));\n\t\toutput.textProperty().bind(Lang.getBinding(\"dialog.header.rename-file-error\"));\n\n\t\t// Bind conflict property\n\t\tnameConflict.bind(nameInput.textProperty().map(bundle::containsKey));\n\t\taccept.disableProperty().bind(isIllegalValue.or(nameConflict));\n\t\toutput.visibleProperty().bind(nameConflict);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param declaringClass\n\t * \t\tClass the field is declared in.\n\t * \t\tUsed to check for name overlap.\n\t * @param member\n\t * \t\tCurrent field info.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic NamePopup forFieldRename(@Nonnull ClassInfo declaringClass, @Nonnull ClassMember member) {\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.rename-field\"));\n\t\toutput.textProperty().bind(Lang.getBinding(\"dialog.header.rename-field-error\"));\n\n\t\t// Bind conflict property\n\t\tString descriptor = member.getDescriptor();\n\t\tnameConflict.bind(nameInput.textProperty().map(name -> declaringClass.getDeclaredField(name, descriptor) != null));\n\t\taccept.disableProperty().bind(isIllegalValue.or(nameConflict));\n\t\toutput.visibleProperty().bind(nameConflict);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param declaringClass\n\t * \t\tClass the field is declared in.\n\t * \t\tUsed to check for name overlap.\n\t * @param member\n\t * \t\tCurrent field info.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic NamePopup forFieldCopy(@Nonnull ClassInfo declaringClass, @Nonnull ClassMember member) {\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.copy-field\"));\n\t\toutput.textProperty().bind(Lang.getBinding(\"dialog.header.copy-field-error\"));\n\n\t\t// Bind conflict property\n\t\tString descriptor = member.getDescriptor();\n\t\tnameConflict.bind(nameInput.textProperty().map(name -> declaringClass.getDeclaredField(name, descriptor) != null));\n\t\taccept.disableProperty().bind(isIllegalValue.or(nameConflict));\n\t\toutput.visibleProperty().bind(nameConflict);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param declaringClass\n\t * \t\tClass the method is declared in.\n\t * \t\tUsed to check for name overlap.\n\t * @param member\n\t * \t\tCurrent method info.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic NamePopup forMethodRename(@Nonnull ClassInfo declaringClass, @Nonnull ClassMember member) {\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.rename-method\"));\n\t\toutput.textProperty().bind(Lang.getBinding(\"dialog.header.rename-method-error\"));\n\n\t\t// Bind conflict property\n\t\tString descriptor = member.getDescriptor();\n\t\tnameConflict.bind(nameInput.textProperty().map(name -> declaringClass.getDeclaredMethod(name, descriptor) != null));\n\t\taccept.disableProperty().bind(isIllegalValue.or(nameConflict));\n\t\toutput.visibleProperty().bind(nameConflict);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param declaringClass\n\t * \t\tClass the method is declared in.\n\t * \t\tUsed to check for name overlap.\n\t * @param member\n\t * \t\tCurrent method info.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic NamePopup forMethodCopy(@Nonnull ClassInfo declaringClass, @Nonnull ClassMember member) {\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.copy-method\"));\n\t\toutput.textProperty().bind(Lang.getBinding(\"dialog.header.copy-method-error\"));\n\n\t\t// Bind conflict property\n\t\tString descriptor = member.getDescriptor();\n\t\tnameConflict.bind(nameInput.textProperty().map(name -> declaringClass.getDeclaredMethod(name, descriptor) != null));\n\t\taccept.disableProperty().bind(isIllegalValue.or(nameConflict));\n\t\toutput.visibleProperty().bind(nameConflict);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tTarget bundle the directory will reside in.\n\t * \t\tUsed to check for name overlap.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic NamePopup forDirectoryRename(@Nonnull Bundle<?> bundle) {\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.rename-directory\"));\n\t\toutput.textProperty().bind(Lang.getBinding(\"dialog.header.rename-directory-warning\"));\n\n\t\t// Bind conflict property, but only as a warning since merging is allowed.\n\t\tnameConflict.bind(nameInput.textProperty().map(dirName -> bundleHasDirectory(bundle, dirName)));\n\t\taccept.disableProperty().bind(isIllegalValue);\n\t\toutput.visibleProperty().bind(nameConflict);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param bundle\n\t * \t\tTarget bundle the directory will reside in.\n\t * \t\tUsed to check for name overlap.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic NamePopup forDirectoryCopy(@Nonnull Bundle<?> bundle) {\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.copy-directory\"));\n\t\toutput.textProperty().bind(Lang.getBinding(\"dialog.header.rename-directory-warning\"));\n\n\t\t// Bind conflict property, but only as a warning since merging is allowed.\n\t\tnameConflict.bind(nameInput.textProperty().map(dirName -> bundleHasDirectory(bundle, dirName)));\n\t\taccept.disableProperty().bind(isIllegalValue);\n\t\toutput.visibleProperty().bind(nameConflict);\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param binding\n\t * \t\tTitle binding.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic NamePopup withTitle(@Nonnull StringBinding binding) {\n\t\ttitleProperty().bind(binding);\n\t\treturn this;\n\t}\n\n\t@Nonnull\n\tpublic NamePopup withInitialPathName(@Nonnull String name) {\n\t\tinitialText = name;\n\n\t\t// Handle escaping names with newlines and such\n\t\tnameInput.setText(EscapeUtil.escapeStandard(name));\n\n\t\t// Need to get focus before selecting range.\n\t\t// When the input gets focus, it resets the selection.\n\t\t// But if we focus it, then set the selection we're good.\n\t\tnameInput.requestFocus();\n\n\t\t// Select the last portion of the name.\n\t\tnameInput.selectRange(name.lastIndexOf('/') + 1, name.length());\n\t\treturn this;\n\t}\n\n\t@Nonnull\n\tpublic NamePopup withInitialName(@Nonnull String name) {\n\t\tinitialText = name;\n\n\t\t// Handle escaping names with newlines and such\n\t\tnameInput.setText(EscapeUtil.escapeStandard(name));\n\n\t\t// Need to get focus before selecting range.\n\t\t// When the input gets focus, it resets the selection.\n\t\t// But if we focus it, then set the selection we're good.\n\t\tnameInput.requestFocus();\n\n\t\t// Select the name.\n\t\tnameInput.selectAll();\n\t\treturn this;\n\t}\n\n\tprivate static boolean bundleHasDirectory(@Nonnull Bundle<?> bundle, String dirName) {\n\t\tString dirNameWithSlash = dirName + \"/\";\n\t\treturn bundle.keySet().stream().anyMatch(file -> file.startsWith(dirNameWithSlash));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/popup/OpenUrlPopup.java",
    "content": "package software.coley.recaf.ui.control.popup;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.TextField;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.Priority;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.window.RecafScene;\nimport software.coley.recaf.ui.window.RecafStage;\nimport software.coley.recaf.util.Animations;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.services.workspace.io.ResourceImporter;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.net.URI;\nimport java.net.URL;\nimport java.util.concurrent.CompletableFuture;\n\nimport static software.coley.recaf.util.Lang.get;\n\n\n/**\n * Popup for opening a workspace from a URL.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class OpenUrlPopup extends RecafStage {\n\tprivate static final Logger logger = Logging.get(OpenUrlPopup.class);\n\tprivate final TextField input = new TextField();\n\n\t@Inject\n\tpublic OpenUrlPopup(@Nonnull WorkspaceManager workspaceManager, @Nonnull ResourceImporter resourceImporter) {\n\t\tBooleanProperty isInvalidUrl = new SimpleBooleanProperty(true);\n\t\tisInvalidUrl.bind(input.textProperty().map(text -> {\n\t\t\ttry {\n\t\t\t\t// noinspection ResultOfMethodCallIgnored\n\t\t\t\tnew URI(text).toURL();\n\t\t\t\treturn false;\n\t\t\t} catch (Throwable t) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}));\n\t\tObservableValue<String> downloadText = isInvalidUrl.flatMap(invalid ->\n\t\t\t\tinvalid ? Lang.getBinding(\"misc.download.invalid-url\") : Lang.getBinding(\"misc.load\"));\n\t\tButton load = new ActionButton(CarbonIcons.CLOUD_DOWNLOAD, downloadText, () -> {\n\t\t\tinput.setDisable(true);\n\t\t\tCompletableFuture.supplyAsync(() -> {\n\t\t\t\tString url = input.getText();\n\t\t\t\ttry {\n\t\t\t\t\tWorkspaceResource resource = resourceImporter.importResource(URI.create(url));\n\t\t\t\t\tworkspaceManager.setCurrent(new BasicWorkspace(resource));\n\t\t\t\t\treturn true;\n\t\t\t\t} catch (Exception ex) {\n\t\t\t\t\tlogger.error(\"Could not read from URL '{}'\", url, ex);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}).thenAcceptAsync((result) -> {\n\t\t\t\tinput.setDisable(false);\n\t\t\t\tif (result) {\n\t\t\t\t\tclose();\n\t\t\t\t} else {\n\t\t\t\t\tAnimations.animateFailure(input, 1000);\n\t\t\t\t}\n\t\t\t}, FxThreadUtil.executor());\n\t\t});\n\t\tload.disableProperty().bind(input.disabledProperty().or(isInvalidUrl));\n\t\tinput.setPromptText(\"https://example.com/application.jar\");\n\t\tinput.setOnAction(e -> load.getOnAction().handle(e));\n\n\n\t\tGridPane layout = new GridPane(8, 8);\n\t\tColumnConstraints e = new ColumnConstraints(0, -1, Double.MAX_VALUE);\n\t\te.setHgrow(Priority.ALWAYS);\n\t\tlayout.getColumnConstraints().add(e);\n\t\tlayout.add(input, 0, 0);\n\t\tlayout.add(load, 0, 1);\n\t\tlayout.setPadding(new Insets(10));\n\t\tlayout.setAlignment(Pos.TOP_CENTER);\n\n\t\tinput.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);\n\t\tload.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);\n\t\tlayout.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);\n\n\t\tsetMinWidth(300);\n\t\tsetMinHeight(90);\n\t\tsetTitle(get(\"menu.file.openurl\"));\n\t\tsetScene(new RecafScene(layout, 300, 90));\n\t}\n\n\t/**\n\t * Focuses the text input.\n\t */\n\tpublic void requestInputFocus() {\n\t\ttoFront();\n\t\trequestFocus();\n\t\tinput.requestFocus();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/popup/OverrideMethodPopup.java",
    "content": "package software.coley.recaf.ui.control.popup;\n\nimport atlantafx.base.controls.Spacer;\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.paint.Color;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.path.WorkspacePathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceVertex;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.PathNodeTree;\nimport software.coley.recaf.ui.window.RecafScene;\nimport software.coley.recaf.ui.window.RecafStage;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.function.BiConsumer;\n\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.CHECKMARK;\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.CLOSE;\n\n/**\n * Popup to show a selection of method to override from parent classes.\n *\n * @author Matt Coley\n */\npublic class OverrideMethodPopup extends RecafStage {\n\tprivate final PathNodeTree tree;\n\n\tpublic OverrideMethodPopup(@Nonnull Actions actions, @Nonnull CellConfigurationService configurationService,\n\t                           @Nonnull InheritanceGraph inheritanceGraph, @Nonnull Workspace workspace,\n\t                           @Nonnull ClassInfo targetClass, @Nonnull BiConsumer<ClassInfo, MethodMember> memberConsumer) {\n\t\tWorkspacePathNode rootPath = PathNodes.workspacePath(workspace);\n\t\tTreeItem<PathNode<?>> rootItem = new TreeItem<>(rootPath);\n\t\ttree = new PathNodeTree(configurationService, actions);\n\t\ttree.setShowRoot(false);\n\t\ttree.setRoot(rootItem);\n\t\ttree.getStyleClass().add(\"border-muted\");\n\n\t\t// Keyboard accept/cancel\n\t\tObservableValue<Boolean> disable = tree.getSelectionModel().selectedItemProperty().map(i -> !(i.getValue() instanceof ClassMemberPathNode));\n\t\ttree.setOnKeyPressed(e -> {\n\t\t\tif (e.getCode() == KeyCode.ENTER && !disable.getValue()) {\n\t\t\t\taccept(memberConsumer);\n\t\t\t} else if (e.getCode() == KeyCode.ESCAPE) {\n\t\t\t\thide();\n\t\t\t}\n\t\t});\n\n\t\t// Setup tree contents\n\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(targetClass.getName());\n\t\tif (vertex != null) {\n\t\t\tvertex.allParents().forEach(parent -> {\n\t\t\t\tClassPathNode parentPath = workspace.findClass(parent.getName());\n\t\t\t\tif (parentPath == null)\n\t\t\t\t\treturn;\n\t\t\t\tTreeItem<PathNode<?>> parentItem = new TreeItem<>(parentPath);\n\t\t\t\trootItem.getChildren().add(parentItem);\n\t\t\t\tfor (MethodMember method : parent.getValue().getMethods()) {\n\t\t\t\t\tif (method.hasPrivateModifier() || method.hasFinalModifier() || method.hasNativeModifier())\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tparentItem.getChildren().add(new TreeItem<>(parentPath.child(method)));\n\t\t\t\t}\n\t\t\t\tparentItem.setExpanded(true);\n\t\t\t});\n\t\t}\n\n\t\tButton acceptButton = new ActionButton(new FontIconView(CHECKMARK, Color.LAWNGREEN), () -> accept(memberConsumer));\n\t\tButton cancelButton = new ActionButton(new FontIconView(CLOSE, Color.RED), this::hide);\n\t\tacceptButton.disableProperty().bind(disable);\n\t\tHBox buttons = new HBox(acceptButton, new Spacer(), cancelButton);\n\t\tVBox layout = new VBox(tree, buttons);\n\t\tlayout.setSpacing(10);\n\t\tlayout.setAlignment(Pos.TOP_CENTER);\n\t\tlayout.setPadding(new Insets(10));\n\t\tsetMinWidth(500);\n\t\tsetMinHeight(230);\n\t\tsetMaxHeight(460);\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.title.override-method\"));\n\t\tsetScene(new RecafScene(layout, 500, 350));\n\t}\n\n\tprivate void accept(@Nonnull BiConsumer<ClassInfo, MethodMember> memberConsumer) {\n\t\tTreeItem<PathNode<?>> selectedItem = tree.getSelectionModel().getSelectedItem();\n\t\tif (selectedItem != null && selectedItem.getValue() instanceof ClassMemberPathNode memberPath) {\n\t\t\tClassInfo owner = memberPath.getValueOfType(ClassInfo.class);\n\t\t\tMethodMember method = (MethodMember) memberPath.getValue();\n\t\t\tif (owner != null)\n\t\t\t\tmemberConsumer.accept(owner, method);\n\t\t}\n\t\thide();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/popup/SelectionPopup.java",
    "content": "package software.coley.recaf.ui.control.popup;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.paint.Color;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.window.RecafScene;\nimport software.coley.recaf.ui.window.RecafStage;\n\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\n\nimport static atlantafx.base.theme.Styles.*;\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.CHECKMARK;\nimport static org.kordamp.ikonli.carbonicons.CarbonIcons.CLOSE;\n\n/**\n * Common item selection popup handling.\n *\n * @author Matt Coley\n */\n@SuppressWarnings(\"unchecked\")\npublic abstract class SelectionPopup<T> extends RecafStage {\n\tprotected Function<T, String> textMapper;\n\tprotected Function<T, Node> graphicMapper;\n\n\t/**\n\t * @param consumer\n\t * \t\tAction to run on selected items.\n\t */\n\tprotected void setup(@Nonnull Consumer<List<T>> consumer) {\n\t\tButton accept = new ActionButton(new FontIconView(CHECKMARK, Color.LAWNGREEN), () -> accept(consumer));\n\t\taccept.disableProperty().bind(isNullSelection());\n\t\tButton cancel = new ActionButton(new FontIconView(CLOSE, Color.RED), this::hide);\n\t\taccept.getStyleClass().addAll(BUTTON_ICON, BUTTON_OUTLINED, SUCCESS);\n\t\tcancel.getStyleClass().addAll(BUTTON_ICON, BUTTON_OUTLINED, DANGER);\n\n\t\t// Layout\n\t\tHBox buttons = new HBox(accept, cancel);\n\t\tbuttons.setSpacing(10);\n\t\tbuttons.setPadding(new Insets(10, 0, 10, 0));\n\t\tbuttons.setAlignment(Pos.CENTER_RIGHT);\n\t\tVBox layout = new VBox(getSelectionComponent(), buttons);\n\t\tlayout.setAlignment(Pos.TOP_CENTER);\n\t\tlayout.setPadding(new Insets(10));\n\t\tsetScene(new RecafScene(layout, 400, 300));\n\t}\n\n\t/**\n\t * @param consumer\n\t * \t\tAction to run on accept.\n\t */\n\tprotected void accept(@Nonnull Consumer<List<T>> consumer) {\n\t\taccept(adaptCurrentSelection(), consumer);\n\t}\n\n\t/**\n\t * @param selectedItems\n\t * \t\tSelected items to accept selection of.\n\t * @param consumer\n\t * \t\tAction to run on accept.\n\t */\n\tprivate void accept(@Nonnull List<T> selectedItems, @Nonnull Consumer<List<T>> consumer) {\n\t\tCompletableFuture.runAsync(() -> consumer.accept(selectedItems));\n\t\thide();\n\t}\n\n\t@Nonnull\n\tprotected abstract Node getSelectionComponent();\n\n\t@Nonnull\n\tprotected abstract List<T> adaptCurrentSelection();\n\n\t@Nonnull\n\tprotected abstract ObservableValue<Boolean> isNullSelection();\n\n\n\t/**\n\t * Enables multiple selection.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic abstract <P extends SelectionPopup<T>> P withMultipleSelection();\n\n\t/**\n\t * @param binding\n\t * \t\tTitle binding.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic <P extends SelectionPopup<T>> P withTitle(@Nonnull StringBinding binding) {\n\t\ttitleProperty().bind(binding);\n\t\treturn (P) this;\n\t}\n\n\t/**\n\t * @param textMapper\n\t * \t\tList view item text mapper.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic <P extends SelectionPopup<T>> P withTextMapping(@Nonnull Function<T, String> textMapper) {\n\t\tthis.textMapper = textMapper;\n\t\treturn (P) this;\n\t}\n\n\t/**\n\t * @param graphicMapper\n\t * \t\tList view item graphic mapper.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic <P extends SelectionPopup<T>> P withGraphicMapping(@Nonnull Function<T, Node> graphicMapper) {\n\t\tthis.graphicMapper = graphicMapper;\n\t\treturn (P) this;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/AbstractLineItemTracking.java",
    "content": "package software.coley.recaf.ui.control.richtext;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.fxmisc.richtext.CodeArea;\nimport org.fxmisc.richtext.model.PlainTextChange;\nimport org.fxmisc.richtext.model.ReadOnlyStyledDocument;\nimport org.fxmisc.richtext.model.TwoDimensional;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.ui.control.richtext.inheritance.InheritanceTracking;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemTracking;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NavigableMap;\nimport java.util.TreeMap;\nimport java.util.TreeSet;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.function.Consumer;\nimport java.util.function.Predicate;\n\n/**\n * Common base for tracking line-based items within an {@link Editor}.\n *\n * @param <T>\n * \t\tItem type.\n * @param <L>\n * \t\tListener type.\n *\n * @author Matt Coley\n * @see ProblemTracking\n * @see InheritanceTracking\n */\npublic abstract class AbstractLineItemTracking<T extends Comparable<T>, L extends PrioritySortable> implements EditorComponent, Consumer<PlainTextChange> {\n\tprotected static final DebuggingLogger logger = Logging.get(AbstractLineItemTracking.class);\n\tprotected final List<L> listeners = new CopyOnWriteArrayList<>();\n\tprotected final NavigableMap<Integer, List<T>> items = new TreeMap<>();\n\tprotected Editor editor;\n\n\t/**\n\t * @param item\n\t * \t\tItem to get the line of.\n\t *\n\t * @return Line number of the item.\n\t */\n\tprotected abstract int getLine(@Nonnull T item);\n\n\t/**\n\t * @param item\n\t * \t\tItem to create a copy of with a new line number.\n\t * @param newLine\n\t * \t\tLine number of the new item.\n\t *\n\t * @return Copy of the item with the new line number.\n\t */\n\tprotected abstract T withLine(@Nonnull T item, int newLine);\n\n\t/**\n\t * Invoke all listeners to notify them of an invalidation of tracked items.\n\t *\n\t * @param failureMessage\n\t * \t\tMessage to log when handling of a listener throws an exception.\n\t */\n\tprotected abstract void notifyListeners(@Nonnull String failureMessage);\n\n\t/**\n\t * @param item\n\t * \t\tItem to add.\n\t *\n\t * @return {@code true} when the item was added successfully.\n\t */\n\tpublic boolean addItem(@Nonnull T item) {\n\t\tList<T> list;\n\t\tsynchronized (items) {list = items.computeIfAbsent(getLine(item), k -> new ArrayList<>());}\n\t\tboolean insert = Lists.sortedInsert(list, item);\n\t\tif (insert)\n\t\t\tnotifyListeners(\"Exception thrown when adding item to tracking\");\n\t\treturn insert;\n\t}\n\n\t/**\n\t * @param additions\n\t * \t\tItems to add.\n\t *\n\t * @return {@code true} when the items were added successfully.\n\t */\n\tpublic boolean addItems(@Nonnull Collection<T> additions) {\n\t\tList<T> list;\n\t\tboolean update = false;\n\t\tsynchronized (items) {\n\t\t\tfor (T add : additions) {\n\t\t\t\tlist = items.computeIfAbsent(getLine(add), k -> new ArrayList<>());\n\t\t\t\tupdate |= Lists.sortedInsert(list, add);\n\t\t\t}\n\t\t}\n\t\tif (update)\n\t\t\tnotifyListeners(\"Exception thrown when adding items to tracking\");\n\t\treturn update;\n\t}\n\n\t/**\n\t * @param line\n\t * \t\tLine containing items to remove.\n\t *\n\t * @return {@code true} when an item at the line was removed.\n\t * {@code false} when there were no items on the line.\n\t */\n\tpublic boolean removeByLine(int line) {\n\t\tboolean updated;\n\t\tsynchronized (items) {updated = items.remove(line) != null;}\n\t\tif (updated)\n\t\t\tnotifyListeners(\"Exception thrown when removing items from tracking\");\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Clear all tracked items.\n\t */\n\tpublic void clear() {\n\t\tboolean updated = !items.isEmpty();\n\t\tsynchronized (items) {items.clear();}\n\t\tif (updated)\n\t\t\tnotifyListeners(\"Exception thrown when clearing items from tracking\");\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t *\n\t * @return {@code true} when listener was added.\n\t */\n\tpublic boolean addListener(@Nonnull L listener) {\n\t\t// PrioritySortable is used by callers; keep the same behavior.\n\t\treturn PrioritySortable.add(listeners, listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t *\n\t * @return {@code true} when listener was removed.\n\t * {@code false} when listener was not present to begin with.\n\t */\n\tpublic boolean removeListener(@Nonnull L listener) {\n\t\treturn listeners.remove(listener);\n\t}\n\n\t/**\n\t * @param line\n\t * \t\tLine number to get items for.\n\t *\n\t * @return Matched items on the given line.\n\t */\n\t@Nonnull\n\tpublic List<T> getItemsOnLine(int line) {\n\t\tsynchronized (items) {return items.getOrDefault(line, Collections.emptyList());}\n\t}\n\n\t/**\n\t * @param line\n\t * \t\tLine number of item.\n\t *\n\t * @return First item on the line.\n\t */\n\t@Nullable\n\tpublic T getFirstItemOnLine(int line) {\n\t\tList<T> items = getItemsOnLine(line);\n\t\tif (items.isEmpty())\n\t\t\treturn null;\n\t\treturn items.getFirst();\n\t}\n\n\t/**\n\t * @return Map of all items by line number.\n\t */\n\t@Nonnull\n\tpublic NavigableMap<Integer, List<T>> getItems() {\n\t\treturn Collections.synchronizedNavigableMap(items);\n\t}\n\n\t/**\n\t * @return All items as a flattened list.\n\t */\n\t@Nonnull\n\tpublic List<T> getAllItems() {\n\t\tList<T> list = new ArrayList<>();\n\t\tsynchronized (items) {items.values().forEach(list::addAll);}\n\t\treturn list;\n\t}\n\n\t/**\n\t * Get all items as a flattened list that match the given filter.\n\t *\n\t * @param filter\n\t * \t\tItem filter.\n\t *\n\t * @return Matched items.\n\t */\n\t@Nonnull\n\tpublic List<T> getItems(@Nonnull Predicate<T> filter) {\n\t\tList<T> list = new ArrayList<>();\n\t\tsynchronized (items) {\n\t\t\titems.values().stream()\n\t\t\t\t\t.flatMap(Collection::stream)\n\t\t\t\t\t.filter(filter)\n\t\t\t\t\t.forEach(list::add);\n\t\t}\n\t\treturn list;\n\t}\n\n\t@Override\n\tpublic void accept(PlainTextChange change) {\n\t\t// Skip if there is no associated editor, or there are no items to update\n\t\tif (editor == null || items.isEmpty()) return;\n\n\t\t// TODO: There are some edge cases where the tracking of item indicators will fail, and we just\n\t\t//  delete them. Deleting an empty line before a line with an error will void it.\n\t\t//  I'm not really sure how to make a clean fix for that, but because the rest of\n\t\t//  it works relatively well I'm not gonna touch it for now.\n\t\ttry {\n\t\t\tString insertedText = change.getInserted();\n\t\t\tString removedText = change.getRemoved();\n\t\t\tboolean lineInserted = insertedText.contains(\"\\n\");\n\t\t\tboolean lineRemoved = removedText.contains(\"\\n\");\n\n\t\t\t// Handle line removal/insertion.\n\t\t\t//\n\t\t\t// Some thoughts, you may ask why we do an \"if\" block for each, but not with else if.\n\t\t\t// Well, copy-pasting text does both. So we remove then insert for replacement.\n\t\t\tif (lineRemoved) {\n\t\t\t\tReadOnlyStyledDocument<?, ?, ?> lastDocumentSnapshot = editor.getLastDocumentSnapshot();\n\n\t\t\t\t// Get line number and add +1 to make it 1-indexed\n\t\t\t\tint start = lastDocumentSnapshot.offsetToPosition(change.getPosition(), TwoDimensional.Bias.Backward).getMajor() + 1;\n\n\t\t\t\t// End line number needs +1 since it will include the next line due to inclusion of \"\\n\"\n\t\t\t\tint end = lastDocumentSnapshot.offsetToPosition(change.getRemovalEnd(), TwoDimensional.Bias.Backward).getMajor() + 1;\n\n\t\t\t\tonLinesRemoved(start, end);\n\t\t\t}\n\t\t\tif (lineInserted) {\n\t\t\t\tCodeArea area = editor.getCodeArea();\n\n\t\t\t\t// Get line number and add +1 to make it 1-indexed\n\t\t\t\tint start = area.offsetToPosition(change.getPosition(), TwoDimensional.Bias.Backward).getMajor() + 1;\n\n\t\t\t\t// End line number doesn't need +1 since it will include the next line due to inclusion of \"\\n\"\n\t\t\t\tint end = area.offsetToPosition(change.getInsertionEnd(), TwoDimensional.Bias.Backward).getMajor();\n\n\t\t\t\tonLinesInserted(start, end);\n\t\t\t}\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Error updating offsets in text document\", t);\n\t\t}\n\t}\n\n\t/**\n\t * Shifts items beyond the given range by {@code 1 + (endLine - startLine)}.\n\t * Items in the removed range are deleted.\n\t *\n\t * @param startLine\n\t * \t\tStarting range of lines inserted <i>(inclusive)</i>.\n\t * @param endLine\n\t * \t\tEnding range of lines inserted <i>(inclusive)</i>.\n\t */\n\tprotected void onLinesInserted(int startLine, int endLine) {\n\t\tlogger.debugging(l -> l.trace(\"Lines inserted: {}-{}\", startLine, endLine));\n\t\tTreeSet<Map.Entry<Integer, List<T>>> set = new TreeSet<>((o1, o2) -> Integer.compare(o2.getKey(), o1.getKey()));\n\t\tset.addAll(items.entrySet());\n\t\tset.stream()\n\t\t\t\t.filter(e -> e.getKey() >= startLine)\n\t\t\t\t.forEach(e -> {\n\t\t\t\t\tint line = e.getKey();\n\t\t\t\t\tList<T> list = e.getValue();\n\n\t\t\t\t\t// Shift all items down by the shift amount.\n\t\t\t\t\tint shift = 1 + endLine - startLine;\n\t\t\t\t\tremoveByLine(line);\n\t\t\t\t\tlist.forEach(i -> {\n\t\t\t\t\t\tlogger.debugging(l -> l.trace(\"Move item '{}' down {} lines\", i, shift));\n\t\t\t\t\t\taddItem(withLine(i, line + shift));\n\t\t\t\t\t});\n\t\t\t\t});\n\t}\n\n\t/**\n\t * Shifts items beyond the given range by {@code endLine - startLine}.\n\t * Items in the removed range are deleted.\n\t *\n\t * @param startLine\n\t * \t\tStarting range of lines removed <i>(inclusive)</i>.\n\t * @param endLine\n\t * \t\tEnding range of lines removed <i>(exclusive)</i>.\n\t */\n\tprotected void onLinesRemoved(int startLine, int endLine) {\n\t\tlogger.debugging(l -> l.trace(\"Lines removed: {}-{}\", startLine, endLine));\n\n\t\t// We will want to sort the order of removed lines so we\n\t\tTreeSet<Map.Entry<Integer, List<T>>> set = new TreeSet<>(Map.Entry.comparingByKey());\n\t\tsynchronized (items) {set.addAll(items.entrySet());}\n\t\tset.stream()\n\t\t\t\t.filter(e -> e.getKey() >= startLine)\n\t\t\t\t.forEach(e -> {\n\t\t\t\t\tint line = e.getKey();\n\t\t\t\t\tList<T> list = e.getValue();\n\n\t\t\t\t\t// Shift all items up by the shift amount\n\t\t\t\t\tint shift = endLine - startLine;\n\t\t\t\t\tremoveByLine(line);\n\n\t\t\t\t\t// Don't add item back if it's in the removed range\n\t\t\t\t\tlist.stream()\n\t\t\t\t\t\t\t.filter(i -> getLine(i) < startLine || getLine(i) > endLine)\n\t\t\t\t\t\t\t.forEach(i -> {\n\t\t\t\t\t\t\t\tlogger.debugging(l -> l.trace(\"Move item '{}' up {} lines\", i, shift));\n\t\t\t\t\t\t\t\taddItem(withLine(i, line - shift));\n\t\t\t\t\t\t\t});\n\t\t\t\t});\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tthis.editor = editor;\n\t\tclear();\n\t\teditor.getTextChangeEventStream().addObserver(this);\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tthis.editor = null;\n\t\tclear();\n\t\teditor.getTextChangeEventStream().removeObserver(this);\n\t}\n}\n\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/Editor.java",
    "content": "package software.coley.recaf.ui.control.richtext;\n\nimport com.google.common.collect.EvictingQueue;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.value.ObservableValue;\nimport javafx.collections.ObservableList;\nimport javafx.scene.Node;\nimport javafx.scene.control.IndexRange;\nimport javafx.scene.control.ScrollBar;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.Region;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.text.Text;\nimport org.fxmisc.flowless.Cell;\nimport org.fxmisc.flowless.VirtualFlow;\nimport org.fxmisc.richtext.CodeArea;\nimport org.fxmisc.richtext.GenericStyledArea;\nimport org.fxmisc.richtext.StyleActions;\nimport org.fxmisc.richtext.model.PlainTextChange;\nimport org.fxmisc.richtext.model.ReadOnlyStyledDocument;\nimport org.fxmisc.richtext.model.StyleSpans;\nimport org.fxmisc.richtext.model.StyledDocument;\nimport org.fxmisc.richtext.model.TwoDimensional;\nimport org.reactfx.Change;\nimport org.reactfx.EventStream;\nimport org.reactfx.EventStreams;\nimport org.reactfx.collection.MemoizationList;\nimport org.slf4j.Logger;\nimport software.coley.collections.Lists;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.Closing;\nimport software.coley.recaf.ui.control.VirtualizedScrollPaneWrapper;\nimport software.coley.recaf.ui.control.richtext.bracket.SelectedBracketTracking;\nimport software.coley.recaf.ui.control.richtext.linegraphics.RootLineGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemTracking;\nimport software.coley.recaf.ui.control.richtext.suggest.TabCompleter;\nimport software.coley.recaf.ui.control.richtext.syntax.StyleResult;\nimport software.coley.recaf.ui.control.richtext.syntax.SyntaxHighlighter;\nimport software.coley.recaf.ui.control.richtext.syntax.SyntaxUtil;\nimport software.coley.recaf.ui.pane.editing.ProblemOverlay;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.IntRange;\nimport software.coley.recaf.util.ReflectUtil;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.util.threading.ThreadUtil;\n\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutorService;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\n\n/**\n * Modular text editor control.\n * <ul>\n *     <li>Configure syntax with {@link #setSyntaxHighlighter(SyntaxHighlighter)}</li>\n *     <li>Configure selected bracket tracking with {@link #setSelectedBracketTracking(SelectedBracketTracking)}</li>\n *     <li>Configure line graphics via {@link #getRootLineGraphicFactory()}</li>\n * </ul>\n *\n * @author Matt Coley\n */\npublic class Editor extends BorderPane implements Closing {\n\tprivate static final Logger logger = Logging.get(Editor.class);\n\tpublic static final int SHORTER_DELAY_MS = 25;\n\tpublic static final int SHORT_DELAY_MS = 150;\n\tpublic static final int MEDIUM_DELAY_MS = 400;\n\tprivate static final StyleResult FALLBACK_STYLE_RESULT = new StyleResult(StyleSpans.singleton(Collections.emptyList(), 0), 0);\n\tprivate final Map<String, EditorComponent> components = new ConcurrentHashMap<>();\n\tprivate final StackPane stackPane = new StackPane();\n\tprivate final CodeArea codeArea = new SafeCodeArea();\n\tprivate final VirtualizedScrollPaneWrapper<CodeArea> codeScrollWrapper;\n\tprivate final VirtualFlow<?, ?> virtualFlow;\n\tprivate final MemoizationList<Cell<?, ?>> virtualCellList;\n\tprivate final ExecutorService syntaxPool = ThreadPoolFactory.newSingleThreadExecutor(\"syntax-highlight\");\n\tprivate final RootLineGraphicFactory rootLineGraphicFactory = new RootLineGraphicFactory(this);\n\tprivate final EventStream<Change<Integer>> caretPosEventStream;\n\tprivate final EvictingQueue<KeyCode> recentKeys = EvictingQueue.create(5);\n\tprivate ReadOnlyStyledDocument<Collection<String>, String, Collection<String>> lastDocumentSnapshot;\n\tprivate ScrollReset lastScrollReset = null;\n\tprivate CaretReset lastCaretReset = null;\n\tprivate TabCompleter<?> tabCompleter;\n\tprivate SyntaxHighlighter syntaxHighlighter;\n\tprivate SelectedBracketTracking selectedBracketTracking;\n\tprivate ProblemTracking problemTracking;\n\tprivate ProblemOverlay problemOverlay;\n\n\t/**\n\t * New editor instance.\n\t */\n\tpublic Editor() {\n\t\t// Get the reflection hacks out of the way first.\n\t\t//  - Want to have access to scrollbars & the internal 'virtualFlow'\n\t\tcodeScrollWrapper = new VirtualizedScrollPaneWrapper<>(codeArea);\n\n\t\tvirtualFlow = Unchecked.get(() -> ReflectUtil.quietGet(codeArea, GenericStyledArea.class.getDeclaredField(\"virtualFlow\")));\n\t\tObject virtualCellManager = Unchecked.get(() -> ReflectUtil.quietGet(virtualFlow, VirtualFlow.class.getDeclaredField(\"cellListManager\")));\n\t\tvirtualCellList = ReflectUtil.quietInvoke(virtualCellManager.getClass(), virtualCellManager, \"getLazyCellList\", new Class[0], new Object[0]);\n\n\t\t// Initial layout.\n\t\tsetCenter(stackPane);\n\t\tstackPane.getChildren().add(codeScrollWrapper);\n\n\t\t// Do not want text wrapping in a code editor.\n\t\tcodeArea.setWrapText(false);\n\n\t\t// Add event filter to hook tab usage.\n\t\tcodeArea.addEventFilter(KeyEvent.KEY_PRESSED, e -> {\n\t\t\ttry {\n\t\t\t\trecentKeys.add(e.getCode());\n\t\t\t\tif (e.getCode() == KeyCode.TAB)\n\t\t\t\t\thandleTab(e);\n\t\t\t\telse if (e.getCode() == KeyCode.ENTER)\n\t\t\t\t\thandleNewline(e);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Error handling tab/newline interception in editor\", t);\n\t\t\t}\n\t\t});\n\n\t\t// Set paragraph graphic factory to the user-configurable root graphics factory.\n\t\tcodeArea.setParagraphGraphicFactory(rootLineGraphicFactory);\n\n\t\t// This property copies the style of adjacent characters when typing (instead of having no style).\n\t\t// It may not seem like much, but it makes our restyle range computation logic much simpler.\n\t\t// Consider a multi-line comment. If you had this set to use the initial style (none) it would break up\n\t\t// multi-line comment style spans. We would have to re-stitch them together based on the inserted text position\n\t\t// which would be a huge pain in the ass.\n\t\tcodeArea.setUseInitialStyleForInsertion(false);\n\n\t\t// Register a text change listener for updating caret/scroll positions after text updates.\n\t\tcodeArea.plainTextChanges().addObserver(change -> {\n\t\t\tif (lastCaretReset != null) {\n\t\t\t\tlastCaretReset.changed(change);\n\t\t\t\tlastCaretReset = null;\n\t\t\t}\n\t\t\tif (lastScrollReset != null) {\n\t\t\t\tlastScrollReset.changed(change);\n\t\t\t\tlastScrollReset = null;\n\t\t\t}\n\t\t});\n\n\t\t// Register a text change listener for recording state used for tab completion.\n\t\tcodeArea.plainTextChanges().addObserver(change -> {\n\t\t\ttry {\n\t\t\t\tif (tabCompleter != null)\n\t\t\t\t\ttabCompleter.onFineTextUpdate(change);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Error handling tab-completion update in editor\", t);\n\t\t\t}\n\t\t});\n\n\t\t// Register a text change listener that operates on reduces calls (limit calls to when user stops typing).\n\t\t// Used for:\n\t\t//  - Restyling text near inserted/removed text\n\t\t//  - Taking document snapshots\n\t\tcodeArea.plainTextChanges()\n\t\t\t\t.reduceSuccessions(Collections::singletonList, Lists::add, Duration.ofMillis(SHORT_DELAY_MS))\n\t\t\t\t.addObserver(changes -> {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// Pass to highlighter.\n\t\t\t\t\t\tif (syntaxHighlighter != null) {\n\t\t\t\t\t\t\tfor (PlainTextChange change : changes) {\n\t\t\t\t\t\t\t\tschedule(syntaxPool, FALLBACK_STYLE_RESULT, () -> {\n\t\t\t\t\t\t\t\t\tString text = getText();\n\t\t\t\t\t\t\t\t\tIntRange range = SyntaxUtil.getRangeForRestyle(text, getStyleSpans(), syntaxHighlighter, change);\n\t\t\t\t\t\t\t\t\tint start = range.start();\n\t\t\t\t\t\t\t\t\tint end = range.end();\n\t\t\t\t\t\t\t\t\treturn new StyleResult(syntaxHighlighter.createStyleSpans(text, start, end), start);\n\t\t\t\t\t\t\t\t}, result -> setStyleSpans(result.position(), result.spans()));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Do rough completion updates.\n\t\t\t\t\t\tif (tabCompleter != null)\n\t\t\t\t\t\t\ttabCompleter.onRoughTextUpdate(changes);\n\n\t\t\t\t\t\t// Record content of area.\n\t\t\t\t\t\tlastDocumentSnapshot = codeArea.getContent().snapshot();\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tlogger.error(\"Uncaught error on editor reduced-succession update\", t);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t// Create event-streams for various events.\n\t\tcaretPosEventStream = EventStreams.changesOf(codeArea.caretPositionProperty());\n\n\t\t// Initial snapshot state.\n\t\tlastDocumentSnapshot = ReadOnlyStyledDocument.from(codeArea.getDocument());\n\t}\n\n\t@Override\n\tpublic void close() {\n\t\tif (selectedBracketTracking != null)\n\t\t\tselectedBracketTracking.close();\n\t\tif (!syntaxPool.isShutdown())\n\t\t\tsyntaxPool.shutdownNow();\n\t}\n\n\t/**\n\t * @return {@code true} to indicate the contents of this editor are editable. {@code false} for read-only content.\n\t */\n\tpublic boolean isEditable() {\n\t\treturn codeArea.isEditable();\n\t}\n\n\t/**\n\t * Immediately show the given paragraph at the top of the editor.\n\t * This is opposed to {@link CodeArea#showParagraphAtTop(int)} which operates on a slight delay.\n\t *\n\t * @param paragraph\n\t * \t\tParagraph index.\n\t */\n\tpublic void showParagraphAtTop(int paragraph) {\n\t\tvirtualFlow.showAsFirst(paragraph);\n\t}\n\n\t/**\n\t * Immediately show the given paragraph at the bottom of the editor.\n\t * This is opposed to {@link CodeArea#showParagraphAtTop(int)} which operates on a slight delay.\n\t *\n\t * @param paragraph\n\t * \t\tParagraph index.\n\t */\n\tpublic void showParagraphAtBottom(int paragraph) {\n\t\tvirtualFlow.showAsLast(paragraph);\n\t}\n\n\t/**\n\t * Immediately show the given paragraph at the center of the editor.\n\t * This is opposed to {@link CodeArea#showParagraphAtCenter(int)} which operates on a slight delay.\n\t *\n\t * @param paragraph\n\t * \t\tParagraph index.\n\t */\n\tpublic void showParagraphAtCenter(int paragraph) {\n\t\t// Approximate center position.\n\t\t// - Assuming all cells are the same height, we can compute the offset needed to center the paragraph.\n\t\tint count = 1 + Math.max(virtualFlow.getLastVisibleIndex() - virtualFlow.getFirstVisibleIndex(), 0);\n\t\tdouble paragraphHeight = getHeight() / count;\n\t\tvirtualFlow.showAtOffset(paragraph, count / 2.0 * paragraphHeight / 2.0);\n\t}\n\n\t/**\n\t * Delegates to {@link StyleActions#setStyleSpans(int, StyleSpans)} but with some scroll-position preservation logic.\n\t *\n\t * @param from\n\t * \t\tText position to start applying styles at.\n\t * @param spans\n\t * \t\tStyle spans to apply.\n\t */\n\tprivate void setStyleSpans(int from, @Nonnull StyleSpans<Collection<String>> spans) {\n\t\t// Updating the styles can cause the 'Navigator' to set its target position back to zero for... some reason.\n\t\t// See Navigator:\n\t\t//  - setTargetPosition\n\t\t//  - scrollCurrentPositionBy\n\t\t// To prevent jank, we record the first visible index before the update, and restore it after.\n\t\t//\n\t\t// We use the CodeArea's \"showParagraphAtTop\" instead of our direct one on the VirtualFlow, because the CodeArea's\n\t\t// suspension handling is necessary to keep event ordering correct (where the restoration happens after the janky reset).\n\t\tint virtualFlowFirst = virtualFlow.getFirstVisibleIndex();\n\t\tcodeArea.setStyleSpans(from, spans);\n\t\tcodeArea.showParagraphAtTop(virtualFlowFirst);\n\t}\n\n\t/**\n\t * The passed inputs for the position will be modified as per\n\t * {@link SyntaxUtil#getRangeForRestyle(String, StyleSpans, SyntaxHighlighter, PlainTextChange)}.\n\t * For this reason, you do not have to be super exact with the given values.\n\t *\n\t * @param position\n\t * \t\tStart position to begin the restyling at.\n\t * @param length\n\t * \t\tLength of the restyled range.\n\t *\n\t * @return Restyle future.\n\t */\n\t@Nonnull\n\tpublic CompletableFuture<Void> restyleAtPosition(int position, int length) {\n\t\tif (syntaxHighlighter != null) {\n\t\t\treturn schedule(syntaxPool, FALLBACK_STYLE_RESULT, () -> {\n\t\t\t\tIntRange range = SyntaxUtil.getRangeForRestyle(getText(), getStyleSpans(),\n\t\t\t\t\t\tsyntaxHighlighter, new PlainTextChange(position, \"\", \".\".repeat(length)));\n\t\t\t\tint start = range.start();\n\t\t\t\tint end = range.end();\n\t\t\t\treturn new StyleResult(syntaxHighlighter.createStyleSpans(getText(), start, end), start);\n\t\t\t}, result -> setStyleSpans(result.position(), result.spans()));\n\t\t}\n\t\treturn CompletableFuture.completedFuture(null);\n\t}\n\n\t/**\n\t * The editor is a {@link BorderPane} layout. The sides can be used to toggle \"drawers\" of sorts.\n\t * The center is home to the primary component, the {@link #getCodeArea() code-area}.\n\t *\n\t * @return The {@link StackPane} present in the {@link #getCenter() center} of the editor.\n\t */\n\t@Nonnull\n\tpublic StackPane getPrimaryStack() {\n\t\treturn stackPane;\n\t}\n\n\t/**\n\t * Redraw visible paragraph graphics.\n\t * <br>\n\t * <b>Must be called on FX thread.</b>\n\t */\n\tpublic void redrawParagraphGraphics() {\n\t\tint startParagraphIndex = Math.max(0, virtualFlow.getFirstVisibleIndex() - 1);\n\t\tint endParagraphIndex = Math.min(codeArea.getParagraphs().size() - 1, virtualFlow.getLastVisibleIndex());\n\t\tfor (int i = startParagraphIndex; i <= endParagraphIndex; i++)\n\t\t\tcodeArea.recreateParagraphGraphic(i);\n\t}\n\n\t/**\n\t * @return Current style spans for the entire document.\n\t */\n\t@Nonnull\n\tpublic StyleSpans<Collection<String>> getStyleSpans() {\n\t\treturn codeArea.getStyleSpans(0, getTextLength());\n\t}\n\n\t/**\n\t * @return Current length of document text.\n\t */\n\tpublic int getTextLength() {\n\t\treturn codeArea.getLength();\n\t}\n\n\t/**\n\t * @return Current document text.\n\t */\n\t@Nonnull\n\tpublic String getText() {\n\t\treturn Objects.requireNonNullElse(codeArea.getText(), \"\");\n\t}\n\n\t/**\n\t * @return The prior document state, from the last {@link #getTextChangeEventStream() text change event}.\n\t */\n\t@Nonnull\n\tpublic ReadOnlyStyledDocument<Collection<String>, String, Collection<String>> getLastDocumentSnapshot() {\n\t\treturn lastDocumentSnapshot;\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tText to set.\n\t */\n\tpublic void setText(@Nullable String text) {\n\t\t// Filter input\n\t\tif (text == null)\n\t\t\ttext = \"\";\n\n\t\t// Prepare reset of caret/scroll position\n\t\tlastCaretReset = new CaretReset(codeArea.getCaretPosition());\n\t\tlastScrollReset = new ScrollReset(virtualFlow.getFirstVisibleIndex());\n\n\t\t// Replace the full text document\n\t\tif (getTextLength() == 0) {\n\t\t\tcodeArea.appendText(text);\n\t\t} else {\n\t\t\tcodeArea.replaceText(text);\n\n\t\t\t// Whole text replacement often results in the text not applying a restyle, resulting in all default text.\n\t\t\t// Scheduling a restyle at the start ensures if this happens it gets fixed in the next FX update cycle.\n\t\t\tif (!text.isBlank())\n\t\t\t\trestyleAtPosition(0, 0);\n\t\t}\n\t}\n\n\t/**\n\t * Adds a tab indentation into the given paragraph.\n\t *\n\t * @param paragraph\n\t * \t\tParagraph to indent.\n\t */\n\tpublic void indent(int paragraph) {\n\t\tString paragraphContents = codeArea.getParagraph(paragraph).getText();\n\t\tint column = 0;\n\t\tfor (; column < paragraphContents.length(); column++) {\n\t\t\tchar c = paragraphContents.charAt(column);\n\t\t\tif (c != ' ' && c != '\\t') break;\n\t\t}\n\t\tcodeArea.insert(paragraph, column, newText(\"\\t\"));\n\t}\n\n\t/**\n\t * Removes indentation from the given paragraph.\n\t *\n\t * @param paragraph\n\t * \t\tParagraph to unindent.\n\t *\n\t * @return {@code true} when there was text removed (successful unindentation).\n\t */\n\tpublic boolean unindent(int paragraph) {\n\t\tString paragraphContents = codeArea.getParagraph(paragraph).getText();\n\t\tint column = 0;\n\t\tfor (; column < paragraphContents.length(); column++) {\n\t\t\tchar c = paragraphContents.charAt(column);\n\t\t\tif (c != ' ' && c != '\\t') break;\n\t\t}\n\t\tif (column > 0) {\n\t\t\tcodeArea.deleteText(paragraph, column - 1, paragraph, column);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Delegates to {@link CodeArea#textProperty()}.\n\t * <br>\n\t * Do not use this to set text. Instead, use {@link #setText(String)}.\n\t *\n\t * @return Property representation of {@link #getText()}.\n\t */\n\t@Nonnull\n\tpublic ObservableValue<String> textProperty() {\n\t\treturn codeArea.textProperty();\n\t}\n\n\t/**\n\t * Delegates to {@link CodeArea#plainTextChanges()}.\n\t *\n\t * @return Event stream for changes to {@link #textProperty()}.\n\t */\n\t@Nonnull\n\tpublic EventStream<PlainTextChange> getTextChangeEventStream() {\n\t\treturn codeArea.plainTextChanges();\n\t}\n\n\t/**\n\t * @return Event stream wrapper for {@link CodeArea#caretPositionProperty()}.\n\t */\n\t@Nonnull\n\tpublic EventStream<Change<Integer>> getCaretPosEventStream() {\n\t\treturn caretPosEventStream;\n\t}\n\n\t/**\n\t * @return The root line graphics factory.\n\t */\n\t@Nonnull\n\tpublic RootLineGraphicFactory getRootLineGraphicFactory() {\n\t\treturn rootLineGraphicFactory;\n\t}\n\n\t/**\n\t * @return Current highlighter.\n\t */\n\t@Nullable\n\tpublic SyntaxHighlighter getSyntaxHighlighter() {\n\t\treturn syntaxHighlighter;\n\t}\n\n\t/**\n\t * @param syntaxHighlighter\n\t * \t\tHighlighter to use.\n\t */\n\tpublic void setSyntaxHighlighter(@Nullable SyntaxHighlighter syntaxHighlighter) {\n\t\t// Uninstall prior.\n\t\tSyntaxHighlighter previousSyntaxHighlighter = this.syntaxHighlighter;\n\t\tif (previousSyntaxHighlighter != null)\n\t\t\tpreviousSyntaxHighlighter.uninstall(this);\n\n\t\t// Set and install new instance.\n\t\tthis.syntaxHighlighter = syntaxHighlighter;\n\t\tif (syntaxHighlighter != null) {\n\t\t\tsyntaxHighlighter.install(this);\n\t\t\tString text = getText();\n\t\t\tif (!text.isBlank())\n\t\t\t\tsetStyleSpans(0, syntaxHighlighter.createStyleSpans(text, 0, getTextLength()));\n\t\t}\n\t}\n\n\t/**\n\t * @param selectedBracketTracking\n\t * \t\tNew selected bracket tracking implementation, or {@code null} to disable selected bracket tracking.\n\t */\n\tpublic void setSelectedBracketTracking(@Nullable SelectedBracketTracking selectedBracketTracking) {\n\t\t// Uninstall prior.\n\t\tSelectedBracketTracking previousSelectedBracketTracking = this.selectedBracketTracking;\n\t\tif (previousSelectedBracketTracking != null)\n\t\t\tpreviousSelectedBracketTracking.uninstall(this);\n\n\t\t// Set and install new instance.\n\t\tthis.selectedBracketTracking = selectedBracketTracking;\n\t\tif (selectedBracketTracking != null)\n\t\t\tselectedBracketTracking.install(this);\n\t}\n\n\t/**\n\t * @return Selected bracket tracking implementation.\n\t */\n\t@Nullable\n\tpublic SelectedBracketTracking getSelectedBracketTracking() {\n\t\treturn selectedBracketTracking;\n\t}\n\n\t/**\n\t * @param problemTracking\n\t * \t\tProblem tracking implementation.\n\t */\n\tpublic void setProblemTracking(@Nullable ProblemTracking problemTracking) {\n\t\t// Uninstall prior.\n\t\tProblemTracking previousProblemTracking = this.problemTracking;\n\t\tif (previousProblemTracking != null)\n\t\t\tpreviousProblemTracking.uninstall(this);\n\t\tif (problemOverlay != null) {\n\t\t\tproblemOverlay.uninstall(this);\n\t\t\tproblemOverlay = null;\n\t\t}\n\n\t\t// Set and install new instance.\n\t\tthis.problemTracking = problemTracking;\n\t\tif (problemTracking != null) {\n\t\t\tproblemTracking.install(this);\n\t\t\tproblemOverlay = new ProblemOverlay();\n\t\t\tproblemOverlay.install(this);\n\t\t}\n\t}\n\n\t/**\n\t * @return Problem tracking implementation.\n\t */\n\t@Nullable\n\tpublic ProblemTracking getProblemTracking() {\n\t\treturn problemTracking;\n\t}\n\n\t/**\n\t * @return Tab completion implementation.\n\t */\n\t@Nullable\n\tpublic TabCompleter<?> getTabCompleter() {\n\t\treturn tabCompleter;\n\t}\n\n\t/**\n\t * @param tabCompleter\n\t * \t\tTab completion implementation.\n\t */\n\tpublic void setTabCompleter(@Nullable TabCompleter<?> tabCompleter) {\n\t\t// Uninstall prior.\n\t\tTabCompleter<?> previousTabCompleter = this.tabCompleter;\n\t\tif (previousTabCompleter != null)\n\t\t\tpreviousTabCompleter.uninstall(this);\n\n\t\t// Set and install new instance.\n\t\tthis.tabCompleter = tabCompleter;\n\t\tif (tabCompleter != null)\n\t\t\ttabCompleter.install(this);\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tComponent key.\n\t *\n\t * @return Associated component if found.\n\t */\n\t@Nullable\n\tpublic EditorComponent getComponent(@Nonnull String key) {\n\t\treturn components.get(key);\n\t}\n\n\t/**\n\t * @param key\n\t * \t\tComponent key.\n\t * @param component\n\t * \t\tComponent to associate.\n\t */\n\tpublic void setComponent(@Nonnull String key, @Nullable EditorComponent component) {\n\t\tif (component == null)\n\t\t\tcomponents.remove(key);\n\t\telse\n\t\t\tcomponents.put(key, component);\n\t}\n\n\t/**\n\t * @return Backing text editor component.\n\t */\n\t@Nonnull\n\tpublic CodeArea getCodeArea() {\n\t\treturn codeArea;\n\t}\n\n\t/**\n\t * @return Virtual flow backing the {@link #getCodeArea() code area}.\n\t */\n\t@Nonnull\n\tpublic VirtualFlow<?, ?> getVirtualFlow() {\n\t\treturn virtualFlow;\n\t}\n\n\t/**\n\t * @return Virtualized cell list within the {@link #getVirtualFlow() virtual flow}.\n\t */\n\t@Nonnull\n\tpublic MemoizationList<Cell<?, ?>> getVirtualCellList() {\n\t\treturn virtualCellList;\n\t}\n\n\t/**\n\t * Get text nodes on a paragraph\n\t * <p>\n\t * Be <b>very aware</b> of when you call this. You may encounter unexpected values if invoked during early\n\t * layout of your node / scene.\n\t * <p>\n\t * This method is why we have to do the {@link FxThreadUtil#delayedRun(long, Runnable)} call above.\n\t * Normally when you use {@code virtualFlow.getCellIfVisible(paragraph)} it lays out the nodes for\n\t * you so that you don't run into this problem. The problem is it lays out the whole {@code ParagraphBox}\n\t * class, which includes the graphic factory we're currently populating the content of.\n\t * This means using that method will cause a {@link StackOverflowError}.\n\t * Thus, we have the hacky delayed run instead.\n\t *\n\t * @param paragraph\n\t * \t\tParagraph index to get the text nodes of.\n\t *\n\t * @return List of text nodes in the paragraph.\n\t */\n\t@Nonnull\n\tpublic List<Text> getTextNodes(int paragraph) {\n\t\t// Get the cell from the given paragraph. It should exist since we're\n\t\t// initializing a paragraph graphic for it.\n\t\tOptional<Cell<?, ?>> cell = virtualCellList.getIfMemoized(paragraph);\n\t\tif (cell.isEmpty())\n\t\t\treturn Collections.emptyList();\n\n\t\t// ParagraphBox is private in RichTextFX, but we just need to get the children so\n\t\t// casting to region suffices.\n\t\tRegion paragraphBox = (Region) cell.get().getNode();\n\t\tObservableList<Node> paragraphBoxChildren = paragraphBox.getChildrenUnmodifiable();\n\n\t\tif (paragraphBoxChildren.isEmpty())\n\t\t\treturn Collections.emptyList();\n\n\t\t// The text flow is always the first child of the box.\n\t\tRegion textFlow = (Region) paragraphBoxChildren.getFirst();\n\n\t\t// In the text flow, we want the first 'Text' child. This should be the first one with empty spaces.\n\t\tObservableList<Node> flowChildren = textFlow.getChildrenUnmodifiable();\n\t\treturn Unchecked.cast(flowChildren.stream()\n\t\t\t\t.filter(c -> c instanceof Text)\n\t\t\t\t.toList());\n\t}\n\n\t/**\n\t * Compute the width of blank text before non-blank text.\n\t *\n\t * @param paragraph\n\t * \t\tParagraph index to compute empty space (in pixels) to the first non-whitespace character.\n\t *\n\t * @return Pixels to first non-whitespace character.\n\t *\n\t * @see #getTextNodes(int)\n\t */\n\tpublic double computeWhitespacePrefixWidth(int paragraph) {\n\t\tList<Text> textNodes = getTextNodes(paragraph);\n\t\tdouble width = 0;\n\n\t\tfor (Text textNode : textNodes) {\n\t\t\tString text = textNode.getText();\n\t\t\tdouble boundWidth = textNode.getBoundsInLocal().getWidth();\n\t\t\tif (text.isBlank()) {\n\t\t\t\t// Texts that are blank are all whitespace, add it up.\n\t\t\t\twidth += boundWidth;\n\t\t\t} else {\n\t\t\t\t// Some texts have leading whitespace that we want to consider.\n\t\t\t\tint whitespacePrefix = StringUtil.getWhitespacePrefixLength(text);\n\t\t\t\tif (whitespacePrefix > 0) {\n\t\t\t\t\tdouble charWidth = boundWidth / StringUtil.getTabAdjustedLength(text);\n\t\t\t\t\twidth += charWidth * whitespacePrefix;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\treturn width;\n\t}\n\n\t/**\n\t * Compute the width of text until a specific character.\n\t *\n\t * @param paragraph\n\t * \t\tParagraph index to compute the width of.\n\t * @param character\n\t * \t\tCharacter index to compute the width until.\n\t *\n\t * @return Width of text until the character.\n\t */\n\tpublic double computeWidthUntilCharacter(int paragraph, int character) {\n\t\tList<Text> textNodes = getTextNodes(paragraph);\n\t\tdouble width = 0;\n\t\tint index = 0;\n\t\tText lastNode = null;\n\n\t\tfor (Text textNode : textNodes) {\n\t\t\tString text = textNode.getText();\n\t\t\tdouble boundWidth = textNode.getBoundsInLocal().getWidth();\n\n\t\t\tif (index + text.length() < character) {\n\t\t\t\tindex += text.length();\n\t\t\t\twidth += boundWidth;\n\t\t\t} else {\n\t\t\t\tdouble charWidth = boundWidth / StringUtil.getTabAdjustedLength(text);\n\t\t\t\twidth += charWidth * (character - index);\n\t\t\t\treturn width;\n\t\t\t}\n\n\t\t\tlastNode = textNode;\n\t\t}\n\n\t\t// we never reached the character\n\t\tif (index < character) {\n\t\t\tdouble charWidth;\n\t\t\tif (lastNode == null)\n\t\t\t\tcharWidth = 1.7;\n\t\t\telse\n\t\t\t\tcharWidth = lastNode.getBoundsInLocal().getWidth() / StringUtil.getTabAdjustedLength(lastNode.getText());\n\n\t\t\twidth += charWidth * (character - index);\n\t\t}\n\n\t\treturn width;\n\t}\n\n\t/**\n\t * @param line\n\t * \t\tParagraph index, 0-based.\n\t *\n\t * @return {@code true} when the paragraph is visible.\n\t */\n\tpublic boolean isParagraphVisible(int line) {\n\t\t// TODO: If we ever add paragraph folding back, we need to check those cases here and return false\n\n\t\t// We use the internal virtual flow because the provided methods call 'layout()' unnecessarily\n\t\t//  - firstVisibleParToAllParIndex()\n\t\t//  - lastVisibleParToAllParIndex()\n\t\t// It is very likely by the time of calling this that our text is already populated and laid out.\n\t\t// This gets called rather frequently so the constant layout requests contribute a massive waste of time.\n\t\t// If we use these methods from the internal 'VirtualFlow' we skip all that and the result is almost instant.\n\t\treturn line >= virtualFlow.getFirstVisibleIndex() && line <= virtualFlow.getLastVisibleIndex();\n\t}\n\n\t/**\n\t * @return {@link #getCodeArea() Code area's} horizontal scrollbar.\n\t */\n\t@Nonnull\n\tpublic ScrollBar getHorizontalScrollbar() {\n\t\treturn codeScrollWrapper.getHorizontalScrollbar();\n\t}\n\n\t/**\n\t * @return {@link #getCodeArea() Code area's} vertical scrollbar.\n\t */\n\t@Nonnull\n\tpublic ScrollBar getVerticalScrollbar() {\n\t\treturn codeScrollWrapper.getVerticalScrollbar();\n\t}\n\n\t/**\n\t * @param supplierService\n\t * \t\tExecutor service to run the supplier on.\n\t * @param fallback\n\t * \t\tFallback value when the supplier action encounters an exception.\n\t * @param supplier\n\t * \t\tValue supplier.\n\t * @param consumer\n\t * \t\tValue consumer, run on the JavaFX UI thread.\n\t * @param <T>\n\t * \t\tValue type.\n\t *\n\t * @return Future of consumer completion.\n\t */\n\t@Nonnull\n\tpublic <T> CompletableFuture<Void> schedule(@Nonnull ExecutorService supplierService, @Nullable T fallback,\n\t                                            @Nonnull Supplier<T> supplier, @Nonnull Consumer<T> consumer) {\n\t\tif (supplierService.isShutdown())\n\t\t\treturn CompletableFuture.completedFuture(null);\n\t\treturn CompletableFuture.supplyAsync(ThreadUtil.wrap(supplier, fallback), supplierService)\n\t\t\t\t.thenAcceptAsync(ThreadUtil.wrap(consumer), FxThreadUtil.executor());\n\t}\n\n\t/**\n\t * Some RichTextFX methods require styled documents, hence this helper for converting simple strings into those.\n\t *\n\t * @param text\n\t * \t\tText to wrap in a styled document with the default values.\n\t *\n\t * @return Text document of the requested text.\n\t */\n\t@Nonnull\n\tpublic StyledDocument<Collection<String>, String, Collection<String>> newText(@Nonnull String text) {\n\t\treturn ReadOnlyStyledDocument.fromString(text, codeArea.getInitialParagraphStyle(),\n\t\t\t\tcodeArea.getInitialTextStyle(), codeArea.getSegOps());\n\t}\n\n\t/**\n\t * Handles newline events. Inserts a new line, but matches the indentation level of the previous line.\n\t *\n\t * @param event\n\t * \t\tKey event where {@link KeyCode#ENTER} was pressed.\n\t */\n\tprivate void handleNewline(@Nonnull KeyEvent event) {\n\t\t// Consume the event so the normal 'enter' behavior is skipped.\n\t\tevent.consume();\n\n\t\t// Abort if handling tab completion.\n\t\t// Holding shift bypasses tab completion.\n\t\tboolean shiftMask = event.isShiftDown();\n\t\tif (!shiftMask && handleTabCompletion(event))\n\t\t\treturn;\n\n\t\t// If there is selected contents, newline should replace it.\n\t\tIndexRange selection = codeArea.getSelection();\n\t\tif (selection.getLength() > 0) {\n\t\t\tcodeArea.replace(selection.getStart(), selection.getEnd(), newText(\"\\n\"));\n\t\t\treturn;\n\t\t}\n\n\t\t// Compute matching indentation level.\n\t\tint paragraph = codeArea.getCurrentParagraph();\n\t\tString paragraphContents = codeArea.getParagraph(paragraph).getText();\n\t\tint indent = 0;\n\t\tfor (; indent < paragraphContents.length(); indent++) {\n\t\t\tchar c = paragraphContents.charAt(indent);\n\t\t\tif (c != ' ' && c != '\\t') break;\n\t\t}\n\n\t\t// Padding to insert in into the next line.\n\t\tString padding = paragraphContents.substring(0, indent);\n\n\t\t// For open brackets we'd like to do an extra level of indentation\n\t\t// and also complete the brace for the user.\n\t\tchar closingBrace = '\\0';\n\t\tfor (int i = paragraphContents.length() - 1; i >= indent; i--) {\n\t\t\tchar c = paragraphContents.charAt(i);\n\t\t\tif (c == '{') {\n\t\t\t\tclosingBrace = '}';\n\t\t\t\tpadding += '\\t';\n\t\t\t\tbreak;\n\t\t\t} else if (c == '[') {\n\t\t\t\tclosingBrace = ']';\n\t\t\t\tpadding += '\\t';\n\t\t\t\tbreak;\n\t\t\t} else if (c != ' ' && c != '\\t') {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Insert the indented newline + closing brace (if needed)\n\t\tint caret = codeArea.getCaretPosition();\n\t\tif (closingBrace == '\\0' || !recentKeys.contains(KeyCode.OPEN_BRACKET)) {\n\t\t\tcodeArea.insertText(caret, '\\n' + padding);\n\t\t} else {\n\t\t\t// Insert the '}' or ']' and move the caret between\n\t\t\tcodeArea.insertText(caret, '\\n' + padding + '\\n' + StringUtil.limit(padding, padding.length() - 1) + closingBrace);\n\t\t\tcodeArea.moveTo(caret + padding.length() + 1);\n\t\t}\n\n\t\t// Clear recent keys so that we reset the state of tracking for the open-bracket key.\n\t\trecentKeys.clear();\n\t}\n\n\t/**\n\t * Handles tab events.\n\t *\n\t * @param event\n\t * \t\tKey event where {@link KeyCode#TAB} was pressed.\n\t */\n\tprivate void handleTab(@Nonnull KeyEvent event) {\n\t\tIndexRange selection = codeArea.getSelection();\n\n\t\t// Skip if selection is empty (unless doing shift-tab for unindentation)\n\t\tif (selection.getLength() == 0 && !event.isShiftDown()) {\n\t\t\t// Abort if handling tab completion.\n\t\t\tif (handleTabCompletion(event))\n\t\t\t\tevent.consume();\n\n\t\t\t// Skip tab handling below.\n\t\t\treturn;\n\t\t}\n\n\t\t// Consume the event so the tab key does not get handled (no insertion of \\t)\n\t\tevent.consume();\n\n\t\t// Detailed selection info.\n\t\tTwoDimensional.Position selectionStart2D = codeArea.offsetToPosition(selection.getStart(), TwoDimensional.Bias.Forward);\n\t\tTwoDimensional.Position selectionEnd2D = codeArea.offsetToPosition(selection.getEnd(), TwoDimensional.Bias.Forward);\n\t\tboolean isMultiLine = selectionStart2D.getMajor() != selectionEnd2D.getMajor();\n\n\t\t// Check if text selection is multi-line\n\t\tboolean doShift = false;\n\t\tif (isMultiLine) {\n\t\t\t// Insert a '\\t' before the first non-whitespace character of all selected paragaphs.\n\t\t\tint start = selection.getStart();\n\t\t\tint end = selection.getEnd();\n\t\t\tint startParagraph = codeArea.offsetToPosition(start, TwoDimensional.Bias.Forward).getMajor();\n\t\t\tint endParagraph = codeArea.offsetToPosition(end, TwoDimensional.Bias.Backward).getMajor();\n\t\t\tfor (int i = startParagraph; i <= endParagraph; i++) {\n\t\t\t\t// Not ideal as it counts as multiple actions (not undo-able in one go) but good enough for now.\n\t\t\t\tif (event.isShiftDown()) {\n\t\t\t\t\tdoShift = unindent(i);\n\t\t\t\t} else {\n\t\t\t\t\tdoShift = true;\n\t\t\t\t\tindent(i);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Insert a '\\t' before the first non-whitespace character of the paragraph.\n\t\t\tint paragraph = codeArea.getCurrentParagraph();\n\t\t\tif (event.isShiftDown()) {\n\t\t\t\tdoShift = unindent(paragraph);\n\t\t\t} else {\n\t\t\t\tdoShift = true;\n\t\t\t\tindent(paragraph);\n\t\t\t}\n\t\t}\n\n\t\t// Re-select the original text.\n\t\tif (doShift)\n\t\t\tcodeArea.selectRange(selectionStart2D.getMajor(), selectionStart2D.getMinor() + 1,\n\t\t\t\t\tselectionEnd2D.getMajor(), selectionEnd2D.getMinor() + 1);\n\t}\n\n\t/**\n\t * Handle tab completion <i>(completion also works with enter key)</i>\n\t *\n\t * @param event\n\t * \t\tKey event where {@link KeyCode#TAB} or {@link KeyCode#ENTER} was pressed.\n\t *\n\t * @return {@code true} when a completion was made.\n\t */\n\tprivate boolean handleTabCompletion(@Nonnull KeyEvent event) {\n\t\treturn tabCompleter != null && tabCompleter.requestCompletion(event);\n\t}\n\n\t/**\n\t * Handles re-scrolling to the same location after the text document has been updated.\n\t *\n\t * @see #setText(String)\n\t */\n\tprivate class ScrollReset {\n\t\tprivate final int firstIndex;\n\n\t\tScrollReset(int firstIndex) {\n\t\t\tthis.firstIndex = firstIndex;\n\t\t}\n\n\t\tprivate void changed(@Nonnull PlainTextChange change) {\n\t\t\tshowParagraphAtTop(firstIndex);\n\t\t}\n\t}\n\n\t/**\n\t * Handles re-positioning the caret to the same location after the text document has been updated.\n\t *\n\t * @see #setText(String)\n\t */\n\tprivate class CaretReset {\n\t\tprivate final int pos;\n\n\t\tCaretReset(int pos) {\n\t\t\tthis.pos = pos;\n\t\t}\n\n\t\tprivate void changed(@Nonnull PlainTextChange change) {\n\t\t\tcodeArea.moveTo(Math.min(codeArea.getLength(), pos));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/EditorComponent.java",
    "content": "package software.coley.recaf.ui.control.richtext;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Outline for all components that can be added to an {@link Editor}.\n *\n * @author Matt Coley\n */\npublic interface EditorComponent {\n\t/**\n\t * Called when the component is installed into the given editor.\n\t *\n\t * @param editor\n\t * \t\tEditor installed to.\n\t */\n\tvoid install(@Nonnull Editor editor);\n\n\t/**\n\t * Called when the component is removed from the given editor.\n\t *\n\t * @param editor\n\t * \t\tEditor removed from.\n\t */\n\tvoid uninstall(@Nonnull Editor editor);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/SafeCodeArea.java",
    "content": "package software.coley.recaf.ui.control.richtext;\n\nimport org.fxmisc.richtext.CharacterHit;\nimport org.fxmisc.richtext.CodeArea;\n\n/**\n * Code area with additional parameter checks for some common edge cases.\n *\n * @author Matt Coley\n */\npublic class SafeCodeArea extends CodeArea {\n\t@Override\n\tpublic Position offsetToPosition(int charOffset, Bias bias) {\n\t\t// Covering out-of-bounds offset cases, which otherwise crash in the base impl.\n\t\tif (charOffset <= 0)\n\t\t\treturn position(0, 0);\n\t\telse if (charOffset >= getLength()) {\n\t\t\tint paragraph = getParagraphs().size() - 1;\n\t\t\treturn position(paragraph, getParagraphLength(paragraph));\n\t\t}\n\n\t\treturn super.offsetToPosition(charOffset, bias);\n\t}\n\n\t@Override\n\tpublic CharacterHit hit(double x, double y) {\n\t\ttry {\n\t\t\t// Possible race condition of a virtual cell not being made 'present' yet\n\t\t\t// so the virtual-flow hit-test throws a NoSuchElementException.\n\t\t\treturn super.hit(x, y);\n\t\t} catch (Throwable t) {\n\t\t\t// There's not a great way to recover, but 0 is always a safe value.\n\t\t\treturn CharacterHit.insertionAt(0);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void moveTo(int pos) {\n\t\tif (pos >= 0 && pos <= getLength())\n\t\t\tsuper.moveTo(pos);\n\t}\n\n\t@Override\n\tpublic void moveTo(int paragraphIndex, int columnIndex) {\n\t\tif (paragraphIndex >= 0 && paragraphIndex < getParagraphs().size())\n\t\t\tsuper.moveTo(paragraphIndex, columnIndex);\n\t}\n\n\t@Override\n\tpublic void moveTo(int pos, SelectionPolicy selectionPolicy) {\n\t\tif (pos >= 0 && pos <= getLength())\n\t\t\tsuper.moveTo(pos, selectionPolicy);\n\t}\n\n\t@Override\n\tpublic void moveTo(int paragraphIndex, int columnIndex, SelectionPolicy selectionPolicy) {\n\t\tif (paragraphIndex >= 0 && paragraphIndex < getParagraphs().size())\n\t\t\tsuper.moveTo(paragraphIndex, columnIndex, selectionPolicy);\n\t}\n\n\t@Override\n\tpublic void selectRange(int anchor, int caretPosition) {\n\t\tif (anchor >= 0 && anchor <= getLength() && caretPosition >= 0 && caretPosition <= getLength())\n\t\t\tsuper.selectRange(anchor, caretPosition);\n\t}\n\n\t@Override\n\tpublic void selectRange(int anchorParagraph, int anchorColumn, int caretPositionParagraph, int caretPositionColumn) {\n\t\tif (anchorParagraph >= 0 && anchorParagraph < getParagraphs().size() &&\n\t\t\t\tcaretPositionParagraph >= 0 && caretPositionParagraph < getParagraphs().size())\n\t\t\tsuper.selectRange(anchorParagraph, anchorColumn, caretPositionParagraph, caretPositionColumn);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/ScrollbarPaddingUtil.java",
    "content": "package software.coley.recaf.ui.control.richtext;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.geometry.Insets;\nimport javafx.scene.Node;\nimport javafx.scene.layout.StackPane;\nimport software.coley.recaf.ui.pane.editing.ProblemOverlay;\nimport software.coley.recaf.ui.pane.editing.AbstractDecompilerPaneConfigurator;\n\n/**\n * Used by {@link Node} values to be shown on an editor's {@link Editor#getPrimaryStack()}.\n * If a node is shown on the right, the padding will need to be adjusted when scrollbars are visible.\n *\n * @author Matt Coley\n * @see ProblemOverlay\n * @see AbstractDecompilerPaneConfigurator\n */\npublic class ScrollbarPaddingUtil {\n\t/**\n\t * When the {@link Editor#getVerticalScrollbar()} is visible, our {@link StackPane#setMargin(Node, Insets)} will cause\n\t * us to overlap with it. This doesn't look great, so when it is visible we will shift a bit over to the left so that we\n\t * do not overlap.\n\t *\n\t * @param node\n\t * \t\tNode to update.\n\t * @param currentlyVisible\n\t * \t\tCurrent visibility state of the editor's vertical scrollbar.\n\t */\n\tpublic static void handleScrollbarVisibility(@Nonnull Node node, boolean currentlyVisible) {\n\t\tStackPane.setMargin(node, new Insets(7, currentlyVisible ? 14 : 7, 7, 7));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/bracket/BracketMatchGraphicFactory.java",
    "content": "package software.coley.recaf.ui.control.richtext.bracket;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Orientation;\nimport javafx.geometry.Pos;\nimport javafx.scene.DepthTest;\nimport javafx.scene.control.Separator;\nimport javafx.scene.layout.StackPane;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.linegraphics.AbstractLineGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.linegraphics.LineContainer;\n\n/**\n * Graphic factory that adds a line indicator to matched lines containing the\n * {@link SelectedBracketTracking#getRange() current bracket pair}.\n *\n * @author Matt Coley\n * @see SelectedBracketTracking\n */\npublic class BracketMatchGraphicFactory extends AbstractLineGraphicFactory {\n\tprivate Editor editor;\n\n\t/**\n\t * New graphic factory.\n\t */\n\tpublic BracketMatchGraphicFactory() {\n\t\tsuper(P_BRACKET_MATCH);\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tthis.editor = editor;\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tthis.editor = null;\n\t}\n\n\n\t@Override\n\tpublic void apply(@Nonnull LineContainer container, int paragraph) {\n\t\tSelectedBracketTracking selectedBracketTracking = editor.getSelectedBracketTracking();\n\n\t\t// Always null if no bracket tracking is registered for the editor.\n\t\tif (selectedBracketTracking == null) return;\n\n\t\t// Add brace line for selected.\n\t\tif (selectedBracketTracking.isSelectedParagraph(paragraph)) {\n\t\t\tSelectedLineSeparator separator = new SelectedLineSeparator();\n\t\t\tStackPane.setAlignment(separator, Pos.CENTER_RIGHT);\n\t\t\tcontainer.addTopLayer(separator);\n\t\t}\n\t}\n\n\tstatic class SelectedLineSeparator extends Separator {\n\t\tprivate SelectedLineSeparator() {\n\t\t\tsuper(Orientation.VERTICAL);\n\t\t\tgetStyleClass().add(\"matched-brace-line\");\n\t\t\tsetPadding(new Insets(0));\n\t\t}\n\n\t\t@Override\n\t\tprotected void setWidth(double value) {\n\t\t\tsuper.setWidth(value);\n\t\t\t// Keeps the vertical separator 'inline' with the parent container's edge\n\t\t\tsetTranslateX(-value);\n\t\t}\n\n\t\t@Override\n\t\tprotected void setHeight(double value) {\n\t\t\tsuper.setHeight(value + 1);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/bracket/SelectedBracketTracking.java",
    "content": "package software.coley.recaf.ui.control.richtext.bracket;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.fxmisc.richtext.CodeArea;\nimport org.fxmisc.richtext.model.TwoDimensional;\nimport org.reactfx.Change;\nimport org.reactfx.EventStream;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.Closing;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.EditorComponent;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.IntRange;\nimport software.coley.recaf.util.NumberUtil;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\n\nimport java.time.Duration;\nimport java.util.Objects;\nimport java.util.concurrent.ExecutorService;\nimport java.util.function.Consumer;\n\n/**\n * Bracket tracking component for {@link Editor}.\n * Records matching bracket pairs the user is interacting with.\n *\n * @author Matt Coley\n * @see BracketMatchGraphicFactory Adds a line indicator to an {@link Editor} for lines covering the {@link #getRange() range}.\n * @see Editor#setSelectedBracketTracking(SelectedBracketTracking) Call to install into an {@link Editor}.\n */\npublic class SelectedBracketTracking implements EditorComponent, Closing, Consumer<Change<Integer>> {\n\tprivate static final Logger logger = Logging.get(SelectedBracketTracking.class);\n\tprivate final ExecutorService service = ThreadPoolFactory.newSingleThreadExecutor(\"brackets\");\n\tprivate EventStream<Change<Integer>> lastEventStream;\n\tprivate CodeArea codeArea;\n\tprivate Editor editor;\n\tprivate IntRange range;\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tthis.editor = editor;\n\t\tcodeArea = editor.getCodeArea();\n\n\t\t// Register event observer with short delay so rapid event fires only\n\t\t// consume the most recent event instance.\n\t\tlastEventStream = editor.getCaretPosEventStream()\n\t\t\t\t.successionEnds(Duration.ofMillis(Editor.SHORT_DELAY_MS));\n\t\tlastEventStream.addObserver(this);\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tif (codeArea != null) {\n\t\t\tlastEventStream.removeObserver(this);\n\t\t\tlastEventStream = null;\n\t\t\tcodeArea = null;\n\t\t}\n\t}\n\n\t@Override\n\tpublic void accept(Change<Integer> change) {\n\t\t// Submit task to check for open/close pair\n\t\tif (!service.isShutdown())\n\t\t\tservice.submit(() -> setRange(scanAround(change.getNewValue())));\n\t}\n\n\t/**\n\t * @return Current range of bracket pair. {@code null} when no pair is selected currently.\n\t */\n\t@Nullable\n\tpublic IntRange getRange() {\n\t\treturn range;\n\t}\n\n\t/**\n\t * Assigns the current selected bracket pair range.\n\t * When the new value differs from the last value, the linked editor will redraw its paragraph graphics.\n\t *\n\t * @param newRange\n\t * \t\tNew range to assign. {@code null} to remove selected bracket pair range.\n\t */\n\tprivate void setRange(@Nullable IntRange newRange) {\n\t\tIntRange lastRange = range;\n\t\trange = newRange;\n\n\t\t// Redraw paragraphs visible when the range is modified.\n\t\tif (!Objects.equals(lastRange, newRange))\n\t\t\tFxThreadUtil.run(() -> editor.redrawParagraphGraphics());\n\t}\n\n\t/**\n\t * @param line\n\t * \t\tLine number.\n\t *\n\t * @return {@code true} when the line belongs to the {@link #getRange() selected bracket pair range}.\n\t */\n\tpublic boolean isSelectedLine(int line) {\n\t\treturn isSelectedParagraph(line - 1);\n\t}\n\n\t/**\n\t * @param paragraph\n\t * \t\tParagraph index, which is {@code line - 1}.\n\t *\n\t * @return {@code true} when the line belongs to the {@link #getRange() selected bracket pair range}.\n\t */\n\tpublic boolean isSelectedParagraph(int paragraph) {\n\t\tif (range == null) return false;\n\n\t\ttry {\n\t\t\tint length = editor.getTextLength();\n\t\t\tint start = NumberUtil.intClamp(range.start(), 0, length - 1);\n\t\t\tint end = NumberUtil.intClamp(range.end(), 0, length - 1);\n\n\t\t\t// Check paragraph beyond start range.\n\t\t\tTwoDimensional.Position startPos = codeArea.offsetToPosition(start, TwoDimensional.Bias.Backward);\n\t\t\tint startParagraph = startPos.getMajor();\n\t\t\tif (paragraph < startParagraph) return false;\n\n\t\t\t// Check paragraph before end range.\n\t\t\tTwoDimensional.Position endPos = codeArea.offsetToPosition(end, TwoDimensional.Bias.Forward);\n\t\t\tint endParagraph = endPos.getMajor();\n\t\t\treturn paragraph <= endParagraph;\n\t\t} catch (Exception ex) {\n\t\t\t// Some potential edge cases in 'offsetToPosition' we'll want to look out for.\n\t\t\tlogger.error(\"Failed to map selected bracket range to paragraph for line {}\", paragraph + 1, ex);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprotected IntRange scanAround(int pos) {\n\t\tint len = codeArea.getLength();\n\n\t\t// Check if character adjacent to the caret position is an open or closing bracket.\n\t\tIntRange found = null;\n\t\tif (pos > 0)\n\t\t\tfound = scanAt(pos - 1);\n\t\tif (found == null && pos < len)\n\t\t\tfound = scanAt(pos);\n\t\treturn found;\n\t}\n\n\t@Nullable\n\tprivate IntRange scanAt(int pos) {\n\t\tString text = codeArea.getText();\n\t\tif (pos < 0 || pos >= text.length())\n\t\t\treturn null;\n\n\t\tchar c = text.charAt(pos);\n\t\tchar openChar;\n\t\tchar closeChar;\n\t\tboolean forward;\n\t\tif (c == '{' || c == '}') {\n\t\t\topenChar = '{';\n\t\t\tcloseChar = '}';\n\t\t\tforward = c == '{';\n\t\t} else if (c == '[' || c == ']') {\n\t\t\topenChar = '[';\n\t\t\tcloseChar = ']';\n\t\t\tforward = c == '[';\n\t\t} else if (c == '(' || c == ')') {\n\t\t\topenChar = '(';\n\t\t\tcloseChar = ')';\n\t\t\tforward = c == '(';\n\t\t} else {\n\t\t\t// Not a supported bracket pair\n\t\t\treturn null;\n\t\t}\n\n\t\treturn forward ? scanForwards(text, pos, openChar, closeChar) :\n\t\t\t\tscanBackwards(text, pos, openChar, closeChar);\n\t}\n\n\tprivate IntRange scanForwards(String text, int pos, char openChar, char closeChar) {\n\t\tint start = pos;\n\t\tint end;\n\t\tint open;\n\t\tint close;\n\t\tint balance = 1;\n\t\tdo {\n\t\t\topen = text.indexOf(openChar, pos + 1);\n\t\t\tclose = text.indexOf(closeChar, pos + 1);\n\n\t\t\tif (open != -1 && open < close) {\n\t\t\t\tbalance++;\n\t\t\t\tpos = open;\n\t\t\t} else if (close != -1 && close > pos) {\n\t\t\t\tbalance--;\n\t\t\t\tpos = close;\n\n\t\t\t\t// Balance solved\n\t\t\t\tif (balance == 0) {\n\t\t\t\t\tend = pos;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Neither an open nor close character was found.\n\t\t\t\t// The balance is not resolved, no match.\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} while (true);\n\n\t\t// End found? Create range.\n\t\treturn (end > 0) ? new IntRange(start, end) : null;\n\t}\n\n\tprivate IntRange scanBackwards(String text, int pos, char openChar, char closeChar) {\n\t\tint start = -1;\n\t\tint end = pos;\n\t\tint balance = 1;\n\t\tint open;\n\t\tint close;\n\t\tboolean remaining = true;\n\t\tdo {\n\t\t\topen = text.lastIndexOf(openChar, pos - 1);\n\t\t\tclose = text.lastIndexOf(closeChar, pos - 1);\n\n\t\t\tif (open > close)\n\t\t\t\tbalance--;\n\t\t\telse if (close != -1 && close < pos)\n\t\t\t\tbalance++;\n\t\t\telse\n\t\t\t\treturn null;\n\n\t\t\t// Balance solved\n\t\t\tif (balance == 0) {\n\t\t\t\tstart = open;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Not solved, continue scanning by setting the cut-off for backwards scan\n\t\t\t// to the next closest item of the open/close character matches.\n\t\t\tint next = Math.max(open, close);\n\n\t\t\t// If the next position is the same, or beyond the current position\n\t\t\t// then that means we have exhausted all matches, and the balance is not solved.\n\t\t\tif (next >= pos)\n\t\t\t\tremaining = false;\n\t\t\tpos = next;\n\t\t} while (remaining);\n\n\t\t// Start found? Create range.\n\t\treturn (start >= 0) ? new IntRange(start, end) : null;\n\t}\n\n\t@Override\n\tpublic void close() {\n\t\tif (!service.isShutdown())\n\t\t\tservice.shutdownNow();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/inheritance/Inheritance.java",
    "content": "package software.coley.recaf.ui.control.richtext.inheritance;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.path.ClassMemberPathNode;\n\n/**\n * Outline of an inheritance item for a method declaration.\n * Will be either a child <i>(where the annotated method is overridden)</i>\n * or a parent <i>(where the annotated method is the override)</i>.\n *\n * @author Matt Coley\n */\npublic sealed interface Inheritance extends Comparable<Inheritance> {\n\t/**\n\t * @return Line number of the inheritance marker.\n\t */\n\tint line();\n\n\t/**\n\t * @return Path to the class member the inheritance applies to.\n\t */\n\tClassMemberPathNode path();\n\n\t/**\n\t * @param newLine\n\t * \t\tNew line for inheritance.\n\t *\n\t * @return Copy of the current inheritance, but with the line number modified.\n\t */\n\tInheritance withLine(int newLine);\n\n\t@Override\n\tdefault int compareTo(@Nonnull Inheritance o) {\n\t\tint cmp = Integer.compare(line(), o.line());\n\t\tif (cmp == 0) {\n\t\t\tif (this instanceof Child && o instanceof Parent)\n\t\t\t\tcmp = -1;\n\t\t\telse if (this instanceof Parent && o instanceof Child)\n\t\t\t\tcmp = 1;\n\t\t}\n\t\treturn cmp;\n\t}\n\n\t/**\n\t * Inheritance item representing a child override method.\n\t */\n\trecord Child(int line, @Nonnull ClassMemberPathNode path) implements Inheritance {\n\t\t@Override\n\t\tpublic Inheritance withLine(int newLine) {\n\t\t\treturn new Child(newLine, path);\n\t\t}\n\n\t\t@Override\n\t\tpublic int compareTo(@Nonnull Inheritance o) {\n\t\t\tint cmp = Inheritance.super.compareTo(o);\n\t\t\tif (cmp == 0 && o instanceof Child otherChild)\n\t\t\t\tcmp = path.localCompare(otherChild.path);\n\t\t\treturn cmp;\n\t\t}\n\t}\n\n\t/**\n\t * Inheritance item representing an overridden parent method.\n\t */\n\trecord Parent(int line, @Nonnull ClassMemberPathNode path) implements Inheritance {\n\t\t@Override\n\t\tpublic Inheritance withLine(int newLine) {\n\t\t\treturn new Parent(newLine, path);\n\t\t}\n\n\t\t@Override\n\t\tpublic int compareTo(@Nonnull Inheritance o) {\n\t\t\tint cmp = Inheritance.super.compareTo(o);\n\t\t\tif (cmp == 0 && o instanceof Child otherChild)\n\t\t\t\tcmp = path.localCompare(otherChild.path);\n\t\t\treturn cmp;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/inheritance/InheritanceGutterGraphicFactory.java",
    "content": "package software.coley.recaf.ui.control.richtext.inheritance;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Cursor;\nimport javafx.scene.Node;\nimport javafx.scene.control.ContextMenu;\nimport javafx.scene.control.MenuItem;\nimport javafx.scene.layout.HBox;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.IncompletePathException;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.ui.control.ActionMenuItem;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.linegraphics.AbstractLineGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.linegraphics.LineContainer;\nimport software.coley.recaf.ui.control.richtext.linegraphics.LineGraphicFactory;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.SVG;\n\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * Graphic factory that adds overlays to line graphics indicating the inheritance status of declared methods on the line.\n *\n * @author Matt Coley\n * @see InheritanceTracking\n */\npublic class InheritanceGutterGraphicFactory extends AbstractLineGraphicFactory {\n\tprivate static final Logger logger = Logging.get(InheritanceGutterGraphicFactory.class);\n\tprivate final CellConfigurationService configurationService;\n\tprivate final Actions actions;\n\tprivate Editor editor;\n\n\t/**\n\t * New graphic factory.\n\t *\n\t * @param configurationService\n\t * \t\tCell configuration service for fetching method graphics and such.\n\t * @param actions\n\t * \t\tActions for navigation when clicking on inheritance items.\n\t */\n\tpublic InheritanceGutterGraphicFactory(@Nonnull CellConfigurationService configurationService,\n\t                                       @Nonnull Actions actions) {\n\t\tsuper(LineGraphicFactory.P_LINE_INHERITANCES);\n\n\t\tthis.configurationService = configurationService;\n\t\tthis.actions = actions;\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tthis.editor = editor;\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tthis.editor = null;\n\t}\n\n\t@Override\n\tpublic void apply(@Nonnull LineContainer container, int paragraph) {\n\t\tInheritanceTracking inheritanceTracking = (InheritanceTracking) editor.getComponent(InheritanceTracking.COMPONENT_KEY);\n\n\t\t// Always null if no bracket tracking is registered for the editor.\n\t\tif (inheritanceTracking == null)\n\t\t\treturn;\n\n\t\t// Add problem graphic overlay to lines with problems.\n\t\tint line = paragraph + 1;\n\n\t\t// Skip if there are no inheritance items at all.\n\t\tint directions = (int) inheritanceTracking.getAllItems().stream().map(Inheritance::getClass).distinct().count();\n\t\tif (directions == 0)\n\t\t\treturn;\n\n\t\t// Separate by type.\n\t\tList<Inheritance> items = inheritanceTracking.getItemsOnLine(line);\n\t\tList<Inheritance.Parent> parents = items.stream()\n\t\t\t\t.filter(Inheritance.Parent.class::isInstance)\n\t\t\t\t.map(Inheritance.Parent.class::cast).toList();\n\t\tList<Inheritance.Child> children = items.stream()\n\t\t\t\t.filter(Inheritance.Child.class::isInstance)\n\t\t\t\t.map(Inheritance.Child.class::cast).toList();\n\n\t\t// Wrapper box that will always be a given size based on how many directions of inheritance there are.\n\t\t// This ensures lines with no inheritance still have the same gutter width as lines that do.\n\t\tHBox box = new HBox();\n\t\tbox.setAlignment(Pos.CENTER_LEFT);\n\t\tbox.setSpacing(2);\n\t\tbox.setPadding(new Insets(0, 0, 0, 4));\n\t\tbox.setMinWidth(6 + directions * 16);\n\n\t\t// Add inheritance icons.\n\t\tif (!parents.isEmpty()) {\n\t\t\tboolean isInterface = Objects.requireNonNull(parents.getFirst().path().getParent()).getValue().hasInterfaceModifier();\n\t\t\tNode node = SVG.ofIconFile(isInterface ? SVG.METHOD_IMPLEMENTING : SVG.METHOD_OVERRIDING);\n\t\t\tnode.setCursor(Cursor.HAND);\n\t\t\tnode.setOnMousePressed(e -> {\n\t\t\t\tContextMenu menu = new ContextMenu();\n\t\t\t\tMenuItem title = new MenuItem();\n\t\t\t\ttitle.getStyleClass().addAll(Styles.TEXT_BOLD, Styles.BG_INSET);\n\t\t\t\ttitle.setDisable(true);\n\t\t\t\ttitle.textProperty().bind(Lang.getBinding(\"hierarchy.parents\"));\n\t\t\t\tmenu.getItems().add(title);\n\t\t\t\tmenu.setAutoHide(true);\n\t\t\t\tfor (Inheritance.Parent parent : parents) {\n\t\t\t\t\tClassMemberPathNode parentPath = parent.path();\n\t\t\t\t\tmenu.getItems().add(new ActionMenuItem(configurationService.textOf(Objects.requireNonNull(parentPath.getParent())), configurationService.graphicOf(parentPath), () -> {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tactions.gotoDeclaration(parentPath);\n\t\t\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\t\t\tlogger.warn(\"Failed to navigate to parent method: {}\", parentPath, ex);\n\t\t\t\t\t\t}\n\t\t\t\t\t}));\n\t\t\t\t}\n\t\t\t\tmenu.show(box, e.getScreenX(), e.getScreenY());\n\t\t\t});\n\t\t\tbox.getChildren().add(node);\n\t\t}\n\t\tif (!children.isEmpty()) {\n\t\t\tboolean isInterface = Objects.requireNonNull(children.getFirst().path().getParent()).getValue().hasInterfaceModifier();\n\t\t\tNode node = SVG.ofIconFile(isInterface ? SVG.METHOD_IMPLEMENTED : SVG.METHOD_OVERRIDDEN);\n\t\t\tnode.setCursor(Cursor.HAND);\n\t\t\tnode.setOnMousePressed(e -> {\n\t\t\t\tContextMenu menu = new ContextMenu();\n\t\t\t\tMenuItem title = new MenuItem();\n\t\t\t\ttitle.getStyleClass().addAll(Styles.TEXT_BOLD, Styles.BG_INSET);\n\t\t\t\ttitle.setDisable(true);\n\t\t\t\ttitle.textProperty().bind(Lang.getBinding(\"hierarchy.children\"));\n\t\t\t\tmenu.getItems().add(title);\n\t\t\t\tmenu.setAutoHide(true);\n\t\t\t\tfor (Inheritance.Child child : children) {\n\t\t\t\t\tClassMemberPathNode childPath = child.path();\n\t\t\t\t\tmenu.getItems().add(new ActionMenuItem(configurationService.textOf(Objects.requireNonNull(childPath.getParent())), configurationService.graphicOf(childPath), () -> {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tactions.gotoDeclaration(childPath);\n\t\t\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\t\t\tlogger.warn(\"Failed to navigate to child method: {}\", childPath, ex);\n\t\t\t\t\t\t}\n\t\t\t\t\t}));\n\t\t\t\t}\n\t\t\t\tmenu.show(box, e.getScreenX(), e.getScreenY());\n\t\t\t});\n\t\t\tbox.getChildren().add(node);\n\t\t}\n\n\t\tcontainer.addHorizontal(box);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/inheritance/InheritanceInvalidationListener.java",
    "content": "package software.coley.recaf.ui.control.richtext.inheritance;\n\nimport software.coley.recaf.behavior.PrioritySortable;\n\n/**\n * Listener to receive updates when any change occurs in a {@link InheritanceTracking}'s tracked inheritances map.\n *\n * @author Matt Coley\n */\npublic interface InheritanceInvalidationListener extends PrioritySortable {\n\t/**\n\t * Called when any changes to tracked inheritances occurs.\n\t */\n\tvoid onInheritanceInvalidation();\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/inheritance/InheritanceTracking.java",
    "content": "package software.coley.recaf.ui.control.richtext.inheritance;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.ui.control.richtext.AbstractLineItemTracking;\nimport software.coley.recaf.ui.control.richtext.Editor;\n\n/**\n * Tracking for method inheritance icons to display in an {@link Editor}.\n *\n * @author Matt Coley\n */\npublic class InheritanceTracking extends AbstractLineItemTracking<Inheritance, InheritanceInvalidationListener> {\n\t/** Key for {@link Editor#getComponent(java.lang.String)}. */\n\tpublic static final String COMPONENT_KEY = \"inheritance-tracking\";\n\n\t@Override\n\tprotected void notifyListeners(@Nonnull String failureMessage) {\n\t\tUnchecked.checkedForEach(listeners, InheritanceInvalidationListener::onInheritanceInvalidation,\n\t\t\t\t(listener, t) -> logger.error(failureMessage, t));\n\t}\n\n\t@Override\n\tprotected int getLine(@Nonnull Inheritance item) {\n\t\treturn item.line();\n\t}\n\n\t@Override\n\tprotected Inheritance withLine(@Nonnull Inheritance item, int newLine) {\n\t\treturn item.withLine(newLine);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/linegraphics/AbstractLineGraphicFactory.java",
    "content": "package software.coley.recaf.ui.control.richtext.linegraphics;\n\n/**\n * Base implementation of {@link LineGraphicFactory}.\n *\n * @author Matt Coley\n */\npublic abstract class AbstractLineGraphicFactory implements LineGraphicFactory {\n\tprivate final int priority;\n\n\t/**\n\t * @param priority\n\t * \t\tPriority dictating the order of graphics displayed in {@link RootLineGraphicFactory}.\n\t * \t\tSee {@link LineGraphicFactory} for constants.\n\t */\n\tprotected AbstractLineGraphicFactory(int priority) {\n\t\tthis.priority = priority;\n\t}\n\n\t@Override\n\tpublic int priority() {\n\t\treturn priority;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/linegraphics/AbstractTextBoundLineGraphicFactory.java",
    "content": "package software.coley.recaf.ui.control.richtext.linegraphics;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Pos;\nimport javafx.scene.layout.StackPane;\nimport software.coley.recaf.ui.control.VirtualizedScrollPaneWrapper;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.SceneUtils;\n\n/**\n * Base implementation of a {@link LineGraphicFactory} which has\n * displayed graphics offset to appear on top of the editor's contents.\n *\n * @author Justus Garbe\n */\npublic abstract class AbstractTextBoundLineGraphicFactory extends AbstractLineGraphicFactory {\n\tprotected final int containerHeight = 16; // Each line graphic region is only 16px tall\n\tprotected final int containerWidth = 16;\n\tprotected Editor editor;\n\n\t/**\n\t * @param priority\n\t * \t\tPriority dictating the order of graphics displayed in {@link RootLineGraphicFactory}.\n\t * \t\tSee {@link LineGraphicFactory} for constants.\n\t */\n\tprotected AbstractTextBoundLineGraphicFactory(int priority) {\n\t\tsuper(priority);\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tthis.editor = editor;\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tthis.editor = null;\n\t}\n\n\t@Override\n\tpublic void apply(@Nonnull LineContainer container, int paragraph) {\n\t\t// To keep the ordering of the line graphic factory priority we need to add the stack pane now\n\t\t// since the rest of the work is done async below. We want this to have zero width so that it doesn't\n\t\t// shit the editor around when the content becomes active/inactive.\n\t\tStackPane stack = new StackPane();\n\t\tstack.setManaged(false);\n\t\tstack.setPrefWidth(0);\n\t\tstack.setMouseTransparent(true);\n\t\tcontainer.addHorizontal(stack);\n\n\t\t// This delayed execution looks stupid because it is, however it is necessary.\n\t\t//\n\t\t// Any operation that requires knowledge of positioning and sizing cannot be\n\t\t// done on the same frame as the creation of the cell. Thus, we'll delay it by\n\t\t// one frame.\n\t\tFxThreadUtil.delayedRun(0, () -> {\n\t\t\tstack.setPrefSize(containerWidth, containerHeight);\n\t\t\tstack.setAlignment(Pos.CENTER_LEFT);\n\t\t\tSceneUtils.getParentOfTypeLater(container, VirtualizedScrollPaneWrapper.class).whenComplete((parentScroll, error) -> {\n\t\t\t\tObservableValue<? extends Number> translateX;\n\t\t\t\tif (parentScroll != null) {\n\t\t\t\t\ttranslateX = Bindings.add(container.widthProperty().subtract(containerHeight), parentScroll.horizontalScrollProperty().negate());\n\t\t\t\t} else {\n\t\t\t\t\t// Should never happen since the 'VirtualizedScrollPaneWrapper' is mandated internally by 'Editor'.\n\t\t\t\t\ttranslateX = container.widthProperty().subtract(containerHeight);\n\t\t\t\t}\n\t\t\t\tstack.translateXProperty().bind(translateX);\n\t\t\t});\n\n\t\t\tapply(stack, paragraph);\n\t\t});\n\t}\n\n\t/**\n\t * Apply the line graphic to the given pane,\n\t * the pane is always bound to the beginning of the line.\n\t *\n\t * @param pane\n\t * \t\tThe pane to apply the graphic to.\n\t * @param paragraph\n\t * \t\tThe paragraph index.\n\t */\n\tprotected abstract void apply(@Nonnull StackPane pane, int paragraph);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/linegraphics/LineContainer.java",
    "content": "package software.coley.recaf.ui.control.richtext.linegraphics;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\n\n/**\n * Container for adding sub-graphics per-line graphic.\n * Created in {@link RootLineGraphicFactory}.\n *\n * @author Matt Coley\n */\npublic class LineContainer extends StackPane {\n\tprivate static final int LINE_V_PADDING = 1;\n\tprivate static final int LINE_H_PADDING = 5;\n\tprivate static final Insets PADDING = new Insets(LINE_V_PADDING, LINE_H_PADDING, LINE_V_PADDING, LINE_H_PADDING);\n\tprivate final HBox box = new HBox();\n\n\t/**\n\t * Accessible only to local package.\n\t */\n\tLineContainer() {\n\t\tbox.setAlignment(Pos.CENTER_LEFT);\n\t\tbox.setPadding(PADDING);\n\t\tgetChildren().add(box);\n\t}\n\n\t/**\n\t * @param child\n\t * \t\tChild to add spanning horizontally in the container.\n\t * \t\tAppends to the left.\n\t */\n\tpublic void addHorizontal(@Nonnull Node child) {\n\t\tbox.getChildren().add(child);\n\t}\n\n\t/**\n\t * @param child\n\t * \t\tChild to add on top of the container.\n\t * \t\tThis refers to Z-indexing, not north-south verticality.\n\t */\n\tpublic void addTopLayer(@Nonnull Node child) {\n\t\tgetChildren().add(child);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/linegraphics/LineGraphicFactory.java",
    "content": "package software.coley.recaf.ui.control.richtext.linegraphics;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.Node;\nimport software.coley.recaf.ui.control.richtext.EditorComponent;\nimport software.coley.recaf.ui.control.richtext.bracket.BracketMatchGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemGutterGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemSquiggleGraphicFactory;\n\nimport java.util.function.IntFunction;\n\n/**\n * RichTextFX only uses {@link IntFunction} for graphics factories. We want to create our own system where\n * any number of graphic factories can be added, and the result will always be consistently displayed.\n * <br>\n * To facilitate this, we have this type which also is comparable to other instances of the same type.\n * Each graphic factory has a {@link #priority()} which defines its placement in the {@link RootLineGraphicFactory}.\n * Lower values appear first.\n *\n * @author Matt Coley\n * @see AbstractLineGraphicFactory Base implementation of this type.\n * @see RootLineGraphicFactory The root implementation which managed displaying other {@link LineGraphicFactory} instances in order.\n */\npublic interface LineGraphicFactory extends EditorComponent, Comparable<LineGraphicFactory> {\n\t/**\n\t * Priority for {@link LineNumberFactory}.\n\t */\n\tint P_LINE_NUMBERS = 0;\n\t/**\n\t * Priority for {@link ProblemGutterGraphicFactory}.\n\t */\n\tint P_LINE_PROBLEMS = 100;\n\t/**\n\t * Priority for {@link ProblemSquiggleGraphicFactory}.\n\t */\n\tint P_LINE_PROBLEM_SQUIGGLES = 101;\n\t/**\n\t * Priority for {@link ProblemSquiggleGraphicFactory}.\n\t */\n\tint P_LINE_INHERITANCES = 200;\n\t/**\n\t * Priority for {@link BracketMatchGraphicFactory}.\n\t */\n\tint P_BRACKET_MATCH = 1000;\n\n\t/**\n\t * @return Order priority for sorting in {@link RootLineGraphicFactory}. Lower values appear first.\n\t */\n\tint priority();\n\n\t/**\n\t * @param container\n\t * \t\tContainer to add nodes to if a graphic needs to be generated.\n\t * \t\tUse {@link LineContainer#addHorizontal(Node)} and {@link LineContainer#addTopLayer(Node)}.\n\t * @param paragraph\n\t * \t\tCurrent paragraph index.\n\t * \t\tLine would be {@code paragraph + 1}.\n\t */\n\tvoid apply(@Nonnull LineContainer container, int paragraph);\n\n\t@Override\n\tdefault int compareTo(@Nonnull LineGraphicFactory o) {\n\t\treturn Integer.compare(priority(), o.priority());\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/linegraphics/LineNumberFactory.java",
    "content": "package software.coley.recaf.ui.control.richtext.linegraphics;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport org.fxmisc.richtext.CodeArea;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.util.StringUtil;\n\n/**\n * Graphic factory to draw line numbers.\n *\n * @author Matt Coley\n */\npublic class LineNumberFactory extends AbstractLineGraphicFactory {\n\tprivate CodeArea codeArea;\n\n\t/**\n\t * New line number factory.\n\t */\n\tpublic LineNumberFactory() {\n\t\tsuper(P_LINE_NUMBERS);\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tcodeArea = editor.getCodeArea();\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tcodeArea = null;\n\t}\n\n\t@Override\n\tpublic void apply(@Nonnull LineContainer container, int paragraph) {\n\t\tif (codeArea == null) return;\n\n\t\tLabel label = new Label(format(paragraph + 1, computeDigits(codeArea.getParagraphs().size())));\n\t\tHBox.setHgrow(label, Priority.ALWAYS);\n\t\tcontainer.addHorizontal(label);\n\t}\n\n\t@Nonnull\n\tprivate static String format(int line, int digits) {\n\t\treturn String.format(StringUtil.fillLeft(digits, \" \", String.valueOf(line)));\n\t}\n\n\tprivate static int computeDigits(int size) {\n\t\treturn (int) Math.floor(Math.log10(size)) + 1;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/linegraphics/RootLineGraphicFactory.java",
    "content": "package software.coley.recaf.ui.control.richtext.linegraphics;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.Node;\nimport javafx.scene.layout.BorderPane;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.bracket.BracketMatchGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemGutterGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemSquiggleGraphicFactory;\n\nimport java.util.SortedSet;\nimport java.util.TreeSet;\nimport java.util.function.IntFunction;\n\n/**\n * Graphic factory for {@link Editor}.\n * <br>\n * Handles registration and display of additional {@link LineGraphicFactory} instances in a consistent manner.\n *\n * @author Matt Coley\n */\npublic class RootLineGraphicFactory extends AbstractLineGraphicFactory implements IntFunction<Node> {\n\tprivate final SortedSet<LineGraphicFactory> factories = new TreeSet<>();\n\tprivate final Editor editor;\n\n\t/**\n\t * @param editor\n\t * \t\tBase editor to work off of.\n\t */\n\tpublic RootLineGraphicFactory(@Nonnull Editor editor) {\n\t\tsuper(-1);\n\t\tthis.editor = editor;\n\t\taddLineGraphicFactory(new LineNumberFactory());\n\t}\n\n\t/**\n\t * Adds the default graphic factories used for editor displays when the content is code.\n\t */\n\tpublic void addDefaultCodeGraphicFactories() {\n\t\taddLineGraphicFactories(\n\t\t\t\tnew BracketMatchGraphicFactory(),\n\t\t\t\tnew ProblemGutterGraphicFactory(),\n\t\t\t\tnew ProblemSquiggleGraphicFactory()\n\t\t);\n\t}\n\n\t/**\n\t * Adds the given factories.\n\t *\n\t * @param factories\n\t * \t\tGraphic factories to add.\n\t */\n\tpublic void addLineGraphicFactories(LineGraphicFactory... factories) {\n\t\tfor (LineGraphicFactory factory : factories)\n\t\t\taddLineGraphicFactory(factory);\n\t}\n\n\t/**\n\t * Adds the given factory.\n\t *\n\t * @param factory\n\t * \t\tGraphic factory to add.\n\t */\n\tpublic void addLineGraphicFactory(@Nonnull LineGraphicFactory factory) {\n\t\tfactories.add(factory);\n\t\tfactory.install(editor);\n\t}\n\n\t/**\n\t * @param factory\n\t * \t\tGraphic factory to remove.\n\t *\n\t * @return {@code true} when removed. {@code false} when did not exist.\n\t */\n\tpublic boolean removeLineGraphicFactory(@Nonnull LineGraphicFactory factory) {\n\t\tif (factories.remove(factory)) {\n\t\t\tfactory.uninstall(editor);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void apply(@Nonnull LineContainer container, int paragraph) {\n\t\t// no-op, this method is implemented by line-graphic factory children.\n\t}\n\n\t@Override\n\tpublic Node apply(int paragraph) {\n\t\t// Add all sub-factories in sorted order.\n\t\tLineContainer lineContainer = new LineContainer();\n\t\tfor (LineGraphicFactory factory : factories)\n\t\t\tfactory.apply(lineContainer, paragraph);\n\n\t\t// Wrap so the padding of the HBox expands the space of the 'lineno'.\n\t\tBorderPane wrapper = new BorderPane(lineContainer);\n\t\twrapper.getStyleClass().add(\"lineno\");\n\t\t// Note: The dimensions you will see on 'wrapper' do not appear to map its effective bounds.\n\t\t// We used to set the cursor to DEFAULT here, but this led to the editor's TEXT cursor being\n\t\t// replaced even when the visible bounds of this wrapper were not intersected with, and all\n\t\t// sub-nodes of the wrapper were marked as mouse-transparent.\n\t\t// The solution for now seems to just not specify a cursor.\n\t\treturn wrapper;\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tthrow new IllegalArgumentException(\"The root line graphic factory should never be uninstalled!\");\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/Problem.java",
    "content": "package software.coley.recaf.ui.control.richtext.problem;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.services.compile.CompilerDiagnostic;\n\n/**\n * Outline of a problem.\n *\n * @param line\n * \t\tLine the problem occurred on.\n * @param column\n * \t\tColumn in the line the problem occurred on.\n * \t\tMay be negative if position information is not available.\n * @param length\n * \t\tLength beyond the column position where the message applies to.\n * \t\tMay be negative if position information is not available.\n * @param level\n * \t\tProblem level.\n * @param phase\n * \t\tProblem phase, stating at what point in the process the problem occurred.\n * @param message\n * \t\tProblem message.\n *\n * @author Matt Coley\n */\npublic record Problem(int line, int column, int length, ProblemLevel level, ProblemPhase phase,\n                      String message) implements Comparable<Problem> {\n\n\t/**\n\t * @param diagnostic\n\t * \t\tCompiler diagnostic message to adapt.\n\t *\n\t * @return Problem from the diagnostic data.\n\t */\n\t@Nonnull\n\tpublic static Problem fromDiagnostic(@Nonnull CompilerDiagnostic diagnostic) {\n\t\tProblemLevel level = switch (diagnostic.level()) {\n\t\t\tcase WARNING -> ProblemLevel.WARN;\n\t\t\tcase INFO -> ProblemLevel.INFO;\n\t\t\tdefault -> ProblemLevel.ERROR;\n\t\t};\n\t\treturn new Problem(diagnostic.line(), diagnostic.column(), diagnostic.length(),\n\t\t\t\tlevel, ProblemPhase.BUILD, diagnostic.message());\n\t}\n\n\t/**\n\t * @param newLine\n\t * \t\tNew line for problem.\n\t *\n\t * @return Copy of the current problem, but with the line number modified.\n\t */\n\t@Nonnull\n\tpublic Problem withLine(int newLine) {\n\t\treturn new Problem(newLine, column, length, level, phase, message);\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn line + \":\" + level.name() + \": \" + message;\n\t}\n\n\t@Override\n\tpublic int compareTo(Problem o) {\n\t\tint cmp = Integer.compare(line, o.line);\n\t\tif (cmp == 0)\n\t\t\tcmp = Integer.compare(column, o.column);\n\t\tif (cmp == 0)\n\t\t\tcmp = level.compareTo(o.level);\n\t\treturn cmp;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemGutterGraphicFactory.java",
    "content": "package software.coley.recaf.ui.control.richtext.problem;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.Cursor;\nimport javafx.scene.Node;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Rectangle;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.linegraphics.AbstractLineGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.linegraphics.LineContainer;\nimport software.coley.recaf.ui.control.richtext.linegraphics.LineGraphicFactory;\n\n/**\n * Graphic factory that adds overlays to line graphics indicating the problem status of the line.\n *\n * @author Matt Coley\n * @see ProblemTracking\n */\npublic class ProblemGutterGraphicFactory extends AbstractLineGraphicFactory  {\n\tprivate Editor editor;\n\n\t/**\n\t * New graphic factory.\n\t */\n\tpublic ProblemGutterGraphicFactory() {\n\t\tsuper(LineGraphicFactory.P_LINE_PROBLEMS);\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tthis.editor = editor;\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tthis.editor = null;\n\t}\n\n\t@Override\n\tpublic void apply(@Nonnull LineContainer container, int paragraph) {\n\t\tProblemTracking problemTracking = editor.getProblemTracking();\n\n\t\t// Always null if no bracket tracking is registered for the editor.\n\t\tif (problemTracking == null) return;\n\n\t\t// Add problem graphic overlay to lines with problems.\n\t\tint line = paragraph + 1;\n\t\tProblem problem = problemTracking.getFirstItemOnLine(line);\n\t\tif (problem != null) {\n\t\t\tProblemLevel level = problem.level();\n\t\t\tColor levelColor = switch (level) {\n\t\t\t\tcase ERROR -> Color.RED;\n\t\t\t\tcase WARN -> Color.YELLOW;\n\t\t\t\tdefault -> Color.TURQUOISE;\n\t\t\t};\n\t\t\tNode graphic = switch (level) {\n\t\t\t\tcase ERROR -> new FontIconView(CarbonIcons.ERROR, levelColor);\n\t\t\t\tcase WARN -> new FontIconView(CarbonIcons.WARNING_ALT, levelColor);\n\t\t\t\tdefault -> new FontIconView(CarbonIcons.INFORMATION, levelColor);\n\t\t\t};\n\n\t\t\t// Workaround: When launching Recaf with some specific window scale values\n\t\t\t// a rounding error causes the shape to infinitely expand.\n\t\t\t// If we map the double value to an int, it acts as a floor operation and prevents the issue.\n\t\t\tRectangle shape = new Rectangle();\n\t\t\tshape.widthProperty().bind(container.widthProperty().map(Number::intValue));\n\t\t\tshape.heightProperty().bind(container.heightProperty().map(Number::intValue));\n\t\t\tshape.setCursor(Cursor.HAND);\n\t\t\tshape.setFill(levelColor);\n\t\t\tshape.setOpacity(0.33);\n\n\t\t\tTooltip tooltip = new Tooltip(formatTooltipMessage(problem));\n\t\t\ttooltip.getStyleClass().add(\"mono-text\");\n\t\t\ttooltip.setGraphic(graphic);\n\t\t\tswitch (level) {\n\t\t\t\tcase ERROR -> tooltip.getStyleClass().add(\"error-text\");\n\t\t\t\tcase WARN -> tooltip.getStyleClass().add(\"warn-text\");\n\t\t\t}\n\t\t\tTooltip.install(shape, tooltip);\n\t\t\tcontainer.addTopLayer(shape);\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate static String formatTooltipMessage(@Nonnull Problem problem) {\n\t\tStringBuilder sb = new StringBuilder();\n\t\tint line = problem.line();\n\t\tif (line > 0) {\n\t\t\tint column = problem.column();\n\t\t\tif (column >= 0) {\n\t\t\t\tsb.append(\"Column \").append(column);\n\t\t\t}\n\t\t} else {\n\t\t\tsb.append(\"Unknown line\");\n\t\t}\n\t\treturn sb.append('\\n').append(problem.message()).toString();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemInvalidationListener.java",
    "content": "package software.coley.recaf.ui.control.richtext.problem;\n\nimport software.coley.recaf.behavior.PrioritySortable;\n\n/**\n * Listener to receive updates when any change occurs in a {@link ProblemTracking}'s tracked problem map.\n *\n * @author Matt Coley\n */\npublic interface ProblemInvalidationListener extends PrioritySortable {\n\t/**\n\t * Called when any changes to tracked problems occurs.\n\t */\n\tvoid onProblemInvalidation();\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemLevel.java",
    "content": "package software.coley.recaf.ui.control.richtext.problem;\n\n/**\n * Severity level of a problem.\n *\n * @author Matt Coley\n */\npublic enum ProblemLevel {\n\tDEBUG,\n\tINFO,\n\tWARN,\n\tERROR\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemPhase.java",
    "content": "package software.coley.recaf.ui.control.richtext.problem;\n\n/**\n * Phase in logic where the problem occurred.\n *\n * @author Matt Coley\n */\npublic enum ProblemPhase {\n\t/**\n\t * Occurs prior to 'building' the content.\n\t * <ul>\n\t * <li>For Java, this would be analyzing the syntax and ensuring it is complaint, before attempting to compile.</li>\n\t * <li>For bytecode, this would be building the AST model.</li>\n\t * </ul>\n\t */\n\tLINT,\n\t/**\n\t * Occurs while 'building' the content.\n\t * <ul>\n\t * <li>For Java, this would be logic within {@code javac} handling failures.</li>\n\t * <li>For bytecode, this would be converting the AST model to binary form.</li>\n\t * </ul>\n\t */\n\tBUILD,\n\t/**\n\t * Occurs after content was built.\n\t */\n\tPOST_PROCESS\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemSquiggleGraphicFactory.java",
    "content": "package software.coley.recaf.ui.control.richtext.problem;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport software.coley.bentofx.control.canvas.PixelCanvas;\nimport software.coley.bentofx.control.canvas.PixelPainter;\nimport software.coley.bentofx.control.canvas.PixelPainterIntArgb;\nimport software.coley.recaf.ui.control.richtext.linegraphics.AbstractTextBoundLineGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.linegraphics.LineGraphicFactory;\nimport software.coley.recaf.util.Colors;\n\nimport java.util.List;\n\n/**\n * Graphic factory that adds overlays squiggles to the editor text where problems occur.\n *\n * @author Matt Coley\n * @author Justus Garbe\n * @see ProblemTracking\n */\npublic class ProblemSquiggleGraphicFactory extends AbstractTextBoundLineGraphicFactory {\n\tprivate final PixelPainter<?> pixelPainter = new PixelPainterIntArgb();\n\n\tpublic ProblemSquiggleGraphicFactory() {\n\t\tsuper(LineGraphicFactory.P_LINE_PROBLEM_SQUIGGLES);\n\t}\n\n\t@Override\n\tprotected void apply(@Nonnull StackPane pane, int paragraph) {\n\t\tif (editor.getProblemTracking() == null)\n\t\t\treturn;\n\n\t\tList<Problem> problems = editor.getProblemTracking().getItemsOnLine(paragraph + 1);\n\t\tif (problems.isEmpty())\n\t\t\treturn;\n\n\t\tfor (Problem problem : problems)\n\t\t\tprepareProblem(problem, pane, paragraph);\n\t}\n\n\t/**\n\t * Adds the given problem to the stack pane.\n\t *\n\t * @param problem\n\t * \t\tProblem to add.\n\t * @param pane\n\t * \t\tPane to add the problem display to.\n\t * @param paragraph\n\t * \t\tCurrent paragraph index.\n\t */\n\tprivate void prepareProblem(@Nonnull Problem problem, @Nonnull StackPane pane, int paragraph) {\n\t\t// Compute the width of the draw canvas and offset required\n\t\tint index = problem.column() + 1;\n\t\tdouble toStart = editor.computeWidthUntilCharacter(paragraph, index);\n\t\tdouble toEnd = editor.computeWidthUntilCharacter(paragraph, index + problem.length());\n\t\ttoEnd = Math.max(toEnd, toStart + 6);\n\t\tint errorWidth = (int) (toEnd - toStart);\n\n\t\t// Create the overlay\n\t\tPixelCanvas canvas = new PixelCanvas(pixelPainter, errorWidth, containerHeight);\n\t\tcanvas.setManaged(false);\n\t\tcanvas.setMouseTransparent(true);\n\t\tcanvas.resize(errorWidth, containerHeight);\n\t\tcanvas.setTranslateX(toStart);\n\t\tcanvas.setTranslateY(2);\n\n\t\tpane.getChildren().add(canvas);\n\n\t\tdrawWaves(canvas, problem.level() == ProblemLevel.WARN, errorWidth);\n\t}\n\n\t/**\n\t * Draws a saw-tooth wave pattern for the given error level of the given width.\n\t *\n\t * @param canvas\n\t * \t\tDrawing destination.\n\t * @param warn\n\t * \t\tError level, either {@code true} for warning, or {@code false} for errors.\n\t * @param width\n\t * \t\tLength of waves to draw.\n\t */\n\tprivate void drawWaves(@Nonnull PixelCanvas canvas, boolean warn, double width) {\n\t\t// Make a solid yellow/red line based on error level\n\t\tint color = Colors.argb(warn ? Color.YELLOW : Color.RED);\n\n\t\t/* // TODO: Reimplement squiggle drawing with the more simple PixelCanvas\n\t\t// wave heights\n\t\tfinal double scalingFactor = .7;\n\t\tfinal double waveUp = containerHeight - 3 * scalingFactor;\n\t\tfinal double waveDown = containerHeight - 6 * scalingFactor;\n\n\t\tfinal double stepScale = .6 - (scalingFactor / 5);\n\t\tfinal double step = waveDown * stepScale;\n\t\tfinal double halfStep = step / 2;\n\n\t\tfinal double downStop = width - halfStep;\n\t\tfinal double upStop = width - step;\n\n\t\t// We want to draw waves such that the last wave is on the border of the errorWidth\n\t\tfor (double x = 0; x < width; x += step) {\n\t\t\tgc.moveTo(x, waveUp);\n\t\t\tif (x < downStop)\n\t\t\t\tgc.lineTo(x + halfStep, waveDown);\n\t\t\tif (x < upStop)\n\t\t\t\tgc.lineTo(x + step, waveUp);\n\t\t}*/\n\n\t\tfinal int lineWidth = 1;\n\t\tcanvas.drawHorizontalLine(0, containerHeight - lineWidth, width, lineWidth, color);\n\t\tcanvas.commit();\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/problem/ProblemTracking.java",
    "content": "package software.coley.recaf.ui.control.richtext.problem;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.ui.control.richtext.AbstractLineItemTracking;\nimport software.coley.recaf.ui.control.richtext.Editor;\n\nimport java.util.List;\n\n/**\n * Tracking for problems to display in an {@link Editor}.\n *\n * @author Matt Coley\n */\npublic class ProblemTracking extends AbstractLineItemTracking<Problem, ProblemInvalidationListener> {\n\t/**\n\t * @param level\n\t * \t\tProblem level to filter problems by.\n\t *\n\t * @return List of problems matching the given level.\n\t */\n\t@Nonnull\n\tpublic List<Problem> getProblemsByLevel(@Nonnull ProblemLevel level) {\n\t\treturn getItems(p -> p.level() == level);\n\t}\n\n\t/**\n\t * @param phase\n\t * \t\tProblem phase to filter problems by.\n\t *\n\t * @return List of problems matching the given phase.\n\t */\n\t@Nonnull\n\tpublic List<Problem> getProblemsByPhase(@Nonnull ProblemPhase phase) {\n\t\treturn getItems(p -> p.phase() == phase);\n\t}\n\n\t/**\n\t * @param problem\n\t * \t\tExact instance of a problem to remove.\n\t *\n\t * @return {@code true} when the problem was removed.\n\t * {@code false} when the problem instance was not contained in the problems map.\n\t */\n\tpublic boolean removeByInstance(@Nonnull Problem problem) {\n\t\tList<Problem> list;\n\t\tsynchronized (items) {list = items.get(problem.line());}\n\t\tif (list != null) {\n\t\t\tboolean updated = list.removeIf(p -> p == problem);\n\t\t\tif (updated)\n\t\t\t\tnotifyListeners(\"Exception thrown when removing problem from tracking\");\n\t\t\t// if list empty, remove the entry\n\t\t\tif (list.isEmpty())\n\t\t\t\titems.remove(problem.line());\n\t\t\treturn updated;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param phase\n\t * \t\tThe phase to remove problems of.\n\t *\n\t * @return {@code true} when one or more problems matching the phase were removed.\n\t */\n\tpublic boolean removeByPhase(@Nonnull ProblemPhase phase) {\n\t\tboolean updated;\n\t\tsynchronized (items) {updated = items.values().removeIf(list -> list.removeIf(p -> p.phase() == phase));}\n\t\tif (updated)\n\t\t\tnotifyListeners(\"Exception thrown when removing problems from tracking\");\n\t\treturn updated;\n\t}\n\n\t@Override\n\tprotected void notifyListeners(@Nonnull String failureMessage) {\n\t\tUnchecked.checkedForEach(listeners, ProblemInvalidationListener::onProblemInvalidation,\n\t\t\t\t(listener, t) -> logger.error(failureMessage, t));\n\n\t}\n\n\t@Override\n\tprotected int getLine(@Nonnull Problem item) {\n\t\treturn item.line();\n\t}\n\n\t@Override\n\tprotected Problem withLine(@Nonnull Problem item, int newLine) {\n\t\treturn item.withLine(newLine);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/search/SearchBar.java",
    "content": "package software.coley.recaf.ui.control.richtext.search;\n\nimport atlantafx.base.controls.CustomTextField;\nimport atlantafx.base.controls.Popover;\nimport atlantafx.base.controls.Spacer;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.SimpleIntegerProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.EventHandler;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Group;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TextField;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport org.fxmisc.richtext.CodeArea;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport regexodus.Matcher;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.ui.config.KeybindingConfig;\nimport software.coley.recaf.ui.control.AbstractSearchBar;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.EditorComponent;\nimport software.coley.recaf.util.*;\n\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Search bar component, with layout and functionality inspired from IntelliJ's.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class SearchBar implements EditorComponent, EventHandler<KeyEvent> {\n\tprivate final KeybindingConfig keys;\n\tprivate Editor editor;\n\tprivate FindAndReplaceSearchBar bar;\n\n\t@Inject\n\tpublic SearchBar(@Nonnull KeybindingConfig keys) {\n\t\tthis.keys = keys;\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tthis.editor = editor;\n\t\tbar = new FindAndReplaceSearchBar(editor);\n\t\tNodeEvents.addKeyPressHandler(editor, this);\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\teditor.setTop(null);\n\t\tNodeEvents.removeKeyPressHandler(editor, this);\n\t\tbar = null;\n\t\tthis.editor = null;\n\t}\n\n\t@Override\n\tpublic void handle(KeyEvent event) {\n\t\tif (keys.getFind().match(event)) {\n\t\t\t// Show if not visible\n\t\t\tif (!bar.isVisible())\n\t\t\t\tbar.show();\n\n\t\t\t// Update input text based on current selection\n\t\t\tif (editor != null) {\n\t\t\t\tString selectedText = editor.getCodeArea().getSelectedText();\n\t\t\t\tif (!selectedText.isEmpty()) bar.getSearchTextProperty().setValue(selectedText);\n\t\t\t}\n\n\t\t\t// Grab focus\n\t\t\tbar.requestSearchFocus();\n\t\t\tbar.hideReplace();\n\t\t} else if (keys.getReplace().match(event)) {\n\t\t\t// Show if not visible\n\t\t\tif (!bar.isVisible())\n\t\t\t\tbar.show();\n\n\t\t\t// Update input text based on current selection\n\t\t\tif (editor != null) {\n\t\t\t\tString selectedText = editor.getCodeArea().getSelectedText();\n\t\t\t\tif (!selectedText.isEmpty()) bar.getSearchTextProperty().setValue(selectedText);\n\t\t\t}\n\n\t\t\t// Grab focus\n\t\t\tbar.showReplace();\n\t\t\tbar.replaceInput.requestFocus();\n\t\t}\n\t}\n\n\n\t/**\n\t * The actual search bar.\n\t */\n\tprivate static class FindAndReplaceSearchBar extends AbstractSearchBar {\n\t\tprivate final SimpleIntegerProperty lastResultIndex = new SimpleIntegerProperty(-1);\n\t\tprivate final ObservableList<String> pastReplaces = FXCollections.observableArrayList();\n\t\tprivate final ObservableList<Match> resultRanges = FXCollections.observableArrayList();\n\t\tprivate final CustomTextField replaceInput = new CustomTextField();\n\t\tprivate final Button oldReplaces = new Button();\n\t\tprivate final HBox replaceLine = new HBox();\n\t\tprivate final Editor editor;\n\t\tprivate Button prev;\n\t\tprivate Button next;\n\t\tprivate Button close;\n\t\tprivate Button replace;\n\t\tprivate Button replaceAll;\n\n\t\tprivate FindAndReplaceSearchBar(@Nonnull Editor editor) {\n\t\t\tthis.editor = editor;\n\n\t\t\tsetup();\n\t\t}\n\n\t\t@Override\n\t\tpublic void setup() {\n\t\t\t// Refresh results when text changes.\n\t\t\teditor.getTextChangeEventStream()\n\t\t\t\t\t.successionEnds(Duration.ofMillis(150))\n\t\t\t\t\t.addObserver(changes -> refreshResults());\n\n\t\t\t// Create buttons to iterate through results.\n\t\t\tprev = new ActionButton(CarbonIcons.ARROW_UP, this::prev);\n\t\t\tnext = new ActionButton(CarbonIcons.ARROW_DOWN, this::next);\n\t\t\tprev.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.ACCENT, Styles.SMALL);\n\t\t\tnext.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.ACCENT, Styles.SMALL);\n\t\t\tprev.disableProperty().bind(hasResults.not());\n\t\t\tnext.disableProperty().bind(hasResults.not());\n\n\t\t\t// Button to close the search bar.\n\t\t\tclose = new ActionButton(CarbonIcons.CLOSE, this::hide);\n\t\t\tclose.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.ACCENT, Styles.SMALL);\n\n\t\t\t// Replace buttons.\n\t\t\treplace = new ActionButton(Lang.getBinding(\"find.replace\"), this::replace);\n\t\t\treplaceAll = new ActionButton(Lang.getBinding(\"find.replaceall\"), this::replaceAll);\n\t\t\treplace.getStyleClass().addAll(Styles.SMALL);\n\t\t\treplaceAll.getStyleClass().addAll(Styles.SMALL);\n\t\t\treplace.disableProperty().bind(hasResults.not());\n\t\t\treplaceAll.disableProperty().bind(hasResults.not());\n\n\t\t\t// Add to past replaces when enter is pressed.\n\t\t\treplaceInput.setLeft(oldReplaces);\n\t\t\treplaceInput.setOnKeyPressed(this::onReplaceInputKeyPress);\n\t\t\treplaceInput.setOnKeyReleased(e -> updateReplacePreview());\n\n\t\t\t// Initially hidden.\n\t\t\thide();\n\n\t\t\t// Parent setup done last since we initialize some controls it will depend on above.\n\t\t\tsuper.setup();\n\t\t}\n\n\t\t@Override\n\t\tprotected void setupLayout() {\n\t\t\treplaceInput.prefWidthProperty().bind(searchInput.widthProperty());\n\t\t\tHBox prevAndNext = new HBox(prev, next);\n\t\t\tprevAndNext.setAlignment(Pos.CENTER);\n\t\t\tprevAndNext.setFillHeight(false);\n\t\t\tHBox.setHgrow(searchInput, Priority.ALWAYS);\n\t\t\tHBox searchLine = new HBox(searchInput, resultCount, prevAndNext, new Spacer(), close);\n\t\t\tsearchLine.setAlignment(Pos.CENTER_LEFT);\n\t\t\tsearchLine.setSpacing(10);\n\t\t\tsearchLine.setPadding(new Insets(0, 5, 0, 0));\n\t\t\tHBox.setHgrow(replaceInput, Priority.ALWAYS);\n\t\t\treplaceLine.getChildren().addAll(replaceInput, new Spacer(0), replace, replaceAll);\n\t\t\treplaceLine.setAlignment(Pos.CENTER_LEFT);\n\t\t\treplaceLine.setSpacing(10);\n\t\t\treplaceLine.setPadding(new Insets(0, 5, 0, 0));\n\t\t\treplaceLine.setVisible(false); // invis + group = 0 height (group wrapping is required for this to work)\n\t\t\tgetChildren().addAll(searchLine, new Group(replaceLine));\n\t\t}\n\n\t\t@Override\n\t\tprotected void onSearchInputKeyPress(@Nonnull KeyEvent e) {\n\t\t\t// Handle navigating to next entry and closing\n\t\t\tKeyCode code = e.getCode();\n\t\t\tif (code == KeyCode.ENTER) {\n\t\t\t\tnext();\n\t\t\t} else if (code == KeyCode.ESCAPE) {\n\t\t\t\thide();\n\t\t\t}\n\n\t\t\tsuper.onSearchInputKeyPress(e);\n\t\t}\n\n\t\tprotected void onReplaceInputKeyPress(@Nonnull KeyEvent e) {\n\t\t\t// Handle replacing the next entry and closing\n\t\t\tKeyCode code = e.getCode();\n\t\t\tif (code == KeyCode.ENTER) {\n\t\t\t\treplace();\n\t\t\t} else if (code == KeyCode.ESCAPE) {\n\t\t\t\thide();\n\t\t\t}\n\n\t\t\t// Update past replacements\n\t\t\twhile (pastReplaces.size() > MAX_HISTORY)\n\t\t\t\tpastReplaces.remove(pastReplaces.size() - 1);\n\t\t}\n\n\t\t@Override\n\t\tprotected void bindResultCountDisplay(@Nonnull StringProperty resultTextProperty) {\n\t\t\tresultTextProperty.bind(lastResultIndex.map(n -> {\n\t\t\t\tint i = n.intValue();\n\t\t\t\tif (i < 0) {\n\t\t\t\t\treturn Lang.get(\"menu.search.noresults\");\n\t\t\t\t} else {\n\t\t\t\t\treturn (i + 1) + \"/\" + resultRanges.size();\n\t\t\t\t}\n\t\t\t}));\n\t\t}\n\n\t\t@Override\n\t\tprotected void refreshResults() {\n\t\t\t// Reset index before any results are found.\n\t\t\tlastResultIndex.set(-1);\n\n\t\t\t// Skip when there is nothing\n\t\t\tString search = searchInput.getText();\n\t\t\tif (search == null || search.isEmpty()) {\n\t\t\t\tresultRanges.clear();\n\t\t\t\thasResults.set(false);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check for regex, then do a standard search if not regex.\n\t\t\tString text = editor.getText();\n\t\t\tList<Match> tempRanges = new ArrayList<>();\n\t\t\tif (regex.get()) {\n\t\t\t\t// Validate the regex.\n\t\t\t\tRegexUtil.RegexValidation validation = RegexUtil.validate(search);\n\t\t\t\tPopover popoverValidation = null;\n\t\t\t\tif (validation.valid()) {\n\t\t\t\t\t// It's valid, populate the ranges.\n\t\t\t\t\tMatcher matcher = RegexUtil.getMatcher(search, text);\n\t\t\t\t\twhile (matcher.find())\n\t\t\t\t\t\ttempRanges.add(Match.match(matcher.start(), matcher.end(), matcher));\n\t\t\t\t} else {\n\t\t\t\t\t// It's not valid. Tell the user what went wrong.\n\t\t\t\t\tpopoverValidation = new Popover(new Label(validation.message()));\n\t\t\t\t\tpopoverValidation.setHeaderAlwaysVisible(true);\n\t\t\t\t\tpopoverValidation.titleProperty().bind(Lang.getBinding(\"find.regexinvalid\"));\n\t\t\t\t\tpopoverValidation.show(searchInput);\n\t\t\t\t}\n\n\t\t\t\t// Hide the prior popover if any exists.\n\t\t\t\tObject old = searchInput.getProperties().put(\"regex-popover\", popoverValidation);\n\t\t\t\tif (old instanceof Popover oldPopover)\n\t\t\t\t\toldPopover.hide();\n\t\t\t} else {\n\t\t\t\t// Modify the text/search for case-insensitive searches.\n\t\t\t\tif (!caseSensitivity.get()) {\n\t\t\t\t\ttext = text.toLowerCase();\n\t\t\t\t\tsearch = search.toLowerCase();\n\t\t\t\t}\n\n\t\t\t\t// Loop over text, finding each index of matches.\n\t\t\t\tint size = search.length();\n\t\t\t\tint i = 0;\n\t\t\t\twhile (i < text.length()) {\n\t\t\t\t\tint start = text.indexOf(search, i);\n\t\t\t\t\tif (start > -1) {\n\t\t\t\t\t\tint end = start + size;\n\t\t\t\t\t\ttempRanges.add(Match.match(start, end));\n\t\t\t\t\t\ti = end;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update properties.\n\t\t\thasResults.set(!tempRanges.isEmpty());\n\t\t\tresultRanges.setAll(tempRanges);\n\n\t\t\t// Update selection to show the match closest to the user's current caret position.\n\t\t\tif (!tempRanges.isEmpty()) {\n\t\t\t\tCodeArea area = editor.getCodeArea();\n\t\t\t\tint lastMatchedTerm = area.getSelectedText().length();\n\t\t\t\tint caret = area.getCaretPosition();\n\t\t\t\tint searchStart = Math.max(0, caret - lastMatchedTerm - 1);\n\t\t\t\tint rangeIndex = Lists.sortedInsertIndex(resultRanges, Match.match(searchStart, searchStart));\n\t\t\t\tif (rangeIndex >= resultRanges.size())\n\t\t\t\t\trangeIndex = 0;\n\t\t\t\tlastResultIndex.set(rangeIndex);\n\n\t\t\t\t// Only update selection when the search inputs are focused.\n\t\t\t\t// We can re-compute due to the editor's text changing. So if a user is typing in there, this would be annoying.\n\t\t\t\tif (inputsAreFocused()) {\n\t\t\t\t\tIntRange targetRange = resultRanges.get(rangeIndex).range();\n\t\t\t\t\tarea.selectRange(targetRange.start(), targetRange.end());\n\t\t\t\t\tarea.showParagraphAtCenter(area.getCurrentParagraph());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tprivate void updateReplacePreview() {\n\t\t\t// We do this in on-release so the replacement input text value is up-to-date in this event processing.\n\t\t\t// Show a preview of the replacement for regex matches when it has regex group replacement.\n\t\t\t// For non-regex or simple replacements, we don't need to show anything.\n\t\t\tif (isVisible() && regex.get()) {\n\t\t\t\tint resultIndex = lastResultIndex.get();\n\t\t\t\tPopover popoverPreview;\n\t\t\t\tif (resultIndex == -1) {\n\t\t\t\t\tpopoverPreview = null;\n\t\t\t\t} else {\n\t\t\t\t\tString replacement = getReplacement(resultIndex);\n\t\t\t\t\tif (!replacement.equals(replaceInput.getText())) {\n\t\t\t\t\t\tpopoverPreview = new Popover(new Label(replacement));\n\t\t\t\t\t\tpopoverPreview.setHeaderAlwaysVisible(true);\n\t\t\t\t\t\tpopoverPreview.titleProperty().bind(Lang.getBinding(\"find.regexreplace\"));\n\t\t\t\t\t\tpopoverPreview.show(replaceInput);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpopoverPreview = null;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Hide the prior popover if any exists.\n\t\t\t\tObject old = searchInput.getProperties().put(\"regex-preview\", popoverPreview);\n\t\t\t\tif (old instanceof Popover oldPopover)\n\t\t\t\t\toldPopover.hide();\n\t\t\t}\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected List<TextField> getInputFields() {\n\t\t\treturn List.of(searchInput, replaceInput);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected List<Button> getInputButtons() {\n\t\t\treturn List.of(oldSearches, oldReplaces);\n\t\t}\n\n\t\t/**\n\t\t * Select the next match.\n\t\t */\n\t\tprivate void next() {\n\t\t\trecordSearch();\n\n\t\t\t// No ranges for current search query, so do nothing.\n\t\t\tlastResultIndex.set(-1);\n\t\t\tif (resultRanges.isEmpty())\n\t\t\t\treturn;\n\n\t\t\t// Get the next range index by doing a search starting from the current caret position + 1.\n\t\t\tCodeArea area = editor.getCodeArea();\n\t\t\tint caret = area.getCaretPosition() + 1;\n\t\t\tint rangeIndex = Lists.sortedInsertIndex(resultRanges, Match.match(caret, caret));\n\t\t\tif (rangeIndex >= resultRanges.size())\n\t\t\t\trangeIndex = 0;\n\n\t\t\t// Set index & select the range.\n\t\t\tlastResultIndex.set(rangeIndex);\n\t\t\tIntRange range = resultRanges.get(rangeIndex).range();\n\t\t\tarea.selectRange(range.start(), range.end());\n\t\t\tarea.showParagraphAtCenter(area.getCurrentParagraph());\n\t\t}\n\n\t\t/**\n\t\t * Select the previous match.\n\t\t */\n\t\tprivate void prev() {\n\t\t\trecordSearch();\n\n\t\t\t// No ranges for current search query, so do nothing.\n\t\t\tlastResultIndex.set(-1);\n\t\t\tif (resultRanges.isEmpty())\n\t\t\t\treturn;\n\n\t\t\t// Get the previous range index by doing a search starting from the current selection position in the text,\n\t\t\t// then go back by one position, wrapping around if necessary.\n\t\t\tCodeArea area = editor.getCodeArea();\n\t\t\tint caret = area.getCaretPosition();\n\t\t\tint rangeIndex = Lists.sortedInsertIndex(resultRanges, Match.match(caret - area.getSelectedText().length(), caret)) - 1;\n\t\t\tif (rangeIndex < 0)\n\t\t\t\trangeIndex = resultRanges.size() - 1;\n\n\t\t\t// Set index & select the range.\n\t\t\tlastResultIndex.set(rangeIndex);\n\t\t\tIntRange range = resultRanges.get(rangeIndex).range();\n\t\t\tarea.selectRange(range.start(), range.end());\n\t\t\tarea.showParagraphAtCenter(area.getCurrentParagraph());\n\t\t}\n\n\t\t/**\n\t\t * Replaces the current selected range.\n\t\t */\n\t\tprivate void replace() {\n\t\t\trecordReplace();\n\n\t\t\t// Replace the current index.\n\t\t\tint index = lastResultIndex.get();\n\t\t\tif (index >= 0)\n\t\t\t\treplaceResult(index);\n\n\t\t\t// Highlight next result.\n\t\t\trefreshResults();\n\t\t}\n\n\t\t/**\n\t\t * Replaces all ranges.\n\t\t */\n\t\tprivate void replaceAll() {\n\t\t\trecordReplace();\n\n\t\t\t// Iterate backwards, replacing all matches.\n\t\t\t// We record the start/end ranges so that we can do a re-style after for the affected range.\n\t\t\tList<Match> rangesCopy = new ArrayList<>(resultRanges);\n\t\t\tint max = rangesCopy.size() - 1;\n\t\t\tfor (int i = max; i >= 0; i--)\n\t\t\t\treplaceResult(i);\n\n\t\t\t// Restyle range from first match through last match.\n\t\t\tint start = rangesCopy.get(0).range().start();\n\t\t\tint end = rangesCopy.get(max).range().end();\n\t\t\teditor.restyleAtPosition(start, end - start);\n\t\t}\n\n\t\t/**\n\t\t * Replaces the range in the {@link #resultRanges} at the given {@code index} with the text of {@link #getReplacement(int)}.\n\t\t *\n\t\t * @param index\n\t\t * \t\tIndex in {@link #resultRanges} to replace.\n\t\t */\n\t\tprivate void replaceResult(int index) {\n\t\t\tString replacement = getReplacement(index);\n\t\t\tCodeArea area = editor.getCodeArea();\n\t\t\tMatch match = resultRanges.get(index);\n\t\t\tIntRange range = match.range();\n\t\t\tarea.replaceText(range.start(), range.end(), replacement);\n\t\t}\n\n\t\t/**\n\t\t * if {@link #regex} is {@code true}, then transformations are made on the replacement text from\n\t\t * {@link #replaceInput} to support regex groups.\n\t\t * <br>\n\t\t * For example:\n\t\t * <ul>\n\t\t *     <li>Search: {@code public (class)}</li>\n\t\t *     <li>Replace: {@code private $1}</li>\n\t\t *     <li>Yields: {@code private class}</li>\n\t\t * </ul>\n\t\t *\n\t\t * @param index\n\t\t * \t\tIndex in {@link #resultRanges} to replace.\n\t\t *\n\t\t * @return Replacement for the given range.\n\t\t */\n\t\t@Nonnull\n\t\tprivate String getReplacement(int index) {\n\t\t\tString replacement = replaceInput.getText();\n\t\t\tMatch match = resultRanges.get(index);\n\t\t\tString[] groups = match.groups();\n\t\t\tif (groups != null) {\n\t\t\t\t// Record '$N' patterns in the string\n\t\t\t\t//  - 0: whole matched text\n\t\t\t\t//  - N: group N's text\n\t\t\t\tMatcher matcher = RegexUtil.getMatcher(\"(?<!\\\\\\\\)(?:(\\\\\\\\\\\\\\\\)*)\\\\$\\\\d+\", replacement);\n\t\t\t\twhile (matcher.find()) {\n\t\t\t\t\tint groupId = Integer.parseInt(matcher.group(0).replaceAll(\"\\\\D+\", \"\"));\n\t\t\t\t\tif (groupId < groups.length) {\n\t\t\t\t\t\tString groupText = groups[groupId];\n\t\t\t\t\t\treplacement = replacement.replace(\"$\" + groupId, groupText);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn replacement;\n\t\t}\n\n\t\t/**\n\t\t * Records the current {@link #searchInput} text to {@link #pastSearches}.\n\t\t */\n\t\tprivate void recordSearch() {\n\t\t\t// Update search input history.\n\t\t\tString searchText = searchInput.getText();\n\t\t\tpastSearches.remove(searchText);\n\t\t\tpastSearches.add(0, searchText);\n\t\t}\n\n\t\t/**\n\t\t * Records the current {@link #replaceInput} text to {@link #pastReplaces}.\n\t\t */\n\t\tprivate void recordReplace() {\n\t\t\t// Also record the search term.\n\t\t\trecordSearch();\n\n\t\t\t// Update replace input history.\n\t\t\tString replacement = replaceInput.getText();\n\t\t\tpastReplaces.remove(replacement);\n\t\t\tpastReplaces.add(0, replacement);\n\t\t}\n\n\t\t/**\n\t\t * @return {@code true} when either the search or replace inputs are focused.\n\t\t */\n\t\tprivate boolean inputsAreFocused() {\n\t\t\treturn searchInput.isFocused() || replaceInput.isFocused();\n\t\t}\n\n\t\t/**\n\t\t * Show the search bar.\n\t\t */\n\t\tpublic void show() {\n\t\t\tsetVisible(true);\n\t\t\tsetDisable(false);\n\t\t\teditor.setTop(this);\n\n\t\t\t// If the editor has selected text, we will copy it to the search input field.\n\t\t\tCodeArea area = editor.getCodeArea();\n\t\t\tString selectedText = area.getSelectedText();\n\t\t\tif (!selectedText.isBlank())\n\t\t\t\tsearchInput.setText(selectedText);\n\t\t}\n\n\t\t/**\n\t\t * Hide the search bar.\n\t\t */\n\t\tprivate void hide() {\n\t\t\tsetVisible(false);\n\t\t\tsetDisable(true);\n\t\t\teditor.setTop(null);\n\n\t\t\t// Need to send focus back to the editor's code-area.\n\t\t\t// Doesn't work without the delay when handled from 'ESCAPE' key-event.\n\t\t\tFxThreadUtil.delayedRun(1, () -> editor.getCodeArea().requestFocus());\n\t\t}\n\n\t\t/**\n\t\t * Shows the replace-bar segment.\n\t\t */\n\t\tpublic void showReplace() {\n\t\t\tif (editor.isEditable())\n\t\t\t\treplaceLine.setVisible(true);\n\t\t}\n\n\t\t/**\n\t\t * Hides the replace-bar segment.\n\t\t */\n\t\tpublic void hideReplace() {\n\t\t\treplaceLine.setVisible(false);\n\t\t}\n\t}\n\n\t/**\n\t * @param range\n\t * \t\tRange of the match.\n\t * @param groups\n\t * \t\tRegex groups of the match, if the match is done with\n\t *        {@link AbstractSearchBar#regexProperty()} enabled.\n\t */\n\tprivate record Match(@Nonnull IntRange range, @Nullable String[] groups) implements Comparable<Match> {\n\t\t@Nonnull\n\t\tstatic Match match(int start, int end) {\n\t\t\treturn new Match(new IntRange(start, end), null);\n\t\t}\n\n\t\t@Nonnull\n\t\tstatic Match match(int start, int end, @Nonnull Matcher matcher) {\n\t\t\treturn new Match(new IntRange(start, end), matcher.groups());\n\t\t}\n\n\t\t@Override\n\t\tpublic int compareTo(Match o) {\n\t\t\treturn range.compareTo(o.range);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/source/JavaContextActionManager.java",
    "content": "package software.coley.recaf.ui.control.richtext.source;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.services.source.AstResolveResult;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * Manager for {@link JavaContextActionManager}. Allows context actions to be observed via listeners.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class JavaContextActionManager {\n\tprivate final List<ResolveListener> resolveListeners = new CopyOnWriteArrayList<>();\n\tprivate final List<SelectListener> selectListeners = new CopyOnWriteArrayList<>();\n\tprivate final List<ResolveListener> resolveListenersView = Collections.unmodifiableList(resolveListeners);\n\tprivate final List<SelectListener> selectListenersView = Collections.unmodifiableList(selectListeners);\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tpublic void addResolveListener(@Nonnull ResolveListener listener) {\n\t\tPrioritySortable.add(resolveListeners, listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tpublic void removeResolveListener(@Nonnull ResolveListener listener) {\n\t\tresolveListeners.remove(listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tpublic void addSelectListener(@Nonnull SelectListener listener) {\n\t\tPrioritySortable.add(selectListeners, listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tpublic void removeSelectListener(@Nonnull SelectListener listener) {\n\t\tselectListeners.remove(listener);\n\t}\n\n\t/**\n\t * @return List of registered context resolution listeners.\n\t */\n\t@Nonnull\n\tpublic List<ResolveListener> getResolveListeners() {\n\t\treturn resolveListenersView;\n\t}\n\n\t/**\n\t * @return List of registered selection listeners.\n\t */\n\t@Nonnull\n\tpublic List<SelectListener> getSelectListeners() {\n\t\treturn selectListenersView;\n\t}\n\n\t/**\n\t * Listener for receiving context resolutions.\n\t */\n\tpublic interface ResolveListener extends PrioritySortable {\n\t\t/**\n\t\t * @param result\n\t\t * \t\tResolution result.\n\t\t * @param pos\n\t\t * \t\tText offset position.\n\t\t */\n\t\tvoid onResolve(@Nonnull AstResolveResult result, int pos);\n\t}\n\n\t/**\n\t * Listener for receiving {@link ClassMember} selections within the Java decompiler display.\n\t */\n\tpublic interface SelectListener extends PrioritySortable {\n\t\t/**\n\t\t * @param memberPath\n\t\t * \t\tPath of member selected.\n\t\t */\n\t\tvoid onSelect(@Nonnull ClassMemberPathNode memberPath);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/source/JavaContextActionSupport.java",
    "content": "package software.coley.recaf.ui.control.richtext.source;\n\nimport atlantafx.base.controls.Popover;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.annotation.PreDestroy;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ContextMenu;\nimport javafx.scene.control.TextArea;\nimport javafx.scene.layout.VBox;\nimport org.fxmisc.richtext.CharacterHit;\nimport org.fxmisc.richtext.CodeArea;\nimport org.fxmisc.richtext.model.PlainTextChange;\nimport org.fxmisc.richtext.model.TwoDimensional;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.Closing;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.cell.context.ContextMenuProviderService;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.inheritance.InheritanceVertex;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.navigation.ClassNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.services.source.AstResolveResult;\nimport software.coley.recaf.services.source.AstService;\nimport software.coley.recaf.services.source.ResolverAdapter;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.EditorComponent;\nimport software.coley.recaf.ui.control.richtext.inheritance.Inheritance;\nimport software.coley.recaf.ui.control.richtext.inheritance.InheritanceGutterGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.inheritance.InheritanceTracking;\nimport software.coley.recaf.ui.pane.editing.ToolsContainerComponent;\nimport software.coley.recaf.ui.pane.editing.assembler.AssemblerContextActionSupport;\nimport software.coley.recaf.ui.pane.editing.tabs.FieldsAndMethodsPane;\nimport software.coley.recaf.util.EscapeUtil;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.util.threading.ThreadUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.sourcesolver.Parser;\nimport software.coley.sourcesolver.model.ClassModel;\nimport software.coley.sourcesolver.model.CompilationUnitModel;\nimport software.coley.sourcesolver.model.MethodModel;\nimport software.coley.sourcesolver.model.VariableModel;\nimport software.coley.sourcesolver.resolve.result.DescribableResolution;\nimport software.coley.sourcesolver.resolve.result.MethodResolution;\nimport software.coley.sourcesolver.util.Range;\n\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NavigableMap;\nimport java.util.TreeMap;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\n\n/**\n * Enables context actions on an {@link Editor} by parsing the source text as Java and modeling the AST.\n * The AST can then be used to get information required for operations offered by {@link ContextMenuProviderService}.\n *\n * @author Matt Coley\n * @see FieldsAndMethodsPane#setupSelectionNavigationListener(ClassNavigable) Originating call for {@link #select(ClassMember)}.\n * @see AssemblerContextActionSupport Alternative for context actions on assembly sources.\n * @see JavaContextActionManager Manager for adding select/resolve listeners.\n */\n@Dependent\npublic class JavaContextActionSupport implements EditorComponent, UpdatableNavigable, Closing {\n\tprivate static final DebuggingLogger logger = Logging.get(JavaContextActionSupport.class);\n\tprivate static final long REPARSE_ELAPSED_TIME = 2_000L;\n\tprivate final ExecutorService parseThreadPool = ThreadPoolFactory.newSingleThreadExecutor(\"java-parse\");\n\tprivate final NavigableMap<Integer, Integer> offsetMap = new TreeMap<>();\n\tprivate final AstAvailabilityButton astAvailabilityButton = new AstAvailabilityButton();\n\tprivate final InheritanceTracking inheritanceTracking = new InheritanceTracking();\n\tprivate final InheritanceGutterGraphicFactory inheritanceGutterGraphicFactory;\n\tprivate final CellConfigurationService cellConfigurationService;\n\tprivate final JavaContextActionManager contextManager;\n\tprivate final AstService astService;\n\tprivate final InheritanceGraph graph;\n\tprivate final Workspace workspace;\n\tprivate final Parser parser;\n\tprivate Future<?> lastFuture;\n\tprivate int lastSourceHash;\n\tprivate long lastTextChangeTimestamp;\n\tprivate ClassPathNode path;\n\tprivate Runnable queuedSelectionTask;\n\tprivate String className;\n\tprivate CompilationUnitModel unit;\n\tprivate ResolverAdapter resolver;\n\tprivate Editor editor;\n\tprivate ContextMenu menu;\n\n\t@Inject\n\tpublic JavaContextActionSupport(@Nonnull CellConfigurationService cellConfigurationService,\n\t                                @Nonnull JavaContextActionManager contextManager,\n\t                                @Nonnull AstService astService,\n\t                                @Nonnull InheritanceGraphService graphService,\n\t                                @Nonnull Actions actions,\n\t                                @Nonnull WorkspaceManager workspaceManager) {\n\t\tthis.inheritanceGutterGraphicFactory = new InheritanceGutterGraphicFactory(cellConfigurationService, actions);\n\t\tthis.cellConfigurationService = cellConfigurationService;\n\t\tthis.contextManager = contextManager;\n\t\tthis.astService = astService;\n\t\tthis.workspace = workspaceManager.getCurrent();\n\t\tthis.graph = graphService.getOrCreateInheritanceGraph(workspace);\n\t\tparser = astService.getSharedJavaParser();\n\t}\n\n\t@PreDestroy\n\tprivate void cleanup() {\n\t\tlastFuture.cancel(true);\n\t\tqueuedSelectionTask = null;\n\t\tparseThreadPool.close();\n\t}\n\n\t/**\n\t * This button visually tells user the state of the AST parse, and is clickable for more information.\n\t * It is intended to be added to {@link ToolsContainerComponent}.\n\t *\n\t * @return UI button/label detailing the current availability of the {@link #getUnit() AST unit}.\n\t */\n\t@Nonnull\n\tpublic AstAvailabilityButton getAvailabilityButton() {\n\t\treturn astAvailabilityButton;\n\t}\n\n\t/**\n\t * @return Current AST for the class.\n\t */\n\t@Nullable\n\tpublic CompilationUnitModel getUnit() {\n\t\treturn unit;\n\t}\n\n\t/**\n\t * Initializes the internal Java source parser.\n\t *\n\t * @param targetClass\n\t * \t\tClass to initialize parser against.\n\t */\n\tpublic void initialize(@Nonnull ClassInfo targetClass) {\n\t\tif (targetClass.isJvmClass()) {\n\t\t\tinitialize(targetClass.asJvmClass());\n\t\t} else if (targetClass.isAndroidClass()) {\n\t\t\tinitialize(targetClass.asAndroidClass());\n\t\t}\n\t}\n\n\t/**\n\t * Initializes the internal Java source parser by converting the Android class to a Java class.\n\t *\n\t * @param targetClass\n\t * \t\tClass to initialize parser against.\n\t */\n\tprivate void initialize(@Nonnull AndroidClassInfo targetClass) {\n\t\tif (targetClass.canMapToJvmClass()) {\n\t\t\tinitialize(targetClass.asJvmClass());\n\t\t}\n\t}\n\n\t/**\n\t * Initializes the internal Java source parser.\n\t *\n\t * @param targetClass\n\t * \t\tClass to initialize parser against.\n\t */\n\tprivate void initialize(@Nonnull JvmClassInfo targetClass) {\n\t\t// Set name\n\t\tclassName = EscapeUtil.escapeStandard(targetClass.getName());\n\t}\n\n\t/**\n\t * Schedules an AST parse job.\n\t */\n\tpublic void scheduleAstParse() {\n\t\tif (editor == null)\n\t\t\tthrow new IllegalStateException(\"Can only initialize after installed to an editor\");\n\n\t\t// Do initial source parse\n\t\tif (!editor.getText().isBlank())\n\t\t\thandleLongDurationChange();\n\t}\n\n\t/**\n\t * Selects a member in the AST.\n\t *\n\t * @param member\n\t * \t\tMember to select.\n\t */\n\tpublic void select(@Nonnull ClassMember member) {\n\t\tCompilationUnitModel localUnit = unit;\n\t\tif (localUnit == null) {\n\t\t\tqueuedSelectionTask = () -> select(member);\n\t\t} else {\n\t\t\tqueuedSelectionTask = null;\n\t\t\ttry {\n\t\t\t\tfor (ClassModel declaredClass : localUnit.getDeclaredClasses()) {\n\t\t\t\t\tif (member.isField()) {\n\t\t\t\t\t\tList<VariableModel> matchedFields = declaredClass.getFields().stream()\n\t\t\t\t\t\t\t\t.filter(v -> v.getName().equals(member.getName()))\n\t\t\t\t\t\t\t\t.toList();\n\t\t\t\t\t\tif (matchedFields.size() == 1) {\n\t\t\t\t\t\t\t// Only one field by the given name.\n\t\t\t\t\t\t\tselectRange(matchedFields.getFirst().getRange());\n\t\t\t\t\t\t} else if (matchedFields.size() > 1) {\n\t\t\t\t\t\t\t// Multiple fields by the given name, need to differentiate by type.\n\t\t\t\t\t\t\tfor (VariableModel field : matchedFields) {\n\t\t\t\t\t\t\t\tif (field.getType().resolve(resolver) instanceof DescribableResolution fieldTypeResolution\n\t\t\t\t\t\t\t\t\t\t&& fieldTypeResolution.getDescribableEntry().getDescriptor().equals(member.getDescriptor())) {\n\t\t\t\t\t\t\t\t\tselectRange(field.getRange());\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tList<MethodModel> matchedMethods = declaredClass.getMethods().stream()\n\t\t\t\t\t\t\t\t.filter(m -> m.getName().equals(member.getName()))\n\t\t\t\t\t\t\t\t.toList();\n\t\t\t\t\t\tif (matchedMethods.size() == 1) {\n\t\t\t\t\t\t\t// Only one method by the given name.\n\t\t\t\t\t\t\tselectRange(matchedMethods.getFirst().getRange());\n\t\t\t\t\t\t} else if (matchedMethods.size() > 1) {\n\t\t\t\t\t\t\t// Multiple methods by the given name, need to differentiate by signature.\n\t\t\t\t\t\t\tfor (MethodModel method : matchedMethods) {\n\t\t\t\t\t\t\t\tif (method.resolve(resolver) instanceof MethodResolution methodResolution\n\t\t\t\t\t\t\t\t\t\t&& methodResolution.getMethodEntry().getDescriptor().equals(member.getDescriptor())) {\n\t\t\t\t\t\t\t\t\tselectRange(method.getRange());\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tUnchecked.checkedForEach(contextManager.getSelectListeners(),\n\t\t\t\t\t\tlistener -> listener.onSelect(path.child(member)),\n\t\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown on select listener '{}'\", listener.getClass(), t));\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Unhandled exception in Java context support - select '{}'\", member.getName(), t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Selects the range in the {@link #editor}.\n\t * Must be called on the FX thread.\n\t *\n\t * @param range\n\t * \t\tRange to select.\n\t */\n\tprivate void selectRange(@Nonnull Range range) {\n\t\tCodeArea area = editor.getCodeArea();\n\t\tarea.selectRange(range.end(), range.begin());\n\t\teditor.showParagraphAtCenter(area.getCurrentParagraph());\n\t}\n\n\t/**\n\t * @param pos\n\t * \t\tOffset in the source.\n\t *\n\t * @return Resolution of content at the given offset.\n\t */\n\t@Nullable\n\tpublic AstResolveResult resolvePosition(int pos) {\n\t\treturn resolvePosition(pos, true);\n\t}\n\n\t@Nullable\n\tprivate AstResolveResult resolvePosition(int pos, boolean doOffset) {\n\t\tif (unit == null || resolver == null) return null;\n\t\tif (doOffset) pos = offset(pos);\n\t\tAstResolveResult result = resolver.resolveThenAdapt(pos);\n\t\tif (result != null) {\n\t\t\tint finalPos = pos;\n\t\t\tUnchecked.checkedForEach(contextManager.getResolveListeners(),\n\t\t\t\t\tlistener -> listener.onResolve(result, finalPos),\n\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown on resolve listener '{}'\", listener.getClass(), t));\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Handle updating the offset-map so that we do not need to do a full reparse of the source.\n\t * <br>\n\t * When the user makes small changes, its unlikely they will be immediately doing context actions in that area.\n\t * We can take advantage of this by not recomputing the AST model for every change, but instead tracking where\n\t * text inserts/deletions occur. We can them map a position in the current text to the original parsed AST.\n\t *\n\t * @param change\n\t * \t\tText changed.\n\t */\n\tprivate void handleShortDurationChange(@Nonnull PlainTextChange change) {\n\t\ttry {\n\t\t\tint position = change.getPosition();\n\t\t\tint offset = change.getNetLength();\n\t\t\toffsetMap.merge(position, offset, Integer::sum);\n\n\t\t\t// Record timestamp of last change.\n\t\t\tlong time = System.currentTimeMillis();\n\t\t\tlong lastTime = lastTextChangeTimestamp;\n\t\t\tlastTextChangeTimestamp = time;\n\n\t\t\t// Ideally this would be using a push-back mechanism instead of a basic time diff check...\n\t\t\t// This method isn't perfect, but is sufficient to avoid excessive redraws.\n\t\t\t//\n\t\t\t// We should register this method as a text-change observer last so any system\n\t\t\t// that will rely on accurate tracking state will have already updated before we redraw.\n\t\t\t// As stated before this is not perfect and a final refresh should occur to ensure no de-syncs,\n\t\t\t// but is sufficient for now.\n\t\t\tif (time - lastTime > 100)\n\t\t\t\tFxThreadUtil.run(() -> editor.redrawParagraphGraphics());\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Unhandled exception merging offset-maps with new text-change\", t);\n\t\t}\n\t}\n\n\t/**\n\t * Handle a full reparse of the source, updating the {@link #unit}.\n\t */\n\tprivate void handleLongDurationChange() {\n\t\t// Skip if parser is not ready yet.\n\t\tif (parser == null)\n\t\t\treturn;\n\n\t\t// Skip if we already shut down the pool.\n\t\tif (parseThreadPool.isShutdown())\n\t\t\treturn;\n\n\t\t// Cancel last parse future if not complete\n\t\tif (lastFuture != null && !lastFuture.isDone())\n\t\t\tlastFuture.cancel(true);\n\n\t\t// Do parsing on BG thread, it can be slower on complex inputs.\n\t\tlastFuture = parseThreadPool.submit(ThreadUtil.wrap(() -> {\n\t\t\tString text = editor.getText();\n\n\t\t\t// Skip if the source hasn't changed since the last time.\n\t\t\t// This may occur when the user inserts some text, then removes it, resulting in the original text again.\n\t\t\tint textHash = text.hashCode();\n\t\t\tif (lastSourceHash == textHash) {\n\t\t\t\tlogger.debugging(l -> l.info(\"Skipping AST parse, source hash has not changed\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlastSourceHash = textHash;\n\n\t\t\t// When code changes are made we want to notify users that while an existing unit is available\n\t\t\t// an up-to-date one is still pending.\n\t\t\tif (unit != null)\n\t\t\t\tastAvailabilityButton.setNewParseInProgress();\n\n\t\t\t// Parse the current source\n\t\t\tlong start = System.currentTimeMillis();\n\t\t\tString classNameEsc = EscapeUtil.escapeStandardAndUnicodeWhitespace(className);\n\t\t\tlogger.debugging(l -> l.info(\"Starting AST parse...\"));\n\t\t\ttry {\n\t\t\t\tCompilationUnitModel resultingUnit;\n\t\t\t\tsynchronized (parser) {\n\t\t\t\t\t// Underlying javac internals are not thread safe so we synchronize on the parser here.\n\t\t\t\t\tresultingUnit = parser.parse(text);\n\t\t\t\t}\n\t\t\t\tlong diffMs = (System.currentTimeMillis() - start);\n\t\t\t\tif (resultingUnit.getDeclaredClasses().isEmpty()) {\n\t\t\t\t\tunit = null;\n\t\t\t\t\tresolver = null;\n\n\t\t\t\t\tlogger.warn(\"Could not create Java AST model from source of: {} after {}ms\", classNameEsc, diffMs);\n\t\t\t\t\tastAvailabilityButton.setUnavailable();\n\t\t\t\t\tinheritanceTracking.clear();\n\t\t\t\t} else {\n\t\t\t\t\tunit = resultingUnit;\n\t\t\t\t\tresolver = astService.newJavaResolver(workspace, resultingUnit);\n\t\t\t\t\tresolver.setClassContext(getPath().getValue());\n\n\t\t\t\t\tlogger.debugging(l -> l.info(\"AST parsed successfully, took {}ms\", diffMs));\n\t\t\t\t\tastAvailabilityButton.setAvailable();\n\t\t\t\t\tpopulateInheritanceTracking();\n\n\t\t\t\t\t// Run queued selection task\n\t\t\t\t\tif (queuedSelectionTask != null)\n\t\t\t\t\t\tFxThreadUtil.run(queuedSelectionTask);\n\t\t\t\t}\n\t\t\t} catch (Throwable ex) {\n\t\t\t\tlong diffMs = (System.currentTimeMillis() - start);\n\t\t\t\tlogger.warn(\"Parse error from source of: {} after {}ms\", classNameEsc, diffMs, ex);\n\t\t\t\tastAvailabilityButton.setParserError(ex);\n\t\t\t}\n\n\t\t\t// Wipe offset map now that we have a new AST\n\t\t\toffsetMap.clear();\n\t\t}));\n\t}\n\n\t/**\n\t * Parse the AST for method declarations and find inheritance relationships with parents/children.\n\t */\n\tprivate void populateInheritanceTracking() {\n\t\tWorkspace localWorkspace = workspace;\n\t\tCompilationUnitModel localUnit = unit;\n\t\tif (workspace == null || localUnit == null || resolver == null)\n\t\t\treturn;\n\t\tCompletableFuture.supplyAsync(() -> {\n\t\t\tList<Inheritance> inheritances = new ArrayList<>();\n\t\t\tList<ClassModel> classModels = localUnit.getRecursiveChildrenOfType(ClassModel.class);\n\t\t\tfor (ClassModel classModel : classModels) {\n\t\t\t\t// Resolve what class each model represents.\n\t\t\t\tAstResolveResult classResolutionResult = resolver.resolveThenAdapt(classModel.getRange().begin());\n\t\t\t\tif (classResolutionResult == null || !(classResolutionResult.path() instanceof ClassPathNode resolvedClassPath))\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Get vertex in inheritance graph for the resolved class.\n\t\t\t\tClassInfo resolvedClass = resolvedClassPath.getValue();\n\t\t\t\tInheritanceVertex resolvedVertex = graph.getVertex(resolvedClass.getName());\n\t\t\t\tif (resolvedVertex == null)\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// Gather parents and children from inheritance graph.\n\t\t\t\t// - Note: The map values may be null if the class is not in the workspace.\n\t\t\t\tMap<InheritanceVertex, ClassPathNode> children = resolvedVertex.allChildren()\n\t\t\t\t\t\t.collect(IdentityHashMap::new, (m, v) -> m.put(v, workspace.findClass(v.getName())), IdentityHashMap::putAll);\n\t\t\t\tMap<InheritanceVertex, ClassPathNode> parents = resolvedVertex.allParents()\n\t\t\t\t\t\t.collect(IdentityHashMap::new, (m, v) -> m.put(v, workspace.findClass(v.getName())), IdentityHashMap::putAll);\n\n\t\t\t\t// For all methods in this model, find matching methods in parents/children and track them.\n\t\t\t\tfor (MethodModel methodModel : classModel.getMethods()) {\n\t\t\t\t\tint methodResolvePos = methodModel.getModifiers().getRange().end();\n\t\t\t\t\tint methodLinePos = methodModel.getReturnType().getRange().end();\n\t\t\t\t\tint line = 1 + editor.getCodeArea().offsetToPosition(methodLinePos, TwoDimensional.Bias.Forward).getMajor();\n\n\t\t\t\t\t// Resolve what method each model represents.\n\t\t\t\t\t// - Underlying model is funky and resolving the modifier position is the best programmatic way to resolve the method.\n\t\t\t\t\t//   The method model's start position likely has an annotation or javadoc there that throws off resolution.\n\t\t\t\t\tAstResolveResult methodResolutionResult = resolver.resolveThenAdapt(methodResolvePos);\n\t\t\t\t\tif (methodResolutionResult == null || !(methodResolutionResult.path() instanceof ClassMemberPathNode resolvedMethodPath))\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tClassMember resolvedMethod = resolvedMethodPath.getValue();\n\n\t\t\t\t\t// Collect methods in parents/children.\n\t\t\t\t\tString methodName = resolvedMethod.getName();\n\t\t\t\t\tString methodDesc = resolvedMethod.getDescriptor();\n\t\t\t\t\tchildren.forEach((child, childClassPath) -> {\n\t\t\t\t\t\tif (child.hasMethod(methodName, methodDesc)) {\n\t\t\t\t\t\t\tClassMemberPathNode childMethodPath = childClassPath.child(methodName, methodDesc);\n\t\t\t\t\t\t\tif (childMethodPath != null)\n\t\t\t\t\t\t\t\tinheritances.add(new Inheritance.Child(line, childMethodPath));\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tparents.forEach((parent, parentClassPath) -> {\n\t\t\t\t\t\tif (parent.hasMethod(methodName, methodDesc)) {\n\t\t\t\t\t\t\tClassMemberPathNode parentMethodPath = parentClassPath.child(methodName, methodDesc);\n\t\t\t\t\t\t\tif (parentMethodPath != null)\n\t\t\t\t\t\t\t\tinheritances.add(new Inheritance.Parent(line, parentMethodPath));\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn inheritances;\n\t\t}, ThreadUtil.executor()).thenAcceptAsync(items -> {\n\t\t\tinheritanceTracking.clear();\n\t\t\tinheritanceTracking.addItems(items);\n\t\t\teditor.redrawParagraphGraphics();\n\t\t}, FxThreadUtil.executor());\n\t}\n\n\t/**\n\t * Offsets the given input index.\n\t *\n\t * @param index\n\t * \t\tInput index.\n\t *\n\t * @return Offset index based on values in {@link #offsetMap} up until the given index.\n\t *\n\t * @see #handleShortDurationChange(PlainTextChange)\n\t */\n\tprivate int offset(int index) {\n\t\tif (offsetMap.isEmpty())\n\t\t\treturn index;\n\t\tNavigableMap<Integer, Integer> subOffsetMap = offsetMap.subMap(0, true, index, false);\n\t\tint offset = -subOffsetMap.values().stream().mapToInt(i -> i).sum();\n\t\tlogger.debugging(l -> l.info(\"Offset request hit index: {} --> {}\", index, index + offset));\n\t\treturn index + offset;\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tthis.editor = editor;\n\n\t\t// Setup inheritance tracking first. It will receive text change events before us.\n\t\tinheritanceTracking.install(editor);\n\n\t\t// Now we register our own text change listeners.\n\t\teditor.getTextChangeEventStream()\n\t\t\t\t.addObserver(this::handleShortDurationChange);\n\t\teditor.getTextChangeEventStream().successionEnds(Duration.ofMillis(REPARSE_ELAPSED_TIME))\n\t\t\t\t.addObserver(e -> handleLongDurationChange());\n\n\t\t// Setup inheritance gutter graphics/tracking.\n\t\teditor.setComponent(InheritanceTracking.COMPONENT_KEY, inheritanceTracking);\n\t\teditor.getRootLineGraphicFactory().addLineGraphicFactory(inheritanceGutterGraphicFactory);\n\n\t\t// Setup context-menu on right-click.\n\t\tCodeArea area = editor.getCodeArea();\n\t\tarea.setOnContextMenuRequested(e -> {\n\t\t\t// Close old menu\n\t\t\tif (menu != null) {\n\t\t\t\tmenu.hide();\n\t\t\t\tmenu = null;\n\t\t\t}\n\n\t\t\t// Check AST model has been generated\n\t\t\tif (unit == null) {\n\t\t\t\tlogger.warn(\"Could not request context menu, AST model not available\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Convert the event position to line/column\n\t\t\tCharacterHit hit = area.hit(e.getX(), e.getY());\n\t\t\tTwoDimensional.Position hitPos = area.offsetToPosition(hit.getInsertionIndex(),\n\t\t\t\t\tTwoDimensional.Bias.Backward);\n\t\t\tint line = hitPos.getMajor() + 1; // Position is 0 indexed\n\t\t\tint column = hitPos.getMinor();\n\n\t\t\t// Sync caret\n\t\t\tarea.moveTo(hit.getInsertionIndex());\n\n\t\t\t// Create menu\n\t\t\tint offsetHitIndex = offset(hit.getInsertionIndex());\n\t\t\tAstResolveResult result = resolvePosition(offsetHitIndex, false);\n\t\t\tif (result != null) {\n\t\t\t\tPathNode<?> path = result.path();\n\t\t\t\tlogger.debugging(l -> l.info(\"Path at offset '{}' = {}\", offsetHitIndex, path));\n\n\t\t\t\t// Map the result's declaration state to a context-source.\n\t\t\t\tContextSource source = result.isDeclaration() ? ContextSource.DECLARATION : ContextSource.REFERENCE;\n\t\t\t\tmenu = cellConfigurationService.contextMenuOf(source, path);\n\t\t\t}\n\n\t\t\t// Show menu\n\t\t\tif (menu != null) {\n\t\t\t\tmenu.setAutoHide(true);\n\t\t\t\tmenu.setHideOnEscape(true);\n\t\t\t\tmenu.show(area.getScene().getWindow(), e.getScreenX(), e.getScreenY());\n\t\t\t\tmenu.requestFocus();\n\t\t\t} else {\n\t\t\t\tlogger.warn(\"No recognized class or member at selected position [line {}, column {}]\", line, column);\n\t\t\t}\n\t\t});\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tinheritanceTracking.uninstall(editor);\n\t\teditor.getRootLineGraphicFactory().removeLineGraphicFactory(inheritanceGutterGraphicFactory);\n\t\teditor.getCodeArea().setOnContextMenuRequested(null);\n\t\teditor.setComponent(InheritanceTracking.COMPONENT_KEY, null);\n\t\tthis.editor = null;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassPathNode getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void requestFocus() {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tclose();\n\t}\n\n\t@Override\n\tpublic void close() {\n\t\tif (!parseThreadPool.isShutdown())\n\t\t\tparseThreadPool.shutdownNow();\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof ClassPathNode classPath) {\n\t\t\tthis.path = classPath;\n\n\t\t\t// Re-initialize the parser if the path updates.\n\t\t\t// This addresses situations where changes to the class introduce new type dependencies.\n\t\t\t// If we used the existing parser, the newly added types would be unresolvable.\n\t\t\tClassInfo classInfo = classPath.getValue();\n\t\t\tThreadUtil.run(() -> initialize(classInfo));\n\t\t}\n\t}\n\n\t/**\n\t * Button/label detailing the current availability of the {@link #unit}.\n\t */\n\tpublic static class AstAvailabilityButton extends Button {\n\t\tprivate AstAvailabilityButton() {\n\t\t\ttextProperty().bind(Lang.getBinding(\"java.parse-state.initial\"));\n\t\t\tsetGraphic(new FontIconView(CarbonIcons.DOCUMENT_UNKNOWN));\n\t\t\tgetStyleClass().addAll(Styles.ACCENT, Styles.FLAT);\n\t\t\tsetOnAction(e -> {\n\t\t\t\tPopover popover = new Popover();\n\t\t\t\tpopover.setContentNode(new BoundLabel(Lang.getBinding(\"java.parse-state.initial-details\")));\n\t\t\t\tpopover.setArrowLocation(Popover.ArrowLocation.BOTTOM_RIGHT);\n\t\t\t\tpopover.show(this);\n\t\t\t});\n\t\t}\n\n\t\t/**\n\t\t * Called when AST unit is good to use.\n\t\t */\n\t\tprivate void setAvailable() {\n\t\t\tFxThreadUtil.run(() -> setVisible(false));\n\t\t}\n\n\t\t/**\n\t\t * Called when an AST unit exists, but a new one is being made.\n\t\t */\n\t\tprivate void setNewParseInProgress() {\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tsetOnAction(e -> {\n\t\t\t\t\tPopover popover = new Popover();\n\t\t\t\t\tpopover.setContentNode(new BoundLabel(Lang.getBinding(\"java.parse-state.new-progress-details\")));\n\t\t\t\t\tpopover.setArrowLocation(Popover.ArrowLocation.BOTTOM_RIGHT);\n\t\t\t\t\tpopover.show(this);\n\t\t\t\t});\n\t\t\t\ttextProperty().unbind();\n\t\t\t\ttextProperty().bind(Lang.getBinding(\"java.parse-state.new-progress\"));\n\t\t\t\tsetVisible(true);\n\t\t\t});\n\t\t}\n\n\t\t/**\n\t\t * Called when a new AST unit was requested, but nothing was returned by the parser.\n\t\t */\n\t\tprivate void setUnavailable() {\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tsetOnAction(null);\n\t\t\t\ttextProperty().unbind();\n\t\t\t\ttextProperty().bind(Lang.getBinding(\"java.parse-state.error\"));\n\t\t\t\tsetVisible(true);\n\t\t\t});\n\t\t}\n\n\t\t/**\n\t\t * Called when a new AST unit was requested, but an error occurred in parsing.\n\t\t *\n\t\t * @param error\n\t\t * \t\tThe exception result from the parser.\n\t\t */\n\t\tprivate void setParserError(@Nonnull Throwable error) {\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\ttextProperty().unbind();\n\t\t\t\ttextProperty().bind(Lang.getBinding(\"java.parse-state.error\"));\n\t\t\t\tsetOnAction(e -> {\n\t\t\t\t\tBoundLabel title = new BoundLabel(Lang.getBinding(\"java.parse-state.error-details\"));\n\n\t\t\t\t\tString exceptionType = error.getClass().getSimpleName();\n\t\t\t\t\tString message = error.getMessage();\n\n\t\t\t\t\tTextArea errorTextArea = new TextArea();\n\t\t\t\t\terrorTextArea.setEditable(false);\n\t\t\t\t\terrorTextArea.setText(exceptionType + \"\\n\" + \"=\".repeat(exceptionType.length()) + \"\\n\" + message);\n\n\t\t\t\t\tPopover popover = new Popover();\n\t\t\t\t\tpopover.setContentNode(new VBox(title, errorTextArea));\n\t\t\t\t\tpopover.setArrowLocation(Popover.ArrowLocation.BOTTOM_RIGHT);\n\t\t\t\t\tpopover.show(this);\n\t\t\t\t});\n\t\t\t\tsetVisible(true);\n\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/suggest/AssemblerTabCompleter.java",
    "content": "package software.coley.recaf.ui.control.richtext.suggest;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.paint.Color;\nimport me.darknet.assembler.ast.ASTElement;\nimport me.darknet.assembler.ast.primitive.ASTCode;\nimport me.darknet.assembler.util.BlwOpcodes;\nimport me.darknet.assembler.util.EscapeUtil;\nimport org.fxmisc.richtext.CodeArea;\nimport org.fxmisc.richtext.model.PlainTextChange;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport regexodus.Matcher;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceVertex;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.pane.editing.assembler.AssemblerPane;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.util.SVG;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.SortedSet;\nimport java.util.TreeSet;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.stream.Stream;\n\n/**\n * Tab completion for {@link AssemblerPane}.\n *\n * @author Matt Coley\n */\npublic class AssemblerTabCompleter implements TabCompleter<AssemblerTabCompleter.AssemblerCompletion> {\n\tprivate final CompletionPopup<AssemblerCompletion> completionPopup;\n\tprivate final Workspace workspace;\n\tprivate final InheritanceGraph inheritanceGraph;\n\tprivate final CellConfigurationService configurationService;\n\tprivate final TabCompletionConfig config;\n\tprivate CodeArea area;\n\tprivate List<ASTElement> ast;\n\tprivate Context context;\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to pull class info from.\n\t * @param inheritanceGraph\n\t * \t\tGraph to pull hierarchies from.\n\t * @param configurationService\n\t * \t\tService to configure cell content.\n\t * @param config\n\t * \t\tTab completion config.\n\t */\n\tpublic AssemblerTabCompleter(@Nonnull Workspace workspace,\n\t                             @Nonnull InheritanceGraph inheritanceGraph,\n\t                             @Nonnull CellConfigurationService configurationService,\n\t                             @Nonnull TabCompletionConfig config) {\n\t\tthis.workspace = workspace;\n\t\tthis.inheritanceGraph = inheritanceGraph;\n\t\tthis.configurationService = configurationService;\n\t\tthis.completionPopup = new AssemblerCompletionPopup(config);\n\t\tthis.config = config;\n\t}\n\n\t/**\n\t * @param ast\n\t * \t\tAST to work off of.\n\t */\n\tpublic void setAst(@Nonnull List<ASTElement> ast) {\n\t\tthis.ast = ast;\n\t}\n\n\t/**\n\t * Remove AST to work off of.\n\t */\n\tpublic void clearAst() {\n\t\tast = null;\n\t}\n\n\t@Override\n\tpublic boolean requestCompletion(@Nonnull KeyEvent event) {\n\t\t// Recompute line context to ensure its up-to-date.\n\t\trecomputeLineContext();\n\n\t\t// Skip if no text context or empty text context.\n\t\tif (context instanceof EmptyContext)\n\t\t\treturn false;\n\n\t\t// Complete if the completion popup is showing.\n\t\treturn completionPopup.isShowing() && completeFromContext(context.partialMatchedText(), completionPopup::doComplete);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<AssemblerCompletion> computeCurrentCompletions() {\n\t\treturn context.complete().stream()\n\t\t\t\t.filter(completion -> completion.text().length() <= config.getMaxCompletionLength())\n\t\t\t\t.toList();\n\t}\n\n\t@Override\n\tpublic void onFineTextUpdate(@Nonnull PlainTextChange change) {\n\t\trecomputeLineContext();\n\t}\n\n\t@Override\n\tpublic void onRoughTextUpdate(@Nonnull List<PlainTextChange> changes) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic boolean isSpecialCompletableKeyCode(@Nullable KeyCode code) {\n\t\t// Support completing:\n\t\t// - '/' which is used to separate packages in class names\n\t\t// - '[' which is used in array descriptors\n\t\t// - ';' which is used to mark the end of object type descriptors\n\t\treturn code == KeyCode.SLASH || code == KeyCode.OPEN_BRACKET || code == KeyCode.SEMICOLON;\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tarea = editor.getCodeArea();\n\t\tcompletionPopup.install(area, this);\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tcompletionPopup.uninstall();\n\t\tarea = null;\n\t}\n\n\tprivate void recomputeLineContext() {\n\t\t// Must have AST to infer data\n\t\tif (ast == null || ast.isEmpty()) {\n\t\t\tcontext = new EmptyContext();\n\t\t\treturn;\n\t\t}\n\n\t\t// Must be in code block\n\t\tboolean inCode = false;\n\t\tint caret = area.getCaretPosition();\n\t\tASTElement element = ast.getFirst().pick(caret);\n\t\twhile (element != null) {\n\t\t\tif (element instanceof ASTCode code) {\n\t\t\t\tif (code.range().within(caret))\n\t\t\t\t\tinCode = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\telement = element.parent();\n\t\t}\n\t\tif (!inCode) {\n\t\t\tcontext = new EmptyContext();\n\t\t\treturn;\n\t\t}\n\n\t\t// Get the line to regex match, and the trimmed slice of it for tab-completion's \"partial text\" to complete.\n\t\t// The line content we track should be up to the caret, but no further since we're completing the text where\n\t\t// the caret is at and not the end of the line.\n\t\tint caretColumn = area.getCaretColumn();\n\t\tString line = area.getParagraph(area.getCurrentParagraph()).getText();\n\t\tif (caretColumn <= line.length())\n\t\t\tline = line.substring(0, caretColumn);\n\t\tString partialText = line.trim();\n\n\t\t// Skip if there is nothing to complete.\n\t\tif (partialText.isEmpty()) {\n\t\t\tcontext = new EmptyContext();\n\t\t\treturn;\n\t\t}\n\n\t\t// Match for:\n\t\t//  - Opcodes\n\t\t//  - Field references\n\t\t//  - Method references\n\t\tif (RegexUtil.matches(\"^\\\\s*(\\\\w+)\\\\s*$\", line)) {\n\t\t\tcontext = new CodeOpcodeContext(partialText);\n\t\t} else {\n\t\t\t// Field matching\n\t\t\tMatcher matcher = RegexUtil.getMatcher(\"^\\\\s*(?:(?:get|put)(?:static|field))\\\\s+({type}[\\\\w\\\\/]+)?(?:\\\\.({name}\\\\w+)?)?(?:\\\\s+({desc}[\\\\w\\\\/;]+))?\\\\s*$\", line);\n\t\t\tif (matcher.find()) {\n\t\t\t\tString type = matcher.group(\"type\");\n\t\t\t\tString name = matcher.group(\"name\");\n\t\t\t\tString desc = matcher.group(\"desc\");\n\t\t\t\tcontext = new CodeFieldContext(partialText, type, name, desc);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Method matching\n\t\t\tmatcher = RegexUtil.getMatcher(\"^\\\\s*(?:(?:invoke)(?:virtual|interface|special|static)(?:interface)?)\\\\s+\" +\n\t\t\t\t\t\"({type}[\\\\w\\\\/]+)?\" +\n\t\t\t\t\t\"(?:\\\\.({name}\\\\w+)?)?\" +\n\t\t\t\t\t\"(?:\\\\s+({desc}.+))?\\\\s*$\", line);\n\t\t\tif (matcher.find()) {\n\t\t\t\tString type = matcher.group(\"type\");\n\t\t\t\tString name = matcher.group(\"name\");\n\t\t\t\tString desc = matcher.group(\"desc\");\n\t\t\t\tcontext = new CodeMethodContext(partialText, type, name, desc);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Type matching for 'new/checkcast/instanceof'\n\t\t\tmatcher = RegexUtil.getMatcher(\"^\\\\s*(?:new|checkcast|instanceof)\\\\s+({type}[\\\\w\\\\/]+)?\\\\s*$\", line);\n\t\t\tif (matcher.find()) {\n\t\t\t\t// Can re-use field/method ctx here for type name completion\n\t\t\t\tString type = matcher.group(\"type\");\n\t\t\t\tcontext = new CodeMethodContext(partialText, type, null, null);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Unknown\n\t\t\tcontext = new EmptyContext();\n\t\t}\n\t}\n\n\tprivate static boolean completeFromContext(@Nullable String context, @Nonnull Predicate<String> completionHandler) {\n\t\tif (context == null)\n\t\t\treturn false;\n\t\tString trimmedContext = context.trim();\n\t\tif (trimmedContext.isBlank())\n\t\t\treturn false;\n\t\treturn completionHandler.test(trimmedContext);\n\t}\n\n\tprivate class AssemblerCompletionPopup extends CompletionPopup<AssemblerCompletion> {\n\t\tprivate AssemblerCompletionPopup(@Nonnull TabCompletionConfig config) {\n\t\t\tsuper(config, STANDARD_CELL_SIZE, AssemblerCompletion::text, completion -> switch (completion) {\n\t\t\t\tcase AssemblerCompletion.Opcode opcode -> {\n\t\t\t\t\tString text = opcode.text();\n\t\t\t\t\tif (text.startsWith(\"invoke\"))\n\t\t\t\t\t\tyield Icons.getIconView(Icons.METHOD);\n\t\t\t\t\tif (text.startsWith(\"get\") || text.startsWith(\"put\"))\n\t\t\t\t\t\tyield Icons.getIconView(Icons.FIELD);\n\t\t\t\t\tif (text.endsWith(\"load\"))\n\t\t\t\t\t\tyield SVG.ofIconFile(SVG.REF_READ);\n\t\t\t\t\tif (text.endsWith(\"store\") || text.equals(\"iinc\"))\n\t\t\t\t\t\tyield SVG.ofIconFile(SVG.REF_WRITE);\n\t\t\t\t\tif (text.contains(\"const\") || text.equals(\"ldc\"))\n\t\t\t\t\t\tyield Icons.getIconView(Icons.PRIMITIVE);\n\t\t\t\t\tif (text.startsWith(\"if\") || text.endsWith(\"switch\"))\n\t\t\t\t\t\tyield new FontIconView(CarbonIcons.FLOW, Color.STEELBLUE);\n\t\t\t\t\tif (text.endsWith(\"add\"))\n\t\t\t\t\t\tyield new FontIconView(CarbonIcons.ADD, Color.LIMEGREEN);\n\t\t\t\t\tif (text.endsWith(\"sub\") || text.endsWith(\"neg\"))\n\t\t\t\t\t\tyield new FontIconView(CarbonIcons.SUBTRACT, Color.RED);\n\t\t\t\t\tif (text.startsWith(\"dup\"))\n\t\t\t\t\t\tyield new FontIconView(CarbonIcons.COPY, Color.DARKGRAY);\n\t\t\t\t\tif (text.endsWith(\"return\"))\n\t\t\t\t\t\tyield new FontIconView(CarbonIcons.TEXT_NEW_LINE, Color.STEELBLUE);\n\t\t\t\t\tif (text.equals(\"new\") || text.equals(\"checkcast\") || text.equals(\"instanceof\"))\n\t\t\t\t\t\tyield Icons.getIconView(Icons.CLASS);\n\t\t\t\t\tif (text.indexOf('2') == 1) // x2y conversions\n\t\t\t\t\t\tyield SVG.ofIconFile(SVG.TYPE_CONVERSION);\n\t\t\t\t\tif (text.contains(\"cmp\"))\n\t\t\t\t\t\tyield new FontIconView(CarbonIcons.COMPARE, Color.DARKGRAY);\n\t\t\t\t\tif (text.contains(\"array\"))\n\t\t\t\t\t\tyield Icons.getIconView(Icons.ARRAY);\n\t\t\t\t\tif (text.equals(\"athrow\"))\n\t\t\t\t\t\tyield SVG.ofIconFile(SVG.EXCEPTION_BREAKPOINT);\n\t\t\t\t\tyield Icons.getIconView(Icons.INTERNAL);\n\t\t\t\t}\n\t\t\t\tcase AssemblerCompletion.Type type -> configurationService.graphicOf(type.path());\n\t\t\t\tcase AssemblerCompletion.Member member -> configurationService.graphicOf(member.path());\n\t\t\t});\n\t\t}\n\n\t\t@Override\n\t\tpublic void completeCurrentSelection() {\n\t\t\tcompleteFromContext(context.partialMatchedText(), this::doComplete);\n\t\t}\n\t}\n\n\t/**\n\t * Context model.\n\t */\n\tprivate interface Context {\n\t\t@Nonnull\n\t\tdefault List<AssemblerCompletion> complete() {\n\t\t\treturn Collections.emptyList();\n\t\t}\n\n\t\t@Nonnull\n\t\tdefault String partialMatchedText() {\n\t\t\treturn \"\";\n\t\t}\n\t}\n\n\t/**\n\t * Nothing to offer.\n\t */\n\tprivate record EmptyContext() implements Context {}\n\n\t/**\n\t * The user is in the code block with only a single token.\n\t * <br>\n\t * We should offer opcodes.\n\t *\n\t * @param partialInput\n\t * \t\tPartial input text.\n\t */\n\tprivate record CodeOpcodeContext(@Nonnull String partialInput) implements Context {\n\t\tprivate static final SortedSet<String> opcodes = new TreeSet<>();\n\n\t\tstatic {\n\t\t\topcodes.addAll(BlwOpcodes.getFilteredOpcodes().keySet());\n\t\t\topcodes.removeIf(op -> (op.contains(\"store\") || op.contains(\"load\")) && op.indexOf('_') > -1);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String partialMatchedText() {\n\t\t\treturn partialInput;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic List<AssemblerCompletion> complete() {\n\t\t\tList<AssemblerCompletion> items = new ArrayList<>();\n\t\t\tString trimmed = partialInput.trim();\n\t\t\tfor (String opcode : opcodes) {\n\t\t\t\tif (opcode.startsWith(trimmed) && !opcode.equals(trimmed))\n\t\t\t\t\titems.add(new AssemblerCompletion.Opcode(opcode));\n\t\t\t}\n\t\t\treturn items;\n\t\t}\n\t}\n\n\t/**\n\t * The user is in the code block typing out a field reference.\n\t * <br>\n\t * We should offer various parts of the field reference based on what is currently written.\n\t */\n\tprivate class CodeFieldContext extends ReferenceContext {\n\t\t/**\n\t\t * @param partialInput\n\t\t * \t\tPartial input text.\n\t\t * @param owner\n\t\t * \t\tReference owner text. May be partial if the type/name are {@code null}.\n\t\t * @param name\n\t\t * \t\tReference name text. May be partial if the type is {@code null}.\n\t\t * @param desc\n\t\t * \t\tReference type text. May be partial.\n\t\t */\n\t\tpublic CodeFieldContext(@Nonnull String partialInput, @Nullable String owner, @Nullable String name, @Nullable String desc) {\n\t\t\tsuper(partialInput, owner, name, desc);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic List<AssemblerCompletion> complete() {\n\t\t\tboolean isStatic = partialInput.contains(\"getstatic \") || partialInput.contains(\"putstatic \");\n\t\t\treturn complete(workspace, inheritanceGraph, c -> c.fieldStream().filter(m -> isStatic == m.hasStaticModifier()));\n\t\t}\n\t}\n\n\t/**\n\t * The user is in the code block typing out a method reference.\n\t * <br>\n\t * We should offer various parts of the method reference based on what is currently written.\n\t */\n\tprivate class CodeMethodContext extends ReferenceContext {\n\t\t/**\n\t\t * @param partialInput\n\t\t * \t\tPartial input text.\n\t\t * @param owner\n\t\t * \t\tReference owner text. May be partial if the type/name are {@code null}.\n\t\t * @param name\n\t\t * \t\tReference name text. May be partial if the type is {@code null}.\n\t\t * @param desc\n\t\t * \t\tReference type text. May be partial.\n\t\t */\n\t\tCodeMethodContext(@Nonnull String partialInput, @Nullable String owner, @Nullable String name, @Nullable String desc) {\n\t\t\tsuper(partialInput, owner, name, desc);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic List<AssemblerCompletion> complete() {\n\t\t\tboolean isStatic = partialInput.contains(\"invokestatic \") || partialInput.contains(\"invokestaticinterface \");\n\t\t\tboolean isSpecial = partialInput.contains(\"invokespecial \");\n\t\t\treturn complete(workspace, inheritanceGraph, c -> c.methodStream().filter(m -> {\n\t\t\t\t// Only invokespecial can be used to call constructors.\n\t\t\t\tif (m.getName().startsWith(\"<\") && !isSpecial)\n\t\t\t\t\treturn false;\n\n\t\t\t\t// Limit by static matching\n\t\t\t\treturn isStatic == m.hasStaticModifier();\n\t\t\t}));\n\t\t}\n\t}\n\n\t/**\n\t * Type or member reference context.\n\t */\n\tprivate abstract static class ReferenceContext implements Context {\n\t\tprotected final String partialInput;\n\t\tprotected final String owner;\n\t\tprotected final String name;\n\t\tprotected final String desc;\n\n\t\t/**\n\t\t * @param partialInput\n\t\t * \t\tPartial input text.\n\t\t * @param owner\n\t\t * \t\tReference owner text. May be partial if the type/name are {@code null}.\n\t\t * @param name\n\t\t * \t\tReference name text. May be partial if the type is {@code null}.\n\t\t * @param desc\n\t\t * \t\tReference type text. May be partial.\n\t\t */\n\t\tpublic ReferenceContext(@Nonnull String partialInput, @Nullable String owner, @Nullable String name, @Nullable String desc) {\n\t\t\tthis.partialInput = partialInput;\n\t\t\tthis.owner = owner;\n\t\t\tthis.name = name;\n\t\t\tthis.desc = desc;\n\t\t}\n\n\t\t@Override\n\t\t@Nonnull\n\t\tpublic final String partialMatchedText() {\n\t\t\treturn partialInput;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic abstract List<AssemblerCompletion> complete();\n\n\t\t@Nonnull\n\t\tprotected List<AssemblerCompletion> complete(@Nonnull Workspace workspace, @Nonnull InheritanceGraph inheritanceGraph,\n\t\t                                             @Nonnull Function<ClassInfo, Stream<? extends ClassMember>> classMemberLookup) {\n\t\t\t// Skip if no owner type specified.\n\t\t\tif (owner == null) return Collections.emptyList();\n\n\t\t\t// Complete class names if the '.' separator is not seen, otherwise suggest any member in the class.\n\t\t\tif (name == null && desc == null) {\n\t\t\t\t// TODO: startsWith is nice, but mirroring IntelliJ would be nicer\n\t\t\t\t//  - Should be able to complete \"new Strin\" to \"new java/lang/String\"\n\t\t\t\t//    - But out completion system just appends text so we'd need to do some more work there.\n\t\t\t\t//  - Also like IntelliJ we should prioritize common completions over niche ones\n\t\t\t\t//    - Also not something that happens right here\n\t\t\t\tif (partialInput.endsWith(\".\")) {\n\t\t\t\t\tClassPathNode ownerPath = workspace.findClass(owner);\n\t\t\t\t\tif (ownerPath != null) {\n\t\t\t\t\t\tList<ClassMember> items = new ArrayList<>();\n\t\t\t\t\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(owner);\n\t\t\t\t\t\tif (vertex != null)\n\t\t\t\t\t\t\tfor (InheritanceVertex parent : vertex.getAllParents())\n\t\t\t\t\t\t\t\tclassMemberLookup.apply(parent.getValue()).forEach(items::add);\n\t\t\t\t\t\tclassMemberLookup.apply(ownerPath.getValue()).forEach(items::add);\n\t\t\t\t\t\treturn items.stream()\n\t\t\t\t\t\t\t\t.map(ownerPath::child)\n\t\t\t\t\t\t\t\t.map(AssemblerCompletion.Member::new)\n\t\t\t\t\t\t\t\t.map(AssemblerCompletion.class::cast)\n\t\t\t\t\t\t\t\t.toList();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// No separator, user is still typing out a class name.\n\t\t\t\t\treturn workspace.findClasses(c -> c.getName().startsWith(owner)).stream()\n\t\t\t\t\t\t\t.map(AssemblerCompletion.Type::new)\n\t\t\t\t\t\t\t.map(AssemblerCompletion.class::cast)\n\t\t\t\t\t\t\t.toList();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Complete member name/descriptors if we have \"owner.x\"\n\t\t\tif (name != null && desc == null) {\n\t\t\t\tClassPathNode ownerPath = workspace.findClass(owner);\n\t\t\t\tif (ownerPath != null) {\n\t\t\t\t\tList<ClassMember> items = new ArrayList<>();\n\t\t\t\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(owner);\n\t\t\t\t\tPredicate<ClassMember> filter = member -> member.getName().startsWith(name);\n\t\t\t\t\tif (vertex != null)\n\t\t\t\t\t\tfor (InheritanceVertex parent : vertex.getAllParents())\n\t\t\t\t\t\t\tclassMemberLookup.apply(parent.getValue())\n\t\t\t\t\t\t\t\t\t.filter(filter)\n\t\t\t\t\t\t\t\t\t.forEach(items::add);\n\t\t\t\t\tclassMemberLookup.apply(ownerPath.getValue())\n\t\t\t\t\t\t\t.filter(filter)\n\t\t\t\t\t\t\t.forEach(items::add);\n\t\t\t\t\treturn items.stream()\n\t\t\t\t\t\t\t.map(ownerPath::child)\n\t\t\t\t\t\t\t.map(AssemblerCompletion.Member::new)\n\t\t\t\t\t\t\t.map(AssemblerCompletion.class::cast)\n\t\t\t\t\t\t\t.toList();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Complete member descriptors if we have \"owner.name\"\n\t\t\tif (desc != null) {\n\t\t\t\tClassPathNode ownerPath = workspace.findClass(owner);\n\t\t\t\tif (ownerPath != null) {\n\t\t\t\t\tList<ClassMember> items = new ArrayList<>();\n\t\t\t\t\tInheritanceVertex vertex = inheritanceGraph.getVertex(owner);\n\t\t\t\t\tPredicate<ClassMember> filter = member -> member.getName().equals(name) && member.getDescriptor().startsWith(desc);\n\t\t\t\t\tif (vertex != null)\n\t\t\t\t\t\tfor (InheritanceVertex parent : vertex.getAllParents())\n\t\t\t\t\t\t\tclassMemberLookup.apply(parent.getValue())\n\t\t\t\t\t\t\t\t\t.filter(filter)\n\t\t\t\t\t\t\t\t\t.forEach(items::add);\n\t\t\t\t\tclassMemberLookup.apply(ownerPath.getValue())\n\t\t\t\t\t\t\t.filter(filter)\n\t\t\t\t\t\t\t.forEach(items::add);\n\t\t\t\t\treturn items.stream()\n\t\t\t\t\t\t\t.map(ownerPath::child)\n\t\t\t\t\t\t\t.map(AssemblerCompletion.Member::new)\n\t\t\t\t\t\t\t.map(AssemblerCompletion.class::cast)\n\t\t\t\t\t\t\t.toList();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn Collections.emptyList();\n\t\t}\n\t}\n\n\t/**\n\t * Completion value type with subtypes for different kinds of completable assembler content.\n\t */\n\tpublic sealed interface AssemblerCompletion {\n\t\t/**\n\t\t * @return Text to complete.\n\t\t */\n\t\t@Nonnull\n\t\tString text();\n\n\t\t/**\n\t\t * @param text\n\t\t * \t\tOpcode name.\n\t\t */\n\t\trecord Opcode(@Nonnull String text) implements AssemblerCompletion {}\n\n\t\t/**\n\t\t * @param path\n\t\t * \t\tPath to type to complete.\n\t\t */\n\t\trecord Type(@Nonnull ClassPathNode path) implements AssemblerCompletion {\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic String text() {\n\t\t\t\treturn EscapeUtil.escapeLiteral(path.getValue().getName());\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * @param path\n\t\t * \t\tPath to member to complete.\n\t\t */\n\t\trecord Member(@Nonnull ClassMemberPathNode path) implements AssemblerCompletion {\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic String text() {\n\t\t\t\tClassMember member = path.getValue();\n\t\t\t\tString name = EscapeUtil.escapeLiteral(member.getName());\n\t\t\t\tString desc = EscapeUtil.escapeLiteral(member.getDescriptor());\n\t\t\t\treturn name + \" \" + desc;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/suggest/CompletionPopup.java",
    "content": "package software.coley.recaf.ui.control.richtext.suggest;\n\nimport com.sun.javafx.util.Utils;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.geometry.BoundingBox;\nimport javafx.geometry.Bounds;\nimport javafx.geometry.Rectangle2D;\nimport javafx.scene.Node;\nimport javafx.scene.control.IndexRange;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.ListView;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.input.MouseEvent;\nimport javafx.stage.Popup;\nimport javafx.stage.Screen;\nimport org.fxmisc.richtext.CodeArea;\nimport software.coley.recaf.ui.control.richtext.Editor;\n\nimport java.util.List;\nimport java.util.Optional;\n\nimport static javafx.scene.input.KeyCode.ENTER;\nimport static javafx.scene.input.KeyCode.TAB;\n\n/**\n * Basic completion popup implementation.\n *\n * @param <T>\n * \t\tCompletion value type.\n *\n * @author Matt Coley\n */\npublic abstract class CompletionPopup<T> {\n\tpublic static final int STANDARD_CELL_SIZE = 20;\n\tprivate final Popup popup = new Popup();\n\tprivate final ListView<T> listView = new ListView<>();\n\tprivate final ScrollPane scrollPane = new ScrollPane(listView);\n\tprivate final CompletionValueTextifier<T> textifier;\n\tprivate final TabCompletionConfig config;\n\tprivate final int cellSize;\n\tprivate CompletionPopupFocuser completionPopupFocuser;\n\tprivate CompletionPopupUpdater<T> completionPopupUpdater;\n\tprivate CodeArea area;\n\tprivate Bounds lastCaretBounds;\n\tprivate int popupSize;\n\tprivate T selected;\n\n\t/**\n\t * @param config\n\t * \t\tTab completion config.\n\t * @param cellSize\n\t * \t\tHeight of cells in the list-view of completion items.\n\t * @param textifier\n\t * \t\tMapper of {@code T} values to {@code String}.\n\t */\n\tpublic CompletionPopup(@Nonnull TabCompletionConfig config, int cellSize,\n\t                       @Nonnull CompletionValueTextifier<T> textifier) {\n\t\tthis(config, cellSize, textifier, t -> null);\n\t}\n\n\t/**\n\t * @param config\n\t * \t\tTab completion config.\n\t * @param cellSize\n\t * \t\tHeight of cells in the list-view of completion items.\n\t * @param textifier\n\t * \t\tMapper of {@code T} values to {@code String}.\n\t * @param graphifier\n\t * \t\tMapper of {@code T} values to display graphics.\n\t */\n\tpublic CompletionPopup(@Nonnull TabCompletionConfig config, int cellSize,\n\t                       @Nonnull CompletionValueTextifier<T> textifier,\n\t                       @Nonnull CompletionValueGraphicMapper<T> graphifier) {\n\t\tthis.config = config;\n\t\tthis.textifier = textifier;\n\n\t\t// Ensure scroll-pane is 'fit-to-height' so there's no empty space wasting virtual scroll space.\n\t\tscrollPane.setFitToHeight(true);\n\t\tscrollPane.setFitToWidth(true);\n\t\tpopup.getContent().add(scrollPane);\n\n\t\t// Auto-hide covers most cases that would be hard to otherwise detect\n\t\t// and isn't overly aggressive to hide it too often.\n\t\tpopup.setAutoHide(true);\n\n\t\t// Auto-fix can move the popup infront of the caret, which is not what we want.\n\t\t// In #show() we will manually adjust the position of the popup to ensure it is on screen.\n\t\tpopup.setAutoFix(false);\n\n\t\t// For simplicity of a few operations we're going to ensure all cells are a fixed size.\n\t\tlistView.setFixedCellSize(this.cellSize = cellSize);\n\t\tlistView.setPrefWidth(400);\n\n\t\t// Track what is the 'selected' value to complete.\n\t\tlistView.getSelectionModel().selectedItemProperty()\n\t\t\t\t.addListener((observable, oldValue, newValue) -> selected = newValue);\n\n\t\t// On mouse release should fire after a click event.\n\t\t// The selected item should be up-to-date, so triggering a completion request should work out.\n\t\tlistView.setOnMouseReleased(event -> completeCurrentSelection());\n\n\t\t// The list-view event handling is a bit weird and breaks 'tab' handling.\n\t\t// So what we'll do is disable focus traversal so tht it doesn't handle anything\n\t\t// unless we specifically tell it to do so.\n\t\tlistView.setFocusTraversable(false);\n\t\tpopup.addEventFilter(KeyEvent.KEY_PRESSED, event -> {\n\t\t\tKeyCode code = event.getCode();\n\n\t\t\t// The event target is initially the popup scene.\n\t\t\t// If the event is not a 'tab' completion we can pass it along.\n\t\t\t// To prevent an infinite loop, we'll change the target value.\n\t\t\tif (event.getTarget() == popup.getScene() && code != TAB && code != ENTER) {\n\t\t\t\tlistView.fireEvent(event.copyFor(popup, listView));\n\n\t\t\t\t// Multi-key action like 'Control Z' fail if we consume the event.\n\t\t\t\tif (!event.isControlDown()) event.consume();\n\t\t\t}\n\t\t});\n\n\t\t// Map the completion values to a nicer display format.\n\t\tlistView.setCellFactory(v -> new ListCell<>() {\n\t\t\t@Override\n\t\t\tprotected void updateItem(T item, boolean empty) {\n\t\t\t\tsuper.updateItem(item, empty);\n\t\t\t\tif (empty || item == null) {\n\t\t\t\t\tsetText(null);\n\t\t\t\t\tsetGraphic(null);\n\t\t\t\t} else {\n\t\t\t\t\tsetText(textifier.toText(item));\n\t\t\t\t\tsetGraphic(graphifier.toGraphic(item));\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Installs this popup into the given editor using the given tab-completer.\n\t * <br>\n\t * Intended to be called from the {@link TabCompleter#install(Editor)} implementation.\n\t *\n\t * @param area\n\t * \t\tEditor code area component to register events within.\n\t * @param completer\n\t * \t\tTab completer this popup will be associated with.\n\t */\n\tpublic void install(@Nonnull CodeArea area, @Nonnull TabCompleter<T> completer) {\n\t\tsetArea(area);\n\n\t\tcompletionPopupFocuser = new CompletionPopupFocuser(this);\n\t\tcompletionPopupUpdater = new CompletionPopupUpdater<>(completer, this);\n\n\t\tarea.addEventFilter(KeyEvent.KEY_RELEASED, completionPopupUpdater);\n\t\tarea.addEventFilter(KeyEvent.KEY_TYPED, completionPopupFocuser);\n\n\t\t// Clicking off of the popup should hide it.\n\t\t// - Clicking on the area is not covered by auto-hide.\n\t\t//   - But clicking on other components is covered.\n\t\t// - Clicking on the area delays the event until after the caret bounds update\n\t\t//   which makes the popup jump around when clicking off the popup.\n\t\t//   - Which we can bypass by hooking the events of the area's parent node.\n\t\tarea.getParent().addEventFilter(MouseEvent.MOUSE_PRESSED, e -> hide());\n\n\t\t// When we scroll the caret bounds change.\n\t\tarea.caretBoundsProperty().addListener((observable, oldValue, newValue) -> {\n\t\t\tif (isShowing() && updateCaretBoundsIndirect())\n\t\t\t\tshow();\n\t\t});\n\t}\n\n\t/**\n\t * Uninstalls this popup from the editor provided in {@link #install(CodeArea, TabCompleter)}.\n\t * <br>\n\t * Intended to be called from the {@link TabCompleter#uninstall(Editor)} implementation.\n\t */\n\tpublic void uninstall() {\n\t\tCodeArea localArea = area;\n\t\tif (localArea != null) {\n\t\t\tif (completionPopupUpdater != null)\n\t\t\t\tlocalArea.removeEventFilter(KeyEvent.KEY_RELEASED, completionPopupUpdater);\n\t\t\tif (completionPopupFocuser != null)\n\t\t\t\tlocalArea.removeEventFilter(KeyEvent.KEY_TYPED, completionPopupFocuser);\n\t\t}\n\t\tsetArea(null);\n\t}\n\n\t/**\n\t * Completes the current selection.\n\t */\n\tpublic abstract void completeCurrentSelection();\n\n\t/**\n\t * @param area\n\t * \t\tArea the popup should show within.\n\t */\n\tpublic void setArea(@Nullable CodeArea area) {\n\t\tthis.area = area;\n\t}\n\n\t/**\n\t * @return Current completion candidate to fill in if {@link TabCompleter#requestCompletion(KeyEvent)} is called.\n\t */\n\t@Nullable\n\tpublic T getSelected() {\n\t\treturn selected;\n\t}\n\n\t/**\n\t * @return {@link Popup#isShowing()}\n\t */\n\tpublic boolean isShowing() {\n\t\treturn popup.isShowing();\n\t}\n\n\t/**\n\t * @see Popup#requestFocus()\n\t */\n\tpublic void requestFocus() {\n\t\tpopup.requestFocus();\n\t}\n\n\t/**\n\t * @see Popup#hide()\n\t */\n\tpublic void hide() {\n\t\tif (isShowing())\n\t\t\tpopup.hide();\n\t}\n\n\t/**\n\t * Shows the popup at the current caret position in the editor.\n\t *\n\t * @see Popup#show(Node, double, double)\n\t */\n\tpublic void show() {\n\t\t// Do nothing if there is no content to display (popup size calculated to be 0)\n\t\tif (popupSize <= 0)\n\t\t\treturn;\n\n\t\tdouble anchorX = config.getPopupPosition().isRight()\n\t\t\t\t? lastCaretBounds.getMaxX()\n\t\t\t\t: lastCaretBounds.getMinX() - popup.getWidth();\n\t\tdouble anchorY = config.getPopupPosition().isAbove()\n\t\t\t\t? lastCaretBounds.getMinY() - popupSize\n\t\t\t\t: lastCaretBounds.getMaxY();\n\n\t\t// Choose another position if the popup is off-screen.\n\t\t// if the popup is off-screen, flip the popup to the other side of the caret on that axis\n\t\t// (it will also avoid popup from being split between two screens)\n\t\t//\n\t\t// Code loosely adapted from PopupWindow#updateWindow(double, double)\n\t\tfinal Screen currentScreen = Utils.getScreenForPoint(anchorX, anchorY);\n\t\tfinal Rectangle2D screenBounds =\n\t\t\t\tUtils.hasFullScreenStage(currentScreen)\n\t\t\t\t\t\t? currentScreen.getBounds()\n\t\t\t\t\t\t: currentScreen.getVisualBounds();\n\t\tif (anchorY + popupSize > screenBounds.getMaxY()) {\n\t\t\tanchorY = lastCaretBounds.getMinY() - popupSize;\n\t\t} else if (anchorY < screenBounds.getMinY()) {\n\t\t\tanchorY = lastCaretBounds.getMaxY();\n\t\t}\n\t\tif (anchorX + popup.getWidth() > screenBounds.getMaxX()) {\n\t\t\tanchorX = lastCaretBounds.getMinX() - popup.getWidth();\n\t\t} else if (anchorX < screenBounds.getMinX()) {\n\t\t\tanchorX = lastCaretBounds.getMaxX();\n\t\t}\n\n\t\tpopup.show(area, anchorX, anchorY);\n\t}\n\n\t/**\n\t * @return {@code true} when the bounds were updated successfully.\n\t */\n\tpublic boolean updateCaretBounds() {\n\t\tBounds bounds = area.caretBoundsProperty().getValue().orElse(null);\n\t\tif (bounds != null) {\n\t\t\tlastCaretBounds = bounds;\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * @return {@code true} when the bounds were updated successfully.\n\t */\n\tpublic boolean updateCaretBoundsIndirect() {\n\t\tBounds bounds = lastCaretBounds;\n\t\tif (bounds == null)\n\t\t\treturn false;\n\n\t\t// The caret bounds can de-sync in some weird cases, so we're better off indirectly computing the current Y position\n\t\t// of the caret by looking at the paragraph's bounds the caret resides in.\n\t\tOptional<Bounds> paragraphBoundsOnScreen = area.getParagraphBoundsOnScreen(Math.min(area.getCurrentParagraph(), area.lastVisibleParToAllParIndex()));\n\t\tif (paragraphBoundsOnScreen.isPresent()) {\n\t\t\tBounds paragraphBounds = paragraphBoundsOnScreen.get();\n\t\t\tlastCaretBounds = new BoundingBox(bounds.getMinX(), paragraphBounds.getMinY(), bounds.getWidth(), bounds.getHeight());\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @return {@code true} when the associated text area has selected text.\n\t */\n\tpublic boolean hasTextSelection() {\n\t\treturn area.getSelection().getLength() > 0;\n\t}\n\n\t/**\n\t * Updates the items to support completion of, and updates the size of the UI\n\t * to fit exactly the number of items to show.\n\t *\n\t * @param items\n\t * \t\tItems to support completion of.\n\t */\n\tpublic void updateItems(@Nonnull List<T> items) {\n\t\t// Compute the size of the popup.\n\t\t//  - (Number of cells to show at a time) * (size of cell)\n\t\t//  - Needs a bit of padding due to the way borders/scrollbars render\n\t\t// The scollpane should be dictating the size since it is the popup content root.\n\t\tint itemCount = items.size();\n\t\tpopupSize = cellSize * (Math.min(itemCount, config.getMaxCompletionRows()) + 1);\n\t\tscrollPane.setPrefHeight(popupSize);\n\t\tscrollPane.setMaxHeight(popupSize);\n\n\t\t// Track what was selected beforehand.\n\t\t// Needs to be done before we reset the list-view items.\n\t\tT localSelected = selected;\n\n\t\t// Update the lists items.\n\t\tlistView.getItems().setAll(items);\n\n\t\t// Select what was previously selected, or the first item if nothing was selected before.\n\t\tif (localSelected != null) {\n\t\t\tint index = items.indexOf(localSelected);\n\t\t\tif (index >= 0 && index < itemCount)\n\t\t\t\tlistView.getSelectionModel().select(index);\n\t\t\telse\n\t\t\t\tlistView.getSelectionModel().select(0);\n\t\t} else if (itemCount > 0) {\n\t\t\tlistView.getSelectionModel().select(0);\n\t\t}\n\t}\n\n\t/**\n\t * Completes the current text with the value of {@link #getSelected()}.\n\t *\n\t * @param partialText\n\t * \t\tCurrent text to complete.\n\t *\n\t * @return {@code true} on successful completion.\n\t */\n\tpublic boolean doComplete(@Nonnull String partialText) {\n\t\t// Get the selected value to complete.\n\t\t// Must be done before we hide the popup.\n\t\tT localSelected = selected;\n\n\t\t// We're done so we can hide the popup.\n\t\thide();\n\n\t\t// Complete the selected item, clearing it now that we're done.\n\t\tif (localSelected != null) {\n\t\t\tselected = null;\n\t\t\tString toComplete = textifier.toText(localSelected);\n\t\t\treturn complete(partialText, toComplete);\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprotected boolean complete(@Nonnull String partialText, @Nonnull String fullText) {\n\t\t// Skip if the partial text (the current line) does not represent a substring of the text to complete (the full text)\n\t\tif (!fullText.startsWith(partialText)) {\n\t\t\t// If the current partial text has separators, check if the full text completion\n\t\t\t// begins at a point AFTER the separator.\n\t\t\tint space = partialText.indexOf(' ');\n\t\t\tif (space >= 0)\n\t\t\t\treturn complete(partialText.substring(space + 1), fullText);\n\t\t\tint dot = partialText.indexOf('.');\n\t\t\tif (dot >= 0)\n\t\t\t\treturn complete(partialText.substring(dot + 1), fullText);\n\n\t\t\t// If the partial text has no separators, check if the full text completion  has separators.\n\t\t\t// If it does, then we'll try to complete a substring of the original completion starting at the separator.\n\t\t\tspace = fullText.indexOf(' ');\n\t\t\tif (space >= 0)\n\t\t\t\treturn complete(partialText, fullText.substring(space + 1));\n\t\t\tdot = fullText.indexOf('.');\n\t\t\tif (dot >= 0)\n\t\t\t\treturn complete(partialText, fullText.substring(dot + 1));\n\n\t\t\t// No sub-sequence of the existing partial text and the text to complete matches.\n\t\t\treturn false;\n\t\t}\n\n\t\t// Insert the rest of the text.\n\t\tString remainingWordText = fullText.substring(partialText.length());\n\t\tarea.insertText(area.getCaretPosition(), remainingWordText);\n\n\t\t// Show completion centered on the screen if it is off-screen.\n\t\tif (area.getCaretBounds().isEmpty())\n\t\t\tarea.showParagraphAtCenter(area.getCurrentParagraph());\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Pass along backspace handling when we observe {@link KeyCode#BACK_SPACE} in a {@link KeyEvent}.\n\t */\n\tpublic void handleBackspace() {\n\t\tIndexRange selection = area.getSelection();\n\t\tif (selection.getLength() > 1) {\n\t\t\tarea.deleteText(selection);\n\t\t} else {\n\t\t\tarea.deletePreviousChar();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/suggest/CompletionPopupFocuser.java",
    "content": "package software.coley.recaf.ui.control.richtext.suggest;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.event.EventHandler;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\n\nimport static javafx.scene.input.KeyCode.DOWN;\nimport static javafx.scene.input.KeyCode.UP;\n\n/**\n * An event handler to target {@link KeyEvent#KEY_TYPED} which passes focus requests to the {@link CompletionPopup}.\n *\n * @author Matt Coley\n */\npublic class CompletionPopupFocuser implements EventHandler<KeyEvent> {\n\tprivate final CompletionPopup<?> completionPopup;\n\n\t/**\n\t * @param completionPopup\n\t * \t\tPopup to send focus events to.\n\t */\n\tpublic CompletionPopupFocuser(@Nonnull CompletionPopup<?> completionPopup) {\n\t\tthis.completionPopup = completionPopup;\n\t}\n\n\t@Override\n\tpublic void handle(@Nonnull KeyEvent event) {\n\t\t// Ensure directional / input keys are sent to the popup.\n\t\tKeyCode code = event.getCode();\n\t\tboolean move = code == UP || code == DOWN;\n\t\tif (completionPopup.isShowing() && move)\n\t\t\tcompletionPopup.requestFocus();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/suggest/CompletionPopupUpdater.java",
    "content": "package software.coley.recaf.ui.control.richtext.suggest;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.event.EventHandler;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\n\nimport java.util.List;\n\nimport static javafx.scene.input.KeyCode.*;\n\n/**\n * An event handler to target {@link KeyEvent#KEY_RELEASED} which updates\n * the items and visibility state of a {@link CompletionPopup}.\n *\n * @author Matt Coley\n */\npublic class CompletionPopupUpdater<T> implements EventHandler<KeyEvent> {\n\tprivate final TabCompleter<T> tabCompleter;\n\tprivate final CompletionPopup<T> completionPopup;\n\n\tpublic CompletionPopupUpdater(@Nonnull TabCompleter<T> tabCompleter,\n\t                              @Nonnull CompletionPopup<T> completionPopup) {\n\t\tthis.tabCompleter = tabCompleter;\n\t\tthis.completionPopup = completionPopup;\n\t}\n\n\t@Override\n\tpublic void handle(KeyEvent event) {\n\t\tKeyCode code = event.getCode();\n\n\t\t// Remove popup if backspace is pressed.\n\t\tif (code == BACK_SPACE) {\n\t\t\t// If the popup is shown, one backspace will be consumed, so we must\n\t\t\t// tell it to manually pass along a backspace to the editor.\n\t\t\tif (completionPopup.isShowing()) {\n\t\t\t\tcompletionPopup.handleBackspace();\n\t\t\t\tcompletionPopup.hide();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove popup if escape is pressed.\n\t\tif (code == ESCAPE) {\n\t\t\tcompletionPopup.hide();\n\t\t\tevent.consume();\n\t\t\treturn;\n\t\t}\n\n\t\t// Skip handling for navigation keys.\n\t\t// These move around the completion menu.\n\t\tif (code.isArrowKey() || code.isNavigationKey() || code == TAB || code == ENTER) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Any non-word character should remove the popup.\n\t\t// Modifiers are also excluded so capitalizing letters won't hide the popup.\n\t\tif (!code.isLetterKey() && !code.isDigitKey() && !code.isModifierKey() && code != PERIOD && !tabCompleter.isSpecialCompletableKeyCode(code)) {\n\t\t\tcompletionPopup.hide();\n\t\t\treturn;\n\t\t}\n\n\t\t// Update the bounds, or hide the popup if the caret bounds cannot be found.\n\t\tif (!completionPopup.updateCaretBounds()) {\n\t\t\tcompletionPopup.hide();\n\t\t\treturn;\n\t\t}\n\n\t\t// Hide if there is selected text. We only tab complete as a word is being written.\n\t\tif (completionPopup.hasTextSelection()) {\n\t\t\tcompletionPopup.hide();\n\t\t\treturn;\n\t\t}\n\n\t\t// Fill completion candidates\n\t\tList<T> items = tabCompleter.computeCurrentCompletions();\n\n\t\t// Update the popup\n\t\tcompletionPopup.updateItems(items);\n\t\tif (!items.isEmpty())\n\t\t\tcompletionPopup.show();\n\t\telse\n\t\t\tcompletionPopup.hide();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/suggest/CompletionValueGraphicMapper.java",
    "content": "package software.coley.recaf.ui.control.richtext.suggest;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.Node;\n\n/**\n * Basic T value to graphic mapper.\n *\n * @param <T>\n * \t\tCompletion value type.\n *\n * @author Matt Coley\n */\npublic interface CompletionValueGraphicMapper<T> {\n\t/**\n\t * @param value\n\t * \t\tValue to represent.\n\t *\n\t * @return Visual accompanying graphic of the value.\n\t */\n\t@Nullable\n\tNode toGraphic(@Nonnull T value);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/suggest/CompletionValueTextifier.java",
    "content": "package software.coley.recaf.ui.control.richtext.suggest;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Basic T value to string mapper.\n *\n * @param <T>\n * \t\tCompletion value type.\n *\n * @author Matt Coley\n */\npublic interface CompletionValueTextifier<T> {\n\t/**\n\t * @param value\n\t * \t\tValue to represent as a string.\n\t *\n\t * @return String representation of the value.\n\t */\n\t@Nonnull\n\tString toText(@Nonnull T value);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/suggest/ExistingWordTabCompleter.java",
    "content": "package software.coley.recaf.ui.control.richtext.suggest;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.input.KeyEvent;\nimport org.fxmisc.richtext.CodeArea;\nimport org.fxmisc.richtext.model.PlainTextChange;\nimport regexodus.Matcher;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.util.RegexUtil;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Predicate;\n\n/**\n * A simple completer where completion candidate phrases are just words found in the current document.\n *\n * @author Matt Coley\n */\npublic class ExistingWordTabCompleter implements TabCompleter<String> {\n\tprivate final Set<String> words = ConcurrentHashMap.newKeySet();\n\tprivate final CompletionPopup<String> completionPopup;\n\tprivate CodeArea area;\n\tprivate String lineContext;\n\n\t/**\n\t * @param config\n\t * \t\tTab completion config.\n\t */\n\tpublic ExistingWordTabCompleter(@Nonnull TabCompletionConfig config) {\n\t\tcompletionPopup = new StringCompletionPopup(config);\n\t}\n\n\t@Override\n\tpublic boolean requestCompletion(@Nonnull KeyEvent event) {\n\t\t// Recompute line context to ensure its up-to-date.\n\t\tString localContext = recomputeLineContext();\n\n\t\t// Skip if no text context or empty text context.\n\t\tif (localContext.isBlank())\n\t\t\treturn false;\n\n\t\t// Complete if the completion popup is showing.\n\t\treturn completionPopup.isShowing() && completeFromContext(localContext, completionPopup::doComplete);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic List<String> computeCurrentCompletions() {\n\t\tList<String> items = new ArrayList<>();\n\t\tString localContext = lineContext;\n\t\tString group = null;\n\t\tMatcher matcher = RegexUtil.getMatcher(\"\\\\w+\", localContext);\n\t\twhile (matcher.find())\n\t\t\tgroup = matcher.group();\n\t\tif (group != null)\n\t\t\tfor (String word : words)\n\t\t\t\tif (word.startsWith(group) && !word.equals(group))\n\t\t\t\t\titems.add(word);\n\t\treturn items;\n\t}\n\n\t@Override\n\tpublic void onFineTextUpdate(@Nonnull PlainTextChange change) {\n\t\trecomputeLineContext();\n\t}\n\n\t@Override\n\tpublic void onRoughTextUpdate(@Nonnull List<PlainTextChange> changes) {\n\t\tfor (PlainTextChange change : changes) {\n\t\t\tString inserted = change.getInserted();\n\t\t\tif (inserted != null && !inserted.isBlank()) {\n\t\t\t\tString[] changedWords = inserted.split(\"\\\\W+\");\n\t\t\t\tCollections.addAll(words, changedWords);\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tarea = editor.getCodeArea();\n\t\tcompletionPopup.install(area, this);\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tcompletionPopup.uninstall();\n\t\tarea = null;\n\t}\n\n\t@Nonnull\n\tprivate String recomputeLineContext() {\n\t\tString line = area.getParagraph(area.getCurrentParagraph()).getText();\n\t\treturn lineContext = computeCompletionContext(line, area.getCaretColumn());\n\t}\n\n\tprivate boolean completeFromCurrentContext(@Nonnull Predicate<String> completionHandler) {\n\t\treturn completeFromContext(lineContext, completionHandler);\n\t}\n\n\tprivate static boolean completeFromContext(@Nullable String context, @Nonnull Predicate<String> completionHandler) {\n\t\tif (context == null)\n\t\t\treturn false;\n\t\tString group = null;\n\t\tMatcher matcher = RegexUtil.getMatcher(\"\\\\w+\", context);\n\t\twhile (matcher.find())\n\t\t\tgroup = matcher.group();\n\t\treturn group != null && completionHandler.test(group);\n\t}\n\n\t@Nonnull\n\tprivate static String computeCompletionContext(@Nonnull String line, int column) {\n\t\t// Nothing to complete from an empty line.\n\t\tif (column == 0) return \"\";\n\n\t\t// Valid cases:\n\t\t//  foo.[TAB]\n\t\t//  foo.a[TAB]\n\t\t//\n\t\t// Invalid cases:\n\t\t//  fo[TAB]o\n\t\t//  [TAB]\n\n\t\tString preColumn = line.substring(0, column);\n\t\tchar preColumnLastChar = preColumn.charAt(preColumn.length() - 1);\n\n\t\t// Check for '.' used as context before the tab was pressed.\n\t\tif (preColumnLastChar == '.') return preColumn.trim();\n\n\t\t// Ensure the content in front of the caret is empty.\n\t\tString postColumn = line.substring(column);\n\t\tif (postColumn.isBlank()) return preColumn.trim();\n\n\t\t// Not a valid case.\n\t\treturn \"\";\n\t}\n\n\tprivate class StringCompletionPopup extends CompletionPopup<String> {\n\t\tprivate StringCompletionPopup(@Nonnull TabCompletionConfig config) {\n\t\t\tsuper(config, STANDARD_CELL_SIZE, t -> t);\n\t\t}\n\n\t\t@Override\n\t\tpublic void completeCurrentSelection() {\n\t\t\tcompleteFromCurrentContext(this::doComplete);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/suggest/TabCompleter.java",
    "content": "package software.coley.recaf.ui.control.richtext.suggest;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport org.fxmisc.richtext.model.PlainTextChange;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.EditorComponent;\n\nimport java.util.List;\n\n/**\n * Outline of tab completion.\n *\n * @param <T>\n * \t\tCompletion value type.\n *\n * @author Matt Coley\n */\npublic interface TabCompleter<T> extends EditorComponent {\n\t/**\n\t * Attempts to fill in the current completion candidate, if one is selected and the tab-completion overlay is active.\n\t *\n\t * @param event\n\t * \t\tKey event where {@link KeyCode#TAB} or {@link KeyCode#ENTER} was pressed.\n\t *\n\t * @return {@code true} when a completion is made.\n\t */\n\tboolean requestCompletion(@Nonnull KeyEvent event);\n\n\t/**\n\t * @return List of potential completions based on the current state.\n\t */\n\t@Nonnull\n\tList<T> computeCurrentCompletions();\n\n\t/**\n\t * Called when the linked {@link Editor} has updated its text.\n\t * This is called for <i>ANY</i> and <i>ALL</i> changes.\n\t * <p>\n\t * Implementations should update any state that is cheap to maintain in here.\n\t */\n\tvoid onFineTextUpdate(@Nonnull PlainTextChange changes);\n\n\t/**\n\t * Called when the linked {@link Editor} has updated its text, with reduced successions.\n\t * <p>\n\t * Implementations should update any state that is expensive to maintain in here.\n\t */\n\tvoid onRoughTextUpdate(@Nonnull List<PlainTextChange> changes);\n\n\t/**\n\t * Called when the {@link CompletionPopupUpdater} observes a typed non-letter/non-digit {@link KeyCode}.\n\t * <p>\n\t * Implementations can check for key-codes not covered by {@link KeyCode#isLetterKey()} and {@link KeyCode#isDigitKey()}\n\t * to support completion of additional characters. For example the {@code []} characters are not covered by default\n\t * and an implementation would need to explicitly support them via this method.\n\t *\n\t * @param code Key-code to check for tab-completion support.\n\t * @return {@code true} when this completer supports the tab-completion of the given key-code.\n\t */\n\tdefault boolean isSpecialCompletableKeyCode(@Nullable KeyCode code) {\n\t\treturn false;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/suggest/TabCompletionConfig.java",
    "content": "package software.coley.recaf.ui.control.richtext.suggest;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.ui.pane.editing.assembler.AssemblerPane;\n\n/**\n * Config for {@link TabCompleter} use cases.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class TabCompletionConfig extends BasicConfigContainer {\n\tprivate final ObservableObject<PopupPosition> popupPosition = new ObservableObject<>(PopupPosition.ABOVE_RIGHT);\n\tprivate final ObservableBoolean enabledInAssembler = new ObservableBoolean(true);\n\tprivate final ObservableInteger maxCompletionRows = new ObservableInteger(15);\n\tprivate final ObservableInteger maxCompletionLength = new ObservableInteger(200);\n\n\t@Inject\n\tpublic TabCompletionConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, \"tab-completion\" + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"popup-position\", PopupPosition.class, popupPosition));\n\t\taddValue(new BasicConfigValue<>(\"enabled-in-assembler\", boolean.class, enabledInAssembler));\n\t\taddValue(new BasicConfigValue<>(\"max-completion-rows\", int.class, maxCompletionRows));\n\t\taddValue(new BasicConfigValue<>(\"max-completion-length\", int.class, maxCompletionLength));\n\t}\n\n\t/**\n\t * @return Current popup position.\n\t */\n\t@Nonnull\n\tpublic PopupPosition getPopupPosition() {\n\t\treturn popupPosition.getValue();\n\t}\n\n\t/**\n\t * @return {@code true} when tab completion in the {@link AssemblerPane} should be registered.\n\t */\n\tpublic boolean isEnabledInAssembler() {\n\t\treturn enabledInAssembler.getValue();\n\t}\n\n\t/**\n\t * @return Number of completions to visually show in a popup/overlay. Always {@code >= 1}.\n\t */\n\tpublic int getMaxCompletionRows() {\n\t\treturn Math.max(1, maxCompletionRows.getValue());\n\t}\n\n\t/**\n\t * @return Max length of a completion string to allow.\n\t */\n\tpublic int getMaxCompletionLength() {\n\t\treturn maxCompletionLength.getValue();\n\t}\n\n\tpublic enum PopupPosition {\n\t\t/**\n\t\t * Popup appears above and to right of the cursor\n\t\t */\n\t\tABOVE_RIGHT,\n\t\t/**\n\t\t * Popup appears below and to right of the cursor\n\t\t */\n\t\tBELOW_RIGHT,\n\t\t/**\n\t\t * Popup appears above and to left of the cursor\n\t\t */\n\t\tABOVE_LEFT,\n\t\t/**\n\t\t * Popup appears below and to left of the cursor\n\t\t */\n\t\tBELOW_LEFT;\n\n\t\tpublic boolean isAbove() {\n\t\t\treturn this == ABOVE_RIGHT || this == ABOVE_LEFT;\n\t\t}\n\n\t\tpublic boolean isRight() {\n\t\t\treturn this == ABOVE_RIGHT || this == BELOW_RIGHT;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/syntax/AbstractSyntaxHighlighter.java",
    "content": "package software.coley.recaf.ui.control.richtext.syntax;\n\nimport jakarta.annotation.Nonnull;\nimport org.fxmisc.richtext.model.StyleSpan;\nimport org.fxmisc.richtext.model.StyleSpans;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\n\nimport java.util.Collection;\nimport java.util.Collections;\n\n/**\n * Common base for syntax highlighter implementations.\n *\n * @author Matt Coley\n */\npublic abstract class AbstractSyntaxHighlighter implements SyntaxHighlighter {\n\tprivate static final Logger logger = Logging.get(AbstractSyntaxHighlighter.class);\n\tprivate static final int DEFAULT_MAX_SPANS = 400_000;\n\tprivate static final int DEFAULT_MAX_SPANS_PER_LINE = 25_000;\n\tprivate final int maxSpans;\n\tprivate final int maxSpansPerLine;\n\n\t/**\n\t * New base highlighter with default span limits.\n\t */\n\tprotected AbstractSyntaxHighlighter() {\n\t\tthis(DEFAULT_MAX_SPANS, DEFAULT_MAX_SPANS_PER_LINE);\n\t}\n\n\t/**\n\t * New base highlighter with the given span limits.\n\t *\n\t * @param maxSpans\n\t * \t\tMax span count to allow in output.\n\t * @param maxSpansPerLine\n\t * \t\tMax span count per line to allow in output.\n\t */\n\tprotected AbstractSyntaxHighlighter(int maxSpans, int maxSpansPerLine) {\n\t\tthis.maxSpans = maxSpans;\n\t\tthis.maxSpansPerLine = maxSpansPerLine;\n\t}\n\n\t@Nonnull\n\t@Override\n\t@SuppressWarnings(\"UnnecessaryLocalVariable\") // Used for optimization\n\tpublic final StyleSpans<Collection<String>> createStyleSpans(@Nonnull String text, int start, int end) {\n\t\tStyleSpans<Collection<String>> spans = createStyleSpansImpl(text, start, end);\n\n\t\t// Prevent bogus inputs from hanging the application by emitting stupidly complex span collections.\n\t\t// This counts the number of global spans across all lines.\n\t\tint spanCount = spans.getSpanCount();\n\t\tif (spanCount > maxSpans) {\n\t\t\tlogger.warn(\"Skipping syntax computation, input of {} spans, max is {}\", spanCount, maxSpans);\n\t\t\treturn StyleSpans.singleton(Collections.emptyList(), end - start);\n\t\t}\n\n\t\t// The more problematic \"lag\" comes when a single line holds many spans.\n\t\t// This is because if 100_000 spans are separated across 100_000 lines, rendering 1 span per line is trivial\n\t\t// when considering the lines are virtualized vertically. However, because text is not horizontally virtualized\n\t\t// 100_000 spans on one line would cause problems.\n\t\tint maxSpansPerLineLocal = maxSpansPerLine;\n\t\tint currentLine = 0;\n\t\tint currentOffset = 0;\n\t\tint nextLineOffset = text.indexOf('\\n');\n\t\tspanCount = 0;\n\t\tfor (StyleSpan<Collection<String>> span : spans) {\n\t\t\tint spanLength = span.getLength();\n\t\t\tint spanBegin = currentOffset;\n\t\t\tint spanEnd = spanBegin + spanLength;\n\t\t\tspanCount++;\n\n\t\t\t// When the span goes beyond the next line offset, we scan forward for the next line that contains the span end.\n\t\t\t// In each scan step, we increment the line and clear the span count since we've moved onto a new line.\n\t\t\twhile (spanEnd > nextLineOffset) {\n\t\t\t\tspanCount = 1;\n\t\t\t\tcurrentLine++;\n\t\t\t\tnextLineOffset = text.indexOf('\\n', nextLineOffset + 1);\n\t\t\t\tif (nextLineOffset == -1) nextLineOffset = end;\n\t\t\t}\n\n\t\t\t// If this line has more spans than allowed, do not yield the results, as that would cause UI slowdowns.\n\t\t\tif (spanCount > maxSpansPerLineLocal) {\n\t\t\t\tlogger.warn(\"Skipping syntax computation, input of {} spans on line {}, max-per-line is {}\", spanCount, currentLine + 1, maxSpans);\n\t\t\t\treturn StyleSpans.singleton(Collections.emptyList(), end - start);\n\t\t\t}\n\n\t\t\t// Move current offset forward by the size of the span.\n\t\t\t// Areas without syntax are still spans, but with an empty list of style classes.\n\t\t\tcurrentOffset += spanLength;\n\t\t}\n\n\t\treturn spans;\n\t}\n\n\t@Nonnull\n\tprotected abstract StyleSpans<Collection<String>> createStyleSpansImpl(@Nonnull String text, int start, int end);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/syntax/RegexLanguages.java",
    "content": "package software.coley.recaf.ui.control.richtext.syntax;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.ui.LanguageStylesheets;\n\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static software.coley.recaf.util.StringUtil.cutOffAtFirst;\nimport static software.coley.recaf.util.StringUtil.shortenPath;\n\n/**\n * Rule-sets to pattern-match languages for syntax highlighting.\n *\n * @author Matt Coley\n * @see RegexSyntaxHighlighter Highlighter implementation that accepts these rule-sets.\n * @see LanguageStylesheets For retrieving stylesheets of the supported languages.\n */\npublic class RegexLanguages {\n\t/* Don't need to use the managed 'Gson' instance from 'GsonProvider' since we only want to read data\n\t * without any custom adapters. Using it would require some larger scale refactoring anyways.\n\t */\n\tprivate static final Gson GSON = new GsonBuilder().create();\n\tprivate static final Map<String, RegexRule> NAME_TO_LANG = new HashMap<>();\n\tprivate static final RegexRule LANG_JAVA;\n\tprivate static final RegexRule LANG_JASM;\n\tprivate static final RegexRule LANG_JSON;\n\tprivate static final RegexRule LANG_XML;\n\tprivate static final RegexRule LANG_ENGIMA_MAP;\n\n\t// Prevent construction\n\tprivate RegexLanguages() {\n\t}\n\n\tstatic {\n\t\ttry {\n\t\t\tLANG_JAVA = addLanguage(\"/syntax/java.json\");\n\t\t\tLANG_JASM = addLanguage(\"/syntax/jasm.json\");\n\t\t\tLANG_JSON = addLanguage(\"/syntax/json.json\");\n\t\t\tLANG_XML = addLanguage(\"/syntax/xml.json\");\n\t\t\tLANG_ENGIMA_MAP = addLanguage(\"/syntax/enigma.json\");\n\t\t} catch (Exception ex) {\n\t\t\tthrow new IllegalStateException(\"Failed to read syntax rules from resources\", ex);\n\t\t}\n\t}\n\n\t/**\n\t * Adds support for the language outlined by the contents of the given file.\n\t * File path should point to a json file modeling {@link RegexRule}.\n\t *\n\t * @param path\n\t * \t\tPath to regex json file. The language name is the file name, without the extension.\n\t * \t\t<ul>\n\t * \t\t    <li>Internal paths start with {@code /}.</li>\n\t * \t\t    <li>External paths should exist if checked by {@code new File(path).exists()}.</li>\n\t * \t\t</ul>\n\t *\n\t * @return Language root rule.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the file cannot be read or mapped into the rule structure.\n\t */\n\t@Nonnull\n\tpublic static RegexRule addLanguage(@Nonnull String path) throws IOException {\n\t\tInputStream stream = RegexLanguages.class.getResourceAsStream(path);\n\t\tif (stream == null)\n\t\t\tstream = new FileInputStream(path);\n\t\tString name = cutOffAtFirst(shortenPath(path), \".\");\n\t\treturn addLanguage(name, stream);\n\t}\n\n\t/**\n\t * Adds support for the language.\n\t *\n\t * @param name\n\t * \t\tLanguage name to use as a key. Will be used in {@link #getLanguages()} and {@link #getLanguage(String)}.\n\t * @param stream\n\t * \t\tInput stream to file to parse.\n\t *\n\t * @return Language root rule.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the file cannot be read or mapped into the rule structure.\n\t * @see LanguageStylesheets#addLanguage(String, String) You should assign a stylesheet by the same language name.\n\t */\n\t@Nonnull\n\tpublic static RegexRule addLanguage(@Nonnull String name, @Nonnull InputStream stream) throws IOException {\n\t\ttry {\n\t\t\tRegexRule lang = GSON.fromJson(new InputStreamReader(stream), RegexRule.class);\n\t\t\tNAME_TO_LANG.put(name, lang);\n\t\t\treturn lang;\n\t\t} catch (Exception ex) {\n\t\t\t// Gson's exceptions are unchecked, so rethrow as checked.\n\t\t\tthrow new IOException(ex.getMessage(), ex);\n\t\t}\n\t}\n\n\t/**\n\t * @return Map of language names to language regex rule roots.\n\t */\n\t@Nonnull\n\tpublic static Map<String, RegexRule> getLanguages() {\n\t\treturn Collections.unmodifiableMap(NAME_TO_LANG);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tName of the language, matching the file path inside Recaf's {@code /syntax/} directory,\n\t * \t\twithout the {@code .json} extension. To get {@link #getJavaLanguage()} use {@code java}.\n\t *\n\t * @return Language regex rule root, or {@code null} if unknown language.\n\t *\n\t * @see LanguageStylesheets#addLanguage(String) Stylesheet to highlight the language with.\n\t */\n\t@Nullable\n\tpublic static RegexRule getLanguage(@Nullable String name) {\n\t\treturn NAME_TO_LANG.get(name);\n\t}\n\n\t/**\n\t * @return Root rule for Java regex matching.\n\t *\n\t * @see LanguageStylesheets#getJavaStylesheet()\n\t */\n\t@Nonnull\n\tpublic static RegexRule getJavaLanguage() {\n\t\treturn LANG_JAVA;\n\t}\n\n\t/**\n\t * @return Root rule for JASM regex matching.\n\t *\n\t * @see LanguageStylesheets#getJasmStylesheet()\n\t */\n\t@Nonnull\n\tpublic static RegexRule getJasmLanguage() {\n\t\treturn LANG_JASM;\n\t}\n\n\t/**\n\t * @return Root rule for JSON regex matching.\n\t *\n\t * @see LanguageStylesheets#getJsonStylesheet()\n\t */\n\t@Nonnull\n\tpublic static RegexRule getJsonLanguage() {\n\t\treturn LANG_JSON;\n\t}\n\n\t/**\n\t * @return Root rule for XML regex matching.\n\t *\n\t * @see LanguageStylesheets#getXmlStylesheet()\n\t */\n\t@Nonnull\n\tpublic static RegexRule getXmlLanguage() {\n\t\treturn LANG_XML;\n\t}\n\n\t/**\n\t * @return Root rule for Engima regex matching.\n\t *\n\t * @see LanguageStylesheets#getEnigmaStylesheet()\n\t */\n\t@Nonnull\n\tpublic static RegexRule getLangEngimaMap() {\n\t\treturn LANG_ENGIMA_MAP;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/syntax/RegexRule.java",
    "content": "package software.coley.recaf.ui.control.richtext.syntax;\n\nimport com.google.gson.annotations.SerializedName;\n\nimport java.util.List;\n\n/**\n * Regex rule data type.\n *\n * @param name\n * \t\tName of rule. Must not contain any whitespaces or special characters.\n * @param regex\n * \t\tRegex pattern.\n * @param classes\n * \t\tStyle classes to apply to matched ranges within a {@link SyntaxHighlighter}.\n * @param subRules\n * \t\tSub-rules which can be used to create sub-matches within ranges matched by this rule.\n * @param backtrackMark\n * \t\tUsed for rules that are variable length. Indicates the start text of such matches.\n * \t\tSee {@link RegexSyntaxHighlighter#expandRange(String, int, int)} for usage.\n * @param advanceMark\n * \t\tUsed for rules that are variable length. Indicates the end text of such matches.\n * \t\tSee {@link RegexSyntaxHighlighter#expandRange(String, int, int)} for usage.\n *\n * @author Matt Coley\n * @see RegexSyntaxHighlighter\n */\npublic record RegexRule(@SerializedName(\"name\") String name,\n\t\t\t\t\t\t@SerializedName(\"regex\") String regex,\n\t\t\t\t\t\t@SerializedName(\"classes\") List<String> classes,\n\t\t\t\t\t\t@SerializedName(\"sub-rules\") List<RegexRule> subRules,\n\t\t\t\t\t\t@SerializedName(\"backtrack-mark\") String backtrackMark,\n\t\t\t\t\t\t@SerializedName(\"advance-mark\") String advanceMark) {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/syntax/RegexSyntaxHighlighter.java",
    "content": "package software.coley.recaf.ui.control.richtext.syntax;\n\nimport jakarta.annotation.Nonnull;\nimport org.fxmisc.richtext.model.StyleSpans;\nimport org.fxmisc.richtext.model.StyleSpansBuilder;\nimport org.slf4j.Logger;\nimport regexodus.Matcher;\nimport regexodus.Pattern;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.util.IntRange;\nimport software.coley.recaf.util.RegexUtil;\n\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Regex backed syntax highlighter.\n *\n * @author Matt Coley\n * @see RegexLanguages Predefined languages to pass to {@link RegexSyntaxHighlighter#RegexSyntaxHighlighter(RegexRule)}.\n */\npublic class RegexSyntaxHighlighter extends AbstractSyntaxHighlighter {\n\tprivate static final Logger logger = Logging.get(RegexSyntaxHighlighter.class);\n\tprivate static final Map<List<RegexRule>, Pattern> patternCache = new ConcurrentHashMap<>();\n\tprivate final RegexRule rootRule;\n\n\t/**\n\t * @param rootRule\n\t * \t\tRoot rule, constituting a language by its sub-rules.\n\t */\n\tpublic RegexSyntaxHighlighter(@Nonnull RegexRule rootRule) {\n\t\tthis.rootRule = rootRule;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected StyleSpans<Collection<String>> createStyleSpansImpl(@Nonnull String text, int start, int end) {\n\t\ttry {\n\t\t\tRegion region = new Region(text, null, rootRule, start, end);\n\t\t\tregion.split(rootRule.subRules());\n\n\t\t\tStyleSpansBuilder<Collection<String>> builder = new StyleSpansBuilder<>();\n\t\t\tregion.visitBuilder(builder);\n\t\t\treturn builder.create();\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Error creating style spans for text\", t);\n\t\t\tthrow t;\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic IntRange expandRange(@Nonnull String text, int initialStart, int initialEnd) {\n\t\tString rangeText = text.substring(initialStart, initialEnd);\n\t\tint start = initialStart;\n\t\tint end = initialEnd;\n\n\t\t// Any rule that has backtracking/advancing needs to be expanded so that if the change breaks the start/end\n\t\t// of one of these matches, the range covers the new start/end. Primary example of this is multi-line comments.\n\t\t//\n\t\t// Example: Deleting the last '/' will expand the range forwards.\n\t\tfor (RegexRule rule : rootRule.subRules()) {\n\t\t\tString backtrackMark = rule.backtrackMark();\n\t\t\tString advanceMark = rule.advanceMark();\n\n\t\t\tif (advanceMark != null && backtrackMark != null) {\n\t\t\t\t// If the range is a FULL match (from start to finish, no leading or trailing text)\n\t\t\t\t// then we do not need to change anything.\n\t\t\t\tif (RegexUtil.matches(rule.regex(), rangeText))\n\t\t\t\t\tbreak;\n\n\t\t\t\t// Positions of marks within the text.\n\t\t\t\tint backtrackMarkIndex = rangeText.indexOf(backtrackMark);\n\t\t\t\tint advanceMarkIndex = rangeText.indexOf(advanceMark);\n\n\t\t\t\t// The change in the text caused our pattern to break its original match.\n\t\t\t\t// We need to restyle a wider range.\n\t\t\t\t//\n\t\t\t\t// Advance forward if start of pattern exists, but no end of pattern exists\n\t\t\t\tif (backtrackMarkIndex > advanceMarkIndex) {\n\t\t\t\t\tend = Math.max(end, text.indexOf(advanceMark, end) + advanceMark.length());\n\n\t\t\t\t\t// Rules will only expand in one direction, so if we made a match by forward expansion\n\t\t\t\t\t// then we do not need to do backtracking for this rule.\n\t\t\t\t\t//\n\t\t\t\t\t// We are done and do not need to check any other rules.\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// Advance backwards if the end of a pattern exists, but no start of pattern exists\n\t\t\t\tif (rangeText.contains(advanceMark)) {\n\t\t\t\t\tint index = text.substring(0, start).lastIndexOf(backtrackMark);\n\t\t\t\t\tif (index >= 0) {\n\t\t\t\t\t\t// Check if there is an advance mark between the 'start' and the last backtrack mark.\n\t\t\t\t\t\t// If so, then backtrack has not found something that is open to this match, as it is closed\n\t\t\t\t\t\t// by the advance mark in the middle.\n\t\t\t\t\t\tint potentialBoundOfDetectedBacktrack = text.substring(0, start).lastIndexOf(advanceMark);\n\t\t\t\t\t\tif (index > potentialBoundOfDetectedBacktrack) {\n\t\t\t\t\t\t\tstart = index;\n\n\t\t\t\t\t\t\t// Rules will only expand in one direction, so if we made a match by backwards expansion\n\t\t\t\t\t\t\t// then we are done with this rule and do not need to check any other rules.\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn new IntRange(start, end);\n\t}\n\n\t/**\n\t * @param rules\n\t * \t\tRules to match against.\n\t *\n\t * @return Compiled regex pattern from the given rules.\n\t */\n\tprivate static Pattern getCombinedPattern(List<RegexRule> rules) {\n\t\t// Cache results. Very likely to encounter rule-collection again which makes it wise to save\n\t\t// the result instead of re-computing every time.\n\t\treturn patternCache.computeIfAbsent(rules, RegexSyntaxHighlighter::createCombinedPattern);\n\t}\n\n\t/**\n\t * @param rules\n\t * \t\tRules to match against.\n\t *\n\t * @return Compiled regex pattern from the given rules.\n\t */\n\tprivate static Pattern createCombinedPattern(List<RegexRule> rules) {\n\t\t// Dummy pattern\n\t\tif (rules.isEmpty()) return RegexUtil.pattern(\"({EMPTY}EMPTY)\");\n\t\tStringBuilder sb = new StringBuilder();\n\t\tfor (RegexRule rule : rules) {\n\t\t\tString pattern = rule.regex();\n\t\t\tsb.append(\"({\").append(rule.name()).append(\"}\").append(pattern).append(\")|\");\n\t\t}\n\t\treturn RegexUtil.pattern(sb.substring(0, sb.length() - 1));\n\t}\n\n\t/**\n\t * @param rules\n\t * \t\tRules to search for.\n\t * @param matcher\n\t * \t\tMatcher to pull rule name from using matched group's name.\n\t *\n\t * @return Rule with matching name as matched group.\n\t */\n\tprivate static RegexRule getRuleFromMatcher(Collection<RegexRule> rules, Matcher matcher) {\n\t\treturn rules.stream()\n\t\t\t\t.filter(rule -> matcher.group(rule.name()) != null)\n\t\t\t\t.findFirst()\n\t\t\t\t.orElse(null);\n\t}\n\n\t/**\n\t * Splittable region breaking down rule matches into ranges and sub-ranges.\n\t */\n\tpublic static class Region {\n\t\tprivate final List<Region> children = new ArrayList<>();\n\t\tprivate final Region parent;\n\t\tprivate final String text;\n\t\tprivate final RegexRule rule;\n\t\tprivate final int start;\n\t\tprivate final int end;\n\n\t\t/**\n\t\t * Constructor for matching a rule.\n\t\t *\n\t\t * @param text\n\t\t * \t\tComplete text.\n\t\t * @param parent\n\t\t * \t\tParent region.\n\t\t * @param rule\n\t\t * \t\tRule associated with region.\n\t\t * @param start\n\t\t * \t\tStart offset in complete text.\n\t\t * @param end\n\t\t * \t\tEnd offset in complete text.\n\t\t */\n\t\tpublic Region(String text, Region parent, RegexRule rule, int start, int end) {\n\t\t\tthis.text = text;\n\t\t\tthis.parent = parent;\n\t\t\tthis.rule = rule;\n\t\t\tthis.start = start;\n\t\t\tthis.end = end;\n\t\t}\n\n\t\t/**\n\t\t * Splits this node into subregions based on the given rules.\n\t\t *\n\t\t * @param rules\n\t\t * \t\tRules to match and split by.\n\t\t */\n\t\tpublic void split(List<RegexRule> rules) {\n\t\t\t// Skip when no rules are given.\n\t\t\tif (rules.isEmpty())\n\t\t\t\treturn;\n\n\t\t\t// Match within give region\n\t\t\tString localText = text.substring(start, end);\n\t\t\tif (localText.isEmpty())\n\t\t\t\treturn;\n\n\t\t\tMatcher matcher = getCombinedPattern(rules).matcher(localText);\n\t\t\twhile (matcher.find()) {\n\t\t\t\t// Create region from found match\n\t\t\t\tint localStart = matcher.start();\n\t\t\t\tint localEnd = matcher.end();\n\t\t\t\tRegexRule matchedRule = getRuleFromMatcher(rules, matcher);\n\t\t\t\tRegion localChild = new Region(text, this, matchedRule, start + localStart, start + localEnd);\n\n\t\t\t\t// Break the new region into smaller ones if the rule associated with the match has sub-rules.\n\t\t\t\tList<RegexRule> subrules = matchedRule.subRules();\n\t\t\t\tif (!subrules.isEmpty()) localChild.split(subrules);\n\n\t\t\t\t// Add child (splitting technically handled in output logic)\n\t\t\t\tchildren.add(localChild);\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Appends style spans to the given builder.\n\t\t * The classes for each span take into account this classes of the {@link #rule} and the {@link #parent} rules.\n\t\t *\n\t\t * @param builder\n\t\t * \t\tBuilder to append to.\n\t\t */\n\t\tpublic void visitBuilder(StyleSpansBuilder<Collection<String>> builder) {\n\t\t\tint lastEnd = start;\n\n\t\t\t// Add children\n\t\t\tfor (Region child : children) {\n\t\t\t\tint childStart = child.start;\n\n\t\t\t\t// Append text not matched at start\n\t\t\t\tif (childStart > lastEnd) {\n\t\t\t\t\tint len = childStart - lastEnd;\n\t\t\t\t\tbuilder.add(child.unmatchedClasses(), len);\n\t\t\t\t}\n\n\t\t\t\t// Append child content\n\t\t\t\tchild.visitBuilder(builder);\n\t\t\t\tlastEnd = child.end;\n\t\t\t}\n\n\t\t\t// Append remaining unmatched text\n\t\t\tint len = end - lastEnd;\n\t\t\tbuilder.add(currentClasses(), len);\n\t\t}\n\n\t\t/**\n\t\t * @return List of classes for matching the {@link #rule} in this region.\n\t\t */\n\t\tpublic List<String> currentClasses() {\n\t\t\tif (parent == null) return Collections.emptyList();\n\t\t\treturn Lists.combine(parent.currentClasses(), rule.classes());\n\t\t}\n\n\t\t/**\n\t\t * @return List of classes for unmatched sections in this region.\n\t\t */\n\t\tpublic List<String> unmatchedClasses() {\n\t\t\tif (parent == null) return Collections.emptyList();\n\t\t\treturn Lists.combine(parent.unmatchedClasses(), parent.rule.classes());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/syntax/StyleResult.java",
    "content": "package software.coley.recaf.ui.control.richtext.syntax;\n\nimport jakarta.annotation.Nonnull;\nimport org.fxmisc.richtext.model.StyleSpans;\n\nimport java.util.Collection;\n\n/**\n * Wrapper of created style-spans and starting position.\n *\n * @author Matt Coley\n */\npublic record StyleResult(@Nonnull StyleSpans<Collection<String>> spans, int position) {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/syntax/SyntaxHighlighter.java",
    "content": "package software.coley.recaf.ui.control.richtext.syntax;\n\nimport jakarta.annotation.Nonnull;\nimport org.fxmisc.richtext.model.PlainTextChange;\nimport org.fxmisc.richtext.model.StyleSpans;\nimport org.objectweb.asm.ClassReader;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.EditorComponent;\nimport software.coley.recaf.util.IntRange;\n\nimport java.util.Collection;\n\n/**\n * Outline of basic syntax highlighting.\n *\n * @author Matt Coley\n * @see RegexSyntaxHighlighter Regex implementation.\n */\npublic interface SyntaxHighlighter extends EditorComponent {\n\t/**\n\t * @param text\n\t * \t\tFull text.\n\t * @param start\n\t * \t\tStart range in text to style.\n\t * @param end\n\t * \t\tEnd range in text to style.\n\t *\n\t * @return Spans for RichTextFX.\n\t */\n\t@Nonnull\n\tStyleSpans<Collection<String>> createStyleSpans(@Nonnull String text, int start, int end);\n\n\t/**\n\t * By default, the {@link Editor} will create a base range to restyle when a text change is made.\n\t * However, in some cases that range may not be complete due to the change made.\n\t * <br>\n\t * Consider in a multi-line comment if you remove the last {@code '/'}. The end is now the next {@code '*\\/'} found\n\t * in the document, which can be much further along in the text than the range created by the {@link Editor}.\n\t * Thus, implementations of {@link SyntaxHighlighter} are given the ability to expand ranges when cases like this\n\t * exist.\n\t *\n\t * @param text\n\t * \t\tFull text.\n\t * @param initialStart\n\t * \t\tStart range in text to style.\n\t * @param initialEnd\n\t * \t\tEnd range in text to style.\n\t *\n\t * @return Expanded range to style.\n\t * By default, returns the given range.\n\t *\n\t * @see SyntaxUtil#getRangeForRestyle(String, StyleSpans, SyntaxHighlighter, PlainTextChange) Usage of this method.\n\t */\n\t@Nonnull\n\tdefault IntRange expandRange(@Nonnull String text, int initialStart, int initialEnd) {\n\t\treturn new IntRange(initialStart, initialEnd);\n\t}\n\n\t/**\n\t * Called when the syntax highlighter is {@link Editor#setSyntaxHighlighter(SyntaxHighlighter) installed} into\n\t * the given editor.\n\t * <br>\n\t * Something a syntax highlighter may want to do is install a custom stylesheet via {@link Editor#getStylesheets()}.\n\t *\n\t * @param editor\n\t * \t\tEditor installed to.\n\t */\n\t@Override\n\tdefault void install(@Nonnull Editor editor) {\n\t\t// no-op by default\n\t}\n\n\t/**\n\t * Called when the syntax highlighter is removed from the given editor.\n\t * <br>\n\t * Should clean up any actions done in {@link #install(Editor)}.\n\t *\n\t * @param editor\n\t * \t\tEditor removed from.\n\t */\n\t@Override\n\tdefault void uninstall(@Nonnull Editor editor) {\n\t\t// no-op by default\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/syntax/SyntaxUtil.java",
    "content": "package software.coley.recaf.ui.control.richtext.syntax;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport org.fxmisc.richtext.model.PlainTextChange;\nimport org.fxmisc.richtext.model.StyleSpan;\nimport org.fxmisc.richtext.model.StyleSpans;\nimport org.slf4j.Logger;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.util.IntRange;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static java.lang.Math.*;\n\n/**\n * Various syntax utils, mostly for range computations.\n *\n * @author Matt Coley\n */\npublic class SyntaxUtil {\n\tprivate static final Logger logger = Logging.get(SyntaxUtil.class);\n\n\t/**\n\t * @param text\n\t * \t\tFull document text.\n\t * @param styleSpans\n\t * \t\tExisting style-spans for the full document text.\n\t * @param syntaxHighlighter\n\t * \t\tExisting highlighter to use for {@link SyntaxHighlighter#expandRange(String, int, int)}.\n\t * \t\tMay be {@code null} to skip this process.\n\t * @param change\n\t * \t\tThe change made in the editor's text.\n\t *\n\t * @return Range to restyle in order to fit the changed text.\n\t */\n\tpublic static IntRange getRangeForRestyle(@Nonnull String text,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull StyleSpans<Collection<String>> styleSpans,\n\t\t\t\t\t\t\t\t\t\t\t  @Nullable SyntaxHighlighter syntaxHighlighter,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull PlainTextChange change) {\n\t\tint textLength = text.length();\n\t\tif (textLength == 0) return IntRange.EMPTY;\n\n\t\t// Get range of change\n\t\tString inserted = change.getInserted();\n\t\tString removed = change.getRemoved();\n\t\tint changeStart = change.getPosition();\n\t\tint changeEnd;\n\t\tif (!inserted.isBlank()) {\n\t\t\tchangeEnd = change.getInsertionEnd();\n\t\t} else if (!removed.isBlank()) {\n\t\t\tchangeEnd = change.getRemovalEnd();\n\t\t} else {\n\t\t\tchangeEnd = changeStart + Math.abs(change.getNetLength());\n\t\t}\n\n\t\t// Expand the range to the boundaries of top-level flattened style-spans.\n\t\t// If you change a character in the middle of a multi-line comment, this will make the\n\t\t// bounds fit from '/*' to '*/'. This way when we pass the range to the styler it operates on a range\n\t\t// that previously matched a top-level rule. In most cases it will make the same match again.\n\t\tint styleStart;\n\t\tint styleEnd;\n\t\tList<IntRange> topLevelSpanRanges = SyntaxUtil.flatten(styleSpans);\n\t\ttry {\n\t\t\t// Find where the change start and end positions would be inserted into the flattened ranges.\n\t\t\tIntRange startMarker = new IntRange(changeStart - 1, changeStart);\n\t\t\tIntRange endMarker = new IntRange(changeEnd - 1, changeEnd);\n\t\t\tint searchEnd = topLevelSpanRanges.size() - 1;\n\t\t\tint startIndex = abs(Lists.binarySearch(topLevelSpanRanges, startMarker));\n\t\t\tint endIndex = abs(Lists.binarySearch(topLevelSpanRanges, endMarker, startIndex, searchEnd));\n\n\t\t\t// Normalize binary search results.\n\t\t\tint size = topLevelSpanRanges.size() - 1;\n\t\t\tstartIndex = min(size, max(0, startIndex));\n\t\t\tendIndex = min(size, max(0, endIndex));\n\n\t\t\t// Map to positions in the text.\n\t\t\tstyleStart = topLevelSpanRanges.get(startIndex).start();\n\t\t\tstyleEnd = topLevelSpanRanges.get(endIndex).end();\n\t\t} catch (Exception ex) {\n\t\t\tlogger.error(\"Failed to get span range for change\", ex);\n\t\t\tthrow new IllegalStateException(ex);\n\t\t}\n\n\t\t// Fit to paragraph start/end (newline boundaries)\n\t\tstyleStart = Math.min(styleStart, text.substring(0, styleStart).lastIndexOf('\\n') + 1);\n\t\tstyleEnd = Math.max(styleEnd, text.indexOf('\\n', styleEnd - 1));\n\n\t\t// Allow the syntax highlighter implementation to reshape the range.\n\t\t// Building on the example from before with multi-line comments, lets say you delete the last `/` that\n\t\t// closes a multi-line comment. The existing range up until here will cut off at the end of the prior range\n\t\t// of the multi-line comment. But now that the comment is open, it will continue until the next '*/'.\n\t\t//\n\t\t// Because of this, we let the syntax highlighter implementations expand the range if they have constructs\n\t\t// like multi-line comments.\n\t\tif (syntaxHighlighter != null) {\n\t\t\tIntRange expandedRange = syntaxHighlighter.expandRange(text, styleStart, styleEnd);\n\t\t\tstyleStart = Math.min(styleStart, expandedRange.start());\n\t\t\tstyleEnd = Math.max(styleEnd, expandedRange.end());\n\n\t\t\t// Fit to paragraph start/end (newline boundaries) again\n\t\t\tstyleStart = Math.min(styleStart, text.substring(0, styleStart).lastIndexOf('\\n') + 1);\n\t\t\tstyleEnd = Math.max(styleEnd, text.indexOf('\\n', styleEnd - 1));\n\t\t}\n\n\t\treturn new IntRange(styleStart, styleEnd);\n\t}\n\n\t/**\n\t * The {@link StyleSpans} of RichTextFX are not a tree-like structure. If you have something you wish to style in\n\t * the middle of another block, you have to create three blocks instead of having a sub-range in the parent span.\n\t * <br>\n\t * Having to deal with this layout is kind of annoying when regenerating styles. To deal with that, we flatten cases\n\t * as described prior into a single range. If it were ideally represented as a tree, we are stripping all but the\n\t * base layer.\n\t * <br>\n\t * Usage of this can be seen in {@link #getRangeForRestyle(String, StyleSpans, SyntaxHighlighter, PlainTextChange)}.\n\t *\n\t * @param styleSpans\n\t * \t\tCollection of {@link StyleSpan}.\n\t *\n\t * @return Flat representation of ranges.\n\t */\n\tpublic static List<IntRange> flatten(StyleSpans<Collection<String>> styleSpans) {\n\t\t// Map the style spans (which only know their own length) into ranges that know their absolute positions.\n\t\tList<StyledRange> styledRanges = new ArrayList<>();\n\t\tint position = 0;\n\t\tfor (StyleSpan<Collection<String>> span : styleSpans) {\n\t\t\tint length = span.getLength();\n\t\t\tstyledRanges.add(new StyledRange(new IntRange(position, position + length), span.getStyle()));\n\t\t\tposition += length;\n\t\t}\n\n\t\t// Flatten the ranges together where possible.\n\t\t// For example, where each line represents a range, and letters are classes:\n\t\t//\n\t\t// This sample will be merged into one range of A:\n\t\t//\n\t\t//  A\n\t\t//  AB\n\t\t//  A\n\t\tint i = 0;\n\t\twhile (i < styledRanges.size() - 1) {\n\t\t\tStyledRange current = styledRanges.get(i);\n\t\t\tStyledRange next = styledRanges.get(i + 1);\n\t\t\tif (current.canMergeInto(next)) {\n\t\t\t\tcurrent.merge(next);\n\t\t\t\tstyledRanges.remove(next);\n\t\t\t} else {\n\t\t\t\ti++;\n\t\t\t}\n\t\t}\n\n\t\t// Map back to regular ranges.\n\t\treturn styledRanges.stream()\n\t\t\t\t.map(StyledRange::getRange)\n\t\t\t\t.filter(range -> !range.empty())\n\t\t\t\t.toList();\n\t}\n\n\t/**\n\t * Range mapped to a {@link StyleSpan}.\n\t * Contains the collection of style classes of the span.\n\t */\n\tprivate static class StyledRange {\n\t\tprivate final Collection<String> style;\n\t\tprivate IntRange range;\n\n\t\t/**\n\t\t * @param range\n\t\t * \t\tSpan range.\n\t\t * @param style\n\t\t * \t\tSpan style classes.\n\t\t */\n\t\tpublic StyledRange(@Nonnull IntRange range, @Nonnull Collection<String> style) {\n\t\t\tthis.range = range;\n\t\t\tthis.style = style;\n\t\t}\n\n\t\t/**\n\t\t * @return Span style classes.\n\t\t */\n\t\t@Nonnull\n\t\tpublic Collection<String> getStyle() {\n\t\t\treturn style;\n\t\t}\n\n\t\t/**\n\t\t * @return Span range.\n\t\t */\n\t\t@Nonnull\n\t\tpublic IntRange getRange() {\n\t\t\treturn range;\n\t\t}\n\n\t\t/**\n\t\t * @param other\n\t\t * \t\tAnother range, should be the next range in a containing {@link List}.\n\t\t *\n\t\t * @return {@code true} when the given range contains all the classes of our current range.\n\t\t */\n\t\tpublic boolean canMergeInto(@Nonnull StyledRange other) {\n\t\t\tif (style.isEmpty()) return false;\n\t\t\treturn other.style.containsAll(style);\n\t\t}\n\n\t\t/**\n\t\t * Adds the length of the given range to our own.\n\t\t * Assumes the ranges are adjacent.\n\t\t *\n\t\t * @param other\n\t\t * \t\tOther range to merge into this one.\n\t\t */\n\t\tpublic void merge(@Nonnull StyledRange other) {\n\t\t\trange = range.extendForwards(other.range.length());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/tree/FilterableTreeItem.java",
    "content": "package software.coley.recaf.ui.control.tree;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ListChangeListener;\nimport javafx.collections.ObservableList;\nimport javafx.collections.transformation.FilteredList;\nimport javafx.scene.control.TreeItem;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.util.ReflectUtil;\n\nimport java.lang.reflect.Field;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.function.Predicate;\n\n/**\n * Filterable tree item.\n *\n * @param <T>\n * \t\tType of value held by the item.\n *\n * @author <a href=\"https://stackoverflow.com/a/34426897\">kaznovac</a>\n * @author Matt Coley - Optimization of child management.\n */\npublic class FilterableTreeItem<T> extends TreeItem<T> {\n\tprivate static final Field CHILDREN_LISTENER_FIELD;\n\tprivate static final Field CHILDREN_FIELD;\n\tprivate static final Logger logger = Logging.get(FilterableTreeItem.class);\n\tprivate final ObservableList<TreeItem<T>> sourceChildren = FXCollections.observableArrayList();\n\tprivate final ObjectProperty<TreeItem<T>> sourceParent = new SimpleObjectProperty<>();\n\tprivate final ObjectProperty<Predicate<TreeItem<T>>> predicate = new SimpleObjectProperty<>();\n\n\tprotected FilterableTreeItem() {\n\t\t// Support filtering by using a filtered list backing.\n\t\t// - Apply predicate to items\n\t\tFilteredList<TreeItem<T>> filteredChildren = new FilteredList<>(sourceChildren);\n\t\tfilteredChildren.predicateProperty().bind(Bindings.createObjectBinding(() -> child -> {\n\t\t\tif (child instanceof FilterableTreeItem<T> filterable) {\n\t\t\t\tfilterable.predicateProperty().set(predicate.get());\n\t\t\t\tif (filterable.forceVisible())\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (predicate.get() == null || !child.getChildren().isEmpty())\n\t\t\t\treturn true;\n\t\t\tboolean matched = predicate.get().test(child);\n\t\t\tonMatchResult(child, matched);\n\t\t\treturn matched;\n\t\t}, predicate));\n\n\t\t// Reflection hackery\n\t\tsetUnderlyingChildren(filteredChildren);\n\t}\n\n\t/**\n\t * @return Predicate property.\n\t */\n\t@Nonnull\n\tpublic ObjectProperty<Predicate<TreeItem<T>>> predicateProperty() {\n\t\treturn predicate;\n\t}\n\n\t@Override\n\t@Deprecated(since = \"Use FilterableTreeItem dedicated methods for interacting with children\")\n\tpublic ObservableList<TreeItem<T>> getChildren() {\n\t\treturn super.getChildren();\n\t}\n\n\t/**\n\t * @return Unfiltered children.\n\t */\n\t@Nonnull\n\tpublic ObservableList<TreeItem<T>> getSourceChildren() {\n\t\treturn sourceChildren;\n\t}\n\n\t/**\n\t * @return Source parent property, ignoring filtering.\n\t */\n\t@Nonnull\n\tpublic ObjectProperty<TreeItem<T>> sourceParentProperty() {\n\t\treturn sourceParent;\n\t}\n\n\t/**\n\t * @return Source parent, ignoring filtering.\n\t */\n\t@Nullable\n\tpublic TreeItem<T> getSourceParent() {\n\t\treturn sourceParent.get();\n\t}\n\n\t/**\n\t * @return {@code true} when the item MUST be shown.\n\t */\n\tpublic boolean forceVisible() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * Allow additional action on items when the {@link #predicate} is updated.\n\t *\n\t * @param child\n\t * \t\tChild item.\n\t * @param matched\n\t * \t\tMatch status.\n\t */\n\tprotected void onMatchResult(@Nonnull TreeItem<T> child, boolean matched) {\n\t\t// Expand items that match, hide those that do not.\n\t\tif (matched) {\n\t\t\tTreeItems.expandParents(child);\n\t\t} else {\n\t\t\tchild.setExpanded(false);\n\t\t}\n\t}\n\n\t/**\n\t * Use reflection to directly set the underlying {@link TreeItem#children} field.\n\t * The javadoc for that field literally says:\n\t * <pre>\n\t *  It is important that interactions with this list go directly into the\n\t *  children property, rather than via getChildren(), as this may be\n\t *  a very expensive call.\n\t * </pre>\n\t * This will additionally add the current tree-item's {@link TreeItem#childrenListener} to\n\t * the given list's listeners.\n\t *\n\t * @param list\n\t * \t\tChildren list.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tprivate void setUnderlyingChildren(@Nonnull ObservableList<TreeItem<T>> list) {\n\t\tField childrenListenerField = CHILDREN_LISTENER_FIELD;\n\t\tif (childrenListenerField == null) {\n\t\t\t// See static block below.\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\t// Add our change listener to the passed list.\n\t\t\tlist.addListener((ListChangeListener<? super TreeItem<T>>) childrenListenerField.get(this));\n\n\t\t\t// Directly set \"TreeItem.children\"\n\t\t\tCHILDREN_FIELD.set(this, list);\n\t\t} catch (ReflectiveOperationException ex) {\n\t\t\tlogger.error(\"Failed to update filterable children\", ex);\n\t\t}\n\t}\n\n\t/**\n\t * Add an unfiltered unsorted child to this item.\n\t *\n\t * @param item\n\t * \t\tChild item to add.\n\t */\n\t@SuppressWarnings({\"unchecked\", \"rawtypes\"})\n\tpublic void addAndSortChild(@Nonnull TreeItem<T> item) {\n\t\tsynchronized (sourceChildren) {\n\t\t\tif (sourceChildren.isEmpty()) {\n\t\t\t\tsourceChildren.add(item);\n\t\t\t} else {\n\t\t\t\tint index = Collections.binarySearch((List) getChildren(), item);\n\t\t\t\tif (index < 0)\n\t\t\t\t\tindex = -(index + 1);\n\t\t\t\tsourceChildren.add(index, item);\n\t\t\t}\n\t\t\tif (item instanceof FilterableTreeItem<?> filterableItem)\n\t\t\t\tfilterableItem.sourceParent.set(Unchecked.cast(this));\n\t\t}\n\t}\n\n\t/**\n\t * Add an unfiltered sorted child to this item.\n\t * It is assumed the index provided will maintain sorted order when used with {@link List#add(int, Object)}.\n\t *\n\t * @param item\n\t * \t\tChild item to add.\n\t * @param index\n\t * \t\tIndex to add the child at, assumed to maintain sorted order.\n\t */\n\tpublic void addPreSortedChild(@Nonnull TreeItem<T> item, int index) {\n\t\tsynchronized (sourceChildren) {\n\t\t\tif (sourceChildren.isEmpty())\n\t\t\t\tsourceChildren.add(item);\n\t\t\telse\n\t\t\t\tsourceChildren.add(index, item);\n\t\t\tif (item instanceof FilterableTreeItem<?> filterableItem)\n\t\t\t\tfilterableItem.sourceParent.set(Unchecked.cast(this));\n\t\t}\n\t}\n\n\t/**\n\t * Add an unfiltered sorted child to this item.\n\t * It is assumed this method is used exclusively and all paths are added in sorted order.\n\t *\n\t * @param item\n\t * \t\tChild item to add.\n\t */\n\tprotected void addPreSortedChild(@Nonnull TreeItem<T> item) {\n\t\tsynchronized (sourceChildren) {\n\t\t\tsourceChildren.add(item);\n\t\t\tif (item instanceof FilterableTreeItem<?> filterableItem)\n\t\t\t\tfilterableItem.sourceParent.set(Unchecked.cast(this));\n\t\t}\n\t}\n\n\t/**\n\t * Remove an unfiltered child from this item.\n\t *\n\t * @param child\n\t * \t\tChild item to remove.\n\t *\n\t * @return {@code true} when child removed.\n\t * {@code false} when there was no item removed.\n\t */\n\tpublic boolean removeSourceChild(@Nonnull TreeItem<T> child) {\n\t\tsynchronized (sourceChildren) {\n\t\t\tif (child instanceof FilterableTreeItem<?> filterableItem)\n\t\t\t\tfilterableItem.sourceParent.set(null);\n\t\t\treturn sourceChildren.remove(child);\n\t\t}\n\t}\n\n\t/**\n\t * @param comparator\n\t * \t\tComparator to run to handle child sorting.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic <I extends TreeItem<T>> void sortChildren(@Nonnull Comparator<I> comparator) {\n\t\tsynchronized (sourceChildren) {\n\t\t\tsourceChildren.sort((Comparator<TreeItem<T>>) comparator);\n\t\t}\n\t}\n\n\t/**\n\t * @return {@code true} when this tree item is a true leaf based on the unfiltered children.\n\t */\n\tprotected boolean isSourceLeaf() {\n\t\treturn sourceChildren.isEmpty();\n\t}\n\n\tstatic {\n\t\tField childrenListenerField;\n\t\tField childrenField;\n\t\ttry {\n\t\t\tchildrenListenerField = ReflectUtil.getDeclaredField(TreeItem.class, \"childrenListener\");\n\t\t\tchildrenField = ReflectUtil.getDeclaredField(TreeItem.class, \"children\");\n\t\t} catch (NoSuchFieldException ex) {\n\t\t\tlogger.error(\"Failed to get necessary fields to set children list for TreeItem\", ex);\n\t\t\tlogger.error(\"Did the implementation of JavaFX change?\");\n\t\t\tchildrenListenerField = null;\n\t\t\tchildrenField = null;\n\t\t}\n\t\tCHILDREN_LISTENER_FIELD = childrenListenerField;\n\t\tCHILDREN_FIELD = childrenField;\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/tree/TreeFiltering.java",
    "content": "package software.coley.recaf.ui.control.tree;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.TextField;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\nimport javafx.scene.input.KeyCode;\nimport software.coley.recaf.ui.pane.WorkspaceExplorerPane;\nimport software.coley.recaf.ui.pane.editing.tabs.FieldsAndMethodsPane;\nimport software.coley.recaf.util.NodeEvents;\n\n/**\n * Common tree filtering setup.\n *\n * @author Matt Coley\n * @see WorkspaceExplorerPane Used for workspace filter.\n * @see FieldsAndMethodsPane Used for member filter.\n */\npublic class TreeFiltering {\n\t/**\n\t * @param filter\n\t * \t\tText input with filter text.\n\t * @param tree\n\t * \t\tTree to filter based on input.\n\t * \t\tAssumed that tree contents are {@link FilterableTreeItem}.\n\t */\n\t@SuppressWarnings({\"unchecked\", \"rawtypes\"})\n\tpublic static void install(@Nonnull TextField filter, @Nonnull TreeView<?> tree) {\n\t\tNodeEvents.addKeyPressHandler(filter, e -> {\n\t\t\tif (e.getCode() == KeyCode.ESCAPE) {\n\t\t\t\tfilter.clear();\n\t\t\t} else if (e.getCode().isArrowKey()) {\n\t\t\t\ttree.requestFocus();\n\n\t\t\t\t// Select first expanded leaf when arrow keys used.\n\t\t\t\t// This should jump the navigation close to where the use intends\n\t\t\t\t// to be instead of restarting selection/focus at the top of the tree.\n\t\t\t\tTreeItem<?> item = tree.getRoot();\n\t\t\t\twhile (item.isExpanded() && item.getChildren().size() > 0) {\n\t\t\t\t\tboolean matched = false;\n\t\t\t\t\tfor (TreeItem<?> child : item.getChildren()) {\n\t\t\t\t\t\tif (child.isExpanded() || child.isLeaf()) {\n\t\t\t\t\t\t\tmatched = true;\n\t\t\t\t\t\t\titem = child;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!matched)\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\ttree.getSelectionModel().select((TreeItem) item);\n\t\t\t}\n\t\t});\n\t\tNodeEvents.addKeyPressHandler(tree, e -> {\n\t\t\tString text = e.getText();\n\t\t\tif (e.getCode() == KeyCode.ESCAPE) {\n\t\t\t\tfilter.clear();\n\t\t\t} else if (e.getCode() == KeyCode.ENTER) {\n\t\t\t\t// no-op, allow tree may have key-bind action for 'enter'\n\t\t\t} else if (!(e.isControlDown() || e.isAltDown()) && text != null && !text.isEmpty()) {\n\t\t\t\t// If no mask key is held down, request focus for the filter.\n\t\t\t\tfilter.requestFocus();\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/tree/TreeItems.java",
    "content": "package software.coley.recaf.ui.control.tree;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.control.MultipleSelectionModel;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\nimport software.coley.collections.Unchecked;\n\n/**\n * Utilities for {@link TreeItem} types.\n *\n * @author Matt Coley\n */\npublic class TreeItems {\n\t/**\n\t * Expand all parents to this item.\n\t */\n\tpublic static void expandParents(@Nonnull TreeItem<?> item) {\n\t\twhile ((item = getParent(item)) != null)\n\t\t\titem.setExpanded(true);\n\t}\n\n\t/**\n\t * Opens children recursively as long as only as there is only a path of single children.\n\t *\n\t * @param item\n\t * \t\tItem to recursively open.\n\t */\n\tpublic static void recurseOpen(@Nonnull TreeItem<?> item) {\n\t\titem.setExpanded(true);\n\t\tif (item.getChildren().size() == 1)\n\t\t\trecurseOpen(item.getChildren().get(0));\n\t}\n\n\t/**\n\t * Closes children recursively.\n\t *\n\t * @param tree\n\t * \t\tTree containing the item.\n\t * @param item\n\t * \t\tItem to recursively close.\n\t */\n\tpublic static void recurseClose(@Nonnull TreeView<?> tree, @Nonnull TreeItem<?> item) {\n\t\tMultipleSelectionModel<TreeItem<?>> selectionModel = Unchecked.cast(tree.getSelectionModel());\n\t\tboolean wasSelected = selectionModel.getSelectedItem() == item;\n\t\trecurseClose(item);\n\n\t\t// For some reason closing a tree item screws with selection in weird ways.\n\t\t// So we'll re-select the item afterward if it was previously the selected item.\n\t\tif (wasSelected) selectionModel.select(item);\n\t}\n\n\t/**\n\t * Closes children recursively.\n\t *\n\t * @param item\n\t * \t\tItem to recursively close.\n\t */\n\tprivate static void recurseClose(@Nonnull TreeItem<?> item) {\n\t\tif (!item.isLeaf() && item.isExpanded()) {\n\t\t\titem.setExpanded(false);\n\t\t\titem.getChildren().forEach(TreeItems::recurseClose);\n\t\t}\n\t}\n\n\t/**\n\t * @param item\n\t * \t\tTree item to get parent of.\n\t *\n\t * @return Parent tree item.\n\t */\n\t@Nullable\n\tprivate static TreeItem<?> getParent(@Nonnull TreeItem<?> item) {\n\t\tTreeItem<?> parent = item.getParent();\n\t\tif (parent == null && item instanceof FilterableTreeItem<?> filterableItem)\n\t\t\tparent = filterableItem.sourceParentProperty().get();\n\t\treturn parent;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/tree/WorkspaceRootTreeNode.java",
    "content": "package software.coley.recaf.ui.control.tree;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.Named;\nimport software.coley.recaf.path.BundlePathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.DirectoryPathNode;\nimport software.coley.recaf.path.EmbeddedResourceContainerPathNode;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.ResourcePathNode;\nimport software.coley.recaf.path.WorkspacePathNode;\nimport software.coley.recaf.ui.config.WorkspaceExplorerConfig;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.WorkspaceModificationListener;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.ResourceAndroidClassListener;\nimport software.coley.recaf.workspace.model.resource.ResourceFileListener;\nimport software.coley.recaf.workspace.model.resource.ResourceJvmClassListener;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Workspace tree item subtype representing the root of the tree.\n * <p>\n * This root offers utilities for {@link #build() automatically building} a full representation of the workspace.\n * To filter what kinds of contents are inserted when building the model, you should override the {@code visitX}\n * methods in a child class.\n *\n * @author Matt Coley\n */\npublic class WorkspaceRootTreeNode extends WorkspaceTreeNode {\n\tprivate final WorkspaceExplorerConfig explorerConfig;\n\tprivate final WorkspacePathNode rootPath;\n\tprivate final Workspace workspace;\n\tprivate final ListenerHost listenerHost = new ListenerHost();\n\n\t/**\n\t * Create new node with path value.\n\t *\n\t * @param rootPath\n\t * \t\tPath to workspace root.\n\t */\n\tpublic WorkspaceRootTreeNode(@Nonnull WorkspaceExplorerConfig explorerConfig, @Nonnull WorkspacePathNode rootPath) {\n\t\tsuper(rootPath);\n\n\t\tthis.explorerConfig = explorerConfig;\n\t\tthis.rootPath = rootPath;\n\t\tworkspace = rootPath.getValue();\n\t}\n\n\t/**\n\t * Build the tree model from the associated {@link Workspace}.\n\t */\n\tpublic void build() {\n\t\tList<WorkspaceResource> resources = workspace.getAllResources(false);\n\t\tfor (WorkspaceResource resource : resources)\n\t\t\tvisitResource(resource);\n\t}\n\n\t/**\n\t * Register listeners on the associated {@link Workspace} to facilitate automatic updates to this tree model.\n\t */\n\tpublic void addWorkspaceListeners() {\n\t\tWorkspace workspace = rootPath.getValue();\n\n\t\t// Add listeners\n\t\tworkspace.addWorkspaceModificationListener(listenerHost);\n\t\tfor (WorkspaceResource resource : workspace.getAllResources(false))\n\t\t\tresource.addListener(listenerHost);\n\t}\n\n\t/**\n\t * Unregister listeners on the associated {@link Workspace}.\n\t */\n\tpublic void removeWorkspaceListeners() {\n\t\tWorkspace workspace = rootPath.getValue();\n\n\t\t// Remove listeners\n\t\tworkspace.removeWorkspaceModificationListener(listenerHost);\n\t\tfor (WorkspaceResource resource : workspace.getAllResources(false))\n\t\t\tresource.removeListener(listenerHost);\n\t}\n\n\t/**\n\t * Adds the given resource to the tree.\n\t * All paths to items contained by the resource are generated <i>(classes, files, etc)</i>.\n\t *\n\t * @param resource\n\t * \t\tResource to add to the tree.\n\t */\n\tprotected void visitResource(@Nonnull WorkspaceResource resource) {\n\t\tResourcePathNode resourcePath = rootPath.child(resource);\n\t\tresource.classBundleStream().forEach(bundle -> visitClasses(resourcePath, bundle));\n\t\tresource.fileBundleStream().forEach(bundle -> visitFiles(resourcePath, bundle));\n\n\t\t// Create sub-trees for embedded resources\n\t\tMap<String, WorkspaceFileResource> embeddedResources = resource.getEmbeddedResources();\n\t\tif (!embeddedResources.isEmpty()) {\n\t\t\tEmbeddedResourceContainerPathNode containerPath = resourcePath.embeddedChildContainer();\n\t\t\tembeddedResources.entrySet().stream() // Insert in sorted order of path name\n\t\t\t\t\t.sorted((o1, o2) -> Named.STRING_PATH_COMPARATOR.compare(o1.getKey(), o2.getKey()))\n\t\t\t\t\t.map(Map.Entry::getValue)\n\t\t\t\t\t.forEach(embeddedResource -> {\n\t\t\t\t\t\tResourcePathNode resourcePathEmbedded = containerPath.child(embeddedResource);\n\t\t\t\t\t\tembeddedResource.classBundleStream().forEach(bundle -> visitClasses(resourcePathEmbedded, bundle));\n\t\t\t\t\t\tembeddedResource.fileBundleStream().forEach(bundle -> visitFiles(resourcePathEmbedded, bundle));\n\t\t\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Adds the given class bundle to the tree.\n\t *\n\t * @param containingResourcePath\n\t * \t\tPath to resource holding classes.\n\t * @param bundle\n\t * \t\tBundle of classes to insert.\n\t */\n\tprotected void visitClasses(@Nonnull ResourcePathNode containingResourcePath,\n\t                            @Nonnull ClassBundle<?> bundle) {\n\t\tMap<String, DirectoryPathNode> directories = new HashMap<>();\n\t\tBundlePathNode bundlePath = containingResourcePath.child(bundle);\n\t\tfor (ClassInfo classInfo : bundle.values()) {\n\t\t\tString packageName = interceptDirectoryName(classInfo.getPackageName());\n\t\t\tDirectoryPathNode packagePath = directories.computeIfAbsent(packageName, bundlePath::child);\n\t\t\tvisitClass(packagePath, classInfo);\n\t\t}\n\t}\n\n\t/**\n\t * Adds the given class to the tree.\n\t *\n\t * @param packagePath\n\t * \t\tPath of the class's containing package.\n\t * @param classInfo\n\t * \t\tClass to insert into the tree.\n\t */\n\tprotected void visitClass(@Nonnull DirectoryPathNode packagePath, @Nonnull ClassInfo classInfo) {\n\t\tClassPathNode classPath = packagePath.child(classInfo);\n\t\tWorkspaceTreeNode.getOrInsertIntoTree(this, classPath);\n\t}\n\n\t/**\n\t * Adds the given file bundle to the tree.\n\t *\n\t * @param containingResourcePath\n\t * \t\tPath to resource holding files.\n\t * @param bundle\n\t * \t\tBundle of files to insert.\n\t */\n\tprotected void visitFiles(@Nonnull ResourcePathNode containingResourcePath,\n\t                          @Nonnull FileBundle bundle) {\n\t\tMap<String, DirectoryPathNode> directories = new HashMap<>();\n\t\tBundlePathNode bundlePath = containingResourcePath.child(bundle);\n\t\tfor (FileInfo fileInfo : bundle.values()) {\n\t\t\tString directoryName = interceptDirectoryName(fileInfo.getDirectoryName());\n\t\t\tDirectoryPathNode directoryPath = directories.computeIfAbsent(directoryName, bundlePath::child);\n\t\t\tvisitFile(directoryPath, fileInfo);\n\t\t}\n\t}\n\n\t/**\n\t * Adds the given file to the tree.\n\t *\n\t * @param directoryPath\n\t * \t\tPath of the file's containing directory.\n\t * @param fileInfo\n\t * \t\tFile to insert into the tree.\n\t */\n\tprotected void visitFile(@Nonnull DirectoryPathNode directoryPath, @Nonnull FileInfo fileInfo) {\n\t\tFilePathNode filePath = directoryPath.child(fileInfo);\n\t\tWorkspaceTreeNode.getOrInsertIntoTree(this, filePath);\n\t}\n\n\t/**\n\t * @param directory\n\t * \t\tInput package or directory name.\n\t *\n\t * @return Filtered name to prevent bogus paths with thousands of embedded directories.\n\t */\n\t@Nullable\n\tprivate String interceptDirectoryName(@Nullable String directory) {\n\t\tif (directory == null)\n\t\t\treturn null;\n\t\tList<String> split = StringUtil.fastSplit(directory, true, '/');\n\t\tint max = explorerConfig.getMaxTreeDirectoryDepth();\n\t\tif (split.size() > max)\n\t\t\treturn StringUtil.cutOffAtNth(directory, '/', max) + \"...\";\n\t\treturn directory;\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to check.\n\t *\n\t * @return {@code true} when it matches our current {@link #workspace}.\n\t */\n\tpublic boolean isTargetWorkspace(@Nonnull Workspace workspace) {\n\t\treturn this.workspace == workspace;\n\t}\n\n\t/**\n\t * @param resource\n\t * \t\tResource to check.\n\t *\n\t * @return {@code true} when it belongs to the target workspace.\n\t */\n\tprivate boolean isTargetResource(@Nonnull WorkspaceResource resource) {\n\t\tif (workspace.getPrimaryResource() == resource)\n\t\t\treturn true;\n\t\tfor (WorkspaceResource supportingResource : workspace.getSupportingResources()) {\n\t\t\tif (supportingResource == resource)\n\t\t\t\treturn true;\n\t\t}\n\t\tfor (WorkspaceResource internalSupportingResource : workspace.getInternalSupportingResources()) {\n\t\t\tif (internalSupportingResource == resource)\n\t\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate class ListenerHost implements WorkspaceModificationListener, ResourceJvmClassListener, ResourceAndroidClassListener, ResourceFileListener {\n\n\t\t@Override\n\t\tpublic void onAddLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library) {\n\t\t\tif (isTargetWorkspace(workspace))\n\t\t\t\tFxThreadUtil.run(() -> visitResource(library));\n\t\t}\n\n\t\t@Override\n\t\tpublic void onRemoveLibrary(@Nonnull Workspace workspace, @Nonnull WorkspaceResource library) {\n\t\t\tif (isTargetWorkspace(workspace))\n\t\t\t\tFxThreadUtil.run(() -> removeNodeByPath(rootPath.child(library)));\n\t\t}\n\n\t\t@Override\n\t\tpublic void onNewClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls) {\n\t\t\tnewClass(resource, bundle, cls);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo oldCls, @Nonnull JvmClassInfo newCls) {\n\t\t\tupdateClass(resource, bundle, oldCls, newCls);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls) {\n\t\t\tremoveClass(resource, bundle, cls);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onNewClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo cls) {\n\t\t\tnewClass(resource, bundle, cls);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo oldCls, @Nonnull AndroidClassInfo newCls) {\n\t\t\tupdateClass(resource, bundle, oldCls, newCls);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo cls) {\n\t\t\tremoveClass(resource, bundle, cls);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onNewFile(@Nonnull WorkspaceResource resource, @Nonnull FileBundle bundle, @Nonnull FileInfo file) {\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tif (isTargetResource(resource))\n\t\t\t\t\tgetOrCreateNodeByPath(rootPath\n\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t.child(interceptDirectoryName(file.getDirectoryName()))\n\t\t\t\t\t\t\t.child(file));\n\t\t\t\telse {\n\t\t\t\t\tWorkspaceResource containingResource = resource.getContainingResource();\n\t\t\t\t\tif (containingResource != null && isTargetResource(containingResource)) {\n\t\t\t\t\t\tgetOrCreateNodeByPath(rootPath\n\t\t\t\t\t\t\t\t.child(containingResource)\n\t\t\t\t\t\t\t\t.embeddedChildContainer()\n\t\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t\t.child(interceptDirectoryName(file.getDirectoryName()))\n\t\t\t\t\t\t\t\t.child(file));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\t@Override\n\t\tpublic void onUpdateFile(@Nonnull WorkspaceResource resource, @Nonnull FileBundle bundle, @Nonnull FileInfo oldFile, @Nonnull FileInfo newFile) {\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tif (isTargetResource(resource)) {\n\t\t\t\t\tWorkspaceTreeNode node = getOrCreateNodeByPath(rootPath\n\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t.child(interceptDirectoryName(oldFile.getDirectoryName()))\n\t\t\t\t\t\t\t.child(oldFile));\n\t\t\t\t\tnode.setValue(rootPath\n\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t.child(newFile.getDirectoryName())\n\t\t\t\t\t\t\t.child(newFile));\n\t\t\t\t} else {\n\t\t\t\t\tWorkspaceResource containingResource = resource.getContainingResource();\n\t\t\t\t\tif (containingResource != null && isTargetResource(containingResource)) {\n\t\t\t\t\t\tWorkspaceTreeNode node = getOrCreateNodeByPath(rootPath.child(containingResource)\n\t\t\t\t\t\t\t\t.embeddedChildContainer()\n\t\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t\t.child(interceptDirectoryName(oldFile.getDirectoryName()))\n\t\t\t\t\t\t\t\t.child(oldFile));\n\t\t\t\t\t\tnode.setValue(rootPath.child(containingResource)\n\t\t\t\t\t\t\t\t.embeddedChildContainer()\n\t\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t\t.child(interceptDirectoryName(newFile.getDirectoryName()))\n\t\t\t\t\t\t\t\t.child(newFile));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\t@Override\n\t\tpublic void onRemoveFile(@Nonnull WorkspaceResource resource, @Nonnull FileBundle bundle, @Nonnull FileInfo file) {\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tif (isTargetResource(resource))\n\t\t\t\t\tremoveNodeByPath(rootPath\n\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t.child(interceptDirectoryName(file.getDirectoryName()))\n\t\t\t\t\t\t\t.child(file));\n\t\t\t\telse {\n\t\t\t\t\tWorkspaceResource containingResource = resource.getContainingResource();\n\t\t\t\t\tif (containingResource != null && isTargetResource(containingResource)) {\n\t\t\t\t\t\tremoveNodeByPath(rootPath\n\t\t\t\t\t\t\t\t.child(containingResource)\n\t\t\t\t\t\t\t\t.embeddedChildContainer()\n\t\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t\t.child(interceptDirectoryName(file.getDirectoryName()))\n\t\t\t\t\t\t\t\t.child(file));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tprivate void newClass(@Nonnull WorkspaceResource resource, @Nonnull ClassBundle<?> bundle, @Nonnull ClassInfo cls) {\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tif (isTargetResource(resource))\n\t\t\t\t\tgetOrCreateNodeByPath(rootPath\n\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t.child(interceptDirectoryName(cls.getPackageName()))\n\t\t\t\t\t\t\t.child(cls));\n\t\t\t\telse {\n\t\t\t\t\tWorkspaceResource containingResource = resource.getContainingResource();\n\t\t\t\t\tif (containingResource != null && isTargetResource(containingResource)) {\n\t\t\t\t\t\tgetOrCreateNodeByPath(rootPath\n\t\t\t\t\t\t\t\t.child(containingResource)\n\t\t\t\t\t\t\t\t.embeddedChildContainer()\n\t\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t\t.child(interceptDirectoryName(cls.getPackageName()))\n\t\t\t\t\t\t\t\t.child(cls));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tprivate void updateClass(@Nonnull WorkspaceResource resource, @Nonnull ClassBundle<?> bundle, @Nonnull ClassInfo oldCls, @Nonnull ClassInfo newCls) {\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tif (isTargetResource(resource)) {\n\t\t\t\t\tWorkspaceTreeNode node = getOrCreateNodeByPath(rootPath\n\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t.child(interceptDirectoryName(oldCls.getPackageName()))\n\t\t\t\t\t\t\t.child(oldCls));\n\t\t\t\t\tnode.setValue(rootPath\n\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t.child(newCls.getPackageName())\n\t\t\t\t\t\t\t.child(newCls));\n\t\t\t\t} else {\n\t\t\t\t\tWorkspaceResource containingResource = resource.getContainingResource();\n\t\t\t\t\tif (containingResource != null && isTargetResource(containingResource)) {\n\t\t\t\t\t\tWorkspaceTreeNode node = getOrCreateNodeByPath(rootPath.child(containingResource)\n\t\t\t\t\t\t\t\t.embeddedChildContainer()\n\t\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t\t.child(interceptDirectoryName(oldCls.getPackageName()))\n\t\t\t\t\t\t\t\t.child(oldCls));\n\t\t\t\t\t\tnode.setValue(rootPath.child(containingResource)\n\t\t\t\t\t\t\t\t.embeddedChildContainer()\n\t\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t\t.child(interceptDirectoryName(newCls.getPackageName()))\n\t\t\t\t\t\t\t\t.child(newCls));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tprivate void removeClass(@Nonnull WorkspaceResource resource, @Nonnull ClassBundle<?> bundle, @Nonnull ClassInfo cls) {\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tif (isTargetResource(resource))\n\t\t\t\t\tremoveNodeByPath(rootPath\n\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t.child(interceptDirectoryName(cls.getPackageName()))\n\t\t\t\t\t\t\t.child(cls));\n\t\t\t\telse {\n\t\t\t\t\tWorkspaceResource containingResource = resource.getContainingResource();\n\t\t\t\t\tif (containingResource != null && isTargetResource(containingResource)) {\n\t\t\t\t\t\tremoveNodeByPath(rootPath\n\t\t\t\t\t\t\t\t.child(containingResource)\n\t\t\t\t\t\t\t\t.embeddedChildContainer()\n\t\t\t\t\t\t\t\t.child(resource)\n\t\t\t\t\t\t\t\t.child(bundle)\n\t\t\t\t\t\t\t\t.child(interceptDirectoryName(cls.getPackageName()))\n\t\t\t\t\t\t\t\t.child(cls));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/tree/WorkspaceTree.java",
    "content": "package software.coley.recaf.ui.control.tree;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.TreeItem;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.path.WorkspacePathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.workspace.WorkspaceCloseListener;\nimport software.coley.recaf.ui.config.KeybindingConfig;\nimport software.coley.recaf.ui.config.WorkspaceExplorerConfig;\nimport software.coley.recaf.ui.control.PathNodeTree;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.NodeEvents;\nimport software.coley.recaf.workspace.model.Workspace;\n\n/**\n * Tree view for navigating a {@link Workspace}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class WorkspaceTree extends PathNodeTree implements WorkspaceCloseListener {\n\tprivate final WorkspaceExplorerConfig explorerConfig;\n\tprivate WorkspaceRootTreeNode root;\n\tprivate Workspace workspace;\n\n\t/**\n\t * Initialize empty tree.\n\t *\n\t * @param configurationService\n\t * \t\tService to configure cell content.\n\t */\n\t@Inject\n\tpublic WorkspaceTree(@Nonnull CellConfigurationService configurationService, @Nonnull Actions actions,\n\t                     @Nonnull KeybindingConfig keys, @Nonnull WorkspaceExplorerConfig explorerConfig) {\n\t\tsuper(configurationService, actions);\n\n\t\tthis.explorerConfig = explorerConfig;\n\n\t\t// Additional workspace-explorer specific bind handling\n\t\tNodeEvents.addKeyPressHandler(this, e -> {\n\t\t\tif (keys.getRename().match(e)) {\n\t\t\t\tTreeItem<PathNode<?>> selectedItem = getSelectionModel().getSelectedItem();\n\t\t\t\tif (selectedItem != null)\n\t\t\t\t\tactions.rename(selectedItem.getValue());\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Sets the workspace, and creates a complete model for it.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to represent.\n\t */\n\tpublic void createWorkspaceRoot(@Nullable Workspace workspace) {\n\t\t// Remove listeners on old workspace root node\n\t\tif (root != null)\n\t\t\troot.removeWorkspaceListeners();\n\n\t\t// Update workspace reference & populate root.\n\t\tthis.workspace = workspace;\n\t\tif (workspace == null) {\n\t\t\troot = null;\n\t\t} else {\n\t\t\t// Create root\n\t\t\tWorkspacePathNode rootPath = PathNodes.workspacePath(workspace);\n\t\t\troot = new WorkspaceRootTreeNode(explorerConfig, rootPath);\n\t\t\troot.build();\n\t\t\troot.addWorkspaceListeners();\n\t\t}\n\t\tFxThreadUtil.run(() -> setRoot(root));\n\t}\n\n\t@Override\n\tpublic void onWorkspaceClosed(@Nonnull Workspace workspace) {\n\t\t// Workspace closed, disable tree.\n\t\tif (root.isTargetWorkspace(workspace))\n\t\t\tFxThreadUtil.run(() -> setDisable(true));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/tree/WorkspaceTreeCell.java",
    "content": "package software.coley.recaf.ui.control.tree;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.TreeCell;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.cell.context.ContextSource;\n\nimport java.util.function.Function;\n\n/**\n * Cell for rendering {@link PathNode} items.\n *\n * @author Matt Coley\n */\npublic class WorkspaceTreeCell extends TreeCell<PathNode<?>> {\n\tprotected final Function<PathNode<?>, ContextSource> sourceFunc;\n\tprotected final CellConfigurationService configurationService;\n\n\t/**\n\t * @param source\n\t * \t\tContext requester source.\n\t * @param configurationService\n\t * \t\tService to configure cell content.\n\t */\n\tpublic WorkspaceTreeCell(@Nonnull ContextSource source,\n\t                         @Nonnull CellConfigurationService configurationService) {\n\t\tthis(path -> source, configurationService);\n\t}\n\n\t/**\n\t * @param sourceFunc\n\t * \t\tContext requester source function.\n\t * @param configurationService\n\t * \t\tService to configure cell content.\n\t */\n\tpublic WorkspaceTreeCell(@Nonnull Function<PathNode<?>, ContextSource> sourceFunc,\n\t                         @Nonnull CellConfigurationService configurationService) {\n\t\tthis.sourceFunc = sourceFunc;\n\t\tthis.configurationService = configurationService;\n\t}\n\n\t@Override\n\tprotected void updateItem(PathNode<?> item, boolean empty) {\n\t\tsuper.updateItem(item, empty);\n\n\t\t// Always reset the cell between item updates.\n\t\tconfigurationService.reset(this);\n\n\t\t// Apply new cell properties if the item is valid.\n\t\tif (!empty && item != null)\n\t\t\tpopulate(item);\n\t}\n\n\t/**\n\t * Called when this cell is updated to hold a new {@link PathNode}.\n\t *\n\t * @param path\n\t * \t\tPath to represent in this cell.\n\t */\n\tprotected void populate(@Nonnull PathNode<?> path) {\n\t\tconfigurationService.configure(this, path, sourceFunc.apply(path));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/tree/WorkspaceTreeFilterPane.java",
    "content": "package software.coley.recaf.ui.control.tree;\n\nimport atlantafx.base.controls.CustomTextField;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.scene.control.TextField;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.layout.BorderPane;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.DirectoryPathNode;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.ui.control.BoundToggleIcon;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.util.Lang;\n\nimport java.util.function.Predicate;\n\n/**\n * Pane component to filter what is visible in a given {@link WorkspaceTree}.\n *\n * @author Matt Coley\n */\npublic class WorkspaceTreeFilterPane extends BorderPane {\n\tprivate final ObjectProperty<Predicate<TreeItem<PathNode<?>>>> currentPredicate = new SimpleObjectProperty<>();\n\tprivate final SimpleBooleanProperty caseSensitivity = new SimpleBooleanProperty(false);\n\tprivate final CustomTextField textField = new CustomTextField();\n\n\t/**\n\t * @param tree\n\t * \t\tTree to filter.\n\t */\n\tpublic WorkspaceTreeFilterPane(@Nonnull WorkspaceTree tree) {\n\t\tBoundToggleIcon toggleSensitivity = new BoundToggleIcon(new FontIconView(CarbonIcons.LETTER_CC), caseSensitivity).withTooltip(\"misc.casesensitive\");\n\t\ttoggleSensitivity.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.ACCENT, Styles.FLAT, Styles.SMALL);\n\t\ttextField.rightProperty().set(toggleSensitivity);\n\n\t\ttextField.promptTextProperty().bind(Lang.getBinding(\"workspace.filter-prompt\"));\n\t\tsetCenter(textField);\n\t\tgetStyleClass().add(\"workspace-filter-pane\");\n\t\ttextField.getStyleClass().add(\"workspace-filter-text\");\n\n\t\ttextField.textProperty().addListener((ob, old, cur) -> update(tree));\n\t\tcaseSensitivity.addListener((ob, old, cur) -> update(tree));\n\t}\n\n\t/**\n\t * @return Current predicate assigned to the workspace tree as a filter.\n\t */\n\t@Nonnull\n\tpublic ObjectProperty<Predicate<TreeItem<PathNode<?>>>> currentPredicateProperty() {\n\t\treturn currentPredicate;\n\t}\n\n\t/**\n\t * @return Text field for filtering by path names.\n\t */\n\t@Nonnull\n\tpublic TextField getTextField() {\n\t\treturn textField;\n\t}\n\n\tprivate void update(@Nonnull WorkspaceTree tree) {\n\t\tWorkspaceTreeNode root = (WorkspaceTreeNode) tree.getRoot();\n\t\tif (root == null) return;\n\n\t\tObjectProperty<Predicate<TreeItem<PathNode<?>>>> rootPredicate = root.predicateProperty();\n\t\tif (textField.getText().isEmpty()) {\n\t\t\trootPredicate.set(null);\n\t\t\tcurrentPredicate.set(null);\n\t\t} else {\n\t\t\tPredicate<TreeItem<PathNode<?>>> matcher = this::match;\n\t\t\trootPredicate.set(matcher);\n\t\t\tcurrentPredicate.set(matcher);\n\t\t}\n\t}\n\n\tprivate boolean match(@Nonnull TreeItem<PathNode<?>> item) {\n\t\tPathNode<?> node = item.getValue();\n\t\tString path = switch (node) {\n\t\t\tcase DirectoryPathNode directoryNode -> directoryNode.getValue();\n\t\t\tcase ClassPathNode classPathNode -> classPathNode.getValue().getName();\n\t\t\tcase FilePathNode classPathNode -> classPathNode.getValue().getName();\n\t\t\tcase null, default -> null;\n\t\t};\n\n\t\t// Some PathNode types do not correlate to things that can be represented as file paths.\n\t\t// For instance, the actual file bundle containing files, can't represent that because it is\n\t\t// effectively the root.\n\t\t//\n\t\t// When we find a PathNode that does not have a \"file path\" like string, we will only show it\n\t\t// if it has children. This will hide trees that don't have any actual results in them.\n\t\tif (path == null) return !item.getChildren().isEmpty();\n\n\t\t// Otherwise, for things like classes and files, we'll match their path in their respective bundles\n\t\t// to the text-field input.\n\t\treturn caseSensitivity.get() ?\n\t\t\t\tpath.contains(textField.getText()) :\n\t\t\t\tpath.toLowerCase().contains(textField.getText().toLowerCase());\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/control/tree/WorkspaceTreeNode.java",
    "content": "package software.coley.recaf.ui.control.tree;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.collections.ObservableList;\nimport javafx.scene.control.TreeItem;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.path.BundlePathNode;\nimport software.coley.recaf.path.DirectoryPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.List;\n\n/**\n * Tree item subtype for more convenience tree building operations.\n *\n * @author Matt Coley\n */\npublic class WorkspaceTreeNode extends FilterableTreeItem<PathNode<?>> implements Comparable<WorkspaceTreeNode> {\n\t/**\n\t * Create new node with path value.\n\t *\n\t * @param path\n\t * \t\tPath of represented item.\n\t */\n\tpublic WorkspaceTreeNode(PathNode<?> path) {\n\t\tsetValue(path);\n\t}\n\n\t/**\n\t * Removes a tree node from the tree by its {@link PathNode} equality.\n\t *\n\t * @param path\n\t * \t\tPath to remove from the tree.\n\t *\n\t * @return {@code true} when removal is a success.\n\t * {@code false} if nothing was removed.\n\t */\n\tpublic synchronized boolean removeNodeByPath(@Nonnull PathNode<?> path) {\n\t\t// Get node by path from the root.\n\t\tWorkspaceTreeNode nodeByPath = getRoot().getNodeByPath(path);\n\n\t\t// Get that node's parent, remove the child.\n\t\tif (nodeByPath != null) {\n\t\t\tWorkspaceTreeNode parentNode = nodeByPath.getSourceParentNode();\n\t\t\tif (parentNode != null) {\n\t\t\t\tboolean removed = parentNode.removeSourceChild(nodeByPath);\n\t\t\t\twhile (parentNode.isSourceLeaf() && parentNode.getSourceParentNode() != null) {\n\t\t\t\t\tWorkspaceTreeNode parentOfParent = parentNode.getSourceParentNode();\n\t\t\t\t\tparentOfParent.removeSourceChild(parentNode);\n\t\t\t\t\tparentNode = parentOfParent;\n\t\t\t\t}\n\t\t\t\treturn removed;\n\t\t\t}\n\t\t}\n\n\t\t// No known node by path.\n\t\treturn false;\n\t}\n\n\t/**\n\t * Gets or creates a tree node by the given {@link PathNode}.\n\t *\n\t * @param path\n\t * \t\tPath associated with node to look for in tree.\n\t *\n\t * @return Node containing the path in the tree.\n\t */\n\t@Nonnull\n\tpublic synchronized WorkspaceTreeNode getOrCreateNodeByPath(@Nonnull PathNode<?> path) {\n\t\t// Call from root node only.\n\t\tWorkspaceTreeNode root = getRoot();\n\n\t\t// Lookup and/or create nodes for path.\n\t\treturn getOrInsertIntoTree(root, path);\n\t}\n\n\t/**\n\t * Searches for a {@link WorkspaceTreeNode} item in the tree model, matching the given path.\n\t *\n\t * @param path\n\t * \t\tPath associated with node to look for in tree.\n\t *\n\t * @return Node containing the path in the tree.\n\t */\n\t@Nullable\n\tpublic synchronized WorkspaceTreeNode getNodeByPath(@Nonnull PathNode<?> path) {\n\t\t// Base case, we are that path.\n\t\tPathNode<?> value = getValue();\n\t\tif (path.equals(value))\n\t\t\treturn this;\n\n\t\t// Check all children for a match, regardless of the current filter.\n\t\tfor (TreeItem<PathNode<?>> child : getSourceChildren())\n\t\t\tif (path.isDescendantOf(child.getValue()) && child instanceof WorkspaceTreeNode childNode)\n\t\t\t\treturn childNode.getNodeByPath(path);\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @return First child tree node. {@code null} if no child is found.\n\t */\n\t@Nullable\n\tpublic synchronized WorkspaceTreeNode getFirstChild() {\n\t\t// Get first child, regardless of the current filter.\n\t\tvar children = getSourceChildren();\n\t\treturn children.isEmpty()\n\t\t\t\t? null : children.getFirst() instanceof WorkspaceTreeNode node\n\t\t\t\t? node : null;\n\t}\n\n\t/**\n\t * @return The root of this tree node's parent hierarchy.\n\t */\n\t@Nonnull\n\tpublic WorkspaceTreeNode getRoot() {\n\t\tWorkspaceTreeNode root = this;\n\t\twhile (true) {\n\t\t\tWorkspaceTreeNode parentNode = root.getSourceParentNode();\n\t\t\tif (parentNode == null)\n\t\t\t\tbreak;\n\t\t\troot = parentNode;\n\t\t}\n\t\treturn root;\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to check against.\n\t *\n\t * @return {@code true} when the current node's path matches.\n\t */\n\tpublic boolean matches(@Nonnull PathNode<?> path) {\n\t\treturn path.equals(getValue());\n\t}\n\n\t/**\n\t * @return {@link #getSourceParent()} but cast to {@link WorkspaceTreeNode}.\n\t */\n\t@Nullable\n\tpublic WorkspaceTreeNode getSourceParentNode() {\n\t\treturn (WorkspaceTreeNode) getSourceParent();\n\t}\n\n\t@Override\n\tpublic int compareTo(@Nonnull WorkspaceTreeNode o) {\n\t\treturn getValue().compareTo(o.getValue());\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn getClass().getSimpleName() + \"[\" + getValue().toString() + \"]\";\n\t}\n\n\t/**\n\t * A debugging util to inspect the state of the tree without having to dig through\n\t * the actual references in the debugger.\n\t *\n\t * @return String representation of this tree and all of its descendants.\n\t */\n\t@Nonnull\n\tpublic String printTree() {\n\t\tStringBuilder sb = new StringBuilder(toString());\n\t\tfor (TreeItem<PathNode<?>> child : getSourceChildren()) {\n\t\t\tif (child instanceof WorkspaceTreeNode childNode) {\n\t\t\t\tString childTree = childNode.printTree();\n\t\t\t\tfor (String childTreeEntry : StringUtil.fastSplit(childTree, false, '\\n')) {\n\t\t\t\t\tsb.append(\"\\n    \").append(childTreeEntry);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn sb.toString();\n\t}\n\n\t/**\n\t * Get/insert a {@link WorkspaceTreeNode} holding the given {@link PathNode} from/to the tree model.\n\t *\n\t * @param node\n\t * \t\tTree node to insert into.\n\t * @param path\n\t * \t\tPath to insert, relative to the given node.\n\t *\n\t * @return Inserted node.\n\t */\n\t@Nonnull\n\tpublic static WorkspaceTreeNode getOrInsertIntoTree(@Nonnull WorkspaceTreeNode node, @Nonnull PathNode<?> path) {\n\t\t// Edge case handling for directory nodes.\n\t\tif (path instanceof DirectoryPathNode directoryPath) {\n\t\t\t// If we have parent links in our path, insert those first.\n\t\t\t// We should generate up to whatever context our parent is.\n\t\t\tBundlePathNode parent = directoryPath.getParent();\n\t\t\tif (parent != null)\n\t\t\t\tnode = getOrInsertIntoTree(node, parent);\n\n\t\t\t// Work off of the first node that does NOT contain a directory value.\n\t\t\t// This should result in the node pointing to a bundle.\n\t\t\twhile (node.getValue() instanceof DirectoryPathNode) {\n\t\t\t\tnode = (WorkspaceTreeNode) node.getSourceParent();\n\t\t\t\tif (node == null)\n\t\t\t\t\tthrow new IllegalStateException(\"Directory path node had no parent in workspace tree\");\n\t\t\t}\n\n\t\t\t// Insert the directory path, separated by '/'.\n\t\t\t// Update 'node' as we build/fetch the directory path items.\n\t\t\t// We use '-1' as a limit in split to allow empty directories to be split properly:\n\t\t\t//  '//' --> ['', '', '']\n\t\t\tString fullDirectory = directoryPath.getValue();\n\t\t\tString[] directoryParts = fullDirectory.split(\"/\", -1);\n\n\t\t\tStringBuilder directoryBuilder = new StringBuilder();\n\t\t\tfor (String directoryPart : directoryParts) {\n\t\t\t\t// Build up directory path.\n\t\t\t\tdirectoryBuilder.append(directoryPart).append('/');\n\t\t\t\tString directoryName = directoryBuilder.substring(0, directoryBuilder.length() - 1);\n\t\t\t\tDirectoryPathNode localPathNode = directoryPath.withDirectory(directoryName);\n\n\t\t\t\t// Get existing tree node, or create child if non-existent\n\t\t\t\tWorkspaceTreeNode childNode;\n\t\t\t\tObservableList<WorkspaceTreeNode> children = Unchecked.cast(node.getSourceChildren());\n\t\t\t\tint index = binaryUnboxingSearch(children, localPathNode);\n\t\t\t\tif (index >= 0)\n\t\t\t\t\tchildNode = children.get(index);\n\t\t\t\telse {\n\t\t\t\t\tchildNode = new WorkspaceTreeNode(localPathNode);\n\t\t\t\t\tnode.addPreSortedChild(childNode, -(index + 1));\n\t\t\t\t}\n\n\t\t\t\t// Prepare for next directory path entry.\n\t\t\t\tnode = childNode;\n\t\t\t}\n\t\t\treturn node;\n\t\t}\n\n\t\t// If we have parent links in our path, insert those first.\n\t\t// We should generate up to whatever context our parent is.\n\t\tPathNode<?> parent = path.getParent();\n\t\tif (parent != null)\n\t\t\tnode = getOrInsertIntoTree(node, parent);\n\t\telse if (path.typeIdMatch(node.getValue())) {\n\t\t\t// We are the root link in the path. This check ensures that as the root type we do not\n\t\t\t// insert a new tree-node of the same value, to the children list of the root tree node.\n\t\t\treturn node;\n\t\t}\n\n\t\t// Check if already inserted.\n\t\tObservableList<WorkspaceTreeNode> children = Unchecked.cast(node.getSourceChildren());\n\t\tWorkspaceTreeNode inserted;//= new WorkspaceTreeNode(path);\n\t\tint index = binaryUnboxingSearch(children, path);\n\t\tif (index >= 0)\n\t\t\treturn children.get(index);\n\t\tinserted = new WorkspaceTreeNode(path);\n\n\t\t// Not already inserted, create a new node and insert it.\n\t\tnode.addPreSortedChild(inserted, -(index + 1));\n\n\t\treturn inserted;\n\t}\n\n\t/**\n\t * Binary search but using {@link PathNode#localCompare(software.coley.recaf.path.PathNode)}\n\t * rather than the standard {@link java.lang.Comparable#compareTo(java.lang.Object)}.\n\t */\n\tprivate static int binaryUnboxingSearch(@Nonnull List<WorkspaceTreeNode> items, @Nonnull PathNode<?> target) {\n\t\tint first = 0;\n\t\tint last = items.size() - 1;\n\t\twhile (first <= last) {\n\t\t\tint middle = (first + last) >>> 1;\n\t\t\tPathNode<?> item = items.get(middle).getValue();\n\t\t\tint compResult = item.getClass() == target.getClass() ?\n\t\t\t\t\titem.localCompare(target) :\n\t\t\t\t\titem.compareTo(target);\n\t\t\tif (compResult < 0)\n\t\t\t\tfirst = middle + 1;\n\t\t\telse if (compResult > 0)\n\t\t\t\tlast = middle - 1;\n\t\t\telse\n\t\t\t\treturn middle;\n\t\t}\n\t\treturn -(first + 1);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/dnd/DragAndDrop.java",
    "content": "package software.coley.recaf.ui.dnd;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.input.DragEvent;\nimport javafx.scene.input.Dragboard;\nimport javafx.scene.input.TransferMode;\nimport javafx.scene.layout.Region;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Drag and drop utilities.\n *\n * @author Matt Coley\n */\npublic class DragAndDrop {\n\tprivate static final Logger logger = Logging.get(DragAndDrop.class);\n\n\t/**\n\t * Install drag-drop support on the given region.\n\t *\n\t * @param region\n\t * \t\tControl to support.\n\t * @param listener\n\t * \t\tBehavior when file content is dropped into the region.\n\t */\n\tpublic static void installFileSupport(@Nonnull Region region, @Nonnull FileDropListener listener) {\n\t\tregion.setOnDragOver(e -> onDragOver(region, e));\n\t\tregion.setOnDragDropped(e -> onDragDropped(region, e, listener));\n\t\tregion.setOnDragEntered(e -> onDragEntered(region, e, listener));\n\t\tregion.setOnDragExited(e -> onDragExited(region, e, listener));\n\t}\n\n\t/**\n\t * Handle drag-over.\n\t *\n\t * @param region\n\t * \t\tRegion dragged over.\n\t * @param event\n\t * \t\tDrag event.\n\t */\n\tprivate static void onDragOver(@Nonnull Region region, @Nonnull DragEvent event) {\n\t\t// TODO: Some platforms may report files via 'text/uri-list'\n\t\t//  - Hard to test due to diversity of linux desktop environments, but seems to only be reported on linux.\n\t\t//  - In general we'd want to support 'hasUrl' as well here and below then map the uris to paths.\n\t\tif (event.getGestureSource() != region && event.getDragboard().hasFiles()) {\n\t\t\tevent.acceptTransferModes(TransferMode.COPY_OR_MOVE);\n\t\t\tevent.consume();\n\t\t}\n\t}\n\n\t/**\n\t * Pass drag-drop to listener.\n\t *\n\t * @param region\n\t * \t\tRegion dropped completed on.\n\t * @param event\n\t * \t\tDrag event.\n\t * @param listener\n\t * \t\tListener to call when drop completed.\n\t */\n\tprivate static void onDragDropped(@Nonnull Region region, @Nonnull DragEvent event, @Nonnull FileDropListener listener) {\n\t\tDragboard db = event.getDragboard();\n\t\tboolean success = true;\n\t\tif (db.hasFiles()) {\n\t\t\ttry {\n\t\t\t\tList<Path> paths = db.getFiles().stream()\n\t\t\t\t\t\t.map(File::toPath)\n\t\t\t\t\t\t.collect(Collectors.toList());\n\t\t\t\tlistener.onDragDrop(region, event, paths);\n\t\t\t} catch (IOException ex) {\n\t\t\t\tlogger.error(\"Failed drag-and-drop due to IO\", ex);\n\t\t\t\tsuccess = false;\n\t\t\t} catch (Throwable ex) {\n\t\t\t\tlogger.error(\"Failed drag-and-drop due to unhanded error\", ex);\n\t\t\t\tsuccess = false;\n\t\t\t}\n\t\t\tevent.consume();\n\t\t}\n\t\tevent.setDropCompleted(success);\n\t}\n\n\t/**\n\t * Pass drag-entering to listener.\n\t *\n\t * @param region\n\t * \t\tRegion dragged over.\n\t * @param event\n\t * \t\tDrag event.\n\t * @param listener\n\t * \t\tListener to call when drag enters.\n\t */\n\tprivate static void onDragEntered(@Nonnull Region region, @Nonnull DragEvent event, @Nonnull FileDropListener listener) {\n\t\tlistener.onDragEnter(region, event);\n\t\tevent.consume();\n\t}\n\n\t/**\n\t * Pass drag-leaving to listener.\n\t *\n\t * @param region\n\t * \t\tRegion dragged over.\n\t * @param event\n\t * \t\tDrag event.\n\t * @param listener\n\t * \t\tListener to call when drag leaves.\n\t */\n\tprivate static void onDragExited(@Nonnull Region region, @Nonnull DragEvent event, @Nonnull FileDropListener listener) {\n\t\tlistener.onDragExit(region, event);\n\t\tevent.consume();\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/dnd/FileDropListener.java",
    "content": "package software.coley.recaf.ui.dnd;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.input.DragEvent;\nimport javafx.scene.layout.Region;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.List;\n\n/**\n * Simple file drop listener.\n *\n * @author Matt Coley\n */\npublic interface FileDropListener {\n\t/**\n\t * Called when the drop is complete.\n\t *\n\t * @param region\n\t * \t\tRegion dropped on.\n\t * @param event\n\t * \t\tDrop event.\n\t * @param files\n\t * \t\tFiles dropped.\n\t *\n\t * @throws IOException\n\t * \t\tWhen handling of the files fails.\n\t */\n\tvoid onDragDrop(@Nonnull Region region, @Nonnull DragEvent event, @Nonnull List<Path> files) throws IOException;\n\n\t/**\n\t * Called when drag moves over the control.\n\t *\n\t * @param region\n\t * \t\tRegion moused over.\n\t * @param event\n\t * \t\tDrag event.\n\t */\n\tdefault void onDragEnter(@Nonnull Region region, @Nonnull DragEvent event) {\n\t\t// no-op\n\t}\n\n\t/**\n\t * Called when drag moves outside the control.\n\t *\n\t * @param region\n\t * \t\tRegion left.\n\t * @param event\n\t * \t\tDrag event.\n\t */\n\tdefault void onDragExit(@Nonnull Region region, @Nonnull DragEvent event) {\n\t\t// no-op\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/dnd/WorkspaceLoadingDropListener.java",
    "content": "package software.coley.recaf.ui.dnd;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.input.DragEvent;\nimport javafx.scene.layout.Region;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.ui.config.WorkspaceExplorerConfig;\nimport software.coley.recaf.ui.pane.WelcomePane;\nimport software.coley.recaf.ui.pane.WorkspaceExplorerPane;\nimport software.coley.recaf.util.ErrorDialogs;\nimport software.coley.recaf.workspace.PathLoadingManager;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static software.coley.recaf.util.Lang.getBinding;\n\n/**\n * Listener implementation to handle drag-and-drop to load workspace content.\n *\n * @author Matt Coley\n * @see WorkspaceExplorerPane Usage for when a workspace is already open.\n * @see WelcomePane Usage for when a user first starts Recaf.\n */\n@ApplicationScoped\npublic class WorkspaceLoadingDropListener implements FileDropListener {\n\tprivate static final Logger logger = Logging.get(WorkspaceExplorerPane.class);\n\tprivate final WorkspaceExplorerConfig config;\n\tprivate final PathLoadingManager pathLoadingManager;\n\tprivate final WorkspaceManager workspaceManager;\n\n\t@Inject\n\tpublic WorkspaceLoadingDropListener(@Nonnull WorkspaceExplorerConfig config,\n\t\t\t\t\t\t\t\t\t\t@Nonnull PathLoadingManager pathLoadingManager,\n\t\t\t\t\t\t\t\t\t\t@Nonnull WorkspaceManager workspaceManager) {\n\t\tthis.config = config;\n\t\tthis.pathLoadingManager = pathLoadingManager;\n\t\tthis.workspaceManager = workspaceManager;\n\t}\n\n\t@Override\n\tpublic void onDragDrop(@Nonnull Region region, @Nonnull DragEvent event, @Nonnull List<Path> files) {\n\t\t// Sanity check input\n\t\tif (files.isEmpty()) return;\n\n\t\tif (config.createOnDragDrop() || !workspaceManager.hasCurrentWorkspace()) {\n\t\t\t// Windows sucks: https://superuser.com/questions/1696568/windows-explorer-file-order-in-the-clipboard\n\t\t\t//  - We can get the last clicked file to always be first when dragging from explorer\n\t\t\t//    but everything else will be in shuffled order, and we can't do anything about it.\n\t\t\t//  - Example: Using 'everything' instead of explorer for selection, we cannot guarantee drag-n-drop order.\n\t\t\tPath primary = files.getFirst();\n\t\t\tList<Path> supporting = files.size() > 1 ? files.subList(1, files.size()) : Collections.emptyList();\n\t\t\tpathLoadingManager.asyncNewWorkspace(primary, supporting, err -> {\n\t\t\t\tlogger.error(\"Failed to create new workspace from dropped files\", err);\n\t\t\t\tErrorDialogs.show(\n\t\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.title\"),\n\t\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.header\"),\n\t\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.content\"),\n\t\t\t\t\t\terr\n\t\t\t\t);\n\t\t\t});\n\t\t} else if (workspaceManager.hasCurrentWorkspace() && config.appendOnDragDrop()) {\n\t\t\t// Append files to current workspace\n\t\t\tpathLoadingManager.asyncAddSupportingResourcesToWorkspace(workspaceManager.getCurrent(), files, err -> {\n\t\t\t\tlogger.error(\"Failed to add supporting resources from dropped files\", err);\n\t\t\t\tErrorDialogs.show(\n\t\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.title\"),\n\t\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.header\"),\n\t\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.content\"),\n\t\t\t\t\t\terr\n\t\t\t\t);\n\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/docking/DockingManager.java",
    "content": "package software.coley.recaf.ui.docking;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Orientation;\nimport javafx.geometry.Side;\nimport javafx.scene.Node;\nimport javafx.scene.Scene;\nimport javafx.scene.control.ContextMenu;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.MenuItem;\nimport javafx.scene.control.SplitPane;\nimport javafx.stage.Stage;\nimport org.kordamp.ikonli.Ikon;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.bentofx.Bento;\nimport software.coley.bentofx.building.DockBuilding;\nimport software.coley.bentofx.building.StageBuilding;\nimport software.coley.bentofx.control.DragDropStage;\nimport software.coley.bentofx.control.Headers;\nimport software.coley.bentofx.dockable.Dockable;\nimport software.coley.bentofx.dockable.DockableDragDropBehavior;\nimport software.coley.bentofx.dockable.DockableIconFactory;\nimport software.coley.bentofx.layout.DockContainer;\nimport software.coley.bentofx.layout.container.DockContainerBranch;\nimport software.coley.bentofx.layout.container.DockContainerLeaf;\nimport software.coley.bentofx.layout.container.DockContainerRootBranch;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.PriorityKeys;\nimport software.coley.recaf.services.info.summary.ResourceSummaryServiceConfig;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.navigation.NavigationManager;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.services.workspace.WorkspaceCloseListener;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.services.workspace.WorkspaceOpenListener;\nimport software.coley.recaf.ui.control.ActionMenuItem;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.pane.LoggingPane;\nimport software.coley.recaf.ui.pane.WelcomePane;\nimport software.coley.recaf.ui.pane.WorkspaceExplorerPane;\nimport software.coley.recaf.ui.pane.WorkspaceInformationPane;\nimport software.coley.recaf.ui.window.RecafScene;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.UUID;\n\n/**\n * Facilitates creation, inspection, and updates of dockable UI content.\n * <p>\n * This currently covers:\n * <ul>\n *     <li>Displaying {@link WelcomePane} when no workspace is open</li>\n *     <li>Displaying {@link WorkspaceExplorerPane} when a workspace is open</li>\n * </ul>\n *\n * @author Matt Coley\n * @see NavigationManager\n */\n@ApplicationScoped\npublic class DockingManager {\n\tprivate static final Logger logger = Logging.get(DockingManager.class);\n\n\t/** Size in px for {@link #newBottomContainer()} */\n\tprivate static final double BOTTOM_SIZE = 100;\n\t/** Size in px for the {@link WorkspaceExplorerPane} in {@link #newWorkspaceContainer()} */\n\tprivate static final double TOP_WORKSPACE_EXPLORER_SIZE = 0.25;\n\t/** Split layout holding {@link #ID_CONTAINER_ROOT_TOP} and {@link #ID_CONTAINER_ROOT_BOTTOM} */\n\tpublic static final String ID_CONTAINER_ROOT_SPLIT = \"layout-root-split\";\n\t/** Top half of the main UI at initial layout. */\n\tpublic static final String ID_CONTAINER_ROOT_TOP = \"layout-root-top\";\n\t/** Bottom half of the main UI at initial layout. */\n\tpublic static final String ID_CONTAINER_ROOT_BOTTOM = \"layout-root-bottom\";\n\t/** {@link DockContainerLeaf} holding the {@link WorkspaceExplorerPane} */\n\tpublic static final String ID_CONTAINER_WORKSPACE_TOOLS = \"layout-workspace-tools\";\n\t/** {@link DockContainerLeaf} holding the primary editor tabs. */\n\tpublic static final String ID_CONTAINER_WORKSPACE_PRIMARY = \"layout-workspace-primary\";\n\t/** Bento group ID for tool tabs. */\n\tpublic static final int GROUP_TOOLS = 1;\n\t/** Bento group ID for things that can go anywhere, except for in {@link #GROUP_NEVER_RECEIVE}. */\n\tpublic static final int GROUP_ANYWHERE = -1;\n\t/** Bento group ID for things that can never have things dragged on them. */\n\tpublic static final int GROUP_NEVER_RECEIVE = -25565;\n\n\tprivate final Actions actions;\n\tprivate final Instance<LoggingPane> loggingPaneProvider;\n\tprivate final Instance<WelcomePane> welcomePaneProvider;\n\tprivate final Instance<WorkspaceExplorerPane> workspaceExplorerProvider;\n\tprivate final DockContainerRootBranch root;\n\tprivate final ResourceSummaryServiceConfig resourceSummaryConfig;\n\n\t// The primary bento instance\n\tprivate final Bento bento = new Bento() {\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected DockBuilding newDockBuilding() {\n\t\t\treturn new DockBuilding(this) {\n\t\t\t\t@Nonnull\n\t\t\t\t@Override\n\t\t\t\tpublic DockContainerLeaf leaf(@Nonnull String identifier) {\n\t\t\t\t\tDockContainerLeaf leaf = super.leaf(identifier);\n\t\t\t\t\tleaf.setMenuFactory(DockingManager.this::buildMenu);\n\t\t\t\t\treturn leaf;\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected StageBuilding newStageBuilding() {\n\t\t\treturn new StageBuilding(this) {\n\t\t\t\t@Override\n\t\t\t\tprotected void initializeFromSource(@Nonnull Scene sourceScene, @Nonnull Scene newScene,\n\t\t\t\t                                    @Nullable Stage sourceStage, @Nonnull DragDropStage newStage,\n\t\t\t\t                                    boolean sourceIsOwner) {\n\t\t\t\t\t// Always pass 'false' for 'sourceIsOwner'. Window ownership is generally something you would\n\t\t\t\t\t// want to specify, but it makes the window behave like a dialog which cannot be minimized.\n\t\t\t\t\tsuper.initializeFromSource(sourceScene, newScene, sourceStage, newStage, false);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected DockableDragDropBehavior newDragDropBehavior() {\n\t\t\treturn new DockableDragDropBehavior() {\n\t\t\t\t@Override\n\t\t\t\tpublic boolean canReceiveDockable(@Nonnull DockContainerLeaf targetContainer, @Nullable Side targetSide,\n\t\t\t\t                                  @Nonnull Dockable dockable) {\n\t\t\t\t\t// If we see the 'never receive' ID in a container then we bail.\n\t\t\t\t\tif (targetContainer.getDockables().stream().anyMatch(d -> d.getDragGroupMask() == GROUP_NEVER_RECEIVE))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\t// If the dockable being dragged can go 'anywhere' then we let it go anywhere.\n\t\t\t\t\tif (dockable.getDragGroupMask() == GROUP_ANYWHERE)\n\t\t\t\t\t\treturn true;\n\n\t\t\t\t\t// Otherwise we fall back to the default behavior.\n\t\t\t\t\treturn DockableDragDropBehavior.super.canReceiveDockable(targetContainer, targetSide, dockable);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t};\n\n\t@Inject\n\tpublic DockingManager(@Nonnull WorkspaceManager workspaceManager,\n\t                      @Nonnull WindowManager windowManager,\n\t                      @Nonnull Actions actions,\n\t                      @Nonnull Instance<LoggingPane> loggingPaneProvider,\n\t                      @Nonnull Instance<WelcomePane> welcomePaneProvider,\n\t                      @Nonnull Instance<WorkspaceInformationPane> workspaceInfoProvider,\n\t                      @Nonnull Instance<WorkspaceExplorerPane> workspaceExplorerProvider,\n\t                      @Nonnull ResourceSummaryServiceConfig resourceSummaryConfig) {\n\t\tthis.actions = actions;\n\t\tthis.loggingPaneProvider = loggingPaneProvider;\n\t\tthis.welcomePaneProvider = welcomePaneProvider;\n\t\tthis.workspaceExplorerProvider = workspaceExplorerProvider;\n\t\tthis.resourceSummaryConfig = resourceSummaryConfig;\n\n\t\t// Stages created via our docking framework need to be tracked in the window manager.\n\t\tbento.stageBuilding().setStageFactory(originScene -> {\n\t\t\tDragDropStage stage = new DragDropStage(true);\n\t\t\twindowManager.register(\"dnd-\" + UUID.randomUUID(), stage);\n\t\t\treturn stage;\n\t\t});\n\t\tbento.stageBuilding().setSceneFactory((sourceScene, content, width, height) -> {\n\t\t\tcontent.getStyleClass().add(\"bg-inset\");\n\t\t\treturn new RecafScene(content, width, height);\n\t\t});\n\n\t\t// Due to how we style the headers, we want the drawing to be clipped.\n\t\tbento.controlsBuilding().setHeadersFactory(ClippedHeaders::new);\n\n\t\t// Register listeners\n\t\tworkspaceManager.addWorkspaceOpenListener(new SummaryDisplayListener());\n\t\tworkspaceManager.addWorkspaceCloseListener(new WelcomeDisplayListener());\n\n\t\t// Create root\n\t\tDockBuilding builder = bento.dockBuilding();\n\t\tDockContainer top = newWelcomeContainer();\n\t\tDockContainer bottom = newBottomContainer();\n\t\tbuilder.root(ID_CONTAINER_ROOT_SPLIT);\n\t\troot = builder.root(ID_CONTAINER_ROOT_SPLIT);\n\t\troot.setOrientation(Orientation.VERTICAL);\n\t\troot.addContainers(top, bottom);\n\t\troot.setContainerSizePx(bottom, BOTTOM_SIZE);\n\t\tbento.registerRoot(root);\n\t}\n\n\t/**\n\t * @return Backing bento docking instance.\n\t */\n\t@Nonnull\n\tpublic Bento getBento() {\n\t\treturn bento;\n\t}\n\n\t/**\n\t * @return The root docking container.\n\t */\n\t@Nonnull\n\tpublic DockContainerRootBranch getRoot() {\n\t\treturn root;\n\t}\n\n\t/**\n\t * The primary container is where content in the workspace is displayed when opened.\n\t * Opening classes and files should place content in here.\n\t *\n\t * @return The primary docking container leaf where most content is placed in the UI.\n\t */\n\t@Nonnull\n\tpublic DockContainerLeaf getPrimaryDockingContainer() {\n\t\tvar path = bento.search().container(DockingManager.ID_CONTAINER_WORKSPACE_PRIMARY);\n\t\tif (path != null && path.tailContainer() instanceof DockContainerLeaf leaf)\n\t\t\treturn leaf;\n\t\t// TODO: Only fails when opening tutorial\n\t\tthrow new IllegalStateException(\"Primary docking leaf could not be found\");\n\t}\n\n\t/**\n\t * Creates a {@link Dockable} that is assigned to the {@link #GROUP_TOOLS} group.\n\t *\n\t * @param translationKey\n\t * \t\tDockable title translation key.\n\t * @param icon\n\t * \t\tDockable icon.\n\t * @param content\n\t * \t\tDockable content to display.\n\t *\n\t * @return Created dockable.\n\t */\n\t@Nonnull\n\tpublic Dockable newToolDockable(@Nonnull String translationKey, @Nonnull Ikon icon, @Nonnull Node content) {\n\t\treturn newToolDockable(translationKey, d -> new FontIconView(icon), content);\n\t}\n\n\t/**\n\t * Creates a {@link Dockable} that is assigned to the {@link #GROUP_TOOLS} group.\n\t *\n\t * @param translationKey\n\t * \t\tDockable title translation key.\n\t * @param iconFactory\n\t * \t\tDockable icon factory.\n\t * @param content\n\t * \t\tDockable content to display.\n\t *\n\t * @return Created dockable.\n\t */\n\t@Nonnull\n\tpublic Dockable newToolDockable(@Nonnull String translationKey, @Nonnull DockableIconFactory iconFactory, @Nonnull Node content) {\n\t\tDockable dockable = bento.dockBuilding().dockable(translationKey);\n\t\tdockable.titleProperty().bind(Lang.getBinding(translationKey));\n\t\tdockable.setNode(content);\n\t\tdockable.setIconFactory(iconFactory);\n\t\tdockable.setClosable(false);\n\t\tdockable.setDragGroupMask(GROUP_TOOLS);\n\t\treturn dockable;\n\t}\n\n\t/**\n\t * Creates a {@link Dockable}.\n\t *\n\t * @param title\n\t * \t\tDockable title.\n\t * @param icon\n\t * \t\tDockable icon.\n\t * @param content\n\t * \t\tDockable content to display.\n\t *\n\t * @return Created dockable.\n\t */\n\t@Nonnull\n\tpublic Dockable newDockable(@Nonnull String title, @Nonnull Ikon icon, @Nonnull Node content) {\n\t\treturn newDockable(title, d -> new FontIconView(icon), content);\n\t}\n\n\t/**\n\t * Creates a {@link Dockable}.\n\t *\n\t * @param title\n\t * \t\tDockable title.\n\t * @param iconFactory\n\t * \t\tDockable icon factory.\n\t * @param content\n\t * \t\tDockable content to display.\n\t *\n\t * @return Created dockable.\n\t */\n\t@Nonnull\n\tpublic Dockable newDockable(@Nonnull String title, @Nonnull DockableIconFactory iconFactory, @Nonnull Node content) {\n\t\tDockable dockable = bento.dockBuilding().dockable();\n\t\tdockable.setTitle(title);\n\t\tdockable.setNode(content);\n\t\tdockable.setIconFactory(iconFactory);\n\t\treturn dockable;\n\t}\n\n\t/**\n\t * Creates a {@link Dockable}.\n\t *\n\t * @param translationKey\n\t * \t\tDockable title translation key.\n\t * @param icon\n\t * \t\tDockable icon.\n\t * @param content\n\t * \t\tDockable content to display.\n\t *\n\t * @return Created dockable.\n\t */\n\t@Nonnull\n\tpublic Dockable newTranslatableDockable(@Nonnull String translationKey, @Nonnull Ikon icon, @Nonnull Node content) {\n\t\treturn newTranslatableDockable(translationKey, d -> new FontIconView(icon), content);\n\t}\n\n\t/**\n\t * Creates a {@link Dockable}.\n\t *\n\t * @param translationKey\n\t * \t\tDockable title translation key.\n\t * @param iconFactory\n\t * \t\tDockable icon factory.\n\t * @param content\n\t * \t\tDockable content to display.\n\t *\n\t * @return Created dockable.\n\t */\n\t@Nonnull\n\tpublic Dockable newTranslatableDockable(@Nonnull String translationKey, @Nonnull DockableIconFactory iconFactory, @Nonnull Node content) {\n\t\treturn newTranslatableDockable(Lang.getBinding(translationKey), iconFactory, content);\n\t}\n\n\t/**\n\t * Creates a {@link Dockable}.\n\t *\n\t * @param titleBinding\n\t * \t\tDockable title translation binding.\n\t * @param iconFactory\n\t * \t\tDockable icon factory.\n\t * @param content\n\t * \t\tDockable content to display.\n\t *\n\t * @return Created dockable.\n\t */\n\t@Nonnull\n\tpublic Dockable newTranslatableDockable(@Nonnull ObservableValue<String> titleBinding, @Nonnull DockableIconFactory iconFactory, @Nonnull Node content) {\n\t\tDockable dockable = bento.dockBuilding().dockable();\n\t\tdockable.titleProperty().bind(titleBinding);\n\t\tdockable.setNode(content);\n\t\tdockable.setIconFactory(iconFactory);\n\t\treturn dockable;\n\t}\n\n\t/**\n\t * @return Newly created leaf container.\n\t */\n\t@Nonnull\n\tpublic DockContainerLeaf newLeafContainer() {\n\t\treturn newLeafContainer(UUID.randomUUID().toString());\n\t}\n\n\t/**\n\t * @param id\n\t * \t\tID of the container to create.\n\t *\n\t * @return Newly created leaf container.\n\t */\n\t@Nonnull\n\tpublic DockContainerLeaf newLeafContainer(@Nonnull String id) {\n\t\treturn bento.dockBuilding().leaf(id);\n\t}\n\n\t@Nonnull\n\tprivate DockContainerLeaf newWelcomeContainer() {\n\t\tDockable welcome = newTranslatableDockable(\"welcome.title\", CarbonIcons.EARTH_FILLED, welcomePaneProvider.get());\n\t\twelcome.setClosable(false);\n\n\t\tDockContainerLeaf leaf = newLeafContainer(ID_CONTAINER_ROOT_TOP);\n\t\tleaf.setCanSplit(false);\n\t\tleaf.setPruneWhenEmpty(false);\n\t\tleaf.addDockable(welcome);\n\t\treturn leaf;\n\t}\n\n\t@Nonnull\n\tprivate DockContainerBranch newWorkspaceContainer() {\n\t\t// Container to hold:\n\t\t//  - Workspace explorer\n\t\tDockContainerLeaf explorer = newLeafContainer(ID_CONTAINER_WORKSPACE_TOOLS);\n\t\texplorer.setCanSplit(false);\n\t\texplorer.addDockables(newToolDockable(\"workspace.title\", CarbonIcons.TREE_VIEW, workspaceExplorerProvider.get()));\n\t\tSplitPane.setResizableWithParent(explorer.asRegion(), false);\n\n\t\t// Container to hold:\n\t\t//  - Tabs for displaying open classes/files in the workspace\n\t\tDockContainerLeaf primary = newLeafContainer(ID_CONTAINER_WORKSPACE_PRIMARY);\n\t\tprimary.setPruneWhenEmpty(false);\n\n\t\t// Combining the two into a branch\n\t\tDockContainerBranch branch = bento.dockBuilding().branch(ID_CONTAINER_ROOT_TOP);\n\t\tbranch.addContainers(explorer, primary);\n\t\tbranch.setContainerSizePercent(explorer, TOP_WORKSPACE_EXPLORER_SIZE);\n\n\t\t// We don't prune when empty because it breaks the 'replace top' container logic we have when\n\t\t// new workspaces get opened or existing ones get closed. As long as the top exists we can always\n\t\t// show new content (welcome display, or new workspace display) when needed.\n\t\tbranch.setPruneWhenEmpty(false);\n\n\t\treturn branch;\n\t}\n\n\t@Nonnull\n\tprivate DockContainerLeaf newBottomContainer() {\n\t\tDockContainerLeaf leaf = newLeafContainer(ID_CONTAINER_ROOT_BOTTOM);\n\t\tleaf.setCanSplit(false);\n\t\tleaf.setSide(Side.BOTTOM);\n\t\tleaf.addDockables(newToolDockable(\"logging.title\", CarbonIcons.TERMINAL, loggingPaneProvider.get()));\n\t\tSplitPane.setResizableWithParent(leaf, false);\n\t\treturn leaf;\n\t}\n\n\t@Nonnull\n\tprivate ContextMenu buildMenu(@Nonnull DockContainerLeaf container) {\n\t\tContextMenu menu = new ContextMenu();\n\t\tif (!container.isCollapsed())\n\t\t\t// TODO: Swapping sides while collapsed is buggy\n\t\t\taddSideOptions(menu, container);\n\t\treturn menu;\n\t}\n\n\tprivate static void addSideOptions(@Nonnull ContextMenu menu, @Nonnull DockContainerLeaf container) {\n\t\tfor (Side side : Side.values()) {\n\t\t\tFontIconView sideIcon = switch (side) {\n\t\t\t\tcase TOP -> new FontIconView(CarbonIcons.OPEN_PANEL_FILLED_TOP);\n\t\t\t\tcase BOTTOM -> new FontIconView(CarbonIcons.OPEN_PANEL_FILLED_BOTTOM);\n\t\t\t\tcase LEFT -> new FontIconView(CarbonIcons.OPEN_PANEL_FILLED_LEFT);\n\t\t\t\tcase RIGHT -> new FontIconView(CarbonIcons.OPEN_PANEL_FILLED_RIGHT);\n\t\t\t};\n\t\t\tLabel graphic = new Label(side == container.sideProperty().get() ? \"✓\" : \" \", sideIcon);\n\t\t\tMenuItem item = new ActionMenuItem(Lang.getBinding(\"misc.direction.\" + side.name().toLowerCase()), graphic,\n\t\t\t\t\t() -> container.sideProperty().set(side));\n\t\t\tmenu.getItems().add(item);\n\t\t}\n\t}\n\n\t/**\n\t * Listener to show the workspace summary page for opened workspaces.\n\t */\n\tprivate class SummaryDisplayListener implements WorkspaceOpenListener {\n\t\t@Override\n\t\tpublic void onWorkspaceOpened(@Nonnull Workspace workspace) {\n\t\t\t// Replace root with a summary of the workspace when it is opened.\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tif (bento.search().replaceContainer(ID_CONTAINER_ROOT_TOP, DockingManager.this::newWorkspaceContainer)) {\n\t\t\t\t\tif (resourceSummaryConfig.getSummarizeOnOpen().getValue()) actions.openSummary();\n\t\t\t\t} else {\n\t\t\t\t\tlogger.error(\"Failed replacing root on workspace open\");\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\t@Override\n\t\tpublic int getPriority() {\n\t\t\t// This is set to run 'EARLIEST' so that other open listeners that use FX callbacks\n\t\t\t// will register their callbacks after this one. This ensures the 'ID_CONTAINER_WORKSPACE_PRIMARY' is\n\t\t\t// available when those other callbacks are executed.\n\t\t\treturn PriorityKeys.EARLIEST;\n\t\t}\n\t}\n\n\t/**\n\t * Listener to show the welcome page when no workspace is open.\n\t */\n\tprivate class WelcomeDisplayListener implements WorkspaceCloseListener {\n\t\t@Override\n\t\tpublic void onWorkspaceClosed(@Nonnull Workspace workspace) {\n\t\t\t// When a workspace is closed, show the welcome screen.\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tif (!bento.search().replaceContainer(ID_CONTAINER_ROOT_TOP, DockingManager.this::newWelcomeContainer))\n\t\t\t\t\tlogger.error(\"Failed replacing root on workspace close\");\n\t\t\t});\n\t\t}\n\n\t\t@Override\n\t\tpublic int getPriority() {\n\t\t\t// This is set to run 'LATEST' so that the replace operation happens AFTER the navigation manager\n\t\t\t// handles closing UI content from the workspace being closed. If this happens too early then that logic\n\t\t\t// in the navigation manager will close our 'welcome' page/container.\n\t\t\treturn PriorityKeys.LATEST;\n\t\t}\n\t}\n\n\t/**\n\t * Headers impl that configures render clipping.\n\t */\n\tprivate static class ClippedHeaders extends Headers {\n\t\t/**\n\t\t * @param container\n\t\t * \t\tParent container.\n\t\t * @param orientation\n\t\t * \t\tWhich axis to layout children on.\n\t\t * @param side\n\t\t * \t\tSide in the parent container where tabs are displayed.\n\t\t */\n\t\tprivate ClippedHeaders(@Nonnull DockContainerLeaf container, @Nonnull Orientation orientation, @Nonnull Side side) {\n\t\t\tsuper(container, orientation, side);\n\t\t\tsetupClip();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/docking/EmbeddedBento.java",
    "content": "package software.coley.recaf.ui.docking;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.input.MouseButton;\nimport software.coley.bentofx.Bento;\nimport software.coley.bentofx.building.HeaderFactory;\nimport software.coley.bentofx.control.ContentWrapper;\nimport software.coley.bentofx.control.Header;\nimport software.coley.bentofx.control.HeaderPane;\nimport software.coley.bentofx.control.Headers;\nimport software.coley.bentofx.dockable.Dockable;\nimport software.coley.bentofx.layout.container.DockContainerLeaf;\nimport software.coley.bentofx.layout.container.DockContainerRootBranch;\n\nimport java.util.List;\n\n/**\n * An extension of {@link Bento} used for embedded circumstances.\n * We only want bento for docking layout + collapsible panels.\n * These should not handle any sort of drag-drop behaviors, allowing\n * any parent/containing bento content to behave properly.\n *\n * @author Matt Coley\n */\npublic class EmbeddedBento extends Bento {\n\tprivate static final String EMBEDDED_CLASS = \"embedded-bento\";\n\n\t/**\n\t * New embedded bento.\n\t */\n\tpublic EmbeddedBento() {\n\t\t// Construct without configuring drag-drop support\n\t\tcontrolsBuilding().setContentWrapperFactory(container -> new ContentWrapper(container) {\n\t\t\t@Override\n\t\t\tprotected void setupDragDrop(@Nonnull DockContainerLeaf container) {\n\t\t\t\t// no-op\n\t\t\t}\n\t\t});\n\t\tcontrolsBuilding().setHeadersFactory((container, orientation, side) -> new Headers(container, orientation, side) {\n\t\t\t@Override\n\t\t\tprotected void setupDragDrop(@Nonnull DockContainerLeaf container) {\n\t\t\t\t// no-op\n\t\t\t}\n\t\t});\n\n\t\t// Construct without initializing drag-drop event handling\n\t\tcontrolsBuilding().setHeaderFactory(Header::new);\n\t}\n\n\t@Override\n\tpublic boolean registerRoot(@Nonnull DockContainerRootBranch container) {\n\t\tList<String> styleClasses = container.getStyleClass();\n\t\tif (!styleClasses.contains(EMBEDDED_CLASS))\n\t\t\tstyleClasses.add(EMBEDDED_CLASS);\n\t\treturn super.registerRoot(container);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/media/CombinedPlayer.java",
    "content": "package software.coley.recaf.ui.media;\n\nimport jakarta.annotation.Nonnull;\n\nimport java.io.IOException;\nimport java.util.List;\n\n/**\n * Media-player that wraps multiple other implementations for wider support.\n *\n * @author Matt Coley\n */\npublic class CombinedPlayer extends Player {\n\tprivate final List<Player> delegates;\n\tprivate Player currentPlayer;\n\n\t/**\n\t * @param delegates\n\t * \t\tBacking players.\n\t */\n\tpublic CombinedPlayer(@Nonnull List<Player> delegates) {\n\t\tthis.delegates = delegates;\n\t}\n\n\t@Override\n\tpublic void play() {\n\t\tif (currentPlayer != null)\n\t\t\tcurrentPlayer.play();\n\t}\n\n\t@Override\n\tpublic void pause() {\n\t\tif (currentPlayer != null)\n\t\t\tcurrentPlayer.pause();\n\t}\n\n\t@Override\n\tpublic void seek(double millis) {\n\t\tif (currentPlayer != null)\n\t\t\tcurrentPlayer.seek(millis);\n\t}\n\n\t@Override\n\tpublic void stop() {\n\t\tif (currentPlayer != null)\n\t\t\tcurrentPlayer.stop();\n\t}\n\n\t@Override\n\tpublic void reset() {\n\t\tif (currentPlayer != null)\n\t\t\tcurrentPlayer.reset();\n\t}\n\n\t@Override\n\tpublic void dispose() {\n\t\tif (currentPlayer != null)\n\t\t\tcurrentPlayer.dispose();\n\t}\n\n\t@Override\n\tpublic void addPlaybackListener(Runnable r) {\n\t\tif (currentPlayer != null)\n\t\t\tcurrentPlayer.addPlaybackListener(r);\n\t}\n\n\t@Override\n\tpublic double getMaxSeconds() {\n\t\tif (currentPlayer != null)\n\t\t\treturn currentPlayer.getMaxSeconds();\n\t\treturn super.getMaxSeconds();\n\t}\n\n\t@Override\n\tpublic double getCurrentSeconds() {\n\t\tif (currentPlayer != null)\n\t\t\treturn currentPlayer.getCurrentSeconds();\n\t\treturn super.getCurrentSeconds();\n\t}\n\n\t@Override\n\tpublic void setSpectrumListener(SpectrumListener listener) {\n\t\tsuper.setSpectrumListener(listener);\n\t\t// Also set listener for delegated players\n\t\tdelegates.forEach(delegate -> delegate.setSpectrumListener(listener));\n\t}\n\n\t@Override\n\tpublic void load(String path) throws IOException {\n\t\t// Reset prior content\n\t\tstop();\n\t\t// Attempt to load content from delegates\n\t\tIOException lastError = null;\n\t\tfor (Player player : delegates) {\n\t\t\ttry {\n\t\t\t\tplayer.load(path);\n\t\t\t\t// Success, use ths player\n\t\t\t\tcurrentPlayer = player;\n\t\t\t\treturn;\n\t\t\t} catch (IOException ex) {\n\t\t\t\tlastError = ex;\n\t\t\t\t// ignore to allow other delegated players to attempt loading\n\t\t\t}\n\t\t}\n\t\tthrow new IOException(\"Failed to load audio file from path: \" + path, lastError);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/media/FxPlayer.java",
    "content": "package software.coley.recaf.ui.media;\n\nimport com.sun.media.jfxmediaimpl.NativeMediaManager;\nimport javafx.scene.media.AudioSpectrumListener;\nimport javafx.scene.media.Media;\nimport javafx.scene.media.MediaPlayer;\nimport javafx.util.Duration;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.util.RecafURLStreamHandlerProvider;\nimport software.coley.recaf.util.ReflectUtil;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.IOException;\nimport java.lang.invoke.MethodHandle;\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * A media-player using JavaFX's {@link MediaPlayer} with the use of {@link RecafURLStreamHandlerProvider}\n * which allows pulling audio from 'memory' via the current {@link Workspace}.\n *\n * @author Matt Coley\n */\npublic class FxPlayer extends Player implements AudioSpectrumListener {\n\tprivate static final Logger logger = Logging.get(FxPlayer.class);\n\tprivate final List<Runnable> playbackListeners = new ArrayList<>(2);\n\tprivate SpectrumEvent eventInstance;\n\tprivate MediaPlayer player;\n\tMedia media;\n\n\t@Override\n\tpublic void play() {\n\t\tif (player != null) {\n\t\t\tplayer.play();\n\t\t\tplayer.setAudioSpectrumListener(this);\n\t\t\tplaybackListeners.forEach(Runnable::run);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void pause() {\n\t\t// TODO: Pausing and then unpausing sometimes causes the spectrum listener to feel 'laggy'\n\t\t//  - but the spectrum interval is still the same so its not like that is getting reset\n\t\t//  - happens more consistently with mp3 files, m4a is seemingly unaffected\n\t\t//  - and pausing/unpausing can sometimes also fix the 'laggy' feeling.\n\t\tif (player != null) {\n\t\t\tplayer.pause();\n\t\t\tplayer.setAudioSpectrumListener(null);\n\t\t\tplaybackListeners.forEach(Runnable::run);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void seek(double millis) {\n\t\tif (player != null) {\n\t\t\tplayer.seek(Duration.millis(millis));\n\t\t\tplaybackListeners.forEach(Runnable::run);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void stop() {\n\t\tif (player != null) {\n\t\t\t// Stop is supposed to call 'seek(0)' in implementation but for some reason it is not consistent.\n\t\t\t// Especially if the current state is 'paused'. If we request the seek ourselves it *seems* more reliable.\n\t\t\tplayer.seek(Duration.ZERO);\n\t\t\tplayer.stop();\n\t\t\tplayer.setAudioSpectrumListener(null);\n\t\t\tplaybackListeners.forEach(Runnable::run);\n\n\t\t\t// Reset spectrum data\n\t\t\tSpectrumListener listener = getSpectrumListener();\n\t\t\tif (listener != null && eventInstance != null) {\n\t\t\t\tArrays.fill(eventInstance.magnitudes(), -100);\n\t\t\t\tlistener.onSpectrum(eventInstance);\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void reset() {\n\t\tstop();\n\t\tmedia = null;\n\t\tplayer = null;\n\t}\n\n\t@Override\n\tpublic void dispose() {\n\t\tplaybackListeners.clear();\n\n\t\tif (player != null) {\n\t\t\tplayer.dispose();\n\t\t\tplayer = null;\n\t\t}\n\n\t\tmedia = null;\n\t}\n\n\t@Override\n\tpublic void addPlaybackListener(Runnable r) {\n\t\tplaybackListeners.add(r);\n\t}\n\n\t@Override\n\tpublic void load(String path) throws IOException {\n\t\ttry {\n\t\t\tmedia = MediaHacker.create(path);\n\t\t\tplayer = new MediaPlayer(media);\n\t\t\tplayer.setOnError(() -> logger.warn(\"FX media player error\"));\n\t\t\tplayer.setOnStalled(() -> logger.warn(\"FX media player stalled\"));\n\t\t\tplayer.audioSpectrumIntervalProperty().set(0.04);\n\t\t\tplayer.setAudioSpectrumListener(this);\n\t\t} catch (Exception ex) {\n\t\t\treset();\n\t\t\tthrow new IOException(\"Failed to load content from: \" + path, ex);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void spectrumDataUpdate(double timestamp, double duration, float[] magnitudes, float[] phases) {\n\t\tSpectrumListener listener = getSpectrumListener();\n\t\tif (listener != null) {\n\t\t\tif (eventInstance == null || eventInstance.magnitudes().length != magnitudes.length)\n\t\t\t\teventInstance = new SpectrumEvent(magnitudes);\n\t\t\telse\n\t\t\t\tSystem.arraycopy(magnitudes, 0, eventInstance.magnitudes(), 0, magnitudes.length);\n\t\t\tlistener.onSpectrum(eventInstance);\n\t\t}\n\t\tplaybackListeners.forEach(Runnable::run);\n\t}\n\n\t@Override\n\tpublic double getMaxSeconds() {\n\t\tif (player != null)\n\t\t\treturn player.getTotalDuration().toSeconds();\n\t\treturn -1;\n\t}\n\n\t@Override\n\tpublic double getCurrentSeconds() {\n\t\tif (player != null)\n\t\t\treturn player.getCurrentTime().toSeconds();\n\t\treturn -1;\n\t}\n\n\t/**\n\t * @return Current player for {@link #getMedia() media}.\n\t */\n\tpublic MediaPlayer getPlayer() {\n\t\treturn player;\n\t}\n\n\t/**\n\t * @return Current loaded media.\n\t */\n\tpublic Media getMedia() {\n\t\treturn media;\n\t}\n\n\t/**\n\t * @return {@code true} when there is content loaded in the player.\n\t */\n\tpublic boolean hasContent() {\n\t\treturn player != null;\n\t}\n\n\tstatic {\n\t\tif (!RecafURLStreamHandlerProvider.installed)\n\t\t\tthrow new IllegalStateException(\"Recaf URL stream handler not installed!\");\n\t\ttry {\n\t\t\t// Inject protocol name in manager\n\t\t\tNativeMediaManager manager = NativeMediaManager.getDefaultInstance();\n\t\t\tField fProtocols = ReflectUtil.getDeclaredField(NativeMediaManager.class, \"supportedProtocols\");\n\t\t\tfProtocols.setAccessible(true);\n\t\t\tList<String> protocols = ReflectUtil.quietGet(manager, fProtocols);\n\t\t\tprotocols.add(RecafURLStreamHandlerProvider.recafFile);\n\n\t\t\t// Inject protocol name into platform impl\n\t\t\tClass<?> platformImpl = Class.forName(\"com.sun.media.jfxmediaimpl.platform.gstreamer.GSTPlatform\");\n\t\t\tfProtocols = platformImpl.getDeclaredField(\"PROTOCOLS\");\n\t\t\tfProtocols.setAccessible(true);\n\t\t\tString[] protocolArray = (String[]) fProtocols.get(null);\n\t\t\tString[] protocolArrayPlus = new String[protocolArray.length + 1];\n\t\t\tSystem.arraycopy(protocolArray, 0, protocolArrayPlus, 0, protocolArray.length);\n\t\t\tprotocolArrayPlus[protocolArray.length] = RecafURLStreamHandlerProvider.recafFile;\n\n\t\t\t// Required for newer versions of Java.\n\t\t\t// Ignore the compiler warning about 'invokeExact being confused' - its correct as-is.\n\t\t\tMethodHandle setter = ReflectUtil.lookup()\n\t\t\t\t\t.findStaticSetter(platformImpl, fProtocols.getName(), fProtocols.getType());\n\t\t\tsetter.invokeExact(protocolArrayPlus);\n\t\t} catch (Throwable t) {\n\t\t\tthrow new IllegalStateException(\"Could not hijack platforms to support recaf URI protocol\", t);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/media/MediaHacker.java",
    "content": "package software.coley.recaf.ui.media;\n\nimport com.sun.media.jfxmedia.locator.Locator;\nimport com.sun.media.jfxmediaimpl.MediaUtils;\nimport jakarta.annotation.Nonnull;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableMap;\nimport javafx.scene.media.Media;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.util.RecafURLStreamHandlerProvider;\nimport software.coley.recaf.util.ReflectUtil;\nimport software.coley.recaf.util.UnsafeUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport sun.misc.Unsafe;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.HashMap;\nimport java.util.concurrent.CountDownLatch;\n\n/**\n * Please look away.\n *\n * @author Matt Coley\n */\npublic class MediaHacker {\n\t/**\n\t * Jesus christ, this is so fucking bad... I just want in-memory playback, oh my god.\n\t *\n\t * @param path\n\t * \t\tURL to load, pointing to a {@link FileInfo} in a {@link Workspace}.\n\t *\n\t * @return Media instance with content loaded in memory.\n\t */\n\t@Nonnull\n\tpublic static Media create(@Nonnull String path) throws IOException {\n\t\t// TODO: Maybe hack into JarFileFactory#fileCache and go through the regular URL constructor for Media\n\t\t//  - set the url to be cached to facilitate looking up in that cache\n\t\t//  - clear the file cache when workspace is closed\n\t\tString uriPath = RecafURLStreamHandlerProvider.fileUri(path);\n\t\ttry {\n\t\t\t// We need to bypass the constructor due to how URI is handled normally preventing normal usage of our\n\t\t\t// custom URI scheme. This has caused me much pain.\n\t\t\tUnsafe unsafe = UnsafeUtil.get();\n\t\t\tMedia media = (Media) unsafe.allocateInstance(Media.class);\n\n\t\t\t//  private MetadataListener metadataListener = new _MetadataListener()\n\t\t\tClass<?> type = Class.forName(Media.class.getName() + \"$_MetadataListener\");\n\t\t\tConstructor<?> ctor = type.getDeclaredConstructor(Media.class);\n\t\t\tctor.setAccessible(true);\n\t\t\tObject value = ctor.newInstance(media);\n\t\t\tField metadataListener = ReflectUtil.getDeclaredField(Media.class, \"metadataListener\");\n\t\t\tmetadataListener.setAccessible(true);\n\t\t\tReflectUtil.quietSet(media, metadataListener, value);\n\n\t\t\t//  private final ObservableMap<String,Object> metadataBacking = FXCollections.observableMap(new HashMap<String,Object>());\n\t\t\tField metadataBacking = ReflectUtil.getDeclaredField(Media.class, \"metadataBacking\");\n\t\t\tmetadataBacking.setAccessible(true);\n\t\t\tObservableMap<Object, Object> vMetadataBacking = FXCollections.observableMap(new HashMap<>());\n\t\t\tReflectUtil.quietSet(media, metadataBacking, vMetadataBacking);\n\n\t\t\t//  private final ObservableList<Track> tracksBacking = FXCollections.observableArrayList();\n\t\t\tField tracksBacking = ReflectUtil.getDeclaredField(Media.class, \"tracksBacking\");\n\t\t\ttracksBacking.setAccessible(true);\n\t\t\tObject vTracksBacking = FXCollections.observableArrayList();\n\t\t\tReflectUtil.quietSet(media, tracksBacking, vTracksBacking);\n\n\t\t\t//  private ObservableMap<String, Duration> markers = FXCollections.observableMap(new HashMap<String,Duration>());\n\t\t\tField markers = ReflectUtil.getDeclaredField(Media.class, \"markers\");\n\t\t\tmarkers.setAccessible(true);\n\t\t\tReflectUtil.quietSet(media, markers, FXCollections.observableMap(new HashMap<>()));\n\n\t\t\t// this.source = uriPath;\n\t\t\tField source = ReflectUtil.getDeclaredField(Media.class, \"source\");\n\t\t\tsource.setAccessible(true);\n\t\t\tReflectUtil.quietSet(media, source, uriPath);\n\n\t\t\t//  metadata = FXCollections.unmodifiableObservableMap(metadataBacking);\n\t\t\tField metadata = ReflectUtil.getDeclaredField(Media.class, \"metadata\");\n\t\t\tmetadata.setAccessible(true);\n\t\t\tReflectUtil.quietSet(media, metadata, vMetadataBacking);\n\n\t\t\t//  tracks = FXCollections.unmodifiableObservableList(tracksBacking);\n\t\t\tField tracks = ReflectUtil.getDeclaredField(Media.class, \"tracks\");\n\t\t\ttracks.setAccessible(true);\n\t\t\tReflectUtil.quietSet(media, tracks, vTracksBacking);\n\n\t\t\tField jfxLocator = ReflectUtil.getDeclaredField(Media.class, \"jfxLocator\");\n\t\t\tLocator locator = new LocatorImpl(new URI(uriPath));\n\t\t\tReflectUtil.quietSet(media, jfxLocator, locator);\n\n\t\t\ttry {\n\t\t\t\tif (locator.canBlock()) {\n\t\t\t\t\t// private class InitLocator implements Runnable\n\t\t\t\t\ttype = Class.forName(Media.class.getName() + \"$InitLocator\");\n\t\t\t\t\tctor = type.getDeclaredConstructor(Media.class);\n\t\t\t\t\tctor.setAccessible(true);\n\t\t\t\t\tvalue = ctor.newInstance(media);\n\n\t\t\t\t\tThread t = new Thread((Runnable) value);\n\t\t\t\t\tt.setDaemon(true);\n\t\t\t\t\tt.start();\n\t\t\t\t} else {\n\t\t\t\t\tlocator.init();\n\t\t\t\t\tReflectUtil.getDeclaredMethod(Media.class, \"runMetadataParser\").invoke(media);\n\t\t\t\t}\n\t\t\t} catch (URISyntaxException ex) {\n\t\t\t\tthrow new IllegalArgumentException(ex);\n\t\t\t} catch (FileNotFoundException ex) {\n\t\t\t\tthrow new IOException(\"Media unavailable\", ex);\n\t\t\t} catch (IOException ex) {\n\t\t\t\tthrow new IOException(\"Media inaccessible\", ex);\n\t\t\t} catch (com.sun.media.jfxmedia.MediaException ex) {\n\t\t\t\tthrow new IOException(\"Media unsupported\", ex);\n\t\t\t}\n\t\t\treturn media;\n\t\t} catch (Throwable t) {\n\t\t\tthrow new IllegalStateException(t);\n\t\t}\n\t}\n\n\t/**\n\t * A custom locator with a modified {@link Locator#init()} that allows\n\t * us to use {@link RecafURLStreamHandlerProvider}. Without this override we are limited to a few basic\n\t * URI schemes such as {@code file, jar, jrt, http, https}. But since we want to keep things in memory none\n\t * of these are satisfactory.\n\t *\n\t * @author Matt Coley\n\t */\n\tprivate static class LocatorImpl extends Locator {\n\t\tpublic LocatorImpl(URI uri) throws URISyntaxException {\n\t\t\tsuper(uri);\n\t\t\tinit();\n\t\t}\n\n\t\t@Override\n\t\tpublic void init() {\n\t\t\ttry {\n\t\t\t\t// Need to decrement latch since we override the base init call\n\t\t\t\tField fLatch = ReflectUtil.getDeclaredField(Locator.class, \"readySignal\");\n\t\t\t\tfLatch.setAccessible(true);\n\t\t\t\tCountDownLatch latch = ReflectUtil.quietGet(this, fLatch);\n\t\t\t\tlatch.countDown();\n\n\t\t\t\t// Update the content type\n\t\t\t\tbyte[] section = new byte[MediaUtils.MAX_FILE_SIGNATURE_LENGTH];\n\t\t\t\tInputStream stream = uri.toURL().openStream();\n\t\t\t\tif (stream.read(section) > 0)\n\t\t\t\t\tcontentType = MediaUtils.fileSignatureToContentType(section, MediaUtils.MAX_FILE_SIGNATURE_LENGTH);\n\n\t\t\t\t// Cache so that the 'ConnectionHolder' uses the memory implementation, which supports seeking.\n\t\t\t\t// Without seek support 'stop' does not work.\n\t\t\t\tcacheMedia();\n\n\t\t\t\t// Odd note on m4a support, they rarely work if you request the player to start immediately.\n\t\t\t\t// But if you wait and then request playback it works most of the time.\n\t\t\t} catch (Exception ex) {\n\t\t\t\tthrow new IllegalStateException(ex);\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/media/Player.java",
    "content": "package software.coley.recaf.ui.media;\n\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.IOException;\n\n/**\n * A common base for media players sourced from different back-ends.\n *\n * @author Matt Coley\n * @see FxPlayer\n * @see CombinedPlayer\n */\npublic abstract class Player {\n\tprivate SpectrumListener spectrumListener;\n\n\t/**\n\t * Play the track.\n\t */\n\tpublic abstract void play();\n\n\t/**\n\t * Pause the track.\n\t */\n\tpublic abstract void pause();\n\n\t/**\n\t * Seek to the given time (in millis).\n\t *\n\t * @param millis\n\t * \t\tTime to seek to.\n\t */\n\tpublic abstract void seek(double millis);\n\n\t/**\n\t * Stop the track.\n\t */\n\tpublic abstract void stop();\n\n\t/**\n\t * Stop the track and clear references.\n\t */\n\tpublic abstract void reset();\n\n\t/**\n\t * Clean up resources.\n\t */\n\tpublic abstract void dispose();\n\n\t/**\n\t * Adds a playback listener.\n\t *\n\t * @param r\n\t * \t\tRunnable to call when the playback state changes.\n\t */\n\tpublic abstract void addPlaybackListener(Runnable r);\n\n\t/**\n\t * @return Current spectrum listener.\n\t */\n\tpublic SpectrumListener getSpectrumListener() {\n\t\treturn spectrumListener;\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tNew spectrum listener.\n\t */\n\tpublic void setSpectrumListener(SpectrumListener listener) {\n\t\tthis.spectrumListener = listener;\n\t}\n\n\t/**\n\t * Initialize a new track.\n\t *\n\t * @param path\n\t * \t\tFile path name in the current {@link Workspace}.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the file cannot be loaded for playback.\n\t */\n\tpublic abstract void load(String path) throws IOException;\n\n\t/**\n\t * @return Length of the currently loaded media in seconds,\n\t * or a negative value if the duration could not be determined.\n\t */\n\tpublic double getMaxSeconds() {\n\t\treturn -1;\n\t}\n\n\t/**\n\t * @return Current offset in the currently loaded media in seconds,\n\t * or a negative value if the current time could not be determined.\n\t */\n\tpublic double getCurrentSeconds() {\n\t\treturn -1;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/media/SpectrumEvent.java",
    "content": "package software.coley.recaf.ui.media;\n\n/**\n * Spectrum data.\n *\n * @param magnitudes\n * \t\tMagnitudes of the spectrum\n *\n * @author Matt Coley\n */\npublic record SpectrumEvent(float[] magnitudes) {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/media/SpectrumListener.java",
    "content": "package software.coley.recaf.ui.media;\n\n/**\n * Simple spectrum data listener.\n *\n * @author Matt Coley\n */\npublic interface SpectrumListener {\n\t/**\n\t * @param event Spectrum data.\n\t */\n\tvoid onSpectrum(SpectrumEvent event);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/menubar/AnalysisMenu.java",
    "content": "package software.coley.recaf.ui.menubar;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.MenuItem;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.ActionMenuItem;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.docking.DockingManager;\nimport software.coley.recaf.ui.window.DeobfuscationWindow;\n\nimport java.util.UUID;\n\nimport static software.coley.recaf.util.Lang.getBinding;\nimport static software.coley.recaf.util.Menus.action;\n\n/**\n * Analysis menu component for {@link MainMenu}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class AnalysisMenu extends WorkspaceAwareMenu {\n\tprivate final WindowManager windowManager;\n\tprivate final Instance<DeobfuscationWindow> deobfuscationWindowProvider;\n\n\t@Inject\n\tpublic AnalysisMenu(@Nonnull WorkspaceManager workspaceManager,\n\t                    @Nonnull DockingManager dockingManager,\n\t                    @Nonnull WindowManager windowManager,\n\t                    @Nonnull Instance<DeobfuscationWindow> deobfuscationWindowProvider,\n\t                    @Nonnull Actions actions) {\n\t\tsuper(workspaceManager);\n\n\t\tthis.windowManager = windowManager;\n\t\tthis.deobfuscationWindowProvider = deobfuscationWindowProvider;\n\n\t\tdisableProperty().bind(hasWorkspace.not());\n\t\ttextProperty().bind(getBinding(\"menu.analysis\"));\n\t\tsetGraphic(new FontIconView(CarbonIcons.CHART_CUSTOM));\n\n\t\tMenuItem itemViewSummary = action(\"menu.analysis.summary\", CarbonIcons.INFORMATION, actions::openSummary);\n\t\titemViewSummary.disableProperty().bind(hasWorkspace.not());\n\t\tgetItems().add(itemViewSummary);\n\n\t\tActionMenuItem itemDeobfuscation = action(\"menu.analysis.deobfuscation\", CarbonIcons.DEVELOPMENT, this::openDeobfuscation);\n\t\titemDeobfuscation.disableProperty().bind(hasWorkspace.or(hasAgentWorkspace).not());\n\t\tgetItems().add(itemDeobfuscation);\n\n\t\tActionMenuItem itemListComments = action(\"menu.analysis.list-comments\", CarbonIcons.CHAT, actions::openCommentList);\n\t\titemListComments.disableProperty().bind(hasWorkspace.or(hasAgentWorkspace).not());\n\t\tgetItems().add(itemListComments);\n\t}\n\n\t/**\n\t * Display the deobfuscation window.\n\t */\n\tprivate void openDeobfuscation() {\n\t\tDeobfuscationWindow deobfuscationWindow = deobfuscationWindowProvider.get();\n\t\tdeobfuscationWindow.show();\n\t\tdeobfuscationWindow.requestFocus();\n\t\tdeobfuscationWindow.setOnCloseRequest(e -> deobfuscationWindowProvider.destroy(deobfuscationWindow));\n\t\twindowManager.register(\"deobfuscation-\" + UUID.randomUUID(), deobfuscationWindow);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/menubar/ConfigMenu.java",
    "content": "package software.coley.recaf.ui.menubar;\n\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.Menu;\nimport javafx.stage.Stage;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.kordamp.ikonli.javafx.FontIcon;\nimport software.coley.recaf.services.config.ConfigManager;\nimport software.coley.recaf.services.window.WindowManager;\n\nimport static software.coley.recaf.util.Lang.getBinding;\nimport static software.coley.recaf.util.Menus.action;\n\n/**\n * Config menu component for {@link MainMenu}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class ConfigMenu extends Menu {\n\tprivate final WindowManager windowManager;\n\tprivate final ConfigManager configManager;\n\n\t@Inject\n\tpublic ConfigMenu(WindowManager windowManager,\n\t\t\t\t\t  ConfigManager configManager) {\n\t\tthis.windowManager = windowManager;\n\t\tthis.configManager = configManager;\n\n\t\ttextProperty().bind(getBinding(\"menu.config\"));\n\t\tsetGraphic(new FontIcon(CarbonIcons.SETTINGS));\n\n\t\tgetItems().add(action(\"menu.config.edit\", CarbonIcons.CALIBRATE, this::openEditor));\n\t\tgetItems().add(action(\"menu.config.export\", CarbonIcons.DOCUMENT_EXPORT, this::exportProfile));\n\t\tgetItems().add(action(\"menu.config.import\", CarbonIcons.DOCUMENT_IMPORT, this::importProfile));\n\t}\n\n\t/**\n\t * Display the config window.\n\t */\n\tprivate void openEditor() {\n\t\tStage configWindow = windowManager.getConfigWindow();\n\t\tconfigWindow.show();\n\t\tconfigWindow.requestFocus();\n\t}\n\n\t/**\n\t * Exports the current config to a file.\n\t */\n\tprivate void exportProfile() {\n\t\t// TODO: implement\n\t}\n\n\t/**\n\t * Applies values in the profile file provided by the user to the current config.\n\t */\n\tprivate void importProfile() {\n\t\t// TODO: implement\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/menubar/FileMenu.java",
    "content": "package software.coley.recaf.ui.menubar;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.SimpleListProperty;\nimport javafx.collections.ObservableList;\nimport javafx.scene.Node;\nimport javafx.scene.control.Menu;\nimport javafx.scene.control.MenuItem;\nimport javafx.stage.Stage;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.config.RecentFilesConfig;\nimport software.coley.recaf.ui.control.ClosableActionMenuItem;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.popup.OpenUrlPopup;\nimport software.coley.recaf.ui.docking.DockingManager;\nimport software.coley.recaf.ui.pane.WorkspaceBuilderPane;\nimport software.coley.recaf.ui.window.RecafScene;\nimport software.coley.recaf.ui.window.RecafStage;\nimport software.coley.recaf.util.ErrorDialogs;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.IOUtil;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.PathExportingManager;\nimport software.coley.recaf.workspace.PathLoadingManager;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.awt.Toolkit;\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static software.coley.recaf.util.Lang.getBinding;\nimport static software.coley.recaf.util.Menus.*;\n\n/**\n * File menu component for {@link MainMenu}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class FileMenu extends WorkspaceAwareMenu {\n\tprivate static final Logger logger = Logging.get(FileMenu.class);\n\tprivate final Menu menuRecent = menu(\"menu.file.recent\", CarbonIcons.TIME);\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final PathLoadingManager pathLoadingManager;\n\tprivate final PathExportingManager pathExportingManager;\n\tprivate final Instance<OpenUrlPopup> openUrlProvider;\n\tprivate final WindowManager windowManager;\n\t// config\n\tprivate final RecentFilesConfig recentFilesConfig;\n\n\t@Inject\n\tpublic FileMenu(@Nonnull WorkspaceManager workspaceManager,\n\t                @Nonnull PathLoadingManager pathLoadingManager,\n\t                @Nonnull PathExportingManager pathExportingManager,\n\t                @Nonnull Instance<OpenUrlPopup> openUrlProvider,\n\t                @Nonnull DockingManager dockingManager,\n\t                @Nonnull WindowManager windowManager,\n\t                @Nonnull RecentFilesConfig recentFilesConfig) {\n\t\tsuper(workspaceManager);\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.pathLoadingManager = pathLoadingManager;\n\t\tthis.pathExportingManager = pathExportingManager;\n\t\tthis.openUrlProvider = openUrlProvider;\n\t\tthis.windowManager = windowManager;\n\t\tthis.recentFilesConfig = recentFilesConfig;\n\n\t\ttextProperty().bind(getBinding(\"menu.file\"));\n\t\tsetGraphic(new FontIconView(CarbonIcons.WORKSPACE));\n\n\t\tSimpleListProperty<MenuItem> recentItemsProperty = new SimpleListProperty<>(menuRecent.getItems());\n\t\tmenuRecent.disableProperty().bind(recentItemsProperty.emptyProperty());\n\n\t\tMenuItem itemAddToWorkspace = action(\"menu.file.addtoworkspace\", CarbonIcons.WORKSPACE_IMPORT, this::addToWorkspace);\n\t\tMenuItem itemExportPrimary = action(\"menu.file.exportapp\", CarbonIcons.EXPORT, this::exportCurrent);\n\t\tMenuItem itemViewChanges = action(\"menu.file.modifications\", CarbonIcons.COMPARE, this::openChangeViewer);\n\t\tMenuItem itemClose = action(\"menu.file.close\", CarbonIcons.TRASH_CAN, this::closeWorkspace);\n\t\titemAddToWorkspace.disableProperty().bind(hasWorkspace.not());\n\t\titemExportPrimary.disableProperty().bind(hasWorkspace.not().and(hasAgentWorkspace.not()));\n\t\titemViewChanges.disableProperty().set(true); // TODO: Not-implemented\n\t\t// itemViewChanges.disableProperty().bind(hasWorkspace.not());\n\t\titemClose.disableProperty().bind(hasWorkspace.not());\n\n\t\tMenuItem itemQuit = action(\"menu.file.quit\", CarbonIcons.CLOSE, this::quit);\n\t\tObservableList<MenuItem> items = getItems();\n\t\titems.add(action(\"menu.file.openworkspace\", CarbonIcons.FOLDER_ADD, this::openWorkspace));\n\t\titems.add(itemAddToWorkspace);\n\t\titems.add(menuRecent);\n\t\titems.add(action(\"menu.file.attach\", CarbonIcons.DEBUG, this::openAttach));\n\t\titems.add(action(\"menu.file.openurl\", CarbonIcons.ATTACHMENT, this::openUrl));\n\t\titems.add(separator());\n\t\titems.add(itemExportPrimary);\n\t\titems.add(itemViewChanges);\n\t\titems.add(separator());\n\t\titems.add(itemClose);\n\t\titems.add(itemQuit);\n\n\t\trefreshRecent();\n\t}\n\n\t@Override\n\tprotected void workspaceStateChanged() {\n\t\t// Add\n\t\tif (workspaceManager.hasCurrentWorkspace()) {\n\t\t\tWorkspace current = workspaceManager.getCurrent();\n\t\t\trecentFilesConfig.addWorkspace(current);\n\t\t}\n\n\t\t// Refresh\n\t\trefreshRecent();\n\t}\n\n\t/**\n\t * Update the items in the recent workspace menu.\n\t */\n\tpublic void refreshRecent() {\n\t\tmenuRecent.getItems().clear();\n\t\tList<RecentFilesConfig.WorkspaceModel> recentWorkspaces = recentFilesConfig.getRecentWorkspaces().getValue();\n\t\tfor (RecentFilesConfig.WorkspaceModel model : recentWorkspaces) {\n\t\t\tint libraryCount = model.libraries().size();\n\t\t\tString title;\n\t\t\tif (libraryCount > 0) {\n\t\t\t\ttitle = model.primary().getSimpleName() + \" + \" + libraryCount;\n\t\t\t} else {\n\t\t\t\ttitle = model.primary().getSimpleName();\n\t\t\t}\n\n\t\t\tRunnable remove = () -> recentWorkspaces.remove(model);\n\t\t\tif (model.canLoadWorkspace()) {\n\t\t\t\t// Workspace can be loaded\n\t\t\t\tString primaryPathString = model.primary().path();\n\t\t\t\tString extension = IOUtil.getExtension(primaryPathString);\n\t\t\t\tNode graphic = new File(primaryPathString).isDirectory() ?\n\t\t\t\t\t\tIcons.getIconView(Icons.FOLDER) :\n\t\t\t\t\t\tIcons.getIconView(Icons.getIconPathForFileExtension(extension));\n\t\t\t\tmenuRecent.getItems().add(new ClosableActionMenuItem(title, graphic, () -> {\n\t\t\t\t\t// If the model can no longer be loaded remove it.\n\t\t\t\t\tif (!model.canLoadWorkspace()) {\n\t\t\t\t\t\tToolkit.getDefaultToolkit().beep();\n\t\t\t\t\t\tremove.run();\n\t\t\t\t\t\tlogger.warn(\"Recent workspace for '{}' cannot be found\", title);\n\t\t\t\t\t\trefreshRecent();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Get paths from model\n\t\t\t\t\tPath primaryPath = Paths.get(primaryPathString);\n\t\t\t\t\tList<Path> supportingPaths = model.libraries().stream()\n\t\t\t\t\t\t\t.map(resource -> Paths.get(resource.path()))\n\t\t\t\t\t\t\t.toList();\n\n\t\t\t\t\t// Pass to loader\n\t\t\t\t\tpathLoadingManager.asyncNewWorkspace(primaryPath, supportingPaths, ex -> {\n\t\t\t\t\t\tToolkit.getDefaultToolkit().beep();\n\t\t\t\t\t\trecentWorkspaces.remove(model);\n\t\t\t\t\t\trefreshRecent();\n\t\t\t\t\t\tlogger.error(\"Failed to open recent workspace for '{}'\", title, ex);\n\t\t\t\t\t\tErrorDialogs.show(\n\t\t\t\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.title\"),\n\t\t\t\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.header\"),\n\t\t\t\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.content\"),\n\t\t\t\t\t\t\t\tex\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\t\t\t\t}, remove));\n\t\t\t} else {\n\t\t\t\t// Workspace cannot be loaded (missing data), keep in list in-case user can restore file,\n\t\t\t\t// but allow user to remove it on their own too.\n\t\t\t\tNode graphic = new FontIconView(CarbonIcons.UNKNOWN);\n\t\t\t\tmenuRecent.getItems().add(new ClosableActionMenuItem(title, graphic, remove, remove));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Display the workspace wizard.\n\t */\n\tprivate void openWorkspace() {\n\t\tStage stage = new RecafStage();\n\t\tWorkspaceBuilderPane root = new WorkspaceBuilderPane(pathLoadingManager, recentFilesConfig, () -> FxThreadUtil.run(stage::close));\n\t\tstage.titleProperty().bind(Lang.getBinding(\"dialog.title.create-workspace\"));\n\t\tstage.setScene(new RecafScene(root));\n\t\tstage.setMinWidth(650);\n\t\tstage.setMinHeight(400);\n\t\tstage.show();\n\t\twindowManager.registerAnonymous(stage);\n\t}\n\n\t/**\n\t * Display a wizard for adding additional resources.\n\t */\n\tprivate void addToWorkspace() {\n\t\tif (!workspaceManager.hasCurrentWorkspace())\n\t\t\treturn;\n\t\tStage stage = new RecafStage();\n\t\tWorkspaceBuilderPane root = new WorkspaceBuilderPane(pathLoadingManager, recentFilesConfig, workspaceManager.getCurrent(), () -> FxThreadUtil.run(stage::close));\n\t\tstage.titleProperty().bind(Lang.getBinding(\"menu.file.addtoworkspace\"));\n\t\tstage.setScene(new RecafScene(root));\n\t\tstage.setMinWidth(650);\n\t\tstage.setMinHeight(400);\n\t\tstage.show();\n\t\twindowManager.registerAnonymous(stage);\n\t}\n\n\t/**\n\t * Display the attach window.\n\t */\n\tprivate void openAttach() {\n\t\tStage remoteVmWindow = windowManager.getRemoteVmWindow();\n\t\tremoteVmWindow.show();\n\t\tremoteVmWindow.requestFocus();\n\t}\n\n\t/**\n\t * Display the open-url window.\n\t */\n\tprivate void openUrl() {\n\t\tOpenUrlPopup popup = openUrlProvider.get();\n\t\tpopup.show();\n\t\tpopup.requestInputFocus();\n\t\tpopup.setOnHiding(e -> openUrlProvider.destroy(popup));\n\t}\n\n\t/**\n\t * Display the change viewer window.\n\t */\n\tprivate void openChangeViewer() {\n\t\t// TODO: Reimplement change viewer, give it its own @Dependent window like 'RemoteVirtualMachinesWindow'\n\t\t//       and the behavior above in 'openAttach'\n\t}\n\n\t/**\n\t * Delegate to {@link PathExportingManager#exportCurrent()}.\n\t */\n\tprivate void exportCurrent() {\n\t\tpathExportingManager.exportCurrent();\n\t}\n\n\t/**\n\t * Delegate to {@link WorkspaceManager#closeCurrent()}.\n\t */\n\tprivate void closeWorkspace() {\n\t\tworkspaceManager.closeCurrent();\n\t}\n\n\t/**\n\t * Close all windows, which should trigger application shutdown.\n\t */\n\tprivate void quit() {\n\t\t// Close all windows. The main window's exit handler should handle the application shutdown.\n\t\tfor (Stage window : new ArrayList<>(windowManager.getActiveWindows()))\n\t\t\twindow.close();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/menubar/HelpMenu.java",
    "content": "package software.coley.recaf.ui.menubar;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.Menu;\nimport javafx.stage.Stage;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.tutorial.TutorialWorkspaceBuilder;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.util.DesktopUtil;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.util.threading.ThreadUtil;\n\nimport java.net.URI;\n\nimport static software.coley.recaf.util.Lang.getBinding;\nimport static software.coley.recaf.util.Menus.action;\n\n/**\n * Help menu component for {@link MainMenu}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class HelpMenu extends Menu {\n\tprivate static final Logger logger = Logging.get(HelpMenu.class);\n\tprivate final TutorialWorkspaceBuilder tutorialWorkspaceBuilder;\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final WindowManager windowManager;\n\n\t@Inject\n\tpublic HelpMenu(@Nonnull WindowManager windowManager,\n\t                @Nonnull WorkspaceManager workspaceManager,\n\t                @Nonnull TutorialWorkspaceBuilder tutorialWorkspaceBuilder) {\n\t\tthis.tutorialWorkspaceBuilder =tutorialWorkspaceBuilder;\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.windowManager = windowManager;\n\n\t\ttextProperty().bind(getBinding(\"menu.help\"));\n\t\tsetGraphic(new FontIconView(CarbonIcons.HELP));\n\n\t\tgetItems().add(action(\"menu.help.sysinfo\", CarbonIcons.INFORMATION, this::openSystem));\n\t\tgetItems().add(action(\"menu.help.tutorial\", CarbonIcons.PEDESTRIAN_CHILD, this::openTutorial));\n\t\tgetItems().add(action(\"menu.help.docs\", CarbonIcons.NOTEBOOK_REFERENCE, this::openDocumentation));\n\t\tgetItems().add(action(\"menu.help.docsdev\", CarbonIcons.NOTEBOOK_REFERENCE, this::openDeveloperDocumentation));\n\t\tgetItems().add(action(\"menu.help.github\", CarbonIcons.LOGO_GITHUB, this::openGithub));\n\t\tgetItems().add(action(\"menu.help.issues\", CarbonIcons.LOGO_GITHUB, this::openGithubIssues));\n\t\tgetItems().add(action(\"menu.help.discord\", Icons.DISCORD, this::openDiscord));\n\t}\n\n\t/**\n\t * Display the tutorial workspace.\n\t */\n\tprivate void openTutorial() {\n\t\tThreadUtil.run(() -> workspaceManager.setCurrent(tutorialWorkspaceBuilder.generateWorkspace()));\n\t}\n\n\t/**\n\t * Display the system information window.\n\t */\n\tprivate void openSystem() {\n\t\tStage systemWindow = windowManager.getSystemInfoWindow();\n\t\tsystemWindow.show();\n\t\tsystemWindow.requestFocus();\n\t}\n\n\t/**\n\t * Gotta pump up that member count, right?\n\t */\n\tprivate void openDiscord() {\n\t\tbrowse(\"https://discord.gg/Bya5HaA\");\n\t}\n\n\t/**\n\t * Star pls.\n\t */\n\tprivate void openGithub() {\n\t\tbrowse(\"https://github.com/Col-E/Recaf\");\n\t}\n\n\t/**\n\t * Bugs and features I'll get to <i>\"eventually(tm)\"</i>\n\t */\n\tprivate void openGithubIssues() {\n\t\tbrowse(\"https://github.com/Col-E/Recaf/issues\");\n\t}\n\n\t/**\n\t * Let's be honest, nobody reads this... <i>Unless we force them to.</i>\n\t */\n\tprivate void openDocumentation() {\n\t\tbrowse(\"https://recaf.coley.software/user/index.html\");\n\t}\n\n\t/**\n\t * Now, if you're reading this one, that's pretty cool.\n\t */\n\tprivate void openDeveloperDocumentation() {\n\t\tbrowse(\"https://recaf.coley.software/dev/index.html\");\n\t}\n\n\n\tprivate static void browse(String uri) {\n\t\ttry {\n\t\t\tDesktopUtil.showDocument(new URI(uri));\n\t\t} catch (Exception ex) {\n\t\t\tlogger.error(\"Failed to open link\", ex);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/menubar/MainMenu.java",
    "content": "package software.coley.recaf.ui.menubar;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport javafx.geometry.Insets;\nimport javafx.scene.control.MenuBar;\n\n/**\n * Main menu component, bundling sub-menu components.\n *\n * @author Matt Coley\n * @see MainMenuProvider A producer bean allows you to @Inject this type.\n */\npublic class MainMenu extends MenuBar {\n\tprivate final FileMenu fileMenu;\n\tprivate final ConfigMenu configMenu;\n\tprivate final SearchMenu searchMenu;\n\tprivate final MappingMenu mappingMenu;\n\tprivate final AnalysisMenu analysisMenu;\n\tprivate final ScriptMenu scriptMenu;\n\tprivate final HelpMenu helpMenu;\n\n\t/**\n\t * Called from {@link MainMenuProvider} which facilitates exposing\n\t * this class as an effective {@link ApplicationScoped} bean.\n\t *\n\t * @param fileMenu\n\t * \t\tFile menu instance.\n\t * @param configMenu\n\t * \t\tConfig menu instance.\n\t * @param searchMenu\n\t * \t\tSearch menu instance.\n\t * @param mappingMenu\n\t * \t\tMapping menu instance.\n\t * @param analysisMenu\n\t * \t\tAnalysis menu instance.\n\t * @param scriptMenu\n\t * \t\tScript menu instance.\n\t * @param helpMenu\n\t * \t\tHelp menu instance.\n\t */\n\tMainMenu(@Nonnull FileMenu fileMenu,\n\t         @Nonnull ConfigMenu configMenu,\n\t         @Nonnull SearchMenu searchMenu,\n\t         @Nonnull MappingMenu mappingMenu,\n\t         @Nonnull AnalysisMenu analysisMenu,\n\t         @Nonnull ScriptMenu scriptMenu,\n\t         @Nonnull HelpMenu helpMenu) {\n\t\tthis.fileMenu = fileMenu;\n\t\tthis.configMenu = configMenu;\n\t\tthis.searchMenu = searchMenu;\n\t\tthis.mappingMenu = mappingMenu;\n\t\tthis.analysisMenu = analysisMenu;\n\t\tthis.scriptMenu = scriptMenu;\n\t\tthis.helpMenu = helpMenu;\n\n\t\tgetMenus().addAll(fileMenu, configMenu, searchMenu, mappingMenu, analysisMenu, scriptMenu, helpMenu);\n\t\tsetPadding(new Insets(0, 0, 2, 0));\n\t}\n\n\t/** @return File menu instance. */\n\t@Nonnull\n\tpublic FileMenu getFileMenu() {\n\t\treturn fileMenu;\n\t}\n\n\t/** @return Config menu instance. */\n\t@Nonnull\n\tpublic ConfigMenu getConfigMenu() {\n\t\treturn configMenu;\n\t}\n\n\t/** @return Search menu instance. */\n\t@Nonnull\n\tpublic SearchMenu getSearchMenu() {\n\t\treturn searchMenu;\n\t}\n\n\t/** @return Mapping menu instance. */\n\t@Nonnull\n\tpublic MappingMenu getMappingMenu() {\n\t\treturn mappingMenu;\n\t}\n\n\t/** @return Analysis menu instance. */\n\t@Nonnull\n\tpublic AnalysisMenu getAnalysisMenu() {\n\t\treturn analysisMenu;\n\t}\n\n\t/** @return Script menu instance. */\n\t@Nonnull\n\tpublic ScriptMenu getScriptMenu() {\n\t\treturn scriptMenu;\n\t}\n\n\t/** @return Help menu instance. */\n\t@Nonnull\n\tpublic HelpMenu getHelpMenu() {\n\t\treturn helpMenu;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/menubar/MainMenuProvider.java",
    "content": "package software.coley.recaf.ui.menubar;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.enterprise.inject.Produces;\nimport jakarta.inject.Inject;\n\n/**\n * Facilitate the injection of {@link MainMenu} as a singleton bean.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class MainMenuProvider {\n\tprivate final MainMenu mainMenu;\n\n\t@Inject\n\tpublic MainMenuProvider(@Nonnull FileMenu fileMenu,\n\t                        @Nonnull ConfigMenu configMenu,\n\t                        @Nonnull SearchMenu searchMenu,\n\t                        @Nonnull MappingMenu mappingMenu,\n\t                        @Nonnull AnalysisMenu analysisMenu,\n\t                        @Nonnull ScriptMenu scriptMenu,\n\t                        @Nonnull HelpMenu helpMenu) {\n\t\tthis.mainMenu = new MainMenu(fileMenu, configMenu, searchMenu, mappingMenu, analysisMenu, scriptMenu, helpMenu);\n\t}\n\n\t/**\n\t * @return Shared main menu instance.\n\t */\n\t@Nonnull\n\t@Produces\n\tpublic MainMenu getMainMenu() {\n\t\treturn mainMenu;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/menubar/MappingMenu.java",
    "content": "package software.coley.recaf.ui.menubar;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.Menu;\nimport javafx.scene.control.MenuItem;\nimport javafx.scene.control.SeparatorMenuItem;\nimport javafx.stage.Stage;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.MappingHelper;\nimport software.coley.recaf.services.mapping.aggregate.AggregateMappingManager;\nimport software.coley.recaf.services.mapping.format.MappingFileFormat;\nimport software.coley.recaf.services.mapping.format.MappingFormatManager;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.config.RecentFilesConfig;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.window.MappingApplicationWindow;\nimport software.coley.recaf.ui.window.MappingGeneratorWindow;\nimport software.coley.recaf.util.FileChooserBundle;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\n\nimport java.io.File;\nimport java.util.IdentityHashMap;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport static software.coley.recaf.util.Lang.getBinding;\nimport static software.coley.recaf.util.Menus.*;\n\n/**\n * Mapping menu component for {@link MainMenu}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class MappingMenu extends WorkspaceAwareMenu {\n\tprivate static final Logger logger = Logging.get(MappingMenu.class);\n\tprivate final WindowManager windowManager;\n\tprivate final MappingFormatManager formatManager;\n\tprivate final MappingHelper mappingHelper;\n\n\t@Inject\n\tpublic MappingMenu(@Nonnull WindowManager windowManager,\n\t                   @Nonnull WorkspaceManager workspaceManager,\n\t                   @Nonnull AggregateMappingManager aggregateMappingManager,\n\t                   @Nonnull MappingFormatManager formatManager,\n\t                   @Nonnull MappingHelper mappingHelper,\n\t                   @Nonnull Instance<MappingGeneratorWindow> generatorWindowProvider,\n\t                   @Nonnull Instance<MappingApplicationWindow> applyWindowProvider,\n\t                   @Nonnull RecentFilesConfig recentFiles) {\n\t\tsuper(workspaceManager);\n\n\t\tthis.windowManager = windowManager;\n\t\tthis.formatManager = formatManager;\n\t\tthis.mappingHelper = mappingHelper;\n\n\t\ttextProperty().bind(getBinding(\"menu.mappings\"));\n\t\tsetGraphic(new FontIconView(CarbonIcons.MAP_BOUNDARY));\n\n\t\tFileChooserBundle choosers = FileChooserBundle.fromRecent(recentFiles);\n\n\t\tMenu apply = menu(\"menu.mappings.apply\", CarbonIcons.DOCUMENT_IMPORT);\n\t\tMenu export = menu(\"menu.mappings.export\", CarbonIcons.DOCUMENT_EXPORT);\n\t\tMap<MappingFileFormat, MenuItem> formatToExportAsItems = new IdentityHashMap<>();\n\t\tfor (String formatName : formatManager.getMappingFileFormats()) {\n\t\t\t// Temp instance to check for special cases.\n\t\t\tMappingFileFormat tmpFormat = formatManager.createFormatInstance(formatName);\n\t\t\tif (tmpFormat == null) continue;\n\n\t\t\tMappingMenuItems mappingItems = createMappingItems(formatName, choosers);\n\t\t\tapply.getItems().add(mappingItems.importAsItem());\n\t\t\tif (tmpFormat.supportsExportText()) {\n\t\t\t\texport.getItems().add(mappingItems.exportAsItem());\n\t\t\t\tformatToExportAsItems.put(tmpFormat, mappingItems.exportAsItem());\n\t\t\t} else {\n\t\t\t\tMenuItem item = new MenuItem();\n\t\t\t\titem.textProperty().bind(Lang.format(\"menu.mappings.export.unsupported\", formatName));\n\t\t\t\titem.setGraphic(new FontIconView(CarbonIcons.CLOSE));\n\t\t\t\titem.setDisable(true);\n\t\t\t\texport.getItems().add(item);\n\t\t\t}\n\t\t}\n\n\t\tgetItems().addAll(apply, export,\n\t\t\t\taction(\"menu.mappings.generate\", CarbonIcons.LICENSE_MAINTENANCE, () -> openGenerate(generatorWindowProvider)),\n\t\t\t\taction(\"menu.mappings.view\", CarbonIcons.VIEW, this::openView),\n\t\t\t\tnew SeparatorMenuItem(),\n\t\t\t\taction(\"menu.mappings.apply-advanced\", CarbonIcons.LICENSE_GLOBAL, () -> openApply(applyWindowProvider))\n\t\t);\n\n\t\t// Disable if attached via agent, or there is no workspace\n\t\tdisableProperty().bind(hasAgentWorkspace.or(hasWorkspace.not()));\n\n\t\t// Disable formats that require field type differentiation if we have aggregate data that does not have differentiation.\n\t\taggregateMappingManager.addAggregatedMappingsListener(mappings -> {\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tfor (var formatItemEntry : formatToExportAsItems.entrySet()) {\n\t\t\t\t\tif (formatItemEntry.getKey().doesSupportFieldTypeDifferentiation())\n\t\t\t\t\t\tformatItemEntry.getValue().setDisable(mappings.isMissingFieldDescriptors());\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\tworkspaceManager.addWorkspaceCloseListener(closedWorkspace -> {\n\t\t\t// Re-enable when closing so the next workspace has a clean slate\n\t\t\tformatToExportAsItems.values().forEach(i -> i.setDisable(false));\n\t\t});\n\t}\n\n\t@Nonnull\n\tprivate MappingMenuItems createMappingItems(@Nonnull String formatName,\n\t                                            @Nonnull FileChooserBundle choosers) {\n\t\tMappingFileFormat format = Objects.requireNonNull(formatManager.createFormatInstance(formatName),\n\t\t\t\t\"Failed creating mapping format instance for: \" + formatName);\n\t\tMenuItem importAsItem = actionLiteral(formatName, CarbonIcons.LICENSE, () -> {\n\t\t\t// Show the prompt, load the mappings text ant attempt to load them.\n\t\t\tFile file = choosers.showFileOpen(windowManager.getMainWindow());\n\t\t\tif (file != null) {\n\t\t\t\ttry {\n\t\t\t\t\tIntermediateMappings mappings = mappingHelper.parse(format, file.toPath());\n\t\t\t\t\tmappingHelper.applyMappings(format, mappings);\n\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\tlogger.error(\"Failed importing mappings from {}\", file.getName(), t);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tMenuItem exportAsItem = actionLiteral(formatName, CarbonIcons.LICENSE, () -> {\n\t\t\t// Show the prompt, write current mappings to the given path.\n\t\t\tFile file = choosers.showFileExport(windowManager.getMainWindow());\n\t\t\tif (file != null) {\n\t\t\t\tmappingHelper.exportMappingsFile(format, file.toPath());\n\t\t\t}\n\t\t});\n\t\treturn new MappingMenuItems(importAsItem, exportAsItem);\n\t}\n\n\tprivate void openGenerate(@Nonnull Instance<MappingGeneratorWindow> generatorWindowProvider) {\n\t\tMappingGeneratorWindow window = generatorWindowProvider.get();\n\t\twindow.setOnCloseRequest(e -> generatorWindowProvider.destroy(window));\n\t\twindow.show();\n\t\twindow.requestFocus();\n\t\twindowManager.registerAnonymous(window);\n\t}\n\n\tprivate void openView() {\n\t\tStage window = windowManager.getMappingPreviewWindow();\n\t\twindow.show();\n\t\twindow.requestFocus();\n\t}\n\n\tprivate void openApply(@Nonnull Instance<MappingApplicationWindow> applyWindowProvider) {\n\t\tMappingApplicationWindow window = applyWindowProvider.get();\n\t\twindow.setOnCloseRequest(e -> applyWindowProvider.destroy(window));\n\t\twindow.show();\n\t\twindow.requestFocus();\n\t\twindowManager.registerAnonymous(window);\n\t}\n\n\tprivate record MappingMenuItems(@Nonnull MenuItem importAsItem, @Nullable MenuItem exportAsItem) {}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/menubar/ScriptMenu.java",
    "content": "package software.coley.recaf.ui.menubar;\n\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.Menu;\nimport javafx.scene.control.MenuItem;\nimport javafx.stage.Stage;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableCollection;\nimport software.coley.recaf.services.script.ScriptEngine;\nimport software.coley.recaf.services.script.ScriptFile;\nimport software.coley.recaf.services.script.ScriptManager;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.pane.ScriptManagerPane;\nimport software.coley.recaf.util.FxThreadUtil;\n\nimport java.util.List;\nimport java.util.TreeSet;\n\nimport static software.coley.recaf.util.Lang.getBinding;\nimport static software.coley.recaf.util.Menus.*;\n\n/**\n * Scripting menu component for {@link MainMenu}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class ScriptMenu extends Menu {\n\tprivate final Menu menuScripts = menu(\"menu.scripting.list\", CarbonIcons.LIST);\n\tprivate final WindowManager windowManager;\n\tprivate final ObservableCollection<ScriptFile, List<ScriptFile>> scriptFiles;\n\tprivate final ObservableBoolean hasScripts;\n\tprivate final ScriptManagerPane scriptManagerPane;\n\tprivate final ScriptEngine engine;\n\n\t@Inject\n\tpublic ScriptMenu(WindowManager windowManager, ScriptManager scriptManager, ScriptEngine engine,\n\t\t\t\t\t  ScriptManagerPane scriptManagerPane) {\n\t\tthis.windowManager = windowManager;\n\t\tthis.scriptManagerPane = scriptManagerPane;\n\t\tthis.engine = engine;\n\n\t\tscriptFiles = scriptManager.getScriptFiles();\n\t\thasScripts = scriptFiles.mapBoolean(List::isEmpty).negated();\n\n\t\ttextProperty().bind(getBinding(\"menu.scripting\"));\n\t\tsetGraphic(new FontIconView(CarbonIcons.SCRIPT));\n\n\t\tgetItems().add(menuScripts);\n\t\tgetItems().add(action(\"menu.scripting.manage\", CarbonIcons.INFORMATION, this::openManager));\n\t\tgetItems().add(action(\"menu.scripting.new\", CarbonIcons.ADD_ALT, this::newScript));\n\n\t\thasScripts.addChangeListener((ob, old, cur) -> refreshList());\n\t\trefreshList();\n\t}\n\n\t/**\n\t * Update the items in the scripts menu.\n\t */\n\tprivate void refreshList() {\n\t\tFxThreadUtil.run(() -> {\n\t\t\tmenuScripts.getItems().clear();\n\t\t\tif (hasScripts.getValue()) {\n\t\t\t\tfor (ScriptFile scriptFile : new TreeSet<>(scriptFiles)) {\n\t\t\t\t\tmenuScripts.getItems().add(actionLiteral(scriptFile.name(), CarbonIcons.PLAY_FILLED,\n\t\t\t\t\t\t\t() -> scriptFile.execute(engine)));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tMenuItem item = new MenuItem();\n\t\t\t\titem.setGraphic(new FontIconView(CarbonIcons.SEARCH));\n\t\t\t\titem.textProperty().bind(getBinding(\"menu.scripting.none-found\"));\n\t\t\t\titem.setDisable(true);\n\t\t\t\tmenuScripts.getItems().add(item);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Display the script manager window.\n\t */\n\tprivate void openManager() {\n\t\tStage scriptsWindow = windowManager.getScriptManagerWindow();\n\t\tscriptsWindow.show();\n\t\tscriptsWindow.requestFocus();\n\t}\n\n\t/**\n\t * Opens a new script editor.\n\t */\n\tprivate void newScript() {\n\t\tscriptManagerPane.newScript();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/menubar/SearchMenu.java",
    "content": "package software.coley.recaf.ui.menubar;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.FontIconView;\n\nimport static software.coley.recaf.util.Lang.getBinding;\nimport static software.coley.recaf.util.Menus.action;\n\n/**\n * Search menu component for {@link MainMenu}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class SearchMenu extends WorkspaceAwareMenu {\n\n\t@Inject\n\tpublic SearchMenu(@Nonnull WorkspaceManager workspaceManager,\n\t\t\t\t\t  @Nonnull Actions actions) {\n\t\tsuper(workspaceManager);\n\n\t\ttextProperty().bind(getBinding(\"menu.search\"));\n\t\tsetGraphic(new FontIconView(CarbonIcons.SEARCH));\n\n\t\tgetItems().add(action(\"menu.search.string\", CarbonIcons.QUOTES, actions::openNewStringSearch));\n\t\tgetItems().add(action(\"menu.search.number\", CarbonIcons.NUMBER_0, actions::openNewNumberSearch));\n\t\tgetItems().add(action(\"menu.search.class.type-references\", CarbonIcons.CODE_REFERENCE, actions::openNewClassReferenceSearch));\n\t\tgetItems().add(action(\"menu.search.class.member-references\", CarbonIcons.CODE_REFERENCE, actions::openNewMemberReferenceSearch));\n\t\tgetItems().add(action(\"menu.search.class.member-declarations\", CarbonIcons.CODE, actions::openNewMemberDeclarationSearch));\n\t\tgetItems().add(action(\"menu.search.class.instruction\", CarbonIcons.CODE, actions::openNewInstructionSearch));\n\n\t\tdisableProperty().bind(hasWorkspace.not());\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/menubar/WorkspaceAwareMenu.java",
    "content": "package software.coley.recaf.ui.menubar;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.scene.control.Menu;\nimport software.coley.recaf.services.workspace.WorkspaceCloseListener;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.services.workspace.WorkspaceOpenListener;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.AgentServerRemoteVmResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\n/**\n * {@link Menu} that is aware of the current workspace environment.\n * <br>\n * Implementations must be scoped beans that provide access to {@link WorkspaceManager}.\n *\n * @author Matt Coley\n */\npublic abstract class WorkspaceAwareMenu extends Menu implements WorkspaceOpenListener, WorkspaceCloseListener {\n\tprotected final BooleanProperty hasWorkspace = new SimpleBooleanProperty(false);\n\tprotected final BooleanProperty hasAgentWorkspace = new SimpleBooleanProperty(false);\n\n\tprotected WorkspaceAwareMenu(@Nonnull WorkspaceManager workspaceManager) {\n\t\tworkspaceManager.addWorkspaceOpenListener(this);\n\t\tworkspaceManager.addWorkspaceCloseListener(this);\n\t}\n\n\t@Override\n\tpublic void onWorkspaceClosed(@Nonnull Workspace workspace) {\n\t\tFxThreadUtil.run(() -> {\n\t\t\thasWorkspace.set(false);\n\t\t\thasAgentWorkspace.set(false);\n\t\t\tworkspaceStateChanged();\n\t\t});\n\t}\n\n\t@Override\n\tpublic void onWorkspaceOpened(@Nonnull Workspace workspace) {\n\t\tFxThreadUtil.run(() -> {\n\t\t\tWorkspaceResource primaryResource = workspace.getPrimaryResource();\n\t\t\thasWorkspace.set(true);\n\t\t\thasAgentWorkspace.set(primaryResource instanceof AgentServerRemoteVmResource);\n\t\t\tworkspaceStateChanged();\n\t\t});\n\t}\n\n\tprotected void workspaceStateChanged() {\n\t\t// no-op by default\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/CommentEditPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.layout.BorderPane;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.properties.builtin.CachedDecompileProperty;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.comment.ClassComments;\nimport software.coley.recaf.services.comment.CommentManager;\nimport software.coley.recaf.services.comment.WorkspaceComments;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.NavigationManager;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.ui.config.KeybindingConfig;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.bracket.SelectedBracketTracking;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.ui.pane.editing.AbstractDecompilePane;\nimport software.coley.recaf.ui.pane.editing.ClassPane;\nimport software.coley.recaf.util.Animations;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.threading.ThreadUtil;\n\nimport java.util.Collection;\nimport java.util.Collections;\n\n/**\n * Pane for editing the comments on a class, field, or method.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class CommentEditPane extends BorderPane implements UpdatableNavigable, DocumentationPane {\n\tprivate final CommentManager commentManager;\n\tprivate final NavigationManager navigationManager;\n\tprivate final Editor editor;\n\tprivate PathNode<?> path;\n\n\t@Inject\n\tpublic CommentEditPane(@Nonnull CommentManager commentManager,\n\t\t\t\t\t\t   @Nonnull NavigationManager navigationManager,\n\t\t\t\t\t\t   @Nonnull KeybindingConfig keys,\n\t\t\t\t\t\t   @Nonnull SearchBar searchBar) {\n\t\tthis.commentManager = commentManager;\n\t\tthis.navigationManager = navigationManager;\n\n\t\t// Configure the editor\n\t\teditor = new Editor();\n\t\teditor.setSelectedBracketTracking(new SelectedBracketTracking());\n\t\teditor.getRootLineGraphicFactory().addDefaultCodeGraphicFactories();\n\t\tsearchBar.install(editor);\n\n\t\t// Setup keybindings\n\t\tsetOnKeyPressed(e -> {\n\t\t\tif (keys.getSave().match(e))\n\t\t\t\tThreadUtil.run(this::save);\n\t\t});\n\n\t\t// Layout\n\t\tsetCenter(editor);\n\t}\n\n\t@Override\n\tpublic boolean isTrackable() {\n\t\t// Disabling tracking allows other panels with the same path-node to be opened.\n\t\treturn false;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tsetDisable(true);\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tthis.path = path;\n\n\t\tWorkspaceComments workspaceComments = commentManager.getCurrentWorkspaceComments();\n\t\tif (workspaceComments == null)\n\t\t\treturn;\n\n\t\tString comment = null;\n\t\tif (path instanceof ClassMemberPathNode memberPath) {\n\t\t\tClassPathNode classPath = memberPath.getParent();\n\t\t\tClassComments classComments = workspaceComments.getClassComments(classPath);\n\t\t\tif (classComments != null) {\n\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\tif (member.isMethod()) {\n\t\t\t\t\tcomment = classComments.getMethodComment(member.getName(), member.getDescriptor());\n\t\t\t\t} else {\n\t\t\t\t\tcomment = classComments.getFieldComment(member.getName(), member.getDescriptor());\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (path instanceof ClassPathNode classPath) {\n\t\t\tClassComments classComments = workspaceComments.getClassComments(classPath);\n\t\t\tif (classComments != null)\n\t\t\t\tcomment = classComments.getClassComment();\n\t\t}\n\n\t\tif (comment != null)\n\t\t\teditor.setText(comment);\n\t}\n\n\t/**\n\t * Called when {@link KeybindingConfig#getSave()} is pressed.\n\t * <br>\n\t * Updates the comment in the {@link ClassComments} associated with the {@link #path}.\n\t */\n\tprivate void save() {\n\t\tif (path == null)\n\t\t\treturn;\n\n\t\tWorkspaceComments workspaceComments = commentManager.getCurrentWorkspaceComments();\n\t\tif (workspaceComments == null)\n\t\t\treturn;\n\n\t\tString comment = editor.getText();\n\t\tif (comment.isBlank())\n\t\t\tcomment = null; // Will remove the comment\n\t\tif (path instanceof ClassMemberPathNode memberPath) {\n\t\t\tClassPathNode classPath = memberPath.getParent();\n\t\t\tClassComments classComments = workspaceComments.getOrCreateClassComments(classPath);\n\t\t\tClassMember member = memberPath.getValue();\n\t\t\tif (member.isMethod()) {\n\t\t\t\tclassComments.setMethodComment(member.getName(), member.getDescriptor(), comment);\n\t\t\t} else {\n\t\t\t\tclassComments.setFieldComment(member.getName(), member.getDescriptor(), comment);\n\t\t\t}\n\t\t\tinvalidateDecompile(classPath);\n\t\t\tFxThreadUtil.run(() -> Animations.animateSuccess(editor, 1000));\n\t\t} else if (path instanceof ClassPathNode classPath) {\n\t\t\tClassComments classComments = workspaceComments.getOrCreateClassComments(classPath);\n\t\t\tclassComments.setClassComment(comment);\n\t\t\tinvalidateDecompile(classPath);\n\t\t\tFxThreadUtil.run(() -> Animations.animateSuccess(editor, 1000));\n\t\t}\n\t}\n\n\tprivate void invalidateDecompile(@Nonnull ClassPathNode classPath) {\n\t\t// Void the cached decompilation.\n\t\tClassInfo classInfo = classPath.getValue();\n\t\tCachedDecompileProperty.remove(classInfo);\n\n\t\t// Trigger a re-decompilation.\n\t\tnavigationManager.getNavigableChildrenByPath(classPath).forEach(child -> {\n\t\t\tif (child instanceof ClassPane classPane && classPath.equals(classPane.getPath()))\n\t\t\t\tclassPane.getNavigableChildren().forEach(subchild -> {\n\t\t\t\t\tif (subchild instanceof AbstractDecompilePane decompilePane)\n\t\t\t\t\t\tdecompilePane.decompile();\n\t\t\t\t});\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/CommentListPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.StringProperty;\nimport javafx.geometry.Insets;\nimport javafx.scene.Cursor;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.TextField;\nimport javafx.scene.layout.*;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.*;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.comment.*;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Pane for listing comments made on classes and their members in the current workspace.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class CommentListPane extends BorderPane implements Navigable, DocumentationPane, CommentUpdateListener, CommentContainerListener {\n\tprivate static final Logger logger = Logging.get(CommentListPane.class);\n\tprivate final Map<String, ClassCommentPane> classToPane = new ConcurrentHashMap<>();\n\tprivate final CommentSearchPane searchPane = new CommentSearchPane();\n\tprivate final VBox commentsList = new VBox();\n\tprivate final WorkspacePathNode workspacePath;\n\tprivate final CellConfigurationService cellConfigurationService;\n\tprivate final CommentManager commentManager;\n\tprivate final Actions actions;\n\n\t@Inject\n\tpublic CommentListPane(@Nonnull CommentManager commentManager,\n\t                       @Nonnull CellConfigurationService cellConfigurationService,\n\t                       @Nonnull WorkspaceManager workspaceManager,\n\t                       @Nonnull Actions actions) {\n\t\tthis.cellConfigurationService = cellConfigurationService;\n\t\tthis.commentManager = commentManager;\n\t\tthis.actions = actions;\n\n\t\tworkspacePath = PathNodes.workspacePath(Objects.requireNonNull(workspaceManager.getCurrent(), \"Cannot open comment list if no workspace is open\"));\n\n\t\t// Some basic UI layout.\n\t\tcommentsList.setSpacing(10);\n\t\tcommentsList.setPadding(new Insets(10));\n\t\tScrollPane scroll = new ScrollPane(commentsList);\n\t\tscroll.getStyleClass().add(\"dark-scroll-pane\");\n\t\tscroll.setFitToWidth(true);\n\t\tsetCenter(scroll);\n\t\tsetBottom(searchPane);\n\n\t\t// Register self as a listener so that when comment updates are made we can change the display.\n\t\tcommentManager.addCommentListener(this);\n\t\tcommentManager.addCommentContainerListener(this);\n\n\t\t// Listener won't cover items that already exist.\n\t\tpopulateInitialComments();\n\t}\n\n\t@Override\n\tpublic boolean isTrackable() {\n\t\t// We want this type to be navigable to benefit from automatic close support.\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void onClassCommentUpdated(@Nonnull ClassPathNode path, @Nullable String comment) {\n\t\tClassCommentPane pane = getOrCreateCommentPane(path);\n\t\tif (pane != null)\n\t\t\tpane.onClassCommentUpdated(path, comment);\n\t}\n\n\t@Override\n\tpublic void onFieldCommentUpdated(@Nonnull ClassMemberPathNode path, @Nullable String comment) {\n\t\tClassPathNode classPath = Objects.requireNonNull(path.getParent());\n\t\tClassCommentPane pane = getOrCreateCommentPane(classPath);\n\t\tif (pane != null)\n\t\t\tpane.onFieldCommentUpdated(path, comment);\n\t}\n\n\t@Override\n\tpublic void onMethodCommentUpdated(@Nonnull ClassMemberPathNode path, @Nullable String comment) {\n\t\tClassPathNode classPath = Objects.requireNonNull(path.getParent());\n\t\tClassCommentPane pane = getOrCreateCommentPane(classPath);\n\t\tif (pane != null)\n\t\t\tpane.onMethodCommentUpdated(path, comment);\n\t}\n\n\t@Override\n\tpublic void onClassContainerRemoved(@Nonnull ClassPathNode path, @Nullable ClassComments comments) {\n\t\tClassCommentPane pane = classToPane.get(path.getValue().getName());\n\t\tif (pane != null) {\n\t\t\tFxThreadUtil.run(() -> commentsList.getChildren().remove(pane));\n\t\t}\n\t}\n\n\t/**\n\t * Create comment panes for all class comments in the current workspace.\n\t */\n\tprivate void populateInitialComments() {\n\t\tWorkspaceComments workspaceComments = commentManager.getCurrentWorkspaceComments();\n\t\tif (workspaceComments == null) return;\n\t\tfor (ClassComments classComments : workspaceComments)\n\t\t\tcommentsList.getChildren().add(new ClassCommentPane(classComments));\n\t}\n\n\t@Nullable\n\tprivate ClassCommentPane getOrCreateCommentPane(@Nonnull ClassPathNode path) {\n\t\t// Skip if workspace container is null.\n\t\tWorkspaceComments workspaceComments = commentManager.getCurrentWorkspaceComments();\n\t\tif (workspaceComments == null)\n\t\t\treturn null;\n\n\t\t// Skip if class container is null.\n\t\tString name = path.getValue().getName();\n\t\tClassComments classComments = workspaceComments.getClassComments(path);\n\t\tif (classComments == null)\n\t\t\treturn null;\n\n\t\t// Get or create the comment pane.\n\t\tClassCommentPane pane = classToPane.get(name);\n\t\tif (pane == null) {\n\t\t\tClassCommentPane newPane = new ClassCommentPane(classComments);\n\t\t\tFxThreadUtil.run(() -> commentsList.getChildren().add(newPane));\n\t\t\tpane = newPane;\n\t\t}\n\n\t\treturn pane;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn workspacePath;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\t// Important that we unregister ourselves to prevent leakage.\n\t\tcommentManager.removeCommentListener(this);\n\t\tcommentManager.removeCommentContainerListener(this);\n\n\t\t// Clear out the UI.\n\t\tfor (ClassCommentPane value : classToPane.values())\n\t\t\tvalue.clear();\n\t\tclassToPane.clear();\n\t\tcommentsList.getChildren().clear();\n\t}\n\n\t/**\n\t * Displays content from a {@link ClassComments} container.\n\t */\n\tprivate class ClassCommentPane extends BorderPane implements CommentUpdateListener {\n\t\tprivate final Label classComment = new Label();\n\t\tprivate final Map<String, Label> memberComments = new ConcurrentHashMap<>();\n\t\tprivate final DelegatingClassComments comments;\n\t\tprivate final ClassPathNode classPath;\n\t\tprivate String fullTextCached;\n\n\t\tprivate ClassCommentPane(@Nonnull ClassComments comments) {\n\t\t\tif (comments instanceof DelegatingClassComments delegatingComments) {\n\t\t\t\tthis.comments = delegatingComments;\n\n\t\t\t\t// Common class level display setup.\n\t\t\t\tclassPath = delegatingComments.getPath();\n\t\t\t\tclassToPane.put(classPath.getValue().getName(), this);\n\t\t\t\tclassComment.setGraphicTextGap(10);\n\t\t\t\tclassComment.getStyleClass().add(Styles.TEXT_SUBTLE);\n\t\t\t\tclassComment.setStyle(\"\"\"\n\t\t\t\t\t\t-fx-border-color: -color-border-default;\n\t\t\t\t\t\t-fx-border-width: 0 0 1 0;\n\t\t\t\t\t\t\"\"\");\n\t\t\t\tsetTop(classComment);\n\t\t\t\tgetStyleClass().add(\"tooltip\");\n\n\t\t\t\t// For searching, hide this pane if the search text is not in any of our comments.\n\t\t\t\tvisibleProperty().bind(searchPane.searchTextProperty()\n\t\t\t\t\t\t.map(search -> search.isBlank() || buildFullText().toLowerCase().contains(search.toLowerCase())));\n\t\t\t\tmanagedProperty().bind(visibleProperty());\n\n\t\t\t\t// Initial layout.\n\t\t\t\trefresh();\n\t\t\t} else {\n\t\t\t\tthrow new IllegalStateException(\"Expected enriched delegate comment model, but got persist model\");\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Full re-population of comment data.\n\t\t */\n\t\tprivate void refresh() {\n\t\t\t// Clear out old comment mappings\n\t\t\tmemberComments.clear();\n\n\t\t\t// Create labels for class + class-comment\n\t\t\tLabel classPreview = new Label();\n\t\t\tNode graphic = cellConfigurationService.graphicOf(classPath);\n\t\t\tclassPreview.setCursor(Cursor.HAND);\n\t\t\tclassPreview.setText(cellConfigurationService.textOf(classPath));\n\t\t\tclassPreview.setGraphic(graphic);\n\t\t\tclassPreview.setOnMousePressed(e -> {\n\t\t\t\ttry {\n\t\t\t\t\tactions.gotoDeclaration(classPath);\n\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\tlogger.error(\"Cannot open comment target: {}\", classPath, ex);\n\t\t\t\t}\n\t\t\t});\n\t\t\tclassComment.setGraphic(classPreview);\n\t\t\tclassComment.setText(filterComment(comments.getClassComment()));\n\n\t\t\t// Create labels for all class members + their comments.\n\t\t\tGridPane body = new GridPane();\n\t\t\tbody.setHgap(10);\n\t\t\tbody.setVgap(10);\n\t\t\tbody.setPadding(new Insets(10));\n\t\t\tClassInfo classInfo = classPath.getValue();\n\t\t\tfor (FieldMember field : classInfo.getFields()) {\n\t\t\t\tString comment = comments.getFieldComment(field);\n\t\t\t\taddMemberComment(comment, classPath.child(field), body);\n\t\t\t}\n\t\t\tfor (MethodMember method : classInfo.getMethods()) {\n\t\t\t\tString comment = comments.getMethodComment(method);\n\t\t\t\taddMemberComment(comment, classPath.child(method), body);\n\t\t\t}\n\t\t\tsetCenter(body);\n\t\t}\n\n\t\tprivate void addMemberComment(@Nullable String comment, @Nonnull ClassMemberPathNode path, @Nonnull GridPane grid) {\n\t\t\tif (comment == null) return;\n\n\t\t\t// Label to show the member name + graphic.\n\t\t\tClassMember member = path.getValue();\n\t\t\tLabel memberPreview = new Label();\n\t\t\tmemberPreview.setCursor(Cursor.HAND);\n\t\t\tmemberPreview.setGraphic(cellConfigurationService.graphicOf(path));\n\t\t\tmemberPreview.setText(cellConfigurationService.textOf(path));\n\t\t\tmemberPreview.setOnMousePressed(e -> {\n\t\t\t\ttry {\n\t\t\t\t\tClassPathNode classPath = Objects.requireNonNull(path.getParent());\n\t\t\t\t\tactions.gotoDeclaration(classPath).requestFocus(member);\n\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\tlogger.error(\"Cannot open comment target: {}\", path, ex);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Label for the actual comment.\n\t\t\tLabel commentPreview = new Label(filterComment(comment));\n\t\t\tcommentPreview.getStyleClass().add(Styles.TEXT_SUBTLE);\n\n\t\t\t// Layout in the grid.\n\t\t\tint row = grid.getRowCount();\n\t\t\tgrid.add(memberPreview, 0, row);\n\t\t\tgrid.add(commentPreview, 1, row);\n\t\t\tmemberComments.put(memberKey(member), commentPreview);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onClassCommentUpdated(@Nonnull ClassPathNode path, @Nullable String comment) {\n\t\t\tif (isApplicableClass(path)) {\n\t\t\t\tfullTextCached = null;\n\t\t\t\tFxThreadUtil.run(() -> classComment.setText(comment));\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic void onFieldCommentUpdated(@Nonnull ClassMemberPathNode path, @Nullable String comment) {\n\t\t\tonMemberCommentUpdated(path, comment);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onMethodCommentUpdated(@Nonnull ClassMemberPathNode path, @Nullable String comment) {\n\t\t\tonMemberCommentUpdated(path, comment);\n\t\t}\n\n\t\tprivate void onMemberCommentUpdated(@Nonnull ClassMemberPathNode path, @Nullable String comment) {\n\t\t\tif (isApplicableClass(path.getParent())) {\n\t\t\t\tfullTextCached = null;\n\t\t\t\tClassMember member = path.getValue();\n\t\t\t\tLabel memberComment = memberComments.get(memberKey(member));\n\t\t\t\tif (comment != null && memberComment != null)\n\t\t\t\t\tFxThreadUtil.run(() -> memberComment.setText(comment));\n\t\t\t\telse\n\t\t\t\t\t// Newly commented members and removed comments, trigger a refresh so things are in order.\n\t\t\t\t\tFxThreadUtil.run(this::refresh);\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * @param path\n\t\t * \t\tPath to check.\n\t\t *\n\t\t * @return {@code true} when the given path matches our {@link #classPath}.\n\t\t */\n\t\tprivate boolean isApplicableClass(@Nullable ClassPathNode path) {\n\t\t\tif (path == null || classPath == null) return false;\n\t\t\treturn path.getValue().getName().equals(classPath.getValue().getName());\n\t\t}\n\n\t\t/**\n\t\t * @param member\n\t\t * \t\tSome member.\n\t\t *\n\t\t * @return Lookup key for {@link #memberComments}.\n\t\t */\n\t\t@Nonnull\n\t\tprivate String memberKey(@Nonnull ClassMember member) {\n\t\t\treturn member.getName() + '.' + member.getDescriptor();\n\t\t}\n\n\t\t/**\n\t\t * @param comment\n\t\t * \t\tInput comment.\n\t\t *\n\t\t * @return Filtered comment with no newlines, length limited.\n\t\t */\n\t\t@Nonnull\n\t\tprivate String filterComment(@Nullable String comment) {\n\t\t\tif (comment == null)\n\t\t\t\treturn \"\";\n\t\t\tcomment = comment.replace('\\n', ' ');\n\t\t\tif (comment.length() > 60)\n\t\t\t\tcomment = comment.substring(0, 60) + \"...\";\n\t\t\treturn comment;\n\t\t}\n\n\t\t/**\n\t\t * @return All comments combined.\n\t\t */\n\t\t@Nonnull\n\t\tprivate String buildFullText() {\n\t\t\tif (fullTextCached == null) {\n\t\t\t\tStringBuilder sb = new StringBuilder();\n\t\t\t\tString comment = comments.getClassComment();\n\t\t\t\tif (comment != null) sb.append(comment);\n\n\t\t\t\tClassInfo classInfo = classPath.getValue();\n\t\t\t\tfor (FieldMember field : classInfo.getFields()) {\n\t\t\t\t\tcomment = comments.getFieldComment(field);\n\t\t\t\t\tif (comment != null) sb.append(comment);\n\t\t\t\t}\n\t\t\t\tfor (MethodMember method : classInfo.getMethods()) {\n\t\t\t\t\tcomment = comments.getMethodComment(method);\n\t\t\t\t\tif (comment != null) sb.append(comment);\n\t\t\t\t}\n\t\t\t\tfullTextCached = sb.toString();\n\t\t\t}\n\t\t\treturn fullTextCached;\n\t\t}\n\n\t\t/**\n\t\t * Clear out UI.\n\t\t */\n\t\tprivate void clear() {\n\t\t\tif (visibleProperty().isBound())\n\t\t\t\tvisibleProperty().unbind();\n\t\t\tsetCenter(null);\n\t\t}\n\t}\n\n\t/**\n\t * Basic search pane.\n\t */\n\tprivate static class CommentSearchPane extends HBox {\n\t\tprivate final TextField searchText = new TextField();\n\n\t\tpublic CommentSearchPane() {\n\t\t\tsearchText.promptTextProperty().bind(Lang.getBinding(\"comments.search.prompt\"));\n\t\t\tHBox.setHgrow(searchText, Priority.ALWAYS);\n\t\t\tsetStyle(\"\"\"\n\t\t\t\t\t-fx-background-color: -color-bg-default;\n\t\t\t\t\t-fx-border-color: -color-border-default;\n\t\t\t\t\t-fx-border-width: 1 0 0 0;\n\t\t\t\t\t\"\"\");\n\t\t\tsetPadding(new Insets(10));\n\t\t\tsetSpacing(10);\n\t\t\tgetChildren().add(searchText);\n\t\t}\n\n\t\t/**\n\t\t * @return Search text.\n\t\t */\n\t\t@Nonnull\n\t\tpublic StringProperty searchTextProperty() {\n\t\t\treturn searchText.textProperty();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/ConfigPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport atlantafx.base.theme.Styles;\nimport atlantafx.base.theme.Tweaks;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.scene.Node;\nimport javafx.scene.control.*;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.VBox;\nimport org.kordamp.ikonli.Ikon;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.config.ConfigValue;\nimport software.coley.recaf.services.config.*;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.FontIconView;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.TreeMap;\n\nimport static software.coley.recaf.config.ConfigGroups.PACKAGE_SPLIT;\nimport static software.coley.recaf.config.ConfigGroups.getGroupPackages;\nimport static software.coley.recaf.util.Lang.getBinding;\n\n/**\n * Pane to display all config values.\n *\n * @author Matt Coley\n * @see ConfigManager Source of values to pull from.\n * @see ConfigComponentManager Controls how to represent {@link ConfigValue} instances.\n * @see ConfigIconManager Controls which icons to show in the tree for {@link ConfigContainer} paths.\n */\n@Dependent\npublic class ConfigPane extends SplitPane implements ManagedConfigListener {\n\tprivate final Map<String, ContainerPane> idToPage = new TreeMap<>();\n\tprivate final Map<String, TreeItem<String>> idToTree = new TreeMap<>();\n\tprivate final TreeItem<String> root = new TreeItem<>(\"root\");\n\tprivate final TreeView<String> tree = new TreeView<>();\n\tprivate final ScrollPane content = new ScrollPane();\n\tprivate final ConfigComponentManager componentManager;\n\tprivate final ConfigIconManager iconManager;\n\n\t@Inject\n\tpublic ConfigPane(@Nonnull ConfigManager configManager,\n\t                  @Nonnull ConfigComponentManager componentManager,\n\t                  @Nonnull ConfigIconManager iconManager) {\n\t\tthis.componentManager = componentManager;\n\t\tthis.iconManager = iconManager;\n\t\tconfigManager.addManagedConfigListener(this);\n\n\t\t// Setup UI\n\t\tinitialize();\n\n\t\t// Initial state from existing containers\n\t\tfor (ConfigContainer container : configManager.getContainers())\n\t\t\tonRegister(container);\n\n\t\t// Select first page\n\t\ttree.getSelectionModel().select(0);\n\t}\n\n\tprivate void initialize() {\n\t\tcontent.setFitToWidth(true);\n\t\troot.setExpanded(true);\n\t\ttree.setShowRoot(false);\n\t\ttree.setRoot(root);\n\t\ttree.getStyleClass().addAll(Tweaks.EDGE_TO_EDGE, Styles.DENSE);\n\t\ttree.setCellFactory(param -> new TreeCell<>() {\n\t\t\t@Override\n\t\t\tprotected void updateItem(String item, boolean empty) {\n\t\t\t\tsuper.updateItem(item, empty);\n\n\t\t\t\tif (empty || item == null) {\n\t\t\t\t\ttextProperty().unbind();\n\t\t\t\t\ttextProperty().set(null);\n\t\t\t\t\tsetGraphic(null);\n\t\t\t\t} else {\n\t\t\t\t\tIkon icon = iconManager.getGroupIcon(item);\n\t\t\t\t\tif (icon == null)\n\t\t\t\t\t\ticon = iconManager.getContainerIcon(item);\n\t\t\t\t\tsetGraphic(new FontIconView(Objects.requireNonNullElse(icon, CarbonIcons.DOT_MARK)));\n\t\t\t\t\ttextProperty().bind(getBinding(item));\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\ttree.getSelectionModel().selectedItemProperty().addListener((ob, old, cur) -> {\n\t\t\tif (cur != null) {\n\t\t\t\tContainerPane page = idToPage.get(cur.getValue());\n\t\t\t\tif (page != null) content.setContent(page);\n\t\t\t\telse content.setContent(new MissingPage(cur.getValue()));\n\t\t\t}\n\t\t});\n\n\t\t// Layout\n\t\tSplitPane.setResizableWithParent(tree, false);\n\t\tgetItems().addAll(tree, content);\n\t\tsetDividerPositions(0.3);\n\t}\n\n\t@Override\n\tpublic void onRegister(@Nonnull ConfigContainer container) {\n\t\t// Skip empty containers\n\t\tif (container.getValues().isEmpty())\n\t\t\treturn;\n\n\t\t// Setup tree structure.\n\t\tTreeItem<String> item = getItem(container, true);\n\t\tif (item != null) {\n\t\t\tString pageKey = item.getValue() + PACKAGE_SPLIT + container.getId();\n\t\t\tTreeItem<String> treeItem = new TreeItem<>(pageKey);\n\t\t\titem.getChildren().add(treeItem);\n\n\t\t\t// Register page.\n\t\t\tidToPage.put(pageKey, new ContainerPane(container));\n\t\t\tidToTree.put(pageKey, treeItem);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void onUnregister(@Nonnull ConfigContainer container) {\n\t\tTreeItem<String> item = getItem(container, false);\n\t\twhile (item != null) {\n\t\t\tTreeItem<String> parent = item.getParent();\n\t\t\tList<TreeItem<String>> children = parent.getChildren();\n\t\t\tchildren.remove(item);\n\n\t\t\t// Determine if the parent also needs to be pruned when it is empty.\n\t\t\tif (children.isEmpty()) item = parent;\n\t\t\telse break;\n\t\t}\n\t}\n\n\t/**\n\t * @param item\n\t * \t\tTree item to look in.\n\t * @param name\n\t * \t\tName to match.\n\t *\n\t * @return Child with {@link TreeItem#getValue()} matching the given name value.\n\t */\n\t@Nullable\n\tprivate TreeItem<String> getChildTreeItemByName(@Nonnull TreeItem<String> item, @Nonnull String name) {\n\t\tfor (TreeItem<String> child : item.getChildren())\n\t\t\tif (name.equals(child.getValue()))\n\t\t\t\treturn child;\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param container\n\t * \t\tContainer to get item of.\n\t * @param createIfMissing\n\t * \t\tFlag to create item if it does not exist.\n\t *\n\t * @return Tree item if it exists. Otherwise {@code null}.\n\t */\n\t@Nullable\n\tprivate TreeItem<String> getItem(@Nonnull ConfigContainer container, boolean createIfMissing) {\n\t\tTreeItem<String> currentItem = root;\n\t\tString currentPackage = null;\n\t\tString[] packages = getGroupPackages(container);\n\t\tfor (String packageName : packages) {\n\t\t\tif (currentPackage == null)\n\t\t\t\tcurrentPackage = packageName;\n\t\t\telse\n\t\t\t\tcurrentPackage += PACKAGE_SPLIT + packageName;\n\n\t\t\t// Get or setup tree item.\n\t\t\tTreeItem<String> child = getChildTreeItemByName(currentItem, currentPackage);\n\t\t\tif (child == null) {\n\t\t\t\tif (createIfMissing) {\n\t\t\t\t\t// No existing tree item, create one.\n\t\t\t\t\tchild = new TreeItem<>(currentPackage);\n\t\t\t\t\tchild.setExpanded(true);\n\n\t\t\t\t\t// Insert child in sorted order.\n\t\t\t\t\tList<String> childrenNames = currentItem.getChildren().stream()\n\t\t\t\t\t\t\t.map(TreeItem::getValue)\n\t\t\t\t\t\t\t.toList();\n\t\t\t\t\tint insertIndex = Lists.sortedInsertIndex(childrenNames, currentPackage);\n\t\t\t\t\tcurrentItem.getChildren().add(insertIndex, child);\n\t\t\t\t} else {\n\t\t\t\t\t// Exit, cannot find item\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcurrentItem = child;\n\t\t}\n\t\treturn currentItem;\n\t}\n\n\t/**\n\t * Page for a single {@link ConfigContainer}.\n\t */\n\tprivate class ContainerPane extends GridPane {\n\t\t@SuppressWarnings({\"rawtypes\", \"unchecked\"})\n\t\tprivate ContainerPane(@Nonnull ConfigContainer container) {\n\t\t\t// Plugin configs are given special treatment.\n\t\t\t// They are not expected to install additional translations, so their ID's will be used as literal names.\n\t\t\tboolean isThirdPartyConfig = ConfigGroups.EXTERNAL.equals(container.getGroup());\n\n\t\t\t// Title\n\t\t\tLabel title = isThirdPartyConfig ? new Label(container.getId()) :\n\t\t\t\t\tnew BoundLabel(getBinding(container.getGroupAndId()));\n\t\t\ttitle.getStyleClass().add(Styles.TITLE_4);\n\t\t\tadd(title, 0, 0, 2, 1);\n\t\t\tadd(new Separator(), 0, 1, 2, 1);\n\n\t\t\t// Values\n\t\t\tMap<String, ConfigValue<?>> values = container.getValues();\n\t\t\tint row = 2;\n\t\t\tfor (Map.Entry<String, ConfigValue<?>> entry : values.entrySet()) {\n\t\t\t\tConfigValue<?> value = entry.getValue();\n\t\t\t\tif (value.isHidden()) continue;\n\t\t\t\tConfigComponentFactory componentFactory = componentManager.getFactory(container, value);\n\t\t\t\tif (componentFactory.isStandAlone()) {\n\t\t\t\t\tadd(componentFactory.create(container, value), 0, row, 2, 1);\n\t\t\t\t} else {\n\t\t\t\t\tString key = container.getScopedId(value);\n\t\t\t\t\tif (isThirdPartyConfig) {\n\t\t\t\t\t\tadd(new Label(value.getId()), 0, row);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tadd(new BoundLabel(getBinding(key)), 0, row);\n\t\t\t\t\t}\n\t\t\t\t\tadd(componentFactory.create(container, value), 1, row);\n\t\t\t\t}\n\t\t\t\trow++;\n\t\t\t}\n\n\t\t\t// Layout\n\t\t\tsetPadding(new Insets(10));\n\t\t\tsetVgap(5);\n\t\t\tsetHgap(5);\n\t\t\tColumnConstraints columnLabel = new ColumnConstraints();\n\t\t\tColumnConstraints columnEditor = new ColumnConstraints();\n\t\t\tcolumnEditor.setHgrow(Priority.ALWAYS);\n\t\t\tgetColumnConstraints().addAll(columnLabel, columnEditor);\n\t\t}\n\t}\n\n\t/**\n\t * Page to show child-pages when the group itself does not have content.\n\t */\n\tprivate class MissingPage extends VBox {\n\t\tpublic MissingPage(@Nonnull String id) {\n\t\t\t// Title\n\t\t\tObservableList<Node> children = getChildren();\n\t\t\tLabel title = new BoundLabel(getBinding(id));\n\t\t\ttitle.getStyleClass().add(Styles.TITLE_4);\n\t\t\tchildren.add(title);\n\t\t\tchildren.add(new Separator());\n\n\t\t\t// Sub-menus\n\t\t\tfor (String key : idToPage.keySet()) {\n\t\t\t\tif (key.startsWith(id)) {\n\t\t\t\t\tHyperlink child = new Hyperlink();\n\t\t\t\t\tchild.textProperty().bind(getBinding(key));\n\t\t\t\t\tchild.setOnAction(e -> {\n\t\t\t\t\t\tTreeItem<String> item = idToTree.get(key);\n\t\t\t\t\t\ttree.getSelectionModel().select(item);\n\t\t\t\t\t});\n\t\t\t\t\tchildren.add(child);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Layout\n\t\t\tsetPadding(new Insets(10));\n\t\t\tsetSpacing(5);\n\t\t\tsetFillWidth(true);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/DocumentationPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\n/**\n * Common type for panels used for documentation.\n *\n * @author Matt Coley\n */\npublic interface DocumentationPane {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/LoggingPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Cursor;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Circle;\nimport javafx.scene.shape.Polygon;\nimport javafx.scene.shape.Shape;\nimport javafx.util.Duration;\nimport org.fxmisc.richtext.CodeArea;\nimport org.slf4j.event.Level;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.LogConsumer;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.file.RecafDirectoriesConfig;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.linegraphics.LineContainer;\nimport software.coley.recaf.ui.control.richtext.linegraphics.LineGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Queue;\nimport java.util.Random;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Pane for displaying logger calls.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class LoggingPane extends BorderPane implements LogConsumer<String> {\n\tprivate final List<LogCallInfo> infos = Collections.synchronizedList(new ArrayList<>());\n\tprivate final Queue<String> messageQueue = new ArrayDeque<>();\n\tprivate final Editor editor = new Editor();\n\tprivate final CodeArea codeArea = editor.getCodeArea();\n\n\t@Inject\n\tpublic LoggingPane(@Nonnull RecafDirectoriesConfig config, @Nonnull SearchBar searchBar) {\n\t\tLogging.addLogConsumer(this);\n\t\tcodeArea.setEditable(false);\n\t\tsearchBar.install(editor);\n\t\teditor.getRootLineGraphicFactory().addLineGraphicFactory(new LoggingLineFactory());\n\t\tsetCenter(editor);\n\n\t\t// Initial line\n\t\tinfos.add(new LogCallInfo(Level.TRACE, null));\n\t\tcodeArea.appendText(\"Current log will write to: \" + StringUtil.pathToAbsoluteString(config.getCurrentLogPath()));\n\n\t\t// We want to reduce the calls to the FX thread, so we will chunk log-appends into groups\n\t\t// occurring every 500ms, which shouldn't be too noticeable, and save us some CPU time.\n\t\tThreadPoolFactory.newScheduledThreadPool(\"logging-pane\", 1, true)\n\t\t\t\t.scheduleAtFixedRate(() -> {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tStringBuilder messageBuilder = new StringBuilder();\n\t\t\t\t\t\tsynchronized (messageQueue) {\n\t\t\t\t\t\t\tif (messageQueue.isEmpty())\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\tString message;\n\t\t\t\t\t\t\twhile ((message = messageQueue.poll()) != null)\n\t\t\t\t\t\t\t\tmessageBuilder.append(message).append('\\n');\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (messageBuilder.length() > 1) {\n\t\t\t\t\t\t\tString collectedMessage = messageBuilder.substring(0, messageBuilder.length() - 1);\n\t\t\t\t\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\t\t\t\t\tcodeArea.appendText(\"\\n\" + collectedMessage);\n\t\t\t\t\t\t\t\tcodeArea.showParagraphAtBottom(codeArea.getParagraphs().size() - 1);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\t// We don't want to cause infinite loops by causing uncaught exceptions to trigger another\n\t\t\t\t\t\t// logger call, so we will just print the trace here and move on.\n\t\t\t\t\t\tt.printStackTrace();\n\t\t\t\t\t}\n\t\t\t\t}, 100, 500, TimeUnit.MILLISECONDS);\n\t}\n\n\t@Override\n\tpublic void accept(@Nonnull String loggerName, @Nonnull Level level, @Nullable String messageContent) {\n\t\taccept(loggerName, level, messageContent, null);\n\t}\n\n\t@Override\n\tpublic void accept(@Nonnull String loggerName, @Nonnull Level level, @Nullable String messageContent, @Nullable Throwable throwable) {\n\t\tif (messageContent == null) {\n\t\t\tif (throwable == null)\n\t\t\t\treturn;\n\t\t\tmessageContent = Objects.requireNonNullElse(throwable.getMessage(), throwable.getClass().getSimpleName());\n\t\t}\n\t\tinfos.add(new LogCallInfo(level, throwable));\n\t\tsynchronized (messageQueue) {\n\t\t\tmessageQueue.add(messageContent);\n\t\t}\n\t}\n\n\tprivate record LogCallInfo(\n\t\t\t@Nonnull Level level,\n\t\t\t@Nullable Throwable throwable) {}\n\n\tprivate class LoggingLineFactory implements LineGraphicFactory {\n\t\tprivate static final Insets PADDING = new Insets(0, 10, 0, 0);\n\t\tprivate static final double SIZE = 4;\n\t\tprivate static final double[] TRIANGLE = {\n\t\t\t\tSIZE, 0, // Size used for circles is radius, so for triangles we want to double positions based on it.\n\t\t\t\tSIZE * 2, SIZE * 2,\n\t\t\t\t0, SIZE * 2\n\t\t};\n\n\t\t@Override\n\t\tpublic int priority() {\n\t\t\treturn -1;\n\t\t}\n\n\t\t@Override\n\t\tpublic void apply(@Nonnull LineContainer container, int paragraph) {\n\t\t\tif (paragraph >= infos.size())\n\t\t\t\treturn;\n\t\t\tLogCallInfo info = infos.get(paragraph);\n\t\t\tShape shape;\n\t\t\tswitch (info.level) {\n\t\t\t\tcase ERROR -> {\n\t\t\t\t\tif (info.throwable == null)\n\t\t\t\t\t\tshape = new Circle(SIZE, Color.RED);\n\t\t\t\t\telse {\n\t\t\t\t\t\tshape = new Polygon(TRIANGLE);\n\t\t\t\t\t\tshape.setFill(Color.RED);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcase WARN -> shape = new Circle(SIZE, Color.YELLOW);\n\t\t\t\tcase INFO -> shape = new Circle(SIZE, Color.LIGHTBLUE);\n\t\t\t\tcase DEBUG -> shape = new Circle(SIZE, Color.CORNFLOWERBLUE);\n\t\t\t\tcase TRACE -> shape = new Circle(SIZE, Color.DODGERBLUE);\n\t\t\t\tdefault -> throw new IllegalArgumentException(\"Unsupported logging level\");\n\t\t\t}\n\t\t\tshape.setOpacity(0.65);\n\n\t\t\t// Wrap and provide right-side padding to give the indicator space between it and the line no.\n\t\t\tHBox wrapper = new HBox(shape);\n\t\t\twrapper.setAlignment(Pos.CENTER);\n\t\t\twrapper.setPadding(PADDING);\n\t\t\twrapper.setCursor(Cursor.HAND);\n\t\t\tif (info.throwable != null) {\n\t\t\t\tTooltip tooltip = new Tooltip(StringUtil.traceToString(info.throwable));\n\t\t\t\ttooltip.setShowDelay(Duration.ZERO);\n\t\t\t\tTooltip.install(wrapper, tooltip);\n\t\t\t}\n\t\t\tcontainer.addHorizontal(wrapper);\n\t\t}\n\n\t\t@Override\n\t\tpublic void install(@Nonnull Editor editor) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic void uninstall(@Nonnull Editor editor) {\n\t\t\t// no-op\n\t\t}\n\t}\n\n\tprivate static class PruneError extends RuntimeException {\n\t\tprivate PruneError() {\n\t\t\tsuper(null, null, false, false);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/MappingApplicationPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport atlantafx.base.controls.Spacer;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.collections.FXCollections;\nimport javafx.geometry.Insets;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.CheckMenuItem;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.MenuButton;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.util.StringConverter;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.info.association.FileTypeSyntaxAssociationService;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.MappingHelper;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.mapping.UniqueKeyMappings;\nimport software.coley.recaf.services.mapping.format.EnigmaMappings;\nimport software.coley.recaf.services.mapping.format.MappingFileFormat;\nimport software.coley.recaf.services.mapping.format.MappingFormatManager;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.config.RecentFilesConfig;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.util.FileChooserBundle;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.awt.Toolkit;\nimport java.io.File;\nimport java.util.List;\n\n@Dependent\npublic class MappingApplicationPane extends BorderPane {\n\tprivate static final Logger logger = Logging.get(MappingApplicationPane.class);\n\tprivate final ObjectProperty<IntermediateMappings> mappingsProperty = new SimpleObjectProperty<>();\n\tprivate final ObjectProperty<MappingFileFormat> mappingFormatProperty = new SimpleObjectProperty<>();\n\tprivate final BooleanProperty ignoreHierarchy = new SimpleBooleanProperty();\n\tprivate final MappingFormatManager formatManager;\n\tprivate final MappingHelper mappingHelper;\n\tprivate final FileTypeSyntaxAssociationService languageAssociation;\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final RecentFilesConfig recentFilesConfig;\n\tprivate final Instance<SearchBar> searchBarProvider;\n\tprivate Runnable applyCallback;\n\n\t@Inject\n\tpublic MappingApplicationPane(@Nonnull MappingFormatManager formatManager,\n\t                              @Nonnull MappingHelper mappingHelper,\n\t                              @Nonnull FileTypeSyntaxAssociationService languageAssociation,\n\t                              @Nonnull WorkspaceManager workspaceManager,\n\t                              @Nonnull RecentFilesConfig recentFilesConfig,\n\t                              @Nonnull Instance<SearchBar> searchBarProvider) {\n\t\tthis.formatManager = formatManager;\n\t\tthis.mappingHelper = mappingHelper;\n\t\tthis.languageAssociation = languageAssociation;\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.recentFilesConfig = recentFilesConfig;\n\t\tthis.searchBarProvider = searchBarProvider;\n\n\t\tsetBottom(createButtonBar());\n\t\tsetCenter(createDisplay());\n\t}\n\n\t@Nonnull\n\tprivate Node createDisplay() {\n\t\t// TODO: Put more in this display\n\t\t//  - Warning of classes in mappings that are not found in workspace\n\t\t//    - Unless the config for 'ignoreHierarchy' is true\n\t\t//  - Preview of class before/after similar to deobfuscation window\n\n\t\tEditor editor = new Editor();\n\t\teditor.getRootLineGraphicFactory().addDefaultCodeGraphicFactories();\n\t\teditor.getCodeArea().setEditable(false);\n\t\teditor.setText(\"No mappings loaded\");\n\t\tlanguageAssociation.configureEditorSyntax(\"enigma\", editor);\n\n\t\tSearchBar searchBar = searchBarProvider.get();\n\t\tsearchBar.install(editor);\n\n\t\tmappingsProperty.addListener((ob, old, cur) -> {\n\t\t\tif (cur == null) {\n\t\t\t\teditor.setText(\"No mappings loaded\");\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tString mappingsText = mappingFormatProperty.get().exportText(cur);\n\t\t\t\t\teditor.setText(mappingsText);\n\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\teditor.setText(\"Failed previewing mappings\\n\" + StringUtil.traceToString(t));\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\treturn editor;\n\t}\n\n\t@Nonnull\n\tprivate HBox createButtonBar() {\n\t\t// Dropdown of available formats\n\t\tList<MappingFileFormat> formats = formatManager.getMappingFileFormats().stream()\n\t\t\t\t.map(formatManager::createFormatInstance)\n\t\t\t\t.toList();\n\t\tComboBox<MappingFileFormat> formatBox = new ComboBox<>(FXCollections.observableList(formats));\n\t\tformatBox.setConverter(new StringConverter<>() {\n\t\t\t@Override\n\t\t\tpublic String toString(MappingFileFormat format) {\n\t\t\t\treturn format.implementationName();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic MappingFileFormat fromString(String s) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t});\n\t\tformatBox.setValue(formats.getFirst());\n\t\tmappingFormatProperty.bind(formatBox.valueProperty());\n\t\tBooleanProperty disableDirImport = new SimpleBooleanProperty();\n\t\tdisableDirImport.bind(formatBox.valueProperty().map(f -> !(f instanceof EnigmaMappings)));\n\n\t\t// Settings menu\n\t\t//  - Allows tweaking of mapping application process parameters\n\t\tCheckMenuItem uniqueKeyed = new CheckMenuItem();\n\t\tignoreHierarchy.bind(uniqueKeyed.selectedProperty());\n\t\tuniqueKeyed.textProperty().bind(Lang.getBinding(\"mapapply.settings.unique\"));\n\t\tMenuButton settingsMenu = new MenuButton(null, new FontIconView(CarbonIcons.SETTINGS), uniqueKeyed);\n\n\t\t// Action buttons\n\t\tFileChooserBundle choosers = FileChooserBundle.fromRecent(recentFilesConfig);\n\t\tButton setMappingFile = new ActionButton(CarbonIcons.DOCUMENT, Lang.getBinding(\"mapapply.pick.file\"), () -> {\n\t\t\t// Show the prompt, load the mappings text ant attempt to load them.\n\t\t\tFile file = choosers.showFileOpen(getScene().getWindow());\n\t\t\tif (file != null) {\n\t\t\t\ttry {\n\t\t\t\t\tIntermediateMappings mappings = mappingHelper.parse(formatBox.getValue(), file.toPath());\n\t\t\t\t\tmappingsProperty.set(mappings);\n\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\tlogger.error(\"Failed importing mappings from {}\", file.getName(), t);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tButton setMappingDir = new ActionButton(CarbonIcons.FOLDER, Lang.getBinding(\"mapapply.pick.dir\"), () -> {\n\t\t\t// Handle implementation specific directory loading\n\t\t\tif (formatBox.getValue() instanceof EnigmaMappings enigmaMappings) {\n\t\t\t\tFile file = choosers.showDirOpen(getScene().getWindow());\n\t\t\t\tif (file != null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tIntermediateMappings mappings = enigmaMappings.parse(file.toPath());\n\t\t\t\t\t\tlogger.info(\"Loaded enigma directory mappings from {}\", file.getName());\n\t\t\t\t\t\tmappingsProperty.set(mappings);\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tlogger.error(\"Failed importing mappings from {}\", file.getName(), t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tToolkit.getDefaultToolkit().beep();\n\t\t\t}\n\t\t});\n\t\tsetMappingFile.disableProperty().bind(mappingFormatProperty.isNull());\n\t\tsetMappingDir.disableProperty().bind(mappingFormatProperty.isNull().or(disableDirImport));\n\t\tButton apply = new ActionButton(CarbonIcons.CAFE, Lang.getBinding(\"mapapply\"), () -> {\n\t\t\ttry {\n\t\t\t\tMappings mappings;\n\t\t\t\tif (ignoreHierarchy.get()) {\n\t\t\t\t\tmappings = new UniqueKeyMappings(mappingsProperty.get());\n\t\t\t\t} else {\n\t\t\t\t\tmappings = mappingsProperty.get();\n\t\t\t\t}\n\n\t\t\t\tmappingHelper.applyMappings(formatBox.getValue(), mappings);\n\t\t\t\tif (applyCallback != null) applyCallback.run();\n\t\t\t} catch (Exception ex) {\n\t\t\t\tlogger.error(\"Failed to read {} mappings\", formatBox.getValue().implementationName(), ex);\n\t\t\t}\n\t\t});\n\t\tapply.disableProperty().bind(mappingsProperty.isNull());\n\n\t\tHBox buttonBar = new HBox(formatBox, setMappingFile, setMappingDir, new Spacer(), apply, settingsMenu);\n\t\tbuttonBar.setSpacing(5);\n\t\tbuttonBar.setPadding(new Insets(5));\n\t\treturn buttonBar;\n\t}\n\n\tpublic void setApplyCallback(@Nullable Runnable applyCallback) {\n\t\tthis.applyCallback = applyCallback;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/MappingGeneratorPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport atlantafx.base.controls.Card;\nimport atlantafx.base.controls.ModalPane;\nimport atlantafx.base.layout.InputGroup;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.annotation.PreDestroy;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.BooleanBinding;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.IntegerProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.ReadOnlyObjectProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleIntegerProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ListView;\nimport javafx.scene.control.MenuButton;\nimport javafx.scene.control.SplitPane;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Pane;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.text.TextAlignment;\nimport javafx.util.StringConverter;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.config.ConfigContainer;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.config.ConfigComponentFactory;\nimport software.coley.recaf.services.config.ConfigComponentManager;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.MappingApplier;\nimport software.coley.recaf.services.mapping.MappingApplierService;\nimport software.coley.recaf.services.mapping.MappingResults;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.mapping.aggregate.AggregateMappingManager;\nimport software.coley.recaf.services.mapping.aggregate.AggregatedMappings;\nimport software.coley.recaf.services.mapping.format.EnigmaMappings;\nimport software.coley.recaf.services.mapping.gen.MappingGenerator;\nimport software.coley.recaf.services.mapping.gen.filter.ExcludeClassesFilter;\nimport software.coley.recaf.services.mapping.gen.filter.ExcludeExistingMappedFilter;\nimport software.coley.recaf.services.mapping.gen.filter.ExcludeModifiersNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.ExcludeNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeClassesFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeKeywordNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeLongNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeModifiersNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeNonAsciiNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeNonJavaIdentifierNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.IncludeWhitespaceNameFilter;\nimport software.coley.recaf.services.mapping.gen.filter.NameGeneratorFilter;\nimport software.coley.recaf.services.mapping.gen.naming.DeconflictingNameGenerator;\nimport software.coley.recaf.services.mapping.gen.naming.IncrementingNameGeneratorProvider;\nimport software.coley.recaf.services.mapping.gen.naming.NameGenerator;\nimport software.coley.recaf.services.mapping.gen.naming.NameGeneratorProvider;\nimport software.coley.recaf.services.mapping.gen.naming.NameGeneratorProviders;\nimport software.coley.recaf.services.search.match.StringPredicate;\nimport software.coley.recaf.services.search.match.StringPredicateProvider;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.LanguageStylesheets;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.ActionMenuItem;\nimport software.coley.recaf.ui.control.BoundCheckBox;\nimport software.coley.recaf.ui.control.BoundComboBox;\nimport software.coley.recaf.ui.control.BoundIntSpinner;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.BoundTextField;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.ReorderableListCell;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.ui.control.richtext.syntax.RegexLanguages;\nimport software.coley.recaf.ui.control.richtext.syntax.RegexSyntaxHighlighter;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.ToStringConverter;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.BiConsumer;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\n\nimport static software.coley.recaf.util.Lang.getBinding;\n\n/**\n * Pane for configurable mapping generation.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class MappingGeneratorPane extends StackPane {\n\tprivate static final Logger logger = Logging.get(MappingGenerator.class);\n\tprivate static final ToStringConverter<String> textPredicateConverter = ToStringConverter.from(MappingGeneratorPane::predicateIdToTranslation);\n\tprivate static final NameGeneratorProvider<?> fallbackProvider = new IncrementingNameGeneratorProvider();\n\tprivate final StringProperty currentProvider = new SimpleStringProperty(IncrementingNameGeneratorProvider.ID);\n\tprivate final ObjectProperty<Mappings> mappingsToApply = new SimpleObjectProperty<>();\n\tprivate final ListView<FilterWithConfigNode<?>> filters = new ListView<>();\n\tprivate final List<String> stringPredicates;\n\tprivate final List<String> stringPredicatesWithNull;\n\tprivate final Workspace workspace;\n\tprivate final NameGeneratorProviders nameGeneratorProviders;\n\tprivate final StringPredicateProvider stringPredicateProvider;\n\tprivate final MappingGenerator mappingGenerator;\n\tprivate final ConfigComponentManager componentManager;\n\tprivate final InheritanceGraph inheritanceGraph;\n\tprivate final ModalPane modal = new ModalPane();\n\tprivate final MappingApplierService mappingApplierService;\n\tprivate final Pane previewGroup;\n\tprivate Runnable applyCallback;\n\n\t@Inject\n\tpublic MappingGeneratorPane(@Nonnull WorkspaceManager workspaceManager,\n\t                            @Nonnull NameGeneratorProviders nameGeneratorProviders,\n\t                            @Nonnull StringPredicateProvider stringPredicateProvider,\n\t                            @Nonnull MappingGenerator mappingGenerator,\n\t                            @Nonnull ConfigComponentManager componentManager,\n\t                            @Nonnull InheritanceGraphService graphService,\n\t                            @Nonnull AggregateMappingManager aggregateMappingManager,\n\t                            @Nonnull MappingApplierService mappingApplierService,\n\t                            @Nonnull Instance<SearchBar> searchBarProvider) {\n\t\tthis.workspace = workspaceManager.getCurrent();\n\t\tthis.nameGeneratorProviders = nameGeneratorProviders;\n\t\tthis.stringPredicateProvider = stringPredicateProvider;\n\t\tthis.mappingGenerator = mappingGenerator;\n\t\tthis.componentManager = componentManager;\n\t\tthis.inheritanceGraph = Objects.requireNonNull(graphService.getCurrentWorkspaceInheritanceGraph(), \"Graph not created\");\n\t\tthis.mappingApplierService = mappingApplierService;\n\n\t\t// Cache text matchers.\n\t\tstringPredicates = stringPredicateProvider.getBiStringMatchers().keySet().stream().sorted().toList();\n\t\tstringPredicatesWithNull = Lists.add(stringPredicates, null);\n\n\t\t// Create filter list and editor controls.\n\t\tNode filterGroup = createFilterDisplay(aggregateMappingManager);\n\n\t\t// Create preview.\n\t\tBorderPane wrapper = new BorderPane();\n\t\tpreviewGroup = wrapper;\n\t\tFxThreadUtil.run(() -> wrapper.setCenter(createPreviewDisplay(searchBarProvider)));\n\n\t\t// Layout and wrap up.\n\t\tSplitPane horizontalWrapper = new SplitPane(filterGroup, previewGroup);\n\t\thorizontalWrapper.setDividerPositions(0.5);\n\t\tSplitPane.setResizableWithParent(filterGroup, false);\n\n\t\tgetChildren().addAll(modal, horizontalWrapper);\n\t}\n\n\t@PreDestroy\n\tprivate void destroy() {\n\t\t// We want to clear out a number of things here to assist in proper GC cleanup.\n\t\t//  - Mappings (which can hold a workspace/graph reference)\n\t\t//  - UI components (which can hold large virtualized text models for the mapping text representation)\n\t\tFxThreadUtil.run(() -> {\n\t\t\tmappingsToApply.setValue(null);\n\t\t\tpreviewGroup.getChildren().clear();\n\t\t\tfilters.getItems().clear();\n\t\t\tgetChildren().clear();\n\t\t});\n\t}\n\n\tpublic void addConfiguredFilter(@Nonnull FilterWithConfigNode<?> filterConfig) {\n\t\tfilters.getItems().add(filterConfig);\n\t}\n\n\t@SuppressWarnings({\"rawtypes\", \"unchecked\"})\n\tprivate void configureGenerator() {\n\t\t// Create the configuration card.\n\t\tCard card = new Card();\n\t\tcard.maxWidthProperty().bind(widthProperty().multiply(0.70));\n\t\tcard.maxHeightProperty().bind(heightProperty().divide(2));\n\n\t\t// Populate it based on available items in the current name provider's config.\n\t\tConfigContainer container = nameGeneratorProviders.getProviders().get(currentProvider.get());\n\t\tif (container != null && !container.getValues().isEmpty()) {\n\t\t\tboolean isThirdPartyConfig = ConfigGroups.EXTERNAL.equals(container.getGroup());\n\t\t\tGridPane grid = createGrid();\n\t\t\tcontainer.getValues().forEach((id, value) -> {\n\t\t\t\tint row = grid.getRowCount();\n\t\t\t\tString key = container.getScopedId(value);\n\t\t\t\tif (isThirdPartyConfig) {\n\t\t\t\t\tgrid.add(new Label(value.getId()), 0, row);\n\t\t\t\t} else {\n\t\t\t\t\tgrid.add(new BoundLabel(getBinding(key)), 0, row);\n\t\t\t\t}\n\t\t\t\tConfigComponentFactory factory = componentManager.getFactory(container, value);\n\t\t\t\tNode node = factory.create(container, value);\n\t\t\t\tgrid.add(node, 1, row);\n\t\t\t});\n\t\t\tcard.setBody(grid);\n\t\t} else {\n\t\t\tBoundLabel label = new BoundLabel(getBinding(\"mapgen.configure.nothing\"));\n\t\t\tlabel.setMaxHeight(Integer.MAX_VALUE);\n\t\t\tlabel.setMaxWidth(Integer.MAX_VALUE);\n\t\t\tlabel.setTextAlignment(TextAlignment.CENTER);\n\t\t\tlabel.setAlignment(Pos.CENTER);\n\t\t\tcard.setBody(label);\n\t\t}\n\n\t\t// Show the configuration card.\n\t\tmodal.show(card);\n\t}\n\n\t/**\n\t * Generates the mappings for the current settings. The result is displayed in the {@link #previewGroup}.\n\t */\n\tpublic void generate() {\n\t\t// Linking items in reverse order from the list.\n\t\tNameGeneratorFilter filter = null;\n\t\tList<FilterWithConfigNode<?>> list = filters.getItems();\n\t\tfor (int i = list.size() - 1; i >= 0; i--) {\n\t\t\tFilterWithConfigNode<?> configurableFilter = list.get(i);\n\t\t\tfilter = configurableFilter.build(filter);\n\t\t}\n\t\tNameGeneratorFilter finalFilter = filter;\n\n\t\t// Generate the mappings\n\t\tpreviewGroup.setDisable(true);\n\t\tCompletableFuture.supplyAsync(() -> {\n\t\t\t// Get provider from current chosen option, or fallback if option is unavailable\n\t\t\tNameGeneratorProvider<?> provider = nameGeneratorProviders.getProviders()\n\t\t\t\t\t.getOrDefault(currentProvider.get(), fallbackProvider);\n\t\t\tNameGenerator generator = provider.createGenerator();\n\n\t\t\t// Pass along the workspace for name deconfliction\n\t\t\tif (generator instanceof DeconflictingNameGenerator deconflictingGenerator)\n\t\t\t\tdeconflictingGenerator.setWorkspace(workspace);\n\n\t\t\t// Generate the mappings\n\t\t\treturn mappingGenerator.generate(workspace, workspace.getPrimaryResource(), inheritanceGraph, generator, finalFilter);\n\t\t}).whenCompleteAsync((mappings, error) -> {\n\t\t\tpreviewGroup.setDisable(false);\n\t\t\tif (mappings != null) {\n\t\t\t\tmappingsToApply.set(mappings);\n\t\t\t} else if (error != null) {\n\t\t\t\tlogger.error(\"Failed to generate mappings\", error);\n\t\t\t}\n\t\t}, FxThreadUtil.executor());\n\t}\n\n\tprivate void apply() {\n\t\tMappings mappings = mappingsToApply.get();\n\t\tif (mappings == null)\n\t\t\treturn;\n\n\t\tMappingApplier applier = mappingApplierService.inCurrentWorkspace();\n\t\tif (applier == null)\n\t\t\treturn;\n\n\t\t// Apply the mappings\n\t\tMappingResults results = applier.applyToPrimaryResource(mappings);\n\t\tresults.apply();\n\n\t\t// Clear property now that the mappings have been applied\n\t\tmappingsToApply.set(null);\n\n\t\t// Notify listeners\n\t\tif (applyCallback != null) applyCallback.run();\n\t}\n\n\t@Nonnull\n\tprivate Node createPreviewDisplay(@Nonnull Instance<SearchBar> searchBarProvider) {\n\t\t// Editor to preview the current mappings\n\t\tEditor editor = new Editor();\n\t\teditor.setText(\"# A preview of your mappings will appear here once generated\");\n\t\teditor.getStylesheets().add(LanguageStylesheets.getEnigmaStylesheet());\n\t\teditor.setSyntaxHighlighter(new RegexSyntaxHighlighter(RegexLanguages.getLangEngimaMap()));\n\t\teditor.getCodeArea().setEditable(false);\n\t\teditor.disableProperty().bind(mappingsToApply.isNull());\n\t\teditor.getStyleClass().add(Styles.BORDER_DEFAULT);\n\t\tsearchBarProvider.get().install(editor);\n\n\t\t// Label describing how much of the workspace will be updated by the current mappings.\n\t\tLabel stats = new Label();\n\t\tstats.textProperty().bind(Lang.getBinding(\"mapgen.preview.empty\"));\n\t\tmappingsToApply.addListener((ob, old, cur) -> {\n\t\t\tif (stats.textProperty().isBound()) stats.textProperty().unbind();\n\t\t\tif (cur == null) {\n\t\t\t\t// Shouldn't happen, but here just in case\n\t\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\t\tstats.textProperty().bind(Lang.getBinding(\"mapgen.preview.empty\"));\n\t\t\t\t\teditor.setText(\"# Nothing\");\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\t// Update stats\n\t\t\t\tIntermediateMappings mappings = cur.exportIntermediate();\n\t\t\t\tint classes = mappings.getClasses().size();\n\t\t\t\tint fields = mappings.getFields().values().stream().mapToInt(List::size).sum();\n\t\t\t\tint methods = mappings.getMethods().values().stream().mapToInt(List::size).sum();\n\t\t\t\tint variables = mappings.getVariables().values().stream().mapToInt(List::size).sum();\n\t\t\t\tString formatted = \"\"\"\n\t\t\t\t\t\tMappings will rename:\n\t\t\t\t\t\t - %d classes\n\t\t\t\t\t\t - %d fields\n\t\t\t\t\t\t - %d methods\n\t\t\t\t\t\t - %d variables\n\t\t\t\t\t\t\"\"\".formatted(classes, fields, methods, variables);\n\t\t\t\tstats.setText(formatted);\n\n\t\t\t\t// Also update editor preview\n\t\t\t\tString mappingText = new EnigmaMappings().exportText(mappings).replace(\"\\0\", \"\");\n\t\t\t\tFxThreadUtil.run(() -> editor.setText(mappingText));\n\t\t\t}\n\t\t});\n\n\t\t// Buttons to generate/apply mappings\n\t\tButton configureGenerator = new ActionButton(new FontIconView(CarbonIcons.SETTINGS, 20), this::configureGenerator);\n\t\tButton generateButton = new ActionButton(Lang.getBinding(\"mapgen.generate\"), this::generate);\n\t\tButton applyButton = new ActionButton(Lang.getBinding(\"mapgen.apply\"), this::apply);\n\t\tList<String> providerIds = nameGeneratorProviders.getProviders().keySet().stream().sorted().collect(Collectors.toList());\n\t\tBoundComboBox<String> generatorCombo = new BoundComboBox<>(currentProvider, providerIds, new StringConverter<>() {\n\t\t\t@Override\n\t\t\tpublic String toString(String s) {\n\t\t\t\treturn StringUtil.uppercaseFirstChar(s).replace('-', ' ');\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic String fromString(String s) {\n\t\t\t\treturn s;\n\t\t\t}\n\t\t});\n\t\tgeneratorCombo.getStyleClass().add(\"dark-combo-box\");\n\t\tgenerateButton.setMaxWidth(Double.MAX_VALUE);\n\t\tapplyButton.setMaxWidth(Double.MAX_VALUE);\n\t\tHBox.setHgrow(applyButton, Priority.ALWAYS);\n\t\tapplyButton.disableProperty().bind(mappingsToApply.isNull());\n\n\t\t// Layout\n\t\tInputGroup inputGroup = new InputGroup(configureGenerator, generateButton, generatorCombo);\n\t\tHBox.setHgrow(generateButton, Priority.ALWAYS);\n\t\tHBox.setHgrow(inputGroup, Priority.ALWAYS);\n\t\tHBox buttons = new HBox(inputGroup, applyButton);\n\t\tbuttons.setSpacing(10);\n\t\tVBox layout = new VBox(editor, stats, buttons);\n\t\tVBox.setVgrow(editor, Priority.ALWAYS);\n\t\tlayout.setSpacing(10);\n\t\treturn layout;\n\t}\n\n\t@Nonnull\n\tprivate Node createFilterDisplay(@Nonnull AggregateMappingManager aggregateMappingManager) {\n\t\t// List to house current filters.\n\t\tfilters.setCellFactory(param -> new ConfiguredFilterCell());\n\t\tfilters.getItems().add(new ExcludeExistingMapped(Objects.requireNonNull(aggregateMappingManager.getAggregatedMappings())));\n\t\tReadOnlyObjectProperty<FilterWithConfigNode<?>> selectedItem = filters.getSelectionModel().selectedItemProperty();\n\t\tBooleanBinding hasItemSelection = selectedItem.isNull();\n\n\t\t// Button to add new filters to the list.\n\t\t// - Pops open a modal to configure the filter, and an OK button to commit it.\n\t\tObjectProperty<Supplier<FilterWithConfigNode<?>>> nodeSupplier = new SimpleObjectProperty<>();\n\t\tButton addFilter = new ActionButton(CarbonIcons.ADD_ALT, Lang.getBinding(\"mapgen.filters.add\"), () -> {\n\t\t\tFilterWithConfigNode<?> filterConfig = nodeSupplier.get().get();\n\t\t\tshowConfigurator(filterConfig, () -> addConfiguredFilter(filterConfig));\n\t\t});\n\t\taddFilter.disableProperty().bind(nodeSupplier.isNull());\n\n\t\t// Button to edit the selected filter.\n\t\tButton editSelectedFilter = new ActionButton(CarbonIcons.EDIT, Lang.getBinding(\"mapgen.filters.edit\"), () -> {\n\t\t\tFilterWithConfigNode<?> filterConfig = selectedItem.get();\n\t\t\tshowConfigurator(filterConfig, null);\n\t\t});\n\t\tBooleanProperty isZero = new SimpleBooleanProperty();\n\t\tselectedItem.addListener((ob, old, cur) -> isZero.set(cur == null || cur.getInputCount() == 0));\n\t\teditSelectedFilter.disableProperty().bind(hasItemSelection.or(isZero));\n\n\t\t// Button to remove the selected filter.\n\t\tButton deleteSelectedFilter = new ActionButton(CarbonIcons.TRASH_CAN, Lang.getBinding(\"mapgen.filters.delete\"), () -> {\n\t\t\tfilters.getItems().remove(selectedItem.get());\n\t\t});\n\t\tdeleteSelectedFilter.disableProperty().bind(hasItemSelection);\n\n\t\t// Dropdown to change the type of filter made with the add button.\n\t\tMenuButton dropdown = new MenuButton();\n\t\tStringProperty dropdownText = dropdown.textProperty();\n\t\tdropdownText.bind(Lang.getBinding(\"mapgen.filters.type\"));\n\t\tdropdown.getItems().addAll(\n\t\t\t\ttypeSetAction(nodeSupplier, dropdownText, \"mapgen.filter.excludealreadymapped\",\n\t\t\t\t\t\t() -> new ExcludeExistingMapped(Objects.requireNonNull(aggregateMappingManager.getAggregatedMappings()))),\n\t\t\t\ttypeSetAction(nodeSupplier, dropdownText, \"mapgen.filter.excludename\", ExcludeName::new),\n\t\t\t\ttypeSetAction(nodeSupplier, dropdownText, \"mapgen.filter.excludeclasses\", ExcludeClasses::new),\n\t\t\t\ttypeSetAction(nodeSupplier, dropdownText, \"mapgen.filter.excludemodifier\", ExcludeModifiers::new),\n\t\t\t\ttypeSetAction(nodeSupplier, dropdownText, \"mapgen.filter.includename\", IncludeName::new),\n\t\t\t\ttypeSetAction(nodeSupplier, dropdownText, \"mapgen.filter.includelong\", IncludeLongName::new),\n\t\t\t\ttypeSetAction(nodeSupplier, dropdownText, \"mapgen.filter.includeclasses\", IncludeClasses::new),\n\t\t\t\ttypeSetAction(nodeSupplier, dropdownText, \"mapgen.filter.includemodifier\", IncludeModifiers::new),\n\t\t\t\ttypeSetAction(nodeSupplier, dropdownText, \"mapgen.filter.includewhitespacenames\", IncludeWhitespaceNames::new),\n\t\t\t\ttypeSetAction(nodeSupplier, dropdownText, \"mapgen.filter.includenonasciinames\", IncludeNonAsciiNames::new),\n\t\t\t\ttypeSetAction(nodeSupplier, dropdownText, \"mapgen.filter.includenonjavaidentifiers\", IncludeNonJavaIdentifierNames::new),\n\t\t\t\ttypeSetAction(nodeSupplier, dropdownText, \"mapgen.filter.includekeywords\", IncludeKeywordNames::new)\n\t\t);\n\n\t\t// Layout\n\t\taddFilter.setMaxWidth(Double.MAX_VALUE);\n\t\teditSelectedFilter.setMaxWidth(Double.MAX_VALUE);\n\t\tdeleteSelectedFilter.setMaxWidth(Double.MAX_VALUE);\n\t\tHBox.setHgrow(addFilter, Priority.ALWAYS);\n\t\tHBox.setHgrow(editSelectedFilter, Priority.ALWAYS);\n\t\tHBox.setHgrow(deleteSelectedFilter, Priority.ALWAYS);\n\t\tVBox filterButtons = new VBox(new InputGroup(dropdown, addFilter),\n\t\t\t\tnew HBox(editSelectedFilter, deleteSelectedFilter)\n\t\t);\n\t\tfilterButtons.setFillWidth(true);\n\t\tVBox layout = new VBox(filters, filterButtons);\n\t\tVBox.setVgrow(filters, Priority.ALWAYS);\n\t\treturn layout;\n\t}\n\n\t@Nonnull\n\tprivate ActionMenuItem typeSetAction(@Nonnull ObjectProperty<Supplier<FilterWithConfigNode<?>>> nodeSupplier,\n\t                                     @Nonnull StringProperty parentText,\n\t                                     @Nonnull String translationKey,\n\t                                     @Nonnull Supplier<FilterWithConfigNode<?>> supplier) {\n\t\tStringBinding nameBinding = Lang.getBinding(translationKey);\n\t\treturn new ActionMenuItem(nameBinding,\n\t\t\t\t() -> {\n\t\t\t\t\tnodeSupplier.set(supplier);\n\t\t\t\t\tparentText.unbind();\n\t\t\t\t\tparentText.bind(nameBinding);\n\t\t\t\t});\n\t}\n\n\tprivate void showConfigurator(@Nonnull FilterWithConfigNode<?> filterConfig, @Nullable Runnable onDone) {\n\t\tNode configurator = filterConfig.getConfigurator();\n\t\tint inputCount = filterConfig.getInputCount();\n\t\tif (inputCount > 0) {\n\t\t\t// At least one input, show them via the modal\n\t\t\tLabel title = new BoundLabel(filterConfig.display());\n\t\t\ttitle.getStyleClass().add(Styles.TITLE_3);\n\t\t\tVBox layout = new VBox(title, configurator, new ActionButton(Lang.getBinding(\"misc.done\"), () -> {\n\t\t\t\tmodal.hide(true);\n\t\t\t\tif (onDone != null)\n\t\t\t\t\tonDone.run();\n\t\t\t}));\n\t\t\tlayout.setSpacing(10);\n\t\t\tlayout.setFillWidth(true);\n\t\t\tlayout.setAlignment(Pos.CENTER);\n\t\t\tCard card = new Card();\n\t\t\tcard.maxWidthProperty().bind(widthProperty().multiply(0.70));\n\t\t\tcard.maxHeightProperty().bind(heightProperty().divide(2));\n\t\t\tcard.setBody(layout);\n\t\t\tmodal.show(card);\n\t\t\tconfigurator.requestFocus();\n\t\t} else {\n\t\t\t// No inputs, just do the action\n\t\t\tif (onDone != null)\n\t\t\t\tonDone.run();\n\t\t}\n\t}\n\n\t/**\n\t * @param applyCallback\n\t * \t\tCallback to run when the generated mapping are successfully applied.\n\t */\n\tpublic void setApplyCallback(@Nullable Runnable applyCallback) {\n\t\tthis.applyCallback = applyCallback;\n\t}\n\n\t@Nonnull\n\tprivate static String predicateIdToTranslation(@Nullable String id) {\n\t\tif (id == null) return Lang.get(\"misc.ignored\");\n\t\treturn Lang.get(StringPredicate.TRANSLATION_PREFIX + id);\n\t}\n\n\t@Nonnull\n\tprivate static GridPane createGrid() {\n\t\tGridPane grid = new GridPane();\n\t\tgrid.setAlignment(Pos.CENTER);\n\t\tgrid.setVgap(5);\n\t\tgrid.setHgap(5);\n\t\tgrid.setPadding(new Insets(5));\n\t\treturn grid;\n\t}\n\n\t/**\n\t * Config node for {@link ExcludeNameFilter}.\n\t */\n\tpublic class ExcludeName extends FilterWithConfigNode<ExcludeNameFilter> {\n\t\tprivate final StringProperty classPredicateId = new SimpleStringProperty();\n\t\tprivate final StringProperty fieldPredicateId = new SimpleStringProperty();\n\t\tprivate final StringProperty methodPredicateId = new SimpleStringProperty();\n\t\tprivate final StringProperty variablePredicateId = new SimpleStringProperty();\n\t\tprivate final StringProperty className = new SimpleStringProperty(\"com/example/Foo\");\n\t\tprivate final StringProperty fieldName = new SimpleStringProperty(\"foo\");\n\t\tprivate final StringProperty methodName = new SimpleStringProperty(\"getFoo\");\n\t\tprivate final StringProperty variableName = new SimpleStringProperty(\"fizz\");\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic ObservableValue<String> display() {\n\t\t\treturn classPredicateId.isNotNull().flatMap(hasClass -> {\n\t\t\t\tif (hasClass)\n\t\t\t\t\treturn Bindings.concat(Lang.getBinding(\"mapgen.filter.excludename\"), \": \", classPredicateId.flatMap(key -> {\n\t\t\t\t\t\tif (StringPredicateProvider.KEY_ANYTHING.equals(key))\n\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.anything\");\n\t\t\t\t\t\telse if (StringPredicateProvider.KEY_NOTHING.equals(key))\n\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.zilch\");\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\treturn className;\n\t\t\t\t\t}));\n\t\t\t\treturn fieldPredicateId.isNotNull().flatMap(hasField -> {\n\t\t\t\t\tif (hasField)\n\t\t\t\t\t\treturn Bindings.concat(Lang.getBinding(\"mapgen.filter.excludename\"), \": \", fieldPredicateId.flatMap(key -> {\n\t\t\t\t\t\t\tif (StringPredicateProvider.KEY_ANYTHING.equals(key))\n\t\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.anything\");\n\t\t\t\t\t\t\telse if (StringPredicateProvider.KEY_NOTHING.equals(key))\n\t\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.zilch\");\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\treturn fieldName;\n\t\t\t\t\t\t}));\n\t\t\t\t\treturn methodPredicateId.isNotNull().flatMap(hasMethod -> {\n\t\t\t\t\t\tif (hasMethod)\n\t\t\t\t\t\t\treturn Bindings.concat(Lang.getBinding(\"mapgen.filter.excludename\"), \": \", methodPredicateId.flatMap(key -> {\n\t\t\t\t\t\t\t\tif (StringPredicateProvider.KEY_ANYTHING.equals(key))\n\t\t\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.anything\");\n\t\t\t\t\t\t\t\telse if (StringPredicateProvider.KEY_NOTHING.equals(key))\n\t\t\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.zilch\");\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t\treturn methodName;\n\t\t\t\t\t\t\t}));\n\t\t\t\t\t\treturn variablePredicateId.isNotNull().flatMap(hasVariable -> {\n\t\t\t\t\t\t\tif (hasVariable)\n\t\t\t\t\t\t\t\treturn Bindings.concat(Lang.getBinding(\"mapgen.filter.excludename\"), \": \", variablePredicateId.flatMap(key -> {\n\t\t\t\t\t\t\t\t\tif (StringPredicateProvider.KEY_ANYTHING.equals(key))\n\t\t\t\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.anything\");\n\t\t\t\t\t\t\t\t\telse if (StringPredicateProvider.KEY_NOTHING.equals(key))\n\t\t\t\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.zilch\");\n\t\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t\t\treturn variableName;\n\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\treturn Lang.getBinding(\"misc.ignored\");\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected Function<NameGeneratorFilter, ExcludeNameFilter> makeProvider() {\n\t\t\treturn next -> new ExcludeNameFilter(next,\n\t\t\t\t\tclassPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(classPredicateId.get(), className.get()) : null,\n\t\t\t\t\tfieldPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(fieldPredicateId.get(), fieldName.get()) : null,\n\t\t\t\t\tmethodPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(methodPredicateId.get(), methodName.get()) : null,\n\t\t\t\t\tvariablePredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(variablePredicateId.get(), variableName.get()) : null\n\t\t\t);\n\t\t}\n\n\t\t@Override\n\t\tprotected void fillConfigurator(@Nonnull BiConsumer<StringBinding, Node> sink) {\n\t\t\tBoundTextField txtClass = new BoundTextField(className);\n\t\t\tBoundTextField txtField = new BoundTextField(fieldName);\n\t\t\tBoundTextField txtMethod = new BoundTextField(methodName);\n\t\t\tBoundTextField txtVariable = new BoundTextField(variableName);\n\t\t\ttxtClass.disableProperty().bind(classPredicateId.isNull()\n\t\t\t\t\t.or(classPredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING))\n\t\t\t\t\t.or(classPredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING)));\n\t\t\ttxtField.disableProperty().bind(fieldPredicateId.isNull()\n\t\t\t\t\t.or(fieldPredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING))\n\t\t\t\t\t.or(fieldPredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING)));\n\t\t\ttxtMethod.disableProperty().bind(methodPredicateId.isNull()\n\t\t\t\t\t.or(methodPredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING))\n\t\t\t\t\t.or(methodPredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING)));\n\t\t\ttxtVariable.disableProperty().bind(variablePredicateId.isNull()\n\t\t\t\t\t.or(variablePredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING))\n\t\t\t\t\t.or(variablePredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING)));\n\n\t\t\tGridPane grid = new GridPane();\n\t\t\tgrid.setVgap(5);\n\t\t\tgrid.setHgap(5);\n\t\t\tgrid.addRow(0, new BoundLabel(Lang.getBinding(\"mapgen.filter.owner-name\")),\n\t\t\t\t\ttxtClass, new BoundComboBox<>(classPredicateId, stringPredicatesWithNull, textPredicateConverter));\n\t\t\tgrid.addRow(1, new BoundLabel(Lang.getBinding(\"mapgen.filter.field-name\")),\n\t\t\t\t\ttxtField, new BoundComboBox<>(fieldPredicateId, stringPredicatesWithNull, textPredicateConverter));\n\t\t\tgrid.addRow(2, new BoundLabel(Lang.getBinding(\"mapgen.filter.method-name\")),\n\t\t\t\t\ttxtMethod, new BoundComboBox<>(methodPredicateId, stringPredicatesWithNull, textPredicateConverter));\n\t\t\tgrid.addRow(3, new BoundLabel(Lang.getBinding(\"mapgen.filter.variable-name\")),\n\t\t\t\t\ttxtVariable, new BoundComboBox<>(variablePredicateId, stringPredicatesWithNull, textPredicateConverter));\n\t\t\tsink.accept(null, grid);\n\t\t}\n\t}\n\n\t/**\n\t * Config node for {@link ExcludeClassesFilter}.\n\t */\n\tpublic class ExcludeClasses extends FilterWithConfigNode<ExcludeClassesFilter> {\n\t\tprivate final StringProperty classPredicateId = new SimpleStringProperty(StringPredicateProvider.KEY_CONTAINS);\n\t\tprivate final StringProperty className = new SimpleStringProperty(\"com/example/Foo\");\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic ObservableValue<String> display() {\n\t\t\treturn Bindings.concat(Lang.getBinding(\"mapgen.filter.excludeclasses\"), \": \", className);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\t@SuppressWarnings(\"DataFlowIssue\")\n\t\tprotected Function<NameGeneratorFilter, ExcludeClassesFilter> makeProvider() {\n\t\t\treturn next -> {\n\t\t\t\tString id = classPredicateId.get();\n\t\t\t\tStringPredicate predicate;\n\t\t\t\tif (id == null) predicate = stringPredicateProvider.newNothingPredicate();\n\t\t\t\telse predicate = stringPredicateProvider.newBiStringPredicate(id, className.get());\n\t\t\t\treturn new ExcludeClassesFilter(next, predicate);\n\t\t\t};\n\t\t}\n\n\t\t@Override\n\t\tprotected void fillConfigurator(@Nonnull BiConsumer<StringBinding, Node> sink) {\n\t\t\tBoundTextField txtClass = new BoundTextField(className);\n\t\t\tsink.accept(Lang.getBinding(\"search.textmode\"), new BoundComboBox<>(classPredicateId, stringPredicates, textPredicateConverter));\n\t\t\tsink.accept(Lang.getBinding(\"mapgen.filter.class-name\"), txtClass);\n\t\t}\n\t}\n\n\t/**\n\t * Config node for {@link ExcludeExistingMappedFilter}.\n\t */\n\tpublic static class ExcludeExistingMapped extends FilterWithConfigNode<ExcludeExistingMappedFilter> {\n\t\tprivate final AggregatedMappings aggregate;\n\n\t\tprivate ExcludeExistingMapped(@Nonnull AggregatedMappings aggregate) {\n\t\t\tthis.aggregate = aggregate;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic ObservableValue<String> display() {\n\t\t\treturn Lang.getBinding(\"mapgen.filter.excludealreadymapped\");\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected Function<NameGeneratorFilter, ExcludeExistingMappedFilter> makeProvider() {\n\t\t\treturn next -> new ExcludeExistingMappedFilter(next, aggregate);\n\t\t}\n\n\t\t@Override\n\t\tprotected void fillConfigurator(@Nonnull BiConsumer<StringBinding, Node> sink) {\n\t\t}\n\t}\n\n\t/**\n\t * Config node for {@link ExcludeModifiersNameFilter}.\n\t */\n\tpublic static class ExcludeModifiers extends FilterWithConfigNode<ExcludeModifiersNameFilter> {\n\t\tprivate final StringProperty modifiers = new SimpleStringProperty(\"public protected\");\n\t\tprivate final BooleanProperty classes = new SimpleBooleanProperty(true);\n\t\tprivate final BooleanProperty fields = new SimpleBooleanProperty(true);\n\t\tprivate final BooleanProperty methods = new SimpleBooleanProperty(true);\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic ObservableValue<String> display() {\n\t\t\treturn Bindings.concat(Lang.getBinding(\"mapgen.filter.excludemodifier\"), \": \", modifiers.map(text -> {\n\t\t\t\tString[] words = text.split(\"\\\\s+\");\n\t\t\t\treturn Arrays.stream(words)\n\t\t\t\t\t\t.filter(word -> !AccessFlag.getFlags(word).isEmpty())\n\t\t\t\t\t\t.collect(Collectors.joining(\" \"));\n\t\t\t}));\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected Function<NameGeneratorFilter, ExcludeModifiersNameFilter> makeProvider() {\n\t\t\treturn next -> {\n\t\t\t\tCollection<Integer> flags = AccessFlag.getFlags(modifiers.get()).stream()\n\t\t\t\t\t\t.map(AccessFlag::getMask)\n\t\t\t\t\t\t.collect(Collectors.toList());\n\t\t\t\treturn new ExcludeModifiersNameFilter(next, flags, classes.get(), fields.get(), methods.get());\n\t\t\t};\n\t\t}\n\n\t\t@Override\n\t\tprotected void fillConfigurator(@Nonnull BiConsumer<StringBinding, Node> sink) {\n\t\t\tsink.accept(Lang.getBinding(\"mapgen.filter.excludemodifier\"), new BoundTextField(modifiers)\n\t\t\t\t\t.withTooltip(\"mapgen.filter.modifiers.tooltip\"));\n\t\t\tsink.accept(null, new BoundCheckBox(Lang.getBinding(\"mapgen.filter.excludeclass\"), classes));\n\t\t\tsink.accept(null, new BoundCheckBox(Lang.getBinding(\"mapgen.filter.excludefield\"), fields));\n\t\t\tsink.accept(null, new BoundCheckBox(Lang.getBinding(\"mapgen.filter.excludemethod\"), methods));\n\t\t}\n\t}\n\n\t/**\n\t * Config node for {@link IncludeNameFilter}.\n\t */\n\tpublic class IncludeName extends FilterWithConfigNode<IncludeNameFilter> {\n\t\tprivate final StringProperty classPredicateId = new SimpleStringProperty();\n\t\tprivate final StringProperty fieldPredicateId = new SimpleStringProperty();\n\t\tprivate final StringProperty methodPredicateId = new SimpleStringProperty();\n\t\tprivate final StringProperty variablePredicateId = new SimpleStringProperty();\n\t\tprivate final StringProperty className = new SimpleStringProperty(\"com/example/Foo\");\n\t\tprivate final StringProperty fieldName = new SimpleStringProperty(\"foo\");\n\t\tprivate final StringProperty methodName = new SimpleStringProperty(\"getFoo\");\n\t\tprivate final StringProperty variableName = new SimpleStringProperty(\"fizz\");\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic ObservableValue<String> display() {\n\t\t\treturn classPredicateId.isNotNull().flatMap(hasClass -> {\n\t\t\t\tif (hasClass)\n\t\t\t\t\treturn Bindings.concat(Lang.getBinding(\"mapgen.filter.includename\"), \": \", classPredicateId.flatMap(key -> {\n\t\t\t\t\t\tif (StringPredicateProvider.KEY_ANYTHING.equals(key))\n\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.anything\");\n\t\t\t\t\t\telse if (StringPredicateProvider.KEY_NOTHING.equals(key))\n\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.zilch\");\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\treturn className;\n\t\t\t\t\t}));\n\t\t\t\treturn fieldPredicateId.isNotNull().flatMap(hasField -> {\n\t\t\t\t\tif (hasField)\n\t\t\t\t\t\treturn Bindings.concat(Lang.getBinding(\"mapgen.filter.includename\"), \": \", fieldPredicateId.flatMap(key -> {\n\t\t\t\t\t\t\tif (StringPredicateProvider.KEY_ANYTHING.equals(key))\n\t\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.anything\");\n\t\t\t\t\t\t\telse if (StringPredicateProvider.KEY_NOTHING.equals(key))\n\t\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.zilch\");\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\treturn fieldName;\n\t\t\t\t\t\t}));\n\t\t\t\t\treturn methodPredicateId.isNotNull().flatMap(hasMethod -> {\n\t\t\t\t\t\tif (hasMethod)\n\t\t\t\t\t\t\treturn Bindings.concat(Lang.getBinding(\"mapgen.filter.includename\"), \": \", methodPredicateId.flatMap(key -> {\n\t\t\t\t\t\t\t\tif (StringPredicateProvider.KEY_ANYTHING.equals(key))\n\t\t\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.anything\");\n\t\t\t\t\t\t\t\telse if (StringPredicateProvider.KEY_NOTHING.equals(key))\n\t\t\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.zilch\");\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t\treturn methodName;\n\t\t\t\t\t\t\t}));\n\t\t\t\t\t\treturn variablePredicateId.isNotNull().flatMap(hasVariable -> {\n\t\t\t\t\t\t\tif (hasVariable)\n\t\t\t\t\t\t\t\treturn Bindings.concat(Lang.getBinding(\"mapgen.filter.includename\"), \": \", variablePredicateId.flatMap(key -> {\n\t\t\t\t\t\t\t\t\tif (StringPredicateProvider.KEY_ANYTHING.equals(key))\n\t\t\t\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.anything\");\n\t\t\t\t\t\t\t\t\telse if (StringPredicateProvider.KEY_NOTHING.equals(key))\n\t\t\t\t\t\t\t\t\t\treturn Lang.getBinding(\"string.match.zilch\");\n\t\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t\t\treturn variableName;\n\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\treturn Lang.getBinding(\"misc.ignored\");\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected Function<NameGeneratorFilter, IncludeNameFilter> makeProvider() {\n\t\t\treturn next -> new IncludeNameFilter(next,\n\t\t\t\t\tclassPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(classPredicateId.get(), className.get()) : null,\n\t\t\t\t\tfieldPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(fieldPredicateId.get(), fieldName.get()) : null,\n\t\t\t\t\tmethodPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(methodPredicateId.get(), methodName.get()) : null,\n\t\t\t\t\tvariablePredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(variablePredicateId.get(), variableName.get()) : null\n\t\t\t);\n\t\t}\n\n\t\t@Override\n\t\tprotected void fillConfigurator(@Nonnull BiConsumer<StringBinding, Node> sink) {\n\t\t\tBoundTextField txtClass = new BoundTextField(className);\n\t\t\tBoundTextField txtField = new BoundTextField(fieldName);\n\t\t\tBoundTextField txtMethod = new BoundTextField(methodName);\n\t\t\tBoundTextField txtVariable = new BoundTextField(variableName);\n\t\t\ttxtClass.disableProperty().bind(classPredicateId.isNull()\n\t\t\t\t\t.or(classPredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING))\n\t\t\t\t\t.or(classPredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING)));\n\t\t\ttxtField.disableProperty().bind(fieldPredicateId.isNull()\n\t\t\t\t\t.or(fieldPredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING))\n\t\t\t\t\t.or(fieldPredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING)));\n\t\t\ttxtMethod.disableProperty().bind(methodPredicateId.isNull()\n\t\t\t\t\t.or(methodPredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING))\n\t\t\t\t\t.or(methodPredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING)));\n\t\t\ttxtVariable.disableProperty().bind(variablePredicateId.isNull()\n\t\t\t\t\t.or(variablePredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING))\n\t\t\t\t\t.or(variablePredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING)));\n\n\t\t\tGridPane grid = new GridPane();\n\t\t\tgrid.setVgap(5);\n\t\t\tgrid.setHgap(5);\n\t\t\tgrid.addRow(0, new BoundLabel(Lang.getBinding(\"mapgen.filter.owner-name\")),\n\t\t\t\t\ttxtClass, new BoundComboBox<>(classPredicateId, stringPredicatesWithNull, textPredicateConverter));\n\t\t\tgrid.addRow(1, new BoundLabel(Lang.getBinding(\"mapgen.filter.field-name\")),\n\t\t\t\t\ttxtField, new BoundComboBox<>(fieldPredicateId, stringPredicatesWithNull, textPredicateConverter));\n\t\t\tgrid.addRow(2, new BoundLabel(Lang.getBinding(\"mapgen.filter.method-name\")),\n\t\t\t\t\ttxtMethod, new BoundComboBox<>(methodPredicateId, stringPredicatesWithNull, textPredicateConverter));\n\t\t\tgrid.addRow(3, new BoundLabel(Lang.getBinding(\"mapgen.filter.variable-name\")),\n\t\t\t\t\ttxtVariable, new BoundComboBox<>(variablePredicateId, stringPredicatesWithNull, textPredicateConverter));\n\t\t\tsink.accept(null, grid);\n\t\t}\n\t}\n\n\t/**\n\t * Config node for {@link IncludeClassesFilter}.\n\t */\n\tpublic class IncludeClasses extends FilterWithConfigNode<IncludeClassesFilter> {\n\t\tprivate final StringProperty classPredicateId = new SimpleStringProperty(StringPredicateProvider.KEY_CONTAINS);\n\t\tprivate final StringProperty className = new SimpleStringProperty(\"com/example/Foo\");\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic ObservableValue<String> display() {\n\t\t\treturn Bindings.concat(Lang.getBinding(\"mapgen.filter.includeclasses\"), \": \", className);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\t@SuppressWarnings(\"DataFlowIssue\")\n\t\tprotected Function<NameGeneratorFilter, IncludeClassesFilter> makeProvider() {\n\t\t\treturn next -> {\n\t\t\t\tString id = classPredicateId.get();\n\t\t\t\tStringPredicate predicate;\n\t\t\t\tif (id == null) predicate = stringPredicateProvider.newNothingPredicate();\n\t\t\t\telse predicate = stringPredicateProvider.newBiStringPredicate(id, className.get());\n\t\t\t\treturn new IncludeClassesFilter(next, predicate);\n\t\t\t};\n\t\t}\n\n\t\t@Override\n\t\tprotected void fillConfigurator(@Nonnull BiConsumer<StringBinding, Node> sink) {\n\t\t\tBoundTextField txtClass = new BoundTextField(className);\n\t\t\tsink.accept(Lang.getBinding(\"search.textmode\"), new BoundComboBox<>(classPredicateId, stringPredicates, textPredicateConverter));\n\t\t\tsink.accept(Lang.getBinding(\"mapgen.filter.class-name\"), txtClass);\n\t\t}\n\t}\n\n\t/**\n\t * Config node for {@link IncludeModifiersNameFilter}.\n\t */\n\tpublic static class IncludeModifiers extends FilterWithConfigNode<IncludeModifiersNameFilter> {\n\t\tprivate final StringProperty modifiers = new SimpleStringProperty(\"public protected\");\n\t\tprivate final BooleanProperty classes = new SimpleBooleanProperty(true);\n\t\tprivate final BooleanProperty fields = new SimpleBooleanProperty(true);\n\t\tprivate final BooleanProperty methods = new SimpleBooleanProperty(true);\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic ObservableValue<String> display() {\n\t\t\treturn Bindings.concat(Lang.getBinding(\"mapgen.filter.includemodifier\"), \": \", modifiers.map(text -> {\n\t\t\t\tString[] words = text.split(\"\\\\s+\");\n\t\t\t\treturn Arrays.stream(words)\n\t\t\t\t\t\t.filter(word -> !AccessFlag.getFlags(word).isEmpty())\n\t\t\t\t\t\t.collect(Collectors.joining(\" \"));\n\t\t\t}));\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected Function<NameGeneratorFilter, IncludeModifiersNameFilter> makeProvider() {\n\t\t\treturn next -> {\n\t\t\t\tCollection<Integer> flags = AccessFlag.getFlags(modifiers.get()).stream()\n\t\t\t\t\t\t.map(AccessFlag::getMask)\n\t\t\t\t\t\t.collect(Collectors.toList());\n\t\t\t\treturn new IncludeModifiersNameFilter(next, flags, classes.get(), fields.get(), methods.get());\n\t\t\t};\n\t\t}\n\n\t\t@Override\n\t\tprotected void fillConfigurator(@Nonnull BiConsumer<StringBinding, Node> sink) {\n\t\t\tsink.accept(Lang.getBinding(\"mapgen.filter.includemodifier\"), new BoundTextField(modifiers)\n\t\t\t\t\t.withTooltip(\"mapgen.filter.modifiers.tooltip\"));\n\t\t\tsink.accept(null, new BoundCheckBox(Lang.getBinding(\"mapgen.filter.includeclass\"), classes));\n\t\t\tsink.accept(null, new BoundCheckBox(Lang.getBinding(\"mapgen.filter.includefield\"), fields));\n\t\t\tsink.accept(null, new BoundCheckBox(Lang.getBinding(\"mapgen.filter.includemethod\"), methods));\n\t\t}\n\t}\n\n\t/**\n\t * Config node for {@link IncludeLongNameFilter}.\n\t */\n\tpublic static class IncludeLongName extends FilterWithConfigNode<IncludeLongNameFilter> {\n\t\tprivate final IntegerProperty length = new SimpleIntegerProperty();\n\t\tprivate final BooleanProperty classes = new SimpleBooleanProperty(true);\n\t\tprivate final BooleanProperty fields = new SimpleBooleanProperty(true);\n\t\tprivate final BooleanProperty methods = new SimpleBooleanProperty(true);\n\t\tprivate final BooleanProperty variables = new SimpleBooleanProperty(true);\n\n\t\tpublic IncludeLongName() {}\n\n\t\tpublic IncludeLongName(int maxLength) {\n\t\t\tlength.set(maxLength);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic ObservableValue<String> display() {\n\t\t\treturn Bindings.concat(Lang.getBinding(\"mapgen.filter.includelong\"), \": \", length.map(i -> Integer.toString(i.intValue())));\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected Function<NameGeneratorFilter, IncludeLongNameFilter> makeProvider() {\n\t\t\treturn next -> new IncludeLongNameFilter(next, length.get(), classes.get(), fields.get(), methods.get(), variables.get());\n\t\t}\n\n\t\t@Override\n\t\tprotected void fillConfigurator(@Nonnull BiConsumer<StringBinding, Node> sink) {\n\t\t\tsink.accept(Lang.getBinding(\"mapgen.filter.includelong\"), new BoundIntSpinner(length, 1, Integer.MAX_VALUE));\n\t\t\tsink.accept(null, new BoundCheckBox(Lang.getBinding(\"mapgen.filter.includeclass\"), classes));\n\t\t\tsink.accept(null, new BoundCheckBox(Lang.getBinding(\"mapgen.filter.includefield\"), fields));\n\t\t\tsink.accept(null, new BoundCheckBox(Lang.getBinding(\"mapgen.filter.includemethod\"), methods));\n\t\t\tsink.accept(null, new BoundCheckBox(Lang.getBinding(\"mapgen.filter.includevariable\"), variables));\n\t\t}\n\t}\n\n\t/**\n\t * Config node for {@link IncludeKeywordNameFilter}.\n\t */\n\tpublic static class IncludeKeywordNames extends FilterWithConfigNode<IncludeKeywordNameFilter> {\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic ObservableValue<String> display() {\n\t\t\treturn Lang.getBinding(\"mapgen.filter.includekeywords\");\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected Function<NameGeneratorFilter, IncludeKeywordNameFilter> makeProvider() {\n\t\t\treturn IncludeKeywordNameFilter::new;\n\t\t}\n\n\t\t@Override\n\t\tprotected void fillConfigurator(@Nonnull BiConsumer<StringBinding, Node> sink) {\n\t\t}\n\t}\n\n\t/**\n\t * Config node for {@link IncludeNonAsciiNameFilter}.\n\t */\n\tpublic static class IncludeNonAsciiNames extends FilterWithConfigNode<IncludeNonAsciiNameFilter> {\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic ObservableValue<String> display() {\n\t\t\treturn Lang.getBinding(\"mapgen.filter.includenonasciinames\");\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected Function<NameGeneratorFilter, IncludeNonAsciiNameFilter> makeProvider() {\n\t\t\treturn IncludeNonAsciiNameFilter::new;\n\t\t}\n\n\t\t@Override\n\t\tprotected void fillConfigurator(@Nonnull BiConsumer<StringBinding, Node> sink) {\n\t\t}\n\t}\n\n\t/**\n\t * Config node for {@link IncludeNonJavaIdentifierNameFilter}.\n\t */\n\tpublic static class IncludeNonJavaIdentifierNames extends FilterWithConfigNode<IncludeNonJavaIdentifierNameFilter> {\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic ObservableValue<String> display() {\n\t\t\treturn Lang.getBinding(\"mapgen.filter.includenonjavaidentifiers\");\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected Function<NameGeneratorFilter, IncludeNonJavaIdentifierNameFilter> makeProvider() {\n\t\t\treturn IncludeNonJavaIdentifierNameFilter::new;\n\t\t}\n\n\t\t@Override\n\t\tprotected void fillConfigurator(@Nonnull BiConsumer<StringBinding, Node> sink) {\n\t\t}\n\t}\n\n\t/**\n\t * Config node for {@link IncludeNonAsciiNameFilter}.\n\t */\n\tpublic static class IncludeWhitespaceNames extends FilterWithConfigNode<IncludeWhitespaceNameFilter> {\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic ObservableValue<String> display() {\n\t\t\treturn Lang.getBinding(\"mapgen.filter.includewhitespacenames\");\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tprotected Function<NameGeneratorFilter, IncludeWhitespaceNameFilter> makeProvider() {\n\t\t\treturn IncludeWhitespaceNameFilter::new;\n\t\t}\n\n\t\t@Override\n\t\tprotected void fillConfigurator(@Nonnull BiConsumer<StringBinding, Node> sink) {\n\t\t}\n\t}\n\n\t/**\n\t * List cell to render {@link FilterWithConfigNode}.\n\t */\n\tprivate static class ConfiguredFilterCell extends ReorderableListCell<FilterWithConfigNode<?>> {\n\t\t@Override\n\t\tprotected void updateItem(FilterWithConfigNode<?> item, boolean empty) {\n\t\t\tsuper.updateItem(item, empty);\n\n\t\t\tStringProperty property = textProperty();\n\t\t\tif (empty || item == null) {\n\t\t\t\tproperty.unbind();\n\t\t\t\tsetText(null);\n\t\t\t} else {\n\t\t\t\t// Hoisting this out so that the synthetic arg to the lambda is a 'int' and not 'item'\n\t\t\t\tfinal int index = (getIndex() + 1);\n\t\t\t\tproperty.bind(item.display().map(display -> index + \". \" + display));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Base to create a filter with configuration node.\n\t *\n\t * @param <F>\n\t * \t\tFilter type.\n\t */\n\tpublic static abstract class FilterWithConfigNode<F extends NameGeneratorFilter> {\n\t\tprivate final Function<NameGeneratorFilter, F> filterProvider;\n\t\tprivate Node node;\n\t\tprivate int inputs;\n\n\t\tprivate FilterWithConfigNode() {\n\t\t\tthis.filterProvider = makeProvider();\n\t\t}\n\n\t\t@Nonnull\n\t\tpublic abstract ObservableValue<String> display();\n\n\t\t@Nonnull\n\t\tpublic Node getConfigurator() {\n\t\t\tif (node == null) {\n\t\t\t\tGridPane grid = createGrid();\n\t\t\t\tfillConfigurator((key, editor) -> {\n\t\t\t\t\tinputs++;\n\t\t\t\t\tint row = grid.getRowCount();\n\t\t\t\t\tif (key == null) {\n\t\t\t\t\t\tgrid.add(editor, 0, row, 2, 1);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tgrid.add(new BoundLabel(key), 0, row);\n\t\t\t\t\t\tgrid.add(editor, 1, row);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tnode = grid;\n\t\t\t}\n\t\t\treturn node;\n\t\t}\n\n\t\t@Nonnull\n\t\tpublic NameGeneratorFilter build(@Nullable NameGeneratorFilter previous) {\n\t\t\treturn filterProvider.apply(previous);\n\t\t}\n\n\t\t@Nonnull\n\t\tprotected abstract Function<NameGeneratorFilter, F> makeProvider();\n\n\t\t/**\n\t\t * @return Number of configurable inputs.\n\t\t */\n\t\tpublic int getInputCount() {\n\t\t\treturn inputs;\n\t\t}\n\n\t\tprotected abstract void fillConfigurator(@Nonnull BiConsumer<StringBinding, Node> sink);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/MappingProgressPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleListProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.scene.Node;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.ContextMenu;\nimport javafx.scene.control.Label;\nimport javafx.scene.effect.Effect;\nimport javafx.scene.effect.Glow;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.Region;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.paint.Color;\nimport javafx.scene.shape.Rectangle;\nimport org.reactfx.EventStreams;\nimport software.coley.collections.tree.SortedTreeImpl;\nimport software.coley.collections.tree.Tree;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.info.properties.builtin.HasMappedReferenceProperty;\nimport software.coley.recaf.info.properties.builtin.OriginalClassNameProperty;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.services.mapping.IntermediateMappings;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.mapping.aggregate.AggregateMappingManager;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.config.WorkspaceExplorerConfig;\nimport software.coley.recaf.ui.control.PannableView;\nimport software.coley.recaf.ui.window.MappingProgressWindow;\nimport software.coley.recaf.util.Colors;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.ToStringConverter;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.util.threading.ThreadUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.AndroidClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.ResourceAndroidClassListener;\nimport software.coley.recaf.workspace.model.resource.ResourceJvmClassListener;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\nimport software.coley.treemap.TreeMapPane;\nimport software.coley.treemap.content.SimpleHierarchicalTreeContent;\nimport software.coley.treemap.content.TreeContent;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.function.ToDoubleFunction;\n\n/**\n * Pane to display the current mapping progress of classes in the workspace.\n * Classes change color to indicate if they are mapped or not, and how fully mapped their declared members are.\n *\n * @author Matt Coley\n * @see MappingProgressWindow\n */\n@Dependent\npublic class MappingProgressPane extends BorderPane implements ResourceJvmClassListener, ResourceAndroidClassListener {\n\tprivate static final Color RED = Color.web(\"rgb(80, 0, 0)\");\n\tprivate static final Color GREEN = Color.web(\"rgb(0, 80, 0)\");\n\tprivate static final Color MID = Colors.interpolateHsb(RED, GREEN, 0.5F);\n\tprivate static final ExecutorService pool = ThreadPoolFactory.newSingleThreadExecutor(\"mapping-preview\");\n\tprivate static final Metric[] metrics = new Metric[]{\n\t\t\tnew Metric.MetricSize(), new Metric.MetricMemberCount()\n\t};\n\tprivate final CellConfigurationService configurationService;\n\tprivate final ObjectProperty<Metric> metric = new SimpleObjectProperty<>();\n\tprivate final ObjectProperty<SelectionInfo> selectedPath = new SimpleObjectProperty<>();\n\tprivate final ObservableList<TreeContent> treeContentListDelegate = FXCollections.observableArrayList();\n\tprivate final ObservableList<TreeContent> treeContentList;\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final BooleanProperty active = new SimpleBooleanProperty();\n\tprivate final WorkspaceExplorerConfig explorerConfig;\n\tprivate IntermediateMappings mappings;\n\tprivate Callable<Boolean> pendingUpdate;\n\n\t@Inject\n\tpublic MappingProgressPane(@Nonnull CellConfigurationService configurationService,\n\t                           @Nonnull WorkspaceExplorerConfig explorerConfig,\n\t                           @Nonnull Instance<AggregateMappingManager> aggregateMappingManagerInstance,\n\t                           @Nonnull WorkspaceManager workspaceManager) {\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.configurationService = configurationService;\n\t\tthis.explorerConfig = explorerConfig;\n\n\t\t// Create the tree-map pane\n\t\tTreeMapPane<TreeContent> treeMapPane = TreeMapPane.forTreeContent();\n\t\tPannableView treeMapWrapper = new PannableView(treeMapPane);\n\n\t\t// Create an initial dummy tree.\n\t\ttreeContentList = FXCollections.observableArrayList();\n\t\ttreeMapPane.valueListProperty().set(treeContentList);\n\n\t\t// When a workspace is opened, refresh the tree and listen for changes on the new workspace.\n\t\tworkspaceManager.addWorkspaceOpenListener(workspace -> {\n\t\t\tCompletableFuture.supplyAsync(() -> workspace.findClasses(false, c -> true).size(), ThreadUtil.executor())\n\t\t\t\t\t.thenAcceptAsync(classes -> {\n\t\t\t\t\t\ttreeMapPane.setPrefWidth(40 * classes);\n\t\t\t\t\t\ttreeMapPane.setPrefHeight(5 * classes);\n\t\t\t\t\t\ttreeMapWrapper.resetTranslation();\n\t\t\t\t\t\ttreeMapWrapper.resetZoom();\n\t\t\t\t\t}, FxThreadUtil.executor());\n\n\t\t\t// When the mappings update, refresh the tree and mappings reference.\n\t\t\taggregateMappingManagerInstance.get().addAggregatedMappingsListener(this::updateTreeAndMappings);\n\n\t\t\t// Initial outline\n\t\t\tupdateTree();\n\n\t\t\t// Listen for updates\n\t\t\tworkspace.getPrimaryResource().addListener(this);\n\t\t});\n\n\t\tworkspaceManager.addWorkspaceCloseListener(workspace -> {\n\t\t\t// The pending update + mappings can be cleared once the workspace is closed.\n\t\t\tpendingUpdate = null;\n\t\t\tmappings = null;\n\n\t\t\t// And the tree content can be cleared.\n\t\t\ttreeContentListDelegate.clear();\n\t\t\ttreeContentList.clear();\n\n\t\t\t// Clear selection, as it holds a path which contains workspace references,\n\t\t\t// which can prevent GC from freeing it.\n\t\t\tselectedPath.set(null);\n\t\t});\n\n\t\t// The tree update passes data to this delegate list.\n\t\t// We use ReactFX to merge rapid changes to limit redundant work.\n\t\tEventStreams.changesOf(treeContentListDelegate)\n\t\t\t\t.reduceSuccessions((change, change2) -> change2, Duration.ofMillis(500))\n\t\t\t\t.addObserver(c -> {\n\t\t\t\t\tFxThreadUtil.run(() -> treeContentList.setAll(treeContentListDelegate));\n\t\t\t\t});\n\n\t\t// When the metric measurement changes, update the tree.\n\t\tmetric.addListener((ob, old, cur) -> updateTree());\n\n\t\t// Only update the UI when visible\n\t\tactive.addListener((ob, old, cur) -> {\n\t\t\tCallable<Boolean> update = pendingUpdate;\n\t\t\tif (cur && update != null) {\n\t\t\t\tpendingUpdate = null;\n\t\t\t\tpool.submit(update);\n\t\t\t}\n\t\t});\n\n\t\t// Layout\n\t\tComboBox<Metric> comboMetric = new ComboBox<>(FXCollections.observableArrayList(metrics));\n\t\tcomboMetric.setConverter(ToStringConverter.from(metric -> metric.name().get()));\n\t\tmetric.bind(comboMetric.getSelectionModel().selectedItemProperty());\n\t\tcomboMetric.getSelectionModel().select(0);\n\t\tBorderPane wrapper = new BorderPane();\n\t\twrapper.centerProperty().bind(selectedPath.map(selection -> {\n\t\t\t// No selection? Empty region.\n\t\t\tif (selection == null)\n\t\t\t\treturn new Region();\n\n\t\t\tClassPathNode path = selection.path();\n\t\t\tLabel className = new Label(configurationService.textOf(path));\n\n\t\t\tboolean isNameMapped = OriginalClassNameProperty.get(path.getValue()) != null;\n\t\t\tLabel nameIsMapped = new Label(String.format(\" - %s\", isNameMapped ? \"class remapped\" : \"class not mapped\"));\n\t\t\tLabel fieldsMapped = new Label(String.format(\" - %d/%d fields\", selection.mappedFields, selection.fields));\n\t\t\tLabel methodsMapped = new Label(String.format(\" - %d/%d methods\", selection.mappedMethods, selection.methods));\n\t\t\tif (isNameMapped)\n\t\t\t\tnameIsMapped.setTextFill(Color.GREEN.brighter());\n\t\t\tif (selection.mappedFields == selection.fields)\n\t\t\t\tfieldsMapped.setTextFill(Color.GREEN.brighter());\n\t\t\tif (selection.mappedMethods == selection.methods)\n\t\t\t\tmethodsMapped.setTextFill(Color.GREEN.brighter());\n\t\t\tclassName.setGraphic(configurationService.graphicOf(path));\n\t\t\tclassName.getStyleClass().add(Styles.TITLE_4);\n\n\t\t\treturn new VBox(className, nameIsMapped, fieldsMapped, methodsMapped);\n\t\t}));\n\t\tVBox legend = new VBox(\n\t\t\t\tmakeLegendEntry(\"Unmapped\", RED),\n\t\t\t\tmakeLegendEntry(\"Partially mapped\", MID),\n\t\t\t\tmakeLegendEntry(\"Fully mapped\", GREEN)\n\t\t);\n\t\tVBox rightContent = new VBox(legend, comboMetric);\n\t\trightContent.setSpacing(4);\n\t\twrapper.setRight(rightContent);\n\t\twrapper.setPadding(new Insets(8));\n\t\twrapper.setStyle(\"-fx-background-color: rgba(0, 0, 0, 0.2)\");\n\t\tsetCenter(treeMapWrapper);\n\t\tsetBottom(wrapper);\n\t}\n\n\t@Nonnull\n\tprivate static Label makeLegendEntry(@Nonnull String text, @Nonnull Color color) {\n\t\tRectangle rectangle = new Rectangle(10, 10);\n\t\trectangle.setFill(color);\n\t\trectangle.setStroke(Color.DARKGRAY);\n\n\t\tLabel label = new Label(text);\n\t\tlabel.setGraphic(rectangle);\n\t\treturn label;\n\t}\n\n\t@Override\n\tpublic void onNewClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo cls) {\n\t\tupdateTree();\n\t}\n\n\t@Override\n\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo oldCls, @Nonnull AndroidClassInfo newCls) {\n\t\tupdateTree();\n\t}\n\n\t@Override\n\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull AndroidClassBundle bundle, @Nonnull AndroidClassInfo cls) {\n\t\tupdateTree();\n\t}\n\n\t@Override\n\tpublic void onNewClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls) {\n\t\tupdateTree();\n\t}\n\n\t@Override\n\tpublic void onUpdateClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo oldCls, @Nonnull JvmClassInfo newCls) {\n\t\tupdateTree();\n\t}\n\n\t@Override\n\tpublic void onRemoveClass(@Nonnull WorkspaceResource resource, @Nonnull JvmClassBundle bundle, @Nonnull JvmClassInfo cls) {\n\t\tupdateTree();\n\t}\n\n\tprivate void updateTreeAndMappings(@Nullable Mappings mappings) {\n\t\tthis.mappings = mappings == null ? null : mappings.exportIntermediate();\n\t\tupdateTree();\n\t}\n\n\tprivate void updateTree() {\n\t\tif (workspaceManager.hasCurrentWorkspace()) {\n\t\t\tWorkspace workspace = workspaceManager.getCurrent();\n\t\t\tCallable<Boolean> action = () -> treeContentListDelegate.setAll(buildTreeRoots(workspaceToTree(workspace)));\n\t\t\tif (active.get())\n\t\t\t\tpool.submit(action);\n\t\t\telse\n\t\t\t\tpendingUpdate = action;\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate Tree<String, ClassPathNode> workspaceToTree(@Nonnull Workspace workspace) {\n\t\tTree<String, ClassPathNode> tree = new SortedTreeImpl<>();\n\t\tworkspace.findClasses(false, classInfo -> true)\n\t\t\t\t.forEach(classPath -> {\n\t\t\t\t\t// Only match classes in the primary resource\n\t\t\t\t\tWorkspaceResource resource = classPath.getValueOfType(WorkspaceResource.class);\n\t\t\t\t\tif (resource != workspace.getPrimaryResource())\n\t\t\t\t\t\treturn;\n\n\t\t\t\t\tClassInfo info = classPath.getValue();\n\t\t\t\t\tString name = info.getName();\n\t\t\t\t\tString[] sections = name.split(\"/\");\n\t\t\t\t\tint maxSplit = explorerConfig.getMaxTreeDirectoryDepth();\n\t\t\t\t\tif (sections.length > maxSplit) {\n\t\t\t\t\t\tname = StringUtil.cutOffAtNth(name, '/', maxSplit) + \"/\" + StringUtil.shortenPath(name);\n\t\t\t\t\t\tsections = name.split(\"/\");\n\t\t\t\t\t}\n\n\t\t\t\t\tTree<String, ClassPathNode> path = tree;\n\t\t\t\t\tfor (int i = 0; i < sections.length; i++) {\n\t\t\t\t\t\tString section = sections[i];\n\t\t\t\t\t\tif (i == sections.length - 1)\n\t\t\t\t\t\t\tpath = path.computeIfAbsent(section, s -> new SortedTreeImpl<>(classPath));\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tpath = path.computeIfAbsent(section, s -> new SortedTreeImpl<>());\n\t\t\t\t\t}\n\t\t\t\t});\n\t\treturn tree;\n\t}\n\n\t@Nonnull\n\tprivate List<TreeContent> buildTreeRoots(@Nonnull Tree<String, ClassPathNode> tree) {\n\t\tif (tree.isBranch()) {\n\t\t\tObservableList<TreeContent> list = FXCollections.observableArrayList();\n\t\t\tfor (Tree<String, ClassPathNode> subtree : tree.values())\n\t\t\t\tlist.addAll(buildTreeRoots(subtree));\n\t\t\treturn Collections.singletonList(new SimpleHierarchicalTreeContent(new SimpleListProperty<>(list)));\n\t\t} else {\n\t\t\tClassPathNode value = tree.getValue();\n\t\t\tif (value == null)\n\t\t\t\treturn Collections.emptyList();\n\t\t\treturn Collections.singletonList(toContent(value));\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate TreeContent toContent(@Nonnull ClassPathNode value) {\n\t\treturn new ContextualClassTreeContent(value);\n\t}\n\n\t/**\n\t * @return Property controlling UI updates.\n\t */\n\t@Nonnull\n\tpublic BooleanProperty activeProperty() {\n\t\treturn active;\n\t}\n\n\t/**\n\t * Metric base.\n\t */\n\tprivate static class Metric {\n\t\tprivate final StringBinding name;\n\t\tprivate final ToDoubleFunction<ClassPathNode> func;\n\n\t\tprotected Metric(@Nonnull String name, @Nonnull ToDoubleFunction<ClassPathNode> func) {\n\t\t\tthis.name = Lang.getBinding(name);\n\t\t\tthis.func = func;\n\t\t}\n\n\t\t/**\n\t\t * @param node\n\t\t * \t\tInput path to class.\n\t\t *\n\t\t * @return Weighted value of class, per the metric conversion function.\n\t\t */\n\t\tpublic double toWeight(@Nonnull ClassPathNode node) {\n\t\t\treturn func.applyAsDouble(node);\n\t\t}\n\n\t\t/**\n\t\t * @return Name of the metric.\n\t\t */\n\t\t@Nonnull\n\t\tpublic StringBinding name() {\n\t\t\treturn name;\n\t\t}\n\n\t\t/**\n\t\t * Metric for raw-size of classes.\n\t\t */\n\t\tprivate static class MetricSize extends Metric {\n\t\t\tpublic MetricSize() {\n\t\t\t\tsuper(\"mapprog.metric.size\", MetricSize::map);\n\t\t\t}\n\n\t\t\tprivate static double map(@Nonnull ClassPathNode path) {\n\t\t\t\tdouble weight;\n\t\t\t\tClassInfo info = path.getValue();\n\t\t\t\tif (info instanceof JvmClassInfo jvmClassInfo) {\n\t\t\t\t\tweight = jvmClassInfo.getBytecode().length;\n\t\t\t\t} else {\n\t\t\t\t\t// TODO: Compute rough android size (can estimate based on insn count and such)\n\t\t\t\t\tweight = 1;\n\t\t\t\t}\n\t\t\t\treturn weight;\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Metric for number of members in classes.\n\t\t */\n\t\tprivate static class MetricMemberCount extends Metric {\n\t\t\tpublic MetricMemberCount() {\n\t\t\t\tsuper(\"mapprog.metric.membercount\", MetricMemberCount::map);\n\t\t\t}\n\n\t\t\tprivate static double map(@Nonnull ClassPathNode path) {\n\t\t\t\tClassInfo info = path.getValue();\n\t\t\t\treturn info.getMethods().size() + info.getFields().size();\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Contextual tree-content allowing context-actions to be used on the display.\n\t * <br>\n\t * The mapped content held by each content item transitions from red to green with more mapping progress.\n\t */\n\tprivate class ContextualClassTreeContent implements TreeContent, ContextSource {\n\t\tprivate static final Effect GLOW = new Glow();\n\t\tprivate final ClassPathNode path;\n\t\tprivate final double weight;\n\t\tprivate Node node;\n\n\t\tpublic ContextualClassTreeContent(@Nonnull ClassPathNode path) {\n\t\t\tthis.path = path;\n\t\t\tthis.weight = metric.get().toWeight(path);\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean isDeclaration() {\n\t\t\treturn false;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean isReference() {\n\t\t\treturn true;\n\t\t}\n\n\t\t@Override\n\t\tpublic double getValueWeight() {\n\t\t\treturn weight;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic Node getNode() {\n\t\t\tif (node == null) {\n\t\t\t\tClassInfo classInfo = path.getValue();\n\t\t\t\tString text = configurationService.textOf(path);\n\t\t\t\tNode graphic = configurationService.graphicOf(path);\n\t\t\t\tLabel label = new Label(text);\n\t\t\t\tlabel.setGraphic(graphic);\n\t\t\t\tlabel.setMouseTransparent(true);\n\t\t\t\tBorderPane wrapper = new BorderPane(label);\n\t\t\t\twrapper.setOnContextMenuRequested(e -> {\n\t\t\t\t\tContextMenu contextMenu = configurationService.contextMenuOf(this, path);\n\t\t\t\t\tif (contextMenu != null)\n\t\t\t\t\t\tcontextMenu.show(wrapper, e.getScreenX(), e.getScreenY());\n\t\t\t\t});\n\t\t\t\twrapper.setOnMouseExited(e -> wrapper.setEffect(null));\n\n\t\t\t\t// Track number of mapped members\n\t\t\t\tint fields = 0;\n\t\t\t\tint mappedFields = 0;\n\t\t\t\tfor (FieldMember field : classInfo.getFields()) {\n\t\t\t\t\tfields++;\n\t\t\t\t\tif (mappings != null && mappings.getMappedFieldName(classInfo, field) != null)\n\t\t\t\t\t\tmappedFields++;\n\t\t\t\t}\n\t\t\t\tint methods = 0;\n\t\t\t\tint mappedMethods = 0;\n\t\t\t\tfor (MethodMember method : classInfo.getMethods()) {\n\t\t\t\t\tmethods++;\n\t\t\t\t\tif (mappings != null && mappings.getMappedMethodName(classInfo, method) != null)\n\t\t\t\t\t\tmappedMethods++;\n\t\t\t\t}\n\n\t\t\t\tboolean hasMappedRefs = HasMappedReferenceProperty.get(classInfo);\n\t\t\t\tif (hasMappedRefs) {\n\t\t\t\t\tboolean isClassMapped = OriginalClassNameProperty.get(classInfo) != null;\n\t\t\t\t\tint total = fields + methods;\n\t\t\t\t\tint mapped = mappedFields + mappedMethods;\n\n\t\t\t\t\t// Percent calc:\n\t\t\t\t\t//  50% from member mappings\n\t\t\t\t\t//  25% from class being mapped\n\t\t\t\t\t//  25% for any reference being mapped\n\t\t\t\t\tfloat membersMappedContrib = total == 0 ? 0.5F : (mapped / (float) total) * 0.5F;\n\t\t\t\t\tfloat nameMappedContrib = (isClassMapped ? 0.25F : 0);\n\t\t\t\t\tfloat percentMapped = 0.25F + membersMappedContrib + nameMappedContrib;\n\t\t\t\t\tColor interpolatedColor = Colors.interpolateHsb(RED, GREEN, percentMapped);\n\t\t\t\t\twrapper.setStyle(\"-fx-background-color: rgb(%d, %d, %d); -fx-border-color: black; -fx-border-width: 1px\".formatted(\n\t\t\t\t\t\t\t(int) (interpolatedColor.getRed() * 255),\n\t\t\t\t\t\t\t(int) (interpolatedColor.getGreen() * 255),\n\t\t\t\t\t\t\t(int) (interpolatedColor.getBlue() * 255)\n\t\t\t\t\t));\n\t\t\t\t} else {\n\t\t\t\t\t// Not touched by any mapping\n\t\t\t\t\twrapper.setStyle(\"-fx-background-color: rgb(80, 0, 0); -fx-border-color: black; -fx-border-width: 1px\");\n\t\t\t\t}\n\t\t\t\tSelectionInfo selectionInfo = new SelectionInfo(path, fields, mappedFields, methods, mappedMethods);\n\t\t\t\twrapper.setOnMouseEntered(e -> {\n\t\t\t\t\twrapper.setEffect(GLOW);\n\t\t\t\t\tselectedPath.set(selectionInfo);\n\t\t\t\t});\n\t\t\t\tnode = wrapper;\n\t\t\t}\n\t\t\treturn node;\n\t\t}\n\t}\n\n\t/**\n\t * Wrapper of what is selected.\n\t *\n\t * @param path\n\t * \t\tSelected content.\n\t * @param fields\n\t * \t\tFields declared.\n\t * @param mappedFields\n\t * \t\tFields mapped.\n\t * @param methods\n\t * \t\tMethods declared.\n\t * @param mappedMethods\n\t * \t\tMethods mapped.\n\t */\n\tprivate record SelectionInfo(ClassPathNode path, int fields, int mappedFields, int methods, int mappedMethods) {\n\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/RemoteVirtualMachinesPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport atlantafx.base.theme.Styles;\nimport atlantafx.base.theme.Tweaks;\nimport com.sun.tools.attach.VirtualMachineDescriptor;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Hyperlink;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.Separator;\nimport javafx.scene.control.SplitPane;\nimport javafx.scene.control.Tab;\nimport javafx.scene.control.TabPane;\nimport javafx.scene.control.TableColumn;\nimport javafx.scene.control.TableView;\nimport javafx.scene.control.cell.TextFieldTableCell;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.paint.Color;\nimport org.kordamp.ikonli.Ikon;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.collections.func.UncheckedSupplier;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.attach.AttachManager;\nimport software.coley.recaf.services.attach.JmxBeanServerConnection;\nimport software.coley.recaf.services.attach.NamedMBeanInfo;\nimport software.coley.recaf.services.attach.PostScanListener;\nimport software.coley.recaf.services.workspace.WorkspaceCloseListener;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.window.RemoteVirtualMachinesWindow;\nimport software.coley.recaf.util.DesktopUtil;\nimport software.coley.recaf.util.ErrorDialogs;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.threading.ThreadUtil;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceRemoteVmResource;\n\nimport javax.management.MBeanAttributeInfo;\nimport javax.management.MBeanFeatureInfo;\nimport java.io.IOException;\nimport java.lang.reflect.Array;\nimport java.net.URI;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.stream.Collectors;\n\nimport static software.coley.recaf.util.Lang.getBinding;\n\n/**\n * Pane for displaying available remote JVMs from {@link AttachManager}.\n *\n * @author Matt Coley\n * @see RemoteVirtualMachinesWindow\n */\n@Dependent\npublic class RemoteVirtualMachinesPane extends BorderPane implements PostScanListener, WorkspaceCloseListener {\n\tprivate static final Logger logger = Logging.get(RemoteVirtualMachinesPane.class);\n\tprivate final ObservableObject<VirtualMachineDescriptor> connectedVm = new ObservableObject<>(null);\n\tprivate final BooleanProperty hasVms = new SimpleBooleanProperty(true);\n\tprivate final Map<VirtualMachineDescriptor, VmPane> vmCellMap = new HashMap<>();\n\tprivate final Map<VirtualMachineDescriptor, Button> vmButtonMap = new HashMap<>();\n\tprivate final VBox vmButtonsList = new VBox();\n\tprivate final BorderPane vmDisplayPane = new BorderPane();\n\tprivate final AttachManager attachManager;\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate VmPane currentVmPane;\n\n\t@Inject\n\tpublic RemoteVirtualMachinesPane(@Nonnull AttachManager attachManager,\n\t                                 @Nonnull WorkspaceManager workspaceManager) {\n\t\tthis.attachManager = attachManager;\n\t\tthis.workspaceManager = workspaceManager;\n\n\t\t// Register this class as scan listener so that we can update the UI live as updates come in.\n\t\tattachManager.addPostScanListener(this);\n\n\t\t// Register this class as a close listener so that we can know when the current attached resource is closed.\n\t\t// We'll reset any UI state after doing this associated with being connected.\n\t\tworkspaceManager.addWorkspaceCloseListener(this);\n\n\t\t// Setup UI\n\t\tif (attachManager.canAttach())\n\t\t\tinitialize();\n\t\telse\n\t\t\tinitializeWithoutAttach();\n\t}\n\n\t/**\n\t * Sets up the UI, and binds passive scanning to only occur while this pane is displayed.\n\t */\n\tprivate void initialize() {\n\t\t// Make a display to notify users there are no available virtual machines to attach to.\n\t\t// This will only show when we have nothing to show.\n\t\tBoundLabel novmTitle = new BoundLabel(getBinding(\"attach.no-vms\"));\n\t\tHyperlink jepLink = new Hyperlink(\"JEP 451: Disallow the Dynamic Loading of Agents\");\n\t\tHyperlink disableLink = new Hyperlink(\"Bypassing \\\"-XX:+DisableAttachMechanism\\\"\");\n\t\tjepLink.setPadding(new Insets(0, 0, 0, 10));\n\t\tjepLink.setOnAction(e -> {\n\t\t\ttry {\n\t\t\t\tDesktopUtil.showDocument(new URI(\"https://openjdk.org/jeps/451\"));\n\t\t\t} catch (Exception ex) {\n\t\t\t\tlogger.error(\"Failed to open link\", ex);\n\t\t\t}\n\t\t});\n\t\tdisableLink.setPadding(new Insets(0, 0, 0, 10));\n\t\tdisableLink.setOnAction(e -> {\n\t\t\ttry {\n\t\t\t\tDesktopUtil.showDocument(new URI(\"https://stackoverflow.com/questions/42495455/how-can-i-bypass-a-xxdisableattachmechanism-java-vm-option\"));\n\t\t\t} catch (Exception ex) {\n\t\t\t\tlogger.error(\"Failed to open link\", ex);\n\t\t\t}\n\t\t});\n\t\tnovmTitle.getStyleClass().add(Styles.TITLE_2);\n\t\tGridPane noVmOverlay = new GridPane();\n\t\tnoVmOverlay.setAlignment(Pos.CENTER);\n\t\tnoVmOverlay.setPadding(new Insets(20));\n\t\tnoVmOverlay.setVgap(10);\n\t\tnoVmOverlay.getStyleClass().addAll(Styles.ELEVATED_1, Styles.BG_INSET);\n\t\tnoVmOverlay.add(novmTitle, 0, noVmOverlay.getRowCount());\n\t\tnoVmOverlay.add(new BoundLabel(Lang.getBinding(\"attach.no-vms.detail\")), 0, noVmOverlay.getRowCount());\n\t\tnoVmOverlay.add(new Separator(), 0, noVmOverlay.getRowCount());\n\t\tnoVmOverlay.add(new BoundLabel(Lang.getBinding(\"attach.problem.disable-attach\")), 0, noVmOverlay.getRowCount());\n\t\tnoVmOverlay.add(disableLink, 0, noVmOverlay.getRowCount());\n\t\tnoVmOverlay.add(new BoundLabel(Lang.getBinding(\"attach.problem.java-21\")), 0, noVmOverlay.getRowCount());\n\t\tnoVmOverlay.add(jepLink, 0, noVmOverlay.getRowCount());\n\t\tnoVmOverlay.visibleProperty().bind(hasVms.not());\n\n\t\t// Layout\n\t\tStackPane stack = new StackPane();\n\t\tvmButtonsList.setPadding(new Insets(5));\n\t\tvmButtonsList.setAlignment(Pos.TOP_LEFT);\n\t\tvmButtonsList.setSpacing(5);\n\t\tScrollPane scroll = new ScrollPane(vmButtonsList);\n\t\tscroll.setFitToWidth(true);\n\t\tSplitPane split = new SplitPane(scroll, vmDisplayPane);\n\t\tSplitPane.setResizableWithParent(scroll, false);\n\t\tsplit.setDividerPositions(0.3);\n\t\tstack.getChildren().addAll(split, noVmOverlay);\n\t\tsetCenter(stack);\n\t}\n\n\t/**\n\t * Place a warning box stating that the feature is not available.\n\t */\n\tprivate void initializeWithoutAttach() {\n\t\tLabel graphic = new Label();\n\t\tgraphic.setGraphic(new FontIconView(CarbonIcons.ERROR, 128, Color.RED));\n\t\tgraphic.setAlignment(Pos.CENTER);\n\n\t\tLabel title = new Label();\n\t\ttitle.getStyleClass().add(Styles.TITLE_1);\n\t\ttitle.textProperty().bind(getBinding(\"attach.unsupported\"));\n\t\ttitle.setAlignment(Pos.CENTER);\n\n\t\tLabel description = new Label();\n\t\tdescription.getStyleClass().add(Styles.TITLE_4);\n\t\tdescription.textProperty().bind(getBinding(\"attach.unsupported.detail\"));\n\t\tdescription.setAlignment(Pos.CENTER);\n\n\t\tVBox box = new VBox(graphic, title, description);\n\t\tbox.setMaxHeight(Double.MAX_VALUE);\n\t\tbox.setMaxWidth(Double.MAX_VALUE);\n\t\tbox.setMinHeight(250);\n\t\tbox.setMinWidth(300);\n\t\tbox.setAlignment(Pos.CENTER);\n\t\tbox.getStyleClass().add(\"tooltip\");\n\n\t\t// Layout 'box' centered on the pane\n\t\tVBox vwrap = new VBox(box);\n\t\tvwrap.setAlignment(Pos.CENTER);\n\t\tvwrap.setMaxHeight(Double.MAX_VALUE);\n\t\tvwrap.setMaxWidth(Double.MAX_VALUE);\n\t\tHBox hwrap = new HBox(vwrap);\n\t\thwrap.setAlignment(Pos.CENTER);\n\t\thwrap.setMaxHeight(Double.MAX_VALUE);\n\t\thwrap.setMaxWidth(Double.MAX_VALUE);\n\t\thwrap.setMouseTransparent(true);\n\n\t\tsetCenter(hwrap);\n\t}\n\n\t@Override\n\tpublic void onScanCompleted(@Nonnull Set<VirtualMachineDescriptor> added,\n\t                            @Nonnull Set<VirtualMachineDescriptor> removed) {\n\t\tFxThreadUtil.run(() -> {\n\t\t\t// Add new VM's found\n\t\t\tfor (VirtualMachineDescriptor descriptor : added) {\n\t\t\t\tif (vmCellMap.containsKey(descriptor))\n\t\t\t\t\tcontinue;\n\t\t\t\tVmPane cell = new VmPane(descriptor);\n\t\t\t\tvmCellMap.put(descriptor, cell);\n\n\t\t\t\t// Button to display the cell\n\t\t\t\tButton button = new ActionButton(cell.getLabel(), () -> {\n\t\t\t\t\tvmDisplayPane.setCenter(cell);\n\t\t\t\t\tcurrentVmPane = cell;\n\n\t\t\t\t\t// Mark button as 'selected'\n\t\t\t\t\tfor (Node child : vmButtonsList.getChildren())\n\t\t\t\t\t\tchild.getStyleClass().remove(Styles.BUTTON_OUTLINED);\n\t\t\t\t\tvmButtonMap.get(descriptor).getStyleClass().add(Styles.BUTTON_OUTLINED);\n\t\t\t\t});\n\t\t\t\tbutton.setFocusTraversable(false);\n\t\t\t\tbutton.setMaxWidth(Double.MAX_VALUE);\n\t\t\t\tbutton.setAlignment(Pos.CENTER_LEFT);\n\t\t\t\tbutton.getStyleClass().add(\"muted\");\n\t\t\t\tvmButtonMap.put(descriptor, button);\n\t\t\t\tvmButtonsList.getChildren().add(button);\n\n\t\t\t\t// Highlight the button bright green if it is the current attached VM.\n\t\t\t\tconnectedVm.addChangeListener((ob, old, cur) -> {\n\t\t\t\t\tif (cur == descriptor) {\n\t\t\t\t\t\tbutton.getStyleClass().addAll(Styles.SUCCESS);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbutton.getStyleClass().removeAll(Styles.SUCCESS);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Remove VM's that are no longer alive.\n\t\t\tfor (VirtualMachineDescriptor descriptor : removed) {\n\t\t\t\tButton removedButton = vmButtonMap.remove(descriptor);\n\t\t\t\tvmButtonsList.getChildren().remove(removedButton);\n\n\t\t\t\tVmPane cell = vmCellMap.remove(descriptor);\n\t\t\t\tif (cell != null) cell.setDisable(true);\n\t\t\t}\n\n\t\t\t// Refresh tracking properties.\n\t\t\tFxThreadUtil.set(hasVms, !vmCellMap.isEmpty());\n\n\t\t\t// Refresh current cell.\n\t\t\tif (currentVmPane != null && !currentVmPane.isDisabled())\n\t\t\t\tcurrentVmPane.update();\n\t\t});\n\t}\n\n\t@Override\n\tpublic void onWorkspaceClosed(@Nonnull Workspace workspace) {\n\t\tconnectedVm.setValue(null);\n\t}\n\n\t/**\n\t * Display for a remote JVM.\n\t */\n\tprivate class VmPane extends VBox {\n\t\tprivate final ContentTabs contentGrid;\n\t\tprivate final String label;\n\n\t\t/**\n\t\t * @param descriptor\n\t\t * \t\tAssociated descriptor.\n\t\t */\n\t\tVmPane(VirtualMachineDescriptor descriptor) {\n\t\t\tthis.contentGrid = new ContentTabs(attachManager, descriptor);\n\t\t\tVBox.setVgrow(contentGrid, Priority.ALWAYS);\n\n\t\t\t// Remote VM info\n\t\t\tint pid = attachManager.getVirtualMachinePid(descriptor);\n\t\t\tString mainClass = attachManager.getVirtualMachineMainClass(descriptor);\n\t\t\tthis.label = pid + \": \" + mainClass;\n\n\t\t\t// Create title controls\n\t\t\tboolean canConnect = attachManager.getVirtualMachineConnectionFailure(descriptor) == null;\n\t\t\tCarbonIcons titleIcon = canConnect ? CarbonIcons.DEBUG : CarbonIcons.ERROR_FILLED;\n\t\t\tFontIconView titleGraphic = new FontIconView(titleIcon, 28, canConnect ? Color.LIME.brighter() : Color.RED);\n\t\t\tButton connectButton = new ActionButton(titleGraphic, getBinding(\"attach.connect\"), () -> {\n\t\t\t\tif (workspaceManager.closeCurrent()) {\n\t\t\t\t\tThreadUtil.run(() -> {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tWorkspaceRemoteVmResource vmResource = attachManager.createRemoteResource(descriptor);\n\t\t\t\t\t\t\tvmResource.connect();\n\t\t\t\t\t\t\tworkspaceManager.setCurrent(new BasicWorkspace(vmResource));\n\t\t\t\t\t\t\tconnectedVm.setValue(descriptor);\n\t\t\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\t\t\tlogger.error(\"Failed to connect to remote VM: {}\", label, ex);\n\t\t\t\t\t\t\tErrorDialogs.show(getBinding(\"dialog.error.attach.title\"),\n\t\t\t\t\t\t\t\t\tgetBinding(\"dialog.error.attach.header\"),\n\t\t\t\t\t\t\t\t\tgetBinding(\"dialog.error.attach.content\"),\n\t\t\t\t\t\t\t\t\tex);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t\t// Give the button a rounded appearance, which becomes solid\n\t\t\tconnectButton.setMinWidth(120);\n\t\t\tconnectButton.getStyleClass().add(Styles.ROUNDED);\n\t\t\tconnectButton.setFocusTraversable(false);\n\t\t\tconnectedVm.addChangeListener((obs, old, cur) -> {\n\t\t\t\tif (cur == descriptor) {\n\t\t\t\t\tconnectButton.getStyleClass().addAll(Styles.ACCENT, Styles.SUCCESS);\n\t\t\t\t\tconnectButton.setMouseTransparent(true); // Disable clicking again until disconnected\n\t\t\t\t} else {\n\t\t\t\t\tconnectButton.getStyleClass().removeAll(Styles.ACCENT, Styles.SUCCESS);\n\t\t\t\t\tconnectButton.setMouseTransparent(false);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Layout\n\t\t\tLabel title = new Label(label);\n\t\t\ttitle.getStyleClass().add(Styles.TEXT_CAPTION);\n\t\t\ttitle.setPadding(new Insets(10));\n\t\t\tHBox titleWrapper = new HBox(connectButton, title);\n\t\t\ttitleWrapper.setAlignment(Pos.CENTER_LEFT);\n\t\t\ttitleWrapper.setPadding(new Insets(10));\n\t\t\ttitleWrapper.setSpacing(10);\n\n\t\t\t// Layout\n\t\t\tgetChildren().addAll(titleWrapper, contentGrid);\n\t\t}\n\n\t\t/**\n\t\t * @return Display title.\n\t\t */\n\t\tpublic String getLabel() {\n\t\t\treturn label;\n\t\t}\n\n\t\t/**\n\t\t * Updates the content display.\n\t\t */\n\t\tvoid update() {\n\t\t\tcontentGrid.update();\n\t\t}\n\n\t\t/**\n\t\t * Wrapper of multiple content titles.\n\t\t */\n\t\tprivate static class ContentTabs extends TabPane {\n\t\t\tprivate final List<AbstractContentTile> tiles = new ArrayList<>();\n\n\t\t\tpublic ContentTabs(AttachManager attachManager, VirtualMachineDescriptor descriptor) {\n\t\t\t\t// Property tile\n\t\t\t\tadd(new AbstractContentTile() {\n\t\t\t\t\tprivate final TableView<String> propertyTable = new TableView<>();\n\t\t\t\t\tprivate Properties lastProperties;\n\n\t\t\t\t\t@Override\n\t\t\t\t\tvoid setup() {\n\t\t\t\t\t\tTableColumn<String, String> keyColumn = new TableColumn<>(\"Key\");\n\t\t\t\t\t\tkeyColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue()));\n\t\t\t\t\t\tTableColumn<String, String> valueColumn = new TableColumn<>(\"Value\");\n\t\t\t\t\t\tvalueColumn.setCellValueFactory(param -> new SimpleStringProperty(Objects.toString(lastProperties.get(param.getValue()))));\n\t\t\t\t\t\tvalueColumn.setCellFactory(TextFieldTableCell.forTableColumn());\n\n\t\t\t\t\t\tObservableList<TableColumn<String, ?>> columns = propertyTable.getColumns();\n\t\t\t\t\t\tcolumns.add(keyColumn);\n\t\t\t\t\t\tcolumns.add(valueColumn);\n\n\t\t\t\t\t\tpropertyTable.setEditable(true);\n\t\t\t\t\t\tpropertyTable.getStyleClass().addAll(Styles.STRIPED, Tweaks.EDGE_TO_EDGE);\n\t\t\t\t\t\tpropertyTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);\n\t\t\t\t\t\tkeyColumn.setMaxWidth(1f * Integer.MAX_VALUE * 25);\n\t\t\t\t\t\tvalueColumn.setMaxWidth(1f * Integer.MAX_VALUE * 75);\n\t\t\t\t\t\tvalueColumn.setEditable(true); // Allow double-clicking to allow interaction with the value text\n\n\t\t\t\t\t\tsetCenter(propertyTable);\n\t\t\t\t\t}\n\n\t\t\t\t\t@Override\n\t\t\t\t\tvoid update() {\n\t\t\t\t\t\t// Update property table if there are changes\n\t\t\t\t\t\tProperties properties = attachManager.getVirtualMachineProperties(descriptor);\n\t\t\t\t\t\tif (Objects.equals(lastProperties, properties)) return;\n\t\t\t\t\t\tlastProperties = properties;\n\n\t\t\t\t\t\t// Update table\n\t\t\t\t\t\tObservableList<String> items = propertyTable.getItems();\n\t\t\t\t\t\tList<String> keys = properties.keySet().stream().map(Object::toString).sorted().toList();\n\t\t\t\t\t\titems.clear();\n\t\t\t\t\t\titems.addAll(keys);\n\t\t\t\t\t}\n\n\t\t\t\t\t@Override\n\t\t\t\t\tTab tab() {\n\t\t\t\t\t\tTab tab = new Tab();\n\t\t\t\t\t\ttab.setClosable(false);\n\t\t\t\t\t\ttab.setGraphic(new FontIconView(CarbonIcons.SETTINGS));\n\t\t\t\t\t\ttab.textProperty().bind(getBinding(\"attach.tab.properties\"));\n\t\t\t\t\t\treturn tab;\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// JMX tiles\n\t\t\t\tJmxBeanServerConnection jmxConnection = attachManager.getJmxServerConnection(descriptor);\n\t\t\t\tif (jmxConnection == null) {\n\t\t\t\t\tlogger.warn(\"Failed to get JMX connection for descriptor: {}\", descriptor);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tList<JmxWrapper> beanSuppliers = List.of(\n\t\t\t\t\t\tnew JmxWrapper(CarbonIcons.OBJECT_STORAGE, \"attach.tab.classloading\", jmxConnection::getClassloadingBeanInfo),\n\t\t\t\t\t\tnew JmxWrapper(CarbonIcons.QUERY_QUEUE, \"attach.tab.compilation\", jmxConnection::getCompilationBeanInfo),\n\t\t\t\t\t\tnew JmxWrapper(CarbonIcons.SCREEN, \"attach.tab.system\", jmxConnection::getOperatingSystemBeanInfo),\n\t\t\t\t\t\tnew JmxWrapper(CarbonIcons.METER, \"attach.tab.runtime\", jmxConnection::getRuntimeBeanInfo),\n\t\t\t\t\t\tnew JmxWrapper(CarbonIcons.PARENT_CHILD, \"attach.tab.thread\", jmxConnection::getThreadBeanInfo)\n\t\t\t\t);\n\t\t\t\tfor (JmxWrapper wrapper : beanSuppliers) {\n\t\t\t\t\tadd(new AbstractContentTile() {\n\t\t\t\t\t\tprivate final TableView<String> propertyTable = new TableView<>();\n\t\t\t\t\t\tprivate Map<String, String> lastAttributeMap;\n\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tvoid setup() {\n\t\t\t\t\t\t\tTableColumn<String, String> keyColumn = new TableColumn<>(\"Key\");\n\t\t\t\t\t\t\tkeyColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue()));\n\n\t\t\t\t\t\t\tTableColumn<String, String> valueColumn = new TableColumn<>(\"Value\");\n\t\t\t\t\t\t\tvalueColumn.setCellValueFactory(param -> new SimpleStringProperty(Objects.toString(lastAttributeMap.get(param.getValue()))));\n\n\t\t\t\t\t\t\tObservableList<TableColumn<String, ?>> columns = propertyTable.getColumns();\n\t\t\t\t\t\t\tcolumns.add(keyColumn);\n\t\t\t\t\t\t\tcolumns.add(valueColumn);\n\n\t\t\t\t\t\t\tpropertyTable.getStyleClass().addAll(Styles.STRIPED, Tweaks.EDGE_TO_EDGE);\n\t\t\t\t\t\t\tpropertyTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);\n\t\t\t\t\t\t\tkeyColumn.setMaxWidth(1f * Integer.MAX_VALUE * 25);\n\t\t\t\t\t\t\tvalueColumn.setMaxWidth(1f * Integer.MAX_VALUE * 75);\n\n\t\t\t\t\t\t\tsetCenter(propertyTable);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tvoid update() {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t// Update attribute table if there are changes.\n\t\t\t\t\t\t\t\t//\n\t\t\t\t\t\t\t\t// The bean supplier can block, and if the connection has died this block will go until\n\t\t\t\t\t\t\t\t// a timeout period elapses. So we want to do this in the background.\n\t\t\t\t\t\t\t\tCompletableFuture.supplyAsync(() -> {\n\t\t\t\t\t\t\t\t\tNamedMBeanInfo beanInfo = wrapper.beanSupplier().get();\n\t\t\t\t\t\t\t\t\tMBeanAttributeInfo[] attributes = beanInfo.getAttributes();\n\t\t\t\t\t\t\t\t\treturn Arrays.stream(attributes)\n\t\t\t\t\t\t\t\t\t\t\t.collect(Collectors.toMap(MBeanFeatureInfo::getDescription, attribute -> {\n\t\t\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\t\t\tObject value = beanInfo.getAttributeValue(jmxConnection, attribute);\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (value != null) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (value.getClass().isArray()) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tvalue = Arrays.toString(convertToObjectArray(value));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn Objects.toString(value);\n\t\t\t\t\t\t\t\t\t\t\t\t} catch (Exception ex) {\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn \"?\";\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}));\n\n\t\t\t\t\t\t\t\t}).whenCompleteAsync((attributeMap, error) -> {\n\t\t\t\t\t\t\t\t\tif (Objects.equals(lastAttributeMap, attributeMap)) return;\n\t\t\t\t\t\t\t\t\tlastAttributeMap = attributeMap;\n\n\t\t\t\t\t\t\t\t\t// Update table\n\t\t\t\t\t\t\t\t\tObservableList<String> items = propertyTable.getItems();\n\t\t\t\t\t\t\t\t\tList<String> keys = attributeMap.keySet().stream().map(Object::toString).sorted().toList();\n\t\t\t\t\t\t\t\t\titems.clear();\n\t\t\t\t\t\t\t\t\titems.addAll(keys);\n\n\t\t\t\t\t\t\t\t\t// Enable on success\n\t\t\t\t\t\t\t\t\tsetDisable(false);\n\t\t\t\t\t\t\t\t}, FxThreadUtil.executor());\n\t\t\t\t\t\t\t} catch (Exception ex) {\n\t\t\t\t\t\t\t\t// Disable on failure\n\t\t\t\t\t\t\t\tsetDisable(true);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t@Override\n\t\t\t\t\t\tTab tab() {\n\t\t\t\t\t\t\tTab tab = new Tab();\n\t\t\t\t\t\t\ttab.setClosable(false);\n\t\t\t\t\t\t\ttab.setGraphic(new FontIconView(wrapper.icon()));\n\t\t\t\t\t\t\ttab.textProperty().bind(getBinding(wrapper.langKey()));\n\t\t\t\t\t\t\treturn tab;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @param tile\n\t\t\t * \t\tTile to add to the content grid.\n\t\t\t */\n\t\t\tprivate void add(AbstractContentTile tile) {\n\t\t\t\ttile.setup();\n\t\t\t\ttiles.add(tile);\n\t\t\t\tTab tab = tile.tab();\n\t\t\t\ttab.setContent(tile);\n\t\t\t\tgetTabs().add(tab);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Updates all blocks.\n\t\t\t */\n\t\t\tpublic void update() {\n\t\t\t\tfor (AbstractContentTile block : tiles)\n\t\t\t\t\tblock.update();\n\t\t\t}\n\n\t\t\t@SuppressWarnings(\"all\")\n\t\t\tstatic Object[] convertToObjectArray(Object array) {\n\t\t\t\tClass componentType = array.getClass().getComponentType();\n\t\t\t\tif (componentType.isPrimitive()) {\n\t\t\t\t\tint length = Array.getLength(array);\n\t\t\t\t\tObject[] boxedArray = new Object[length];\n\t\t\t\t\tfor (int i = 0; i < length; i++)\n\t\t\t\t\t\tboxedArray[i] = Array.get(array, i);\n\t\t\t\t\treturn boxedArray;\n\t\t\t\t} else {\n\t\t\t\t\treturn (Object[]) array;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Simple content tile.\n\t\t */\n\t\tprivate static abstract class AbstractContentTile extends BorderPane {\n\t\t\tabstract void setup();\n\n\t\t\tabstract void update();\n\n\t\t\tabstract Tab tab();\n\t\t}\n\n\t\t/**\n\t\t * Wrapper to hold both the given values.\n\t\t *\n\t\t * @param icon\n\t\t * \t\tGraphic icon representation.\n\t\t * @param langKey\n\t\t * \t\tIdentifier for language name lookup.\n\t\t * @param beanSupplier\n\t\t * \t\tSupplier to the current bean info.\n\t\t */\n\t\tprivate record JmxWrapper(Ikon icon, String langKey,\n\t\t                          UncheckedSupplier<NamedMBeanInfo> beanSupplier) {\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/ScriptManagerPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport atlantafx.base.controls.Popover;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.beans.binding.StringBinding;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Orientation;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.Separator;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.paint.Color;\nimport javafx.stage.FileChooser;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.compile.CompilerDiagnostic;\nimport software.coley.recaf.services.file.RecafDirectoriesConfig;\nimport software.coley.recaf.services.info.association.FileTypeSyntaxAssociationService;\nimport software.coley.recaf.services.script.ScriptEngine;\nimport software.coley.recaf.services.script.ScriptFile;\nimport software.coley.recaf.services.script.ScriptManager;\nimport software.coley.recaf.services.script.ScriptManagerConfig;\nimport software.coley.recaf.services.window.WindowFactory;\nimport software.coley.recaf.ui.config.KeybindingConfig;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.ScrollbarPaddingUtil;\nimport software.coley.recaf.ui.control.richtext.bracket.SelectedBracketTracking;\nimport software.coley.recaf.ui.control.richtext.problem.Problem;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemPhase;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemTracking;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.ui.window.RecafScene;\nimport software.coley.recaf.util.*;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.TreeSet;\n\nimport static software.coley.recaf.util.Lang.getBinding;\n\n/**\n * Pane to display available scripts.\n *\n * @author Matt Coley\n * @author yapht\n * @see ScriptManager Source of scripts to pull from.\n */\n@Dependent\npublic class ScriptManagerPane extends BorderPane {\n\tprivate static final Logger logger = Logging.get(ScriptManagerPane.class);\n\tprivate final VBox scriptList = new VBox();\n\tprivate final ScriptManager scriptManager;\n\tprivate final ScriptManagerConfig config;\n\tprivate final ScriptEngine engine;\n\tprivate final FileTypeSyntaxAssociationService languageAssociation;\n\tprivate final WindowFactory windowFactory;\n\tprivate final RecafDirectoriesConfig directories;\n\tprivate final KeybindingConfig keys;\n\tprivate final Instance<SearchBar> searchBarProvider;\n\n\t@Inject\n\tpublic ScriptManagerPane(@Nonnull ScriptManagerConfig config,\n\t                         @Nonnull ScriptManager scriptManager,\n\t                         @Nonnull ScriptEngine engine,\n\t                         @Nonnull FileTypeSyntaxAssociationService languageAssociation,\n\t                         @Nonnull WindowFactory windowFactory,\n\t                         @Nonnull RecafDirectoriesConfig directories,\n\t                         @Nonnull KeybindingConfig keys,\n\t                         @Nonnull Instance<SearchBar> searchBarProvider) {\n\t\tthis.windowFactory = windowFactory;\n\t\tthis.scriptManager = scriptManager;\n\t\tthis.config = config;\n\t\tthis.engine = engine;\n\t\tthis.languageAssociation = languageAssociation;\n\t\tthis.directories = directories;\n\t\tthis.keys = keys;\n\t\tthis.searchBarProvider = searchBarProvider;\n\n\t\tscriptManager.getScriptFiles().addChangeListener((ob, old, cur) -> refreshScripts());\n\t\trefreshScripts();\n\n\t\tscriptList.setFillWidth(true);\n\t\tscriptList.setSpacing(10);\n\t\tscriptList.setPadding(new Insets(10));\n\n\t\tScrollPane scroll = new ScrollPane(scriptList);\n\t\tscroll.getStyleClass().add(\"dark-scroll-pane\");\n\t\tscroll.setFitToWidth(true);\n\t\tsetCenter(scroll);\n\n\t\tHBox controls = new HBox();\n\t\tcontrols.setStyle(\"\"\"\n\t\t\t\t-fx-background-color: -color-bg-default;\n\t\t\t\t-fx-border-color: -color-border-default;\n\t\t\t\t-fx-border-width: 1 0 0 0;\n\t\t\t\t\"\"\");\n\t\tcontrols.setPadding(new Insets(10));\n\t\tcontrols.setSpacing(10);\n\t\tcontrols.getChildren().addAll(\n\t\t\t\tnew ActionButton(CarbonIcons.EDIT, getBinding(\"menu.scripting.new\"), this::newScript),\n\t\t\t\tnew ActionButton(CarbonIcons.FOLDER, getBinding(\"menu.scripting.browse\"), this::browse)\n\t\t);\n\t\tcontrols.getChildren().forEach(b -> b.getStyleClass().add(\"muted\"));\n\t\tsetBottom(controls);\n\t}\n\n\t/**\n\t * Repopulate the script list.\n\t */\n\tprivate void refreshScripts() {\n\t\tFxThreadUtil.run(() -> {\n\t\t\tscriptList.getChildren().clear();\n\t\t\tfor (ScriptFile scriptFile : new TreeSet<>(scriptManager.getScriptFiles())) {\n\t\t\t\tscriptList.getChildren().add(new ScriptEntry(scriptFile));\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * @param script\n\t * \t\tScript to edit.\n\t */\n\tprivate void editScript(@Nonnull ScriptFile script) {\n\t\tScriptEditor scriptEditor = new ScriptEditor(languageAssociation, script.source(), searchBarProvider.get())\n\t\t\t\t.withPath(script.path());\n\t\tScene scene = new RecafScene(scriptEditor, 750, 400);\n\t\twindowFactory.createAnonymousStage(scene, getBinding(\"menu.scripting.editor\"), 750, 400).show();\n\t}\n\n\t/**\n\t * Opens a new script editor.\n\t */\n\tpublic void newScript() {\n\t\t// TODO: Editor save prompts file location to save to in scripts dir\n\t\t//  - Add toggle in manager button list to create 'advanced' script using class model\n\t\tString template = \"\"\"\n\t\t\t\t// ==Metadata==\n\t\t\t\t// @name Name\n\t\t\t\t// @description Description\n\t\t\t\t// @version 1.0.0\n\t\t\t\t// @author Author\n\t\t\t\t// ==/Metadata==\n\t\t\t\t\t\t\t\t\n\t\t\t\tSystem.out.println(\"Hello world\");\n\t\t\t\t\"\"\";\n\t\tScriptEditor scriptEditor = new ScriptEditor(languageAssociation, template, searchBarProvider.get());\n\t\tScene scene = new RecafScene(scriptEditor, 750, 400);\n\t\twindowFactory.createAnonymousStage(scene, getBinding(\"menu.scripting.editor\"), 750, 400).show();\n\t}\n\n\t/**\n\t * Opens the local scripts directory.\n\t */\n\tprivate void browse() {\n\t\ttry {\n\t\t\tDesktopUtil.showDocument(config.getScriptsDirectory().toUri());\n\t\t} catch (IOException ex) {\n\t\t\tlogger.error(\"Failed to show scripts directory\", ex);\n\t\t}\n\t}\n\n\t/**\n\t * Editor for scripts.\n\t */\n\tprivate class ScriptEditor extends BorderPane {\n\t\tprivate final ProblemTracking problemTracking = new ProblemTracking();\n\t\tprivate final Editor editor = new Editor();\n\t\tprivate Path scriptPath;\n\n\t\tprivate ScriptEditor(@Nonnull FileTypeSyntaxAssociationService associationService, @Nonnull String initialText, @Nonnull SearchBar searchBar) {\n\t\t\teditor.setText(initialText);\n\t\t\teditor.getCodeArea().getUndoManager().forgetHistory();\n\t\t\tassociationService.configureEditorSyntax(\"java\", editor);\n\t\t\teditor.setSelectedBracketTracking(new SelectedBracketTracking());\n\t\t\teditor.setProblemTracking(problemTracking);\n\t\t\teditor.getRootLineGraphicFactory().addDefaultCodeGraphicFactories();\n\n\t\t\t// Add extra components\n\t\t\tsearchBar.install(editor);\n\t\t\teditor.getPrimaryStack().getChildren().add(new RunScriptComponent());\n\n\t\t\t// Setup keybindings\n\t\t\tsetOnKeyPressed(e -> {\n\t\t\t\tif (keys.getSave().match(e))\n\t\t\t\t\tsave();\n\t\t\t});\n\n\t\t\t// Layout\n\t\t\tsetCenter(editor);\n\t\t}\n\n\t\t/**\n\t\t * @param scriptPath\n\t\t * \t\tLocation on disk to save to path to.\n\t\t *\n\t\t * @return Self.\n\t\t */\n\t\t@Nonnull\n\t\tpublic ScriptEditor withPath(@Nonnull Path scriptPath) {\n\t\t\tthis.scriptPath = scriptPath;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * Save the script to {@link #scriptPath}.\n\t\t */\n\t\tprivate void save() {\n\t\t\t// Clear old errors emitted by compilation.\n\t\t\tproblemTracking.removeByPhase(ProblemPhase.BUILD);\n\n\t\t\t// Invoke compiler with data.\n\t\t\tengine.compile(editor.getText()).whenCompleteAsync((result, throwable) -> {\n\t\t\t\tif (result != null && result.wasSuccess()) {\n\t\t\t\t\t// Don't care about compilation, just wanted to validate it was valid semantics.\n\t\t\t\t\tAnimations.animateSuccess(this, 1000);\n\t\t\t\t} else {\n\t\t\t\t\t// Handle compile-result failure, or uncaught thrown exception.\n\t\t\t\t\tif (result != null) {\n\t\t\t\t\t\tfor (CompilerDiagnostic diagnostic : result.diagnostics())\n\t\t\t\t\t\t\tproblemTracking.addItem(Problem.fromDiagnostic(diagnostic));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger.error(\"Compilation encountered an error\", throwable);\n\t\t\t\t\t}\n\t\t\t\t\tAnimations.animateFailure(this, 1000);\n\t\t\t\t}\n\n\t\t\t\t// Write to disk regardless, if path is given. If not given, prompt user for it.\n\t\t\t\tif (scriptPath == null) {\n\t\t\t\t\tFileChooser chooser = new FileChooserBuilder()\n\t\t\t\t\t\t\t.setInitialDirectory(directories.getScriptsDirectory())\n\t\t\t\t\t\t\t.setTitle(Lang.get(\"dialog.file.save\"))\n\t\t\t\t\t\t\t.build();\n\n\t\t\t\t\tFile selected = chooser.showSaveDialog(getScene().getWindow());\n\t\t\t\t\tif (selected != null)\n\t\t\t\t\t\tscriptPath = selected.toPath();\n\t\t\t\t}\n\t\t\t\tif (scriptPath != null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tFiles.writeString(scriptPath, editor.getText());\n\t\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\t\tlogger.error(\"Failed to write to script file\", ex);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Redraw paragraph graphics to update things like in-line problem graphics.\n\t\t\t\teditor.redrawParagraphGraphics();\n\t\t\t}, FxThreadUtil.executor());\n\t\t}\n\n\t\t/**\n\t\t * Editor component to call {@link ScriptEngine#run(String)}.\n\t\t */\n\t\tprivate class RunScriptComponent extends ActionButton {\n\t\t\tprivate RunScriptComponent() {\n\t\t\t\tsuper(new FontIconView(CarbonIcons.PLAY_FILLED, Color.LIME), Lang.getBinding(\"menu.scripting.execute\"),\n\t\t\t\t\t\t() -> {\n\t\t\t\t\t\t\tproblemTracking.removeByPhase(ProblemPhase.BUILD);\n\t\t\t\t\t\t\tengine.run(editor.getText()).whenCompleteAsync((result, throwable) -> {\n\t\t\t\t\t\t\t\tif (result != null && result.wasSuccess()) {\n\t\t\t\t\t\t\t\t\t// Don't care about compilation, just wanted to validate it was valid semantics.\n\t\t\t\t\t\t\t\t\tAnimations.animateSuccess(editor, 1000);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// Handle compile-result failure, or uncaught thrown exception.\n\t\t\t\t\t\t\t\t\tif (result != null) {\n\t\t\t\t\t\t\t\t\t\tfor (CompilerDiagnostic diagnostic : result.getCompileDiagnostics())\n\t\t\t\t\t\t\t\t\t\t\tproblemTracking.addItem(Problem.fromDiagnostic(diagnostic));\n\n\t\t\t\t\t\t\t\t\t\t// Display runtime error if given.\n\t\t\t\t\t\t\t\t\t\tThrowable runtimeThrowable = result.getRuntimeThrowable();\n\t\t\t\t\t\t\t\t\t\tif (runtimeThrowable != null) {\n\t\t\t\t\t\t\t\t\t\t\tLabel traceString = new Label(StringUtil.traceToString(runtimeThrowable));\n\t\t\t\t\t\t\t\t\t\t\ttraceString.setGraphic(new FontIconView(CarbonIcons.ERROR, Color.RED));\n\n\t\t\t\t\t\t\t\t\t\t\tPopover popover = new Popover();\n\t\t\t\t\t\t\t\t\t\t\tpopover.setArrowLocation(Popover.ArrowLocation.BOTTOM_RIGHT);\n\t\t\t\t\t\t\t\t\t\t\tpopover.setContentNode(traceString);\n\n\t\t\t\t\t\t\t\t\t\t\t// Hack to get self\n\t\t\t\t\t\t\t\t\t\t\tObservableList<Node> children = editor.getPrimaryStack().getChildrenUnmodifiable();\n\t\t\t\t\t\t\t\t\t\t\tpopover.show(children.get(children.size() - 1));\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tlogger.error(\"Compilation encountered an error\", throwable);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tAnimations.animateFailure(editor, 1000);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Redraw paragraph graphics to update things like in-line problem graphics.\n\t\t\t\t\t\t\t\teditor.redrawParagraphGraphics();\n\t\t\t\t\t\t\t}, FxThreadUtil.executor());\n\t\t\t\t\t\t});\n\n\t\t\t\t// Layout tweaks\n\t\t\t\tStackPane.setAlignment(this, Pos.BOTTOM_RIGHT);\n\t\t\t\tStackPane.setMargin(this, new Insets(7));\n\t\t\t\teditor.getVerticalScrollbar().visibleProperty()\n\t\t\t\t\t\t.addListener((ob, old, cur) -> ScrollbarPaddingUtil.handleScrollbarVisibility(this, cur));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Entry showing the script details + run/edit action buttons.\n\t */\n\tprivate class ScriptEntry extends BorderPane {\n\t\tprivate ScriptEntry(ScriptFile script) {\n\t\t\tsetPadding(new Insets(10));\n\t\t\tgetStyleClass().add(\"tooltip\");\n\n\t\t\tLabel nameLabel = new Label(script.name());\n\t\t\tnameLabel.setWrapText(true);\n\t\t\tnameLabel.setMinSize(350, 20);\n\t\t\tnameLabel.setMaxWidth(550);\n\t\t\tnameLabel.getStyleClass().add(Styles.TITLE_3);\n\n\t\t\tVBox info = new VBox();\n\t\t\tinfo.getChildren().add(nameLabel);\n\n\t\t\tString description = script.description();\n\t\t\tString author = script.author();\n\t\t\tString version = script.version();\n\t\t\tString url = script.getTagValue(\"url\");\n\n\t\t\tif (!description.isBlank())\n\t\t\t\tinfo.getChildren().add(makeAttribLabel(null, EscapeUtil.unescapeStandard(description)));\n\t\t\tif (!author.isBlank())\n\t\t\t\tinfo.getChildren().add(makeAttribLabel(getBinding(\"menu.scripting.author\"), author));\n\t\t\tif (!version.isBlank())\n\t\t\t\tinfo.getChildren().add(makeAttribLabel(getBinding(\"menu.scripting.version\"), version));\n\t\t\tif (!url.isBlank()) {\n\t\t\t\tinfo.getChildren().add(makeAttribLabel(new StringBinding() {\n\t\t\t\t\t@Override\n\t\t\t\t\tprotected String computeValue() {\n\t\t\t\t\t\treturn \"URL\";\n\t\t\t\t\t}\n\t\t\t\t}, url));\n\t\t\t}\n\n\t\t\tVBox actions = new VBox();\n\t\t\tactions.setSpacing(4);\n\t\t\tactions.setAlignment(Pos.CENTER_RIGHT);\n\n\n\t\t\tScriptEntry entry = this;\n\t\t\tButton executeButton = new ActionButton(CarbonIcons.PLAY_FILLED_ALT, getBinding(\"menu.scripting.execute\"), () -> {\n\t\t\t\tscript.execute(engine)\n\t\t\t\t\t\t.whenComplete((result, error) -> {\n\t\t\t\t\t\t\tif (result != null && result.wasSuccess()) {\n\t\t\t\t\t\t\t\tAnimations.animateSuccess(entry, 1000);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tAnimations.animateFailure(entry, 1000);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t});\n\t\t\texecuteButton.setAlignment(Pos.CENTER_LEFT);\n\t\t\texecuteButton.setPrefSize(130, 30);\n\n\t\t\tButton editButton = new ActionButton(CarbonIcons.EDIT, getBinding(\"menu.scripting.edit\"), () -> editScript(script));\n\t\t\teditButton.setAlignment(Pos.CENTER_LEFT);\n\t\t\teditButton.setPrefSize(130, 30);\n\n\t\t\tactions.getChildren().addAll(executeButton, editButton);\n\n\t\t\tSeparator separator = new Separator(Orientation.HORIZONTAL);\n\t\t\tseparator.prefWidthProperty().bind(scriptList.widthProperty());\n\n\t\t\tsetLeft(info);\n\t\t\tsetRight(actions);\n\n\t\t\tprefWidthProperty().bind(widthProperty());\n\t\t}\n\n\t\t/**\n\t\t * Used to display bullet point format.\n\t\t *\n\t\t * @param langBinding\n\t\t * \t\tLanguage binding for label display.\n\t\t * @param secondaryText\n\t\t * \t\tText to appear after the initial binding text.\n\t\t *\n\t\t * @return Label bound to translatable text.\n\t\t */\n\t\tprivate static Label makeAttribLabel(StringBinding langBinding, String secondaryText) {\n\t\t\tLabel label = new Label(secondaryText);\n\t\t\tlabel.setWrapText(true);\n\t\t\tlabel.setMaxWidth(550);\n\t\t\tif (langBinding != null) {\n\t\t\t\tlabel.textProperty().bind(new StringBinding() {\n\t\t\t\t\t{\n\t\t\t\t\t\tbind(langBinding);\n\t\t\t\t\t}\n\n\t\t\t\t\t@Override\n\t\t\t\t\tprotected String computeValue() {\n\t\t\t\t\t\treturn String.format(\"  • %s: %s\", langBinding.get(), secondaryText);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn label;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/SystemInformationPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Separator;\nimport javafx.scene.input.Clipboard;\nimport javafx.scene.input.ClipboardContent;\nimport javafx.scene.layout.GridPane;\nimport org.slf4j.Logger;\nimport software.coley.recaf.RecafBuildConfig;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.file.RecafDirectoriesConfig;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.SubLabeled;\nimport software.coley.recaf.util.DesktopUtil;\nimport software.coley.recaf.util.StringUtil;\n\nimport javax.tools.ToolProvider;\nimport java.nio.file.Path;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport static software.coley.recaf.util.Lang.getBinding;\n\n/**\n * Panel that shows system information.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class SystemInformationPane extends GridPane {\n\tprivate static final Logger logger = Logging.get(SystemInformationPane.class);\n\tprivate static final int SEP_SIZE = 2;\n\n\t@Inject\n\tpublic SystemInformationPane(@Nonnull RecafDirectoriesConfig directories) {\n\t\tPath baseDir = directories.getBaseDirectory();\n\n\t\t// Grid config\n\t\tsetVgap(4);\n\t\tsetHgap(5);\n\t\tsetPadding(new Insets(15));\n\t\tsetAlignment(Pos.CENTER);\n\t\tint r = 0;\n\n\t\t// System\n\t\taddRow(r++, new SubLabeled(getBinding(\"help.system\"),\n\t\t\t\tgetBinding(\"help.system.sub\")));\n\t\taddRow(r++, new Label(\"Name\"), new Label(System.getProperty(\"os.name\")));\n\t\taddRow(r++, new Label(\"Architecture\"), new Label(System.getProperty(\"os.arch\")));\n\t\taddRow(r++, new Label(\"Processors\"), new Label(String.valueOf(Runtime.getRuntime().availableProcessors())));\n\t\tadd(new Separator(), 0, (SEP_SIZE - 1) + r, 2, SEP_SIZE);\n\t\tr += (SEP_SIZE + 1);\n\n\t\t// Java\n\t\taddRow(r++, new SubLabeled(getBinding(\"help.java\"), getBinding(\"help.java.sub\")));\n\t\taddRow(r++, new Label(\"Version\"), new Label(System.getProperty(\"java.version\")));\n\t\taddRow(r++, new Label(\"VM name\"), new Label(System.getProperty(\"java.vm.name\")));\n\t\taddRow(r++, new Label(\"VM vendor\"), new Label(System.getProperty(\"java.vm.vendor\")));\n\t\taddRow(r++, new Label(\"Home\"), new Label(System.getProperty(\"java.home\")));\n\t\taddRow(r++, new Label(\"Supports compiler\"), new Label(Boolean.toString(\n\t\t\t\tToolProvider.getSystemJavaCompiler() != null\n\t\t)));\n\t\tadd(new Separator(), 0, (SEP_SIZE - 1) + r, 2, SEP_SIZE);\n\t\tr += (SEP_SIZE + 1);\n\n\t\t// JavaFX\n\t\taddRow(r++, new SubLabeled(getBinding(\"help.javafx\"),\n\t\t\t\tgetBinding(\"help.javafx.sub\")));\n\t\taddRow(r++, new Label(\"Version\"), new Label(System.getProperty(\"javafx.version\")));\n\t\tadd(new Separator(), 0, (SEP_SIZE - 1) + r, 2, SEP_SIZE);\n\t\tr += (SEP_SIZE + 1);\n\n\t\t// Recaf\n\t\taddRow(r++, new SubLabeled(getBinding(\"help.recaf\"), getBinding(\"help.recaf.sub\")));\n\t\taddRow(r++, new Label(\"Version\"), new Label(RecafBuildConfig.VERSION));\n\t\taddRow(r++, new Label(\"Build\"), new Label(RecafBuildConfig.GIT_SHA.substring(0, 7) + \" \" + RecafBuildConfig.GIT_DATE));\n\t\taddRow(r++, new Label(\"Settings directory\"), new Label(StringUtil.pathToAbsoluteString(baseDir.toAbsolutePath())));\n\t\tadd(new Separator(), 0, (SEP_SIZE - 1) + r, 2, SEP_SIZE);\n\t\tr += (SEP_SIZE + 1);\n\n\t\t// Copy\n\t\taddRow(r, new ActionButton(getBinding(\"help.copy\"), () -> {\n\t\t\tClipboard clip = Clipboard.getSystemClipboard();\n\t\t\tClipboardContent content = new ClipboardContent();\n\t\t\tcontent.putString(buildClipboard());\n\t\t\tclip.setContent(content);\n\t\t}), new ActionButton(getBinding(\"help.opendir\"), () -> {\n\t\t\ttry {\n\t\t\t\tDesktopUtil.showDocument(baseDir.toUri());\n\t\t\t} catch (Exception ex) {\n\t\t\t\tlogger.error(\"Failed to open Recaf directory\", ex);\n\t\t\t}\n\t\t}));\n\t}\n\n\t@Override\n\tpublic void addRow(int rowIndex, Node... children) {\n\t\tsuper.addRow(rowIndex, children);\n\t\tif (children[0].getClass() == Label.class)\n\t\t\tchildren[0].getStyleClass().add(Styles.TEXT_BOLD);\n\t}\n\n\t/**\n\t * Converts the UI to markdown text.\n\t * Section headers are declared with {@link SubLabeled}.\n\t * Section Key/Value pairs are declared with two consecutive {@link Label}.\n\t *\n\t * @return Markdown string containing all UI elements.\n\t */\n\tpublic String buildClipboard() {\n\t\t// Data collection\n\t\tMap<String, Map<String, String>> data = new LinkedHashMap<>();\n\n\t\t// Current section title/map\n\t\tString currentSection = null;\n\t\tMap<String, String> currentMap = null;\n\n\t\t// Current section key\n\t\tboolean labelIsKey = true;\n\t\tString currentKey = null;\n\t\tfor (Node node : getChildren()) {\n\t\t\t// header\n\t\t\tif (node.getClass() == SubLabeled.class) {\n\t\t\t\tif (currentMap != null) {\n\t\t\t\t\tdata.put(currentSection, currentMap);\n\t\t\t\t}\n\t\t\t\tSubLabeled header = (SubLabeled) node;\n\t\t\t\tcurrentSection = header.getPrimaryText();\n\t\t\t\tcurrentMap = new LinkedHashMap<>();\n\t\t\t}\n\t\t\t// items (key:value), one follows the other\n\t\t\telse if (node.getClass() == Label.class) {\n\t\t\t\tString text = ((Label) node).getText();\n\t\t\t\tif (labelIsKey) {\n\t\t\t\t\tcurrentKey = text;\n\t\t\t\t} else {\n\t\t\t\t\tcurrentMap.put(currentKey, text);\n\t\t\t\t}\n\t\t\t\t// Swap since we do KEY -> VALUE, KEY -> VALUE\n\t\t\t\tlabelIsKey = !labelIsKey;\n\t\t\t}\n\t\t}\n\t\tdata.put(currentSection, currentMap); // Add dangling map\n\n\t\t// Put to string\n\t\tStringBuilder sb = new StringBuilder();\n\t\tdata.forEach((section, map) -> {\n\t\t\tsb.append(\"**\").append(section).append(\"**\\n\")\n\t\t\t\t\t.append(\"| \").append(String.join(\" | \", map.keySet())).append(\" |\\n\")\n\t\t\t\t\t.append(\"| \").append(map.keySet().stream()\n\t\t\t\t\t\t\t.map(s -> \"--------\").collect(Collectors.joining(\" | \"))).append(\" |\\n\")\n\t\t\t\t\t.append(\"| `\").append(String.join(\"` | `\", map.values())).append(\"` |\\n\\n\");\n\t\t});\n\t\treturn sb.toString();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/WelcomePane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Group;\nimport javafx.scene.Node;\nimport javafx.scene.control.Hyperlink;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.VBox;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.recaf.RecafBuildConfig;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.tutorial.TutorialConfig;\nimport software.coley.recaf.services.tutorial.TutorialWorkspaceBuilder;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.config.RecentFilesConfig;\nimport software.coley.recaf.ui.control.BoundHyperlink;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.dnd.DragAndDrop;\nimport software.coley.recaf.ui.dnd.FileDropListener;\nimport software.coley.recaf.ui.dnd.WorkspaceLoadingDropListener;\nimport software.coley.recaf.util.ErrorDialogs;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.IOUtil;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.threading.ThreadUtil;\nimport software.coley.recaf.workspace.PathLoadingManager;\n\nimport java.awt.Toolkit;\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static software.coley.recaf.util.Lang.getBinding;\n\n/**\n * Pane displayed when first opening Recaf.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class WelcomePane extends BorderPane implements Navigable {\n\tprivate static final Logger logger = Logging.get(WelcomePane.class);\n\n\t@Inject\n\tpublic WelcomePane(@Nonnull RecentFilesConfig recentFiles,\n\t                   @Nonnull PathLoadingManager pathLoadingManager,\n\t                   @Nonnull WorkspaceLoadingDropListener listener,\n\t                   @Nonnull WorkspaceManager workspaceManager,\n\t                   @Nonnull TutorialConfig tutorialConfig,\n\t                   @Nonnull TutorialWorkspaceBuilder tutorialWorkspaceBuilder) {\n\t\tFileDropListener disablingListener = (region, event, files) -> {\n\t\t\t// Disable when content is dragged over this panel while the workspace is loading.\n\t\t\t// There's not a great feedback mechanism without a goofy timeout so this silly hack works for now.\n\t\t\tsetDisable(true);\n\t\t\tFxThreadUtil.delayedRun(5000, () -> setDisable(false));\n\n\t\t\t// Delegate to the original listener.\n\t\t\tlistener.onDragDrop(region, event, files);\n\t\t};\n\t\tDragAndDrop.installFileSupport(this, disablingListener);\n\n\t\tint hyperlinkCount;\n\t\tBorderPane wrapper = new BorderPane();\n\t\tVBox versionPane = new VBox();\n\t\tVBox linksPane = new VBox();\n\t\tVBox recentsPane = new VBox();\n\t\tVBox bottomPane = new VBox();\n\t\tlinksPane.setPadding(new Insets(10));\n\t\trecentsPane.setPadding(new Insets(10));\n\t\tbottomPane.setPadding(new Insets(10));\n\t\t{\n\t\t\tString sha = RecafBuildConfig.GIT_SHA;\n\t\t\tLabel title = new Label(\"Recaf \" + RecafBuildConfig.VERSION);\n\t\t\tHyperlink subtitle = new BoundHyperlink(new SimpleStringProperty(\"Build \" + sha.substring(0, 7) + \" - \" + RecafBuildConfig.GIT_DATE), null, \"https://github.com/Col-E/Recaf/commit/\" + sha);\n\t\t\tLabel outdated = new Label(Duration.ofMillis(System.currentTimeMillis() - RecafBuildConfig.BUILD_UNIX_TIME).toDays() + \" \" + Lang.get(\"welcome.dayssince\"));\n\t\t\ttitle.getStyleClass().add(Styles.TITLE_2);\n\t\t\toutdated.getStyleClass().add(Styles.TEXT_SUBTLE);\n\t\t\tversionPane.setAlignment(Pos.CENTER);\n\t\t\tversionPane.getChildren().addAll(title, subtitle, outdated);\n\t\t}\n\t\t{\n\t\t\tLabel title = new BoundLabel(Lang.getBinding(\"welcome.links\"));\n\t\t\tHyperlink home = new BoundHyperlink(Lang.getBinding(\"welcome.links.home\"), Icons.getIconView(Icons.LOGO), \"https://recaf.coley.software/home.html\");\n\t\t\tHyperlink docUser = new BoundHyperlink(Lang.getBinding(\"welcome.links.docs-user\"), new FontIconView(CarbonIcons.PEDESTRIAN_CHILD), \"https://recaf.coley.software/user/index.html\");\n\t\t\tHyperlink docDev = new BoundHyperlink(Lang.getBinding(\"welcome.links.docs-dev\"), new FontIconView(CarbonIcons.PEDESTRIAN), \"https://recaf.coley.software/dev/index.html\");\n\t\t\tHyperlink git = new BoundHyperlink(Lang.getBinding(\"welcome.links.github\"), new FontIconView(CarbonIcons.LOGO_GITHUB), \"https://github.com/Col-E/Recaf\");\n\t\t\tHyperlink discord = new BoundHyperlink(Lang.getBinding(\"welcome.links.discord\"), Icons.getIconView(Icons.DISCORD), \"https://discord.gg/Bya5HaA\");\n\t\t\tHyperlink jvms = new BoundHyperlink(Lang.getBinding(\"welcome.links.jvms\"), Icons.getIconView(Icons.DUKE_THUMBS), \"https://docs.oracle.com/javase/specs/jvms/se25/html/index.html\");\n\t\t\tHyperlink jvmsClass = new BoundHyperlink(Lang.getBinding(\"welcome.links.jvms.class\"), Icons.getIconView(Icons.DUKE_TUMBLE), \"https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html\");\n\t\t\tHyperlink jvmsInsns = new BoundHyperlink(Lang.getBinding(\"welcome.links.jvms.instructions\"), Icons.getIconView(Icons.DUKE_THINK), \"https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-6.html\");\n\n\t\t\ttitle.getStyleClass().add(Styles.TITLE_2);\n\t\t\tdocUser.setPadding(new Insets(0, 0, 0, 20));\n\t\t\tdocDev.setPadding(new Insets(0, 0, 0, 20));\n\t\t\tgit.setPadding(new Insets(0, 0, 0, 20));\n\t\t\tdiscord.setPadding(new Insets(0, 0, 0, 20));\n\t\t\tjvmsClass.setPadding(new Insets(0, 0, 0, 20));\n\t\t\tjvmsInsns.setPadding(new Insets(0, 0, 0, 20));\n\t\t\tlinksPane.setAlignment(Pos.TOP_LEFT);\n\t\t\tlinksPane.getChildren().addAll(title, home, docUser, docDev, git, discord, jvms, jvmsClass, jvmsInsns);\n\t\t\tlinksPane.setSpacing(4);\n\t\t\thyperlinkCount = (int) linksPane.getChildren().stream().filter(n -> n instanceof Hyperlink).count();\n\t\t}\n\t\t{\n\t\t\tLabel title = new BoundLabel(Lang.getBinding(\"menu.file.recent\"));\n\t\t\ttitle.getStyleClass().add(Styles.TITLE_2);\n\n\t\t\trecentsPane.setAlignment(Pos.TOP_LEFT);\n\t\t\trecentsPane.getChildren().addAll(title);\n\t\t\trecentsPane.setSpacing(4);\n\n\t\t\tvar models = recentFiles.getRecentWorkspaces().getValue().stream()\n\t\t\t\t\t.filter(RecentFilesConfig.WorkspaceModel::canLoadWorkspace)\n\t\t\t\t\t.limit(hyperlinkCount)\n\t\t\t\t\t.toList();\n\t\t\tif (models.isEmpty())\n\t\t\t\trecentsPane.getChildren().add(new BoundLabel(Lang.getBinding(\"welcome.norecent\")));\n\t\t\tfor (RecentFilesConfig.WorkspaceModel model : models) {\n\t\t\t\tString primaryPathString = model.primary().path();\n\t\t\t\tString extension = IOUtil.getExtension(primaryPathString);\n\t\t\t\tNode graphic = new File(primaryPathString).isDirectory() ?\n\t\t\t\t\t\tIcons.getIconView(Icons.FOLDER) :\n\t\t\t\t\t\tIcons.getIconView(Icons.getIconPathForFileExtension(extension));\n\n\t\t\t\tHyperlink entry = new Hyperlink(model.primary().getSimpleName(), graphic);\n\t\t\t\tentry.setOnAction(e -> load(pathLoadingManager, model));\n\t\t\t\trecentsPane.getChildren().add(entry);\n\t\t\t}\n\t\t}\n\t\tif (!tutorialConfig.getFinishedTutorial().getValue()) {\n\t\t\tBoundLabel dndLabel = new BoundLabel(getBinding(\"welcome.dnd\"));\n\t\t\tdndLabel.getStyleClass().add(Styles.TEXT_SUBTLE);\n\n\t\t\tHyperlink tutorialLink = new BoundHyperlink(getBinding(\"welcome.tutorial\"), null, () -> {\n\t\t\t\tThreadUtil.run(() -> workspaceManager.setCurrent(tutorialWorkspaceBuilder.generateWorkspace()));\n\t\t\t});\n\n\t\t\tbottomPane.getChildren().addAll(dndLabel, tutorialLink);\n\t\t\tbottomPane.setAlignment(Pos.CENTER);\n\t\t}\n\t\tBorderPane middleWrapper = new BorderPane();\n\t\tmiddleWrapper.setLeft(linksPane);\n\t\tmiddleWrapper.setRight(recentsPane);\n\t\twrapper.setTop(versionPane);\n\t\twrapper.setCenter(middleWrapper);\n\t\twrapper.setBottom(bottomPane);\n\n\n\t\tVBox content = new VBox(new Group(wrapper));\n\t\tcontent.setFillWidth(true);\n\t\tcontent.setAlignment(Pos.CENTER);\n\t\tScrollPane scroll = new ScrollPane(content);\n\t\tscroll.setFitToWidth(true);\n\t\tscroll.setFitToHeight(true);\n\t\tscroll.maxHeightProperty().bind(heightProperty().subtract(5));\n\t\tsetCenter(scroll);\n\t\tgetStyleClass().add(Styles.BG_SUBTLE);\n\t}\n\n\tprivate void load(@Nonnull PathLoadingManager pathLoadingManager, @Nonnull RecentFilesConfig.WorkspaceModel model) {\n\t\t// Disable further interaction while loading\n\t\tsetDisable(true);\n\n\t\t// Get paths from model\n\t\tPath primaryPath = Paths.get(model.primary().path());\n\t\tList<Path> supportingPaths = model.libraries().stream()\n\t\t\t\t.map(resource -> Paths.get(resource.path()))\n\t\t\t\t.toList();\n\n\t\t// Pass to loader\n\t\tpathLoadingManager.asyncNewWorkspace(primaryPath, supportingPaths, ex -> {\n\t\t\tsetDisable(false);\n\t\t\tToolkit.getDefaultToolkit().beep();\n\t\t\tlogger.error(\"Failed to open recent workspace for '{}'\", model.primary().getSimpleName(), ex);\n\t\t\tErrorDialogs.show(\n\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.title\"),\n\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.header\"),\n\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.content\"),\n\t\t\t\t\tex\n\t\t\t);\n\t\t});\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn PathNodes.unique(\"welcome\");\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\t// no-op\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/WorkspaceBuilderPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport atlantafx.base.controls.Spacer;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ListChangeListener;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Group;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.input.Clipboard;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.paint.Color;\nimport javafx.stage.DirectoryChooser;\nimport javafx.stage.FileChooser;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.ui.config.RecentFilesConfig;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.dnd.DragAndDrop;\nimport software.coley.recaf.util.DirectoryChooserBuilder;\nimport software.coley.recaf.util.ErrorDialogs;\nimport software.coley.recaf.util.FileChooserBuilder;\nimport software.coley.recaf.util.IOUtil;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.PathLoadingManager;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.awt.Toolkit;\nimport java.io.File;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static software.coley.recaf.util.Lang.getBinding;\n\n/**\n * Pane to facilitate the creation and modification of {@link Workspace} contents.\n *\n * @author Matt Coley\n */\npublic class WorkspaceBuilderPane extends BorderPane {\n\tprivate static final Logger logger = Logging.get(WorkspaceBuilderPane.class);\n\tprivate final ObservableList<Path> paths = FXCollections.observableArrayList();\n\tprivate final ObjectProperty<Path> primary = new SimpleObjectProperty<>();\n\n\t/**\n\t * Builder pane for adding content to a given workspace.\n\t *\n\t * @param pathLoadingManager\n\t * \t\tLoading support.\n\t * @param recentFilesConfig\n\t * \t\tFile dialog locations.\n\t * @param workspace\n\t * \t\tWorkspace to append to.\n\t * @param onComplete\n\t * \t\tCompletion task.\n\t */\n\tpublic WorkspaceBuilderPane(@Nonnull PathLoadingManager pathLoadingManager,\n\t                            @Nonnull RecentFilesConfig recentFilesConfig,\n\t                            @Nonnull Workspace workspace,\n\t                            @Nonnull Runnable onComplete) {\n\t\t// Allow pasting file paths to append to the paths list.\n\t\taddEventFilter(KeyEvent.KEY_PRESSED, this::handlePaste);\n\n\t\t// Add dropped files to the paths list.\n\t\tDragAndDrop.installFileSupport(this, (region, event, files) -> {\n\t\t\tfor (Path path : files)\n\t\t\t\taddPath(path);\n\t\t});\n\n\t\t// Create the vertical list of paths.\n\t\tVBox flow = new VBox();\n\t\tflow.setPadding(new Insets(15));\n\t\tflow.setAlignment(Pos.CENTER);\n\t\tflow.setFillWidth(true);\n\t\tflow.setSpacing(15);\n\t\tObservableList<Node> flowNodes = flow.getChildren();\n\t\tpaths.addListener((ListChangeListener<Path>) change -> {\n\t\t\tList<Node> newNodes = new ArrayList<>(flowNodes);\n\t\t\twhile (change.next()) {\n\t\t\t\tfor (Path path : change.getRemoved())\n\t\t\t\t\tnewNodes.removeIf(child -> child instanceof FileEntry childEntry && path.equals(childEntry.path));\n\t\t\t\tfor (Path path : change.getAddedSubList())\n\t\t\t\t\tnewNodes.add(new FileEntry(path, false));\n\t\t\t}\n\t\t\tsetNodes(newNodes, flowNodes);\n\t\t});\n\n\t\t// Label to prompt users to drag/drop files here.\n\t\t// Only shown when there are no items.\n\t\tBoundLabel dropPromptLabel = new BoundLabel(Lang.getBinding(\"tree.prompt\"));\n\t\tdropPromptLabel.visibleProperty().bind(primary.isNull());\n\t\tdropPromptLabel.getStyleClass().addAll(Styles.TEXT_SUBTLE);\n\t\tdropPromptLabel.prefWidthProperty().bind(widthProperty());\n\t\tdropPromptLabel.setAlignment(Pos.CENTER);\n\t\tdropPromptLabel.setPadding(new Insets(15));\n\n\t\t// Button to load the workspace based on the order of paths given by the user.\n\t\tActionButton appendWorkspaceButton = new ActionButton(CarbonIcons.WORKSPACE_IMPORT, Lang.getBinding(\"menu.file.addtoworkspace\"), () -> {\n\t\t\tsetDisable(true);\n\t\t\tpathLoadingManager.asyncAddSupportingResourcesToWorkspace(workspace, paths, ex -> {\n\t\t\t\tToolkit.getDefaultToolkit().beep();\n\t\t\t\tlogger.error(\"Failed to load supporting resources from selected files.\", ex);\n\t\t\t\tErrorDialogs.show(\n\t\t\t\t\t\tgetBinding(\"dialog.error.loadsupport.title\"),\n\t\t\t\t\t\tgetBinding(\"dialog.error.loadsupport.header\"),\n\t\t\t\t\t\tgetBinding(\"dialog.error.loadsupport.content\"),\n\t\t\t\t\t\tex\n\t\t\t\t);\n\t\t\t}).whenComplete((_, error) -> {\n\t\t\t\tonComplete.run();\n\t\t\t});\n\t\t});\n\t\tappendWorkspaceButton.getStyleClass().addAll(Styles.BUTTON_OUTLINED);\n\n\t\t// Buttons to add additional paths.\n\t\tActionButton addDir = new ActionButton(CarbonIcons.FOLDER_ADD, Lang.getBinding(\"dialog.file.open.directory\"), () -> {\n\t\t\tFile recentOpenDir = recentFilesConfig.getLastWorkspaceOpenDirectory().unboxingMap(File::new);\n\t\t\tDirectoryChooser chooser = new DirectoryChooserBuilder()\n\t\t\t\t\t.setInitialDirectory(recentOpenDir)\n\t\t\t\t\t.setTitle(Lang.get(\"dialog.file.open\"))\n\t\t\t\t\t.build();\n\n\t\t\t// Show the prompt, update the paths list when complete\n\t\t\tFile file = chooser.showDialog(getScene().getWindow());\n\t\t\tif (file != null) {\n\t\t\t\tString parent = file.getParent();\n\t\t\t\tif (parent != null) recentFilesConfig.getLastWorkspaceOpenDirectory().setValue(parent);\n\t\t\t\taddPath(file.toPath());\n\t\t\t}\n\t\t});\n\t\tActionButton addFile = new ActionButton(CarbonIcons.DOCUMENT_ADD, Lang.getBinding(\"dialog.file.open.file\"), () -> {\n\t\t\tFile recentOpenDir = recentFilesConfig.getLastWorkspaceOpenDirectory().unboxingMap(File::new);\n\t\t\tFileChooser chooser = new FileChooserBuilder()\n\t\t\t\t\t.setInitialDirectory(recentOpenDir)\n\t\t\t\t\t.setTitle(Lang.get(\"dialog.file.open\"))\n\t\t\t\t\t.build();\n\n\t\t\t// Show the prompt, update the paths list when complete\n\t\t\tFile file = chooser.showOpenDialog(getScene().getWindow());\n\t\t\tif (file != null) {\n\t\t\t\tString parent = file.getParent();\n\t\t\t\tif (parent != null) recentFilesConfig.getLastWorkspaceOpenDirectory().setValue(parent);\n\t\t\t\taddPath(file.toPath());\n\t\t\t}\n\t\t});\n\t\taddDir.getStyleClass().addAll(Styles.BUTTON_OUTLINED, Styles.LEFT_PILL);\n\t\taddFile.getStyleClass().addAll(Styles.BUTTON_OUTLINED, Styles.RIGHT_PILL);\n\n\t\tHBox buttons = new HBox(\n\t\t\t\tnew HBox(addDir, addFile),\n\t\t\t\tnew Spacer(),\n\t\t\t\tappendWorkspaceButton\n\t\t);\n\t\tbuttons.setSpacing(10);\n\t\tbuttons.setAlignment(Pos.CENTER);\n\t\tbuttons.setPadding(new Insets(10));\n\n\t\tScrollPane flowWrapper = new ScrollPane(flow);\n\t\tflowWrapper.setFitToWidth(true);\n\t\tflowWrapper.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);\n\t\tflow.prefWidthProperty().bind(flowWrapper.widthProperty());\n\t\tsetTop(new Group(dropPromptLabel));\n\t\tsetCenter(flowWrapper);\n\t\tsetBottom(buttons);\n\n\t\tgetStyleClass().addAll(Styles.BG_INSET);\n\t\tbuttons.getStyleClass().addAll(Styles.BG_DEFAULT);\n\t\tbuttons.setStyle(\"-fx-border-color: -color-border-muted; -fx-border-width: 1px 0 0 0;\");\n\t}\n\n\t/**\n\t * Builder pane for creating a new workspace.\n\t *\n\t * @param pathLoadingManager\n\t * \t\tLoading support.\n\t * @param recentFilesConfig\n\t * \t\tFile dialog locations.\n\t * @param onComplete\n\t * \t\tCompletion task.\n\t */\n\tpublic WorkspaceBuilderPane(@Nonnull PathLoadingManager pathLoadingManager,\n\t                            @Nonnull RecentFilesConfig recentFilesConfig,\n\t                            @Nonnull Runnable onComplete) {\n\t\t// Allow pasting file paths to append to the paths list.\n\t\taddEventFilter(KeyEvent.KEY_PRESSED, this::handlePaste);\n\n\t\t// Add dropped files to the paths list.\n\t\tDragAndDrop.installFileSupport(this, (region, event, files) -> {\n\t\t\tfor (Path path : files)\n\t\t\t\taddPath(path);\n\t\t});\n\n\t\t// Create the vertical list of paths.\n\t\tVBox flow = new VBox();\n\t\tflow.setPadding(new Insets(15));\n\t\tflow.setAlignment(Pos.CENTER);\n\t\tflow.setFillWidth(true);\n\t\tflow.setSpacing(15);\n\t\tObservableList<Node> flowNodes = flow.getChildren();\n\t\tprimary.addListener((ob, old, cur) -> {\n\t\t\tif (cur != null) {\n\t\t\t\t// Resort children so the primary path is always on-top.\n\t\t\t\tsetNodes(new ArrayList<>(flowNodes), flowNodes);\n\t\t\t} else if (!paths.isEmpty()) {\n\t\t\t\t// Select the first path if we lost our selection.\n\t\t\t\tprimary.set(paths.getFirst());\n\t\t\t}\n\t\t});\n\t\tpaths.addListener((ListChangeListener<Path>) change -> {\n\t\t\tList<Node> newNodes = new ArrayList<>(flowNodes);\n\t\t\twhile (change.next()) {\n\t\t\t\tfor (Path path : change.getRemoved())\n\t\t\t\t\tnewNodes.removeIf(child -> child instanceof FileEntry childEntry && path.equals(childEntry.path));\n\t\t\t\tfor (Path path : change.getAddedSubList())\n\t\t\t\t\tnewNodes.add(new FileEntry(path, true));\n\t\t\t}\n\t\t\tsetNodes(newNodes, flowNodes);\n\t\t});\n\n\t\t// Label to prompt users to drag/drop files here.\n\t\t// Only shown when there are no items.\n\t\tBoundLabel dropPromptLabel = new BoundLabel(Lang.getBinding(\"tree.prompt\"));\n\t\tdropPromptLabel.visibleProperty().bind(primary.isNull());\n\t\tdropPromptLabel.getStyleClass().addAll(Styles.TEXT_SUBTLE);\n\t\tdropPromptLabel.prefWidthProperty().bind(widthProperty());\n\t\tdropPromptLabel.setAlignment(Pos.CENTER);\n\t\tdropPromptLabel.setPadding(new Insets(15));\n\n\t\t// Button to load the workspace based on the order of paths given by the user.\n\t\tActionButton openWorkspaceButton = new ActionButton(CarbonIcons.WORKSPACE_IMPORT, Lang.getBinding(\"menu.file.openworkspace\"), () -> {\n\t\t\tPath primaryPath = primary.get();\n\t\t\tList<Path> supportingPaths = paths.stream().filter(p -> !p.equals(primaryPath)).toList();\n\t\t\tsetDisable(true);\n\t\t\tpathLoadingManager.asyncNewWorkspace(primaryPath, supportingPaths, ex -> {\n\t\t\t\tToolkit.getDefaultToolkit().beep();\n\t\t\t\tlogger.error(\"Failed to open workspace for '{}'\", primaryPath.getFileName(), ex);\n\t\t\t\tErrorDialogs.show(\n\t\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.title\"),\n\t\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.header\"),\n\t\t\t\t\t\tgetBinding(\"dialog.error.loadworkspace.content\"),\n\t\t\t\t\t\tex\n\t\t\t\t);\n\t\t\t}).whenComplete((workspace, error) -> {\n\t\t\t\tonComplete.run();\n\t\t\t});\n\t\t});\n\t\topenWorkspaceButton.disableProperty().bind(primary.isNull());\n\t\topenWorkspaceButton.getStyleClass().addAll(Styles.BUTTON_OUTLINED);\n\n\t\t// Buttons to add additional paths.\n\t\tActionButton addDir = new ActionButton(CarbonIcons.FOLDER_ADD, Lang.getBinding(\"dialog.file.open.directory\"), () -> {\n\t\t\tFile recentOpenDir = recentFilesConfig.getLastWorkspaceOpenDirectory().unboxingMap(File::new);\n\t\t\tDirectoryChooser chooser = new DirectoryChooserBuilder()\n\t\t\t\t\t.setInitialDirectory(recentOpenDir)\n\t\t\t\t\t.setTitle(Lang.get(\"dialog.file.open\"))\n\t\t\t\t\t.build();\n\n\t\t\t// Show the prompt, update the paths list when complete\n\t\t\tFile file = chooser.showDialog(getScene().getWindow());\n\t\t\tif (file != null) {\n\t\t\t\tString parent = file.getParent();\n\t\t\t\tif (parent != null) recentFilesConfig.getLastWorkspaceOpenDirectory().setValue(parent);\n\t\t\t\taddPath(file.toPath());\n\t\t\t}\n\t\t});\n\t\tActionButton addFile = new ActionButton(CarbonIcons.DOCUMENT_ADD, Lang.getBinding(\"dialog.file.open.file\"), () -> {\n\t\t\tFile recentOpenDir = recentFilesConfig.getLastWorkspaceOpenDirectory().unboxingMap(File::new);\n\t\t\tFileChooser chooser = new FileChooserBuilder()\n\t\t\t\t\t.setInitialDirectory(recentOpenDir)\n\t\t\t\t\t.setTitle(Lang.get(\"dialog.file.open\"))\n\t\t\t\t\t.build();\n\n\t\t\t// Show the prompt, update the paths list when complete\n\t\t\tFile file = chooser.showOpenDialog(getScene().getWindow());\n\t\t\tif (file != null) {\n\t\t\t\tString parent = file.getParent();\n\t\t\t\tif (parent != null) recentFilesConfig.getLastWorkspaceOpenDirectory().setValue(parent);\n\t\t\t\taddPath(file.toPath());\n\t\t\t}\n\t\t});\n\t\taddDir.getStyleClass().addAll(Styles.BUTTON_OUTLINED, Styles.LEFT_PILL);\n\t\taddFile.getStyleClass().addAll(Styles.BUTTON_OUTLINED, Styles.RIGHT_PILL);\n\n\t\tHBox buttons = new HBox(\n\t\t\t\tnew HBox(addDir, addFile),\n\t\t\t\tnew Spacer(),\n\t\t\t\topenWorkspaceButton\n\t\t);\n\t\tbuttons.setSpacing(10);\n\t\tbuttons.setAlignment(Pos.CENTER);\n\t\tbuttons.setPadding(new Insets(10));\n\n\t\tScrollPane flowWrapper = new ScrollPane(flow);\n\t\tflowWrapper.setFitToWidth(true);\n\t\tflowWrapper.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);\n\t\tflow.prefWidthProperty().bind(flowWrapper.widthProperty());\n\t\tsetTop(new Group(dropPromptLabel));\n\t\tsetCenter(flowWrapper);\n\t\tsetBottom(buttons);\n\n\t\tgetStyleClass().addAll(Styles.BG_INSET);\n\t\tbuttons.getStyleClass().addAll(Styles.BG_DEFAULT);\n\t\tbuttons.setStyle(\"-fx-border-color: -color-border-muted; -fx-border-width: 1px 0 0 0;\");\n\t}\n\n\t/**\n\t * Handle adding files via the clipboard when pasting.\n\t *\n\t * @param e\n\t * \t\tKey press event.\n\t */\n\tprivate void handlePaste(KeyEvent e) {\n\t\tif (e.isControlDown() && e.getCode() == KeyCode.V) {\n\t\t\tClipboard clipboard = Clipboard.getSystemClipboard();\n\n\t\t\t// Handle files\n\t\t\tfor (File file : clipboard.getFiles())\n\t\t\t\taddPath(file.toPath());\n\n\t\t\t// Handle text which may be a path/url to a file\n\t\t\tString url = clipboard.getUrl();\n\t\t\tString string = clipboard.getString();\n\t\t\tif (string != null) {\n\t\t\t\t// Check if it is a file path\n\t\t\t\tif (new File(string).exists())\n\t\t\t\t\taddPath(Paths.get(string));\n\n\t\t\t\t// Check if it is a file uri\n\t\t\t\turl = string;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tURI u = URI.create(url);\n\t\t\t\tif (\"file\".equals(u.getScheme())) {\n\t\t\t\t\tString host = u.getHost();\n\t\t\t\t\tif (host != null)\n\t\t\t\t\t\treturn;\n\t\t\t\t\tString path = u.getPath();\n\t\t\t\t\tFile filePath = new File(path);\n\t\t\t\t\tif (filePath.exists())\n\t\t\t\t\t\taddPath(filePath.toPath());\n\t\t\t\t}\n\t\t\t} catch (IllegalArgumentException _) {}\n\t\t}\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to add.\n\t */\n\tprivate void addPath(@Nonnull Path path) {\n\t\tif (!paths.contains(path)) {\n\t\t\tif (paths.isEmpty())\n\t\t\t\tprimary.set(path);\n\t\t\tpaths.add(path);\n\t\t}\n\t}\n\n\t/**\n\t * Assigns the temporary list, one ordered, to the child list container.\n\t *\n\t * @param temp\n\t * \t\tTemporary items to sort.\n\t * @param children\n\t * \t\tChildren list to update with sorted items.\n\t */\n\tprivate void setNodes(@Nonnull List<Node> temp, @Nonnull ObservableList<Node> children) {\n\t\ttemp.sort((o1, o2) -> {\n\t\t\tint i1 = o1 instanceof FileEntry e ? e.path == primary.get() ? Integer.MIN_VALUE : paths.indexOf(e.path) : 0;\n\t\t\tint i2 = o2 instanceof FileEntry e ? e.path == primary.get() ? Integer.MIN_VALUE : paths.indexOf(e.path) : 0;\n\t\t\treturn Integer.compare(i1, i2);\n\t\t});\n\t\ttemp.forEach(n -> {\n\t\t\tif (n instanceof FileEntry childentry)\n\t\t\t\tchildentry.updateButtons();\n\t\t});\n\t\tchildren.setAll(temp);\n\t}\n\n\tprivate class FileEntry extends HBox {\n\t\tprivate final Path path;\n\t\tprivate final ActionButton up;\n\t\tprivate final ActionButton down;\n\n\t\tFileEntry(@Nonnull Path path, boolean showPrimary) {\n\t\t\tthis.path = path;\n\n\t\t\tString fileName = path.getFileName().toString();\n\t\t\tNode graphic = Files.isDirectory(path) ?\n\t\t\t\t\tIcons.getIconView(Icons.FOLDER, 32) :\n\t\t\t\t\tIcons.getIconView(Icons.getIconPathForFileExtension(IOUtil.getExtension(path)), 32);\n\t\t\tLabel nameLabel = new Label(fileName, graphic);\n\t\t\tnameLabel.setGraphicTextGap(10);\n\n\t\t\t// Button to remove this path from the inputs.\n\t\t\tActionButton remove = new ActionButton(CarbonIcons.TRASH_CAN, Lang.getBinding(\"misc.remove\"), () -> {\n\t\t\t\tpaths.remove(path);\n\t\t\t\tPath primaryPath = primary.get();\n\t\t\t\tif (primaryPath != null && primaryPath.equals(path))\n\t\t\t\t\tprimary.set(null);\n\t\t\t});\n\t\t\tremove.setMinWidth(100);\n\t\t\tremove.setPrefWidth(100);\n\n\t\t\t// Button to mark this path as the primary one.\n\t\t\tActionButton markPrimary = new ActionButton(CarbonIcons.PIN, Lang.getBinding(\"dialog.file.primary\"), () -> primary.set(path));\n\t\t\tmarkPrimary.getStyleClass().addAll(Styles.LEFT_PILL);\n\t\t\tmarkPrimary.visibleProperty().bind(primary.isNotEqualTo(path));\n\t\t\tmarkPrimary.setMinWidth(100);\n\t\t\tmarkPrimary.setPrefWidth(100);\n\n\t\t\t// Up and down arrows to change supporting item order.\n\t\t\tup = new ActionButton(CarbonIcons.ARROW_UP, () -> {\n\t\t\t\tint i = paths.indexOf(path);\n\t\t\t\tif (i > 0) {\n\t\t\t\t\tint off = paths.get(i - 1) == primary.get() ? 2 : 1;\n\t\t\t\t\tpaths.remove(i);\n\t\t\t\t\tpaths.add(i - off, path);\n\t\t\t\t}\n\t\t\t});\n\t\t\tdown = new ActionButton(CarbonIcons.ARROW_DOWN, () -> {\n\t\t\t\tint i = paths.indexOf(path);\n\t\t\t\tif (i >= 0) {\n\t\t\t\t\tint off = i < paths.size() - 1 && paths.get(i + 1) == primary.get() ? 2 : 1;\n\t\t\t\t\tpaths.remove(i);\n\t\t\t\t\tpaths.add(i + off, path);\n\t\t\t\t}\n\t\t\t});\n\t\t\tup.getStyleClass().add(showPrimary ? Styles.CENTER_PILL : Styles.LEFT_PILL);\n\t\t\tdown.getStyleClass().addAll(Styles.RIGHT_PILL);\n\t\t\tup.visibleProperty().bind(primary.isNotEqualTo(path));\n\t\t\tdown.visibleProperty().bind(primary.isNotEqualTo(path));\n\t\t\tif (showPrimary) {\n\t\t\t\tup.prefHeightProperty().bind(markPrimary.heightProperty());\n\t\t\t\tdown.prefHeightProperty().bind(markPrimary.heightProperty());\n\t\t\t}\n\t\t\tupdateButtons();\n\n\t\t\tHBox box = showPrimary ? new HBox(markPrimary, up, down) : new HBox(up, down);\n\t\t\tHBox buttons = new HBox(new Group(box), remove);\n\t\t\tbuttons.spacingProperty().bind(markPrimary.visibleProperty().map(v -> v ? 8 : 0));\n\t\t\tbuttons.setAlignment(Pos.CENTER);\n\n\t\t\tLabel primaryMarker = new BoundLabel(Lang.getBinding(\"dialog.file.primary\"));\n\t\t\tprimaryMarker.setPadding(new Insets(0, 0, 0, 15));\n\t\t\tprimaryMarker.setTextFill(Color.YELLOW);\n\t\t\tprimaryMarker.maxWidth(Integer.MAX_VALUE);\n\t\t\tprimaryMarker.setAlignment(Pos.CENTER);\n\t\t\tprimaryMarker.setGraphic(new FontIconView(CarbonIcons.STAR_FILLED, Color.YELLOW));\n\t\t\tprimaryMarker.visibleProperty().bind(primary.isEqualTo(path));\n\n\t\t\tsetAlignment(Pos.CENTER);\n\t\t\tsetPadding(new Insets(5, 10, 5, 10));\n\t\t\tgetChildren().addAll(nameLabel, new Group(primaryMarker), new Spacer(), buttons);\n\t\t\tgetStyleClass().addAll(Styles.BG_DEFAULT, Styles.BORDER_DEFAULT);\n\t\t}\n\n\t\t/**\n\t\t * Update the up/down arrow enabled states based on this {@link #path} position in {@link #paths}.\n\t\t */\n\t\tprivate void updateButtons() {\n\t\t\t// Ensure we discount the primary path item (as if it does not exist since it is pinned to the top)\n\t\t\tList<Path> filtered = paths.stream().filter(p -> p != primary.get()).toList();\n\t\t\tint i = filtered.indexOf(path);\n\t\t\tup.setDisable(i < 1);\n\t\t\tdown.setDisable(i == filtered.size() - 1);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/WorkspaceExplorerPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.StackPane;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.tree.TreeFiltering;\nimport software.coley.recaf.ui.control.tree.WorkspaceTree;\nimport software.coley.recaf.ui.control.tree.WorkspaceTreeFilterPane;\nimport software.coley.recaf.ui.dnd.DragAndDrop;\nimport software.coley.recaf.ui.dnd.WorkspaceLoadingDropListener;\nimport software.coley.recaf.ui.docking.DockingManager;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Collection;\nimport java.util.Collections;\n\n/**\n * Pane to display the current workspace in a navigable tree layout.\n *\n * @author Matt Coley\n * @see DockingManager\n */\n@Dependent\npublic class WorkspaceExplorerPane extends BorderPane implements Navigable {\n\tprivate final WorkspaceTree workspaceTree;\n\tprivate final Workspace workspace;\n\n\t/**\n\t * @param listener\n\t * \t\tWorkspace drag-and-drop listener.\n\t * @param workspaceTree\n\t * \t\tTree to display workspace with.\n\t * @param workspaceManager\n\t * \t\tManager to pull in current workspace from.\n\t */\n\t@Inject\n\tpublic WorkspaceExplorerPane(@Nonnull WorkspaceLoadingDropListener listener,\n\t                             @Nonnull WorkspaceTree workspaceTree,\n\t                             @Nonnull WorkspaceManager workspaceManager) {\n\t\tthis.workspaceTree = workspaceTree;\n\n\t\t// As we are the explorer pane, these items should be treated as declarations and not references.\n\t\tworkspaceTree.contextSourceObjectPropertyProperty().setValue(ContextSource.DECLARATION);\n\n\t\t// Add filter pane, and hook up key-events so the user can easily\n\t\t// navigate between the tree and the filter.\n\t\tWorkspaceTreeFilterPane workspaceTreeFilterPane = new WorkspaceTreeFilterPane(workspaceTree);\n\t\tTreeFiltering.install(workspaceTreeFilterPane.getTextField(), workspaceTree);\n\n\t\t// Initialize drag-drop support.\n\t\tDragAndDrop.installFileSupport(this, listener);\n\n\t\t// Layout\n\t\tStackPane stack = new StackPane(workspaceTree);\n\t\tsetCenter(stack);\n\t\tsetBottom(workspaceTreeFilterPane);\n\n\t\t// Populate tree\n\t\tworkspace = workspaceManager.getCurrent();\n\t\tif (workspaceManager.hasCurrentWorkspace())\n\t\t\tworkspaceTree.createWorkspaceRoot(workspace);\n\n\t\t// Add label to indicate when filter pane input results in the tree being empty.\n\t\t// This should help out users if they forget they have something in the search bar and the tree looks empty.\n\t\tLabel noResultsLabel = new BoundLabel(Lang.getBinding(\"menu.search.noresults\"));\n\t\tnoResultsLabel.setGraphic(new FontIconView(CarbonIcons.SEARCH));\n\t\tnoResultsLabel.setMouseTransparent(true);\n\t\tnoResultsLabel.setVisible(false);\n\t\tnoResultsLabel.setOpacity(0.5);\n\t\tworkspaceTreeFilterPane.currentPredicateProperty().addListener((ob, old, cur) -> {\n\t\t\tTreeItem<PathNode<?>> root = workspaceTree.getRoot();\n\t\t\tif (root != null) {\n\t\t\t\tnoResultsLabel.setVisible(root.getChildren().isEmpty());\n\t\t\t} else {\n\t\t\t\tnoResultsLabel.setVisible(false);\n\t\t\t}\n\t\t});\n\t\tStackPane.setAlignment(noResultsLabel, Pos.CENTER);\n\t\tstack.getChildren().add(noResultsLabel);\n\t}\n\n\t/**\n\t * @return Tree displaying a workspace.\n\t */\n\t@Nonnull\n\tpublic WorkspaceTree getWorkspaceTree() {\n\t\treturn workspaceTree;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn PathNodes.workspacePath(workspace);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tFxThreadUtil.run(() -> {\n\t\t\tworkspaceTree.setRoot(null);\n\t\t\tsetDisable(true);\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/WorkspaceInformationPane.java",
    "content": "package software.coley.recaf.ui.pane;\n\nimport atlantafx.base.controls.ModalPane;\nimport atlantafx.base.controls.RingProgressIndicator;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Group;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ProgressIndicator;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.TitledPane;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.path.WorkspacePathNode;\nimport software.coley.recaf.services.cell.icon.IconProviderService;\nimport software.coley.recaf.services.cell.text.TextProviderService;\nimport software.coley.recaf.services.info.summary.ResourceSummaryService;\nimport software.coley.recaf.services.info.summary.SummaryConsumer;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.AutoScrollPane;\nimport software.coley.recaf.ui.docking.DockingManager;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\n\n/**\n * Pane to display summary data about the loaded {@link Workspace} when opened.\n *\n * @author Matt Coley\n * @see DockingManager\n * @see ResourceSummaryService\n */\n@Dependent\npublic class WorkspaceInformationPane extends StackPane implements Navigable {\n\tprivate final WorkspacePathNode path;\n\n\t@Inject\n\tpublic WorkspaceInformationPane(@Nonnull TextProviderService textService,\n\t                                @Nonnull IconProviderService iconService,\n\t                                @Nonnull ResourceSummaryService summaryService,\n\t                                @Nonnull WorkspaceManager workspaceManager) {\n\t\tWorkspace workspace = workspaceManager.getCurrent();\n\t\tpath = PathNodes.workspacePath(workspace);\n\n\t\t// Adding content\n\t\tModalPane modal = new ModalPane();\n\t\tGrid content = new Grid();\n\t\tcontent.setPadding(new Insets(10));\n\t\tcontent.prefWidthProperty().bind(widthProperty().subtract(10));\n\t\tScrollPane scroll = new AutoScrollPane(content);\n\t\tgetChildren().addAll(modal, scroll);\n\t\tgetStyleClass().add(\"background\");\n\n\t\t// Set up a \"loading...\" overlay while the summary is still being generated\n\t\tRingProgressIndicator ring = new RingProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS);\n\t\tVBox box = new VBox(ring,\n\t\t\t\tnew BoundLabel(Lang.getBinding(\"workspace.info-progress\")));\n\t\tbox.setAlignment(Pos.CENTER);\n\t\tbox.setSpacing(20);\n\t\tFxThreadUtil.delayedRun(1, () -> modal.show(new Group(box)));\n\n\t\t// Populate summary data for each resource.\n\t\tList<CompletableFuture<Void>> summaryFutures = new ArrayList<>();\n\t\tList<WorkspaceResource> resources = workspace.getAllResources(false);\n\t\tfor (WorkspaceResource resource : resources) {\n\t\t\t// Create header.\n\t\t\tNode graphic = iconService.getResourceIconProvider(workspace, resource).makeIcon();\n\t\t\tLabel title = new Label(textService.getResourceTextProvider(workspace, resource).makeText());\n\t\t\tLabel subtitle = new Label(String.format(\"%d classes, %d files\",\n\t\t\t\t\tresource.classBundleStream().mapToInt(Map::size).sum(),\n\t\t\t\t\tresource.fileBundleStream().mapToInt(Map::size).sum()\n\t\t\t));\n\t\t\ttitle.getStyleClass().add(Styles.TITLE_3);\n\t\t\ttitle.setGraphic(graphic);\n\t\t\tsubtitle.getStyleClass().add(Styles.TEXT_SUBTLE);\n\n\t\t\tif (resources.size() > 1) {\n\t\t\t\t// Add summaries for this resource into a collapsible panel.\n\t\t\t\tGrid section = content.newSection();\n\t\t\t\tTitledPane resourcePane = new TitledPane();\n\t\t\t\tresourcePane.setContent(section);\n\t\t\t\tresourcePane.setGraphic(new VBox(title, subtitle));\n\t\t\t\tcontent.add(resourcePane, 0, content.getRowCount(), 2, 1);\n\t\t\t\tsummaryFutures.add(summaryService.summarizeTo(workspace, resource, section));\n\t\t\t} else {\n\t\t\t\t// Single resource, no need to box it.\n\t\t\t\tVBox wrapper = new VBox(title, subtitle);\n\t\t\t\tcontent.add(wrapper, 0, content.getRowCount(), 2, 1);\n\t\t\t\tsummaryFutures.add(summaryService.summarizeTo(workspace, resource, content.newSection()));\n\t\t\t}\n\t\t}\n\n\t\t// When the summary is done, clear the \"loading...\" overlay.\n\t\tCompletableFuture.allOf(summaryFutures.toArray(CompletableFuture[]::new))\n\t\t\t\t.whenCompleteAsync((ignored, error) -> FxThreadUtil.delayedRun(100, () -> {\n\t\t\t\t\tmodal.hide(true);\n\n\t\t\t\t\t// AtlantaFX's ring progress in the 'indeterminate' state uses the JavaFX 'RotateTransition' animation\n\t\t\t\t\t// which leaks memory if not explicitly stopped. This results in any workspace content never being GC'd.\n\t\t\t\t\t//\n \t\t\t\t\t// See: https://bsky.app/profile/mattcoley.bsky.social/post/3mbvlnisiys2z\n\t\t\t\t\tring.setProgress(0);\n\t\t\t\t\tbox.getChildren().clear();\n\t\t\t\t}));\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tFxThreadUtil.run(() -> {\n\t\t\tgetChildren().clear();\n\t\t\tsetDisable(true);\n\t\t});\n\t}\n\n\tprivate static class Grid extends GridPane implements SummaryConsumer {\n\t\tprivate Grid() {\n\t\t\tsetVgap(5);\n\t\t\tsetHgap(5);\n\t\t\tColumnConstraints column1 = new ColumnConstraints();\n\t\t\tColumnConstraints column2 = new ColumnConstraints();\n\t\t\tcolumn1.setPercentWidth(25);\n\t\t\tcolumn2.setPercentWidth(75);\n\t\t\tgetColumnConstraints().addAll(column1, column2);\n\t\t}\n\n\t\t@Nonnull\n\t\tpublic Grid newSection() {\n\t\t\tGrid section = new Grid();\n\t\t\tadd(section, 0, getRowCount(), 2, 1);\n\t\t\treturn section;\n\t\t}\n\n\t\t@Override\n\t\tpublic void appendSummary(Node node) {\n\t\t\tFxThreadUtil.run(() -> add(node, 0, getRowCount(), 2, 1));\n\t\t}\n\n\t\t@Override\n\t\tpublic void appendSummary(Node left, Node right) {\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tint row = getRowCount();\n\t\t\t\tadd(left, 0, row);\n\t\t\t\tadd(right, 1, row);\n\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/AbstractClassInfoProvider.java",
    "content": "package software.coley.recaf.ui.pane.editing;\n\nimport atlantafx.base.controls.Popover;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.event.ActionEvent;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.navigation.ClassNavigable;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.richtext.Editor;\n\n/**\n * Overlay component for {@link Editor} that allows quick display of class information.\n *\n * @author Matt Coley\n */\npublic abstract class AbstractClassInfoProvider<T extends ClassInfo> extends Button {\n\tprivate final ClassNavigable classProvider;\n\n\t/**\n\t * @param toolsContainer\n\t * \t\tContainer to house tool buttons for display in the {@link Editor}.\n\t * @param classProvider\n\t * \t\tThe provider of the latest class info.\n\t */\n\tpublic AbstractClassInfoProvider(@Nonnull ToolsContainerComponent toolsContainer, @Nonnull ClassNavigable classProvider) {\n\t\tthis.classProvider = classProvider;\n\t\tsetGraphic(new FontIconView(CarbonIcons.INFORMATION));\n\t\tgetStyleClass().addAll(Styles.BUTTON_ICON, Styles.ACCENT, Styles.FLAT);\n\t\tsetOnAction(this::showClassInfoPopover);\n\t\ttoolsContainer.add(this);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate void showClassInfoPopover(@Nonnull ActionEvent e) {\n\t\tClassPathNode path = classProvider.getClassPath();\n\t\tClassInfo info = path.getValue();\n\t\tPopover popover = new Popover(createInfoContent((T) info));\n\t\tpopover.setArrowLocation(Popover.ArrowLocation.BOTTOM_RIGHT);\n\t\tpopover.show(this);\n\t}\n\n\t@Nonnull\n\tprotected abstract Node createInfoContent(@Nonnull T info);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/AbstractContentPane.java",
    "content": "package software.coley.recaf.ui.pane.editing;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Orientation;\nimport javafx.geometry.Side;\nimport javafx.scene.Node;\nimport javafx.scene.control.SplitPane;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.Region;\nimport org.kordamp.ikonli.Ikon;\nimport software.coley.bentofx.Bento;\nimport software.coley.bentofx.building.DockBuilding;\nimport software.coley.bentofx.dockable.Dockable;\nimport software.coley.bentofx.dockable.DockableIconFactory;\nimport software.coley.bentofx.layout.container.DockContainerLeaf;\nimport software.coley.bentofx.layout.container.DockContainerRootBranch;\nimport software.coley.bentofx.path.DockContainerPath;\nimport software.coley.bentofx.util.BentoStates;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.docking.DockingManager;\nimport software.coley.recaf.ui.docking.EmbeddedBento;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.function.Consumer;\n\n/**\n * Base type for common content panes displaying {@link Info} values.\n *\n * @param <P>\n * \t\tInfo path type.\n *\n * @see FilePane For {@link FileInfo}\n * @see ClassPane For {@link ClassInfo}\n */\npublic abstract class AbstractContentPane<P extends PathNode<?>> extends BorderPane implements UpdatableNavigable {\n\tprivate static final String TOOL_TABS_ID = \"tool-tabs\";\n\t/** We use a separate instance because the side-tabs of this pane are not intended to be tracked by our docking manager. */\n\tprivate final Bento bento = new EmbeddedBento();\n\t/** Wrapper to hold {@link #displayWrapper} and {@link Dockable} side tabs. Split according to {@link #toolTabSide}. */\n\tprivate final DockContainerRootBranch displaySplit;\n\t/** Side of the UI to place additional tools on. */\n\tprivate final Side toolTabSide;\n\t/** Wrapper to hold display for provided content. See {@link #generateDisplay()} */\n\tprivate final BorderPane displayWrapper = new BorderPane();\n\tprotected final List<Consumer<P>> pathUpdateListeners = new CopyOnWriteArrayList<>();\n\tprotected final List<Navigable> children = new ArrayList<>();\n\tprotected P path;\n\n\t/**\n\t * New content pane. Any additional tools registered via {@link #addSideTab(Dockable)} will be placed on the right.\n\t */\n\tprotected AbstractContentPane() {\n\t\tthis(Side.RIGHT);\n\t}\n\n\t/**\n\t * New content pane.\n\t *\n\t * @param toolTabSide\n\t * \t\tSide to place additional tools registered via {@link #addSideTab(Dockable)}.\n\t */\n\tprotected AbstractContentPane(@Nonnull Side toolTabSide) {\n\t\tthis.toolTabSide = toolTabSide;\n\n\t\tDockBuilding builder = bento.dockBuilding();\n\t\tDockable dockable = builder.dockable();\n\t\tdockable.setNode(displayWrapper);\n\t\tdockable.setCanBeDragged(false);\n\t\tdockable.setClosable(false);\n\t\tdockable.setDragGroupMask(DockingManager.GROUP_NEVER_RECEIVE);\n\n\t\t// The display container contains the primary content display.\n\t\t//\n\t\t// We set the container's side to 'null' to prevent the rendering of headers\n\t\t// for the wrapper dockable we made above.\n\t\tDockContainerLeaf displayContainer = builder.leaf();\n\t\tdisplayContainer.setSide(null);\n\t\tdisplayContainer.addDockable(dockable);\n\n\t\tdisplaySplit = builder.root();\n\t\tdisplaySplit.addContainer(displayContainer);\n\t\tif (toolTabSide.isHorizontal())\n\t\t\tdisplaySplit.setOrientation(Orientation.VERTICAL);\n\n\t\t// Register so we can search the hierarchy (used later for tool-tabs) before scene graph population.\n\t\tbento.registerRoot(displaySplit);\n\n\t\t// Register the side pseudo-state so we can have side-specific UI styling.\n\t\tRegion rootRegion = displaySplit.asRegion();\n\t\tswitch (toolTabSide) {\n\t\t\tcase TOP -> rootRegion.pseudoClassStateChanged(BentoStates.PSEUDO_SIDE_TOP, true);\n\t\t\tcase BOTTOM -> rootRegion.pseudoClassStateChanged(BentoStates.PSEUDO_SIDE_BOTTOM, true);\n\t\t\tcase LEFT -> rootRegion.pseudoClassStateChanged(BentoStates.PSEUDO_SIDE_LEFT, true);\n\t\t\tcase RIGHT -> rootRegion.pseudoClassStateChanged(BentoStates.PSEUDO_SIDE_RIGHT, true);\n\t\t}\n\t\tsetCenter(rootRegion);\n\t}\n\n\t/**\n\t * @param targetType\n\t * \t\tType of children to filter with.\n\t * @param action\n\t * \t\tAction to run on children of the target type.\n\t * @param <T>\n\t * \t\tTarget type.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tprotected <T> void eachChild(@Nonnull Class<T> targetType, @Nonnull Consumer<T> action) {\n\t\tfor (Navigable child : children) {\n\t\t\tif (targetType.isAssignableFrom(child.getClass())) {\n\t\t\t\taction.accept((T) child);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @return Current display\n\t */\n\t@Nullable\n\tpublic Node getDisplay() {\n\t\treturn displayWrapper.getCenter();\n\t}\n\n\t/**\n\t * Clear the display.\n\t */\n\tprotected void clearDisplay() {\n\t\t// Remove navigable child.\n\t\tif (displayWrapper.getCenter() instanceof Navigable navigable)\n\t\t\tchildren.remove(navigable);\n\n\t\t// Remove display node.\n\t\tdisplayWrapper.setCenter(null);\n\t}\n\n\t/**\n\t * @return {@code true} when there is a current displayed node.\n\t */\n\tprotected boolean hasDisplay() {\n\t\treturn getDisplay() != null;\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to display.\n\t */\n\tprotected void setDisplay(Node node) {\n\t\t// Remove old navigable child.\n\t\tNode old = displayWrapper.getCenter();\n\t\tif (old instanceof Navigable navigableOld)\n\t\t\tchildren.remove(navigableOld);\n\n\t\t// Add navigable child.\n\t\tif (node instanceof Navigable navigableNode)\n\t\t\tchildren.add(navigableNode);\n\n\t\t// Set display node.\n\t\tdisplayWrapper.setCenter(node);\n\t}\n\n\t/**\n\t * Refresh the display.\n\t */\n\tprotected void refreshDisplay() {\n\t\t// Refresh display\n\t\tclearDisplay();\n\t\tgenerateDisplay();\n\n\t\t// Refresh UI with path\n\t\tif (displayWrapper.getCenter() instanceof UpdatableNavigable updatable) {\n\t\t\tPathNode<?> currentPath = getPath();\n\t\t\tif (currentPath != null)\n\t\t\t\tupdatable.onUpdatePath(currentPath);\n\t\t}\n\t}\n\n\t/**\n\t * Generate display for the content denoted by {@link #getPath() the path node}.\n\t * Children implementing this should call {@link #setDisplay(Node)}.\n\t */\n\tprotected abstract void generateDisplay();\n\n\t/**\n\t * Adds a new side tab to this pane.\n\t *\n\t * @param binding\n\t * \t\tSide tab title binding.\n\t * @param icon\n\t * \t\tSide tab icon.\n\t * @param content\n\t * \t\tSide tab content to display.\n\t */\n\tpublic void addSideTab(@Nonnull ObservableValue<String> binding, @Nonnull Ikon icon, @Nullable Node content) {\n\t\taddSideTab(binding, d -> new FontIconView(icon), content);\n\t}\n\n\t/**\n\t * Adds a new side tab to this pane.\n\t *\n\t * @param binding\n\t * \t\tSide tab title binding.\n\t * @param iconFactory\n\t * \t\tSide tab icon factory.\n\t * @param content\n\t * \t\tSide tab content to display.\n\t */\n\tpublic void addSideTab(@Nonnull ObservableValue<String> binding, @Nonnull DockableIconFactory iconFactory, @Nullable Node content) {\n\t\tDockable dockable = bento.dockBuilding().dockable();\n\t\tdockable.setDragGroupMask(DockingManager.GROUP_NEVER_RECEIVE); // Prevent being used as a drag-drop target\n\t\tdockable.setCanBeDragged(false); // Prevent being used as a drag-drop\n\t\tdockable.setClosable(false);\n\t\tdockable.setIconFactory(iconFactory);\n\t\tdockable.setNode(content);\n\t\tdockable.titleProperty().bind(binding);\n\t\taddSideTab(dockable);\n\t}\n\n\t/**\n\t * Adds a new side tab to this pane.\n\t *\n\t * @param dockable\n\t * \t\tSide tab model.\n\t */\n\tpublic void addSideTab(@Nonnull Dockable dockable) {\n\t\tif (displaySplit.getChildContainers().size() < 2) {\n\t\t\tDockBuilding builder = bento.dockBuilding();\n\n\t\t\tDockContainerLeaf leaf = builder.leaf(TOOL_TABS_ID);\n\t\t\tleaf.setCanSplit(false);\n\t\t\tleaf.setSide(toolTabSide);\n\t\t\tleaf.addDockable(dockable);\n\t\t\tSplitPane.setResizableWithParent(leaf.asRegion(), false);\n\n\t\t\tswitch (toolTabSide) {\n\t\t\t\tcase TOP, LEFT -> {\n\t\t\t\t\tdisplaySplit.addContainer(0, leaf);\n\t\t\t\t\tdisplaySplit.setContainerSizePx(leaf, 232);\n\t\t\t\t}\n\t\t\t\tcase BOTTOM, RIGHT -> {\n\t\t\t\t\tdisplaySplit.addContainer(leaf);\n\t\t\t\t\tdisplaySplit.setContainerSizePx(leaf, 180);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdisplaySplit.setContainerCollapsed(leaf, true);\n\t\t} else {\n\t\t\tDockContainerPath path = bento.search().container(TOOL_TABS_ID);\n\t\t\tif (path != null && path.tailContainer() instanceof DockContainerLeaf leaf)\n\t\t\t\tleaf.addDockable(dockable);\n\t\t}\n\n\t\tif (dockable.getNode() instanceof Navigable dockableNode)\n\t\t\tchildren.add(dockableNode);\n\t}\n\n\t/**\n\t * Clears all side tabs from this pane.\n\t */\n\tpublic void clearSideTabs() {\n\t\tDockContainerPath path = bento.search().container(TOOL_TABS_ID);\n\t\tif (path != null && path.tailContainer() instanceof DockContainerLeaf leaf) {\n\t\t\tfor (Dockable dockable : new ArrayList<>(leaf.getDockables())) {\n\t\t\t\tleaf.closeDockable(dockable);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tpublic void addPathUpdateListener(@Nonnull Consumer<P> listener) {\n\t\tpathUpdateListeners.add(listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tpublic void removePathUpdateListener(@Nonnull Consumer<P> listener) {\n\t\tpathUpdateListeners.remove(listener);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn children;\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tchildren.forEach(Navigable::disable);\n\t\tpathUpdateListeners.clear();\n\t\tsetDisable(true);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/AbstractDecompilePane.java",
    "content": "package software.coley.recaf.ui.pane.editing;\n\nimport atlantafx.base.controls.Spacer;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.animation.Transition;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Orientation;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Labeled;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.text.Font;\nimport javafx.util.Duration;\nimport org.fxmisc.richtext.CodeArea;\nimport org.slf4j.Logger;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.properties.builtin.RemapOriginTaskProperty;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.decompile.DecompileResult;\nimport software.coley.recaf.services.decompile.DecompilerManager;\nimport software.coley.recaf.services.decompile.JvmDecompiler;\nimport software.coley.recaf.services.decompile.NoopJvmDecompiler;\nimport software.coley.recaf.services.info.association.FileTypeSyntaxAssociationService;\nimport software.coley.recaf.services.mapping.MappingResults;\nimport software.coley.recaf.services.mapping.Mappings;\nimport software.coley.recaf.services.navigation.ClassNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.services.source.AstMapper;\nimport software.coley.recaf.services.source.AstService;\nimport software.coley.recaf.services.source.ResolverAdapter;\nimport software.coley.recaf.services.tutorial.TutorialConfig;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.bracket.SelectedBracketTracking;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemTracking;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.ui.control.richtext.source.JavaContextActionSupport;\nimport software.coley.recaf.ui.pane.editing.android.AndroidDecompilerPane;\nimport software.coley.recaf.ui.pane.editing.jvm.DecompilerPaneConfig;\nimport software.coley.recaf.ui.pane.editing.jvm.JvmDecompilerPane;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.SceneUtils;\nimport software.coley.recaf.util.StringDiff;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.sourcesolver.model.CompilationUnitModel;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Common outline for decompiler panes utilizing {@link JvmDecompiler}.\n * <br>\n * For Android content it is assumed {@link AndroidClassInfo#asJvmClass()} is implemented as a conversion process.\n *\n * @author Matt Coley\n * @see JvmDecompilerPane\n * @see AndroidDecompilerPane\n */\npublic class AbstractDecompilePane extends BorderPane implements ClassNavigable, UpdatableNavigable {\n\tprivate static final Logger logger = Logging.get(AbstractDecompilePane.class);\n\tprotected final ObservableObject<JvmDecompiler> decompiler = new ObservableObject<>(NoopJvmDecompiler.getInstance());\n\tprotected final ObservableBoolean decompileOutputErrored = new ObservableBoolean(false);\n\tprotected final ObservableBoolean decompileInProgress = new ObservableBoolean(false);\n\tprotected final AtomicBoolean updateLock = new AtomicBoolean();\n\tprotected final ProblemTracking problemTracking = new ProblemTracking();\n\tprotected final AstService astService;\n\tprotected final JavaContextActionSupport contextActionSupport;\n\tprotected final DecompilerManager decompilerManager;\n\tprotected final DecompilerPaneConfig decompileConfig;\n\tprotected final TutorialConfig tutorialConfig;\n\tprotected final Editor editor;\n\tprotected ClassPathNode path;\n\n\tprotected AbstractDecompilePane(@Nonnull DecompilerPaneConfig decompileConfig,\n\t                                @Nonnull TutorialConfig tutorialConfig,\n\t                                @Nonnull SearchBar searchBar,\n\t                                @Nonnull AstService astService,\n\t                                @Nonnull JavaContextActionSupport contextActionSupport,\n\t                                @Nonnull FileTypeSyntaxAssociationService languageAssociation,\n\t                                @Nonnull DecompilerManager decompilerManager) {\n\t\tthis.astService = astService;\n\t\tthis.contextActionSupport = contextActionSupport;\n\t\tthis.decompilerManager = decompilerManager;\n\t\tthis.decompileConfig = decompileConfig;\n\t\tthis.tutorialConfig = tutorialConfig;\n\n\t\tdecompiler.setValue(decompilerManager.getTargetJvmDecompiler());\n\t\tdecompiler.addChangeListener((ob, old, cur) -> decompile());\n\n\t\t// Configure the editor\n\t\teditor = new Editor();\n\t\tlanguageAssociation.configureEditorSyntax(\"java\", editor);\n\t\teditor.setSelectedBracketTracking(new SelectedBracketTracking());\n\t\teditor.setProblemTracking(problemTracking);\n\t\teditor.getRootLineGraphicFactory().addDefaultCodeGraphicFactories();\n\t\tcontextActionSupport.install(editor);\n\t\tsearchBar.install(editor);\n\n\t\t// Add overlay for when decompilation is in-progress\n\t\tDecompileProgressOverlay overlay = new DecompileProgressOverlay();\n\t\tdecompileInProgress.addAsyncChangeListener((ob, old, cur) -> {\n\t\t\tObservableList<Node> children = editor.getPrimaryStack().getChildren();\n\t\t\tif (cur) children.add(overlay);\n\t\t\telse children.remove(overlay);\n\t\t}, FxThreadUtil.executor());\n\n\t\t// Layout\n\t\tsetCenter(editor);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassPathNode getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassPathNode getClassPath() {\n\t\treturn path;\n\t}\n\n\t@Override\n\tpublic void requestFocus(@Nonnull ClassMember member) {\n\t\tcontextActionSupport.select(member);\n\t\trequestFocus();\n\t}\n\n\t@Override\n\tpublic void requestFocus() {\n\t\t// Delegate focus to the editor.\n\t\teditor.getCodeArea().requestFocus();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.singleton(contextActionSupport);\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tsetDisable(true);\n\t\tsetOnKeyPressed(null);\n\t\teditor.close();\n\t\tcontextActionSupport.close();\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\t// Pass to children\n\t\tfor (Navigable navigableChild : getNavigableChildren())\n\t\t\tif (navigableChild instanceof UpdatableNavigable updatableNavigable)\n\t\t\t\tupdatableNavigable.onUpdatePath(path);\n\n\t\t// Handle updates to the decompiled code.\n\t\tif (!updateLock.get() && path instanceof ClassPathNode classPathNode) {\n\t\t\tthis.path = classPathNode;\n\t\t\tClassInfo classInfo = classPathNode.getValue();\n\n\t\t\t// Check if the class is an Android class and can be mapped.\n\t\t\tif (!classInfo.isJvmClass() && classInfo.isAndroidClass()) {\n\t\t\t\tAndroidClassInfo androidInfo = classInfo.asAndroidClass();\n\t\t\t\tif (androidInfo.canMapToJvmClass())\n\t\t\t\t\tclassInfo = androidInfo.asJvmClass();\n\t\t\t\telse\n\t\t\t\t\tthrow new IllegalStateException(\"Decompiler component received non-convertible Android class\");\n\t\t\t}\n\n\t\t\t// Check if we can update the text efficiently with a remapper.\n\t\t\t// If not, then schedule a decompilation instead.\n\t\t\tif (!decompileConfig.getUseMappingAcceleration().getValue() || !handleRemapUpdate(classInfo))\n\t\t\t\tdecompile();\n\t\t}\n\t}\n\n\t/**\n\t * Associates the given {@link ToolsContainerComponent} with this decompile pane's {@link #editor}.\n\t *\n\t * @param toolsContainer\n\t * \t\tTool container to install.\n\t */\n\tprotected void installToolsContainer(@Nonnull ToolsContainerComponent toolsContainer) {\n\t\tDecompileFailureButton failureButton = new DecompileFailureButton();\n\t\tdecompileOutputErrored.addChangeListener((ob, old, cur) -> {\n\t\t\tfailureButton.setVisible(cur);\n\t\t\tif (cur) failureButton.animate();\n\t\t});\n\n\t\ttoolsContainer.install(editor);\n\t\ttoolsContainer.add(contextActionSupport.getAvailabilityButton());\n\t\ttoolsContainer.addLast(failureButton);\n\t}\n\n\t/**\n\t * Attempts to update the {@link #editor}'s text with intent-specific AST operations.\n\t * This includes:\n\t * <ul>\n\t *     <li>{@link AstMapper} when handling classes marked with {@link RemapOriginTaskProperty}</li>\n\t * </ul>\n\t *\n\t * @param classInfo\n\t * \t\tModified class.\n\t *\n\t * @return {@code true} when we were able to handle it with an AST visitors.\n\t */\n\tprivate boolean handleRemapUpdate(@Nonnull ClassInfo classInfo) {\n\t\t// Attempt to handle change with an AST mapping visitor if the change is originating\n\t\t// from a mapping application job.\n\t\tString currentText = editor.getText();\n\t\tif (!currentText.isBlank()) {\n\t\t\tMappingResults mappingOrigin = classInfo.getPropertyValueOrNull(RemapOriginTaskProperty.KEY);\n\t\t\tif (mappingOrigin != null) {\n\t\t\t\tMappings mappings = mappingOrigin.getMappings();\n\n\t\t\t\t// We can handle the update with AST mapping instead of decompiling the class again.\n\t\t\t\tCompilationUnitModel unit = contextActionSupport.getUnit();\n\t\t\t\tWorkspace workspace = path.getValueOfType(Workspace.class);\n\t\t\t\tif (unit != null && workspace != null) {\n\t\t\t\t\tResolverAdapter resolver = astService.newJavaResolver(workspace, unit);\n\t\t\t\t\tresolver.setClassContext(getPath().getValue());\n\t\t\t\t\tString modifiedSource = astService.applyMappings(unit, resolver, mappings);\n\n\t\t\t\t\t// If there were no changes made then the AST service failed to make dynamic changes.\n\t\t\t\t\t// This can happen when the classes being mapped are not in the workspace, but instead\n\t\t\t\t\t// belong to 3rd party libraries not present in the workspace.\n\t\t\t\t\tif (currentText.equals(modifiedSource))\n\t\t\t\t\t\treturn false;\n\n\t\t\t\t\t// We want to get the difference between the current and modified text and update only\n\t\t\t\t\t// the areas of the text that are modified. In most situations this will be much faster\n\t\t\t\t\t// than re-assigning the whole text (which will require restyling the entire document)\n\t\t\t\t\tList<StringDiff.Diff> diffs = StringDiff.diff(currentText, modifiedSource);\n\t\t\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\t\t\t// Track where caret was.\n\t\t\t\t\t\tCodeArea area = editor.getCodeArea();\n\t\t\t\t\t\tint currentParagraph = area.getCurrentParagraph();\n\t\t\t\t\t\tint currentColumn = area.getCaretColumn();\n\n\t\t\t\t\t\t// Apply diffs.\n\t\t\t\t\t\tfor (int i = diffs.size() - 1; i >= 0; i--) {\n\t\t\t\t\t\t\tStringDiff.Diff diff = diffs.get(i);\n\t\t\t\t\t\t\tif (diff.type() == StringDiff.DiffType.CHANGE)\n\t\t\t\t\t\t\t\tarea.replaceText(diff.startA(), diff.endA(), diff.textB());\n\t\t\t\t\t\t\telse if (diff.type() == StringDiff.DiffType.INSERT)\n\t\t\t\t\t\t\t\tarea.insertText(diff.startA(), diff.textB());\n\t\t\t\t\t\t\telse if (diff.type() == StringDiff.DiffType.REMOVE)\n\t\t\t\t\t\t\t\tarea.replaceText(diff.startA(), diff.endA(), \"\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Reset caret.\n\t\t\t\t\t\tarea.moveTo(currentParagraph, currentColumn);\n\t\t\t\t\t});\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Decompiles the class contained by the current {@link #path} and updates the {@link #editor}'s text\n\t * with the decompilation results.\n\t */\n\tpublic void decompile() {\n\t\tWorkspace workspace = path.getValueOfType(Workspace.class);\n\t\tJvmClassInfo classInfo = path.getValue().asJvmClass();\n\n\t\t// Schedule decompilation task, update the editor's text asynchronously on the JavaFX UI thread when complete.\n\t\tdecompileInProgress.setValue(true);\n\t\teditor.setMouseTransparent(true);\n\t\tdecompilerManager.decompile(decompiler.getValue(), workspace, classInfo)\n\t\t\t\t.completeOnTimeout(timeoutResult(), decompileConfig.getTimeoutSeconds().getValue(), TimeUnit.SECONDS)\n\t\t\t\t.whenCompleteAsync((result, throwable) -> {\n\t\t\t\t\teditor.setMouseTransparent(false);\n\t\t\t\t\tdecompileInProgress.setValue(false);\n\n\t\t\t\t\t// Handle uncaught exceptions\n\t\t\t\t\tif (throwable != null) {\n\t\t\t\t\t\tString trace = StringUtil.traceToString(throwable);\n\t\t\t\t\t\teditor.setText(\"/*\\nUncaught exception when decompiling:\\n\" + trace + \"\\n*/\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Handle decompilation result\n\t\t\t\t\tString text = result.getText();\n\t\t\t\t\tif (Objects.equals(text, editor.getText()))\n\t\t\t\t\t\treturn; // Skip if existing text is the same\n\t\t\t\t\tDecompileResult.ResultType resultType = result.getType();\n\t\t\t\t\tdecompileOutputErrored.setValue(resultType == DecompileResult.ResultType.FAILURE);\n\t\t\t\t\tswitch (resultType) {\n\t\t\t\t\t\tcase SUCCESS -> editor.setText(text);\n\t\t\t\t\t\tcase SKIPPED -> editor.setText(text == null ? \"// Decompilation skipped\" : text);\n\t\t\t\t\t\tcase FAILURE -> {\n\t\t\t\t\t\t\tThrowable exception = result.getException();\n\t\t\t\t\t\t\tif (exception != null) {\n\t\t\t\t\t\t\t\tString trace = StringUtil.traceToString(exception);\n\t\t\t\t\t\t\t\teditor.setText(\"/*\\nDecompile failed:\\n\" + trace + \"\\n*/\");\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\teditor.setText(\"/*\\nDecompile failed, but no trace was attached.\\n*/\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Schedule AST parsing for context action support.\n\t\t\t\t\tcontextActionSupport.scheduleAstParse();\n\n\t\t\t\t\t// Prevent undo from reverting to empty state.\n\t\t\t\t\teditor.getCodeArea().getUndoManager().forgetHistory();\n\t\t\t\t}, FxThreadUtil.executor());\n\t}\n\n\t/**\n\t * @return Result made for timed out decompilations.\n\t */\n\t@Nonnull\n\tprivate DecompileResult timeoutResult() {\n\t\tJvmClassInfo info = path.getValue().asJvmClass();\n\t\tJvmDecompiler jvmDecompiler = decompiler.getValue();\n\t\treturn new DecompileResult(\"\"\"\n\t\t\t\t// Decompilation timed out.\n\t\t\t\t//  - Class name: %s\n\t\t\t\t//  - Class size: %d bytes\n\t\t\t\t//  - Decompiler: %s - %s\n\t\t\t\t//  - Timeout: %d seconds\n\t\t\t\t//\n\t\t\t\t// Suggestions:\n\t\t\t\t//  - Increase timeout\n\t\t\t\t//  - Change decompilers in 'config' or bottom right (i)\n\t\t\t\t//  - Deobfuscate heavily obfuscated code and try again\n\t\t\t\t//\n\t\t\t\t// Reminder:\n\t\t\t\t//  - Class information is still available on the side panels ==>\n\t\t\t\t\"\"\".formatted(info.getName(),\n\t\t\t\tinfo.getBytecode().length,\n\t\t\t\tjvmDecompiler.getName(), jvmDecompiler.getVersion(),\n\t\t\t\tdecompileConfig.getTimeoutSeconds().getValue()\n\t\t));\n\t}\n\n\t/**\n\t * And overlay shown while a class is being decompiled.\n\t */\n\tprivate class DecompileProgressOverlay extends VBox {\n\t\tprivate DecompileProgressOverlay() {\n\t\t\tLabel title = new BoundLabel(Lang.getBinding(\"java.decompiling\"));\n\t\t\tLabel text = new Label();\n\t\t\ttitle.getStyleClass().add(Styles.TITLE_3);\n\t\t\ttext.getStyleClass().add(Styles.TEXT_SUBTLE);\n\t\t\ttext.setFont(new Font(\"JetBrains Mono\", 12)); // Pulling from CSS applied to the editor.\n\n\t\t\t// Layout\n\t\t\tgetChildren().addAll(new Spacer(Orientation.VERTICAL), title, text, new Spacer(Orientation.VERTICAL));\n\t\t\tgetStyleClass().addAll(\"background\");\n\t\t\tsetFillWidth(true);\n\t\t\tsetAlignment(Pos.CENTER);\n\n\t\t\t// Setup transition to play whenever decompilation is in progress.\n\t\t\tBytecodeTransition transition = new BytecodeTransition(text);\n\t\t\tdecompileInProgress.addAsyncChangeListener((ob, old, cur) -> {\n\t\t\t\tsetVisible(cur);\n\t\t\t\tif (cur) {\n\t\t\t\t\ttransition.update(path.getValue().asJvmClass());\n\t\t\t\t\ttransition.play();\n\t\t\t\t} else\n\t\t\t\t\ttransition.stop();\n\t\t\t}, FxThreadUtil.executor());\n\t\t}\n\n\t\tprivate static class BytecodeTransition extends Transition {\n\t\t\tprivate final Labeled labeled;\n\t\t\tprivate byte[] bytecode;\n\n\t\t\t/**\n\t\t\t * @param labeled\n\t\t\t * \t\tTarget label.\n\t\t\t */\n\t\t\tpublic BytecodeTransition(@Nonnull Labeled labeled) {\n\t\t\t\tthis.labeled = labeled;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @param info\n\t\t\t * \t\tClass to show bytecode of.\n\t\t\t */\n\t\t\tpublic void update(@Nonnull JvmClassInfo info) {\n\t\t\t\tthis.bytecode = info.getBytecode();\n\t\t\t\tsetCycleDuration(Duration.millis(bytecode.length));\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tprotected void interpolate(double fraction) {\n\t\t\t\tint bytecodeSize = bytecode.length;\n\t\t\t\tint textLength = 18;\n\t\t\t\tint middle = (int) (fraction * bytecodeSize);\n\t\t\t\tint start = middle - (textLength / 2);\n\t\t\t\tint end = middle + (textLength / 2);\n\n\t\t\t\t// We have two rows, top for hex, bottom for text.\n\t\t\t\tStringBuilder sbHex = new StringBuilder();\n\t\t\t\tStringBuilder sbText = new StringBuilder();\n\t\t\t\tfor (int i = start; i < end; i++) {\n\t\t\t\t\tif (i < 0) {\n\t\t\t\t\t\tsbHex.append(\"   \");\n\t\t\t\t\t\tsbText.append(\"   \");\n\t\t\t\t\t} else if (i >= bytecodeSize) {\n\t\t\t\t\t\tsbHex.append(\" ..\");\n\t\t\t\t\t\tsbText.append(\" ..\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\tshort b = (short) (bytecode[i] & 0xFF);\n\t\t\t\t\t\tchar c = (char) b;\n\t\t\t\t\t\tif (Character.isWhitespace(c)) c = ' ';\n\t\t\t\t\t\telse if (c < 32) c = '?';\n\t\t\t\t\t\tString hex = StringUtil.limit(Integer.toHexString(b).toUpperCase(), 2);\n\t\t\t\t\t\tif (hex.length() == 1) hex = \"0\" + hex;\n\t\t\t\t\t\tsbHex.append(StringUtil.fillLeft(3, \" \", hex));\n\t\t\t\t\t\tsbText.append(StringUtil.fillLeft(3, \" \", String.valueOf(c)));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlabeled.setText(sbHex + \"\\n\" + sbText);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/AbstractDecompilerPaneConfigurator.java",
    "content": "package software.coley.recaf.ui.pane.editing;\n\nimport atlantafx.base.controls.Popover;\nimport atlantafx.base.controls.Spacer;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.event.ActionEvent;\nimport javafx.geometry.HPos;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Control;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Spinner;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.paint.Color;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.services.decompile.DecompilerManager;\nimport software.coley.recaf.services.decompile.JvmDecompiler;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.ObservableComboBox;\nimport software.coley.recaf.ui.control.ObservableSpinner;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.pane.editing.jvm.DecompilerPaneConfig;\nimport software.coley.recaf.util.Lang;\n\nimport java.util.ArrayList;\nimport java.util.Objects;\n\n/**\n * Overlay component for {@link Editor} that allows quick configuration of properties of a {@link AbstractDecompilePane}.\n *\n * @author Matt Coley\n */\npublic abstract class AbstractDecompilerPaneConfigurator extends Button {\n\tprivate final DecompilerPaneConfig config;\n\tprivate final ObservableObject<JvmDecompiler> decompiler;\n\tprivate final DecompilerManager decompilerManager;\n\tprivate Popover popover;\n\n\t/**\n\t * @param toolsContainer\n\t * \t\tContainer to house tool buttons for display in the {@link Editor}.\n\t * @param config\n\t * \t\tContaining {@link AbstractDecompilePane} config singleton.\n\t * @param decompiler\n\t * \t\tLocal decompiler implementation.\n\t * @param decompilerManager\n\t * \t\tManager to pull available {@link JvmDecompiler} instances from.\n\t */\n\tpublic AbstractDecompilerPaneConfigurator(@Nonnull ToolsContainerComponent toolsContainer,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull DecompilerPaneConfig config,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull ObservableObject<JvmDecompiler> decompiler,\n\t\t\t\t\t\t\t\t\t\t\t  @Nonnull DecompilerManager decompilerManager) {\n\t\tthis.config = config;\n\t\tthis.decompiler = decompiler;\n\t\tthis.decompilerManager = decompilerManager;\n\t\tsetGraphic(new FontIconView(CarbonIcons.SETTINGS));\n\t\tgetStyleClass().addAll(Styles.BUTTON_ICON, Styles.ACCENT, Styles.FLAT);\n\t\tsetOnAction(this::showConfiguratorPopover);\n\t\ttoolsContainer.add(this);\n\t}\n\n\tprivate void showConfiguratorPopover(ActionEvent e) {\n\t\tif (popover == null) {\n\t\t\tGridPane content = createGrid();\n\n\t\t\t// Wrap in popover\n\t\t\tpopover = new Popover(content);\n\t\t\tpopover.setArrowLocation(Popover.ArrowLocation.BOTTOM_RIGHT);\n\t\t}\n\t\tpopover.show(this);\n\t}\n\n\t@Nonnull\n\tprotected GridPane createGrid() {\n\t\tGridPane content = new GridPane();\n\t\tColumnConstraints col1 = new ColumnConstraints();\n\t\tColumnConstraints col2 = new ColumnConstraints();\n\t\tcol2.setFillWidth(true);\n\t\tcol2.setHgrow(Priority.ALWAYS);\n\t\tcol2.setHalignment(HPos.RIGHT);\n\t\tcontent.getColumnConstraints().addAll(col1, col2);\n\t\tcontent.setHgap(10);\n\t\tcontent.setVgap(5);\n\n\t\t// Decompile config\n\t\tObjectProperty<Node> decompileMatchedState = new SimpleObjectProperty<>();\n\t\tdecompileMatchedState.set(new FontIconView(CarbonIcons.LOCKED));\n\t\tdecompiler.addChangeListener((ob, old, cur) -> {\n\t\t\tboolean matched = Objects.equals(decompilerManager.getServiceConfig().getPreferredJvmDecompiler().getValue(), cur.getName());\n\t\t\tdecompileMatchedState.set(matched ? new FontIconView(CarbonIcons.LOCKED) : new FontIconView(CarbonIcons.UNLOCKED, Color.RED));\n\t\t});\n\t\tdecompilerManager.getServiceConfig().getPreferredJvmDecompiler().addChangeListener((ob, old, cur) -> {\n\t\t\tboolean matched = Objects.equals(decompiler.getValue().getName(), cur);\n\t\t\tdecompileMatchedState.set(matched ? new FontIconView(CarbonIcons.LOCKED) : new FontIconView(CarbonIcons.UNLOCKED, Color.RED));\n\t\t});\n\t\tLabel decompileTitle = new BoundLabel(Lang.getBinding(\"service.decompile\"));\n\t\tdecompileTitle.getStyleClass().addAll(Styles.TEXT_UNDERLINED, Styles.TITLE_4);\n\t\tLabel labelDecompiler = new BoundLabel(Lang.getBinding(\"java.decompiler\"));\n\t\tButton lockDecompiler = new ActionButton(decompileMatchedState, () -> {\n\t\t\tdecompilerManager.getServiceConfig().getPreferredJvmDecompiler().setValue(decompiler.getValue().getName());\n\t\t});\n\t\tlockDecompiler.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.SMALL);\n\t\tHBox boxDecompilerLabel = new HBox(labelDecompiler, new Spacer(), lockDecompiler);\n\t\tboxDecompilerLabel.setAlignment(Pos.CENTER_LEFT);\n\t\tLabel labelTimeout = new BoundLabel(Lang.getBinding(\"service.ui.decompile-pane-config.timeout-seconds\"));\n\t\tSpinner<Integer> spinTimeout = ObservableSpinner.intSpinner(config.getTimeoutSeconds(), 1, Integer.MAX_VALUE);\n\t\tspinTimeout.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL);\n\t\tcontent.add(decompileTitle, 0, 0, 2, 1);\n\t\tcontent.add(boxDecompilerLabel, 0, 1);\n\t\tcontent.add(fix(new ObservableComboBox<>(decompiler, new ArrayList<>(decompilerManager.getJvmDecompilers()))), 1, 1);\n\t\tcontent.add(labelTimeout, 0, 2);\n\t\tcontent.add(fix(spinTimeout), 1, 2);\n\n\t\treturn content;\n\t}\n\n\tprotected static Control fix(Control control) {\n\t\tcontrol.setMaxWidth(Double.MAX_VALUE);\n\t\tGridPane.setFillWidth(control, true);\n\t\treturn control;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/ClassPane.java",
    "content": "package software.coley.recaf.ui.pane.editing;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.navigation.ClassNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.ui.pane.editing.android.AndroidClassPane;\nimport software.coley.recaf.ui.pane.editing.jvm.JvmClassPane;\n\n/**\n * Common outline for displaying {@link ClassInfo} content.\n *\n * @author Matt Coley\n * @see JvmClassPane For {@link JvmClassInfo}.\n * @see AndroidClassPane For {@link AndroidClassInfo}.\n */\npublic abstract class ClassPane extends AbstractContentPane<ClassPathNode> implements ClassNavigable {\n\tprivate static final DebuggingLogger logger = Logging.get(ClassPane.class);\n\n\t@Override\n\tpublic void requestFocus(@Nonnull ClassMember member) {\n\t\t// Delegate to child components\n\t\tfor (Navigable navigableChild : getNavigableChildren())\n\t\t\tif (navigableChild instanceof ClassNavigable navigableClass)\n\t\t\t\tnavigableClass.requestFocus(member);\n\t\t\telse {\n\t\t\t\t// The side-tabs are not class-navigable, but some the side-tab's contents are.\n\t\t\t\t// We will thus check the children of non class-navigable components to address this.\n\t\t\t\tfor (Navigable childOfChild : navigableChild.getNavigableChildren())\n\t\t\t\t\tif (childOfChild instanceof ClassNavigable upnavigableClassatable)\n\t\t\t\t\t\tupnavigableClassatable.requestFocus(member);\n\t\t\t}\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\t// Update if class has changed.\n\t\tif (path instanceof ClassPathNode classPath) {\n\t\t\tthis.path = classPath;\n\t\t\tUnchecked.checkedForEach(pathUpdateListeners, listener -> listener.accept(classPath),\n\t\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when handling class-pane path update callback\", t));\n\n\t\t\t// Initialize UI if it has not been done yet.\n\t\t\tif (!hasDisplay())\n\t\t\t\tgenerateDisplay();\n\n\t\t\t// Notify children of change.\n\t\t\tgetNavigableChildren().forEach(child -> {\n\t\t\t\tif (child instanceof UpdatableNavigable updatable)\n\t\t\t\t\tupdatable.onUpdatePath(path);\n\t\t\t});\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassPathNode getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassPathNode getClassPath() {\n\t\treturn path;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/DecompileFailureButton.java",
    "content": "package software.coley.recaf.ui.pane.editing;\n\nimport atlantafx.base.controls.Popover;\nimport atlantafx.base.theme.Styles;\nimport javafx.animation.FadeTransition;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.paint.Color;\nimport javafx.util.Duration;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\n\n/**\n * Warning icon button to be displayed in the {@link ToolsContainerComponent} when a decompilation fails.\n *\n * @author Matt Coley\n */\npublic class DecompileFailureButton extends Button {\n\tprivate static final int animationCycleMillis = 250;\n\tprivate static final int animationCycles = 9; // odd so it when the animation plays it stays visible\n\tprivate final FadeTransition fadeIn = new FadeTransition();\n\tprivate Popover popover;\n\n\t/**\n\t * New button.\n\t */\n\tpublic DecompileFailureButton() {\n\t\t// Want to be invisible by default. When placed in a 'Group' it will take no space.\n\t\tsetVisible(false);\n\t\tsetOpacity(0);\n\n\t\t// Styles\n\t\tsetGraphic(new FontIconView(CarbonIcons.WARNING_ALT_FILLED, Color.YELLOW));\n\t\tgetStyleClass().addAll(Styles.BUTTON_ICON, Styles.ACCENT, Styles.FLAT);\n\n\t\t// Setup fade to be used when the button is shown.\n\t\tfadeIn.setNode(this);\n\t\tfadeIn.setFromValue(0);\n\t\tfadeIn.setToValue(1);\n\t\tfadeIn.setDuration(Duration.millis(animationCycleMillis));\n\t\tfadeIn.setCycleCount(animationCycles);\n\t\tfadeIn.setAutoReverse(true);\n\n\t\t// Show message on click.\n\t\tsetOnMousePressed(e -> {\n\t\t\tif (popover != null && popover.isShowing())\n\t\t\t\tpopover.hide();\n\n\t\t\tpopover = new Popover(new BoundLabel(Lang.getBinding(\"java.decompile-failure\")));\n\t\t\tpopover.setArrowLocation(Popover.ArrowLocation.BOTTOM_RIGHT);\n\t\t\tpopover.setAutoHide(true);\n\t\t\tpopover.show(this);\n\t\t});\n\t}\n\n\t/**\n\t * Animate the button being displayed.\n\t */\n\tpublic void animate() {\n\t\tif (popover != null && popover.isShowing())\n\t\t\tpopover.hide();\n\n\t\t// Ensure button is visible and play the fade-in animation\n\t\tsetVisible(true);\n\t\tfadeIn.play();\n\n\t\t// Show a message above the button indicating the class failed to decompile\n\t\tpopover = new Popover(new BoundLabel(Lang.getBinding(\"java.decompile-failure.brief\")));\n\t\tpopover.setArrowLocation(Popover.ArrowLocation.BOTTOM_RIGHT);\n\t\tpopover.setAutoHide(true);\n\t\tFxThreadUtil.delayedRun(animationCycleMillis, () -> popover.show(this));\n\t\tFxThreadUtil.delayedRun(animationCycleMillis * animationCycles, () -> popover.hide());\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/FileDisplayMode.java",
    "content": "package software.coley.recaf.ui.pane.editing;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.Node;\nimport org.kordamp.ikonli.Ikon;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.util.Icons;\n\n/**\n * Enum of available editors to display files with.\n *\n * @see FilePane\n */\npublic enum FileDisplayMode {\n\tHEX(\"menu.mode.file.hex\", CarbonIcons.NUMBER_0),\n\tTEXT(\"menu.mode.file.text\", CarbonIcons.STRING_TEXT),\n\tTEXT_BINARY_XML(\"menu.mode.file.text\", CarbonIcons.STRING_TEXT),\n\tIMAGE(\"menu.mode.file.image\", CarbonIcons.IMAGE),\n\tAUDIO(\"menu.mode.file.audio\", CarbonIcons.VOLUME_UP),\n\tVIDEO(\"menu.mode.file.video\", CarbonIcons.VIDEO),\n\tEXECUTABLE_PE(\"menu.mode.file.pe\", CarbonIcons.CODE),\n\tEXECUTABLE_ELF(\"menu.mode.file.elf\", CarbonIcons.CODE);\n\n\tprivate final String key;\n\tprivate final Ikon ikon;\n\n\tFileDisplayMode(@Nonnull String key, @Nonnull Ikon ikon) {\n\t\tthis.key = key;\n\t\tthis.ikon = ikon;\n\t}\n\n\t/**\n\t * @return Translation key.\n\t */\n\t@Nonnull\n\tpublic String getKey() {\n\t\treturn key;\n\t}\n\n\t/**\n\t * @return Display node.\n\t */\n\t@Nonnull\n\tpublic Node newIcon() {\n\t\tif (this == EXECUTABLE_PE || this == EXECUTABLE_ELF)\n\t\t\treturn Icons.getIconView(Icons.FILE_PROGRAM);\n\t\treturn new FontIconView(ikon);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/FilePane.java",
    "content": "package software.coley.recaf.ui.pane.editing;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.event.EventHandler;\nimport javafx.scene.input.KeyEvent;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.AudioFileInfo;\nimport software.coley.recaf.info.BinaryXmlFileInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.ImageFileInfo;\nimport software.coley.recaf.info.NativeLibraryFileInfo;\nimport software.coley.recaf.info.TextFileInfo;\nimport software.coley.recaf.info.VideoFileInfo;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.navigation.FileNavigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.ui.config.KeybindingConfig;\nimport software.coley.recaf.ui.pane.editing.binary.DecodingXmlPane;\nimport software.coley.recaf.ui.pane.editing.binary.ElfPane;\nimport software.coley.recaf.ui.pane.editing.binary.PePane;\nimport software.coley.recaf.ui.pane.editing.binary.hex.HexAdapter;\nimport software.coley.recaf.ui.pane.editing.binary.hex.HexConfig;\nimport software.coley.recaf.ui.pane.editing.media.AudioPane;\nimport software.coley.recaf.ui.pane.editing.media.ImagePane;\nimport software.coley.recaf.ui.pane.editing.media.VideoPane;\nimport software.coley.recaf.ui.pane.editing.text.TextPane;\nimport software.coley.recaf.util.ByteHeaderUtil;\nimport software.coley.recaf.util.threading.ThreadUtil;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static software.coley.recaf.ui.pane.editing.FileDisplayMode.*;\n\n/**\n * Displays various kinds of {@link FileInfo} content by delegating to another view based on the file type.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class FilePane extends AbstractContentPane<FilePathNode> implements FileNavigable {\n\tprivate static final Logger logger = Logging.get(FilePane.class);\n\tprivate final Instance<TextPane> textProvider;\n\tprivate final Instance<ImagePane> imageProvider;\n\tprivate final Instance<VideoPane> videoProvider;\n\tprivate final Instance<AudioPane> audioProvider;\n\tprivate final Instance<PePane> execPeProvider;\n\tprivate final Instance<ElfPane> execElfProvider;\n\tprivate final Instance<DecodingXmlPane> binaryXmlProvider;\n\tprivate final HexConfig hexConfig;\n\tprivate final KeybindingConfig keys;\n\tprivate final List<FileDisplayMode> fileDisplayModes = new ArrayList<>();\n\tprivate final EventHandler<KeyEvent> hexKeyAdapter = this::handleKeys;\n\tprivate FileDisplayMode mode = HEX;\n\n\t@Inject\n\tpublic FilePane(@Nonnull Instance<TextPane> textProvider,\n\t                @Nonnull Instance<AudioPane> audioProvider,\n\t                @Nonnull Instance<ImagePane> imageProvider,\n\t                @Nonnull Instance<VideoPane> videoProvider,\n\t                @Nonnull Instance<PePane> execPeProvider,\n\t                @Nonnull Instance<ElfPane> execElfProvider,\n\t                @Nonnull Instance<DecodingXmlPane> binaryXmlProvider,\n\t                @Nonnull HexConfig hexConfig,\n\t                @Nonnull KeybindingConfig keys) {\n\t\tthis.textProvider = textProvider;\n\t\tthis.audioProvider = audioProvider;\n\t\tthis.imageProvider = imageProvider;\n\t\tthis.videoProvider = videoProvider;\n\t\tthis.execPeProvider = execPeProvider;\n\t\tthis.execElfProvider = execElfProvider;\n\t\tthis.binaryXmlProvider = binaryXmlProvider;\n\t\tthis.hexConfig = hexConfig;\n\t\tthis.keys = keys;\n\n\t\taddEventFilter(KeyEvent.KEY_PRESSED, this::handleKeys);\n\t}\n\n\tpublic void setupForFileType(@Nonnull FileInfo info) {\n\t\tswitch (info) {\n\t\t\tcase TextFileInfo textFileInfo -> setFileDisplayModes(List.of(TEXT, HEX));\n\t\t\tcase BinaryXmlFileInfo binaryXmlFileInfo -> setFileDisplayModes(List.of(TEXT_BINARY_XML, HEX));\n\t\t\tcase VideoFileInfo videoFileInfo -> setFileDisplayModes(List.of(VIDEO, HEX));\n\t\t\tcase AudioFileInfo audioFileInfo -> setFileDisplayModes(List.of(AUDIO, HEX));\n\t\t\tcase ImageFileInfo imageFileInfo -> setFileDisplayModes(List.of(IMAGE, HEX));\n\t\t\tcase NativeLibraryFileInfo nativeLibraryFileInfo -> {\n\t\t\t\t// TODO: Do we want to further specify the type hierarchy for different kinds of native libs?\n\t\t\t\t//  - If we don't this kind of pattern may be repeated elsewhere\n\t\t\t\tif (ByteHeaderUtil.match(info.getRawContent(), ByteHeaderUtil.PE)) {\n\t\t\t\t\tsetFileDisplayModes(List.of(EXECUTABLE_PE, HEX));\n\t\t\t\t} else if (ByteHeaderUtil.match(info.getRawContent(), ByteHeaderUtil.ELF)) {\n\t\t\t\t\tsetFileDisplayModes(List.of(EXECUTABLE_ELF, HEX));\n\t\t\t\t} else {\n\t\t\t\t\tsetFileDisplayModes(List.of(HEX));\n\t\t\t\t}\n\t\t\t}\n\t\t\tdefault -> setFileDisplayModes(List.of(HEX));\n\t\t}\n\t}\n\n\t@Nonnull\n\tpublic List<FileDisplayMode> getFileDisplayModes() {\n\t\treturn Collections.unmodifiableList(fileDisplayModes);\n\t}\n\n\tpublic void setFileDisplayModes(@Nonnull List<FileDisplayMode> modes) {\n\t\tif (modes.isEmpty())\n\t\t\treturn;\n\n\t\tfileDisplayModes.clear();\n\t\tfileDisplayModes.addAll(modes);\n\n\t\tif (!modes.isEmpty())\n\t\t\tsetFileDisplayMode(modes.getFirst());\n\t}\n\n\t@Nonnull\n\tpublic FileDisplayMode getMode() {\n\t\treturn mode;\n\t}\n\n\tpublic void setFileDisplayMode(@Nonnull FileDisplayMode mode) {\n\t\tif (!fileDisplayModes.contains(mode)) {\n\t\t\tlogger.error(\"Cannot set mode to {}, supported modes are set to: {}\", mode, fileDisplayModes);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.mode = mode;\n\t\trefreshDisplay();\n\t}\n\n\t@Override\n\tprotected void generateDisplay() {\n\t\t// If you want to swap out the display, first clear the existing one.\n\t\t// Clearing is done automatically when changing the editor type.\n\t\tif (hasDisplay())\n\t\t\treturn;\n\n\t\t// Update content in pane.\n\t\tswitch (mode) {\n\t\t\tcase HEX -> setDisplay(new HexAdapter(hexConfig));\n\t\t\tcase TEXT -> setDisplay(textProvider.get());\n\t\t\tcase TEXT_BINARY_XML -> setDisplay(binaryXmlProvider.get());\n\t\t\tcase IMAGE -> setDisplay(imageProvider.get());\n\t\t\tcase AUDIO -> setDisplay(audioProvider.get());\n\t\t\tcase VIDEO -> setDisplay(videoProvider.get());\n\t\t\tcase EXECUTABLE_PE -> setDisplay(execPeProvider.get());\n\t\t\tcase EXECUTABLE_ELF -> setDisplay(execElfProvider.get());\n\t\t\tdefault -> throw new IllegalStateException(\"Unknown file mode: \" + mode.name());\n\t\t}\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\t// Update if class has changed.\n\t\tif (path instanceof FilePathNode filePath) {\n\t\t\tthis.path = filePath;\n\t\t\tpathUpdateListeners.forEach(listener -> listener.accept(filePath));\n\n\t\t\t// Initialize UI if it has not been done yet.\n\t\t\tif (!hasDisplay())\n\t\t\t\tgenerateDisplay();\n\n\t\t\t// Notify children of change.\n\t\t\tgetNavigableChildren().forEach(child -> {\n\t\t\t\tif (child instanceof UpdatableNavigable updatable)\n\t\t\t\t\tupdatable.onUpdatePath(path);\n\t\t\t});\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic FilePathNode getPath() {\n\t\treturn path;\n\t}\n\n\tprivate void handleKeys(@Nonnull KeyEvent e) {\n\t\tif ((getDisplay() instanceof HexAdapter adapter)) {\n\t\t\t// Using event filter here because the hex-editor otherwise consumes key events.\n\t\t\tif (keys.getSave().match(e))\n\t\t\t\tThreadUtil.run(adapter::save);\n\t\t\telse if (keys.getUndo().match(e)) {\n\t\t\t\tBundle<?> bundle = path.getValueOfType(Bundle.class);\n\t\t\t\tif (bundle != null)\n\t\t\t\t\tbundle.decrementHistory(path.getValue().getName());\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/ProblemOverlay.java",
    "content": "package software.coley.recaf.ui.pane.editing;\n\nimport atlantafx.base.controls.Popover;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.BooleanBinding;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.IntegerProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleIntegerProperty;\nimport javafx.beans.value.ChangeListener;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Group;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.control.Tooltip;\nimport javafx.scene.effect.BoxBlur;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.paint.Color;\nimport org.fxmisc.richtext.CodeArea;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.EditorComponent;\nimport software.coley.recaf.ui.control.richtext.ScrollbarPaddingUtil;\nimport software.coley.recaf.ui.control.richtext.problem.Problem;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemInvalidationListener;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemLevel;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemTracking;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.PlatformType;\n\nimport java.util.Collection;\n\n/**\n * Simple problem overlay, showing users how many problems of what type there are in the current {@link Editor}.\n *\n * @author Matt Coley\n */\npublic class ProblemOverlay extends Group implements EditorComponent, ProblemInvalidationListener {\n\tprivate final ChangeListener<Boolean> handleScrollbarVisibility = (ob, old, cur) -> ScrollbarPaddingUtil.handleScrollbarVisibility(this, cur);\n\tprivate final IntegerProperty problemCount = new SimpleIntegerProperty(-1);\n\tprivate Editor editor;\n\n\t/**\n\t * New problem overlay.\n\t */\n\tpublic ProblemOverlay() {\n\t\t// Display the number of problems, and their type.\n\t\tButton indicator = new Button();\n\t\tindicator.setFocusTraversable(false);\n\t\tindicator.getStyleClass().addAll(Styles.SMALL, \"muted\");\n\n\t\t// On-click to show a list of all problems\n\t\tindicator.setOnAction(e -> {\n\t\t\tProblemTracking problemTracking = editor.getProblemTracking();\n\t\t\tif (problemTracking == null) return;\n\n\t\t\t// Skip if no problems\n\t\t\tCollection<Problem> problems = problemTracking.getAllItems();\n\t\t\tif (problems.isEmpty()) return;\n\n\t\t\t// Create vertical list\n\t\t\tVBox content = new VBox();\n\t\t\tObservableList<Node> children = content.getChildren();\n\t\t\tfor (Problem problem : problems) {\n\t\t\t\t// Map level to graphic\n\t\t\t\tProblemLevel level = problem.level();\n\t\t\t\tColor levelColor = switch (level) {\n\t\t\t\t\tcase ERROR -> Color.RED;\n\t\t\t\t\tcase WARN -> Color.YELLOW;\n\t\t\t\t\tdefault -> Color.TURQUOISE;\n\t\t\t\t};\n\t\t\t\tNode graphic = switch (level) {\n\t\t\t\t\tcase ERROR -> new FontIconView(CarbonIcons.ERROR, levelColor);\n\t\t\t\t\tcase WARN -> new FontIconView(CarbonIcons.WARNING_ALT, levelColor);\n\t\t\t\t\tdefault -> new FontIconView(CarbonIcons.INFORMATION, levelColor);\n\t\t\t\t};\n\n\t\t\t\t// Create 'N: Message' layout\n\t\t\t\t//  - Exclude line number 'N' when line number is negative\n\t\t\t\tLabel messageLabel = new Label(problem.message());\n\t\t\t\tmessageLabel.setTextFill(levelColor);\n\t\t\t\tmessageLabel.setMaxWidth(Integer.MAX_VALUE);\n\t\t\t\tint line = problem.line();\n\t\t\t\tHBox problemBox;\n\t\t\t\tif (line >= 0) {\n\t\t\t\t\tLabel lineLabel = new Label(String.valueOf(line), graphic);\n\t\t\t\t\tlineLabel.setTextFill(levelColor);\n\t\t\t\t\tlineLabel.getStyleClass().add(Styles.TEXT_BOLD);\n\t\t\t\t\tproblemBox = new HBox(lineLabel, messageLabel);\n\t\t\t\t} else {\n\t\t\t\t\tmessageLabel.setGraphic(graphic);\n\t\t\t\t\tproblemBox = new HBox(messageLabel);\n\t\t\t\t}\n\t\t\t\tproblemBox.setSpacing(5);\n\t\t\t\tproblemBox.setPadding(new Insets(5));\n\n\t\t\t\t// Make on-hover more clearly show which problem is relevant.\n\t\t\t\t// The changing color on-hover also indicates clickable action.\n\t\t\t\tproblemBox.setOnMouseEntered(me -> problemBox.getStyleClass().add(\"background\"));\n\t\t\t\tproblemBox.setOnMouseExited(me -> problemBox.getStyleClass().remove(\"background\"));\n\n\t\t\t\t// When clicked, center the relevant problem.\n\t\t\t\tproblemBox.setOnMousePressed(me -> {\n\t\t\t\t\tCodeArea codeArea = editor.getCodeArea();\n\t\t\t\t\tcodeArea.moveTo(line - 1, 0);\n\t\t\t\t\tcodeArea.selectLine();\n\t\t\t\t\tcodeArea.showParagraphAtCenter(codeArea.getCurrentParagraph());\n\t\t\t\t});\n\t\t\t\tchildren.add(problemBox);\n\t\t\t}\n\n\t\t\tScrollPane scrollWrapper = new ScrollPane(content);\n\t\t\tscrollWrapper.maxHeightProperty().bind(editor.heightProperty().multiply(0.8));\n\t\t\tscrollWrapper.prefViewportWidthProperty().bind(editor.widthProperty().multiply(0.8));\n\t\t\tPopover popover = new Popover(scrollWrapper);\n\t\t\tpopover.setArrowLocation(Popover.ArrowLocation.TOP_RIGHT);\n\t\t\tpopover.show(indicator);\n\n\t\t\t// When mousing away from the problems list make it translucent so the text behind it can be read.\n\t\t\t// Opacity does not seem to work on Linux/Mac, so for now its limited to Windows.\n\t\t\tif (PlatformType.isWindows()) {\n\t\t\t\tBooleanProperty isMouseOver = new SimpleBooleanProperty(true);\n\t\t\t\tscrollWrapper.setOnMouseEntered(me -> isMouseOver.set(true));\n\t\t\t\tscrollWrapper.setOnMouseExited(me -> isMouseOver.set(false));\n\t\t\t\tscrollWrapper.effectProperty().bind(Bindings.when(isMouseOver.not())\n\t\t\t\t\t\t.then(new BoxBlur(5, 5, 1))\n\t\t\t\t\t\t.otherwise((BoxBlur) null));\n\t\t\t\tpopover.opacityProperty().bind(Bindings.when(isMouseOver)\n\t\t\t\t\t\t.then(1.0)\n\t\t\t\t\t\t.otherwise(0.4));\n\t\t\t}\n\t\t});\n\n\t\t// Can recycle the same instance with the indicator graphic\n\t\tFontIconView iconGood = new FontIconView(CarbonIcons.CHECKMARK, Color.LAWNGREEN);\n\t\tFontIconView iconInfo = new FontIconView(CarbonIcons.INFORMATION, Color.TURQUOISE);\n\t\tFontIconView iconWarning = new FontIconView(CarbonIcons.WARNING_ALT, Color.YELLOW);\n\t\tFontIconView iconError = new FontIconView(CarbonIcons.ERROR, Color.RED);\n\t\tindicator.graphicProperty().bind(problemCount.map(size -> {\n\t\t\t// Skip before linked to an editor.\n\t\t\tif (editor == null)\n\t\t\t\treturn null;\n\n\t\t\t// Skip if tracking is disabled.\n\t\t\tProblemTracking tracking = editor.getProblemTracking();\n\t\t\tif (tracking == null)\n\t\t\t\treturn null;\n\n\t\t\t// No problems\n\t\t\tif (size.intValue() <= 0)\n\t\t\t\treturn iconGood;\n\n\t\t\t// Show the count of each kind of problem\n\t\t\tHBox wrapper = new HBox();\n\t\t\twrapper.setSpacing(5);\n\t\t\tint info = tracking.getItems(p -> p.level().ordinal() <= ProblemLevel.INFO.ordinal()).size();\n\t\t\tint warn = tracking.getProblemsByLevel(ProblemLevel.WARN).size();\n\t\t\tint error = tracking.getProblemsByLevel(ProblemLevel.ERROR).size();\n\t\t\tif (info > 0) wrapper.getChildren().add(new Label(String.valueOf(info), iconInfo));\n\t\t\tif (warn > 0) wrapper.getChildren().add(new Label(String.valueOf(warn), iconWarning));\n\t\t\tif (error > 0) wrapper.getChildren().add(new Label(String.valueOf(error), iconError));\n\t\t\treturn wrapper;\n\t\t}));\n\t\tindicator.tooltipProperty().bind(problemCount.map(size -> {\n\t\t\tif (size.intValue() == 0)\n\t\t\t\treturn new Tooltip(Lang.get(\"assembler.problem.0\"));\n\t\t\telse if (size.intValue() == 1)\n\t\t\t\treturn new Tooltip(Lang.get(\"assembler.problem.1\"));\n\t\t\treturn new Tooltip(Lang.get(\"assembler.problem.N\").replace(\"N\", String.valueOf(size)));\n\t\t}));\n\n\t\tBooleanBinding hasProblems = problemCount.greaterThan(0);\n\t\thasProblems.addListener((ob, had, has) -> {\n\t\t\t// When there are problems, this is the left-most button.\n\t\t\t// When there are no problems, this is the only button.\n\t\t\t// Thus, our left-pill state should be bound to having problems.\n\t\t\tObservableList<String> styleClass = indicator.getStyleClass();\n\t\t\tif (has)\n\t\t\t\tstyleClass.add(Styles.LEFT_PILL);\n\t\t\telse\n\t\t\t\tstyleClass.remove(Styles.LEFT_PILL);\n\t\t});\n\n\t\t// Next/prev buttons\n\t\t//  - Actions only available when there are problems.\n\t\tButton prev = new ActionButton(CarbonIcons.ARROW_UP, () -> {\n\t\t\t// Skip if tracking is disabled.\n\t\t\tProblemTracking tracking = editor.getProblemTracking();\n\t\t\tif (tracking == null)\n\t\t\t\treturn;\n\n\t\t\t// Go to previous line with a problem.\n\t\t\tCodeArea codeArea = editor.getCodeArea();\n\t\t\tint line = codeArea.getCurrentParagraph() + 1;\n\t\t\tInteger prevLineWithError = tracking.getItems().floorKey(line - 1);\n\t\t\tif (prevLineWithError == null)\n\t\t\t\tprevLineWithError = tracking.getItems().floorKey(line);\n\t\t\tif (prevLineWithError == null)\n\t\t\t\tprevLineWithError = tracking.getItems().firstKey();\n\t\t\tif (prevLineWithError != null) {\n\t\t\t\tcodeArea.moveTo(prevLineWithError - 1, 0);\n\t\t\t\tcodeArea.selectLine();\n\t\t\t\tcodeArea.showParagraphAtCenter(codeArea.getCurrentParagraph());\n\t\t\t}\n\t\t});\n\t\tButton next = new ActionButton(CarbonIcons.ARROW_DOWN, () -> {\n\t\t\t// Skip if tracking is disabled.\n\t\t\tProblemTracking tracking = editor.getProblemTracking();\n\t\t\tif (tracking == null)\n\t\t\t\treturn;\n\n\t\t\t// Go to next line with a problem.\n\t\t\tCodeArea codeArea = editor.getCodeArea();\n\t\t\tint line = codeArea.getCurrentParagraph() + 1;\n\t\t\tInteger nextLineWithError = tracking.getItems().ceilingKey(line + 1);\n\t\t\tif (nextLineWithError == null)\n\t\t\t\tnextLineWithError = tracking.getItems().ceilingKey(line);\n\t\t\tif (nextLineWithError == null)\n\t\t\t\tnextLineWithError = tracking.getItems().firstKey();\n\t\t\tif (nextLineWithError != null) {\n\t\t\t\tcodeArea.moveTo(nextLineWithError - 1, 0);\n\t\t\t\tcodeArea.selectLine();\n\t\t\t\tcodeArea.showParagraphAtCenter(codeArea.getCurrentParagraph());\n\t\t\t}\n\t\t});\n\t\tprev.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.CENTER_PILL, Styles.SMALL, \"muted\");\n\t\tnext.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.RIGHT_PILL, Styles.SMALL, \"muted\");\n\t\tprev.setFocusTraversable(false);\n\t\tnext.setFocusTraversable(false);\n\t\tHBox buttons = new HBox(prev, next);\n\t\tbuttons.visibleProperty().bind(hasProblems);\n\n\t\t// Add to layout\n\t\tgetChildren().add(new HBox(indicator, new Group(buttons)));\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tProblemTracking tracking = editor.getProblemTracking();\n\t\tif (tracking != null) {\n\t\t\tthis.editor = editor;\n\n\t\t\t// Add to editor.\n\t\t\teditor.getPrimaryStack().getChildren().add(this);\n\n\t\t\t// Track if there are problems\n\t\t\ttracking.addListener(this);\n\n\t\t\t// Initial value set to trigger a UI refresh.\n\t\t\tproblemCount.set(tracking.getAllItems().size());\n\n\t\t\t// Layout tweaks\n\t\t\tStackPane.setAlignment(this, Pos.TOP_RIGHT);\n\t\t\tStackPane.setMargin(this, new Insets(7));\n\t\t\teditor.getVerticalScrollbar().visibleProperty().addListener(handleScrollbarVisibility);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tif (this.editor == editor) {\n\t\t\tthis.editor = null;\n\n\t\t\t// Remove self as listener.\n\t\t\tProblemTracking tracking = editor.getProblemTracking();\n\t\t\tif (tracking != null)\n\t\t\t\ttracking.removeListener(this);\n\n\t\t\t// Remove from editor.\n\t\t\teditor.getPrimaryStack().getChildren().remove(this);\n\t\t\teditor.getVerticalScrollbar().visibleProperty().removeListener(handleScrollbarVisibility);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void onProblemInvalidation() {\n\t\tProblemTracking tracking = editor.getProblemTracking();\n\t\tif (tracking != null)\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tproblemCount.set(tracking.getAllItems().size());\n\t\t\t\teditor.redrawParagraphGraphics();\n\t\t\t});\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/SideTabsInjector.java",
    "content": "package software.coley.recaf.ui.pane.editing;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.navigation.ClassNavigable;\nimport software.coley.recaf.ui.pane.editing.tabs.FieldsAndMethodsPane;\nimport software.coley.recaf.ui.pane.editing.tabs.InheritancePane;\nimport software.coley.recaf.ui.pane.editing.tabs.KotlinMetadataPane;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.kotlin.KotlinMetadata;\nimport software.coley.recaf.util.kotlin.model.KtClass;\n\nimport java.util.function.Consumer;\n\n/**\n * Injector for adding common tool tab content to {@link AbstractContentPane} instances.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class SideTabsInjector {\n\tprivate static final Logger logger = Logging.get(SideTabsInjector.class);\n\tprivate final Instance<FieldsAndMethodsPane> fieldsAndMethodsPaneProvider;\n\tprivate final Instance<InheritancePane> inheritancePaneProvider;\n\tprivate final Instance<KotlinMetadataPane> kotlinPaneProvider;\n\n\t@Inject\n\tpublic SideTabsInjector(@Nonnull Instance<FieldsAndMethodsPane> fieldsAndMethodsPaneProvider,\n\t                        @Nonnull Instance<InheritancePane> inheritancePaneProvider,\n\t                        @Nonnull Instance<KotlinMetadataPane> kotlinPaneProvider) {\n\t\tthis.fieldsAndMethodsPaneProvider = fieldsAndMethodsPaneProvider;\n\t\tthis.inheritancePaneProvider = inheritancePaneProvider;\n\t\tthis.kotlinPaneProvider = kotlinPaneProvider;\n\t}\n\n\t/**\n\t * Registers a path update listener within the pane. Once a {@link AbstractContentPane#getPath() path} is\n\t * assigned to the pane the side-tabs appropriate for the given content will be added.\n\t * <p>\n\t * <b>NOTE:</b> Because path update listeners are handled before paths are passed off to\n\t * {@link AbstractContentPane#getNavigableChildren()} we can add the side-tabs in the listener action\n\t * and still have them be notified of the path update handled in {@link AbstractContentPane#onUpdatePath(PathNode)}.\n\t *\n\t * @param pane\n\t * \t\tPane to inject into.\n\t */\n\tpublic void injectLater(@Nonnull AbstractContentPane<?> pane) {\n\t\tpane.addPathUpdateListener(Unchecked.cast(new TabAdder(pane)));\n\t}\n\n\t/**\n\t * Adds the appropriate side-tabs for the pane's current assigned {@link AbstractContentPane#getPath() path}.\n\t *\n\t * @param pane\n\t * \t\tPane to inject into.\n\t */\n\tpublic void injectNow(@Nonnull AbstractContentPane<?> pane) {\n\t\tPathNode<?> path = pane.getPath();\n\t\tif (path != null) injectInto(pane, path);\n\t\telse logger.warn(\"Attempted to inject side-tabs into content pane with no path registered yet\");\n\t}\n\n\tprivate void injectInto(@Nonnull AbstractContentPane<?> pane, @Nonnull PathNode<?> path) {\n\t\tif (path instanceof ClassPathNode classPath)\n\t\t\tinjectClassTabs(pane);\n\t\telse\n\t\t\tlogger.warn(\"Attempted to inject side-tabs into content pane with unsupported path type: {}\", path.getClass().getSimpleName());\n\t}\n\n\t/**\n\t * Adds class-specific content to the given pane.\n\t *\n\t * @param pane\n\t * \t\tPane to inject tabs into for {@link ClassInfo} content.\n\t */\n\tprivate void injectClassTabs(@Nonnull AbstractContentPane<?> pane) {\n\t\tif (pane instanceof ClassNavigable classNavigable) {\n\t\t\tFieldsAndMethodsPane fieldsAndMethodsPane = fieldsAndMethodsPaneProvider.get();\n\t\t\tInheritancePane inheritancePane = inheritancePaneProvider.get();\n\n\t\t\t// Setup so clicking on items in fields-and-methods pane will synchronize with content in our class pane.\n\t\t\tfieldsAndMethodsPane.setupSelectionNavigationListener(classNavigable);\n\n\t\t\t// Setup side-tabs\n\t\t\tpane.addSideTab(Lang.getBinding(\"fieldsandmethods.title\"), d -> Icons.getIconView(Icons.FIELD_N_METHOD), fieldsAndMethodsPane);\n\t\t\tpane.addSideTab(Lang.getBinding(\"hierarchy.title\"), CarbonIcons.FLOW, inheritancePane);\n\n\t\t\t// Add kotlin metadata tab if metadata is found.\n\t\t\tKtClass metadata = KotlinMetadata.extractKtModel(classNavigable.getClassPath().getValue());\n\t\t\tif (metadata != null) {\n\t\t\t\tKotlinMetadataPane kotlinMetadataPane = kotlinPaneProvider.get();\n\t\t\t\tkotlinMetadataPane.setMetadata(metadata);\n\t\t\t\tpane.addSideTab(Lang.getBinding(\"kotlinmetadata.title\"), d -> Icons.getIconView(Icons.KOTLIN_FLAT), kotlinMetadataPane);\n\t\t\t}\n\t\t} else {\n\t\t\tlogger.warn(\"Called 'injectClassTabs' for non-class navigable content\");\n\t\t}\n\t}\n\n\t/**\n\t * Listener that waits for a non-null input. Once found, the listener removes itself.\n\t */\n\tprivate class TabAdder implements Consumer<PathNode<?>> {\n\t\tprivate final AbstractContentPane<?> pane;\n\n\t\tprivate TabAdder(@Nonnull AbstractContentPane<?> pane) {this.pane = pane;}\n\n\t\t@Override\n\t\tpublic void accept(PathNode<?> path) {\n\t\t\tif (path != null) {\n\t\t\t\tinjectNow(pane);\n\t\t\t\tpane.removePathUpdateListener(Unchecked.cast(this));\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/ToolsContainerComponent.java",
    "content": "package software.coley.recaf.ui.pane.editing;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.value.ChangeListener;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Group;\nimport javafx.scene.Node;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.EditorComponent;\nimport software.coley.recaf.ui.control.richtext.ScrollbarPaddingUtil;\n\n/**\n * Editor component that sits in the bottom right of an {@link Editor} and houses other tool-like buttons.\n *\n * @author Matt Coley\n * @see AbstractDecompilerPaneConfigurator An example of a component that adds itself to this container.\n */\n@Dependent\npublic class ToolsContainerComponent implements EditorComponent {\n\tprivate final HBox container = new HBox();\n\tprivate final Group containerWrapper = new Group(container);\n\tprivate final ChangeListener<Boolean> handleScrollbarVisibility =\n\t\t\t(ob, old, cur) -> ScrollbarPaddingUtil.handleScrollbarVisibility(containerWrapper, cur);\n\n\t@Inject\n\tpublic ToolsContainerComponent() {\n\t\tStackPane.setAlignment(containerWrapper, Pos.BOTTOM_RIGHT);\n\t\tStackPane.setMargin(containerWrapper, new Insets(7));\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to add to this container.\n\t */\n\tpublic void add(@Nonnull Node node) {\n\t\t// Add at index 0 so new items are added to the left side.\n\t\tcontainer.getChildren().add(0, new Group(node));\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to add to this container.\n\t */\n\tpublic void addLast(@Nonnull Node node) {\n\t\t// Add at end so new items are added to the right side.\n\t\tcontainer.getChildren().add(new Group(node));\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\teditor.getPrimaryStack().getChildren().add(containerWrapper);\n\t\teditor.getVerticalScrollbar().visibleProperty().addListener(handleScrollbarVisibility);\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\teditor.getPrimaryStack().getChildren().remove(containerWrapper);\n\t\teditor.getVerticalScrollbar().visibleProperty().removeListener(handleScrollbarVisibility);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/android/AndroidClassEditorType.java",
    "content": "package software.coley.recaf.ui.pane.editing.android;\n\n/**\n * Supported editors for {@link AndroidClassPane}.\n *\n * @author Matt Coley\n */\npublic enum AndroidClassEditorType {\n\tSMALI,\n\tDECOMPILE\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/android/AndroidClassInfoProvider.java",
    "content": "package software.coley.recaf.ui.pane.editing.android;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.geometry.HPos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.Priority;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.navigation.ClassNavigable;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.pane.editing.AbstractClassInfoProvider;\nimport software.coley.recaf.ui.pane.editing.ToolsContainerComponent;\nimport software.coley.recaf.util.Lang;\n\nimport java.util.Objects;\n\n/**\n * Overlay component for {@link Editor} that allows quick display of Android class information.\n *\n * @author Matt Coley\n */\npublic class AndroidClassInfoProvider extends AbstractClassInfoProvider<AndroidClassInfo> {\n\t/**\n\t * @param toolsContainer\n\t * \t\tContainer to house tool buttons for display in the {@link Editor}.\n\t * @param classProvider\n\t * \t\tThe provider of the latest class info.\n\t */\n\tpublic AndroidClassInfoProvider(@Nonnull ToolsContainerComponent toolsContainer, @Nonnull ClassNavigable classProvider) {\n\t\tsuper(toolsContainer, classProvider);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected Node createInfoContent(@Nonnull AndroidClassInfo info) {\n\t\tGridPane content = new GridPane();\n\t\tColumnConstraints col1 = new ColumnConstraints();\n\t\tColumnConstraints col2 = new ColumnConstraints();\n\t\tcol2.setFillWidth(true);\n\t\tcol2.setHgrow(Priority.ALWAYS);\n\t\tcol2.setHalignment(HPos.RIGHT);\n\t\tcontent.getColumnConstraints().addAll(col1, col2);\n\t\tcontent.setHgap(10);\n\t\tcontent.setVgap(5);\n\n\t\tLabel titleLabel = new BoundLabel(Lang.getBinding(\"java.info\"));\n\t\ttitleLabel.getStyleClass().addAll(Styles.TEXT_UNDERLINED, Styles.TITLE_4);\n\n\t\tLabel sourceLabel = new BoundLabel(Lang.getBinding(\"java.info.sourcefile\"));\n\t\tsourceLabel.getStyleClass().addAll(Styles.TEXT_BOLD);\n\n\t\tLabel sourceValueLabel = new Label(Objects.requireNonNullElse(info.getSourceFileName(), \"\"));\n\n\t\tint row = 0;\n\t\tcontent.add(titleLabel, 0, row++, 2, 1);\n\n\t\tcontent.add(sourceLabel, 0, row);\n\t\tcontent.add(sourceValueLabel, 1, row++);\n\n\t\treturn content;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/android/AndroidClassPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.android;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.Label;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.ui.config.ClassEditingConfig;\nimport software.coley.recaf.ui.pane.editing.ClassPane;\nimport software.coley.recaf.ui.pane.editing.SideTabsInjector;\n\n/**\n * Displays {@link AndroidClassInfo} in a configurable manner.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class AndroidClassPane extends ClassPane {\n\tprivate final Instance<AndroidDecompilerPane> decompilerProvider;\n\tprivate AndroidClassEditorType editorType;\n\n\t@Inject\n\tpublic AndroidClassPane(@Nonnull ClassEditingConfig config,\n\t\t\t\t\t\t\t@Nonnull SideTabsInjector sideTabsInjector,\n\t\t\t\t\t\t\t@Nonnull Instance<AndroidDecompilerPane> decompilerProvider) {\n\t\tsideTabsInjector.injectLater(this);\n\t\teditorType = config.getDefaultAndroidEditor().getValue();\n\t\tthis.decompilerProvider = decompilerProvider;\n\t}\n\n\t/**\n\t * @return Current editor display type.\n\t */\n\t@Nonnull\n\tpublic AndroidClassEditorType getEditorType() {\n\t\treturn editorType;\n\t}\n\n\t/**\n\t * @param editorType\n\t * \t\tNew editor display type.\n\t */\n\tpublic void setEditorType(@Nonnull AndroidClassEditorType editorType) {\n\t\tif (this.editorType != editorType) {\n\t\t\tthis.editorType = editorType;\n\t\t\trefreshDisplay();\n\t\t}\n\t}\n\n\t@Override\n\tprotected void generateDisplay() {\n\t\t// If you want to swap out the display, first clear the existing one.\n\t\t// Clearing is done automatically when changing the editor type.\n\t\tif (hasDisplay())\n\t\t\treturn;\n\n\t\t// Update content in pane.\n\t\tAndroidClassEditorType type = getEditorType();\n\t\tswitch (type) {\n\t\t\tcase DECOMPILE -> setDisplay(decompilerProvider.get());\n\t\t\tcase SMALI -> {\n\t\t\t\t// TODO: Create 'Editor' set-up for smali\n\t\t\t\tLabel decompile = new Label(\"TODO: Smali\");\n\t\t\t\tsetDisplay(decompile);\n\t\t\t}\n\t\t\tdefault -> throw new IllegalStateException(\"Unknown editor type: \" + type.name());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/android/AndroidDecompilerPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.android;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.input.MouseButton;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.AndroidClassInfo;\nimport software.coley.recaf.path.IncompletePathException;\nimport software.coley.recaf.services.decompile.DecompilerManager;\nimport software.coley.recaf.services.info.association.FileTypeSyntaxAssociationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.source.AstResolveResult;\nimport software.coley.recaf.services.source.AstService;\nimport software.coley.recaf.services.tutorial.TutorialConfig;\nimport software.coley.recaf.ui.config.KeybindingConfig;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.ui.control.richtext.source.JavaContextActionSupport;\nimport software.coley.recaf.ui.pane.editing.AbstractDecompilePane;\nimport software.coley.recaf.ui.pane.editing.ToolsContainerComponent;\nimport software.coley.recaf.ui.pane.editing.jvm.DecompilerPaneConfig;\n\n/**\n * Displays an {@link AndroidClassInfo} via a configured {@link Editor} as decompiled by {@link DecompilerManager}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class AndroidDecompilerPane extends AbstractDecompilePane {\n\tprivate static final Logger logger = Logging.get(AndroidDecompilerPane.class);\n\n\t@Inject\n\tpublic AndroidDecompilerPane(@Nonnull DecompilerPaneConfig decompilerConfig,\n\t\t\t\t\t\t\t\t @Nonnull TutorialConfig tutorialConfig,\n\t                             @Nonnull KeybindingConfig keys,\n\t                             @Nonnull SearchBar searchBar,\n\t                             @Nonnull ToolsContainerComponent toolsContainer,\n\t                             @Nonnull AstService astService,\n\t                             @Nonnull JavaContextActionSupport contextActionSupport,\n\t                             @Nonnull FileTypeSyntaxAssociationService languageAssociation,\n\t                             @Nonnull DecompilerManager decompilerManager,\n\t                             @Nonnull Actions actions) {\n\t\tsuper(decompilerConfig, tutorialConfig, searchBar, astService, contextActionSupport, languageAssociation, decompilerManager);\n\n\t\t// Install tools container with configurator\n\t\tnew AndroidDecompilerPaneConfigurator(toolsContainer, decompilerConfig, decompiler, decompilerManager);\n\t\tnew AndroidClassInfoProvider(toolsContainer, this);\n\t\tinstallToolsContainer(toolsContainer);\n\n\t\t// Setup keybindings\n\t\tsetOnKeyPressed(e -> {\n\t\t\tif (keys.getRename().match(e)) {\n\t\t\t\t// Resolve what the caret position has, then handle renaming on the generic result.\n\t\t\t\tAstResolveResult result = contextActionSupport.resolvePosition(editor.getCodeArea().getCaretPosition());\n\t\t\t\tif (result != null)\n\t\t\t\t\tactions.rename(result.path());\n\t\t\t} else if (keys.getGoto().match(e)) {\n\t\t\t\t// Resolve what the caret position has, then handle navigating to the resulting path.\n\t\t\t\tAstResolveResult result = contextActionSupport.resolvePosition(editor.getCodeArea().getCaretPosition());\n\t\t\t\tif (result != null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tactions.gotoDeclaration(result.path());\n\t\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\t\t// Should realistically never happen\n\t\t\t\t\t\tlogger.warn(\"Cannot goto location, path incomplete\", ex);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tsetOnMouseClicked(e -> {\n\t\t\tif (e.getButton() == MouseButton.PRIMARY && e.isControlDown()) {\n\t\t\t\t// Resolve what the caret position has, then handle navigating to the resulting path.\n\t\t\t\tAstResolveResult result = contextActionSupport.resolvePosition(editor.getCodeArea().getCaretPosition());\n\t\t\t\tif (result != null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tactions.gotoDeclaration(result.path());\n\t\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\t\t// Should realistically never happen\n\t\t\t\t\t\tlogger.warn(\"Cannot goto location, path incomplete\", ex);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/android/AndroidDecompilerPaneConfigurator.java",
    "content": "package software.coley.recaf.ui.pane.editing.android;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.services.decompile.DecompilerManager;\nimport software.coley.recaf.services.decompile.JvmDecompiler;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.pane.editing.AbstractDecompilerPaneConfigurator;\nimport software.coley.recaf.ui.pane.editing.ToolsContainerComponent;\nimport software.coley.recaf.ui.pane.editing.jvm.DecompilerPaneConfig;\n\n/**\n * Overlay component for {@link Editor} that allows quick configuration of properties of a {@link AndroidDecompilerPane}.\n *\n * @author Matt Coley\n */\npublic class AndroidDecompilerPaneConfigurator extends AbstractDecompilerPaneConfigurator {\n\t/**\n\t * @param toolsContainer\n\t * \t\tContainer to house tool buttons for display in the {@link Editor}.\n\t * @param config\n\t * \t\tContaining {@link AndroidDecompilerPane} config singleton.\n\t * @param decompiler\n\t * \t\tLocal decompiler implementation.\n\t * @param decompilerManager\n\t * \t\tManager to pull available {@link JvmDecompiler} instances from.\n\t */\n\tpublic AndroidDecompilerPaneConfigurator(@Nonnull ToolsContainerComponent toolsContainer,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull DecompilerPaneConfig config,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull ObservableObject<JvmDecompiler> decompiler,\n\t\t\t\t\t\t\t\t\t\t\t @Nonnull DecompilerManager decompilerManager) {\n\t\tsuper(toolsContainer, config, decompiler, decompilerManager);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/AssemblerAstConsumer.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.ast.ASTElement;\nimport software.coley.recaf.services.assembler.AssemblerPipeline;\n\nimport java.util.List;\n\n/**\n * Outline of a component that takes in the parse results of an {@link AssemblerPipeline}.\n *\n * @author Matt Coley\n */\npublic interface AssemblerAstConsumer {\n\t/**\n\t * Called when {@link AssemblerPane} parses some AST.\n\t *\n\t * @param astElements\n\t * \t\tThe parsed AST. Verbosity of contents dependent on the phase.\n\t * @param phase\n\t * \t\tAST parse phase.\n\t */\n\tvoid consumeAst(@Nonnull List<ASTElement> astElements, @Nonnull AstPhase phase);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/AssemblerBuildConsumer.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.compiler.ClassRepresentation;\nimport me.darknet.assembler.compiler.ClassResult;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.services.assembler.AssemblerPipeline;\n\n/**\n * Outline of a component that takes in the build results of an {@link AssemblerPipeline}.\n *\n * @author Matt Coley\n * @see JvmAssemblerBuildConsumer For JVM build results.\n */\npublic interface AssemblerBuildConsumer {\n\t/**\n\t * Called when {@link AssemblerPane} builds a class.\n\t *\n\t * @param result\n\t * \t\tAssembler output model.\n\t * @param classInfo\n\t * \t\tRecaf class model.\n\t */\n\tvoid consumeClass(@Nonnull ClassResult result, @Nonnull ClassInfo classInfo);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/AssemblerContextActionSupport.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ContextMenu;\nimport org.fxmisc.richtext.CharacterHit;\nimport org.fxmisc.richtext.CodeArea;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.path.AssemblerPathData;\nimport software.coley.recaf.path.AssemblerPathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.cell.context.ContextMenuProviderService;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.source.JavaContextActionSupport;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.AssemblyResolution;\nimport software.coley.recaf.ui.pane.editing.assembler.resolve.AssemblyResolver;\n\n/**\n * Enables context actions on an {@link Editor} within an {@link AssemblerPane}.\n * The AST of the last successful parse from the assembler is used to query for menus offered by {@link ContextMenuProviderService}.\n *\n * @author Matt Coley\n * @see JavaContextActionSupport Alternative for context actions on Java source.\n */\n@Dependent\npublic class AssemblerContextActionSupport extends AstBuildConsumerComponent {\n\tprivate static final DebuggingLogger logger = Logging.get(AssemblerContextActionSupport.class);\n\tprivate final AssemblyResolver resolver = new AssemblyResolver();\n\tprivate final CellConfigurationService cellConfigurationService;\n\tprivate ContextMenu menu;\n\n\t@Inject\n\tpublic AssemblerContextActionSupport(@Nonnull CellConfigurationService cellConfigurationService) {\n\t\tthis.cellConfigurationService = cellConfigurationService;\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tsuper.install(editor);\n\t\tCodeArea area = editor.getCodeArea();\n\t\tarea.setOnContextMenuRequested(e -> {\n\t\t\tif (menu != null) {\n\t\t\t\tmenu.hide();\n\t\t\t\tmenu = null;\n\t\t\t}\n\n\t\t\t// Check AST model has been generated\n\t\t\tif (astElements == null) {\n\t\t\t\tlogger.warn(\"Could not request context menu, AST model not available\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Convert the event position to line/column\n\t\t\tCharacterHit hit = area.hit(e.getX(), e.getY());\n\n\t\t\t// Sync caret\n\t\t\tarea.moveTo(hit.getInsertionIndex());\n\n\t\t\t// Create menu\n\t\t\tAssemblyResolution resolution = resolver.resolveAt(hit.getInsertionIndex());\n\t\t\tAssemblerPathData data = new AssemblerPathData(editor, resolution);\n\t\t\tmenu = cellConfigurationService.contextMenuOf(ContextSource.DECLARATION, new AssemblerPathNode(path, data));\n\n\t\t\t// Show menu\n\t\t\tif (menu != null && !menu.getItems().isEmpty()) {\n\t\t\t\tmenu.setAutoHide(true);\n\t\t\t\tmenu.setHideOnEscape(true);\n\t\t\t\tmenu.show(area.getScene().getWindow(), e.getScreenX(), e.getScreenY());\n\t\t\t\tmenu.requestFocus();\n\t\t\t}\n\t\t});\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tsuper.uninstall(editor);\n\t\teditor.getCodeArea().setOnContextMenuRequested(null);\n\t}\n\n\t@Override\n\tprotected void onPipelineOutputUpdate() {\n\t\tresolver.setAst(astElements);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/AssemblerPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.geometry.Side;\nimport me.darknet.assembler.ast.ASTElement;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTField;\nimport me.darknet.assembler.ast.specific.ASTMethod;\nimport me.darknet.assembler.compile.JavaClassRepresentation;\nimport me.darknet.assembler.compiler.ClassRepresentation;\nimport me.darknet.assembler.compiler.ClassResult;\nimport me.darknet.assembler.error.Error;\nimport me.darknet.assembler.error.Result;\nimport me.darknet.assembler.parser.Token;\nimport me.darknet.assembler.util.Location;\nimport org.fxmisc.richtext.CodeArea;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.collections.box.Box;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.DirectoryPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.assembler.AssemblerPipeline;\nimport software.coley.recaf.services.assembler.AssemblerPipelineManager;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.navigation.ClassNavigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.LanguageStylesheets;\nimport software.coley.recaf.ui.config.KeybindingConfig;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.bracket.SelectedBracketTracking;\nimport software.coley.recaf.ui.control.richtext.problem.Problem;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemLevel;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemPhase;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemTracking;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.ui.control.richtext.suggest.AssemblerTabCompleter;\nimport software.coley.recaf.ui.control.richtext.suggest.TabCompletionConfig;\nimport software.coley.recaf.ui.control.richtext.syntax.RegexLanguages;\nimport software.coley.recaf.ui.control.richtext.syntax.RegexSyntaxHighlighter;\nimport software.coley.recaf.ui.pane.editing.AbstractContentPane;\nimport software.coley.recaf.ui.pane.editing.SideTabsInjector;\nimport software.coley.recaf.ui.pane.editing.tabs.FieldsAndMethodsPane;\nimport software.coley.recaf.util.Animations;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\n\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Display dissassembled {@link ClassInfo} and {@link ClassMember} content.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class AssemblerPane extends AbstractContentPane<PathNode<?>> implements UpdatableNavigable, ClassNavigable {\n\tprivate static final Logger logger = Logging.get(AssemblerPane.class);\n\n\tprivate final AssemblerPipelineManager pipelineManager;\n\tprivate final AssemblerToolTabs assemblerToolTabs;\n\tprivate final ProblemTracking problemTracking = new ProblemTracking();\n\tprivate final Editor editor = new Editor();\n\tprivate final AtomicBoolean updateLock = new AtomicBoolean();\n\tprivate AssemblerPipeline<? extends ClassInfo, ? extends ClassResult, ? extends ClassRepresentation> pipeline;\n\tprivate AssemblerTabCompleter tabCompleter;\n\tprivate ClassResult lastResult;\n\tprivate ClassRepresentation lastAssembledClassRepresentation;\n\tprivate ClassInfo lastAssembledClass;\n\tprivate List<Token> lastTokens;\n\tprivate List<ASTElement> lastRoughAst;\n\tprivate List<ASTElement> lastPartialAst;\n\tprivate List<ASTElement> lastConcreteAst;\n\n\t@Inject\n\tpublic AssemblerPane(@Nonnull AssemblerPipelineManager pipelineManager,\n\t                     @Nonnull AssemblerToolTabs assemblerToolTabs,\n\t                     @Nonnull AssemblerContextActionSupport contextActionSupport,\n\t                     @Nonnull SearchBar searchBar,\n\t                     @Nonnull KeybindingConfig keys,\n\t                     @Nonnull SideTabsInjector sideTabsInjector,\n\t                     @Nonnull WorkspaceManager workspaceManager,\n\t                     @Nonnull InheritanceGraphService graphService,\n\t\t\t\t\t\t @Nonnull CellConfigurationService cellConfigurationService,\n\t                     @Nonnull TabCompletionConfig tabCompletionConfig) {\n\t\tsuper(Side.BOTTOM);\n\n\t\tthis.pipelineManager = pipelineManager;\n\t\tthis.assemblerToolTabs = assemblerToolTabs;\n\n\t\tassemblerToolTabs.setOwner(this);\n\n\t\tint timeToWait = pipelineManager.getServiceConfig().getDisassemblyAstParseDelay().getValue();\n\n\t\tInheritanceGraph inheritanceGraph = Objects.requireNonNull(graphService.getCurrentWorkspaceInheritanceGraph(), \"Graph not created\");\n\t\tif (tabCompletionConfig.isEnabledInAssembler()) {\n\t\t\tWorkspace workspace = Objects.requireNonNull(workspaceManager.getCurrent());\n\t\t\ttabCompleter = new AssemblerTabCompleter(workspace, inheritanceGraph, cellConfigurationService, tabCompletionConfig);\n\t\t\teditor.setTabCompleter(tabCompleter);\n\t\t}\n\t\teditor.getCodeArea().getStylesheets().add(LanguageStylesheets.getJasmStylesheet());\n\t\teditor.setSelectedBracketTracking(new SelectedBracketTracking());\n\t\teditor.setSyntaxHighlighter(new RegexSyntaxHighlighter(RegexLanguages.getJasmLanguage()));\n\t\teditor.setProblemTracking(problemTracking);\n\t\teditor.getRootLineGraphicFactory().addDefaultCodeGraphicFactories();\n\t\teditor.getTextChangeEventStream()\n\t\t\t\t.successionEnds(Duration.ofMillis(timeToWait))\n\t\t\t\t.addObserver(e -> assemble());\n\n\t\tcontextActionSupport.install(editor);\n\t\tsearchBar.install(editor);\n\n\t\t// Context action should be passed along path updates\n\t\tchildren.add(contextActionSupport);\n\t\tchildren.add(assemblerToolTabs);\n\n\t\tsetOnKeyPressed(event -> {\n\t\t\tif (keys.getSave().match(event))\n\t\t\t\tassembleAndUpdateWorkspace();\n\t\t});\n\t}\n\n\t/**\n\t * Called by {@link #lateInitForClass(ClassPathNode)} or {@link #lateInitForMethod(ClassMemberPathNode)}.\n\t * <p>\n\t * Does late initialization that couldn't be done in the constructor.\n\t */\n\tprivate void lateInit() {\n\t\t// Some tool tabs are not initialized immediately in the constructor, so we do a late installation of them.\n\t\tassemblerToolTabs.install(editor);\n\t}\n\n\t/**\n\t * Called by {@link #onUpdatePath(PathNode)} once before the {@link #path} is set for the first time.\n\t * <p>\n\t * Sets up {@link FieldsAndMethodsPane} as a side-tab and sets up notifications for {@link AssemblerToolTabs}\n\t * and its children when the selected {@link ClassMember} in the {@link #lastConcreteAst} changes.\n\t *\n\t * @param classPath\n\t * \t\tThe given path.\n\t */\n\tprivate void lateInitForClass(@Nonnull ClassPathNode classPath) {\n\t\t// Since the content displayed is for a whole class, and the tool tabs are scoped to a method, we need to\n\t\t// update them when a method is selected. We do so by tracking the caret position for being within the\n\t\t// range of one of the methods in the last AST model.\n\t\tBox<PathNode<?>> lastPathBox = new Box<>();\n\t\tBox<ClassResult> lastResultBox = new Box<>();\n\t\teditor.getCaretPosEventStream().addObserver(e -> {\n\t\t\tif (lastConcreteAst == null)\n\t\t\t\treturn;\n\t\t\tClassInfo declaringClass = classPath.getValue();\n\t\t\tint caret = editor.getCodeArea().getCaretPosition();\n\t\t\tfor (ASTElement root : lastConcreteAst) {\n\t\t\t\tif (root instanceof ASTClass astClass) {\n\t\t\t\t\tfor (ASTElement child : astClass.children()) {\n\t\t\t\t\t\tClassMember classMember;\n\t\t\t\t\t\tif (child instanceof ASTMethod astMethod && astMethod.range().within(caret)) {\n\t\t\t\t\t\t\tString name = astMethod.getName().literal();\n\t\t\t\t\t\t\tString desc = astMethod.getDescriptor().literal();\n\t\t\t\t\t\t\tclassMember = declaringClass.getDeclaredMethod(name, desc);\n\t\t\t\t\t\t} else if (child instanceof ASTField astField && astField.range().within(caret)) {\n\t\t\t\t\t\t\tString name = astField.getName().literal();\n\t\t\t\t\t\t\tString desc = astField.getDescriptor().literal();\n\t\t\t\t\t\t\tclassMember = declaringClass.getDeclaredField(name, desc);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tPathNode<?> prior = lastPathBox.get();\n\t\t\t\t\t\tif (classMember != null) {\n\t\t\t\t\t\t\tClassMemberPathNode memberPath = classPath.child(classMember);\n\t\t\t\t\t\t\tif (!Objects.equals(prior, memberPath)) {\n\t\t\t\t\t\t\t\tlastPathBox.set(memberPath);\n\t\t\t\t\t\t\t\teachChild(UpdatableNavigable.class, c -> c.onUpdatePath(memberPath));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (!Objects.equals(prior, classPath)) {\n\t\t\t\t\t\t\t\tlastPathBox.set(classPath);\n\t\t\t\t\t\t\t\teachChild(UpdatableNavigable.class, c -> c.onUpdatePath(classPath));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tClassResult oldResult = lastResultBox.get();\n\t\t\t\t\t\tif (oldResult != lastResult) {\n\t\t\t\t\t\t\tlastResultBox.set(lastResult);\n\t\t\t\t\t\t\teachChild(AssemblerBuildConsumer.class, c -> c.consumeClass(lastResult, lastAssembledClass));\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// Common init\n\t\tassemblerToolTabs.onUpdatePath(classPath);\n\t\tlateInit();\n\t}\n\n\t/**\n\t * Called by {@link #onUpdatePath(PathNode)} once before the {@link #path} is set for the first time.\n\t *\n\t * @param memberPathNode\n\t * \t\tThe given path.\n\t */\n\tprivate void lateInitForMethod(@Nonnull ClassMemberPathNode memberPathNode) {\n\t\t// Common init\n\t\tassemblerToolTabs.onUpdatePath(memberPathNode);\n\t\tlateInit();\n\t}\n\n\t@Override\n\tprotected void generateDisplay() {\n\t\tif (!hasDisplay()) {\n\t\t\tsetDisplay(editor);\n\n\t\t\t// Trigger a disassembly so the initial text is set in the editor.\n\t\t\tdisassemble().whenComplete((unused, error) -> {\n\t\t\t\teditor.getCodeArea().getUndoManager().forgetHistory();\n\t\t\t\tif (error == null && unused.isOk())\n\t\t\t\t\tassemble();\n\t\t\t});\n\t\t}\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tsuper.disable();\n\t\teditor.close();\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassPathNode getClassPath() {\n\t\treturn Objects.requireNonNull(path.getPathOfType(ClassInfo.class), \"Missing class parent path\");\n\t}\n\n\t@Override\n\tpublic void requestFocus(@Nonnull ClassMember member) {\n\t\tif (lastConcreteAst != null) {\n\t\t\tfor (ASTElement root : lastConcreteAst) {\n\t\t\t\tif (root instanceof ASTClass astClass) {\n\t\t\t\t\tfor (ASTElement child : astClass.children()) {\n\t\t\t\t\t\tString name;\n\t\t\t\t\t\tString desc;\n\t\t\t\t\t\tif (member.isMethod() && child instanceof ASTMethod astMethod) {\n\t\t\t\t\t\t\tname = astMethod.getName().literal();\n\t\t\t\t\t\t\tdesc = astMethod.getDescriptor().literal();\n\t\t\t\t\t\t} else if (member.isField() && child instanceof ASTField astField) {\n\t\t\t\t\t\t\tname = astField.getName().literal();\n\t\t\t\t\t\t\tdesc = astField.getDescriptor().literal();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tname = desc = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (member.getName().equals(name) && member.getDescriptor().equals(desc)) {\n\t\t\t\t\t\t\tCodeArea area = editor.getCodeArea();\n\t\t\t\t\t\t\tarea.moveTo(child.range().start());\n\t\t\t\t\t\t\tarea.showParagraphAtCenter(area.getCurrentParagraph());\n\t\t\t\t\t\t\trequestFocus();\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void requestFocus() {\n\t\t// Delegate focus to the editor.\n\t\teditor.getCodeArea().requestFocus();\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\t// If we've not seen a path before, do late initialization for UI elements\n\t\t// that require path information.\n\t\tif (this.path == null)\n\t\t\tif (path instanceof ClassPathNode classPathNode)\n\t\t\t\tlateInitForClass(classPathNode);\n\t\t\telse if (path instanceof ClassMemberPathNode memberPathNode)\n\t\t\t\tlateInitForMethod(memberPathNode);\n\n\t\t// Update the path and call any path listeners.\n\t\tthis.path = path;\n\t\tUnchecked.checkedForEach(pathUpdateListeners, listener -> listener.accept(path),\n\t\t\t\t(listener, t) -> logger.error(\"Exception thrown when handling assembler-pane path update callback\", t));\n\n\t\t// Update UI state.\n\t\tif (!updateLock.get()) {\n\t\t\tpipeline = pipelineManager.getPipeline(path);\n\n\t\t\t// Setup from existing class data from the path.\n\t\t\tlastAssembledClass = path.getValueOfType(ClassInfo.class);\n\t\t\tlastAssembledClassRepresentation = pipeline.getRepresentation(Unchecked.cast(lastAssembledClass));\n\t\t\tlastResult = () -> lastAssembledClassRepresentation;\n\t\t\teachChild(UpdatableNavigable.class, c -> c.onUpdatePath(path));\n\t\t\teachChild(AssemblerBuildConsumer.class, c -> c.consumeClass(lastResult, lastAssembledClass));\n\n\t\t\t// Refresh the primary assembler display.\n\t\t\tFxThreadUtil.run(this::refreshDisplay);\n\t\t}\n\t}\n\n\t@Override\n\tpublic boolean isTrackable() {\n\t\t// Disabling tracking allows other panels with the same path-node to be opened.\n\t\treturn false;\n\t}\n\n\t/**\n\t * Disassemble the content of the {@link #path} and set the editor text to the resulting output.\n\t *\n\t * @return Future of disassembling completion.\n\t */\n\t@Nonnull\n\tprivate CompletableFuture<Result<String>> disassemble() {\n\t\tproblemTracking.removeByPhase(ProblemPhase.LINT);\n\t\treturn CompletableFuture.supplyAsync(() -> pipeline.disassemble(path))\n\t\t\t\t.orTimeout(10, TimeUnit.SECONDS)\n\t\t\t\t.whenCompleteAsync((result, error) -> {\n\t\t\t\t\tif (result != null)\n\t\t\t\t\t\tresult.ifOk(editor::setText).ifErr(errors -> processErrors(errors, ProblemPhase.LINT));\n\t\t\t\t\telse\n\t\t\t\t\t\tlogger.error(\"Disassemble encountered an unexpected error\", error);\n\t\t\t\t}, FxThreadUtil.executor());\n\t}\n\n\t/**\n\t * Parse the current editor's text into AST.\n\t *\n\t * @return Future of parse completion.\n\t */\n\t@Nonnull\n\tprivate CompletableFuture<Result<List<ASTElement>>> parseAST() {\n\t\t// Nothing to parse\n\t\tif (editor.getText().isBlank()) return CompletableFuture.completedFuture(null);\n\n\t\t// Clear lint errors since we are running the linter again.\n\t\tif (problemTracking.removeByPhase(ProblemPhase.LINT))\n\t\t\tFxThreadUtil.run(editor::redrawParagraphGraphics);\n\n\t\treturn CompletableFuture.supplyAsync(() -> {\n\t\t\t// Tokenize the current input.\n\t\t\tResult<List<Token>> tokenResult = pipeline.tokenize(editor.getText(), \"<assembler>\");\n\n\t\t\t// Process any errors and assign the latest token list.\n\t\t\tif (tokenResult.hasErr())\n\t\t\t\tprocessErrors(tokenResult.errors(), ProblemPhase.LINT);\n\t\t\tlastTokens = tokenResult.get();\n\n\t\t\t// Attempt to parse the token list into 'rough' AST.\n\t\t\tResult<List<ASTElement>> roughResult = pipeline.roughParse(lastTokens).ifOk(roughAst -> {\n\t\t\t\tlastRoughAst = roughAst;\n\t\t\t}).ifErr((partialAst, errors) -> {\n\t\t\t\t// We failed to parse the token list fully, but may have a partial result.\n\t\t\t\teachChild(AssemblerAstConsumer.class, c -> c.consumeAst(partialAst, AstPhase.ROUGH_PARTIAL));\n\t\t\t\tlastPartialAst = partialAst;\n\t\t\t\tprocessErrors(errors, ProblemPhase.LINT);\n\t\t\t});\n\n\t\t\t// Attempt to complete parsing and transform the 'rough' AST into a 'concrete' AST.\n\t\t\tif (roughResult.isOk()) {\n\t\t\t\treturn pipeline.concreteParse(roughResult.get()).ifOk(concreteAst -> {\n\t\t\t\t\t// The transform was a success.\n\t\t\t\t\tlastConcreteAst = concreteAst;\n\t\t\t\t\tif (tabCompleter != null) tabCompleter.setAst(concreteAst);\n\t\t\t\t\teachChild(AssemblerAstConsumer.class, c -> c.consumeAst(concreteAst, AstPhase.CONCRETE));\n\t\t\t\t}).ifErr((partialAst, errors) -> {\n\t\t\t\t\t// The transform failed.\n\t\t\t\t\tlastPartialAst = partialAst;\n\t\t\t\t\tif (tabCompleter != null) tabCompleter.setAst(partialAst);\n\t\t\t\t\teachChild(AssemblerAstConsumer.class, c -> c.consumeAst(partialAst, AstPhase.CONCRETE_PARTIAL));\n\t\t\t\t\tprocessErrors(errors, ProblemPhase.LINT);\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tif (tabCompleter != null) tabCompleter.clearAst();\n\t\t\t}\n\n\t\t\t// Fall-back to rough AST.\n\t\t\treturn roughResult;\n\t\t}).whenComplete((res, err) -> {\n\t\t\tif (err != null) {\n\t\t\t\tlogger.error(\"Failed to parse assembler content\", err);\n\t\t\t\tFxThreadUtil.run(() -> Animations.animateFailure(editor, 1000));\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Build the contents of the editor's text and update the workspace when successful.\n\t *\n\t * @return Future of parse completion.\n\t */\n\t@Nonnull\n\tprivate CompletableFuture<Void> assemble() {\n\t\t// Ensure the AST is up-to-date before moving onto build stage.\n\t\treturn parseAST().thenAccept(astResult -> {\n\t\t\t// If the parse finished, clear old build errors.\n\t\t\tif (problemTracking.removeByPhase(ProblemPhase.BUILD))\n\t\t\t\tFxThreadUtil.run(editor::redrawParagraphGraphics);\n\n\t\t\t// Skip if any problems remain in the AST\n\t\t\tif (!problemTracking.getProblemsByPhase(ProblemPhase.LINT).isEmpty() || lastConcreteAst == null)\n\t\t\t\treturn;\n\n\t\t\t// Clear build errors since we are running the build process again.\n\t\t\tproblemTracking.removeByPhase(ProblemPhase.BUILD);\n\n\t\t\ttry {\n\t\t\t\t// The 'path' field may not hold the up-to-date class in the workspace.\n\t\t\t\t//\n\t\t\t\t// We want to look up the current version of the class to pass to the assembler pipeline\n\t\t\t\t// so that we don't overwrite the current class with whatever state happens to be in this path instance.\n\t\t\t\t//\n\t\t\t\t// Note: The path isn't updated in this pane because that may mess with the UX - which we want to avoid.\n\t\t\t\tfinal PathNode<?> localPath;\n\t\t\t\tif (path instanceof ClassMemberPathNode memberPath) {\n\t\t\t\t\tClassPathNode localClassPath = Objects.requireNonNull(memberPath.getParent(), \"Member path missing parent\");\n\t\t\t\t\tClassInfo classInfo = localClassPath.getValue();\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tWorkspace workspace = Objects.requireNonNull(memberPath.getValueOfType(Workspace.class), \"Member path missing workspace\");\n\t\t\t\t\tClassPathNode newClassPath = Objects.requireNonNull(workspace.findClass(classInfo.getName()), \"Member parent no longer defined in workspace\");\n\t\t\t\t\tlocalPath = newClassPath.child(member);\n\t\t\t\t} else if (path instanceof ClassPathNode classPath) {\n\t\t\t\t\tClassInfo classInfo = classPath.getValue();\n\t\t\t\t\tWorkspace workspace = Objects.requireNonNull(classPath.getValueOfType(Workspace.class), \"Class path missing workspace\");\n\t\t\t\t\tlocalPath = Objects.requireNonNull(workspace.findClass(classInfo.getName()), \"Class no longer defined in workspace\");\n\t\t\t\t} else {\n\t\t\t\t\tthrow new IllegalStateException(\"Unsupported assembler path type: \" + path);\n\t\t\t\t}\n\n\t\t\t\tpipeline.assemble(lastConcreteAst, localPath).ifOk(result -> {\n\t\t\t\t\tClassRepresentation representation = result.representation();\n\n\t\t\t\t\tlastResult = result;\n\t\t\t\t\tlastAssembledClassRepresentation = representation;\n\n\t\t\t\t\tif (representation instanceof JavaClassRepresentation javaClassRep) {\n\t\t\t\t\t\t// Get last class's field/method count for ensuring no duplication happened.\n\t\t\t\t\t\t// If the user changes the definition of a field/method it inserts a new one\n\t\t\t\t\t\t// instead of changing the existing declaration.\n\t\t\t\t\t\tint fieldCount = lastAssembledClass.getFields().size();\n\t\t\t\t\t\tint methodCount = lastAssembledClass.getMethods().size();\n\n\t\t\t\t\t\t// Update last class.\n\t\t\t\t\t\tClassInfo assembledClass = pipeline.getClassInfo(Unchecked.cast(javaClassRep));\n\n\t\t\t\t\t\t// Update the local path value, this will also inform sub-components of the new content.\n\t\t\t\t\t\t// The update-lock must be set so that we don't trigger a disassembly (which would trigger an endless loop)\n\t\t\t\t\t\tupdateLock.set(true);\n\t\t\t\t\t\tif (localPath instanceof ClassPathNode classPath) {\n\t\t\t\t\t\t\t// Only update if the class name was untouched.\n\t\t\t\t\t\t\tif (assembledClass.getName().equals(classPath.getValue().getName())) {\n\t\t\t\t\t\t\t\tClassPathNode newPath = classPath.getParent().child(assembledClass);\n\t\t\t\t\t\t\t\tonUpdatePath(newPath);\n\t\t\t\t\t\t\t\tlastAssembledClass = assembledClass;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tASTElement sourceAst = lastConcreteAst.get(0);\n\t\t\t\t\t\t\t\tError err = new Error(\"Changing the class name is not allowed in the assembler.\\n\" +\n\t\t\t\t\t\t\t\t\t\t\"Use Recaf's refactoring capabilities to rename classes.\", sourceAst.location());\n\t\t\t\t\t\t\t\tprocessErrors(List.of(err), ProblemPhase.BUILD);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (localPath instanceof ClassMemberPathNode memberPath) {\n\t\t\t\t\t\t\tClassMember oldMember = memberPath.getValue();\n\t\t\t\t\t\t\tClassMember newMember;\n\t\t\t\t\t\t\tString memberType;\n\t\t\t\t\t\t\tif (oldMember.isMethod()) {\n\t\t\t\t\t\t\t\tmemberType = \"method\";\n\t\t\t\t\t\t\t\tnewMember = assembledClass.getDeclaredMethod(oldMember.getName(), oldMember.getDescriptor());\n\t\t\t\t\t\t\t\tif (methodCount != assembledClass.getMethods().size()){\n\t\t\t\t\t\t\t\t\tASTElement sourceAst = lastConcreteAst.get(0);\n\t\t\t\t\t\t\t\t\tError err = new Error(\"Assembling in this context detected a change in the number of methods.\\n\" +\n\t\t\t\t\t\t\t\t\t\t\t\"Check and see if your class has illegal duplicate method definitions.\", sourceAst.location());\n\t\t\t\t\t\t\t\t\tprocessErrors(List.of(err), ProblemPhase.BUILD);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tmemberType = \"field\";\n\t\t\t\t\t\t\t\tnewMember = assembledClass.getDeclaredField(oldMember.getName(), oldMember.getDescriptor());\n\t\t\t\t\t\t\t\tif (fieldCount != assembledClass.getFields().size()){\n\t\t\t\t\t\t\t\t\tASTElement sourceAst = lastConcreteAst.get(0);\n\t\t\t\t\t\t\t\t\tError err = new Error(\"Assembling in this context detected a change in the number of fields.\\n\" +\n\t\t\t\t\t\t\t\t\t\t\t\"Check and see if your class has illegal duplicate field definitions.\", sourceAst.location());\n\t\t\t\t\t\t\t\t\tprocessErrors(List.of(err), ProblemPhase.BUILD);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Only update if the member name/type was untouched.\n\t\t\t\t\t\t\tif (newMember != null) {\n\t\t\t\t\t\t\t\tlastAssembledClass = assembledClass;\n\t\t\t\t\t\t\t\tDirectoryPathNode packagePath = memberPath.getParent().getParent();\n\t\t\t\t\t\t\t\tClassMemberPathNode newPath = packagePath.child(lastAssembledClass).child(newMember);\n\t\t\t\t\t\t\t\tonUpdatePath(newPath);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tASTElement sourceAst = lastConcreteAst.get(0);\n\t\t\t\t\t\t\t\tError err = new Error(\"Changing the \" + memberType + \" name/type is not allowed in the assembler.\\n\" +\n\t\t\t\t\t\t\t\t\t\t\"Use Recaf's refactoring capabilities to rename fields & methods.\", sourceAst.location());\n\t\t\t\t\t\t\t\tprocessErrors(List.of(err), ProblemPhase.BUILD);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tupdateLock.set(false);\n\t\t\t\t\t}\n\t\t\t\t\t/*\n\t\t\t\t\telse if (representation instanceof AndroidClassRepresentation androidClassRep) {\n\t\t\t\t\t\tlastAssembledClass = pipeline.getClassInfo(Unchecked.cast(androidClassRep));\n\t\t\t\t\t}\n\t\t\t\t\t */\n\n\t\t\t\t\teachChild(AssemblerBuildConsumer.class, c -> c.consumeClass(result, lastAssembledClass));\n\t\t\t\t}).ifErr(errors -> processErrors(errors, ProblemPhase.BUILD))\n\t\t\t\t\t\t.ifWarn(warns -> processErrors(warns, ProblemLevel.WARN, ProblemPhase.BUILD));\n\t\t\t} catch (Throwable ex) {\n\t\t\t\tlogger.error(\"Uncaught exception when assembling contents of {}\", path, ex);\n\t\t\t\tFxThreadUtil.run(() -> Animations.animateFailure(editor, 1000));\n\t\t\t}\n\t\t});\n\t}\n\n\t@Nonnull\n\t@SuppressWarnings(\"unchecked\")\n\tprivate CompletableFuture<Void> assembleAndUpdateWorkspace() {\n\t\treturn assemble().whenComplete((unused, error) -> {\n\t\t\tif (lastAssembledClass != null && problemTracking.getProblemsByLevel(ProblemLevel.ERROR).isEmpty()) {\n\t\t\t\tupdateLock.set(true);\n\t\t\t\ttry {\n\t\t\t\t\tBundle<ClassInfo> bundle = path.getValueOfType(Bundle.class);\n\t\t\t\t\tif (bundle == null)\n\t\t\t\t\t\tthrow new IllegalStateException(\"Bundle not included in assembler pane's path\");\n\t\t\t\t\tbundle.put(lastAssembledClass);\n\t\t\t\t\tFxThreadUtil.run(() -> Animations.animateSuccess(editor, 1000));\n\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\tlogger.error(\"Uncaught exception when updating class of {}\", lastAssembledClass.getName(), t);\n\t\t\t\t\tFxThreadUtil.run(() -> Animations.animateWarn(editor, 1000));\n\t\t\t\t} finally {\n\t\t\t\t\tupdateLock.set(false);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tFxThreadUtil.run(() -> Animations.animateFailure(editor, 1000));\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Add the given errors to {@link #problemTracking} and refresh the UI.\n\t *\n\t * @param errors\n\t * \t\tProblems to add.\n\t * @param phase\n\t * \t\tPhase the problems belong to.\n\t */\n\tprivate void processErrors(@Nonnull Collection<Error> errors, @Nonnull ProblemPhase phase) {\n\t\tprocessErrors(errors, ProblemLevel.ERROR, phase);\n\t}\n\n\t/**\n\t * Add the given errors to {@link #problemTracking} and refresh the UI.\n\t *\n\t * @param errors\n\t * \t\tProblems to add.\n\t * @param level\n\t * \t\tSeverity level of problems.\n\t * @param phase\n\t * \t\tPhase the problems belong to.\n\t */\n\tprivate void processErrors(@Nonnull Collection<? extends Error> errors, @Nonnull ProblemLevel level, @Nonnull ProblemPhase phase) {\n\t\tfor (Error error : errors) {\n\t\t\tLocation location = error.getLocation();\n\t\t\tint line = location == null ? 1 : location.line();\n\t\t\tint start = location == null ? 1 : location.column();\n\t\t\tint length = location == null ? 1 : location.length();\n\t\t\tProblem problem = new Problem(line, start, length, level, phase, error.getMessage());\n\t\t\tproblemTracking.addItem(problem);\n\n\t\t\t// REMOVE IS TRACING PARSER ERRORS\n\t\t\t/*\n\t\t\tThrowable trace = new Throwable();\n\t\t\ttrace.setStackTrace(error.getInCodeSource());\n\t\t\tlogger.trace(\"Assembler error\", trace);\n\t\t\tSystem.err.println(error);\n\t\t\t*/\n\t\t}\n\t\tFxThreadUtil.run(() -> {\n\t\t\tif (!errors.isEmpty())\n\t\t\t\teditor.redrawParagraphGraphics();\n\t\t});\n\t}\n\n\t/**\n\t * @return Editor control holding the assembly contents.\n\t */\n\t@Nonnull\n\tpublic Editor getEditor() {\n\t\treturn editor;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/AssemblerToolTabs.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport me.darknet.assembler.ast.ASTElement;\nimport me.darknet.assembler.compiler.ClassResult;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.EditorComponent;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * UI component shown at the bottom of the {@link AssemblerPane}. Wraps various tool components.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class AssemblerToolTabs implements AssemblerAstConsumer, AssemblerBuildConsumer,\n\t\tNavigable, UpdatableNavigable, EditorComponent {\n\tprivate final Instance<JvmStackAnalysisPane> jvmStackAnalysisPaneProvider;\n\tprivate final Instance<JvmVariablesPane> jvmVariablesPaneProvider;\n\tprivate final Instance<JvmExpressionCompilerPane> jvmExpressionCompilerPaneProvider;\n\tprivate final Instance<SnippetsPane> snippetPaneProvider;\n\tprivate final Instance<ControlFlowLines> controlFlowLineProvider;\n\tprivate final List<Navigable> children = new CopyOnWriteArrayList<>();\n\tprivate AssemblerPane owner;\n\tprivate PathNode<?> path;\n\n\t@Inject\n\tpublic AssemblerToolTabs(@Nonnull Instance<JvmStackAnalysisPane> jvmStackAnalysisPaneProvider,\n\t                         @Nonnull Instance<JvmVariablesPane> jvmVariablesPaneProvider,\n\t                         @Nonnull Instance<JvmExpressionCompilerPane> jvmExpressionCompilerPaneProvider,\n\t                         @Nonnull Instance<SnippetsPane> snippetPaneProvider,\n\t                         @Nonnull Instance<ControlFlowLines> controlFlowLineProvider) {\n\t\tthis.jvmStackAnalysisPaneProvider = jvmStackAnalysisPaneProvider;\n\t\tthis.jvmVariablesPaneProvider = jvmVariablesPaneProvider;\n\t\tthis.jvmExpressionCompilerPaneProvider = jvmExpressionCompilerPaneProvider;\n\t\tthis.snippetPaneProvider = snippetPaneProvider;\n\t\tthis.controlFlowLineProvider = controlFlowLineProvider;\n\t}\n\n\t/**\n\t * @param owner\n\t * \t\tThe owning assembler pane to add tabs to.\n\t */\n\tpublic void setOwner(@Nonnull AssemblerPane owner) {\n\t\tthis.owner = owner;\n\t}\n\n\tprivate void createChildren(@Nonnull ClassInfo classInPath) {\n\t\tchildren.clear();\n\n\t\tif (classInPath.isJvmClass()) {\n\t\t\t// Create contents for JVM classes\n\t\t\tJvmStackAnalysisPane stackAnalysisPane = jvmStackAnalysisPaneProvider.get();\n\t\t\tJvmVariablesPane variablesPane = jvmVariablesPaneProvider.get();\n\t\t\tJvmExpressionCompilerPane expressionPane = jvmExpressionCompilerPaneProvider.get();\n\t\t\tSnippetsPane snippetsPane = snippetPaneProvider.get();\n\t\t\tControlFlowLines controlFlowLines = controlFlowLineProvider.get();\n\t\t\tchildren.addAll(Arrays.asList(stackAnalysisPane, variablesPane, expressionPane, snippetsPane, controlFlowLines));\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\towner.clearSideTabs();\n\t\t\t\towner.addSideTab(Lang.getBinding(\"assembler.analysis.title\"), CarbonIcons.VIEW_NEXT, stackAnalysisPane);\n\t\t\t\towner.addSideTab(Lang.getBinding(\"assembler.variables.title\"), CarbonIcons.LIST_BOXES, variablesPane);\n\t\t\t\towner.addSideTab(Lang.getBinding(\"assembler.playground.title\"), CarbonIcons.CODE, expressionPane);\n\t\t\t\towner.addSideTab(Lang.getBinding(\"assembler.snippets.title\"), CarbonIcons.BOOK, snippetsPane);\n\t\t\t\t// Note: There is intentionally no tab for the control flow lines at the moment\n\t\t\t});\n\t\t} else if (classInPath.isAndroidClass()) {\n\t\t\t// Create contents for Android classes\n\t\t}\n\t}\n\n\t@Override\n\tpublic void consumeAst(@Nonnull List<ASTElement> astElements, @Nonnull AstPhase phase) {\n\t\tfor (Navigable navigableChild : getNavigableChildren())\n\t\t\tif (navigableChild instanceof AssemblerAstConsumer consumer)\n\t\t\t\tconsumer.consumeAst(astElements, phase);\n\n\t\tonUpdatePath(path);\n\t}\n\n\t@Override\n\tpublic void consumeClass(@Nonnull ClassResult result,\n\t                         @Nonnull ClassInfo classInfo) {\n\t\tfor (Navigable navigableChild : getNavigableChildren())\n\t\t\tif (navigableChild instanceof AssemblerBuildConsumer consumer)\n\t\t\t\tconsumer.consumeClass(result, classInfo);\n\n\t\tonUpdatePath(path);\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tboolean isInitial = this.path == null;\n\n\t\tthis.path = path;\n\n\t\tif (isInitial) {\n\t\t\tClassInfo classInPath = path.getValueOfType(ClassInfo.class);\n\t\t\tif (classInPath != null)\n\t\t\t\tcreateChildren(classInPath);\n\t\t}\n\n\t\tfor (Navigable navigableChild : getNavigableChildren())\n\t\t\tif (navigableChild instanceof UpdatableNavigable updatableChild)\n\t\t\t\tupdatableChild.onUpdatePath(path);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.unmodifiableList(children);\n\t}\n\n\t@Override\n\tpublic void requestFocus() {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tfor (Navigable navigableChild : getNavigableChildren())\n\t\t\tnavigableChild.disable();\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tchildren.forEach(n -> {\n\t\t\tif (n instanceof EditorComponent component)\n\t\t\t\tcomponent.install(editor);\n\t\t});\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tchildren.forEach(n -> {\n\t\t\tif (n instanceof EditorComponent component)\n\t\t\t\tcomponent.uninstall(editor);\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/AstBuildConsumerComponent.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.ast.ASTElement;\nimport me.darknet.assembler.compile.analysis.MethodAnalysisLookup;\nimport me.darknet.assembler.compile.visitor.JavaCompileResult;\nimport me.darknet.assembler.compiler.ClassResult;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\n\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Contextual assembler component that consumes assembler outputs.\n *\n * @author Matt Coley\n */\npublic abstract class AstBuildConsumerComponent extends ContextualAssemblerComponent implements AssemblerAstConsumer, AssemblerBuildConsumer {\n\tprotected List<ASTElement> astElements = Collections.emptyList();\n\tprotected MethodAnalysisLookup analysisLookup;\n\tprotected ClassInfo currentClass;\n\tprotected MethodMember currentMethod;\n\tprotected FieldMember currentField;\n\n\t@Override\n\tprotected void onSelectClass(@Nonnull ClassInfo declared) {\n\t\tcurrentClass = declared;\n\t\tcurrentField = null;\n\t\tcurrentMethod = null;\n\t\tonClassSelected();\n\t}\n\n\t@Override\n\tprotected void onSelectMethod(@Nonnull ClassInfo declaring, @Nonnull MethodMember method) {\n\t\tcurrentClass = declaring;\n\t\tcurrentMethod = method;\n\t\tcurrentField = null;\n\t\tonMethodSelected();\n\t}\n\n\t@Override\n\tprotected void onSelectField(@Nonnull ClassInfo declaring, @Nonnull FieldMember field) {\n\t\tcurrentClass = declaring;\n\t\tcurrentMethod = null;\n\t\tcurrentField = field;\n\t\tonFieldSelected();\n\t}\n\n\t@Override\n\tpublic void consumeAst(@Nonnull List<ASTElement> astElements, @Nonnull AstPhase phase) {\n\t\tthis.astElements = Collections.unmodifiableList(astElements);\n\t\tonPipelineOutputUpdate();\n\t}\n\n\t@Override\n\tpublic void consumeClass(@Nonnull ClassResult result, @Nonnull ClassInfo classInfo) {\n\t\tif (result instanceof JavaCompileResult javaCompileResult) {\n\t\t\tanalysisLookup = javaCompileResult.analysisLookup();\n\t\t\tonPipelineOutputUpdate();\n\t\t}\n\t}\n\n\t/**\n\t * Called when {@link #currentClass} is updated.\n\t */\n\tprotected void onClassSelected() {}\n\n\t/**\n\t * Called when {@link #currentMethod} is updated.\n\t */\n\tprotected void onMethodSelected() {}\n\n\t/**\n\t * Called when {@link #currentField} is updated.\n\t */\n\tprotected void onFieldSelected() {}\n\n\t/**\n\t * Called when {@link #astElements} or {@link #analysisLookup} updates.\n\t */\n\tprotected void onPipelineOutputUpdate() {}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/AstPhase.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport software.coley.recaf.services.assembler.AssemblerPipeline;\n\nimport java.util.List;\n\n/**\n * Phases of assembly going through a {@link AssemblerPipeline}.\n *\n * @author Matt Coley\n */\npublic enum AstPhase {\n\t/**\n\t * @see AssemblerPipeline#concreteParse(List)\n\t */\n\tROUGH_PARTIAL,\n\t/**\n\t * @see AssemblerPipeline#roughParse(List)\n\t */\n\tROUGH,\n\t/**\n\t * @see AssemblerPipeline#concreteParse(List)\n\t */\n\tCONCRETE_PARTIAL,\n\t/**\n\t * @see AssemblerPipeline#concreteParse(List)\n\t */\n\tCONCRETE\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/AstUsages.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.ast.ASTElement;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Stream;\n\n/**\n * Models AST usage for things that can be read/written to <i>(literally or in an abstract sense)</i>.\n *\n * @param readers\n * \t\tElements that read from the target item.\n * @param writers\n * \t\tElements that write to the target item.\n * @param isParameter\n *        {@code true} when this variable is defined as a method parameter.\n */\npublic record AstUsages(@Nonnull List<ASTElement> readers, @Nonnull List<ASTElement> writers, boolean isParameter) {\n\t/**\n\t * Empty usage.\n\t */\n\tpublic static final AstUsages EMPTY_USAGE = new AstUsages(Collections.emptyList(), Collections.emptyList(), false);\n\n\t/**\n\t * @return Stream of both readers and writers.\n\t */\n\t@Nonnull\n\tpublic Stream<ASTElement> readersAndWriters() {\n\t\treturn Stream.concat(readers.stream(), writers.stream());\n\t}\n\n\t/**\n\t * @param element\n\t * \t\tElement to add as a reader.\n\t *\n\t * @return Copy with added element.\n\t */\n\t@Nonnull\n\tpublic AstUsages withNewRead(@Nonnull ASTElement element) {\n\t\tList<ASTElement> newReaders = new ArrayList<>(readers);\n\t\tnewReaders.add(element);\n\t\treturn new AstUsages(newReaders, writers, isParameter);\n\t}\n\n\t/**\n\t * @param element\n\t * \t\tElement to add as a writer.\n\t *\n\t * @return Copy with added element.\n\t */\n\t@Nonnull\n\tpublic AstUsages withNewWrite(@Nonnull ASTElement element) {\n\t\tList<ASTElement> newWriters = new ArrayList<>(writers);\n\t\tnewWriters.add(element);\n\t\treturn new AstUsages(readers, newWriters, isParameter);\n\t}\n\n\t/**\n\t * @return Copy with {@link #isParameter()} set to {@code true}.\n\t */\n\t@Nonnull\n\tpublic AstUsages asParameter() {\n\t\treturn new AstUsages(readers, writers, true);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/ContextualAssemblerComponent.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.layout.BorderPane;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.cell.context.BasicBlacklistingContextSource;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.EditorComponent;\n\nimport java.util.Collection;\nimport java.util.Collections;\n\n/**\n * Common base for context-sensitive components for the assembler.\n *\n * @author Matt Coley\n */\npublic abstract class ContextualAssemblerComponent extends BorderPane implements Navigable, UpdatableNavigable, EditorComponent {\n\t/** Source with 'refactor' entries hidden */\n\tprotected static final ContextSource CONTEXT_SOURCE = new BasicBlacklistingContextSource(false, s -> s.contains(\"refactor\"));\n\tprotected PathNode<?> path;\n\tprotected Editor editor;\n\n\t/**\n\t * Called when {@link #path} is set to a {@link ClassPathNode}.\n\t *\n\t * @param declared\n\t * \t\tTarget class.\n\t */\n\tprotected abstract void onSelectClass(@Nonnull ClassInfo declared);\n\n\t/**\n\t * Called when {@link #path} is set to a {@link ClassMemberPathNode} of a method.\n\t *\n\t * @param declaring\n\t * \t\tDeclaring class.\n\t * @param method\n\t * \t\tTarget method.\n\t */\n\tprotected abstract void onSelectMethod(@Nonnull ClassInfo declaring, @Nonnull MethodMember method);\n\n\t/**\n\t * Called when {@link #path} is set to a {@link ClassMemberPathNode} of a field.\n\t *\n\t * @param declaring\n\t * \t\tDeclaring class.\n\t * @param field\n\t * \t\tTarget field.\n\t */\n\tprotected abstract void onSelectField(@Nonnull ClassInfo declaring, @Nonnull FieldMember field);\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tthis.editor = editor;\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tthis.editor = null;\n\t\tsetDisable(true);\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tthis.path = path;\n\t\tif (path instanceof ClassPathNode classPathNode) {\n\t\t\tonSelectClass(classPathNode.getValue());\n\t\t} else if (path instanceof ClassMemberPathNode classMemberPathNode) {\n\t\t\tClassInfo declaring = classMemberPathNode.getParent().getValue();\n\t\t\tClassMember member = classMemberPathNode.getValue();\n\t\t\tif (member.isField()) {\n\t\t\t\tonSelectField(declaring, (FieldMember) member);\n\t\t\t} else {\n\t\t\t\tonSelectMethod(declaring, (MethodMember) member);\n\t\t\t}\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\t// no-op\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/ControlFlowLines.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport atlantafx.base.controls.Spacer;\nimport it.unimi.dsi.fastutil.ints.Int2IntArrayMap;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.animation.Interpolator;\nimport javafx.animation.Transition;\nimport javafx.scene.Node;\nimport javafx.scene.effect.Blend;\nimport javafx.scene.effect.BlendMode;\nimport javafx.scene.effect.Bloom;\nimport javafx.scene.effect.ColorAdjust;\nimport javafx.scene.effect.Effect;\nimport javafx.scene.effect.Glow;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.paint.Color;\nimport javafx.util.Duration;\nimport me.darknet.assembler.ast.ASTElement;\nimport me.darknet.assembler.ast.primitive.ASTArray;\nimport me.darknet.assembler.ast.primitive.ASTCode;\nimport me.darknet.assembler.ast.primitive.ASTInstruction;\nimport me.darknet.assembler.ast.primitive.ASTLabel;\nimport me.darknet.assembler.ast.primitive.ASTObject;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTMethod;\nimport me.darknet.assembler.util.Location;\nimport org.reactfx.Change;\nimport org.slf4j.Logger;\nimport software.coley.bentofx.control.canvas.PixelCanvas;\nimport software.coley.bentofx.control.canvas.PixelPainter;\nimport software.coley.bentofx.control.canvas.PixelPainterIntArgb;\nimport software.coley.collections.box.Box;\nimport software.coley.collections.box.IntBox;\nimport software.coley.observables.AbstractObservable;\nimport software.coley.observables.ChangeListener;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.linegraphics.AbstractLineGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.linegraphics.AbstractTextBoundLineGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.linegraphics.LineContainer;\nimport software.coley.recaf.util.Colors;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.threading.ThreadUtil;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.BiConsumer;\nimport java.util.function.Consumer;\n\n/**\n * Controller for displaying control flow jump lines.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class ControlFlowLines extends AstBuildConsumerComponent {\n\tprivate static final Logger logger = Logging.get(ControlFlowLines.class);\n\tprivate static final Set<String> FLOW_INSN_SET = Set.of(\"goto\", \"ifnull\", \"ifnonnull\", \"ifeq\", \"ifne\", \"ifle\", \"ifge\", \"iflt\", \"ifgt\",\n\t\t\t\"if_acmpeq\", \"if_acmpne\", \"if_icmpeq\", \"if_icmpge\", \"if_icmpgt\", \"if_icmple\", \"if_icmplt\", \"if_icmpne\", \"jsr\");\n\tprivate static final Set<String> SWITCH_INSNS = Set.of(\"tableswitch\", \"lookupswitch\");\n\tprivate final Consumer<Change<Integer>> onCaretMove = this::onCaretMove;\n\tprivate final ObservableObject<ASTInstruction> currentInstructionSelection = new ObservableObject<>(null);\n\tprivate final ObservableBoolean drawLines = new ObservableBoolean(false);\n\tprivate final ControlFlowLineFactory arrowFactory = new ControlFlowLineFactory();\n\tprivate final ControlFlowLinesConfig config;\n\tprivate final ListenerHost redrawListener = new ListenerHost();\n\tprivate List<LabelData> model = Collections.emptyList();\n\n\t@Inject\n\tpublic ControlFlowLines(@Nonnull ControlFlowLinesConfig config) {\n\t\tthis.config = config;\n\t}\n\n\t@Override\n\t@SuppressWarnings(\"unchecked\")\n\tpublic void install(@Nonnull Editor editor) {\n\t\tsuper.install(editor);\n\n\t\teditor.getRootLineGraphicFactory().addLineGraphicFactory(arrowFactory);\n\t\teditor.getCaretPosEventStream().addObserver(onCaretMove);\n\n\t\tdrawLines.addChangeListener(redrawListener);\n\t\tcurrentInstructionSelection.addChangeListener(redrawListener);\n\t\tconfig.getConnectionMode().addChangeListener(redrawListener);\n\t\tconfig.getRenderMode().addChangeListener(redrawListener);\n\t}\n\n\t@Override\n\t@SuppressWarnings(\"unchecked\")\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tsuper.uninstall(editor);\n\n\t\tarrowFactory.cleanup();\n\t\teditor.getRootLineGraphicFactory().removeLineGraphicFactory(arrowFactory);\n\t\teditor.getCaretPosEventStream().removeObserver(onCaretMove);\n\n\t\tdrawLines.removeChangeListener(redrawListener);\n\t\tcurrentInstructionSelection.removeChangeListener(redrawListener);\n\t\tconfig.getConnectionMode().removeChangeListener(redrawListener);\n\t\tconfig.getRenderMode().removeChangeListener(redrawListener);\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tsuper.disable();\n\n\t\tif (editor != null)\n\t\t\tuninstall(editor);\n\t}\n\n\t@Override\n\tprotected void onClassSelected() {\n\t\tclearData();\n\t}\n\n\t@Override\n\tprotected void onMethodSelected() {\n\t\tupdateModel();\n\t}\n\n\t@Override\n\tprotected void onFieldSelected() {\n\t\tclearData();\n\t}\n\n\t@Override\n\tprotected void onPipelineOutputUpdate() {\n\t\t// Keep a reference to the old model.\n\t\tList<LabelData> oldModel = model;\n\n\t\t// Update the model.\n\t\tupdateModel();\n\n\t\t// If the model has changed, refresh the visible paragraph graphics.\n\t\t// This can mean a new label as added, new reference to one, etc.\n\t\t// This could result in new line shapes, so redrawing them all is wise.\n\t\tList<LabelData> newModel = model;\n\t\tEditor editor = this.editor;\n\t\tif (editor != null && !Objects.equals(oldModel, newModel))\n\t\t\tFxThreadUtil.run(editor::redrawParagraphGraphics);\n\t}\n\n\t/**\n\t * Handles updating the {@link ControlFlowLineFactory}.\n\t * <p>\n\t * This logic is shoe-horned into here <i>(for now)</i> because\n\t * the variable tracking logic is internal to this class only.\n\t *\n\t * @param caretChange\n\t * \t\tCaret pos change.\n\t */\n\tprivate void onCaretMove(@Nonnull Change<Integer> caretChange) {\n\t\t// Find the selected element off of the FX thread, then update our selection and line draw states on the FX thread.\n\t\tCompletableFuture.supplyAsync(this::findSelected, ThreadUtil.executor()).thenAcceptAsync(selected -> {\n\t\t\ttry {\n\t\t\t\t// Check if the selection was a label or supported instruction.\n\t\t\t\tASTInstruction current = selected.get();\n\t\t\t\tboolean hasSelection = false;\n\t\t\t\tif (current instanceof ASTLabel) {\n\t\t\t\t\thasSelection = true;\n\t\t\t\t} else if (current != null) {\n\t\t\t\t\tString insnName = current.identifier().content();\n\t\t\t\t\tList<ASTElement> arguments = current.arguments();\n\t\t\t\t\tif (!arguments.isEmpty() && FLOW_INSN_SET.contains(insnName) || SWITCH_INSNS.contains(insnName)) {\n\t\t\t\t\t\thasSelection = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcurrentInstructionSelection.setValue(current);\n\t\t\t\tdrawLines.setValue(hasSelection);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Error updating control flow line targets\", t);\n\t\t\t}\n\t\t}, FxThreadUtil.executor());\n\t}\n\n\t@Nonnull\n\tprivate Box<ASTInstruction> findSelected() {\n\t\tBox<ASTInstruction> selected = new Box<>();\n\t\ttry {\n\t\t\tint pos = editor.getCodeArea().getCaretPosition();\n\t\t\tint line = editor.getCodeArea().getCurrentParagraph() + 1;\n\t\t\tfor (ASTElement element : astElements) {\n\t\t\t\tif (element == null)\n\t\t\t\t\tcontinue;\n\t\t\t\tif (element.range().within(pos)) {\n\t\t\t\t\telement.walk(ast -> {\n\t\t\t\t\t\tif (ast instanceof ASTInstruction instruction) {\n\t\t\t\t\t\t\tLocation location = ast.location();\n\t\t\t\t\t\t\tif (location != null && location.line() == line)\n\t\t\t\t\t\t\t\tselected.set(instruction);\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tString identifier = instruction.identifier().content();\n\t\t\t\t\t\t\t\tif ((\"tableswitch\".equals(identifier)\n\t\t\t\t\t\t\t\t\t\t|| \"lookupswitch\".equals(identifier))\n\t\t\t\t\t\t\t\t\t\t&& ast.range().within(pos)) {\n\t\t\t\t\t\t\t\t\tselected.set(instruction);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn !selected.isSet();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (Throwable t) {\n\t\t\tlogger.warn(\"Error finding selected AST element\", t);\n\t\t}\n\t\treturn selected;\n\t}\n\n\tprivate void updateModel() {\n\t\t// Skip if we've not selected a method\n\t\tif (currentMethod == null) {\n\t\t\tmodel = Collections.emptyList();\n\t\t\treturn;\n\t\t}\n\n\t\t// Collect all label usage information from the AST.\n\t\tMap<String, AstUsages> labelUsages = collectLabelReferences();\n\t\tmodel = labelUsages.entrySet().stream()\n\t\t\t\t.filter(e -> !e.getValue().readers().isEmpty()) // Must have a label declaration\n\t\t\t\t.map(e -> new LabelData(e.getKey(), e.getValue(), new Int2IntArrayMap(), new IntBox(-1), new Box<>()))\n\t\t\t\t.sorted(Comparator.comparing(LabelData::name))\n\t\t\t\t.toList();\n\t\tmodel.forEach(data -> {\n\t\t\tList<LabelData> overlapping = data.computeOverlapping(model);\n\t\t\tIntBox slot = data.lineSlot();\n\t\t\tint slotIndex = 0;\n\t\t\twhile (true) {\n\t\t\t\tincr:\n\t\t\t\t{\n\t\t\t\t\tfor (LabelData d : overlapping) {\n\t\t\t\t\t\tif (slotIndex == d.lineSlot().get()) {\n\t\t\t\t\t\t\tslotIndex++;\n\t\t\t\t\t\t\tbreak incr;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tslot.set(slotIndex);\n\t\t});\n\t}\n\n\t@Nonnull\n\tprivate Map<String, AstUsages> collectLabelReferences() {\n\t\tAstUsages emptyUsage = AstUsages.EMPTY_USAGE;\n\t\tMap<String, List<ASTElement>> labelReads = new HashMap<>();\n\t\tMap<String, List<ASTElement>> labelWrites = new HashMap<>();\n\t\tBiConsumer<String, ASTElement> readUpdater = (name, element) -> labelReads.computeIfAbsent(name, n -> new ArrayList<>()).add(element);\n\t\tBiConsumer<String, ASTElement> writeUpdater = (name, element) -> labelWrites.computeIfAbsent(name, n -> new ArrayList<>()).add(element);\n\t\tif (astElements != null) {\n\t\t\tConsumer<ASTMethod> methodConsumer = astMethod -> {\n\t\t\t\tif (currentMethod != null && !Objects.equals(currentMethod.getName(), astMethod.getName().literal()))\n\t\t\t\t\treturn;\n\t\t\t\tASTCode code = astMethod.code();\n\t\t\t\tif (code == null)\n\t\t\t\t\treturn;\n\t\t\t\tfor (ASTInstruction instruction : code.instructions()) {\n\t\t\t\t\tif (instruction instanceof ASTLabel label) {\n\t\t\t\t\t\treadUpdater.accept(label.identifier().content(), label);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tString insnName = instruction.identifier().content();\n\t\t\t\t\t\tList<ASTElement> arguments = instruction.arguments();\n\t\t\t\t\t\tif (!arguments.isEmpty()) {\n\t\t\t\t\t\t\tif (FLOW_INSN_SET.contains(insnName)) {\n\t\t\t\t\t\t\t\twriteUpdater.accept(arguments.getLast().content(), instruction);\n\t\t\t\t\t\t\t} else if (\"tableswitch\".equals(insnName)) {\n\t\t\t\t\t\t\t\tif (!instruction.arguments().isEmpty() && instruction.arguments().getFirst() instanceof ASTObject switchObj) {\n\t\t\t\t\t\t\t\t\tASTArray cases = switchObj.value(\"cases\");\n\t\t\t\t\t\t\t\t\tASTElement defaultCase = switchObj.value(\"default\");\n\t\t\t\t\t\t\t\t\tcases.values().forEach(caseAst -> writeUpdater.accept(caseAst.content(), caseAst));\n\t\t\t\t\t\t\t\t\twriteUpdater.accept(defaultCase.content(), defaultCase);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (\"lookupswitch\".equals(insnName)) {\n\t\t\t\t\t\t\t\tif (!instruction.arguments().isEmpty() && instruction.arguments().getFirst() instanceof ASTObject switchObj) {\n\t\t\t\t\t\t\t\t\tASTElement defaultCase = switchObj.value(\"default\");\n\t\t\t\t\t\t\t\t\twriteUpdater.accept(defaultCase.content(), defaultCase);\n\t\t\t\t\t\t\t\t\tswitchObj.values().pairs().forEach(pair -> {\n\t\t\t\t\t\t\t\t\t\twriteUpdater.accept(pair.second().content(), pair.first());\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t\tfor (ASTElement astElement : astElements) {\n\t\t\t\tif (astElement instanceof ASTMethod astMethod) {\n\t\t\t\t\tmethodConsumer.accept(astMethod);\n\t\t\t\t} else if (astElement instanceof ASTClass astClass) {\n\t\t\t\t\tfor (ASTElement child : astClass.children()) {\n\t\t\t\t\t\tif (child instanceof ASTMethod astMethod) {\n\t\t\t\t\t\t\tmethodConsumer.accept(astMethod);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tSet<String> keys = new HashSet<>();\n\t\tkeys.addAll(labelReads.keySet());\n\t\tkeys.addAll(labelWrites.keySet());\n\t\tMap<String, AstUsages> labelUsages = new HashMap<>();\n\t\tfor (String key : keys) {\n\t\t\tList<ASTElement> reads = labelReads.get(key);\n\t\t\tList<ASTElement> writes = labelWrites.get(key);\n\t\t\tlabelUsages.put(key, new AstUsages(\n\t\t\t\t\tObjects.requireNonNullElse(reads, Collections.emptyList()),\n\t\t\t\t\tObjects.requireNonNullElse(writes, Collections.emptyList()),\n\t\t\t\t\tfalse));\n\t\t}\n\t\treturn labelUsages;\n\t}\n\n\tprivate void clearData() {\n\t\tmodel = Collections.emptyList();\n\t\tcurrentMethod = null;\n\t}\n\n\t/**\n\t * Highlighter which shows read and write access of a {@link LabelData}.\n\t */\n\tprivate class ControlFlowLineFactory extends AbstractTextBoundLineGraphicFactory {\n\t\tprivate static final int MASK_NORTH = 1;\n\t\tprivate static final int MASK_SOUTH = 2;\n\t\tprivate static final int MASK_EAST = 4;\n\t\tprivate final ArrayList<ASTElement> switchDestinations = new ArrayList<>(64);\n\t\tprivate final int[] offsets = new int[containerWidth];\n\t\tprivate final long rainbowHueRotationDurationMillis = 3000;\n\t\tprivate final PixelPainter<?> pixelPainter = new PixelPainterIntArgb();\n\n\t\tprivate ControlFlowLineFactory() {\n\t\t\tsuper(AbstractLineGraphicFactory.P_BRACKET_MATCH - 1);\n\n\t\t\t// Populate offsets\n\t\t\tint j = 0;\n\t\t\tfor (int i = 0; i < offsets.length; i++)\n\t\t\t\toffsets[i] = 1 + (i * 3);\n\t\t}\n\n\t\t@Override\n\t\tpublic void apply(@Nonnull LineContainer container, int paragraph) {\n\t\t\tList<LabelData> localModel = model;\n\n\t\t\tif (!drawLines.getValue() || localModel.isEmpty()) {\n\t\t\t\tcontainer.addHorizontal(new Spacer(0));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsuper.apply(container, paragraph);\n\t\t}\n\n\t\t@Override\n\t\tpublic void apply(@Nonnull StackPane container, int paragraph) {\n\t\t\tList<LabelData> localModel = model;\n\n\t\t\tdouble indent = editor.computeWhitespacePrefixWidth(paragraph) - 3 /* padding so lines aren't right up against text */;\n\t\t\tdouble width = containerWidth + Math.min(100, indent); // Limit dimensions of canvas\n\t\t\tdouble height = containerHeight + 2;\n\n\t\t\tPixelCanvas canvas = null;\n\t\t\tfor (LabelData labelData : localModel) {\n\t\t\t\t// Skip if there are no references to the current label.\n\t\t\t\tList<ASTElement> labelReferrers = labelData.usage().writers();\n\t\t\t\tif (labelReferrers.isEmpty()) continue;\n\n\t\t\t\t// Skip if line is not inside a jump range.\n\t\t\t\tif (!labelData.isInRange(paragraph + 1)) continue;\n\n\t\t\t\t// Handle skipping over cases if we only want to draw lines for what is currently selected.\n\t\t\t\tif (config.getConnectionMode().getValue() == ControlFlowLinesConfig.ConnectionMode.CURRENT_CONNECTION) {\n\t\t\t\t\tASTInstruction value = currentInstructionSelection.getValue();\n\t\t\t\t\tif (value == null) {\n\t\t\t\t\t\t// No current selection?  We can skip everything. Just return.\n\t\t\t\t\t\treturn;\n\t\t\t\t\t} else if (SWITCH_INSNS.contains(value.identifier().literal())) {\n\t\t\t\t\t\t// If the selected item is a switch we want to draw all the lines to all destinations.\n\t\t\t\t\t\t//\n\t\t\t\t\t\t// The label data writers will be targeting the label identifier children in the AST\n\t\t\t\t\t\t// so if we walk the switch instruction's children we can see if the current label data\n\t\t\t\t\t\t// references one of those elements.\n\t\t\t\t\t\tArrayList<ASTElement> destinations = switchDestinations;\n\t\t\t\t\t\tdestinations.clear();\n\t\t\t\t\t\tvalue.walk(e -> {\n\t\t\t\t\t\t\tdestinations.add(e);\n\t\t\t\t\t\t\tdestinations.ensureCapacity(e.children().size() + 1);\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (labelData.usage().readersAndWriters().noneMatch(destinations::contains))\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t} else if (labelData.usage().readersAndWriters().noneMatch(m -> m.equals(value))) {\n\t\t\t\t\t\t// Anything else like a label declaration or a jump instruction mentioning a label\n\t\t\t\t\t\t// can be handled with a basic equality check against all the usage readers/writers.\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// If we've gotten to this point we will need a canvas to draw the lines on.\n\t\t\t\tif (canvas == null) {\n\t\t\t\t\tcanvas = new PixelCanvas(pixelPainter, (int) width, (int) height);\n\t\t\t\t\tcanvas.setManaged(false);\n\t\t\t\t\tcanvas.setMouseTransparent(true);\n\t\t\t\t\tcanvas.resize(width, height);\n\t\t\t\t\tcanvas.clear();\n\n\t\t\t\t\t// Setup canvas styling for the render mode.\n\t\t\t\t\tvar renderMode = config.getRenderMode().getValue();\n\t\t\t\t\tBlend blend = new Blend(BlendMode.HARD_LIGHT);\n\t\t\t\t\tEffect effect = switch (renderMode) {\n\t\t\t\t\t\tcase FLAT -> blend;\n\t\t\t\t\t\tcase RAINBOW_GLOWING, FLAT_GLOWING -> {\n\t\t\t\t\t\t\tBloom bloom = new Bloom(0.2);\n\t\t\t\t\t\t\tGlow glow = new Glow(0.7);\n\t\t\t\t\t\t\tbloom.setInput(blend);\n\t\t\t\t\t\t\tglow.setInput(bloom);\n\t\t\t\t\t\t\tyield glow;\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\tcanvas.setEffect(effect);\n\t\t\t\t\tif (renderMode == ControlFlowLinesConfig.LineRenderMode.RAINBOW_GLOWING) {\n\t\t\t\t\t\tsetupRainbowAnimation(effect, canvas).play();\n\t\t\t\t\t}\n\n\t\t\t\t\tcontainer.getChildren().add(canvas);\n\t\t\t\t}\n\n\t\t\t\t// There is always one 'reader' AKA the label itself.\n\t\t\t\t// We will use this to figure out which direction to draw lines in below.\n\t\t\t\tASTElement labelTarget = labelData.labelDeclaration();\n\t\t\t\tint declarationLine = labelTarget.location().line() - 1;\n\n\t\t\t\tint parallelLines = Math.max(1, labelData.computeOverlapping(model).size());\n\t\t\t\tint lineSlot = labelData.lineSlot().get();\n\t\t\t\tint offsetIndex = lineSlot % offsets.length;\n\t\t\t\tint horizontalOffset = offsetIndex >= 0 && offsetIndex < offsets.length ? offsets[offsetIndex] : 1;\n\t\t\t\tdouble hue = 360.0 / parallelLines * lineSlot;\n\t\t\t\tint color = createColor(hue);\n\t\t\t\tfinal int lineWidth = 1;\n\n\t\t\t\t// Mask for tracking which portions of the jump lines have been drawn.\n\t\t\t\tint shapeMask = 0;\n\n\t\t\t\t// Iterate over AST elements that refer to the label.\n\t\t\t\t// We will use their position and the label declaration position to determine what shape to draw.\n\t\t\t\tfor (ASTElement referrer : labelReferrers) {\n\t\t\t\t\t// Sanity check the AST element has location data.\n\t\t\t\t\tLocation referenceLoc = referrer.location();\n\t\t\t\t\tif (referenceLoc == null) continue;\n\n\t\t\t\t\tint referenceLine = referenceLoc.line() - 1;\n\t\t\t\t\tboolean isBackReference = referenceLine > declarationLine;\n\n\t\t\t\t\tboolean multiLine = labelData.countRefsOnLine(referenceLine) > 0;\n\t\t\t\t\tdouble targetY = multiLine ? horizontalOffset : height / 2;\n\t\t\t\t\tif (referenceLine == paragraph) {\n\t\t\t\t\t\t// The Y coordinates in these lines is the midpoint because as references\n\t\t\t\t\t\t// there should only be one line coming out of them. We don't need to fit\n\t\t\t\t\t\t// multiple lines.\n\t\t\t\t\t\t// Right section\n\t\t\t\t\t\tif (isBackReference) {\n\t\t\t\t\t\t\t// Shape: └\n\t\t\t\t\t\t\tif ((shapeMask & MASK_NORTH) == 0) {\n\t\t\t\t\t\t\t\t// Top section\n\t\t\t\t\t\t\t\tcanvas.drawVerticalLine(horizontalOffset, 0, targetY, lineWidth, color);\n\t\t\t\t\t\t\t\tshapeMask |= MASK_NORTH;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Shape: ┌\n\t\t\t\t\t\t\tif ((shapeMask & MASK_SOUTH) == 0) {\n\t\t\t\t\t\t\t\t// Bottom section\n\t\t\t\t\t\t\t\tcanvas.drawVerticalLine(horizontalOffset, targetY, height - targetY, lineWidth, color);\n\t\t\t\t\t\t\t\tshapeMask |= MASK_SOUTH;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ((shapeMask & MASK_EAST) == 0) {\n\t\t\t\t\t\t\t// Right section\n\t\t\t\t\t\t\tcanvas.drawHorizontalLine(horizontalOffset, targetY, width - horizontalOffset, lineWidth, color);\n\t\t\t\t\t\t\tshapeMask |= MASK_EAST;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcanvas.commit();\n\t\t\t\t\t} else if (paragraph == declarationLine) {\n\t\t\t\t\t\t// Right section\n\t\t\t\t\t\tif (isBackReference) {\n\t\t\t\t\t\t\t// Shape: ┌\n\t\t\t\t\t\t\tif ((shapeMask & MASK_SOUTH) == 0) {\n\t\t\t\t\t\t\t\t// Bottom section\n\t\t\t\t\t\t\t\tcanvas.drawVerticalLine(horizontalOffset, targetY, height - targetY, lineWidth, color);\n\t\t\t\t\t\t\t\tshapeMask |= MASK_SOUTH;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Shape: └\n\t\t\t\t\t\t\tif ((shapeMask & MASK_NORTH) == 0) {\n\t\t\t\t\t\t\t\t// Top section\n\t\t\t\t\t\t\t\tcanvas.drawVerticalLine(horizontalOffset, 0, targetY, lineWidth, color);\n\t\t\t\t\t\t\t\tshapeMask |= MASK_NORTH;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ((shapeMask & MASK_EAST) == 0) {\n\t\t\t\t\t\t\t// Right section\n\t\t\t\t\t\t\tcanvas.drawHorizontalLine(horizontalOffset, targetY, width - horizontalOffset, lineWidth, color);\n\t\t\t\t\t\t\tshapeMask |= MASK_EAST;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcanvas.commit();\n\t\t\t\t\t} else if ((isBackReference && (paragraph > declarationLine && paragraph < referenceLine)) ||\n\t\t\t\t\t\t\t(!isBackReference && (paragraph < declarationLine && paragraph > referenceLine))) {\n\t\t\t\t\t\tif ((shapeMask & MASK_NORTH) == 0) {\n\t\t\t\t\t\t\t// Top section\n\t\t\t\t\t\t\tcanvas.drawVerticalLine(horizontalOffset, 0, height / 2, lineWidth, color);\n\t\t\t\t\t\t\tshapeMask |= MASK_NORTH;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ((shapeMask & MASK_SOUTH) == 0) {\n\t\t\t\t\t\t\t// Bottom section\n\t\t\t\t\t\t\tcanvas.drawVerticalLine(horizontalOffset, height / 2, height / 2, lineWidth, color);\n\t\t\t\t\t\t\tshapeMask |= MASK_SOUTH;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcanvas.commit();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tpublic void cleanup() {\n\t\t\tswitchDestinations.clear();\n\t\t}\n\n\t\tprivate static int createColor(double hue) {\n\t\t\tColor color = Color.hsb(hue, 1.0, 1.0);\n\n\t\t\t// Ensure the color is actually bright enough.\n\t\t\t// In cases like pure blue, we have to lower the saturation incrementally to allow the brightness\n\t\t\t// boosting math to have any effect. The brightness constants should approximate perceived brightness.\n\t\t\tint i = 0;\n\t\t\twhile (i < 30) {\n\t\t\t\tdouble red = color.getRed();\n\t\t\t\tdouble green = color.getGreen();\n\t\t\t\tdouble blue = color.getBlue();\n\t\t\t\tdouble brightness = 0.2126 * red + 0.7152 * green + 0.0722 * blue;\n\t\t\t\tif (brightness > 0.4)\n\t\t\t\t\tbreak;\n\t\t\t\tcolor = color.deriveColor(0, 0.97, 1.2, 1);\n\t\t\t\ti++;\n\t\t\t}\n\n\t\t\treturn Colors.argb(color);\n\t\t}\n\n\t\t@Nonnull\n\t\tprivate Transition setupRainbowAnimation(@Nonnull Effect effect, @Nonnull Node node) {\n\t\t\treturn new Transition() {\n\t\t\t\t{\n\t\t\t\t\tsetInterpolator(Interpolator.LINEAR);\n\t\t\t\t\tsetCycleDuration(Duration.millis(rainbowHueRotationDurationMillis));\n\t\t\t\t\tsetCycleCount(Integer.MAX_VALUE);\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tprotected void interpolate(double frac) {\n\t\t\t\t\tlong now = System.currentTimeMillis();\n\t\t\t\t\tfloat diff = now % rainbowHueRotationDurationMillis;\n\n\t\t\t\t\tfloat halfMillis = (float) rainbowHueRotationDurationMillis / 2;\n\t\t\t\t\tfloat hue = Math.abs((4 * diff / rainbowHueRotationDurationMillis) - 2) - 1;\n\t\t\t\t\tColorAdjust adjust = new ColorAdjust(hue, 0.0, 0.0, 0.0);\n\t\t\t\t\tadjust.setInput(effect);\n\t\t\t\t\tnode.setEffect(adjust);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t}\n\n\t@SuppressWarnings(\"rawtypes\")\n\tprivate class ListenerHost implements ChangeListener {\n\t\t@Override\n\t\tpublic void changed(AbstractObservable ob, Object old, Object current) {\n\t\t\tif (editor != null) editor.redrawParagraphGraphics();\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/ControlFlowLinesConfig.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\n\n/**\n * Config for {@link ControlFlowLines}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class ControlFlowLinesConfig extends BasicConfigContainer {\n\tprivate final ObservableObject<ConnectionMode> connectionMode = new ObservableObject<>(ConnectionMode.ALL_CONNECTIONS);\n\tprivate final ObservableObject<LineRenderMode> renderMode = new ObservableObject<>(LineRenderMode.FLAT);\n\n\t@Inject\n\tpublic ControlFlowLinesConfig() {\n\t\tsuper(ConfigGroups.SERVICE_ASSEMBLER, \"flow-lines\" + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"connection-mode\", ConnectionMode.class, connectionMode));\n\t\taddValue(new BasicConfigValue<>(\"render-mode\", LineRenderMode.class, renderMode));\n\t}\n\n\t/**\n\t * @return Current line connection mode.\n\t */\n\t@Nonnull\n\tpublic ObservableObject<ConnectionMode> getConnectionMode() {\n\t\treturn connectionMode;\n\t}\n\n\t/**\n\t * @return Current line render mode.\n\t */\n\t@Nonnull\n\tpublic ObservableObject<LineRenderMode> getRenderMode() {\n\t\treturn renderMode;\n\t}\n\n\t/**\n\t * Modes for how to render lines.\n\t */\n\tpublic enum LineRenderMode {\n\t\t/**\n\t\t * Simple flat lines.\n\t\t */\n\t\tFLAT,\n\t\t/**\n\t\t * Simple flat lines with some glowing.\n\t\t */\n\t\tFLAT_GLOWING,\n\t\t/**\n\t\t * Party time!\n\t\t */\n\t\tRAINBOW_GLOWING\n\t}\n\n\t/**\n\t * Modes for where to draw lines.\n\t */\n\tpublic enum ConnectionMode {\n\t\t/**\n\t\t * Show control flow connections for all flow edges.\n\t\t */\n\t\tALL_CONNECTIONS,\n\t\t/**\n\t\t * Show control flow connections for only the current item.\n\t\t */\n\t\tCURRENT_CONNECTION\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/JvmAssemblerBuildConsumer.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.compile.JavaClassRepresentation;\nimport me.darknet.assembler.compile.visitor.JavaCompileResult;\nimport me.darknet.assembler.compiler.ClassRepresentation;\nimport me.darknet.assembler.compiler.ClassResult;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.assembler.AssemblerPipeline;\n\n/**\n * Outline of a component that takes in the build results of an {@link AssemblerPipeline} for JVM classes.\n *\n * @author Matt Coley\n */\npublic interface JvmAssemblerBuildConsumer extends AssemblerBuildConsumer {\n\t@Override\n\tdefault void consumeClass(@Nonnull ClassResult result, @Nonnull ClassInfo classInfo) {\n\t\tif (result instanceof JavaCompileResult jcr && classInfo.isJvmClass())\n\t\t\tonClassAssembled(jcr, classInfo.asJvmClass());\n\t}\n\n\t/**\n\t * Called when {@link AssemblerPane} builds a JVM class.\n\t *\n\t * @param result\n\t * \t\tAssembler JVM output model.\n\t * @param classInfo\n\t * \t\tRecaf JVM class model.\n\t */\n\tvoid onClassAssembled(@Nonnull JavaCompileResult result, @Nonnull JvmClassInfo classInfo);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/JvmExpressionCompilerPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport dev.xdark.blw.type.ClassType;\nimport dev.xdark.blw.type.ObjectType;\nimport dev.xdark.blw.type.PrimitiveType;\nimport dev.xdark.blw.type.Types;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.control.SplitPane;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.services.assembler.ExpressionCompileException;\nimport software.coley.recaf.services.assembler.ExpressionCompiler;\nimport software.coley.recaf.services.assembler.ExpressionResult;\nimport software.coley.recaf.services.compile.CompilerDiagnostic;\nimport software.coley.recaf.services.info.association.FileTypeSyntaxAssociationService;\nimport software.coley.recaf.ui.LanguageStylesheets;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.bracket.BracketMatchGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.bracket.SelectedBracketTracking;\nimport software.coley.recaf.ui.control.richtext.problem.*;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.ui.control.richtext.syntax.RegexLanguages;\nimport software.coley.recaf.ui.control.richtext.syntax.RegexSyntaxHighlighter;\nimport software.coley.recaf.util.Animations;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\n\nimport java.time.Duration;\nimport java.util.*;\nimport java.util.concurrent.ExecutorService;\n\n/**\n * Component panel for the assembler which shows the variables of the currently selected method.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class JvmExpressionCompilerPane extends AstBuildConsumerComponent {\n\tprivate static final ExecutorService compilePool = ThreadPoolFactory.newSingleThreadExecutor(\"expr-compile\");\n\tprotected final ProblemTracking problemTracking = new ProblemTracking();\n\tprivate final ExpressionCompiler expressionCompiler;\n\tprivate final Editor javaEditor = new Editor();\n\tprivate final Editor jasmEditor = new Editor();\n\tprivate boolean isDirty;\n\n\t@Inject\n\tpublic JvmExpressionCompilerPane(@Nonnull ExpressionCompiler expressionCompiler,\n\t                                 @Nonnull FileTypeSyntaxAssociationService languageAssociation,\n\t                                 @Nonnull Instance<SearchBar> searchBarProvider) {\n\t\tthis.expressionCompiler = expressionCompiler;\n\n\t\tlanguageAssociation.configureEditorSyntax(\"java\", javaEditor);\n\t\tjavaEditor.setSelectedBracketTracking(new SelectedBracketTracking());\n\t\tjavaEditor.setProblemTracking(problemTracking);\n\t\tjavaEditor.getRootLineGraphicFactory().addDefaultCodeGraphicFactories();\n\t\tjasmEditor.getCodeArea().getStylesheets().add(LanguageStylesheets.getJasmStylesheet());\n\t\tjasmEditor.setSelectedBracketTracking(new SelectedBracketTracking());\n\t\tjasmEditor.setSyntaxHighlighter(new RegexSyntaxHighlighter(RegexLanguages.getJasmLanguage()));\n\t\tjasmEditor.getRootLineGraphicFactory().addLineGraphicFactories(\n\t\t\t\tnew BracketMatchGraphicFactory()\n\t\t);\n\t\tsearchBarProvider.get().install(javaEditor);\n\t\tsearchBarProvider.get().install(jasmEditor);\n\n\t\tSplitPane split = new SplitPane(javaEditor, jasmEditor);\n\t\tsetCenter(split);\n\n\t\tjavaEditor.getTextChangeEventStream()\n\t\t\t\t.reduceSuccessions(Collections::singletonList, Lists::add, Duration.ofMillis(Editor.MEDIUM_DELAY_MS))\n\t\t\t\t.addObserver(unused -> scheduleCompile());\n\t}\n\n\t@Override\n\tprotected void onClassSelected() {\n\t\texpressionCompiler.clearContext();\n\t\tif (canAssignClassContext())\n\t\t\texpressionCompiler.setClassContext(currentClass.asJvmClass());\n\t\tinit(ContextType.CLASS);\n\t\tscheduleCompile();\n\t}\n\n\t@Override\n\tprotected void onMethodSelected() {\n\t\texpressionCompiler.clearContext();\n\t\tif (canAssignClassContext()) {\n\t\t\texpressionCompiler.setClassContext(currentClass.asJvmClass());\n\t\t\tif (canAssignMethodContext()) {\n\t\t\t\texpressionCompiler.setMethodContext(currentMethod);\n\t\t\t}\n\t\t}\n\t\tinit(ContextType.METHOD);\n\t\tscheduleCompile();\n\t}\n\n\t@Override\n\tprotected void onFieldSelected() {\n\t\texpressionCompiler.clearContext();\n\t\tif (canAssignClassContext())\n\t\t\texpressionCompiler.setClassContext(currentClass.asJvmClass());\n\t\tinit(ContextType.FIELD);\n\t\tscheduleCompile();\n\t}\n\n\t@Override\n\tprotected void onPipelineOutputUpdate() {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tjavaEditor.close();\n\t\tjasmEditor.close();\n\t}\n\n\t/**\n\t * Populates the initial text of the expression compiler pane.\n\t *\n\t * @param type\n\t * \t\tContent type in the {@link AssemblerPane}.\n\t */\n\tprivate void init(@Nonnull ContextType type) {\n\t\tif (!javaEditor.getCodeArea().getText().isBlank()) return;\n\n\t\t// TODO: The comment should reflect what contexts are active\n\t\t//  - Should query expression compiler for this info\n\t\tString text = Lang.get(\"assembler.playground.comment\").replace(\"\\\\n\", \"\\n\") + '\\n';\n\n\t\tswitch (type) {\n\t\t\tcase CLASS, FIELD -> text += \"return;\";\n\t\t\tcase METHOD -> {\n\t\t\t\tClassType returnType = Types.methodType(currentMethod.getDescriptor()).returnType();\n\t\t\t\tif (returnType instanceof ObjectType ot) {\n\t\t\t\t\ttext += \"return null;\";\n\t\t\t\t} else if (returnType instanceof PrimitiveType pt) {\n\t\t\t\t\tswitch (pt.descriptor().charAt(0)) {\n\t\t\t\t\t\tcase 'V':\n\t\t\t\t\t\t\ttext += \"return;\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'J':\n\t\t\t\t\t\t\ttext += \"return 0L;\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'D':\n\t\t\t\t\t\t\ttext += \"return 0.0;\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'F':\n\t\t\t\t\t\t\ttext += \"return 0F;\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'I':\n\t\t\t\t\t\t\ttext += \"return 0;\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'C':\n\t\t\t\t\t\t\ttext += \"return 'a';\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'S':\n\t\t\t\t\t\t\ttext += \"return (short) 0;\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'B':\n\t\t\t\t\t\t\ttext += \"return (byte) 0;\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'Z':\n\t\t\t\t\t\t\ttext += \"return false;\";\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tjavaEditor.setText(text);\n\n\t\t// Mark dirty when a user makes a change.\n\t\tjavaEditor.textProperty().addListener(new ChangeListener<>() {\n\t\t\t@Override\n\t\t\tpublic void changed(ObservableValue<? extends String> ob, String old, String cur) {\n\t\t\t\tisDirty = true;\n\t\t\t\tjavaEditor.textProperty().removeListener(this);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Checks for things in the {@link #currentClass} which would prevent its use in the expression compiler as context.\n\t *\n\t * @return {@code true} when the current class can be used as context in the expression compiler.\n\t */\n\tprivate boolean canAssignClassContext() {\n\t\t// We cannot have duplicate field names.\n\t\tSet<String> names = new HashSet<>();\n\t\tfor (FieldMember field : currentClass.getFields()) {\n\t\t\tif (!names.add(field.getName()))\n\t\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * Checks for things in the {@link #currentMethod} which would prevent its use in the expression compiler as context.\n\t *\n\t * @return {@code true} when the current method can be used as context in the expression compiler.\n\t */\n\tprivate boolean canAssignMethodContext() {\n\t\t// If we find things that cannot be allowed as method context, add the checks here\n\t\treturn true;\n\t}\n\n\tprivate void scheduleCompile() {\n\t\tif (isDirty) compilePool.submit(this::compile);\n\t}\n\n\tprivate void compile() {\n\t\tExpressionResult result = expressionCompiler.compile(javaEditor.getText());\n\t\tFxThreadUtil.run(() -> {\n\t\t\tproblemTracking.clear();\n\n\t\t\t// Validate no compiler errors occurred\n\t\t\tList<CompilerDiagnostic> diagnostics = result.getDiagnostics();\n\t\t\tif (!diagnostics.isEmpty()) {\n\t\t\t\tAnimations.animateFailure(javaEditor, 1000);\n\t\t\t\tfor (CompilerDiagnostic diagnostic : diagnostics) {\n\t\t\t\t\tProblem problem = Problem.fromDiagnostic(diagnostic);\n\t\t\t\t\tproblemTracking.addItem(problem);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Validate no compile exception was thrown.\n\t\t\tExpressionCompileException exception = result.getException();\n\t\t\tif (exception != null) {\n\t\t\t\tAnimations.animateFailure(javaEditor, 1000);\n\t\t\t\tproblemTracking.addItem(new Problem(-1, -1, 0,\n\t\t\t\t\t\tProblemLevel.ERROR, ProblemPhase.BUILD, StringUtil.traceToString(exception)));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Should have a result, but null check just to be safe.\n\t\t\tString assembly = result.getAssembly();\n\t\t\tjasmEditor.setText(Objects.requireNonNullElse(assembly, \"<no-output>\"));\n\t\t});\n\t}\n\n\t/**\n\t * Type of content in the containing {@link AssemblerPane}.\n\t */\n\tprivate enum ContextType {\n\t\tCLASS, FIELD, METHOD\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/JvmStackAnalysisPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport atlantafx.base.theme.Styles;\nimport atlantafx.base.theme.Tweaks;\nimport dev.xdark.blw.type.ClassType;\nimport dev.xdark.blw.type.Types;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.scene.control.SplitPane;\nimport javafx.scene.control.TableColumn;\nimport javafx.scene.control.TableView;\nimport me.darknet.assembler.ast.ASTElement;\nimport me.darknet.assembler.ast.primitive.ASTCode;\nimport me.darknet.assembler.ast.primitive.ASTEmpty;\nimport me.darknet.assembler.ast.primitive.ASTInstruction;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTMethod;\nimport me.darknet.assembler.compile.analysis.*;\nimport me.darknet.assembler.compile.analysis.frame.Frame;\nimport me.darknet.assembler.compile.analysis.frame.TypedFrame;\nimport me.darknet.assembler.compile.analysis.frame.ValuedFrame;\nimport me.darknet.assembler.parser.Token;\nimport me.darknet.assembler.parser.TokenType;\nimport me.darknet.assembler.util.Location;\nimport me.darknet.assembler.util.Range;\nimport org.reactfx.EventStreams;\nimport org.slf4j.Logger;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.time.Duration;\nimport java.util.*;\n\n/**\n * Component panel for the assembler which shows the data from stack analysis of the currently selected method.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class JvmStackAnalysisPane extends AstBuildConsumerComponent {\n\tprivate static final Logger logger = Logging.get(JvmStackAnalysisPane.class);\n\tprivate final SimpleObjectProperty<Object> notifyQueue = new SimpleObjectProperty<>(new Object());\n\tprivate final TableView<JvmVariableState> varTable = new TableView<>();\n\tprivate final TableView<JvmStackState> stackTable = new TableView<>();\n\tprivate int lastInsnIndex;\n\n\t@Inject\n\t@SuppressWarnings(\"unchecked\")\n\tpublic JvmStackAnalysisPane(@Nonnull CellConfigurationService cellConfigurationService,\n\t                            @Nonnull TextFormatConfig formatConfig,\n\t                            @Nonnull Workspace workspace) {\n\t\tTableColumn<JvmVariableState, String> columnName = new TableColumn<>(Lang.get(\"assembler.variables.name\"));\n\t\tTableColumn<JvmVariableState, ClassType> columnType = new TableColumn<>(Lang.get(\"assembler.variables.type\"));\n\t\tTableColumn<JvmVariableState, ValueTableCell.ValueWrapper> columnValue = new TableColumn<>(Lang.get(\"assembler.variables.value\"));\n\t\tcolumnName.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().name));\n\t\tcolumnType.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue().value instanceof Value.NullValue ? TypeTableCell.NULL_TYPE : param.getValue().type));\n\t\tcolumnValue.setCellValueFactory(param -> new SimpleObjectProperty<>(new ValueTableCell.ValueWrapper(param.getValue().value, param.getValue().priorValue)));\n\t\tcolumnType.setCellFactory(param -> new TypeTableCell<>(cellConfigurationService, formatConfig, workspace));\n\t\tcolumnValue.setCellFactory(param -> new ValueTableCell<>());\n\t\tvarTable.getColumns().addAll(columnName, columnType, columnValue);\n\n\t\tTableColumn<JvmStackState, ClassType> columnTypeStack = new TableColumn<>(Lang.get(\"assembler.analysis.type\"));\n\t\tTableColumn<JvmStackState, ValueTableCell.ValueWrapper> columnValueStack = new TableColumn<>(Lang.get(\"assembler.analysis.value\"));\n\t\tcolumnTypeStack.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue().value instanceof Value.NullValue ? TypeTableCell.NULL_TYPE : param.getValue().type));\n\t\tcolumnValueStack.setCellValueFactory(param -> new SimpleObjectProperty<>(new ValueTableCell.ValueWrapper(param.getValue().value, param.getValue().priorValue)));\n\t\tcolumnTypeStack.setCellFactory(param -> new TypeTableCell<>(cellConfigurationService, formatConfig, workspace));\n\t\tcolumnValueStack.setCellFactory(param -> new ValueTableCell<>());\n\t\tstackTable.getColumns().addAll(columnTypeStack, columnValueStack);\n\n\t\tvarTable.getStyleClass().addAll(Styles.STRIPED, Tweaks.EDGE_TO_EDGE, \"variable-table\");\n\t\tvarTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);\n\t\tstackTable.getStyleClass().addAll(Styles.STRIPED, Tweaks.EDGE_TO_EDGE, \"variable-table\");\n\t\tstackTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);\n\n\t\tSplitPane split = new SplitPane(stackTable, varTable);\n\t\tsetCenter(split);\n\n\t\tEventStreams.changesOf(notifyQueue)\n\t\t\t\t.reduceSuccessions(Collections::singletonList, Lists::add, Duration.ofMillis(Editor.SHORTER_DELAY_MS))\n\t\t\t\t.addObserver(unused -> {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tupdateTable();\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tlogger.error(\"Error updating stack analysis table\", t);\n\t\t\t\t\t}\n\t\t\t\t});\n\t}\n\n\tprivate void updateTable() {\n\t\tstackTable.setDisable(false);\n\t\tvarTable.setDisable(false);\n\n\t\t// Compute what instruction index the caret is at.\n\t\tint insnIndex = -1;\n\t\tfindIndex:\n\t\t{\n\t\t\tfor (ASTElement astElement : astElements) {\n\t\t\t\tif (astElement instanceof ASTMethod astMethod) {\n\t\t\t\t\tint index = getSelectedInsnIndexOfMethod(astMethod);\n\t\t\t\t\tif (index >= 0) {\n\t\t\t\t\t\tinsnIndex = index;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} else if (astElement instanceof ASTClass astClass) {\n\t\t\t\t\tfor (ASTElement child : astClass.children()) {\n\t\t\t\t\t\tif (child instanceof ASTMethod astMethod) {\n\t\t\t\t\t\t\tint index = getSelectedInsnIndexOfMethod(astMethod);\n\t\t\t\t\t\t\tif (index >= 0) {\n\t\t\t\t\t\t\t\tinsnIndex = index;\n\t\t\t\t\t\t\t\tbreak findIndex;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If we've not moved, no need to update the table.\n\t\tif (lastInsnIndex == insnIndex)\n\t\t\treturn;\n\t\tlastInsnIndex = insnIndex;\n\n\t\t// Skip for invalid index.\n\t\tif (insnIndex < 0)\n\t\t\treturn;\n\n\t\t// Skip of no method analysis for the current method.\n\t\tAnalysisResults analysisResults = analysisLookup.results(currentMethod.getName(), currentMethod.getDescriptor());\n\t\tif (analysisResults == null)\n\t\t\treturn;\n\n\t\t// Skip if no frames.\n\t\tNavigableMap<Integer, Frame> frames = analysisResults.frames();\n\t\tif (frames.isEmpty())\n\t\t\treturn;\n\n\t\t// Compute variable/stack states.\n\t\tList<JvmVariableState> varItems = new ArrayList<>();\n\t\tList<JvmStackState> stackItems = new ArrayList<>();\n\t\tvar entry = frames.floorEntry(insnIndex);\n\t\tvar entryKey = entry.getKey();\n\t\tFrame thisFrame = entry.getValue();\n\t\tif (thisFrame instanceof TypedFrame typedFrame) {\n\t\t\t// Type-only analysis is basic\n\t\t\tfor (ClassType classType : typedFrame.getStack())\n\t\t\t\tstackItems.add(new JvmStackState(classType, Values.valueOf(classType), null));\n\t\t\tfor (Local local : typedFrame.getLocals().values())\n\t\t\t\tvarItems.add(new JvmVariableState(local.name(), local.safeType(), Values.valueOf(local.safeType()), null));\n\t\t} else if (thisFrame instanceof ValuedFrame valuedFrame) {\n\t\t\t// Value analysis will not only track values in a frame, but also let us see if values change across frames\n\t\t\tValuedFrame lastFrame = entryKey == 0 ? null : (ValuedFrame) frames.floorEntry(entryKey - 1).getValue();\n\n\t\t\t// Fill out stack.\n\t\t\tValue[] lastStack = lastFrame == null ? new Value[0] : lastFrame.getStack().toArray(Value[]::new);\n\t\t\tValue[] stack = valuedFrame.getStack().toArray(Value[]::new);\n\t\t\tfor (int i = 0; i < stack.length; i++) {\n\t\t\t\tValue lastValue = i <= lastStack.length - 1 ? lastStack[i] : null;\n\t\t\t\tValue value = stack[i];\n\t\t\t\tstackItems.add(new JvmStackState(Objects.requireNonNullElse(value.type(), Types.OBJECT), value, lastValue));\n\t\t\t}\n\n\t\t\t// And fill out the variables.\n\t\t\tMap<Integer, ValuedLocal> lastLocals = lastFrame == null ? Collections.emptyMap() : lastFrame.getLocals();\n\t\t\tMap<Integer, ValuedLocal> locals = valuedFrame.getLocals();\n\t\t\tfor (ValuedLocal local : locals.values()) {\n\t\t\t\tValuedLocal lastLocal = lastLocals.get(local.index());\n\t\t\t\tvarItems.add(new JvmVariableState(local.name(), local.safeType(), local.value(),\n\t\t\t\t\t\tlastLocal == null ? null : lastLocal.value()));\n\t\t\t}\n\t\t}\n\t\tvarTable.getItems().setAll(varItems);\n\t\tstackTable.getItems().setAll(stackItems);\n\t}\n\n\tprivate int getSelectedInsnIndexOfMethod(@Nonnull ASTMethod method) {\n\t\tint pos = editor.getCodeArea().getCaretPosition();\n\t\tif (!method.range().within(pos))\n\t\t\treturn -1;\n\t\tASTCode code = method.code();\n\t\tif (code == null)\n\t\t\treturn -1;\n\t\tList<ASTInstruction> instructions = code.instructions();\n\t\tint paragraph = editor.getCodeArea().getCurrentParagraph();\n\t\tint result = Collections.binarySearch(instructions, new ASTEmpty(new Token(\n\t\t\t\tnew Range(pos, pos + 1),\n\t\t\t\tnew Location(paragraph, 0, 1, null),\n\t\t\t\tTokenType.IDENTIFIER,\n\t\t\t\t\".\"\n\t\t)), (o1, o2) -> {\n\t\t\tLocation l1 = o1.location();\n\t\t\tLocation l2 = o2.location();\n\t\t\tif (l1 == null) return 1;\n\t\t\telse if (l2 == null) return -1;\n\t\t\treturn Objects.compare(l1, l2, Comparator.naturalOrder());\n\t\t});\n\t\tif (result < 0) result = -result;\n\t\treturn Math.min(instructions.size() - 1, result + 1);\n\t}\n\n\tprivate void clearData() {\n\t\tFxThreadUtil.run(() -> {\n\t\t\tstackTable.setDisable(true);\n\t\t\tvarTable.setDisable(true);\n\t\t\tstackTable.getItems().clear();\n\t\t\tvarTable.getItems().clear();\n\t\t});\n\t\tlastInsnIndex = -1;\n\t}\n\n\tprivate void scheduleTableUpdate() {\n\t\tif (currentMethod == null || analysisLookup == null || editor == null) return;\n\t\tFxThreadUtil.run(() -> notifyQueue.set(new Object()));\n\t}\n\n\t@Override\n\tprotected void onClassSelected() {\n\t\tclearData();\n\t}\n\n\t@Override\n\tprotected void onMethodSelected() {\n\t\tscheduleTableUpdate();\n\t}\n\n\t@Override\n\tprotected void onFieldSelected() {\n\t\tclearData();\n\t}\n\n\t@Override\n\tprotected void onPipelineOutputUpdate() {\n\t\tscheduleTableUpdate();\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tsuper.install(editor);\n\n\t\t// Not reusing this pane, so we don't need to track for removal\n\t\teditor.getCaretPosEventStream()\n\t\t\t\t.reduceSuccessions(Collections::singletonList, Lists::add, Duration.ofMillis(Editor.SHORT_DELAY_MS))\n\t\t\t\t.addObserver(e -> scheduleTableUpdate());\n\t}\n\n\t/**\n\t * Models the state of a variable.\n\t *\n\t * @param name\n\t * \t\tVariable name.\n\t * @param type\n\t * \t\tVariable type.\n\t * @param value\n\t * \t\tVariable value.\n\t * @param priorValue\n\t * \t\tPrior state in previous frame, if known.\n\t */\n\tprivate record JvmVariableState(@Nonnull String name, @Nonnull ClassType type, @Nonnull Value value,\n\t                                @Nullable Value priorValue) {}\n\n\t/**\n\t * Models an item on the stack.\n\t *\n\t * @param type\n\t * \t\tType of item.\n\t * @param value\n\t * \t\tValue of item.\n\t * @param priorValue\n\t * \t\tPrior state in previous frame, if known.\n\t */\n\tprivate record JvmStackState(@Nonnull ClassType type, @Nonnull Value value, @Nullable Value priorValue) {}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/JvmVariablesPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport atlantafx.base.controls.Spacer;\nimport atlantafx.base.theme.Styles;\nimport atlantafx.base.theme.Tweaks;\nimport dev.xdark.blw.type.ClassType;\nimport dev.xdark.blw.type.Types;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.collections.ObservableList;\nimport javafx.scene.Cursor;\nimport javafx.scene.Node;\nimport javafx.scene.control.ContentDisplay;\nimport javafx.scene.control.TableCell;\nimport javafx.scene.control.TableColumn;\nimport javafx.scene.control.TableView;\nimport javafx.scene.paint.Color;\nimport me.darknet.assembler.ast.ASTElement;\nimport me.darknet.assembler.ast.primitive.ASTCode;\nimport me.darknet.assembler.ast.primitive.ASTIdentifier;\nimport me.darknet.assembler.ast.primitive.ASTInstruction;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTMethod;\nimport me.darknet.assembler.compile.analysis.AnalysisResults;\nimport me.darknet.assembler.compile.analysis.frame.Frame;\nimport me.darknet.assembler.util.Location;\nimport me.darknet.assembler.util.Range;\nimport org.fxmisc.richtext.CodeArea;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.reactfx.Change;\nimport org.reactfx.EventStreams;\nimport org.slf4j.Logger;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.IconView;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.linegraphics.AbstractLineGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.linegraphics.LineContainer;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.SVG;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NavigableMap;\nimport java.util.Objects;\nimport java.util.TreeMap;\nimport java.util.function.BiConsumer;\nimport java.util.function.Consumer;\n\n/**\n * Component panel for the assembler which shows the variables of the currently selected method.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class JvmVariablesPane extends AstBuildConsumerComponent {\n\tprivate static final ClassType INVALID_VAR_TYPE_MARKER = Types.VOID;\n\tprivate static final Logger logger = Logging.get(JvmVariablesPane.class);\n\tprivate final SimpleObjectProperty<Object> notifyQueue = new SimpleObjectProperty<>(new Object());\n\tprivate final TableView<VariableData> table = new TableView<>();\n\tprivate final Consumer<Change<Integer>> onCaretMove = this::onCaretMove;\n\tprivate final VarHighlightLineFactory varHighlighter = new VarHighlightLineFactory();\n\n\t@Inject\n\t@SuppressWarnings(\"unchecked\")\n\tpublic JvmVariablesPane(@Nonnull CellConfigurationService cellConfigurationService,\n\t                        @Nonnull TextFormatConfig formatConfig,\n\t                        @Nonnull Workspace workspace) {\n\t\tTableColumn<VariableData, String> columnName = new TableColumn<>(Lang.get(\"assembler.variables.name\"));\n\t\tTableColumn<VariableData, ClassType> columnType = new TableColumn<>(Lang.get(\"assembler.variables.type\"));\n\t\tTableColumn<VariableData, AstUsages> columnUsage = new TableColumn<>(Lang.get(\"assembler.variables.usage\"));\n\t\tcolumnName.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().name()));\n\t\tcolumnType.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue().type()));\n\t\tcolumnUsage.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue().usage()));\n\t\tcolumnType.setCellFactory(param -> new TypeTableCell<>(cellConfigurationService, formatConfig, workspace));\n\t\tcolumnUsage.setCellFactory(param -> new TableCell<>() {\n\t\t\t{\n\t\t\t\tsetContentDisplay(ContentDisplay.RIGHT);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tprotected void updateItem(AstUsages usages, boolean empty) {\n\t\t\t\tsuper.updateItem(usages, empty);\n\t\t\t\tif (empty || usages == null) {\n\t\t\t\t\tsetText(null);\n\t\t\t\t\tsetGraphic(null);\n\t\t\t\t\tsetOnMousePressed(null);\n\t\t\t\t} else {\n\t\t\t\t\tString usageFmt = \"%d reads, %d writes\".formatted(usages.readers().size(), usages.writers().size());\n\t\t\t\t\tsetText(usageFmt);\n\n\t\t\t\t\t// Put a warning symbol on variables that are read from before ever being written to.\n\t\t\t\t\t//  - Parameters are implicitly treated as being written to\n\t\t\t\t\tList<ASTElement> list = usages.readersAndWriters()\n\t\t\t\t\t\t\t.sorted(Comparator.comparing(ASTElement::location))\n\t\t\t\t\t\t\t.toList();\n\t\t\t\t\tif (!list.isEmpty() && !usages.isParameter() && usages.readers().contains(list.getFirst())) {\n\t\t\t\t\t\tBoundLabel warning = new BoundLabel(Lang.getBinding(\"assembler.variables.read-before-write\"),\n\t\t\t\t\t\t\t\tnew FontIconView(CarbonIcons.WARNING, Color.YELLOW));\n\t\t\t\t\t\twarning.getStyleClass().add(Styles.WARNING);\n\t\t\t\t\t\tsetGraphic(warning);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsetGraphic(null);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\ttable.getColumns().addAll(columnName, columnType, columnUsage);\n\t\ttable.getStyleClass().addAll(Styles.STRIPED, Tweaks.EDGE_TO_EDGE, \"variable-table\");\n\t\ttable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);\n\t\ttable.setOnMousePressed(e -> {\n\t\t\tif (e.isPrimaryButtonDown()) {\n\t\t\t\tVariableData selectedItem = table.getSelectionModel().getSelectedItem();\n\t\t\t\tif (selectedItem == null) return;\n\n\t\t\t\t// Collect ranges AST items where the variable is used.\n\t\t\t\tNavigableMap<Integer, Range> elementRanges = new TreeMap<>();\n\t\t\t\tselectedItem.usage().readersAndWriters().forEach(rw -> {\n\t\t\t\t\tRange range = rw.range();\n\t\t\t\t\tif (range != null)\n\t\t\t\t\t\telementRanges.put(range.start(), range);\n\t\t\t\t});\n\n\t\t\t\t// Select next. Wrap around if nothing is next.\n\t\t\t\tCodeArea area = editor.getCodeArea();\n\t\t\t\tint caret = area.getCaretPosition();\n\t\t\t\tvar nextEntry = elementRanges.higherEntry(caret + 1);\n\t\t\t\tif (nextEntry == null) nextEntry = elementRanges.firstEntry();\n\t\t\t\tif (nextEntry == null) return;\n\t\t\t\tRange value = nextEntry.getValue();\n\t\t\t\tarea.selectRange(value.start(), value.end());\n\t\t\t\tarea.showParagraphAtCenter(area.getCurrentParagraph());\n\t\t\t}\n\t\t});\n\n\t\tsetCenter(table);\n\n\t\tEventStreams.changesOf(notifyQueue)\n\t\t\t\t.reduceSuccessions(Collections::singletonList, Lists::add, Duration.ofMillis(Editor.SHORT_DELAY_MS))\n\t\t\t\t.addObserver(unused -> {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tupdateTable();\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tlogger.error(\"Error updating variables table\", t);\n\t\t\t\t\t}\n\t\t\t\t});\n\t}\n\n\t@Override\n\tpublic void install(@Nonnull Editor editor) {\n\t\tsuper.install(editor);\n\n\t\teditor.getRootLineGraphicFactory().addLineGraphicFactory(varHighlighter);\n\t\teditor.getCaretPosEventStream().addObserver(onCaretMove);\n\t}\n\n\t@Override\n\tpublic void uninstall(@Nonnull Editor editor) {\n\t\tsuper.uninstall(editor);\n\n\t\teditor.getRootLineGraphicFactory().removeLineGraphicFactory(varHighlighter);\n\t\teditor.getCaretPosEventStream().removeObserver(onCaretMove);\n\t}\n\n\t@Override\n\tprotected void onClassSelected() {\n\t\tclearData();\n\t}\n\n\t@Override\n\tprotected void onMethodSelected() {\n\t\tscheduleTableUpdate();\n\t}\n\n\t@Override\n\tprotected void onFieldSelected() {\n\t\tclearData();\n\t}\n\n\t@Override\n\tprotected void onPipelineOutputUpdate() {\n\t\tscheduleTableUpdate();\n\t}\n\n\t/**\n\t * Handles updating the {@link VarHighlightLineFactory}.\n\t * <p>\n\t * This logic is shoe-horned into here <i>(for now)</i> because\n\t * the variable tracking logic is internal to this class only.\n\t *\n\t * @param caretChange\n\t * \t\tCaret pos change.\n\t */\n\tprivate void onCaretMove(Change<Integer> caretChange) {\n\t\tint pos = caretChange.getNewValue();\n\n\t\t// Determine which variable is at the caret position\n\t\tVariableData currentVarSelection = null;\n\t\tfor (VariableData item : table.getItems()) {\n\t\t\tAstUsages usage = item.usage();\n\t\t\tASTElement matchedAst = usage.readersAndWriters()\n\t\t\t\t\t.filter(e -> e.range().within(pos))\n\t\t\t\t\t.findFirst().orElse(null);\n\t\t\tif (matchedAst != null) {\n\t\t\t\tcurrentVarSelection = item;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Notify the highlighter of the difference\n\t\tvarHighlighter.setSelectedVariable(currentVarSelection);\n\t}\n\n\tprivate void scheduleTableUpdate() {\n\t\tif (currentMethod == null || analysisLookup == null) return;\n\t\tFxThreadUtil.run(() -> notifyQueue.set(new Object()));\n\t}\n\n\tprivate void updateTable() {\n\t\tObservableList<VariableData> items = table.getItems();\n\t\titems.clear();\n\n\t\t// Collect all variable usage information from the AST.\n\t\tAstUsages emptyUsage = AstUsages.EMPTY_USAGE;\n\t\tMap<String, AstUsages> variableUsages = new HashMap<>();\n\t\tBiConsumer<String, ASTElement> readUpdater = (name, element) -> {\n\t\t\tAstUsages existing = variableUsages.getOrDefault(name, emptyUsage);\n\t\t\tvariableUsages.put(name, existing.withNewRead(element));\n\t\t};\n\t\tBiConsumer<String, ASTElement> writeUpdater = (name, element) -> {\n\t\t\tAstUsages existing = variableUsages.getOrDefault(name, emptyUsage);\n\t\t\tvariableUsages.put(name, existing.withNewWrite(element));\n\t\t};\n\t\tif (astElements != null) {\n\t\t\tConsumer<ASTMethod> methodConsumer = astMethod -> {\n\t\t\t\tif (currentMethod != null && !Objects.equals(currentMethod.getName(), astMethod.getName().literal()))\n\t\t\t\t\treturn;\n\t\t\t\tfor (ASTIdentifier parameter : astMethod.parameters()) {\n\t\t\t\t\tString literalName = parameter.literal();\n\t\t\t\t\tvariableUsages.putIfAbsent(literalName, emptyUsage.asParameter());\n\t\t\t\t}\n\t\t\t\tASTCode code = astMethod.code();\n\t\t\t\tif (code == null)\n\t\t\t\t\treturn;\n\t\t\t\tfor (ASTInstruction instruction : code.instructions()) {\n\t\t\t\t\tString insnName = instruction.identifier().content();\n\t\t\t\t\tif (insnName == null)\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tboolean isLoad = insnName.endsWith(\"load\");\n\t\t\t\t\tboolean isStore = insnName.endsWith(\"store\");\n\t\t\t\t\tif (((isLoad || isStore) && insnName.charAt(1) != 'a') || insnName.equals(\"iinc\")) {\n\t\t\t\t\t\tList<ASTElement> arguments = instruction.arguments();\n\t\t\t\t\t\tif (!arguments.isEmpty()) {\n\t\t\t\t\t\t\tASTElement arg = arguments.get(0);\n\t\t\t\t\t\t\tString varName = arg instanceof ASTIdentifier identifierArg ?\n\t\t\t\t\t\t\t\t\tidentifierArg.literal() : arg.content();\n\t\t\t\t\t\t\tif (isLoad) {\n\t\t\t\t\t\t\t\treadUpdater.accept(varName, instruction);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\twriteUpdater.accept(varName, instruction);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t\tfor (ASTElement astElement : astElements) {\n\t\t\t\tif (astElement instanceof ASTMethod astMethod) {\n\t\t\t\t\tmethodConsumer.accept(astMethod);\n\t\t\t\t} else if (astElement instanceof ASTClass astClass) {\n\t\t\t\t\tfor (ASTElement child : astClass.children()) {\n\t\t\t\t\t\tif (child instanceof ASTMethod astMethod) {\n\t\t\t\t\t\t\tmethodConsumer.accept(astMethod);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Populate the variables map from the stack analysis results.\n\t\tAnalysisResults analysisResults = analysisLookup.results(currentMethod.getName(), currentMethod.getDescriptor());\n\t\tif (analysisResults != null && !analysisResults.frames().isEmpty()) {\n\t\t\t// Linked map for ordering\n\t\t\tMap<String, VariableData> variables = new LinkedHashMap<>();\n\n\t\t\t// In JASM variables are un-scoped, so the last frame should have all entries.\n\t\t\tanalysisResults.frames().values().stream()\n\t\t\t\t\t.flatMap(Frame::locals)\n\t\t\t\t\t.distinct()\n\t\t\t\t\t.forEach(local -> {\n\t\t\t\t\t\tString localName = local.name();\n\t\t\t\t\t\tVariableData data = VariableData.adaptFrom(local, variableUsages.getOrDefault(localName, emptyUsage));\n\t\t\t\t\t\tvariables.put(localName, data);\n\t\t\t\t\t});\n\n\t\t\t// In some cases, the last frame may not have some entries.\n\t\t\t// This generally means there is either a problem with the code or with JASM.\n\t\t\t// Either way, reporting them here with a bogus type is good for diagnosing the issue.\n\t\t\tvariableUsages.forEach((name, usage) -> {\n\t\t\t\tif (!variables.containsKey(name))\n\t\t\t\t\tvariables.put(name, new VariableData(name, INVALID_VAR_TYPE_MARKER, usage));\n\t\t\t});\n\n\t\t\t// Add all found items to the table.\n\t\t\titems.addAll(variables.values());\n\t\t}\n\t}\n\n\tprivate void clearData() {\n\t\tFxThreadUtil.run(() -> table.getItems().clear());\n\t\tcurrentMethod = null;\n\t}\n\n\t/**\n\t * Highlighter which shows read and write access of a {@link VariableData}.\n\t */\n\tprivate class VarHighlightLineFactory extends AbstractLineGraphicFactory {\n\t\tprivate VariableData variable;\n\n\t\tprivate VarHighlightLineFactory() {\n\t\t\tsuper(AbstractLineGraphicFactory.P_LINE_NUMBERS + 1);\n\t\t}\n\n\t\t/**\n\t\t * @param variable\n\t\t * \t\tNew selected variable.\n\t\t */\n\t\tpublic void setSelectedVariable(@Nullable VariableData variable) {\n\t\t\tVariableData existing = this.variable;\n\t\t\tif (existing == null) {\n\t\t\t\tif (variable == null)\n\t\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\tif (existing.matchesNameType(variable))\n\t\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.variable = variable;\n\t\t\teditor.redrawParagraphGraphics();\n\t\t}\n\n\t\t@Override\n\t\tpublic void install(@Nonnull Editor editor) {\n\t\t\t// no-op, outer class has all the data we need\n\t\t}\n\n\t\t@Override\n\t\tpublic void uninstall(@Nonnull Editor editor) {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic void apply(@Nonnull LineContainer container, int paragraph) {\n\t\t\tNode graphic;\n\t\t\tcreateGraphic:\n\t\t\t{\n\t\t\t\tif (variable != null) {\n\t\t\t\t\tfor (ASTElement reader : variable.usage().readers()) {\n\t\t\t\t\t\tLocation location = reader.location();\n\t\t\t\t\t\tif (location != null && location.line() - 1 == paragraph) {\n\t\t\t\t\t\t\tgraphic = SVG.ofIconFile(SVG.REF_READ);\n\t\t\t\t\t\t\tbreak createGraphic;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfor (ASTElement writer : variable.usage().writers()) {\n\t\t\t\t\t\tLocation location = writer.location();\n\t\t\t\t\t\tif (location != null && location.line() - 1 == paragraph) {\n\t\t\t\t\t\t\tgraphic = SVG.ofIconFile(SVG.REF_WRITE);\n\t\t\t\t\t\t\tbreak createGraphic;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tgraphic = new Spacer(IconView.DEFAULT_ICON_SIZE);\n\t\t\t}\n\t\t\tgraphic.setCursor(Cursor.HAND);\n\t\t\tcontainer.addHorizontal(graphic);\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/LabelData.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport it.unimi.dsi.fastutil.ints.Int2IntMap;\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.ast.ASTElement;\nimport me.darknet.assembler.ast.primitive.ASTLabel;\nimport me.darknet.assembler.util.Location;\nimport me.darknet.assembler.util.Range;\nimport software.coley.collections.box.Box;\nimport software.coley.collections.box.IntBox;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.IntSummaryStatistics;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Stream;\n\n/**\n * Models a variable.\n *\n * @param name\n * \t\tName of label.\n * @param usage\n * \t\tUsages of the label in the AST.\n */\npublic record LabelData(@Nonnull String name, @Nonnull AstUsages usage,\n                        @Nonnull Int2IntMap linesOnLinesMap,\n                        @Nonnull IntBox lineSlot, @Nonnull Box<List<LabelData>> overlapping) {\n\t@Nonnull\n\tpublic Range range() {\n\t\tIntSummaryStatistics summary = usage.readersAndWriters()\n\t\t\t\t.mapToInt(e -> Objects.requireNonNull(e.location()).line())\n\t\t\t\t.summaryStatistics();\n\t\treturn new Range(summary.getMin(), summary.getMax());\n\t}\n\n\t@Nonnull\n\tpublic ASTLabel labelDeclaration() {\n\t\treturn (ASTLabel) usage.readers().getFirst();\n\t}\n\n\tpublic long countRefsOnLine(int line) {\n\t\treturn linesOnLinesMap.computeIfAbsent(line,\n\t\t\t\tl -> Math.toIntExact(usage.readersAndWriters()\n\t\t\t\t\t\t.filter(i -> i.location().line() == line)\n\t\t\t\t\t\t.count()\n\t\t\t\t));\n\t}\n\n\t@Nonnull\n\tprivate Stream<ASTElement> matchedLineStream(int line) {\n\t\treturn usage.readersAndWriters().filter(i -> i.location().line() == line);\n\t}\n\n\tpublic List<LabelData> computeOverlapping(@Nonnull Collection<LabelData> labelDatum) {\n\t\treturn overlapping.computeIfAbsent(() -> {\n\t\t\tRange range = range();\n\t\t\tList<LabelData> overlap = new ArrayList<>();\n\t\t\tfor (LabelData data : labelDatum) {\n\t\t\t\t// Skip self\n\t\t\t\tif (name.equals(data.name)) continue;\n\n\t\t\t\t// Skip labels that don't have references\n\t\t\t\tif (data.usage().writers().isEmpty()) continue;\n\n\t\t\t\tRange otherRange = data.range();\n\t\t\t\tif (Math.max(range.start(), otherRange.start()) <= Math.min(range.end(), otherRange.end()))\n\t\t\t\t\toverlap.add(data);\n\t\t\t}\n\t\t\treturn overlap;\n\n\t\t});\n\t}\n\n\tpublic boolean isInRange(int line) {\n\t\tASTLabel declaration = labelDeclaration();\n\t\tLocation declarationLoc = declaration.location();\n\t\tif (declarationLoc == null) return false;\n\t\tint declarationLine = declarationLoc.line();\n\n\t\t// Base case, range included declaration line.\n\t\tif (declarationLine == line) return true;\n\n\t\t// Check if inside range:\n\t\t//  goto X\n\t\t//    ..   <---- line somewhere in here\n\t\t//  X:\n\t\tfor (ASTElement referrer : usage.writers()) {\n\t\t\tLocation referrerLoc = referrer.location();\n\t\t\tif (referrerLoc == null) continue;\n\t\t\tint referrerLine = referrerLoc.line();\n\n\t\t\t// Base case, range included referrer's line.\n\t\t\tif (referrerLine == line) return true;\n\n\t\t\t// Otherwise check if the line is between the range of the reference and the declaration.\n\t\t\t// The range bounds are swapped based on if the reference is forwards or backwards.\n\t\t\tif ((declarationLine > referrerLine) ?\n\t\t\t\t\t(line > referrerLine && line < declarationLine) :\n\t\t\t\t\t(line > declarationLine && line < referrerLine)) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tLabelData labelData = (LabelData) o;\n\n\t\treturn name.equals(labelData.name);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\treturn name.hashCode();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/SnippetsPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport atlantafx.base.controls.Popover;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Group;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.ListView;\nimport javafx.scene.control.TextField;\nimport javafx.scene.effect.Glow;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.assembler.Snippet;\nimport software.coley.recaf.services.assembler.SnippetListener;\nimport software.coley.recaf.services.assembler.SnippetManager;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.ui.LanguageStylesheets;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.GraphicActionButton;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.bracket.SelectedBracketTracking;\nimport software.coley.recaf.ui.control.richtext.syntax.RegexLanguages;\nimport software.coley.recaf.ui.control.richtext.syntax.RegexSyntaxHighlighter;\nimport software.coley.recaf.util.Animations;\nimport software.coley.recaf.util.Lang;\n\nimport java.util.Collection;\nimport java.util.Collections;\n\n/**\n * Pane for creating new snippets, editing existing ones, and listing all available snippets.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class SnippetsPane extends StackPane implements SnippetListener, Navigable {\n\tprivate final SnippetManager snippetManager;\n\tprivate final Editor editor = new Editor();\n\tprivate final GraphicActionButton btnSave;\n\tprivate final GraphicActionButton btnNew;\n\tprivate final GraphicActionButton btnDelete;\n\tprivate final GraphicActionButton btnLoad;\n\tprivate final ObservableList<Snippet> snippetList = FXCollections.observableArrayList();\n\tprivate final ObjectProperty<Snippet> currentSnippet = new SimpleObjectProperty<>();\n\tprivate final ComboBox<Snippet> snippetComboBox = new ComboBox<>(snippetList);\n\n\t@Inject\n\tpublic SnippetsPane(@Nonnull SnippetManager snippetManager) {\n\t\tthis.snippetManager = snippetManager;\n\n\t\t// Initialize snippet list and add listener to ensure it gets updated when new snippets are made/removed.\n\t\tsnippetList.addAll(snippetManager.getSnippets());\n\t\tsnippetManager.addSnippetListener(this);\n\n\t\t// Configure for assembly content\n\t\teditor.disableProperty().bind(currentSnippet.isNull());\n\t\teditor.opacityProperty().bind(currentSnippet.isNull().map(nil -> nil ? 0.3 : 1.0));\n\t\teditor.getCodeArea().getStylesheets().add(LanguageStylesheets.getJasmStylesheet());\n\t\teditor.setSelectedBracketTracking(new SelectedBracketTracking());\n\t\teditor.setSyntaxHighlighter(new RegexSyntaxHighlighter(RegexLanguages.getJasmLanguage()));\n\t\teditor.getRootLineGraphicFactory().addDefaultCodeGraphicFactories();\n\t\tresetContent();\n\n\t\t// Setup actions\n\t\tbtnSave = new GraphicActionButton(CarbonIcons.SAVE, this::saveSnippet);\n\t\tbtnNew = new GraphicActionButton(CarbonIcons.DOCUMENT_ADD, this::newSnippet);\n\t\tbtnDelete = new GraphicActionButton(CarbonIcons.TRASH_CAN, this::deleteSnippet);\n\t\tbtnLoad = new GraphicActionButton(CarbonIcons.FOLDERS, this::loadSnippet);\n\t\tHBox tools = new HBox(btnSave, btnNew, btnDelete, btnLoad);\n\t\ttools.setSpacing(6);\n\t\tbtnDelete.disableProperty().bind(currentSnippet.isNull());\n\t\tbtnSave.disableProperty().bind(currentSnippet.isNull());\n\n\t\t// Bring attention to 'new' and 'load' initially, removed when a snippet is loaded.\n\t\tbringAttention(btnNew, btnLoad);\n\t\tcurrentSnippet.addListener(new ChangeListener<>() {\n\t\t\t@Override\n\t\t\tpublic void changed(ObservableValue<? extends Snippet> observableValue, Snippet snippet, Snippet t1) {\n\t\t\t\tremoveAttention();\n\t\t\t\tcurrentSnippet.removeListener(this);\n\t\t\t}\n\t\t});\n\n\t\t// Layout\n\t\tGroup toolsWrapper = new Group(tools);\n\t\tStackPane.setAlignment(toolsWrapper, Pos.BOTTOM_RIGHT);\n\t\tStackPane.setMargin(toolsWrapper, new Insets(5));\n\t\tgetChildren().addAll(editor, toolsWrapper);\n\t}\n\n\t/**\n\t * Creates a new snippet to work off of.\n\t */\n\tprivate void newSnippet() {\n\t\tLabel nameLabel = new BoundLabel(Lang.getBinding(\"dialog.input.name\"));\n\t\tLabel descLabel = new BoundLabel(Lang.getBinding(\"dialog.input.desc\")); // Close enough\n\t\tTextField nameText = new TextField();\n\t\tTextField descText = new TextField();\n\t\tButton commit = new ActionButton(CarbonIcons.DOCUMENT_ADD, Lang.getBinding(\"dialog.finish\"), () -> {\n\t\t\tcurrentSnippet.set(new Snippet(nameText.getText(), descText.getText(), editor.getText()));\n\t\t\teditor.setText(\"// \" + nameText.getText());\n\t\t\tbringAttention(btnSave); // Must happen after setting the snippet for the order of clearing/setting effects.\n\t\t});\n\t\tcommit.disableProperty().bind(nameText.textProperty().isEmpty());\n\t\tGridPane grid = new GridPane(5, 5);\n\t\tgrid.addRow(0, nameLabel, nameText);\n\t\tgrid.addRow(1, descLabel, descText);\n\t\tgrid.add(commit, 1, 2);\n\n\t\t// Show the new snippet prompt above the new-snippet button\n\t\tPopover popover = new Popover(grid);\n\t\tpopover.setAutoHide(true);\n\t\tpopover.setArrowLocation(Popover.ArrowLocation.BOTTOM_RIGHT);\n\t\tpopover.show(btnNew);\n\t}\n\n\t/**\n\t * Saves / updates the current snippet to the manager.\n\t */\n\tprivate void saveSnippet() {\n\t\tremoveAttention();\n\n\t\t// Should be not-null since the save button is disabled until the property is set.\n\t\tSnippet base = currentSnippet.get();\n\t\tsnippetManager.putSnippet(base.withContent(editor.getText()));\n\t\tAnimations.animateSuccess(editor, 1000);\n\t}\n\n\t/**\n\t * Deletes the current snippet from the manager.\n\t */\n\tprivate void deleteSnippet() {\n\t\tButton commit = new ActionButton(CarbonIcons.TRASH_CAN, Lang.getBinding(\"dialog.confirm\"), () -> {\n\t\t\t// Should be not-null since the delete button is disabled until the property is set.\n\t\t\tSnippet snippet = currentSnippet.get();\n\t\t\tsnippetManager.removeSnippet(snippet);\n\n\t\t\t// Clear the snippet selection and content.\n\t\t\tresetContent();\n\t\t\tcurrentSnippet.set(null);\n\t\t});\n\n\t\t// Show delete prompt above the delete button\n\t\tPopover popover = new Popover(commit);\n\t\tpopover.setAutoHide(true);\n\t\tpopover.setArrowLocation(Popover.ArrowLocation.BOTTOM_RIGHT);\n\t\tpopover.show(btnDelete);\n\t}\n\n\t/**\n\t * Shows the user a list of available snippets to load.\n\t */\n\tprivate void loadSnippet() {\n\t\tListView<Snippet> snippetsView = new ListView<>(snippetList);\n\t\tsnippetsView.setCellFactory(v -> new ListCell<>() {\n\t\t\t@Override\n\t\t\tprotected void updateItem(Snippet item, boolean empty) {\n\t\t\t\tsuper.updateItem(item, empty);\n\n\t\t\t\tif (empty || item == null) {\n\t\t\t\t\tsetGraphic(null);\n\t\t\t\t} else {\n\t\t\t\t\tLabel title = new Label(item.name());\n\t\t\t\t\tLabel desc = new Label(item.description());\n\t\t\t\t\ttitle.getStyleClass().addAll(Styles.TITLE_4, Styles.TEXT_BOLD);\n\t\t\t\t\tdesc.getStyleClass().add(Styles.TEXT_SUBTLE);\n\t\t\t\t\tVBox box = new VBox(title, desc);\n\t\t\t\t\tsetGraphic(box);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tsnippetsView.getSelectionModel().selectedItemProperty().addListener((ob, old, cur) -> {\n\t\t\teditor.setText(cur.content());\n\t\t\tcurrentSnippet.setValue(cur);\n\t\t});\n\t\tsnippetsView.getStyleClass().addAll(Styles.BG_INSET, \"borderless\");\n\t\tsnippetsView.setPrefHeight(50 * snippetList.size() + 20);\n\n\t\t// Show the load prompt above the load button\n\t\tPopover popover = new Popover(snippetsView);\n\t\tpopover.setMaxWidth(400);\n\t\tpopover.setPrefHeight(400);\n\t\tpopover.setAutoHide(true);\n\t\tpopover.setArrowLocation(Popover.ArrowLocation.BOTTOM_RIGHT);\n\t\tpopover.show(btnLoad);\n\t}\n\n\t/**\n\t * Resets the editor text to the default.\n\t */\n\tprivate void resetContent() {\n\t\teditor.setText(\"// Select an existing snippet or make a new one\\n\" +\n\t\t\t\t\"// from the buttons below.\");\n\t}\n\n\tprivate void bringAttention(Node... nodes) {\n\t\tfor (Node node : nodes) {\n\t\t\tnode.setEffect(new Glow(0.7));\n\t\t}\n\t}\n\n\t/**\n\t * Clears the special effects on buttons.\n\t */\n\tprivate void removeAttention() {\n\t\tbtnNew.setEffect(null);\n\t\tbtnLoad.setEffect(null);\n\t\tbtnSave.setEffect(null);\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tsnippetManager.removeSnippetListener(this);\n\t\teditor.close();\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic boolean isTrackable() {\n\t\treturn false;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void onSnippetAdded(@Nonnull Snippet snippet) {\n\t\tLists.sortedInsert(Snippet.NAME_COMPARATOR, snippetList, snippet);\n\t}\n\n\t@Override\n\tpublic void onSnippetModified(@Nonnull Snippet old, @Nonnull Snippet current) {\n\t\tint i = snippetList.indexOf(old);\n\t\tif (i >= 0) snippetList.set(i, current);\n\t}\n\n\t@Override\n\tpublic void onSnippetRemoved(@Nonnull Snippet snippet) {\n\t\tsnippetList.remove(snippet);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/TypeTableCell.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport dev.xdark.blw.type.*;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.Node;\nimport javafx.scene.control.TableCell;\nimport javafx.scene.input.MouseButton;\nimport me.darknet.assembler.compile.analysis.AnalysisUtils;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.cell.context.ContextMenuProvider;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.UUID;\n\n/**\n * Cell for rendering {@link ClassType}.\n *\n * @param <S>\n * \t\tTable-view generic type.\n *\n * @author Matt Coley\n */\npublic class TypeTableCell<S> extends TableCell<S, ClassType> {\n\t/** Special value used to represent null types. Randomized to prevent abuse. */\n\tstatic final ClassType NULL_TYPE = Types.instanceTypeFromInternalName(UUID.randomUUID().toString());\n\tprivate final CellConfigurationService cellConfigurationService;\n\tprivate final TextFormatConfig formatConfig;\n\tprivate final Workspace workspace;\n\n\tpublic TypeTableCell(@Nonnull CellConfigurationService cellConfigurationService,\n\t\t\t\t\t\t @Nonnull TextFormatConfig formatConfig,\n\t\t\t\t\t\t @Nonnull Workspace workspace) {\n\t\tthis.cellConfigurationService = cellConfigurationService;\n\t\tthis.formatConfig = formatConfig;\n\t\tthis.workspace = workspace;\n\t}\n\n\t@Override\n\tprotected void updateItem(ClassType type, boolean empty) {\n\t\tsuper.updateItem(type, empty);\n\t\tif (empty || type == null) {\n\t\t\tsetText(null);\n\t\t\tsetGraphic(null);\n\t\t} else {\n\t\t\tconfigureType(type);\n\t\t}\n\t}\n\n\tprivate void configureType(@Nonnull ClassType type) {\n\t\tCellData data = getTypeData(type);\n\t\tsetGraphic(data.graphic);\n\t\tsetText(data.text);\n\t\tsetOnMouseClicked(e -> {\n\t\t\tif (e.getButton() == MouseButton.SECONDARY) {\n\t\t\t\t// Lazily populate context menus when secondary click is prompted.\n\t\t\t\tif (getContextMenu() == null) setContextMenu(data.contextSupplier.makeMenu());\n\t\t\t}\n\t\t});\n\t\tsetOpacity(data.disabled ? 0.35 : 1);\n\t}\n\n\t@Nonnull\n\tprivate CellData getTypeData(@Nullable ClassType type) {\n\t\tNode graphic;\n\t\tString text;\n\t\tContextMenuProvider contextSupplier = null;\n\t\tboolean disabled = false;\n\t\tif (type == NULL_TYPE || type == null) {\n\t\t\tgraphic = Icons.getIconView(Icons.UNINITIALIZED);\n\t\t\ttext = \"null\";\n\t\t} else if (type == Types.VOID || type == Types.BOX_VOID) {\n\t\t\tdisabled = true;\n\t\t\tgraphic = Icons.getIconView(Icons.UNINITIALIZED);\n\t\t\ttext = \"void\";\n\t\t} else if (type instanceof PrimitiveType primitiveType) {\n\t\t\tdisabled = primitiveType.kind() == PrimitiveKind.T_VOID;\n\t\t\tgraphic = Icons.getIconView(Icons.PRIMITIVE);\n\t\t\ttext = primitiveType.name();\n\t\t} else if (type instanceof InstanceType instanceType) {\n\t\t\tString typeName = instanceType.internalName();\n\t\t\tClassPathNode classPath = workspace.findClass(typeName);\n\t\t\tif (classPath != null) {\n\t\t\t\tgraphic = cellConfigurationService.graphicOf(classPath);\n\t\t\t\ttext = cellConfigurationService.textOf(classPath);\n\t\t\t\tcontextSupplier = () -> cellConfigurationService.contextMenuOf(ContextualAssemblerComponent.CONTEXT_SOURCE, classPath);\n\t\t\t} else {\n\t\t\t\tgraphic = Icons.getIconView(Icons.CLASS);\n\t\t\t\ttext = formatConfig.filter(typeName);\n\t\t\t}\n\t\t} else if (type instanceof ArrayType arrayType) {\n\t\t\tCellData componentModel = getTypeData(arrayType.componentType());\n\t\t\tgraphic = Icons.getIconView(Icons.ARRAY);\n\t\t\ttext = componentModel.text + \"[]\".repeat(arrayType.dimensions());\n\t\t\tcontextSupplier = componentModel.contextSupplier;\n\t\t} else {\n\t\t\ttext = null;\n\t\t\tgraphic = null;\n\t\t}\n\t\treturn new CellData(text, graphic, contextSupplier, disabled);\n\t}\n\n\tprivate record CellData(String text, Node graphic, ContextMenuProvider contextSupplier, boolean disabled) {}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/ValueTableCell.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.collections.ObservableList;\nimport javafx.scene.control.TableCell;\nimport me.darknet.assembler.compile.analysis.Value;\nimport software.coley.recaf.util.EscapeUtil;\n\nimport java.util.Objects;\n\n/**\n * Cell for rendering {@link Value}.\n *\n * @param <S>\n * \t\tTable-view generic type.\n *\n * @author Matt Coley\n */\npublic class ValueTableCell<S> extends TableCell<S, ValueTableCell.ValueWrapper> {\n\tprivate static final String CHANGED = \"analysis-value-changed\";\n\n\t@Override\n\tprotected void updateItem(ValueWrapper wrapper, boolean empty) {\n\t\tsuper.updateItem(wrapper, empty);\n\t\tif (empty || wrapper == null) {\n\t\t\tsetText(null);\n\t\t\tsetGraphic(null);\n\t\t\tgetStyleClass().remove(CHANGED);\n\t\t} else {\n\t\t\tconfigureValue(wrapper);\n\t\t}\n\t}\n\n\tprivate void configureValue(@Nonnull ValueWrapper wrapper) {\n\t\tValue value = wrapper.value;\n\t\tString valueRep = value.valueAsString();\n\t\tif (valueRep != null)\n\t\t\tvalueRep = EscapeUtil.escapeStandardAndUnicodeWhitespace(valueRep);\n\t\tsetText(valueRep);\n\n\t\t// If a prior frame/value exists, highlight changed items\n\t\tObservableList<String> styleClass = getStyleClass();\n\t\tif (valueRep != null) {\n\t\t\tValue priorValue = wrapper.priorValue;\n\t\t\tif (priorValue != null) {\n\t\t\t\tString priorValueRep = priorValue.valueAsString();\n\t\t\t\tif (!Objects.equals(valueRep, priorValueRep) && !styleClass.contains(CHANGED)) {\n\t\t\t\t\tstyleClass.add(CHANGED);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tstyleClass.remove(CHANGED);\n\t}\n\n\t/**\n\t * @param value\n\t * \t\tVariable value.\n\t * @param priorValue\n\t * \t\tPrior state in previous frame, if known.\n\t */\n\tpublic record ValueWrapper(@Nonnull Value value, @Nullable Value priorValue) {}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/VariableData.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler;\n\nimport dev.xdark.blw.type.ClassType;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.compile.analysis.Local;\n\n/**\n * Models a variable.\n *\n * @param name\n * \t\tName of variable.\n * @param type\n * \t\tType of variable.\n * @param usage\n * \t\tUsages of the variable in the AST.\n */\npublic record VariableData(@Nonnull String name, @Nonnull ClassType type, @Nonnull AstUsages usage) {\n\t/**\n\t * @param local\n\t * \t\tblw variable declaration.\n\t * @param usage\n\t * \t\tAST usage.\n\t *\n\t * @return Data from a blw variable, plus AST usage.\n\t */\n\t@Nonnull\n\tpublic static VariableData adaptFrom(@Nonnull Local local, @Nonnull AstUsages usage) {\n\t\treturn new VariableData(local.name(), local.safeType(), usage);\n\t}\n\n\t/**\n\t * @param other\n\t * \t\tOther variable data to check against.\n\t *\n\t * @return {@code true} if the variable held by this data is the same as the other.\n\t */\n\tpublic boolean matchesNameType(@Nullable VariableData other) {\n\t\tif (other == null) return false;\n\t\treturn name.equals(other.name) && type.equals(other.type);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/AssemblyResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\n/**\n * Common resolution type of some <i>\"selected\"</i> text within an {@link software.coley.recaf.ui.pane.editing.assembler.AssemblerPane}.\n * See implementations for possible resolved contents.\n *\n * @author Matt Coley\n */\npublic interface AssemblyResolution {\n\t/**\n\t * Shared empty resolution instance.\n\t */\n\tEmptyResolution EMPTY = new EmptyResolution();\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/AssemblyResolver.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.ast.ASTElement;\nimport me.darknet.assembler.ast.primitive.ASTIdentifier;\nimport me.darknet.assembler.ast.primitive.ASTInstruction;\nimport me.darknet.assembler.ast.primitive.ASTLabel;\nimport me.darknet.assembler.ast.specific.ASTAnnotation;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTException;\nimport me.darknet.assembler.ast.specific.ASTField;\nimport me.darknet.assembler.ast.specific.ASTInner;\nimport me.darknet.assembler.ast.specific.ASTMethod;\nimport me.darknet.assembler.util.Range;\n\nimport java.util.List;\n\n/**\n * Helper for determining what content is at a specific offset within an assembler's last parsed AST.\n *\n * @author Matt Coley\n */\npublic class AssemblyResolver {\n\tprivate List<ASTElement> ast;\n\n\t/**\n\t * @param ast\n\t * \t\tNew AST to operate off of.\n\t */\n\tpublic void setAst(@Nullable List<ASTElement> ast) {\n\t\tthis.ast = ast;\n\t}\n\n\t/**\n\t * @param position\n\t * \t\tOffset in the assembler source/AST.\n\t *\n\t * @return Resolved content at the given position.\n\t */\n\t@Nonnull\n\tpublic AssemblyResolution resolveAt(int position) {\n\t\tif (ast == null)\n\t\t\treturn AssemblyResolution.EMPTY;\n\n\t\tAssemblyResolution resolution = resolveAt(position, null, ast);\n\t\tif (resolution != null)\n\t\t\treturn resolution;\n\n\t\treturn AssemblyResolution.EMPTY;\n\t}\n\n\t@Nullable\n\tprivate static AssemblyResolution resolveAt(int position, @Nullable ASTClass parentClassDec, @Nonnull List<ASTElement> ast) {\n\t\t// Example JASM snippet for reference:\n        /*\n        .super java/lang/Object\n        .annotation TestAnnotation {\n            number: 15,\n            subAnnotation: .annotation org/jetbrains/annotations/NotNull {\n                value: \"Hello, world!\"\n            },\n            stringArray: { \"one\", \"two\", \"three\" }\n        }\n        .inner private static {\n            name: InnerClass,\n            inner: Example$InnerClass,\n            outer: Example\n        }\n        .class public super Example {\n            .method public exampleMethod (LExample;)LExample; {\n                parameters: { this, other },\n                exceptions: { { A, A, B, * } },\n                code: {\n                A:\n                    aload this\n                    areturn\n                B:\n                }\n            }\n        }\n        */\n\t\tfor (ASTElement child : ast) {\n\t\t\tRange range = child.range();\n\t\t\tif (range != null && range.within(position)) {\n\t\t\t\tif (child instanceof ASTMethod method) {\n\t\t\t\t\tASTElement selectedParameter = get(position, method.parameters());\n\t\t\t\t\tif (selectedParameter != null)\n\t\t\t\t\t\treturn new VariableDeclarationResolution(parentClassDec, method, (ASTIdentifier) selectedParameter);\n\n\t\t\t\t\tfor (ASTException exception : method.exceptions()) {\n\t\t\t\t\t\tASTElement selectedLabel = get(position, List.of(exception.start(), exception.end(), exception.handler()));\n\t\t\t\t\t\tif (selectedLabel != null)\n\t\t\t\t\t\t\treturn new LabelReferenceResolution(parentClassDec, method, (ASTIdentifier) selectedLabel);\n\t\t\t\t\t\tASTElement selectedType = get(position, List.of(exception.exceptionType()));\n\t\t\t\t\t\tif (selectedType != null && selectedType.content().charAt(0) == '*')\n\t\t\t\t\t\t\treturn new TypeReferenceResolution(parentClassDec, method, (ASTIdentifier) selectedType);\n\t\t\t\t\t}\n\n\t\t\t\t\tASTElement selectedAnno = get(position, method.getVisibleAnnotations());\n\t\t\t\t\tif (selectedAnno != null)\n\t\t\t\t\t\treturn new MethodAnnotationResolution(parentClassDec, method, (ASTAnnotation) selectedAnno);\n\t\t\t\t\tselectedAnno = get(position, method.getInvisibleAnnotations());\n\t\t\t\t\tif (selectedAnno != null)\n\t\t\t\t\t\treturn new MethodAnnotationResolution(parentClassDec, method, (ASTAnnotation) selectedAnno);\n\n\t\t\t\t\tASTElement selectedInstruction = get(position, method.code().instructions());\n\t\t\t\t\tif (selectedInstruction instanceof ASTLabel label)\n\t\t\t\t\t\treturn new LabelDeclarationResolution(parentClassDec, method, label);\n\t\t\t\t\telse if (selectedInstruction != null) {\n\t\t\t\t\t\t// TODO: Some instructions may reference labels, and we'll want to support those cases here\n\t\t\t\t\t\t//  - jumps\n\t\t\t\t\t\t//  - switch\n\n\t\t\t\t\t\t// TODO: Some instructions may reference variables as well\n\n\t\t\t\t\t\t// TODO: Some instructions may have type references, method handles, etc\n\n\t\t\t\t\t\tASTInstruction insn = (ASTInstruction) selectedInstruction;\n\t\t\t\t\t\treturn new InstructionResolution(parentClassDec, method, insn);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn new MethodResolution(parentClassDec, method);\n\t\t\t\t} else if (child instanceof ASTField field) {\n\t\t\t\t\tASTElement selectedAnno = get(position, field.getVisibleAnnotations());\n\t\t\t\t\tif (selectedAnno != null)\n\t\t\t\t\t\treturn new FieldAnnotationResolution(parentClassDec, field, (ASTAnnotation) selectedAnno);\n\t\t\t\t\tselectedAnno = get(position, field.getInvisibleAnnotations());\n\t\t\t\t\tif (selectedAnno != null)\n\t\t\t\t\t\treturn new FieldAnnotationResolution(parentClassDec, field, (ASTAnnotation) selectedAnno);\n\n\t\t\t\t\treturn new FieldResolution(parentClassDec, field);\n\t\t\t\t} else if (child instanceof ASTClass klass) {\n\t\t\t\t\tASTElement selectedInterface = get(position, klass.getInterfaces());\n\t\t\t\t\tif (selectedInterface != null)\n\t\t\t\t\t\treturn new ClassImplements(klass, (ASTIdentifier) selectedInterface);\n\n\t\t\t\t\tASTElement selectedInner = get(position, klass.getInners());\n\t\t\t\t\tif (selectedInner != null)\n\t\t\t\t\t\treturn new InnerClassResolution(klass, (ASTInner) selectedInner);\n\n\t\t\t\t\tASTElement selectedAnno = get(position, klass.getVisibleAnnotations());\n\t\t\t\t\tif (selectedAnno != null)\n\t\t\t\t\t\treturn new ClassAnnotationResolution(klass, (ASTAnnotation) selectedAnno);\n\t\t\t\t\tselectedAnno = get(position, klass.getInvisibleAnnotations());\n\t\t\t\t\tif (selectedAnno != null)\n\t\t\t\t\t\treturn new ClassAnnotationResolution(klass, (ASTAnnotation) selectedAnno);\n\n\t\t\t\t\tASTIdentifier superName = klass.getSuperName();\n\t\t\t\t\tif (superName != null && superName.range().within(position))\n\t\t\t\t\t\treturn new ClassExtends(klass, superName);\n\n\t\t\t\t\t// Recurse for declared fields/methods\n\t\t\t\t\treturn resolveAt(position, klass, klass.contents());\n\t\t\t\t} else if (child instanceof ASTAnnotation anno) {\n\t\t\t\t\treturn new IndependentAnnotationResolution(anno);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Nullable\n\tprivate static ASTElement get(int position, @Nonnull List<? extends ASTElement> ast) {\n\t\tfor (ASTElement child : ast) {\n\t\t\tRange range = child.range();\n\t\t\tif (range != null && range.within(position)) {\n\t\t\t\treturn child;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/ClassAnnotationResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.ast.specific.ASTAnnotation;\nimport me.darknet.assembler.ast.specific.ASTClass;\n\n/**\n * Resolution of an annotation attached to a class declaration.\n *\n * @param targetClass\n * \t\tClass the annotation is attached to.\n * @param annotation\n * \t\tThe annotation.\n *\n * @author Matt Coley\n */\npublic record ClassAnnotationResolution(@Nonnull ASTClass targetClass, @Nonnull ASTAnnotation annotation) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/ClassExtends.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.ast.primitive.ASTIdentifier;\nimport me.darknet.assembler.ast.specific.ASTClass;\n\n/**\n * Resolution of a class name a class extends.\n *\n * @param klass\n * \t\tThe class declaration.\n * @param superName\n * \t\tThe class name being extended.\n *\n * @author Matt Coley\n */\npublic record ClassExtends(@Nonnull ASTClass klass, @Nonnull ASTIdentifier superName) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/ClassImplements.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.ast.primitive.ASTIdentifier;\nimport me.darknet.assembler.ast.specific.ASTClass;\n\n/**\n * Resolution of an interface name a class implements.\n *\n * @param klass\n * \t\tThe class declaration.\n * @param implemented\n * \t\tThe implemented interface name.\n *\n * @author Matt Coley\n */\npublic record ClassImplements(@Nonnull ASTClass klass, @Nonnull ASTIdentifier implemented) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/EmptyResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\n/**\n * Resolution of nothing.\n *\n * @author Matt Coley\n */\npublic record EmptyResolution() implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/FieldAnnotationResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.ast.specific.ASTAnnotation;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTField;\n\n/**\n * Resolution of an annotation attached to a field declaration.\n *\n * @param parentClass\n * \t\tClass declaring the field.\n * @param targetField\n * \t\tField the annotation is attached to.\n * @param annotation\n * \t\tThe annotation.\n *\n * @author Matt Coley\n */\npublic record FieldAnnotationResolution(@Nullable ASTClass parentClass, @Nonnull ASTField targetField, @Nonnull ASTAnnotation annotation) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/FieldResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTField;\n\n/**\n * Resolution of a declared field.\n *\n * @param parentClass\n * \t\tClass declaring the field. May be {@code null} when the editor is only displaying the field.\n * @param field\n * \t\tThe field.\n *\n * @author Matt Coley\n */\npublic record FieldResolution(@Nullable ASTClass parentClass, @Nonnull ASTField field) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/IndependentAnnotationResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.ast.specific.ASTAnnotation;\n\n/**\n * Resolution of an annotation declared independently <i>(Not attached to another AST item)</i>\n *\n * @param annotation\n * \t\tThe annotation.\n *\n * @author Matt Coley\n */\npublic record IndependentAnnotationResolution(@Nonnull ASTAnnotation annotation) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/InnerClassResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTInner;\n\n/**\n * Resolution of an inner class.\n *\n * @param klass\n * \t\tThe associated class declaration which the inner class is attached to.\n * @param inner\n * \t\tThe inner class.\n *\n * @author Matt Coley\n */\npublic record InnerClassResolution(@Nonnull ASTClass klass, @Nonnull ASTInner inner) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/InstructionResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.ast.primitive.ASTInstruction;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTMethod;\n\n/**\n * Resolution of an instruction within a method.\n *\n * @param parentClass\n * \t\tClass declaring the method. May be {@code null} when the editor is only displaying the method.\n * @param method\n * \t\tMethod declaring the instruction.\n * @param instruction\n * \t\tThe instruction.\n *\n * @author Matt Coley\n */\npublic record InstructionResolution(@Nullable ASTClass parentClass, @Nonnull ASTMethod method, @Nonnull ASTInstruction instruction) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/LabelDeclarationResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.ast.primitive.ASTLabel;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTMethod;\n\n/**\n * Resolution of a label declaration within a method.\n *\n * @param parentClass\n * \t\tClass declaring the method. May be {@code null} when the editor is only displaying the method.\n * @param method\n * \t\tMethod declaring the label.\n * @param label\n * \t\tThe label.\n *\n * @author Matt Coley\n */\npublic record LabelDeclarationResolution(@Nullable ASTClass parentClass, @Nonnull ASTMethod method, @Nonnull ASTLabel label) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/LabelReferenceResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.ast.primitive.ASTIdentifier;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTMethod;\n\n/**\n * Resolution of a label reference within a method.\n *\n * @param parentClass\n * \t\tClass declaring the method. May be {@code null} when the editor is only displaying the method.\n * @param method\n * \t\tMethod declaring the item referencing the given label.\n * @param labelName\n * \t\tThe referenced label name.\n *\n * @author Matt Coley\n */\npublic record LabelReferenceResolution(@Nullable ASTClass parentClass, @Nonnull ASTMethod method, @Nonnull ASTIdentifier labelName) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/MethodAnnotationResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.ast.specific.ASTAnnotation;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTMethod;\n\n/**\n * Resolution of an annotation attached to a method declaration.\n *\n * @param parentClass\n * \t\tClass declaring the method.\n * @param targetMethod\n * \t\tMethod the annotation is attached to.\n * @param annotation\n * \t\tThe annotation.\n *\n * @author Matt Coley\n */\npublic record MethodAnnotationResolution(@Nullable ASTClass parentClass, @Nonnull ASTMethod targetMethod, @Nonnull ASTAnnotation annotation) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/MethodResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTMethod;\n\n/**\n * Resolution of a declared method.\n *\n * @param parentClass\n * \t\tClass declaring the method. May be {@code null} when the editor is only displaying the method.\n * @param method\n * \t\tThe method.\n *\n * @author Matt Coley\n */\npublic record MethodResolution(@Nullable ASTClass parentClass, @Nonnull ASTMethod method) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/TypeReferenceResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.ast.primitive.ASTIdentifier;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTMethod;\n\n/**\n * Resolution of a type reference.\n *\n * @param parentClass\n * \t\tClass declaring the method. May be {@code null} when the editor is only displaying the method.\n * @param method\n * \t\tMethod declaring the instruction/attribute with a type reference.\n * @param typeName\n * \t\tThe type reference's name.\n *\n * @author Matt Coley\n */\npublic record TypeReferenceResolution(@Nullable ASTClass parentClass, @Nonnull ASTMethod method, @Nonnull ASTIdentifier typeName) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/assembler/resolve/VariableDeclarationResolution.java",
    "content": "package software.coley.recaf.ui.pane.editing.assembler.resolve;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport me.darknet.assembler.ast.primitive.ASTIdentifier;\nimport me.darknet.assembler.ast.specific.ASTClass;\nimport me.darknet.assembler.ast.specific.ASTMethod;\n\n/**\n * Resolution of a variable declaration.\n *\n * @param parentClass\n * \t\tClass declaring the method. May be {@code null} when the editor is only displaying the method.\n * @param method\n * \t\tMethod declaring the instruction/attribute with a variable declaration.\n * @param variableName\n * \t\tThe variable's name.\n *\n * @author Matt Coley\n */\npublic record VariableDeclarationResolution(@Nullable ASTClass parentClass, @Nonnull ASTMethod method, @Nonnull ASTIdentifier variableName) implements AssemblyResolution {\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/DecodingXmlPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.layout.BorderPane;\nimport software.coley.android.xml.AndroidResourceProvider;\nimport software.coley.android.xml.XmlDecoder;\nimport software.coley.recaf.info.ArscFileInfo;\nimport software.coley.recaf.info.BinaryXmlFileInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.TextFileInfo;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.navigation.FileNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.services.info.association.FileTypeSyntaxAssociationService;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.bracket.BracketMatchGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.bracket.SelectedBracketTracking;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.android.AndroidRes;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Displays a {@link TextFileInfo} via a configured {@link Editor}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class DecodingXmlPane extends BorderPane implements FileNavigable, UpdatableNavigable {\n\tprotected final AtomicBoolean updateLock = new AtomicBoolean();\n\tprotected final Editor editor;\n\tprotected FilePathNode path;\n\n\t@Inject\n\tpublic DecodingXmlPane(@Nonnull SearchBar searchBar, @Nonnull FileTypeSyntaxAssociationService languageAssociation) {\n\t\t// Configure the editor\n\t\teditor = new Editor();\n\t\teditor.setSelectedBracketTracking(new SelectedBracketTracking());\n\t\teditor.getRootLineGraphicFactory().addLineGraphicFactory(new BracketMatchGraphicFactory());\n\t\tlanguageAssociation.configureEditorSyntax(\"xml\", editor);\n\t\teditor.getCodeArea().setEditable(false);\n\t\tsearchBar.install(editor);\n\n\t\t// Layout\n\t\tsetCenter(editor);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic FilePathNode getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tsetDisable(true);\n\t\tsetOnKeyPressed(null);\n\t\teditor.close();\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\t// Pass to children\n\t\tfor (Navigable navigableChild : getNavigableChildren())\n\t\t\tif (navigableChild instanceof UpdatableNavigable updatableNavigable)\n\t\t\t\tupdatableNavigable.onUpdatePath(path);\n\n\t\t// Handle updates to the text.\n\t\tif (!updateLock.get() && path instanceof FilePathNode filePath) {\n\t\t\tFileInfo info = filePath.getValue();\n\t\t\tif (info instanceof BinaryXmlFileInfo binaryXml) {\n\t\t\t\tthis.path = filePath;\n\t\t\t\ttry {\n\t\t\t\t\t// Attempt to lookup ARSC resource for better decoding.\n\t\t\t\t\tArscFileInfo arscFile = null;\n\t\t\t\t\tWorkspace workspace = path.getValueOfType(Workspace.class);\n\t\t\t\t\tif (workspace != null) {\n\t\t\t\t\t\tFilePathNode arscPath = workspace.findFile(ArscFileInfo.NAME);\n\t\t\t\t\t\tif (arscPath != null) arscFile = (ArscFileInfo) arscPath.getValue();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Decode XML and update the editor text.\n\t\t\t\t\tAndroidResourceProvider arscResources = arscFile == null ? null : arscFile.getResourceInfo();\n\t\t\t\t\tString decodedXml = XmlDecoder.decode(binaryXml.getChunkModel(), AndroidRes.getAndroidBase(), arscResources);\n\t\t\t\t\tFxThreadUtil.run(() -> editor.setText(decodedXml));\n\t\t\t\t} catch (Exception ex) {\n\t\t\t\t\tFxThreadUtil.run(() -> editor.setText(\"<!-- Failed to decode XML:\\n\" +\n\t\t\t\t\t\t\t\"Message: \" + ex.getMessage() + \"\\n\" +\n\t\t\t\t\t\t\tStringUtil.traceToString(ex) + \"\\n--->\"));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/ElfPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport javafx.scene.control.TreeCell;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\nimport javafx.scene.layout.BorderPane;\nimport net.fornwall.jelf.ElfFile;\nimport net.fornwall.jelf.ElfSectionHeader;\nimport net.fornwall.jelf.ElfSegment;\nimport net.fornwall.jelf.ElfStringTable;\nimport net.fornwall.jelf.ElfSymbol;\nimport net.fornwall.jelf.ElfSymbolTableSection;\nimport org.kordamp.ikonli.Ikon;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.navigation.FileNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.Collection;\nimport java.util.Collections;\n\n/**\n * A pane for displaying basic information about Executable and Linkable Format files.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class ElfPane extends BorderPane implements FileNavigable, UpdatableNavigable {\n\tprivate static final Logger logger = Logging.get(ElfPane.class);\n\tprivate FilePathNode path;\n\n\t@Nonnull\n\t@Override\n\tpublic FilePathNode getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof FilePathNode filePath) {\n\t\t\tthis.path = filePath;\n\t\t\trefresh();\n\t\t}\n\t}\n\n\tprivate void refresh() {\n\t\ttry {\n\t\t\tElfFile elf = ElfFile.from(path.getValue().getRawContent());\n\n\t\t\tTreeView<Object> tree = new TreeView<>();\n\t\t\ttree.setShowRoot(false);\n\t\t\ttree.setCellFactory(v -> new ElfCell());\n\t\t\ttree.getSelectionModel().selectedItemProperty().addListener((ob, old, cur) -> {\n\t\t\t\t// TODO: Display content of selected model\n\t\t\t\t//  - Its generally going to be a Tableview that doesn't occupy all the screen real-estate\n\t\t\t\t//    - Maybe there's a better way to display this content then?\n\t\t\t\t//    - If not, we can largely migrate the work from https://github.com/Col-E/Recaf/pull/423/files\n\t\t\t});\n\t\t\tElfItem root = new ElfItem();\n\n\t\t\tElfItem elfHeader = root.child(CarbonIcons.GRID, \"ELF Header\");\n\t\t\tElfItem programHeaders = root.child(CarbonIcons.GRID, \"Program Headers\");\n\t\t\tfor (int i = 0; i < elf.e_phnum; i++) {\n\t\t\t\t// TODO: Better display name derivation based on header type (see segment toString())\n\t\t\t\tElfSegment programHeader = elf.getProgramHeader(i);\n\t\t\t\tif (programHeader != null)\n\t\t\t\t\tprogramHeaders.child(\"Header %d\".formatted(i));\n\t\t\t}\n\n\t\t\tElfItem sectionHeaders = root.child(CarbonIcons.GRID, \"Section Headers\");\n\t\t\tfor (int i = 0; i < elf.e_shnum; i++) {\n\t\t\t\tint sectionIdx = i;\n\t\t\t\tElfSectionHeader sectionHeader = Unchecked.getOr(() -> elf.getSection(sectionIdx).header, null);\n\t\t\t\tif (sectionHeader != null) {\n\t\t\t\t\tString name = sectionHeader.getName();\n\t\t\t\t\tif (!StringUtil.isNullOrEmpty(name))\n\t\t\t\t\t\tsectionHeaders.child(name);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tElfItem stringTable = root.child(CarbonIcons.GRID, \"String Table\");\n\t\t\tElfStringTable table = Unchecked.getOr(elf::getStringTable, null);\n\t\t\tif (table != null) {\n\t\t\t\tfor (int i = 0; i < table.numStrings; i++) {\n\t\t\t\t\t// TODO: Instead of being part of the tree, the table contents will be shown\n\t\t\t\t\t//  in the \"current selection\" display (see above)\n\t\t\t\t\tString str = table.get(i);\n\t\t\t\t\tif (!StringUtil.isNullOrEmpty(str))\n\t\t\t\t\t\tstringTable.child(str);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tElfItem dynamicStringTable = root.child(CarbonIcons.GRID, \"Dynamic String Table\");\n\t\t\ttable = Unchecked.getOr(elf::getDynamicStringTable, null);\n\t\t\tif (table != null) {\n\t\t\t\tfor (int i = 0; i < table.numStrings; i++) {\n\t\t\t\t\t// TODO: Instead of being part of the tree, the table contents will be shown\n\t\t\t\t\t//  in the \"current selection\" display (see above)\n\t\t\t\t\tString str = table.get(i);\n\t\t\t\t\tif (!StringUtil.isNullOrEmpty(str))\n\t\t\t\t\t\tdynamicStringTable.child(str);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tElfItem symbolTable = root.child(CarbonIcons.GRID, \"Symbol Table\");\n\t\t\tElfSymbolTableSection symbolTableSection = Unchecked.getOr(elf::getSymbolTableSection, null);\n\t\t\tif (symbolTableSection != null) {\n\t\t\t\tfor (ElfSymbol symbol : symbolTableSection.symbols) {\n\t\t\t\t\t// TODO: Instead of being part of the tree, the table contents will be shown\n\t\t\t\t\t//  in the \"current selection\" display (see above)\n\t\t\t\t\tString name = symbol.getName();\n\t\t\t\t\tif (!StringUtil.isNullOrEmpty(name))\n\t\t\t\t\t\tsymbolTable.child(name);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tElfItem dynamicSymbolTable = root.child(CarbonIcons.GRID, \"Dynamic Symbol Table\");\n\t\t\tElfSymbolTableSection dynamicSymbolTableSection = Unchecked.getOr(elf::getDynamicSymbolTableSection, null);\n\t\t\tif (dynamicSymbolTableSection != null) {\n\t\t\t\tfor (ElfSymbol symbol : dynamicSymbolTableSection.symbols) {\n\t\t\t\t\t// TODO: Instead of being part of the tree, the table contents will be shown\n\t\t\t\t\t//  in the \"current selection\" display (see above)\n\t\t\t\t\tString name = symbol.getName();\n\t\t\t\t\tif (!StringUtil.isNullOrEmpty(name))\n\t\t\t\t\t\tdynamicSymbolTable.child(name);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttree.setRoot(root);\n\n\t\t\tsetCenter(tree);\n\t\t} catch (Throwable t) {\n\t\t\tlogger.error(\"Failed parsing {}\", path.getValue().getName(), t);\n\t\t}\n\t}\n\n\tprivate static class ElfItem extends TreeItem<Object> {\n\t\t@Nonnull\n\t\tprivate ElfItem child(@Nonnull Ikon icon, @Nonnull Object value) {\n\t\t\tElfItem child = new ElfItem();\n\t\t\tchild.setValue(value);\n\t\t\tchild.setGraphic(new FontIconView(icon));\n\t\t\tgetChildren().add(child);\n\t\t\treturn child;\n\t\t}\n\n\t\t@Nonnull\n\t\tprivate ElfItem child(@Nonnull Object value) {\n\t\t\tElfItem child = new ElfItem();\n\t\t\tchild.setValue(value);\n\t\t\tgetChildren().add(child);\n\t\t\treturn child;\n\t\t}\n\t}\n\n\tprivate static class ElfCell extends TreeCell<Object> {\n\t\t@Override\n\t\tprotected void updateItem(Object item, boolean empty) {\n\t\t\tsuper.updateItem(item, empty);\n\n\t\t\tif (item == null || empty) {\n\t\t\t\tsetText(null);\n\t\t\t\tsetGraphic(null);\n\t\t\t} else if (item instanceof String itemString) {\n\t\t\t\tsetText(itemString);\n\t\t\t}\n\n\t\t\tTreeItem<Object> treeItem = getTreeItem();\n\t\t\tif (treeItem != null)\n\t\t\t\tsetGraphic(treeItem.getGraphic());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/PePane.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport javafx.scene.control.TreeCell;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\nimport javafx.scene.layout.BorderPane;\nimport me.martinez.pe.ExportEntry;\nimport me.martinez.pe.ImportEntry;\nimport me.martinez.pe.LibraryImports;\nimport me.martinez.pe.PeImage;\nimport me.martinez.pe.headers.ImageDataDirectory;\nimport me.martinez.pe.headers.ImageDosHeader;\nimport me.martinez.pe.headers.ImageFileHeader;\nimport me.martinez.pe.headers.ImageNtHeaders;\nimport me.martinez.pe.headers.ImageOptionalHeader;\nimport me.martinez.pe.headers.ImageSectionHeader;\nimport me.martinez.pe.io.CadesBufferStream;\nimport me.martinez.pe.util.ParseError;\nimport me.martinez.pe.util.ParseResult;\nimport org.kordamp.ikonli.Ikon;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.navigation.FileNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.ui.control.FontIconView;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Objects;\n\n/**\n * A pane for displaying basic information about Windows Portable Executables.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class PePane extends BorderPane implements FileNavigable, UpdatableNavigable {\n\tprivate static final Logger logger = Logging.get(PePane.class);\n\tprivate FilePathNode path;\n\n\t@Nonnull\n\t@Override\n\tpublic FilePathNode getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof FilePathNode filePath) {\n\t\t\tthis.path = filePath;\n\t\t\trefresh();\n\t\t}\n\t}\n\n\tprivate void refresh() {\n\t\tParseResult<PeImage> result = PeImage.read(new CadesBufferStream(path.getValue().getRawContent()));\n\t\tif (result.isOk()) {\n\t\t\tPeImage pe = result.getOk();\n\n\t\t\tTreeView<Object> tree = new TreeView<>();\n\t\t\ttree.setShowRoot(false);\n\t\t\ttree.setCellFactory(v -> new PeCell());\n\t\t\ttree.getSelectionModel().selectedItemProperty().addListener((ob, old, cur) -> {\n\t\t\t\t// TODO: Display content of selected model\n\t\t\t\t//  - Its generally going to be a Tableview that doesn't occupy all the screen real-estate\n\t\t\t\t//    - Maybe there's a better way to display this content then?\n\t\t\t\t//    - If not, we can largely migrate the work from https://github.com/Col-E/Recaf/pull/422/files\n\t\t\t});\n\t\t\tPeItem root = new PeItem();\n\t\t\tif (pe.dosHeader != null) {\n\t\t\t\troot.child(CarbonIcons.GRID, pe.dosHeader);\n\t\t\t}\n\n\t\t\tImageNtHeaders ntHeaders = pe.ntHeaders;\n\t\t\tif (ntHeaders != null) {\n\t\t\t\tPeItem ntItem = root.child(CarbonIcons.GRID, ntHeaders);\n\t\t\t\tImageFileHeader fileHeader = ntHeaders.fileHeader;\n\t\t\t\tif (fileHeader != null) {\n\t\t\t\t\tntItem.child(CarbonIcons.BLOCKCHAIN, fileHeader);\n\t\t\t\t}\n\n\t\t\t\tImageOptionalHeader optionalHeader = ntHeaders.optionalHeader;\n\t\t\t\tif (optionalHeader != null) {\n\t\t\t\t\tPeItem optionalItem = ntItem.child(CarbonIcons.BLOCKCHAIN, optionalHeader);\n\t\t\t\t\tImageDataDirectory[] dataDirectory = optionalHeader.dataDirectory;\n\t\t\t\t\tif (dataDirectory != null) {\n\t\t\t\t\t\tfor (ImageDataDirectory imageDataDirectory : dataDirectory) {\n\t\t\t\t\t\t\toptionalItem.child(CarbonIcons.CODE_REFERENCE, imageDataDirectory);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tPeItem sectionHeaders = root.child(CarbonIcons.GRID, \"Section Headers\");\n\t\t\tfor (ParseResult<ImageSectionHeader> headerResult : pe.sectionHeaders) {\n\t\t\t\tif (headerResult.isOk()) {\n\t\t\t\t\tImageSectionHeader header = headerResult.getOk();\n\t\t\t\t\tsectionHeaders.child(header);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tPeItem imports = root.child(CarbonIcons.DOCUMENT_IMPORT, \"Imports\");\n\t\t\tif (pe.imports.isOk()) {\n\t\t\t\tfor (LibraryImports libraryImports : pe.imports.getOk()) {\n\t\t\t\t\tPeItem library = imports.child(CarbonIcons.BOOK, libraryImports);\n\t\t\t\t\tfor (ImportEntry entry : libraryImports.entries) {\n\t\t\t\t\t\tlibrary.child(CarbonIcons.NOTEBOOK_REFERENCE, entry);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tPeItem exports = root.child(CarbonIcons.DOCUMENT_EXPORT, \"Exports\");\n\t\t\tif (pe.exports.isOk()) {\n\t\t\t\tfor (ExportEntry entry : pe.exports.getOk().entries) {\n\t\t\t\t\texports.child(CarbonIcons.NOTEBOOK_REFERENCE, entry);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttree.setRoot(root);\n\t\t\tsetCenter(tree);\n\t\t} else {\n\t\t\tParseError err = result.getErr();\n\t\t\tlogger.error(\"Failed parsing {} - {}\", path.getValue().getName(), err.getReason(), err.getException());\n\t\t}\n\t}\n\n\tprivate static class PeItem extends TreeItem<Object> {\n\t\t@Nonnull\n\t\tprivate PeItem child(@Nonnull Ikon icon, @Nonnull Object value) {\n\t\t\tPeItem child = new PeItem();\n\t\t\tchild.setValue(value);\n\t\t\tchild.setGraphic(new FontIconView(icon));\n\t\t\tgetChildren().add(child);\n\t\t\treturn child;\n\t\t}\n\n\t\t@Nonnull\n\t\tprivate PeItem child(@Nonnull Object value) {\n\t\t\tPeItem child = new PeItem();\n\t\t\tchild.setValue(value);\n\t\t\tgetChildren().add(child);\n\t\t\treturn child;\n\t\t}\n\t}\n\n\tprivate static class PeCell extends TreeCell<Object> {\n\t\t@Override\n\t\tprotected void updateItem(Object item, boolean empty) {\n\t\t\tsuper.updateItem(item, empty);\n\n\t\t\tif (item == null || empty) {\n\t\t\t\tsetText(null);\n\t\t\t\tsetGraphic(null);\n\t\t\t} else if (item instanceof String itemString) {\n\t\t\t\tsetText(itemString);\n\t\t\t} else if (item instanceof ImageDosHeader dosHeader) {\n\t\t\t\tsetText(\"DOS Header\");\n\t\t\t} else if (item instanceof ImageNtHeaders ntHeaders) {\n\t\t\t\tsetText(\"NT Headers\");\n\t\t\t} else if (item instanceof ImageFileHeader fileHeader) {\n\t\t\t\tsetText(\"File Header\");\n\t\t\t} else if (item instanceof ImageOptionalHeader optionalHeader) {\n\t\t\t\tsetText(\"Optional Header\");\n\t\t\t} else if (item instanceof ImageDataDirectory dataDirectory) {\n\t\t\t\tsetText(\"Data Directory\");\n\t\t\t} else if (item instanceof ImageSectionHeader sectionHeader) {\n\t\t\t\tsetText(\"Section Header: \" + sectionHeader.getName());\n\t\t\t} else if (item instanceof LibraryImports libraryImports) {\n\t\t\t\tsetText(\"Library Imports: \" + libraryImports.name);\n\t\t\t} else if (item instanceof ImportEntry importEntry) {\n\t\t\t\tsetText(Objects.requireNonNullElse(importEntry.name, String.valueOf(importEntry.ordinal)));\n\t\t\t} else if (item instanceof ExportEntry exportEntry) {\n\t\t\t\tsetText(Objects.requireNonNullElse(exportEntry.name, String.valueOf(exportEntry.ordinal)));\n\t\t\t}\n\n\t\t\tTreeItem<Object> treeItem = getTreeItem();\n\t\t\tif (treeItem != null)\n\t\t\t\tsetGraphic(treeItem.getGraphic());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/hex/HexAdapter.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary.hex;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.layout.BorderPane;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.path.BundlePathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.util.Animations;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\n\nimport java.util.Collection;\nimport java.util.Collections;\n\n/**\n * Adapts the navigable system with paths to the generic system used by {@link HexEditor}.\n *\n * @author Matt Coley\n */\npublic class HexAdapter extends BorderPane implements UpdatableNavigable {\n\tprivate static final Logger logger = Logging.get(HexAdapter.class);\n\tprivate final HexEditor editor;\n\tprivate PathNode<?> path;\n\n\tpublic HexAdapter(@Nonnull HexConfig config) {\n\t\tsetCenter(editor = new HexEditor(config));\n\t}\n\n\t/**\n\t * Delegates to the hex editor commit action.\n\t * <br>\n\t * Updates the {@link ClassInfo} or {@link FileInfo} represented by the {@link #path} in the workspace.\n\t */\n\tpublic void save() {\n\t\tif (editor.commit()) {\n\t\t\t// TODO: Animating on the hex editor is rather jarring\n\t\t\t//  vs on other controls like a text editor...\n\t\t\tAnimations.animateSuccess(editor, 1000);\n\t\t} else {\n\t\t\tAnimations.animateFailure(editor, 1000);\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void requestFocus() {\n\t\teditor.requestFocus();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tsetCenter(null);\n\t\tsetDisable(true);\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof FilePathNode filePath) {\n\t\t\tFileInfo fileInfo = filePath.getValue();\n\t\t\tbyte[] raw = fileInfo.getRawContent();\n\t\t\tif (editor.hasData()) {\n\t\t\t\teditor.updateData(raw);\n\t\t\t} else {\n\t\t\t\teditor.setInitialData(raw);\n\t\t\t\teditor.setCommitAction(data -> {\n\t\t\t\t\tBundlePathNode parent = filePath.getParent().getParent();\n\t\t\t\t\tFileBundle bundle = (FileBundle) parent.getValue();\n\t\t\t\t\tbundle.put(fileInfo.toFileBuilder().withRawContent(data).build());\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (path instanceof ClassPathNode classPath) {\n\t\t\tClassInfo classInfo = classPath.getValue();\n\t\t\tif (classInfo.isJvmClass()) {\n\t\t\t\tbyte[] bytecode = classInfo.asJvmClass().getBytecode();\n\t\t\t\tif (editor.hasData()) {\n\t\t\t\t\teditor.updateData(bytecode);\n\t\t\t\t} else {\n\t\t\t\t\teditor.setInitialData(bytecode);\n\t\t\t\t\teditor.setCommitAction(data -> {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tBundlePathNode parent = classPath.getParent().getParent();\n\t\t\t\t\t\t\tJvmClassBundle bundle = (JvmClassBundle) parent.getValue();\n\t\t\t\t\t\t\tJvmClassInfo newClass = classInfo.asJvmClass().toJvmClassBuilder().adaptFrom(data).build();\n\t\t\t\t\t\t\tbundle.put(newClass);\n\t\t\t\t\t\t\tFxThreadUtil.run(() -> Animations.animateSuccess(this, 1000));\n\t\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\t\t// Skill issue\n\t\t\t\t\t\t\tlogger.error(\"Hex editor edits to class resulted in an invalid class\", t);\n\t\t\t\t\t\t\tFxThreadUtil.run(() -> Animations.animateFailure(this, 1000));\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/hex/HexConfig.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary.hex;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\n\n/**\n * Config for hex editor.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class HexConfig extends BasicConfigContainer {\n\tprivate final ObservableBoolean showAddress = new ObservableBoolean(true);\n\tprivate final ObservableBoolean showAscii = new ObservableBoolean(true);\n\tprivate final ObservableInteger rowLength = new ObservableInteger(16);\n\tprivate final ObservableInteger rowSplitInterval = new ObservableInteger(8);\n\n\t@Inject\n\tpublic HexConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, \"hex\" + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"show-address\", boolean.class, showAddress));\n\t\taddValue(new BasicConfigValue<>(\"show-ascii\", boolean.class, showAscii));\n\t\taddValue(new BasicConfigValue<>(\"row-length\", int.class, rowLength));\n\t\taddValue(new BasicConfigValue<>(\"row-split-interval\", int.class, rowSplitInterval));\n\t}\n\n\t/**\n\t * @return {@code true} to show the current row's address/offset.\n\t * {@code false} to hide the address/offset display.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getShowAddress() {\n\t\treturn showAddress;\n\t}\n\n\t/**\n\t * @return {@code true} to show the ascii column.\n\t * {@code false} to hide the ascii column.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getShowAscii() {\n\t\treturn showAscii;\n\t}\n\n\t/**\n\t * @return Number of items to show per row.\n\t */\n\t@Nonnull\n\tpublic ObservableInteger getRowLength() {\n\t\treturn rowLength;\n\t}\n\n\t/**\n\t * @return Number of items between each split in the hex column.\n\t */\n\t@Nonnull\n\tpublic ObservableInteger getRowSplitInterval() {\n\t\treturn rowSplitInterval;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/hex/HexEditor.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary.hex;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.IntegerProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleIntegerProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.event.EventHandler;\nimport javafx.geometry.Bounds;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.layout.BorderPane;\nimport org.fxmisc.flowless.VirtualFlow;\nimport org.fxmisc.flowless.VirtualizedScrollPane;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.ui.control.VirtualizedScrollPaneWrapper;\nimport software.coley.recaf.ui.pane.editing.binary.hex.ops.HexAccess;\nimport software.coley.recaf.ui.pane.editing.binary.hex.ops.HexNavigation;\nimport software.coley.recaf.ui.pane.editing.binary.hex.ops.HexOperations;\nimport software.coley.recaf.ui.pane.editing.binary.hex.cell.HexRow;\nimport software.coley.recaf.ui.pane.editing.binary.hex.cell.HexRowHeader;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.NodeEvents;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\n/**\n * Hex editor control.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class HexEditor extends BorderPane {\n\tprivate static final Logger logger = Logging.get(HexEditor.class);\n\tprivate final HexConfig config;\n\tprivate final ObservableList<Integer> rows = FXCollections.observableArrayList();\n\tprivate final IntegerProperty rowCount = new SimpleIntegerProperty(0);\n\tprivate final VirtualFlow<Integer, HexRow> flow;\n\tprivate final HexOperations ops = newHexOperations();\n\tprivate Consumer<byte[]> commitAction;\n\tprivate byte[] data;\n\tprivate byte[] dataDefaultState;\n\n\t@Inject\n\tpublic HexEditor(@Nonnull HexConfig config) {\n\t\tthis.config = config;\n\t\tgetStyleClass().add(\"hex-view\");\n\t\tflow = VirtualFlow.createVertical(rows, row -> new HexRow(config, rowCount, ops, row));\n\t\tflow.setFocusTraversable(true);\n\t\tVirtualizedScrollPane<VirtualFlow<Integer, HexRow>> scroll = new VirtualizedScrollPaneWrapper<>(flow);\n\n\t\tconfig.getRowLength().addChangeListener((ob, old, cur) -> refreshRowDisplay());\n\t\tconfig.getRowSplitInterval().addChangeListener((ob, old, cur) -> refreshRowDisplay());\n\t\tconfig.getShowAddress().addChangeListener((ob, old, cur) -> refreshRowDisplay());\n\t\tconfig.getShowAscii().addChangeListener((ob, old, cur) -> refreshRowDisplay());\n\n\t\tregisterInputListeners();\n\n\t\tsetTop(new HexRowHeader(config, rowCount, ops).getNode());\n\t\tsetCenter(scroll);\n\t}\n\n\tprivate void registerInputListeners() {\n\t\tNodeEvents.addKeyPressHandler(flow, ops.keyListener());\n\t\tNodeEvents.addMousePressHandler(flow, e -> {\n\t\t\tif (!e.isPrimaryButtonDown())\n\t\t\t\treturn;\n\t\t\tint currentOffset = ops.navigation().selectionOffset();\n\t\t\tfor (HexRow cell : flow.visibleCells()) {\n\t\t\t\tBounds bounds = cell.getNode().getBoundsInParent();\n\t\t\t\tif (bounds.contains(e.getX(), e.getY())) {\n\t\t\t\t\tint offset = cell.pickOffsetAtPosition(e.getX(), e.getY());\n\t\t\t\t\tif (offset >= 0) {\n\t\t\t\t\t\tif (currentOffset == offset) {\n\t\t\t\t\t\t\tops.engageCurrent();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tops.navigation().select(offset);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Do not propagate the event up the scene graph.\n\t\t\te.consume();\n\t\t});\n\t\tNodeEvents.addMouseReleaseHandler(flow, e -> {\n\t\t\t// If we've not gotten focus grab it and consume the event so that it does not propagate up the scene graph.\n\t\t\tif (!flow.isFocusWithin()) {\n\t\t\t\te.consume();\n\t\t\t\tflow.requestFocus();\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * @return Hex operations API for this editor.\n\t */\n\t@Nonnull\n\tpublic HexOperations getOperations() {\n\t\treturn ops;\n\t}\n\n\t/**\n\t * The view does not know anything about where the data originates, thus\n\t * it is the caller's responsibility to delegate 'commit' to update the\n\t * original data.\n\t * <p>\n\t * The specified action will be passed the current state of the data as\n\t * seen in the hex-view when the user requests a save.\n\t *\n\t * @param dataCommit\n\t * \t\tAction to handle committing changes of the content.\n\t */\n\tpublic void setCommitAction(@Nullable Consumer<byte[]> dataCommit) {\n\t\tthis.commitAction = dataCommit;\n\t}\n\n\t/**\n\t * Called to 'commit' the changes.\n\t * <p>\n\t * Delegates to the user provided action via {@link #setCommitAction(Consumer)}.\n\t */\n\tpublic boolean commit() {\n\t\tif (data == null) {\n\t\t\tlogger.warn(\"Tried to commit hex-view contents without assocated data.\");\n\t\t\treturn false;\n\t\t}\n\t\tif (commitAction == null) {\n\t\t\tlogger.warn(\"Tried to commit hex-view contents without commit action specified.\");\n\t\t\treturn false;\n\t\t}\n\t\ttry {\n\t\t\tcommitAction.accept(data);\n\t\t\treturn true;\n\t\t} catch (Exception ex) {\n\t\t\tlogger.error(\"Failed to commit hex-view contents\", ex);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * @return {@code true} when data has been assigned to the editor.\n\t */\n\tpublic boolean hasData() {\n\t\treturn data != null;\n\t}\n\n\t/**\n\t * Used to assign the initial state of the data to display.\n\t * For making modifications to the current data, use {@link #updateData(byte[])} instead.\n\t *\n\t * @param data\n\t * \t\tData to assign to the hex-view.\n\t */\n\tpublic void setInitialData(@Nullable byte[] data) {\n\t\tif (data == null)\n\t\t\tdata = new byte[0];\n\t\telse\n\t\t\tdata = Arrays.copyOf(data, data.length);\n\t\tsetData0(data);\n\n\t\t// Scroll to the top\n\t\tFxThreadUtil.run(() -> flow.show(0));\n\t}\n\n\t/**\n\t * Used to update the existing data of the hex-view.\n\t *\n\t * @param data\n\t * \t\tUpdated data model.\n\t */\n\tpublic void updateData(@Nonnull byte[] data) {\n\t\tif (dataDefaultState == null) {\n\t\t\tlogger.warn(\"Tried to update hex-view data before setting the initial data state\");\n\t\t\treturn;\n\t\t}\n\t\tthis.data = data;\n\n\t\trefreshRowDisplay();\n\t}\n\n\t/**\n\t * Reset the data content to what it was when initially calling {@link #setInitialData(byte[])}.\n\t */\n\tpublic void resetData() {\n\t\tthis.data = Arrays.copyOf(dataDefaultState, dataDefaultState.length);\n\t\trefreshRowDisplay();\n\t}\n\n\t/**\n\t * @param data\n\t * \t\tData to set.\n\t */\n\tprivate void setData0(@Nonnull byte[] data) {\n\t\tthis.data = data;\n\t\tthis.dataDefaultState = Arrays.copyOf(data, data.length);\n\n\t\t// Refresh model/display.\n\t\trefreshRowModel();\n\t\trefreshRowDisplay();\n\t}\n\n\t/**\n\t * Called when {@link #data} is updated and requires recomputing how many rows need to be displayed.\n\t */\n\tprivate void refreshRowModel() {\n\t\t// Update observable list model to generate the numbers of rows we want to show.\n\t\tdouble rowLength = config.getRowLength().getValue().doubleValue();\n\t\tint rowCount = (int) Math.max(1, Math.ceil(data.length / rowLength));\n\t\tif (rowCount != this.rows.size()) {\n\t\t\tList<Integer> zeroToRowMax = IntStream.range(0, rowCount)\n\t\t\t\t\t.boxed().collect(Collectors.toList());\n\t\t\tthis.rows.setAll(zeroToRowMax);\n\t\t\tthis.rowCount.setValue(rowCount);\n\t\t}\n\t}\n\n\t/**\n\t * Called when the data is replaced.\n\t */\n\tprotected void refreshRowDisplay() {\n\t\tflow.visibleCells().forEach(HexRow::redraw);\n\t}\n\n\t/**\n\t * Called when the data is replaced.\n\t *\n\t * @param offset\n\t * \t\tData offset.\n\t */\n\tprotected void refreshRowDisplay(int offset) {\n\t\t// Map the offset to the row, then refresh the row UI if it is visible\n\t\tflow.getCellIfVisible(offsetToRowIndex(offset)).ifPresent(HexRow::redraw);\n\t}\n\n\t/**\n\t * Brings the data at the given offset into view.\n\t *\n\t * @param offset\n\t * \t\tData offset.\n\t */\n\tprivate void bringOffsetIntoView(int offset) {\n\t\tint rowLength = config.getRowLength().getValue();\n\t\tint offsetRow = offset / rowLength;\n\t\tint firstVisRow = flow.getFirstVisibleIndex();\n\n\t\t// Checking for one below the current low ensures if we show the current row as the 'first'\n\t\t// then there will be an additional line above it as a sort of buffer.\n\t\tif (offsetRow - 1 < firstVisRow) {\n\t\t\tflow.showAsFirst(Math.max(0, offsetRow));\n\t\t} else {\n\t\t\t// Similarly having a +2 check has the same effect but as a buffer below.\n\t\t\t// Something with virtualization makes an equivalent offset but reversed not work here, so +2 it is.\n\t\t\tint lastVisRow = flow.getLastVisibleIndex();\n\t\t\tif (offsetRow + 2 >= lastVisRow) {\n\t\t\t\tflow.showAsLast(Math.min(rowCount.intValue(), offsetRow + 2));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @param offset\n\t * \t\tData offset.\n\t *\n\t * @return Row index that contains the offset.\n\t */\n\tprivate int offsetToRowIndex(int offset) {\n\t\tint rowLength = config.getRowLength().getValue();\n\t\treturn (offset / rowLength);\n\t}\n\n\t@Nonnull\n\tprivate HexOperations newHexOperations() {\n\t\tIntegerProperty focusedOffset = new SimpleIntegerProperty(0);\n\t\tBooleanProperty isHexColumActive = new SimpleBooleanProperty(true);\n\t\tHexAccess currentRead = () -> data;\n\t\tHexAccess originalRead = () -> dataDefaultState;\n\t\tHexNavigation navigation = new HexNavigation() {\n\t\t\t@Override\n\t\t\tpublic int selectionOffset() {\n\t\t\t\treturn focusedOffset.get();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic boolean isHexColumnSelected() {\n\t\t\t\treturn isHexColumActive.get();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void switchColumns() {\n\t\t\t\tif (config.getShowAscii().getValue()) {\n\t\t\t\t\t// If we show both hex/ascii columns we want to toggle between the two and refresh the display.\n\t\t\t\t\tisHexColumActive.set(!isHexColumActive.get());\n\t\t\t\t\trefreshRowDisplay(focusedOffset.get());\n\t\t\t\t} else {\n\t\t\t\t\t// If we only show the hex column there is nothing to toggle between, and no reason to refresh.\n\t\t\t\t\tisHexColumActive.set(true);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void select(int offset) {\n\t\t\t\tint max = currentRead.length();\n\t\t\t\tint clampedOffset = Math.clamp(offset, 0, max - 1);\n\n\t\t\t\tfocusedOffset.setValue(clampedOffset);\n\n\t\t\t\tflow.visibleCells().forEach(c -> c.updateSelection(clampedOffset));\n\n\t\t\t\tbringOffsetIntoView(offset);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void selectNext() {\n\t\t\t\tselect(focusedOffset.get() + 1);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void selectPrevious() {\n\t\t\t\tselect(focusedOffset.get() - 1);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void selectDown() {\n\t\t\t\tselect(focusedOffset.get() + config.getRowLength().getValue());\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void selectUp() {\n\t\t\t\tselect(focusedOffset.get() - config.getRowLength().getValue());\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void selectDown(int rows) {\n\t\t\t\tselect(focusedOffset.get() + config.getRowLength().getValue() * rows);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void selectUp(int rows) {\n\t\t\t\tselect(focusedOffset.get() - config.getRowLength().getValue() * rows);\n\t\t\t}\n\t\t};\n\t\treturn new HexOperations() {\n\t\t\tprivate final EventHandler<KeyEvent> listener = e -> {\n\t\t\t\t// If the focus is not in the editor, bring it in.\n\t\t\t\tif (!isFocusWithin()) flow.requestFocus();\n\n\t\t\t\t// Do not propagate the key-event up the scene graph.\n\t\t\t\t// Things like arrow keys and tabs if handled by parents can\n\t\t\t\t// unintentionally bring focus away from the editor component.\n\t\t\t\te.consume();\n\n\t\t\t\t// Handle special keys.\n\t\t\t\tKeyCode code = e.getCode();\n\t\t\t\tHexNavigation nav = navigation();\n\t\t\t\tswitch (code) {\n\t\t\t\t\tcase ENTER -> engageCurrent();\n\t\t\t\t\tcase ESCAPE -> cancelCurrent();\n\t\t\t\t\tcase TAB -> nav.switchColumns();\n\t\t\t\t\tcase PAGE_UP -> nav.selectUp(Math.max(1, flow.visibleCells().size() - 1));\n\t\t\t\t\tcase PAGE_DOWN -> nav.selectDown(Math.max(1, flow.visibleCells().size() - 1));\n\t\t\t\t\tcase RIGHT, KP_RIGHT -> nav.selectNext();\n\t\t\t\t\tcase LEFT, KP_LEFT -> nav.selectPrevious();\n\t\t\t\t\tcase UP, KP_UP -> nav.selectUp();\n\t\t\t\t\tcase DOWN, KP_DOWN -> nav.selectDown();\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic HexAccess currentAccess() {\n\t\t\t\treturn currentRead;\n\t\t\t}\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic HexAccess originalAccess() {\n\t\t\t\treturn originalRead;\n\t\t\t}\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic HexNavigation navigation() {\n\t\t\t\treturn navigation;\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void refreshDisplay(int offset, boolean asciiOrigin) {\n\t\t\t\trefreshRowDisplay(offset);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void engageCurrent() {\n\t\t\t\tengage((row, offset) -> row.engage(offset, true));\n\n\t\t\t\t// Engage can initiate editing in a cell, or commit work-in-progress edits.\n\t\t\t\t// When WIP edits are committed, the text editor is swapped out of the scene\n\t\t\t\t// which can cause a loss of focus. We'll catch that and re-focus here.\n\t\t\t\tif (!flow.isFocusWithin()) flow.requestFocus();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void cancelCurrent() {\n\t\t\t\tengage((row, offset) -> row.engage(offset, false));\n\n\t\t\t\t// Same reasoning as with engage-current above.\n\t\t\t\tif (!flow.isFocusWithin()) flow.requestFocus();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void sendKeyToCurrentEngaged(@Nonnull KeyCode code) {\n\t\t\t\tengage((row, offset) -> row.sendKeyToCurrentEngaged(offset, code));\n\t\t\t}\n\n\t\t\t@Nonnull\n\t\t\t@Override\n\t\t\tpublic EventHandler<KeyEvent> keyListener() {\n\t\t\t\treturn listener;\n\t\t\t}\n\n\t\t\tprivate void engage(CellConsumer consumer) {\n\t\t\t\tint currentOffset = focusedOffset.get();\n\n\t\t\t\tbringOffsetIntoView(currentOffset);\n\t\t\t\tint rowLength = config.getRowLength().getValue();\n\n\t\t\t\tOptional<HexRow> cellIfVisible = flow.getCellIfVisible(offsetToRowIndex(currentOffset));\n\t\t\t\tif (cellIfVisible.isPresent()) {\n\t\t\t\t\tHexRow row = cellIfVisible.get();\n\t\t\t\t\tconsumer.accept(row, currentOffset);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tinterface CellConsumer {\n\t\t\t\tvoid accept(@Nonnull HexRow cell, int offset);\n\t\t\t}\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/hex/HexUtil.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary.hex;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.util.StringUtil;\n\n/**\n * Hex utils.\n *\n * @author Matt Coley\n */\npublic class HexUtil {\n\t/** Char used to represent invalid ASCII content */\n\tpublic static final char INV = '\\0';\n\t/** Char used to visualize invalid ASCII content */\n\tpublic static final char INV_DISPLAY = '.';\n\tprivate static final char[] byteToChar = {\n\t\t\tINV, INV, INV, INV, INV, INV, INV, INV, INV, INV, INV, INV, INV, INV, INV, INV,\n\t\t\tINV, INV, INV, INV, INV, INV, INV, INV, INV, INV, INV, INV, INV, INV, INV, INV,\n\t\t\t' ', '!', '\"', '#', '$', '%', '&', '\\'', '(', ')', '*', '+', ',', '-', '.', '/',\n\t\t\t'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',\n\t\t\t'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',\n\t\t\t'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\\\', ']', '^', '_',\n\t\t\t'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',\n\t\t\t'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', INV\n\t};\n\tprivate static final String[] byteToString = {\n\t\t\t\"00\", \"01\", \"02\", \"03\", \"04\", \"05\", \"06\", \"07\", \"08\", \"09\", \"0A\", \"0B\", \"0C\", \"0D\", \"0E\", \"0F\",\n\t\t\t\"10\", \"11\", \"12\", \"13\", \"14\", \"15\", \"16\", \"17\", \"18\", \"19\", \"1A\", \"1B\", \"1C\", \"1D\", \"1E\", \"1F\",\n\t\t\t\"20\", \"21\", \"22\", \"23\", \"24\", \"25\", \"26\", \"27\", \"28\", \"29\", \"2A\", \"2B\", \"2C\", \"2D\", \"2E\", \"2F\",\n\t\t\t\"30\", \"31\", \"32\", \"33\", \"34\", \"35\", \"36\", \"37\", \"38\", \"39\", \"3A\", \"3B\", \"3C\", \"3D\", \"3E\", \"3F\",\n\t\t\t\"40\", \"41\", \"42\", \"43\", \"44\", \"45\", \"46\", \"47\", \"48\", \"49\", \"4A\", \"4B\", \"4C\", \"4D\", \"4E\", \"4F\",\n\t\t\t\"50\", \"51\", \"52\", \"53\", \"54\", \"55\", \"56\", \"57\", \"58\", \"59\", \"5A\", \"5B\", \"5C\", \"5D\", \"5E\", \"5F\",\n\t\t\t\"60\", \"61\", \"62\", \"63\", \"64\", \"65\", \"66\", \"67\", \"68\", \"69\", \"6A\", \"6B\", \"6C\", \"6D\", \"6E\", \"6F\",\n\t\t\t\"70\", \"71\", \"72\", \"73\", \"74\", \"75\", \"76\", \"77\", \"78\", \"79\", \"7A\", \"7B\", \"7C\", \"7D\", \"7E\", \"7F\",\n\t\t\t\"80\", \"81\", \"82\", \"83\", \"84\", \"85\", \"86\", \"87\", \"88\", \"89\", \"8A\", \"8B\", \"8C\", \"8D\", \"8E\", \"8F\",\n\t\t\t\"90\", \"91\", \"92\", \"93\", \"94\", \"95\", \"96\", \"97\", \"98\", \"99\", \"9A\", \"9B\", \"9C\", \"9D\", \"9E\", \"9F\",\n\t\t\t\"A0\", \"A1\", \"A2\", \"A3\", \"A4\", \"A5\", \"A6\", \"A7\", \"A8\", \"A9\", \"AA\", \"AB\", \"AC\", \"AD\", \"AE\", \"AF\",\n\t\t\t\"B0\", \"B1\", \"B2\", \"B3\", \"B4\", \"B5\", \"B6\", \"B7\", \"B8\", \"B9\", \"BA\", \"BB\", \"BC\", \"BD\", \"BE\", \"BF\",\n\t\t\t\"C0\", \"C1\", \"C2\", \"C3\", \"C4\", \"C5\", \"C6\", \"C7\", \"C8\", \"C9\", \"CA\", \"CB\", \"CC\", \"CD\", \"CE\", \"CF\",\n\t\t\t\"D0\", \"D1\", \"D2\", \"D3\", \"D4\", \"D5\", \"D6\", \"D7\", \"D8\", \"D9\", \"DA\", \"DB\", \"DC\", \"DD\", \"DE\", \"DF\",\n\t\t\t\"E0\", \"E1\", \"E2\", \"E3\", \"E4\", \"E5\", \"E6\", \"E7\", \"E8\", \"E9\", \"EA\", \"EB\", \"EC\", \"ED\", \"EE\", \"EF\",\n\t\t\t\"F0\", \"F1\", \"F2\", \"F3\", \"F4\", \"F5\", \"F6\", \"F7\", \"F8\", \"F9\", \"FA\", \"FB\", \"FC\", \"FD\", \"FE\", \"FF\"\n\t};\n\n\t/**\n\t * @param b\n\t * \t\tByte value.\n\t *\n\t * @return Two-char string representation of byte as a hex number.\n\t */\n\t@Nonnull\n\tpublic static String strFormat00(byte b) {\n\t\treturn strFormat00(Byte.toUnsignedInt(b));\n\t}\n\n\t/**\n\t * @param i\n\t * \t\tInt value, assumed to fit into a {@code byte}.\n\t *\n\t * @return Two-char string representation of byte as a hex number.\n\t */\n\t@Nonnull\n\tprivate static String strFormat00(int i) {\n\t\tString hex = byteToString[i];\n\t\treturn StringUtil.fillLeft(2, \"0\", hex);\n\t}\n\n\t/**\n\t * @param length\n\t * \t\tDesired length of output.\n\t * @param value\n\t * \t\tValue to format.\n\t *\n\t * @return String representation of value as a hex number.\n\t */\n\t@Nonnull\n\tpublic static String strFormat(int length, int value) {\n\t\treturn StringUtil.fillLeft(length, \"0\", Integer.toHexString(value).toUpperCase());\n\t}\n\n\t/**\n\t * @param b\n\t * \t\tByte value.\n\t *\n\t * @return Single char string of the byte interpreted as ASCII. Non-renderable content will be {@link #INV_DISPLAY}.\n\t */\n\t@Nonnull\n\tpublic static String strAscii(byte b) {\n\t\treturn String.valueOf(charAscii(b));\n\t}\n\n\t/**\n\t * @param b\n\t * \t\tByte value.\n\t *\n\t * @return Single char of the byte interpreted as ASCII. Non-renderable content will be {@link #INV_DISPLAY}.\n\t */\n\tpublic static char charAscii(byte b) {\n\t\tint codePoint = Byte.toUnsignedInt(b);\n\t\tif (codePoint >= byteToChar.length)\n\t\t\treturn INV_DISPLAY;\n\t\tchar c = byteToChar[codePoint];\n\t\tif (c == INV)\n\t\t\treturn INV_DISPLAY;\n\t\treturn c;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/hex/cell/EditableAsciiCell.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary.hex.cell;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.scene.control.TextFormatter;\nimport software.coley.recaf.ui.pane.editing.binary.hex.ops.HexOperations;\nimport software.coley.recaf.ui.pane.editing.binary.hex.HexUtil;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Objects;\n\n/**\n * Hex cell for displaying a single ascii char.\n *\n * @author Matt Coley\n */\npublic class EditableAsciiCell extends HexCellBase implements HexCell {\n\tprivate final boolean canMarkDirty;\n\tprivate final BooleanProperty dirty = new SimpleBooleanProperty();\n\n\tpublic EditableAsciiCell(@Nonnull HexOperations ops, int offset, byte b) {\n\t\tsuper(ops, offset, b);\n\n\t\t// Despite being 'final' some code in the upstream class will reference it before we initialize it to 'true' here.\n\t\t// Thus, anything before that point should not be allowed to update the 'dirty' property as its under init.\n\t\tcanMarkDirty = true;\n\t}\n\n\t@Override\n\tprotected int maxLength() {\n\t\treturn 1;\n\t}\n\n\t@Nullable\n\t@Override\n\tprotected TextFormatter.Change processTextChange(@Nonnull TextFormatter.Change change, @Nonnull String changeText) {\n\t\t// Mark this cell as being dirty. We'll use this in byte mapping later.\n\t\tif (canMarkDirty) dirty.set(true);\n\n\t\t// No manipulation or additional work needed for this implementation\n\t\treturn change;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected String processEditorText(@Nonnull String text) {\n\t\t// No processing\n\t\treturn text;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected Byte2StringRepresentation mapper() {\n\t\treturn HexUtil::strAscii;\n\t}\n\n\t@Override\n\tprotected byte toByteValue() {\n\t\t// If we're dirty (user input has been made) then we compute the byte value bases on the ascii char value.\n\t\tif (dirty != null && dirty.get()) {\n\t\t\tString text = textProperty.get();\n\t\t\tif (text.isEmpty()) return 0;\n\t\t\treturn text.getBytes(StandardCharsets.US_ASCII)[0];\n\t\t}\n\n\t\t// Delegate to existing value since there is no change.\n\t\treturn ops.currentAccess().getByte(offset());\n\t}\n\n\t@Override\n\tprotected boolean isDataStateChanged() {\n\t\tString original = mapper().map(ops.originalAccess().getByte(offset()));\n\t\tString current = textProperty.get();\n\t\treturn !Objects.equals(original, current);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/hex/cell/EditableHexCell.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary.hex.cell;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.control.TextFormatter;\nimport software.coley.recaf.ui.pane.editing.binary.hex.HexUtil;\nimport software.coley.recaf.ui.pane.editing.binary.hex.ops.HexOperations;\n\n/**\n * Hex cell for displaying a single byte as a hex string.\n *\n * @author Matt Coley\n */\npublic class EditableHexCell extends HexCellBase implements HexCell {\n\tpublic EditableHexCell(@Nonnull HexOperations ops, int offset, byte b) {\n\t\tsuper(ops, offset, b);\n\t}\n\n\t@Override\n\tprotected int maxLength() {\n\t\t// Each hex label uses two characters to represent the byte value display.\n\t\treturn 2;\n\t}\n\n\t@Nullable\n\t@Override\n\tprotected TextFormatter.Change processTextChange(@Nonnull TextFormatter.Change change, @Nonnull String changeText) {\n\t\ttry {\n\t\t\t// Must be hex text\n\t\t\tInteger.parseInt(changeText, 16);\n\t\t\treturn change;\n\t\t} catch (NumberFormatException ex) {\n\t\t\t// Not hex, discard the change.\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected String processEditorText(@Nonnull String text) {\n\t\t// We'll upper-case the editor's text so everything looks consistent when it gets applied to the display label.\n\t\treturn text.toUpperCase();\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected Byte2StringRepresentation mapper() {\n\t\t// byte --> 2 char hex string\n\t\treturn HexUtil::strFormat00;\n\t}\n\n\t@Override\n\tpublic byte toByteValue() {\n\t\t// Parse hex text, which should always be valid due to the editor's applied text-formatter\n\t\t// and its delegation to our implementation of 'processTextChange'.\n\t\tString text = textProperty.get();\n\t\tif (text.isEmpty()) return 0;\n\t\treturn (byte) Integer.parseInt(text, 16);\n\t}\n\n\t@Override\n\tprotected boolean isDataStateChanged() {\n\t\treturn ops.originalAccess().getByte(offset()) != toByteValue();\n\t}\n\n\t@Override\n\tprotected void preProcessCommit() {\n\t\t// Fill in remaining spaces if ending before text input is full.\n\t\twhile (textProperty.length().get() < 2)\n\t\t\ttextProperty.set(textProperty.get() + \"0\");\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/hex/cell/HexCell.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary.hex.cell;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.Node;\nimport javafx.scene.input.KeyCode;\n\n/**\n * Outline of a hex-cell which displays the {@code byte} of data at a given {@link #offset()}.\n *\n * @author Matt Coley\n */\npublic interface HexCell {\n\t/**\n\t * @return Cell display node.\n\t */\n\t@Nonnull\n\tNode node();\n\n\t/**\n\t * @return Offset into the data this cell represents.\n\t */\n\tint offset();\n\n\t/**\n\t * Handle selection gain event.\n\t */\n\tvoid onSelectionGained();\n\n\t/**\n\t * Handle selection lost event.\n\t */\n\tvoid onSelectionLost();\n\n\t/**\n\t * @return {@code true} when the cell is in edit mode.\n\t * {@code false} when the cell is in read-only display mode.\n\t */\n\tboolean isEditing();\n\n\t/**\n\t * Sets the cell to edit mode.\n\t *\n\t * @see #isEditing()\n\t */\n\tvoid beginEdit();\n\n\t/**\n\t * Sets the cell to read-only display mode, committing any edits made if desired.\n\t *\n\t * @param commit\n\t *        {@code true} to commit changes to the data.\n\t *        {@code false} to cancel any edits made in this cell.\n\t */\n\tvoid endEdit(boolean commit);\n\n\t/**\n\t * Used to accept overflow inputs from adjacent cells.\n\t * When typing in one cell is done, the next key press is sent to the next cell <i>(if there is one)</i>.\n\t *\n\t * @param code\n\t * \t\tKey code to handle as user input.\n\t */\n\tvoid handleKeyCode(@Nonnull KeyCode code);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/hex/cell/HexCellBase.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary.hex.cell;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.property.IntegerProperty;\nimport javafx.beans.property.SimpleIntegerProperty;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.css.PseudoClass;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TextField;\nimport javafx.scene.control.TextFormatter;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.BorderPane;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.pane.editing.binary.hex.ops.HexOperations;\nimport software.coley.recaf.util.NodeEvents;\n\n/**\n * Base hex cell implementation.\n *\n * @author Matt Coley\n * @see EditableHexCell For displaying hex codes for bytes.\n * @see EditableAsciiCell For displaying ascii chars for bytes.\n */\npublic abstract class HexCellBase extends BorderPane implements HexCell {\n\tprotected static final PseudoClass PSEUDO_ZERO = PseudoClass.getPseudoClass(\"zero\");\n\tprotected static final PseudoClass PSEUDO_SELECTED = PseudoClass.getPseudoClass(\"selected\");\n\tprotected static final PseudoClass PSEUDO_EDITED = PseudoClass.getPseudoClass(\"edited\");\n\tprotected final StringProperty textProperty = new SimpleStringProperty();\n\tprotected final StringProperty cachedTextProperty = new SimpleStringProperty();\n\tprotected final IntegerProperty offsetProperty = new SimpleIntegerProperty();\n\tprotected final Label label = new BoundLabel(textProperty);\n\tprotected final TextField editor = new TextField();\n\tprotected final HexOperations ops;\n\n\tpublic HexCellBase(@Nonnull HexOperations ops, int offset, byte b) {\n\t\tthis.ops = ops;\n\n\t\t// Only allow the editor to contain hex text.\n\t\tint maxLength = maxLength();\n\t\teditor.setPrefColumnCount(maxLength);\n\t\teditor.setTextFormatter(new TextFormatter<>(c -> {\n\t\t\tString newText = c.getControlNewText();\n\n\t\t\t// Allow blank content\n\t\t\tif (newText.isEmpty())\n\t\t\t\treturn c;\n\n\t\t\t// Don't allow more than two chars worth of content (one byte filtering)\n\t\t\tif (newText.length() > maxLength) {\n\t\t\t\tendEdit(true);\n\n\t\t\t\t// Move to the next editor, and if that succeeds pass the attempted typed character to the next editor.\n\t\t\t\tops.navigation().selectNext();\n\t\t\t\tif (ops.navigation().selectionOffset() != offset) {\n\t\t\t\t\tops.engageCurrent();\n\n\t\t\t\t\t// Pass the key to the next editor.\n\t\t\t\t\tKeyCode code = NodeEvents.getKeycode(newText.charAt(maxLength));\n\t\t\t\t\tif (code != null) ops.sendKeyToCurrentEngaged(code);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn processTextChange(c, newText);\n\t\t}));\n\n\t\t// When the editor updates, set the main property value.\n\t\teditor.textProperty().addListener((ob, old, cur) -> textProperty.setValue(processEditorText(cur)));\n\n\t\t// Setup forwarding to the primary hex editor navigation key listener.\n\t\teditor.setOnKeyPressed(e -> {\n\t\t\t// Some keys should not delegate to the primary key listener, such as navigation and enter.\n\t\t\tKeyCode code = e.getCode();\n\t\t\tif (code.isArrowKey() || code.isNavigationKey() || code == KeyCode.ENTER)\n\t\t\t\treturn;\n\n\t\t\t// The rest can be handled properly by the main editor's key listener.\n\t\t\tops.keyListener().handle(e);\n\t\t});\n\n\n\t\t// When the main property updates, set the editor's text since it's not bound.\n\t\ttextProperty.addListener((ob, old, cur) -> {\n\t\t\tif (!editor.getText().equals(cur))\n\t\t\t\teditor.setText(cur);\n\t\t\tlabel.pseudoClassStateChanged(PSEUDO_ZERO, toByteValue() == 0);\n\t\t});\n\n\t\t// Initially show the read-only label.\n\t\tsetCenter(label);\n\n\t\t// Assign properties last so property listeners configured above can act on them.\n\t\toffsetProperty.setValue(offset);\n\t\toffsetProperty.addListener((ob, old, cur) -> refreshText(cur.intValue()));\n\n\t\t// Update the initial text to model the hexadecimal representation of the byte.\n\t\ttextProperty.setValue(mapper().map(b));\n\t\tcachedTextProperty.set(textProperty.get());\n\n\t\t// Initialize edited pseudo-state.\n\t\tupdateEditedPseudoState();\n\t}\n\n\t/**\n\t * @return Max length of this cell. Should be implemented as a constant value.\n\t */\n\tprotected abstract int maxLength();\n\n\t/**\n\t * @param change\n\t * \t\tChange to accept or deny.\n\t * @param changeText\n\t * \t\tText of the cell if the change were accepted.\n\t *\n\t * @return {@code change} to accept the change, or {@code null} to deny it.\n\t */\n\t@Nullable\n\tprotected abstract TextFormatter.Change processTextChange(@Nonnull TextFormatter.Change change, @Nonnull String changeText);\n\n\t/**\n\t * @param text\n\t * \t\tCurrent text of the {@link #editor}.\n\t *\n\t * @return Text to apply to the {@link #label} based on the editor's text.\n\t */\n\t@Nonnull\n\tprotected abstract String processEditorText(@Nonnull String text);\n\n\t/**\n\t * @return Mapper of {@code byte} values to {@code String} representations.\n\t */\n\t@Nonnull\n\tprotected abstract Byte2StringRepresentation mapper();\n\n\t/**\n\t * @return Byte value of the current cell based on the current text value.\n\t */\n\tprotected abstract byte toByteValue();\n\n\t/**\n\t * @return {@code true} when the data of this cell does not reflect the original state.\n\t */\n\tprotected abstract boolean isDataStateChanged();\n\n\t/**\n\t * Called by {@link #endEdit(boolean)} before a commit is made, allowing processing of the {@link #textProperty}\n\t * before that value is used to update the backing hex content.\n\t */\n\tprotected void preProcessCommit() {\n\t\t// no-op by default\n\t}\n\n\t/**\n\t * Updates the cell to have the {@code :edited} pseudo-state if the current value does not match the orignal value\n\t * of the hex content.\n\t * <p>\n\t * Currently due to the way the hex rows are refreshed after edit commits are made, this only is called during\n\t * the initial construction of the cell.\n\t */\n\tprotected void updateEditedPseudoState() {\n\t\t// Change the label text color if it differs from the original content\n\t\tlabel.pseudoClassStateChanged(PSEUDO_EDITED, isDataStateChanged());\n\t}\n\n\t/**\n\t * Updates the offset value. If the value is not different,\n\t * we refresh the display in case the backing data has changed.\n\t *\n\t * @param offset\n\t * \t\tNew offset value.\n\t */\n\tpublic void updateOffset(int offset) {\n\t\tif (offset == offsetProperty.get())\n\t\t\trefreshText(offset);\n\t\telse\n\t\t\toffsetProperty.set(offset);\n\n\t\t// Refresh the edited pseudo-state since the data could have been changed from the original.\n\t\tupdateEditedPseudoState();\n\t}\n\n\t/**\n\t * Refresh the current text property.\n\t *\n\t * @param offset\n\t * \t\tOffset to pull from.\n\t */\n\tprivate void refreshText(int offset) {\n\t\ttextProperty.set(mapper().map(ops.currentAccess().getByte(offset)));\n\t}\n\n\t/**\n\t * @return Property of the offset into the data this cell represents.\n\t */\n\t@Nonnull\n\tpublic IntegerProperty offsetProperty() {\n\t\treturn offsetProperty;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Node node() {\n\t\treturn this;\n\t}\n\n\t@Override\n\tpublic int offset() {\n\t\treturn offsetProperty.get();\n\t}\n\n\t@Override\n\tpublic void onSelectionGained() {\n\t\tlabel.pseudoClassStateChanged(PSEUDO_SELECTED, true);\n\t}\n\n\t@Override\n\tpublic void onSelectionLost() {\n\t\tlabel.pseudoClassStateChanged(PSEUDO_SELECTED, false);\n\t\tif (isEditing())\n\t\t\tendEdit(true);\n\t}\n\n\t@Override\n\tpublic boolean isEditing() {\n\t\treturn getCenter() == editor;\n\t}\n\n\t@Override\n\tpublic void beginEdit() {\n\t\tcachedTextProperty.set(textProperty.get());\n\t\tsetCenter(editor);\n\t\teditor.requestFocus();\n\t\teditor.selectAll();\n\t}\n\n\t@Override\n\tpublic void endEdit(boolean commit) {\n\t\tif (!commit) {\n\t\t\t// Set text to what it was before the edit began.\n\t\t\ttextProperty.set(cachedTextProperty.get());\n\t\t} else {\n\t\t\tpreProcessCommit();\n\n\t\t\t// Commit the change.\n\t\t\tops.currentAccess().setByte(offset(), toByteValue());\n\n\t\t\t// Refresh the display (so the hex column gets updated)\n\t\t\tops.refreshDisplay(offset(), false);\n\t\t}\n\t\tsetCenter(label);\n\t}\n\n\t@Override\n\tpublic void handleKeyCode(@Nonnull KeyCode code) {\n\t\tif (isEditing()) {\n\t\t\ttextProperty.set(code.getChar());\n\t\t\teditor.positionCaret(textProperty.length().get());\n\t\t}\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"data[\" + offset() + \"] = \" + textProperty.get() + \" / '\" + (char) toByteValue() + \"'\";\n\t}\n\n\t/**\n\t * Map a {@code byte} to {@link String}.\n\t */\n\tprotected interface Byte2StringRepresentation {\n\t\t/**\n\t\t * @param b\n\t\t * \t\tByte input.\n\t\t *\n\t\t * @return String representation of byte.\n\t\t */\n\t\t@Nonnull\n\t\tString map(byte b);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/hex/cell/HexRow.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary.hex.cell;\n\nimport atlantafx.base.controls.Spacer;\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.property.IntegerProperty;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.layout.HBox;\nimport org.fxmisc.flowless.Cell;\nimport org.fxmisc.flowless.VirtualFlow;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.ui.pane.editing.binary.hex.HexConfig;\nimport software.coley.recaf.ui.pane.editing.binary.hex.HexUtil;\nimport software.coley.recaf.ui.pane.editing.binary.hex.ops.HexAccess;\nimport software.coley.recaf.ui.pane.editing.binary.hex.ops.HexOperations;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Consumer;\n\n/**\n * Cell for {@link VirtualFlow} to draw a row of bytes.\n *\n * @author Matt Coley\n */\npublic class HexRow implements Cell<Integer, Node> {\n\tprotected final HBox layout = new HBox();\n\tprotected final HexConfig config;\n\tprotected final IntegerProperty rowCount;\n\tprotected final HexOperations ops;\n\tprotected int baseOffset;\n\tprotected int lastConfigHash;\n\n\tpublic HexRow(@Nonnull HexConfig config, @Nonnull IntegerProperty rowCount, @Nonnull HexOperations ops, int row) {\n\t\tthis.config = config;\n\t\tthis.rowCount = rowCount;\n\t\tthis.ops = ops;\n\t\tthis.baseOffset = row * config.getRowLength().getValue();\n\n\t\tlayout.setMouseTransparent(true);\n\t\tlayout.setAlignment(Pos.CENTER_LEFT);\n\t\tlayout.setSpacing(4);\n\n\t\tbuildFreshLayout();\n\t}\n\n\t/**\n\t * Re-populate the layout.\n\t */\n\tpublic void redraw() {\n\t\tint configHash = config.hashCode();\n\t\tif (lastConfigHash != configHash) {\n\t\t\treset();\n\t\t} else {\n\t\t\trefreshLayout();\n\t\t}\n\t}\n\n\t@Override\n\tpublic Node getNode() {\n\t\treturn layout;\n\t}\n\n\t@Override\n\tpublic boolean isReusable() {\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic void updateItem(Integer row) {\n\t\tint rowLength = config.getRowLength().getValue();\n\t\tint oldBaseOffset = this.baseOffset;\n\t\tint newBaseOffset = row * rowLength;\n\t\tif (oldBaseOffset != newBaseOffset) {\n\t\t\tthis.baseOffset = newBaseOffset;\n\t\t\trefreshLayout();\n\t\t}\n\t}\n\n\t@Override\n\tpublic void reset() {\n\t\t// We only need to clear the layout when the hash changes.\n\t\tint configHash = config.hashCode();\n\t\tif (lastConfigHash != configHash) {\n\t\t\tlayout.getChildren().clear();\n\t\t\tbuildFreshLayout();\n\t\t}\n\t}\n\n\t/**\n\t * Updates the existing {@link HexCellBase} items in this row.\n\t * <br>\n\t * This assumes the {@link #baseOffset} does not represent the header, or the last row in the file\n\t * which are special cases and not supported.\n\t */\n\tprivate void refreshLayout() {\n\t\tif (baseOffset < 0)\n\t\t\tthrow new IllegalStateException(\"Hex row refreshed in unsupported context\");\n\n\t\tObservableList<Node> children = layout.getChildren();\n\t\tint rowLength = config.getRowLength().getValue();\n\t\tint rowSplit = config.getRowSplitInterval().getValue();\n\t\tint addressWidth = Integer.toHexString(rowCount.get() * rowLength).length();\n\t\tboolean showAddress = config.getShowAddress().getValue();\n\t\tboolean showAscii = config.getShowAscii().getValue();\n\n\t\t// Update the label value.\n\t\tint spaces = 0;\n\t\tif (showAddress) {\n\t\t\tLabel lblAddress = (Label) children.getFirst();\n\t\t\tlblAddress.setText(StringUtil.fillLeft(8, \" \", HexUtil.strFormat(addressWidth, baseOffset) + \":\"));\n\t\t\tspaces++;\n\t\t}\n\n\t\t// Update the hex values\n\t\tHexAccess read = ops.currentAccess();\n\t\tint asciiChildOffset = 0;\n\t\tfor (int i = 0; i < rowLength; i++) {\n\t\t\t// Account for spacer padding\n\t\t\tif (i % rowSplit == 0 && i < rowLength - 1)\n\t\t\t\tspaces++;\n\t\t\tint childIndex = i + spaces;\n\t\t\tint offset = baseOffset + i;\n\t\t\tboolean outOfBounds = !read.isInBounds(offset);\n\n\t\t\tHexCellBase valueLabel = (HexCellBase) children.get(childIndex);\n\t\t\tvalueLabel.setDisable(outOfBounds);\n\t\t\tvalueLabel.setOpacity(outOfBounds ? 0.1 : 1);\n\t\t\tvalueLabel.updateOffset(offset);\n\n\t\t\t// Record the last child index as the beginning of where to look for ascii display controls.\n\t\t\tasciiChildOffset = childIndex;\n\t\t}\n\t\tif (showAscii) {\n\t\t\t// Offset by the last row-split padding (not counted above), and the first padding before the ascii begins.\n\t\t\tasciiChildOffset += 2;\n\n\t\t\t// Update ascii values.\n\t\t\tfor (int i = 0; i < rowLength; i++) {\n\t\t\t\tint childIndex = asciiChildOffset + i;\n\t\t\t\tint offset = baseOffset + i;\n\t\t\t\tboolean outOfBounds = !read.isInBounds(offset);\n\n\t\t\t\tHexCellBase asciiLabel = (HexCellBase) children.get(childIndex);\n\t\t\t\tasciiLabel.setDisable(outOfBounds);\n\t\t\t\tasciiLabel.setOpacity(outOfBounds ? 0.1 : 1);\n\t\t\t\tasciiLabel.updateOffset(offset);\n\t\t\t}\n\t\t}\n\n\t\t// Update selection so that the newly generated row will display the current selection\n\t\t// if it appears on this row.\n\t\tupdateSelection(ops.navigation().selectionOffset());\n\t}\n\n\tprotected void buildFreshLayout() {\n\t\tif (baseOffset < 0)\n\t\t\tthrow new IllegalStateException(\"Hex row refreshed in unsupported context\");\n\n\t\tlastConfigHash = config.hashCode();\n\t\tint rowLength = config.getRowLength().getValue();\n\t\tint rowSplit = config.getRowSplitInterval().getValue();\n\t\tint addressWidth = Integer.toHexString(rowCount.get() * rowLength).length();\n\t\tboolean showAddress = config.getShowAddress().getValue();\n\t\tboolean showAscii = config.getShowAscii().getValue();\n\n\t\tObservableList<Node> children = layout.getChildren();\n\n\t\tLabel lblAddress;\n\t\tList<Node> contentHexLabels = new ArrayList<>();\n\t\tList<Node> contentAsciiLabels = new ArrayList<>();\n\t\tHexAccess read = ops.currentAccess();\n\n\t\tlblAddress = new Label(StringUtil.fillLeft(8, \" \", HexUtil.strFormat(addressWidth, baseOffset) + \":\"));\n\t\tfor (int i = 0; i < rowLength; i++) {\n\t\t\tif (i % rowSplit == 0 && i < rowLength - 1)\n\t\t\t\tcontentHexLabels.add(new SmallSpacer());\n\t\t\tint offset = baseOffset + i;\n\t\t\tbyte b = read.getByte(offset);\n\t\t\tboolean outOfBounds = !read.isInBounds(offset);\n\n\t\t\t// Hex labels\n\t\t\tHexCellBase valueLabel = new EditableHexCell(ops, offset, b);\n\t\t\tvalueLabel.setDisable(outOfBounds);\n\t\t\tvalueLabel.setOpacity(outOfBounds ? 0.1 : 1);\n\t\t\tcontentHexLabels.add(valueLabel);\n\n\t\t\t// Ascii labels\n\t\t\tif (showAscii) {\n\t\t\t\tchar c = HexUtil.charAscii(b);\n\t\t\t\tHexCellBase asciiLabel = new EditableAsciiCell(ops, offset, b);\n\t\t\t\tasciiLabel.setDisable(outOfBounds);\n\t\t\t\tasciiLabel.setOpacity(outOfBounds ? 0.1 : 1);\n\t\t\t\tcontentAsciiLabels.add(asciiLabel);\n\t\t\t}\n\t\t}\n\t\tsetChildren(lblAddress, contentHexLabels, contentAsciiLabels);\n\n\t\t// Update selection so that the newly generated row will display the current selection\n\t\t// if it appears on this row.\n\t\tupdateSelection(ops.navigation().selectionOffset());\n\t}\n\n\tprotected void setChildren(@Nonnull Label lblAddress,\n\t                           @Nonnull List<Node> contentHexLabels,\n\t                           @Nonnull List<Node> contentAsciiLabels) {\n\t\tboolean showAddress = config.getShowAddress().getValue();\n\t\tboolean showAscii = config.getShowAscii().getValue();\n\n\t\tList<Node> nodes = new ArrayList<>(contentHexLabels.size() + 2);\n\t\tif (showAddress)\n\t\t\tnodes.add(lblAddress);\n\t\tnodes.addAll(contentHexLabels);\n\t\tif (showAscii) {\n\t\t\tnodes.add(new SmallSpacer());\n\t\t\tnodes.addAll(contentAsciiLabels);\n\t\t}\n\t\tnodes.add(new Spacer()); // Added at the end to occupy space to the right\n\t\tlayout.getChildren().setAll(nodes);\n\t}\n\n\t/**\n\t * @return {@code true} when this row contains the current selected offset.\n\t */\n\tpublic boolean isRowSelected() {\n\t\treturn layout.getChildren().stream()\n\t\t\t\t.anyMatch(child -> child instanceof HexCell cell\n\t\t\t\t\t\t&& cell.offset() == ops.navigation().selectionOffset());\n\t}\n\n\t/**\n\t * Notifies cells of selection updates, primarily to refresh visual cues.\n\t *\n\t * @param offset\n\t * \t\tTarget selection offset.\n\t */\n\tpublic void updateSelection(int offset) {\n\t\tUnchecked.checkedForEach(layout.getChildren(), child -> {\n\t\t\tboolean hexColumn = ops.navigation().isHexColumnSelected();\n\t\t\tif (child instanceof EditableHexCell cell) {\n\t\t\t\tboolean match = hexColumn && cell.offset() == offset;\n\t\t\t\tif (match) cell.onSelectionGained();\n\t\t\t\telse cell.onSelectionLost();\n\t\t\t} else if (child instanceof EditableAsciiCell cell) {\n\t\t\t\tboolean match = !hexColumn && cell.offset() == offset;\n\t\t\t\tif (match) cell.onSelectionGained();\n\t\t\t\telse cell.onSelectionLost();\n\t\t\t}\n\t\t}, (cell, error) -> {});\n\t}\n\n\t/**\n\t * @param offset\n\t * \t\tOffset in the data to check.\n\t *\n\t * @return {@code true} if this row contains the offset.\n\t */\n\tpublic boolean hasOffset(int offset) {\n\t\treturn offset >= baseOffset && offset <= baseOffset + config.getRowLength().getValue();\n\t}\n\n\t/**\n\t * Toggles editing in the cell at the given offset.\n\t *\n\t * @param offset\n\t * \t\tTarget offset to toggle editing on.\n\t * @param initiateEdit\n\t *        {@code true} to trigger the target cell at the offset to begin editing or complete a current edit.\n\t *        {@code false} to cancel editing.\n\t */\n\tpublic void engage(int offset, boolean initiateEdit) {\n\t\tConsumer<HexCellBase> action = cell -> {\n\t\t\tboolean match = cell.offset() == offset;\n\t\t\tif (match) {\n\t\t\t\tif (initiateEdit && cell.isEditing()) {\n\t\t\t\t\t// Complete the current edit\n\t\t\t\t\tcell.endEdit(true);\n\t\t\t\t} else if (initiateEdit) {\n\t\t\t\t\t// Initiate a new edit\n\t\t\t\t\tcell.beginEdit();\n\t\t\t\t} else {\n\t\t\t\t\t// Cancel the current edit\n\t\t\t\t\tcell.endEdit(false);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcell.onSelectionLost();\n\t\t\t}\n\t\t};\n\t\tUnchecked.checkedForEach(layout.getChildren(), child -> {\n\t\t\tboolean hexColumn = ops.navigation().isHexColumnSelected();\n\t\t\tif (hexColumn && child instanceof EditableHexCell cell) {\n\t\t\t\taction.accept(cell);\n\t\t\t} else if (!hexColumn && child instanceof EditableAsciiCell cell) {\n\t\t\t\taction.accept(cell);\n\t\t\t}\n\t\t}, (cell, error) -> {});\n\t}\n\n\t/**\n\t * Finds the cell matching the given offset and delegates key handling for the given key-code to it.\n\t *\n\t * @param offset\n\t * \t\tTarget offset to send key to.\n\t * @param code\n\t * \t\tKey to send.\n\t */\n\tpublic void sendKeyToCurrentEngaged(int offset, @Nonnull KeyCode code) {\n\t\tConsumer<HexCellBase> action = cell -> {\n\t\t\tboolean match = cell.offset() == offset;\n\t\t\tif (match && cell.isEditing()) {\n\t\t\t\tcell.handleKeyCode(code);\n\t\t\t}\n\t\t};\n\t\tUnchecked.checkedForEach(layout.getChildren(), child -> {\n\t\t\tboolean hexColumn = ops.navigation().isHexColumnSelected();\n\t\t\tif (hexColumn && child instanceof EditableHexCell cell) {\n\t\t\t\taction.accept(cell);\n\t\t\t} else if (!hexColumn && child instanceof EditableAsciiCell cell) {\n\t\t\t\taction.accept(cell);\n\t\t\t}\n\t\t}, (cell, error) -> {});\n\t}\n\n\t/**\n\t * @param x\n\t * \t\tLayout x position.\n\t * @param y\n\t * \t\tLayout y position.\n\t *\n\t * @return Offset into the data for the closest cell to the given coordinates.\n\t */\n\tpublic int pickOffsetAtPosition(double x, double y) {\n\t\tHexCell closestChild = null;\n\t\tdouble closestDistance = Integer.MAX_VALUE;\n\t\tint offset = -1;\n\t\tfor (Node child : layout.getChildren()) {\n\t\t\tif (child instanceof HexCell cell) {\n\t\t\t\tint bufferZone = 2;\n\t\t\t\tdouble centerX = cell.node().getBoundsInParent().getCenterX();\n\t\t\t\tdouble distance = Math.abs(centerX - x);\n\t\t\t\tif (distance < closestDistance) {\n\t\t\t\t\tclosestDistance = distance;\n\t\t\t\t\tclosestChild = cell;\n\t\t\t\t\toffset = cell.offset();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Toggle navigation columns if the clicked column is not the current column.\n\t\tif (ops.navigation().isHexColumnSelected() != closestChild instanceof EditableHexCell)\n\t\t\tops.navigation().switchColumns();\n\n\t\treturn offset;\n\t}\n\n\t/**\n\t * Fixed size spacer to put between columns.\n\t */\n\tprotected static class SmallSpacer extends Spacer {\n\t\tprotected SmallSpacer() {\n\t\t\tsetMaxWidth(12);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/hex/cell/HexRowHeader.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary.hex.cell;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.property.IntegerProperty;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.input.KeyCode;\nimport software.coley.recaf.ui.pane.editing.binary.hex.HexConfig;\nimport software.coley.recaf.ui.pane.editing.binary.hex.HexUtil;\nimport software.coley.recaf.ui.pane.editing.binary.hex.ops.HexOperations;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Extended header to display the hex editor header row.\n *\n * @author Matt Coley\n */\npublic class HexRowHeader extends HexRow {\n\tpublic HexRowHeader(@Nonnull HexConfig config, @Nonnull IntegerProperty rowCount, @Nonnull HexOperations ops) {\n\t\tsuper(config, rowCount, ops, -1);\n\t\tlayout.getStyleClass().add(\"header\");\n\t}\n\n\t@Override\n\tprotected void buildFreshLayout() {\n\t\tint rowLength = config.getRowLength().getValue();\n\t\tint rowSplit = config.getRowSplitInterval().getValue();\n\t\tint addressWidth = Integer.toHexString(rowCount.get() * rowLength).length();\n\t\tboolean showAddress = config.getShowAddress().getValue();\n\t\tboolean showAscii = config.getShowAscii().getValue();\n\t\tList<Node> contentHexLabels = new ArrayList<>();\n\t\tLabel lblAddress = new Label(StringUtil.fillLeft(addressWidth + 1, \" \", \"Address:\"));\n\t\tfor (int i = 0; i < rowLength; i++) {\n\t\t\tif (i % rowSplit == 0 && i < rowLength - 1)\n\t\t\t\tcontentHexLabels.add(new SmallSpacer());\n\t\t\tLabel columnOffset = new Label(HexUtil.strFormat00((byte) i));\n\t\t\tcontentHexLabels.add(columnOffset);\n\t\t}\n\t\tList<Node> contentAsciiLabels = new ArrayList<>(Arrays.asList(new Label(\"A\"), new Label(\"S\"), new Label(\"C\"), new Label(\"I\"), new Label(\"I\")));\n\t\tfor (int i = 5; i < rowLength; i++) {\n\t\t\tLabel filler = new Label(\".\");\n\t\t\tfiller.setOpacity(0.2);\n\t\t\tcontentAsciiLabels.add(filler);\n\t\t}\n\t\tsetChildren(lblAddress, contentHexLabels, contentAsciiLabels);\n\t}\n\n\t@Override\n\tpublic boolean isReusable() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic boolean isRowSelected() {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void updateSelection(int offset) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic boolean hasOffset(int offset) {\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic void engage(int offset, boolean initiateEdit) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void sendKeyToCurrentEngaged(int offset, @Nonnull KeyCode code) {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic int pickOffsetAtPosition(double x, double y) {\n\t\treturn -1;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/hex/ops/HexAccess.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary.hex.ops;\n\n/**\n * Outlines the data access model for the hex editor.\n *\n * @author Matt Coley\n */\npublic interface HexAccess {\n\t/**\n\t * @return Data to operate on.\n\t */\n\tbyte[] getData();\n\n\t/**\n\t * @return Length of the data.\n\t */\n\tdefault int length() {\n\t\treturn getData().length;\n\t}\n\n\t/**\n\t * @param offset\n\t * \t\tOffset into the data.\n\t *\n\t * @return Value at offset. Any out of bounds value is mapped to {@code 0}.\n\t */\n\tdefault byte getByte(int offset) {\n\t\tif (isInBounds(offset))\n\t\t\treturn getData()[offset];\n\t\treturn 0;\n\t}\n\n\t/**\n\t * @param offset\n\t * \t\tOffset into the data.\n\t *\n\t * @return {@code true} when the offset is within the data size.\n\t */\n\tdefault boolean isInBounds(int offset) {\n\t\treturn offset >= 0 && offset < length();\n\t}\n\n\t/**\n\t * Update the {@link #getData() data} model with the given value at the given offset.\n\t * Does nothing if the offset is outside the data bounds.\n\t *\n\t * @param offset\n\t * \t\tOffset into the data.\n\t * @param b\n\t * \t\tValue to set.\n\t */\n\tdefault void setByte(int offset, byte b) {\n\t\tif (isInBounds(offset)) getData()[offset] = b;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/hex/ops/HexNavigation.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary.hex.ops;\n\n/**\n * Outlines UI navigation for the hex editor.\n *\n * @author Matt Coley\n */\npublic interface HexNavigation {\n\t/**\n\t * @return Current offset in the data that is selected.\n\t */\n\tint selectionOffset();\n\n\t/**\n\t * The hex editor has two main columns for editing data. The same offset is used for both, but determining\n\t * which one is targeted in the UI is determined here.\n\t *\n\t * @return {@code true} if the selection is in a hex column.\n\t * {@code false} if the selection is in an ascii column.\n\t *\n\t * @see #switchColumns() For changing which column is selected.\n\t */\n\tboolean isHexColumnSelected();\n\n\t/**\n\t * Switches which column is selected, between hex and ascii.\n\t */\n\tvoid switchColumns();\n\n\t/**\n\t * Selects the given offset in the data. The value is clamped to fit if it is out of bounds.\n\t *\n\t * @param offset\n\t * \t\tOffset in the data to select.\n\t */\n\tvoid select(int offset);\n\n\t/**\n\t * Move selection by {@code +1}.\n\t */\n\tvoid selectNext();\n\n\t/**\n\t * Move selection by {@code -1}.\n\t */\n\tvoid selectPrevious();\n\n\t/**\n\t * Move selection by {@code +R} where:\n\t * <ul>\n\t *     <li>{@code R} is the number of values shown per row.</li>\n\t * </ul>\n\t */\n\tvoid selectDown();\n\n\t/**\n\t * Move selection by {@code -R} where:\n\t * <ul>\n\t *     <li>{@code R} is the number of values shown per row.</li>\n\t * </ul>\n\t */\n\tvoid selectUp();\n\n\t/**\n\t * Move selection by {@code +N*R} where {@code R} is the number of values shown per row.\n\t * <ul>\n\t *     <li>{@code R} is the number of values shown per row.</li>\n\t *     <li>{@code N} is the number of rows</li>\n\t * </ul>\n\t */\n\tvoid selectDown(int rows);\n\n\t/**\n\t * Move selection by {@code -N*R} where:\n\t * <ul>\n\t *     <li>{@code R} is the number of values shown per row.</li>\n\t *     <li>{@code N} is the number of rows</li>\n\t * </ul>\n\t */\n\tvoid selectUp(int rows);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/binary/hex/ops/HexOperations.java",
    "content": "package software.coley.recaf.ui.pane.editing.binary.hex.ops;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.event.EventHandler;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\n\n/**\n * Outline holding the data model and UI operations for the hex editor.\n *\n * @author Matt Coley\n */\npublic interface HexOperations {\n\t@Nonnull\n\tHexAccess currentAccess();\n\n\t@Nonnull\n\tHexAccess originalAccess();\n\n\t@Nonnull\n\tHexNavigation navigation();\n\n\tvoid refreshDisplay(int offset, boolean asciiOrigin);\n\n\tvoid engageCurrent();\n\n\tvoid cancelCurrent();\n\n\tvoid sendKeyToCurrentEngaged(@Nonnull KeyCode code);\n\n\t@Nonnull\n\tEventHandler<KeyEvent> keyListener();\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/DecompilerPaneConfig.java",
    "content": "package software.coley.recaf.ui.pane.editing.jvm;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.recaf.config.BasicConfigContainer;\nimport software.coley.recaf.config.BasicConfigValue;\nimport software.coley.recaf.config.ConfigGroups;\nimport software.coley.recaf.services.source.AstMapper;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.pane.editing.android.AndroidDecompilerPane;\nimport software.coley.recaf.util.DevDetection;\n\n/**\n * Config for {@link JvmDecompilerPane} and {@link AndroidDecompilerPane}.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class DecompilerPaneConfig extends BasicConfigContainer {\n\tprivate final ObservableInteger timeoutSeconds = new ObservableInteger(60);\n\tprivate final ObservableBoolean useMappingAcceleration = new ObservableBoolean(false);\n\n\t@Inject\n\tpublic DecompilerPaneConfig() {\n\t\tsuper(ConfigGroups.SERVICE_UI, \"decompile-pane\" + CONFIG_SUFFIX);\n\t\taddValue(new BasicConfigValue<>(\"timeout-seconds\", int.class, timeoutSeconds));\n\t\taddValue(new BasicConfigValue<>(\"mapping-acceleration\", boolean.class, useMappingAcceleration));\n\t}\n\n\t/**\n\t * @return Decompilation timeout in seconds.\n\t */\n\t@Nonnull\n\tpublic ObservableInteger getTimeoutSeconds() {\n\t\treturn timeoutSeconds;\n\t}\n\n\t/**\n\t * @return Flag indicating if {@link AstMapper} should be used to update {@link Editor#getText()}\n\t * instead of decompiling the new remapped class. Generally faster, but can be inaccurate in some edge cases.\n\t */\n\t@Nonnull\n\tpublic ObservableBoolean getUseMappingAcceleration() {\n\t\treturn useMappingAcceleration;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/JvmClassEditorType.java",
    "content": "package software.coley.recaf.ui.pane.editing.jvm;\n\n/**\n * Supported editors for {@link JvmClassPane}.\n *\n * @author Matt Coley\n */\npublic enum JvmClassEditorType {\n\tDECOMPILE,\n\tLOW_LEVEL,\n\tHEX\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/JvmClassInfoProvider.java",
    "content": "package software.coley.recaf.ui.pane.editing.jvm;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.geometry.HPos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.Priority;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.navigation.ClassNavigable;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.pane.editing.AbstractClassInfoProvider;\nimport software.coley.recaf.ui.pane.editing.ToolsContainerComponent;\nimport software.coley.recaf.util.JavaVersion;\nimport software.coley.recaf.util.Lang;\n\nimport java.util.Objects;\n\n/**\n * Overlay component for {@link Editor} that allows quick display of JVM class information.\n *\n * @author Matt Coley\n */\npublic class JvmClassInfoProvider extends AbstractClassInfoProvider<JvmClassInfo> {\n\t/**\n\t * @param toolsContainer\n\t * \t\tContainer to house tool buttons for display in the {@link Editor}.\n\t * @param classProvider\n\t * \t\tThe provider of the latest class info.\n\t */\n\tpublic JvmClassInfoProvider(@Nonnull ToolsContainerComponent toolsContainer, @Nonnull ClassNavigable classProvider) {\n\t\tsuper(toolsContainer, classProvider);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected Node createInfoContent(@Nonnull JvmClassInfo info) {\n\t\tGridPane content = new GridPane();\n\t\tColumnConstraints col1 = new ColumnConstraints();\n\t\tColumnConstraints col2 = new ColumnConstraints();\n\t\tcol2.setFillWidth(true);\n\t\tcol2.setHgrow(Priority.ALWAYS);\n\t\tcol2.setHalignment(HPos.RIGHT);\n\t\tcontent.getColumnConstraints().addAll(col1, col2);\n\t\tcontent.setHgap(10);\n\t\tcontent.setVgap(5);\n\n\t\tLabel titleLabel = new BoundLabel(Lang.getBinding(\"java.info\"));\n\t\ttitleLabel.getStyleClass().addAll(Styles.TEXT_UNDERLINED, Styles.TITLE_4);\n\n\t\tLabel versionLabel = new BoundLabel(Lang.getBinding(\"java.info.version\"));\n\t\tLabel sourceLabel = new BoundLabel(Lang.getBinding(\"java.info.sourcefile\"));\n\t\tversionLabel.getStyleClass().addAll(Styles.TEXT_BOLD);\n\t\tsourceLabel.getStyleClass().addAll(Styles.TEXT_BOLD);\n\n\t\tLabel versionValueLabel = new Label(String.valueOf(info.getVersion() - JavaVersion.VERSION_OFFSET));\n\t\tLabel sourceValueLabel = new Label(Objects.requireNonNullElse(info.getSourceFileName(), \"\"));\n\n\t\tint row = 0;\n\t\tcontent.add(titleLabel, 0, row++, 2, 1);\n\n\t\tcontent.add(versionLabel, 0, row);\n\t\tcontent.add(versionValueLabel, 1, row++);\n\n\t\tcontent.add(sourceLabel, 0, row);\n\t\tcontent.add(sourceValueLabel, 1, row++);\n\n\t\treturn content;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/JvmClassPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.jvm;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.scene.Node;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.ui.config.ClassEditingConfig;\nimport software.coley.recaf.ui.pane.editing.ClassPane;\nimport software.coley.recaf.ui.pane.editing.SideTabsInjector;\nimport software.coley.recaf.ui.pane.editing.binary.hex.HexAdapter;\nimport software.coley.recaf.ui.pane.editing.binary.hex.HexConfig;\nimport software.coley.recaf.ui.pane.editing.jvm.lowlevel.JvmLowLevelPane;\nimport software.coley.recaf.util.SceneUtils;\n\n/**\n * Displays {@link JvmClassInfo} in a configurable manner.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class JvmClassPane extends ClassPane {\n\tprivate final Instance<JvmDecompilerPane> decompilerProvider;\n\tprivate final Instance<JvmLowLevelPane> lowLevelProvider;\n\tprivate final HexConfig hexConfig;\n\tprivate JvmClassEditorType editorType;\n\n\t@Inject\n\tpublic JvmClassPane(@Nonnull ClassEditingConfig config,\n\t                    @Nonnull HexConfig hexConfig,\n\t                    @Nonnull SideTabsInjector sideTabsInjector,\n\t                    @Nonnull Instance<JvmDecompilerPane> decompilerProvider,\n\t                    @Nonnull Instance<JvmLowLevelPane> lowLevelProvider) {\n\t\tthis.hexConfig = hexConfig;\n\t\tthis.decompilerProvider = decompilerProvider;\n\t\tthis.lowLevelProvider = lowLevelProvider;\n\n\t\tsideTabsInjector.injectLater(this);\n\n\t\teditorType = config.getDefaultJvmEditor().getValue();\n\t}\n\n\t/**\n\t * @return Current editor display type.\n\t */\n\t@Nonnull\n\tpublic JvmClassEditorType getEditorType() {\n\t\treturn editorType;\n\t}\n\n\t/**\n\t * @param editorType\n\t * \t\tNew editor display type.\n\t */\n\tpublic void setEditorType(@Nonnull JvmClassEditorType editorType) {\n\t\tif (this.editorType != editorType) {\n\t\t\tthis.editorType = editorType;\n\t\t\trefreshDisplay();\n\t\t}\n\t}\n\n\t@Override\n\tprotected void generateDisplay() {\n\t\t// If you want to swap out the display, first clear the existing one.\n\t\t// Clearing is done automatically when changing the editor type.\n\t\tif (hasDisplay())\n\t\t\treturn;\n\n\t\t// Update content in pane.\n\t\t// We do not need to pass the path along to it here, since the calling context should do that.\n\t\tJvmClassEditorType type = getEditorType();\n\t\tswitch (type) {\n\t\t\tcase DECOMPILE -> setDisplay(decompilerProvider.get());\n\t\t\tcase LOW_LEVEL -> setDisplay(lowLevelProvider.get());\n\t\t\tcase HEX -> setDisplay(new HexAdapter(hexConfig));\n\t\t\tdefault -> throw new IllegalStateException(\"Unknown editor type: \" + type.name());\n\t\t}\n\t}\n\n\t@Override\n\tpublic void requestFocus() {\n\t\tNode display = getDisplay();\n\t\tif (display != null)\n\t\t\tdisplay.requestFocus();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/JvmDecompilerPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.jvm;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.animation.Transition;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Labeled;\nimport javafx.scene.control.TitledPane;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.paint.Color;\nimport javafx.util.Duration;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.builder.JvmClassInfoBuilder;\nimport software.coley.recaf.info.properties.builtin.CachedDecompileProperty;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.IncompletePathException;\nimport software.coley.recaf.services.compile.CompileMap;\nimport software.coley.recaf.services.compile.CompilerDiagnostic;\nimport software.coley.recaf.services.compile.JavacArgumentsBuilder;\nimport software.coley.recaf.services.compile.JavacCompiler;\nimport software.coley.recaf.services.compile.JavacCompilerConfig;\nimport software.coley.recaf.services.decompile.DecompileResult;\nimport software.coley.recaf.services.decompile.DecompilerManager;\nimport software.coley.recaf.services.decompile.JvmDecompiler;\nimport software.coley.recaf.services.info.association.FileTypeSyntaxAssociationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.source.AstResolveResult;\nimport software.coley.recaf.services.source.AstService;\nimport software.coley.recaf.services.tutorial.TutorialConfig;\nimport software.coley.recaf.services.tutorial.TutorialWorkspace;\nimport software.coley.recaf.ui.config.KeybindingConfig;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.ModalPaneComponent;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.problem.Problem;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemLevel;\nimport software.coley.recaf.ui.control.richtext.problem.ProblemPhase;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.ui.control.richtext.source.JavaContextActionSupport;\nimport software.coley.recaf.ui.pane.editing.AbstractDecompilePane;\nimport software.coley.recaf.ui.pane.editing.ToolsContainerComponent;\nimport software.coley.recaf.util.Animations;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.JavaVersion;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\n\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\n\n/**\n * Displays a {@link JvmClassInfo} via a configured {@link Editor} as decompiled by {@link DecompilerManager}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class JvmDecompilerPane extends AbstractDecompilePane {\n\tprivate static final Logger logger = Logging.get(JvmDecompilerPane.class);\n\tprivate static final ExecutorService compilePool = ThreadPoolFactory.newSingleThreadExecutor(\"recompile\");\n\tprivate final ObservableInteger javacTarget;\n\tprivate final ObservableInteger javacDownsampleTarget;\n\tprivate final ObservableBoolean javacDebug;\n\tprivate final ModalPaneComponent overlayModal = new ModalPaneComponent();\n\tprivate final JavacCompiler javac;\n\n\t@Inject\n\tpublic JvmDecompilerPane(@Nonnull DecompilerPaneConfig decompileConfig,\n\t                         @Nonnull TutorialConfig tutorialConfig,\n\t                         @Nonnull KeybindingConfig keys,\n\t                         @Nonnull SearchBar searchBar,\n\t                         @Nonnull ToolsContainerComponent toolsContainer,\n\t                         @Nonnull AstService astService,\n\t                         @Nonnull JavaContextActionSupport contextActionSupport,\n\t                         @Nonnull FileTypeSyntaxAssociationService languageAssociation,\n\t                         @Nonnull DecompilerManager decompilerManager,\n\t                         @Nonnull JavacCompiler javac,\n\t                         @Nonnull JavacCompilerConfig javacConfig,\n\t                         @Nonnull Actions actions) {\n\t\tsuper(decompileConfig, tutorialConfig, searchBar, astService, contextActionSupport, languageAssociation, decompilerManager);\n\t\tthis.javacDebug = new ObservableBoolean(javacConfig.getDefaultEmitDebug().getValue());\n\t\tthis.javacTarget = new ObservableInteger(javacConfig.getDefaultTargetVersion().getValue());\n\t\tthis.javacDownsampleTarget = new ObservableInteger(javacConfig.getDefaultDownsampleTargetVersion().getValue());\n\t\tthis.javac = javac;\n\n\t\t// Install tools container with configurator\n\t\tnew JvmDecompilerPaneConfigurator(toolsContainer, decompileConfig, decompiler, javacTarget, javacDownsampleTarget, javacDebug, decompilerManager);\n\t\tnew JvmClassInfoProvider(toolsContainer, this);\n\t\tinstallToolsContainer(toolsContainer);\n\n\t\t// Setup keybindings\n\t\tsetOnKeyPressed(e -> {\n\t\t\tif (keys.getSave().match(e)) {\n\t\t\t\tsave();\n\t\t\t} else if (keys.getUndo().match(e)) {\n\t\t\t\tBundle<?> bundle = path.getValueOfType(Bundle.class);\n\t\t\t\tif (bundle != null)\n\t\t\t\t\tbundle.decrementHistory(path.getValue().getName());\n\t\t\t} else if (keys.getRename().match(e)) {\n\t\t\t\t// Resolve what the caret position has, then handle renaming on the generic result.\n\t\t\t\tAstResolveResult result = contextActionSupport.resolvePosition(editor.getCodeArea().getCaretPosition());\n\t\t\t\tif (result != null)\n\t\t\t\t\tactions.rename(result.path());\n\t\t\t} else if (keys.getGoto().match(e)) {\n\t\t\t\t// Resolve what the caret position has, then handle navigating to the resulting path.\n\t\t\t\tAstResolveResult result = contextActionSupport.resolvePosition(editor.getCodeArea().getCaretPosition());\n\t\t\t\tif (result != null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tactions.gotoDeclaration(result.path());\n\t\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\t\t// Should realistically never happen\n\t\t\t\t\t\tlogger.warn(\"Cannot goto location, path incomplete\", ex);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tsetOnMouseClicked(e -> {\n\t\t\tif (e.getButton() == MouseButton.PRIMARY && e.isControlDown()) {\n\t\t\t\t// Resolve what the caret position has, then handle navigating to the resulting path.\n\t\t\t\tAstResolveResult result = contextActionSupport.resolvePosition(editor.getCodeArea().getCaretPosition());\n\t\t\t\tif (result != null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tactions.gotoDeclaration(result.path());\n\t\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\t\t// Should realistically never happen\n\t\t\t\t\t\tlogger.warn(\"Cannot goto location, path incomplete\", ex);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// Install overlay modal\n\t\toverlayModal.setPersistent(true);\n\t\toverlayModal.install(editor);\n\t}\n\n\t/**\n\t * Called when {@link KeybindingConfig#getSave()} is pressed.\n\t * <br>\n\t * Compiles the current Java code in the {@link #editor} and updates the workspace\n\t * with the newly compiled {@link JvmClassInfo}.\n\t */\n\tprivate void save() {\n\t\t// Pull data from path.\n\t\tJvmClassInfo info = path.getValue().asJvmClass();\n\t\tWorkspace workspace = path.getValueOfType(Workspace.class);\n\t\tif (workspace == null)\n\t\t\tthrow new IllegalStateException(\"Workspace missing from class path node\");\n\t\tJvmClassBundle bundle = (JvmClassBundle) path.getValueOfType(Bundle.class);\n\t\tif (bundle == null)\n\t\t\tthrow new IllegalStateException(\"Bundle missing from class path node\");\n\n\t\t// Clear old errors emitted by compilation.\n\t\tproblemTracking.removeByPhase(ProblemPhase.BUILD);\n\n\t\t// Invoke compiler with data.\n\t\tString infoName = info.getName();\n\t\tCompletableFuture.supplyAsync(() -> {\n\t\t\tboolean debug = javacDebug.getValue();\n\t\t\tJavacArgumentsBuilder builder = new JavacArgumentsBuilder()\n\t\t\t\t\t.withVersionTarget(useConfiguredVersion(info))\n\t\t\t\t\t.withDownsampleTarget(javacDownsampleTarget.getValue())\n\t\t\t\t\t.withDebugVariables(debug)\n\t\t\t\t\t.withDebugSourceName(debug)\n\t\t\t\t\t.withDebugLineNumbers(debug)\n\t\t\t\t\t.withClassSource(editor.getText())\n\t\t\t\t\t.withClassName(infoName);\n\t\t\treturn javac.compile(builder.build(), workspace, null);\n\t\t}, compilePool).completeOnTimeout(null, 2, TimeUnit.SECONDS).whenCompleteAsync((result, throwable) -> {\n\t\t\t// Handle results.\n\t\t\t//  - Success --> Update content in the containing bundle\n\t\t\t//  - Failure --> Show error + diagnostics to user\n\t\t\tif (result != null && result.wasSuccess()) {\n\t\t\t\t// Renaming is not allowed. Tell the user to use mapping operations.\n\t\t\t\t// This should usually be caught by javac, but we're double-checking here.\n\t\t\t\t// We *could* have some hacky code to work around the rename being done outside the dedicated API,\n\t\t\t\t// but it would be ugly. Find the new name for the class and any inners, copy over properties from\n\t\t\t\t// the old names, apply mapping operations to patch broken references, etc.\n\t\t\t\tCompileMap compilations = result.getCompilations();\n\n\t\t\t\t// Check if the target name still exists.\n\t\t\t\tif (!compilations.containsKey(infoName)) {\n\t\t\t\t\tlogger.warn(\"Please only rename classes via mapping operations.\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Check if any non-external-reference inner class entry no longer exists.\n\t\t\t\t//  - Removal/updating/insertion is OK, renaming is not.\n\t\t\t\t//  - Because inners may have other inners we need to recursively collect inner classes\n\t\t\t\tMap<String, InnerClassInfo> realInners = info.getInnerClasses().stream()\n\t\t\t\t\t\t.filter(inner -> !inner.isExternalReference())\n\t\t\t\t\t\t.collect(Collectors.toMap(InnerClassInfo::getInnerClassName, Function.identity()));\n\t\t\t\tSet<String> names = new HashSet<>();\n\t\t\t\tboolean recurseAddInners;\n\t\t\t\tdo {\n\t\t\t\t\t// Reset the recurse flag each iteration. We'll enable it only if we need to.\n\t\t\t\t\trecurseAddInners = false;\n\t\t\t\t\tfor (String type : new HashSet<>(realInners.keySet())) {\n\t\t\t\t\t\t// Skip if we already checked this type for further inner classes.\n\t\t\t\t\t\tif (!names.add(type)) continue;\n\n\t\t\t\t\t\t// Lookup inner class in workspace and add its inner classes to the map.\n\t\t\t\t\t\tClassPathNode typePath = workspace.findClass(type);\n\t\t\t\t\t\tif (typePath != null) {\n\t\t\t\t\t\t\tList<InnerClassInfo> innerClasses = typePath.getValue().getInnerClasses();\n\t\t\t\t\t\t\tfor (InnerClassInfo inner : innerClasses) {\n\t\t\t\t\t\t\t\tif (inner.isExternalReference()) continue;\n\n\t\t\t\t\t\t\t\t// Enable another recursive pass if a new inner class was found.\n\t\t\t\t\t\t\t\trecurseAddInners |= realInners.putIfAbsent(inner.getInnerClassName(), inner) == null;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} while (recurseAddInners);\n\n\t\t\t\t// Abort if we cannot ensure this compilation includes renaming of inner classes.\n\t\t\t\tSet<String> notInCompilation;\n\t\t\t\tSet<String> notInExisting;\n\t\t\t\tif (!realInners.isEmpty()) {\n\t\t\t\t\t// Collect inner class names from the compilation.\n\t\t\t\t\tSet<String> compiledInnerNames = new HashSet<>(compilations.keySet());\n\t\t\t\t\tcompiledInnerNames.remove(infoName);\n\n\t\t\t\t\t// Collect names that do not exist in the before/after states.\n\t\t\t\t\tnotInCompilation = new HashSet<>();\n\t\t\t\t\tnotInExisting = new HashSet<>();\n\t\t\t\t\tfor (String existingInner : realInners.keySet())\n\t\t\t\t\t\tif (!compiledInnerNames.remove(existingInner))\n\t\t\t\t\t\t\tnotInCompilation.add(existingInner);\n\t\t\t\t\tfor (String compiledInnerName : compiledInnerNames)\n\t\t\t\t\t\tif (!realInners.containsKey(compiledInnerName))\n\t\t\t\t\t\t\tnotInExisting.add(compiledInnerName);\n\n\t\t\t\t\t// If we see names in both collections (that are not anonymous inner classes)\n\t\t\t\t\t// we cannot safely know if this is a result of renaming or not.\n\t\t\t\t\t// But if we see only insertions or only removals that is totally fine.\n\t\t\t\t\tif (notInCompilation.stream().anyMatch(JvmDecompilerPane::isNotAnonymousInnerClass) &&\n\t\t\t\t\t\t\tnotInExisting.stream().anyMatch(JvmDecompilerPane::isNotAnonymousInnerClass)) {\n\t\t\t\t\t\tlogger.warn(\"Please only rename inner classes via mapping operations.\");\n\t\t\t\t\t\tAnimations.animateWarn(this, 1000);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tnotInCompilation = Collections.emptySet();\n\t\t\t\t\tnotInExisting = Collections.emptySet();\n\t\t\t\t}\n\n\t\t\t\t// Compilation map has contents, update the workspace.\n\t\t\t\tAnimations.animateSuccess(this, 1000);\n\t\t\t\tupdateLock.set(true);\n\t\t\t\tcompilations.forEach((name, bytecode) -> {\n\t\t\t\t\tJvmClassInfo newInfo;\n\t\t\t\t\tif (infoName.equals(name)) {\n\t\t\t\t\t\t// Adapt from existing.\n\t\t\t\t\t\tnewInfo = info.toJvmClassBuilder()\n\t\t\t\t\t\t\t\t.adaptFrom(bytecode)\n\t\t\t\t\t\t\t\t.build();\n\t\t\t\t\t\tpath = Objects.requireNonNull(path.getParent(), \"Class missing parent in path\").child(newInfo);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Handle inner classes.\n\t\t\t\t\t\tJvmClassInfo originalClass = bundle.get(name);\n\t\t\t\t\t\tif (originalClass != null) {\n\t\t\t\t\t\t\t// Adapt from existing.\n\t\t\t\t\t\t\tnewInfo = originalClass\n\t\t\t\t\t\t\t\t\t.toJvmClassBuilder()\n\t\t\t\t\t\t\t\t\t.adaptFrom(bytecode)\n\t\t\t\t\t\t\t\t\t.build();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Class is new.\n\t\t\t\t\t\t\tnewInfo = new JvmClassInfoBuilder(bytecode).build();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Update cached decompile property to current editor text.\n\t\t\t\t\t// If the class is opened later, we can use the code we compiled with.\n\t\t\t\t\tJvmDecompiler currentDecompiler = decompiler.getValue();\n\t\t\t\t\tint configHash = currentDecompiler.getConfig().getHash();\n\t\t\t\t\tCachedDecompileProperty.set(newInfo, currentDecompiler,\n\t\t\t\t\t\t\tnew DecompileResult(editor.getText(), configHash));\n\n\t\t\t\t\t// Update the class in the bundle.\n\t\t\t\t\tbundle.put(newInfo);\n\t\t\t\t});\n\t\t\t\tfor (String removedClass : notInCompilation)\n\t\t\t\t\tbundle.remove(removedClass);\n\t\t\t\tupdateLock.set(false);\n\t\t\t} else {\n\t\t\t\t// Handle compile-result failure, or uncaught thrown exception.\n\t\t\t\tif (result != null) {\n\t\t\t\t\tif (result.getDiagnostics().isEmpty() && result.getException() != null)\n\t\t\t\t\t\tproblemTracking.addItem(new Problem(-1, -1, 0,\n\t\t\t\t\t\t\t\tProblemLevel.ERROR, ProblemPhase.BUILD, result.getException().toString()));\n\n\t\t\t\t\tfor (CompilerDiagnostic diagnostic : result.getDiagnostics())\n\t\t\t\t\t\tproblemTracking.addItem(Problem.fromDiagnostic(diagnostic));\n\n\t\t\t\t\t// For first-timers (excluding when the tutorial is open), tell them you cannot save with errors.\n\t\t\t\t\tif (!tutorialConfig.getAcknowledgedSaveWithErrors().getValue() && !(workspace instanceof TutorialWorkspace))\n\t\t\t\t\t\tshowFirstTimeSaveWithErrors();\n\t\t\t\t} else {\n\t\t\t\t\tlogger.error(\"Compilation encountered an error on class '{}'\", infoName, throwable);\n\t\t\t\t}\n\t\t\t\tAnimations.animateFailure(this, 1000);\n\t\t\t}\n\n\t\t\t// Redraw paragraph graphics to update things like in-line problem graphics.\n\t\t\teditor.redrawParagraphGraphics();\n\t\t}, FxThreadUtil.executor());\n\t}\n\n\t/**\n\t * @param info\n\t * \t\tClass to recompile.\n\t *\n\t * @return Target Java version <i>(Standard versioning, not the internal one)</i>.\n\t */\n\tprivate int useConfiguredVersion(@Nonnull JvmClassInfo info) {\n\t\tint version = javacTarget.getValue();\n\n\t\t// Negative: Match class file's version\n\t\tif (version < 0)\n\t\t\treturn JavaVersion.adaptFromClassFileVersion(info.getVersion());\n\n\t\t// Use provided version\n\t\treturn version;\n\t}\n\n\t/**\n\t * Show popup telling user they cannot save with errors.\n\t */\n\tprivate void showFirstTimeSaveWithErrors() {\n\t\tButton acknowledge = new Button();\n\t\tacknowledge.setGraphic(new FontIconView(CarbonIcons.TIMER));\n\t\tacknowledge.setDisable(true); // Enabled after a delay.\n\n\t\tVBox content = new VBox(new BoundLabel(Lang.getBinding(\"java.savewitherrors\")), acknowledge);\n\t\tcontent.setFillWidth(false);\n\t\tcontent.setSpacing(10);\n\t\tcontent.setAlignment(Pos.CENTER);\n\n\t\tTitledPane wrapper = new TitledPane();\n\t\twrapper.textProperty().bind(Lang.getBinding(\"java.savewitherrors.title\"));\n\t\twrapper.setCollapsible(false);\n\t\twrapper.setContent(content);\n\t\twrapper.getStyleClass().add(Styles.ELEVATED_4);\n\t\twrapper.setMaxWidth(650);\n\n\t\toverlayModal.show(wrapper);\n\n\t\t// Start transition which counts down how long until the popup can be closed.\n\t\tWaitToAcknowledgeTransition wait = new WaitToAcknowledgeTransition(acknowledge);\n\t\twait.play();\n\n\t\t// Enable acknowledge button after 5 seconds.\n\t\tFxThreadUtil.delayedRun(wait.getMillis(), () -> {\n\t\t\twait.stop();\n\t\t\tacknowledge.textProperty().bind(Lang.getBinding(\"misc.acknowledge\"));\n\t\t\tacknowledge.setGraphic(new FontIconView(CarbonIcons.CHECKMARK, Color.LIME));\n\t\t\tacknowledge.setDisable(false);\n\t\t});\n\n\t\t// When pressed, mark flag so prompt is not shown again.\n\t\tacknowledge.setOnAction(e -> {\n\t\t\ttutorialConfig.getAcknowledgedSaveWithErrors().setValue(true);\n\t\t\toverlayModal.hide();\n\t\t});\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tInternal class name.\n\t *\n\t * @return {@code true} when it appears to not be a top-level anonymous inner class.\n\t */\n\tprivate static boolean isNotAnonymousInnerClass(@Nonnull String name) {\n\t\treturn !isAnonymousInnerClass(name);\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tInternal class name.\n\t *\n\t * @return {@code true} when it appears to be a top-level anonymous inner class.\n\t */\n\tprivate static boolean isAnonymousInnerClass(@Nonnull String name) {\n\t\t// We should only see numeric names in anonymous inner classes coming from javac\n\t\t// so the first inner split's next character should be a good enough check.\n\t\tint firstInnerSplit = name.indexOf('$');\n\t\treturn firstInnerSplit > 0 && firstInnerSplit + 1 < name.length() && Character.isDigit(name.charAt(firstInnerSplit + 1));\n\t}\n\n\t/**\n\t * Transition to handle countdown to allow acknowledging <i>\"I can not save with errors\"</i>.\n\t */\n\tprivate static class WaitToAcknowledgeTransition extends Transition {\n\t\tprivate static final int SECONDS = 8;\n\t\tprivate final Labeled labeled;\n\n\t\tprivate WaitToAcknowledgeTransition(Labeled labeled) {\n\t\t\tthis.labeled = labeled;\n\t\t\tsetCycleDuration(Duration.seconds(SECONDS));\n\t\t}\n\n\t\tprivate long getMillis() {\n\t\t\treturn SECONDS * 1000;\n\t\t}\n\n\t\t@Override\n\t\tprotected void interpolate(double frac) {\n\t\t\tint secondsLeft = (int) Math.floor(SECONDS - (frac * SECONDS) + 1.01);\n\t\t\tlabeled.setText(secondsLeft + \"...\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/JvmDecompilerPaneConfigurator.java",
    "content": "package software.coley.recaf.ui.pane.editing.jvm;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.GridPane;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableInteger;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.services.compile.JavacCompiler;\nimport software.coley.recaf.services.decompile.DecompilerManager;\nimport software.coley.recaf.services.decompile.JvmDecompiler;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.ObservableCheckBox;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.pane.editing.AbstractDecompilerPaneConfigurator;\nimport software.coley.recaf.ui.pane.editing.ToolsContainerComponent;\nimport software.coley.recaf.util.JavaVersion;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.ToStringConverter;\n\n/**\n * Overlay component for {@link Editor} that allows quick configuration of properties of a {@link JvmDecompilerPane}.\n *\n * @author Matt Coley\n */\npublic class JvmDecompilerPaneConfigurator extends AbstractDecompilerPaneConfigurator {\n\tprivate final ObservableInteger javacTarget;\n\tprivate final ObservableInteger javacDownsampleTarget;\n\tprivate final ObservableBoolean javacDebug;\n\n\t/**\n\t * @param toolsContainer\n\t * \t\tContainer to house tool buttons for display in the {@link Editor}.\n\t * @param config\n\t * \t\tContaining {@link JvmDecompilerPane} config singleton.\n\t * @param decompiler\n\t * \t\tLocal decompiler implementation.\n\t * @param javacTarget\n\t * \t\tLocal target version for {@code javac}.\n\t * @param javacDownsampleTarget\n\t * \t\tLocal target version to downsample to for {@code javac}.\n\t * @param javacDebug\n\t * \t\tLocal debug flag for {@code javac}.\n\t * @param decompilerManager\n\t * \t\tManager to pull available {@link JvmDecompiler} instances from.\n\t */\n\tpublic JvmDecompilerPaneConfigurator(@Nonnull ToolsContainerComponent toolsContainer,\n\t\t\t\t\t\t\t\t\t\t @Nonnull DecompilerPaneConfig config,\n\t\t\t\t\t\t\t\t\t\t @Nonnull ObservableObject<JvmDecompiler> decompiler,\n\t\t\t\t\t\t\t\t\t\t @Nonnull ObservableInteger javacTarget,\n\t\t\t\t\t\t\t\t\t\t @Nonnull ObservableInteger javacDownsampleTarget,\n\t\t\t\t\t\t\t\t\t\t @Nonnull ObservableBoolean javacDebug,\n\t\t\t\t\t\t\t\t\t\t @Nonnull DecompilerManager decompilerManager) {\n\t\tsuper(toolsContainer, config, decompiler, decompilerManager);\n\t\tthis.javacTarget = javacTarget;\n\t\tthis.javacDownsampleTarget = javacDownsampleTarget;\n\t\tthis.javacDebug = javacDebug;\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected GridPane createGrid() {\n\t\tGridPane content = super.createGrid();\n\n\t\t// Compilation config\n\t\tLabel compileTitle = new BoundLabel(Lang.getBinding(\"service.compile\"));\n\t\tcompileTitle.getStyleClass().addAll(Styles.TEXT_UNDERLINED, Styles.TITLE_4);\n\t\tLabel labelTargetVersion = new BoundLabel(Lang.getBinding(\"java.targetversion\"));\n\t\tLabel labelTargetDownsampleVersion = new BoundLabel(Lang.getBinding(\"java.targetdownsampleversion\"));\n\t\tLabel labelDebug = new BoundLabel(Lang.getBinding(\"java.targetdebug\"));\n\n\t\tint row = content.getRowCount() + 1;\n\t\tcontent.add(compileTitle, 0, row++, 2, 1);\n\n\t\tcontent.add(labelTargetVersion, 0, row);\n\t\tcontent.add(fix(new JavacVersionComboBox()), 1, row++);\n\n\t\tcontent.add(labelTargetDownsampleVersion, 0, row);\n\t\tcontent.add(fix(new JavacDownsampleVersionComboBox()), 1, row++);\n\n\t\tcontent.add(labelDebug, 0, row);\n\t\tcontent.add(fix(new ObservableCheckBox(javacDebug, Lang.getBinding(\"misc.enabled\"))), 1, row++);\n\n\t\treturn content;\n\t}\n\n\tprivate class JavacVersionComboBox extends ComboBox<Integer> {\n\t\tprivate JavacVersionComboBox() {\n\t\t\t// TODO: Changing the value in this box causes the UI thread to 'sometimes' freeze the UI\n\t\t\t//  - Nothing in the stacktrace from Recaf, so probably some weird event-loop\n\t\t\t//  - No idea why this would trigger that though\n\t\t\t//  - Does not occur if the valueProperty listener is commented out\n\n\t\t\tint max = JavaVersion.get();\n\t\t\tfor (int i = JavacCompiler.getMinTargetVersion(); i <= max; i++)\n\t\t\t\tgetItems().add(i);\n\n\t\t\t// Edge case for 'automatic'\n\t\t\tgetItems().add(-1);\n\t\t\tsetValue(-1);\n\t\t\tsetConverter(ToStringConverter.from(version -> {\n\t\t\t\tint v = version;\n\t\t\t\tif (v < 0)\n\t\t\t\t\treturn Lang.get(\"java.targetversion.auto\");\n\t\t\t\treturn String.valueOf(v);\n\t\t\t}));\n\n\t\t\t// Hack to prevent odd resize-based deadlock: #798\n\t\t\tint w = 200;\n\t\t\tsetMaxWidth(w);\n\t\t\tsetPrefWidth(w);\n\n\t\t\t// Update property.\n\t\t\tvalueProperty().addListener((ob, old, cur) -> javacTarget.setValue(cur));\n\t\t}\n\t}\n\n\tprivate class JavacDownsampleVersionComboBox extends ComboBox<Integer> {\n\t\tprivate JavacDownsampleVersionComboBox() {\n\t\t\tint max = JavaVersion.get();\n\t\t\tfor (int i = JavacCompiler.MIN_DOWNSAMPLE_VER; i <= max; i++)\n\t\t\t\tgetItems().add(i);\n\n\t\t\t// Edge case for 'disabled'\n\t\t\tgetItems().add(-1);\n\t\t\tsetValue(-1);\n\t\t\tsetConverter(ToStringConverter.from(version -> {\n\t\t\t\tint v = version;\n\t\t\t\tif (v < 0)\n\t\t\t\t\treturn Lang.get(\"java.targetdownsampleversion.disabled\");\n\t\t\t\treturn String.valueOf(v);\n\t\t\t}));\n\n\t\t\t// Hack to prevent odd resize-based deadlock: #798\n\t\t\tint w = 200;\n\t\t\tsetMaxWidth(w);\n\t\t\tsetPrefWidth(w);\n\n\t\t\t// Update property.\n\t\t\tvalueProperty().addListener((ob, old, cur) -> javacDownsampleTarget.setValue(cur));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/lowlevel/ClassElement.java",
    "content": "package software.coley.recaf.ui.pane.editing.jvm.lowlevel;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.event.EventHandler;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TreeCell;\nimport javafx.scene.input.ContextMenuEvent;\nimport software.coley.recaf.util.EscapeUtil;\n\n/**\n * Outlines content displayable in a {@link JvmLowLevelPane}.\n *\n * @author Matt Coley\n */\npublic interface ClassElement {\n\t/**\n\t * @return Prefix text.\n\t */\n\t@Nonnull\n\tString prefix();\n\n\t/**\n\t * @return Primary display text.\n\t */\n\t@Nonnull\n\tString content();\n\n\t/**\n\t * @return Graphic for the element.\n\t */\n\t@Nullable\n\tNode graphic();\n\n\t/**\n\t * @return Handler to provide a context menu for the element.\n\t */\n\t@Nullable\n\tEventHandler<ContextMenuEvent> contextRequest();\n\n\t/**\n\t * Configures a given tree cell with values from this element.\n\t *\n\t * @param cell\n\t * \t\tCell to configure.\n\t */\n\tdefault void configureDisplay(@Nonnull TreeCell<ClassElement> cell) {\n\t\tLabel prefixLabel = new Label(prefix() + \":\", graphic());\n\t\tprefixLabel.getStyleClass().add(Styles.TEXT_BOLD);\n\n\t\tcell.setText(EscapeUtil.escapeStandardAndUnicodeWhitespace(content()));\n\t\tcell.setGraphic(prefixLabel);\n\t\tcell.setOnContextMenuRequested(contextRequest());\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/lowlevel/ClassItem.java",
    "content": "package software.coley.recaf.ui.pane.editing.jvm.lowlevel;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.Node;\nimport javafx.scene.control.ContextMenu;\nimport software.coley.recaf.ui.control.tree.FilterableTreeItem;\n\nimport java.util.function.Function;\n\n/**\n * A filterable tree item of {@link ClassElement}.\n *\n * @author Matt Coley\n */\npublic class ClassItem extends FilterableTreeItem<ClassElement> {\n\t/**\n\t * @param value\n\t * \t\tElement to hold.\n\t */\n\tpublic ClassItem(@Nonnull ClassElement value) {\n\t\tsetValue(value);\n\t}\n\n\t/**\n\t * Creates and adds a child item based on the provided child value.\n\t *\n\t * @param prefix\n\t * \t\tPrefix of child value.\n\t * @param element\n\t * \t\tChild value.\n\t * @param stringMapper\n\t * \t\tMapper from value to display string.\n\t * @param graphicMapper\n\t * \t\tMapper from value to display graphic.\n\t * @param menuMapper\n\t * \t\tMapper from value to context menu.\n\t * @param <E>\n\t * \t\tChild value type.\n\t *\n\t * @return Created child item.\n\t */\n\t@Nonnull\n\tpublic <E> ClassItem item(@Nonnull String prefix, @Nonnull E element,\n\t                          @Nonnull Function<E, String> stringMapper,\n\t                          @Nonnull Function<E, Node> graphicMapper,\n\t                          @Nonnull Function<E, ContextMenu> menuMapper) {\n\t\tClassItem item = new ClassItem(new LazyClassElement<>(prefix, element, stringMapper, graphicMapper, menuMapper));\n\t\titem(item);\n\t\treturn item;\n\t}\n\n\t/**\n\t * Adds a given child item.\n\t *\n\t * @param item\n\t * \t\tChild item.\n\t */\n\tpublic void item(@Nonnull ClassItem item) {\n\t\tgetSourceChildren().add(item);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/lowlevel/JvmLowLevelPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.jvm.lowlevel;\n\nimport atlantafx.base.theme.Styles;\nimport atlantafx.base.theme.Tweaks;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.Node;\nimport javafx.scene.control.ContextMenu;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TreeCell;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.paint.Color;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.cafedude.InvalidClassException;\nimport software.coley.cafedude.classfile.ClassFile;\nimport software.coley.cafedude.classfile.Field;\nimport software.coley.cafedude.classfile.Method;\nimport software.coley.cafedude.classfile.annotation.Annotation;\nimport software.coley.cafedude.classfile.annotation.AnnotationElementValue;\nimport software.coley.cafedude.classfile.annotation.ArrayElementValue;\nimport software.coley.cafedude.classfile.annotation.ClassElementValue;\nimport software.coley.cafedude.classfile.annotation.ElementValue;\nimport software.coley.cafedude.classfile.annotation.EnumElementValue;\nimport software.coley.cafedude.classfile.annotation.PrimitiveElementValue;\nimport software.coley.cafedude.classfile.annotation.Utf8ElementValue;\nimport software.coley.cafedude.classfile.attribute.AnnotationDefaultAttribute;\nimport software.coley.cafedude.classfile.attribute.AnnotationsAttribute;\nimport software.coley.cafedude.classfile.attribute.Attribute;\nimport software.coley.cafedude.classfile.attribute.BootstrapMethodsAttribute;\nimport software.coley.cafedude.classfile.attribute.CharacterRangeTableAttribute;\nimport software.coley.cafedude.classfile.attribute.CodeAttribute;\nimport software.coley.cafedude.classfile.attribute.CompilationIdAttribute;\nimport software.coley.cafedude.classfile.attribute.ConstantValueAttribute;\nimport software.coley.cafedude.classfile.attribute.DefaultAttribute;\nimport software.coley.cafedude.classfile.attribute.DeprecatedAttribute;\nimport software.coley.cafedude.classfile.attribute.EnclosingMethodAttribute;\nimport software.coley.cafedude.classfile.attribute.ExceptionsAttribute;\nimport software.coley.cafedude.classfile.attribute.InnerClassesAttribute;\nimport software.coley.cafedude.classfile.attribute.LineNumberTableAttribute;\nimport software.coley.cafedude.classfile.attribute.LocalVariableTableAttribute;\nimport software.coley.cafedude.classfile.attribute.LocalVariableTypeTableAttribute;\nimport software.coley.cafedude.classfile.attribute.MethodParametersAttribute;\nimport software.coley.cafedude.classfile.attribute.ModuleAttribute;\nimport software.coley.cafedude.classfile.attribute.ModuleHashesAttribute;\nimport software.coley.cafedude.classfile.attribute.ModuleMainClassAttribute;\nimport software.coley.cafedude.classfile.attribute.ModulePackagesAttribute;\nimport software.coley.cafedude.classfile.attribute.ModuleResolutionAttribute;\nimport software.coley.cafedude.classfile.attribute.ModuleTargetAttribute;\nimport software.coley.cafedude.classfile.attribute.NestHostAttribute;\nimport software.coley.cafedude.classfile.attribute.NestMembersAttribute;\nimport software.coley.cafedude.classfile.attribute.ParameterAnnotationsAttribute;\nimport software.coley.cafedude.classfile.attribute.PermittedClassesAttribute;\nimport software.coley.cafedude.classfile.attribute.RecordAttribute;\nimport software.coley.cafedude.classfile.attribute.SignatureAttribute;\nimport software.coley.cafedude.classfile.attribute.SourceDebugExtensionAttribute;\nimport software.coley.cafedude.classfile.attribute.SourceFileAttribute;\nimport software.coley.cafedude.classfile.attribute.SourceIdAttribute;\nimport software.coley.cafedude.classfile.attribute.StackMapTableAttribute;\nimport software.coley.cafedude.classfile.attribute.SyntheticAttribute;\nimport software.coley.cafedude.classfile.behavior.AttributeHolder;\nimport software.coley.cafedude.classfile.constant.ConstDynamic;\nimport software.coley.cafedude.classfile.constant.ConstRef;\nimport software.coley.cafedude.classfile.constant.CpClass;\nimport software.coley.cafedude.classfile.constant.CpDouble;\nimport software.coley.cafedude.classfile.constant.CpEntry;\nimport software.coley.cafedude.classfile.constant.CpFloat;\nimport software.coley.cafedude.classfile.constant.CpInt;\nimport software.coley.cafedude.classfile.constant.CpInternal;\nimport software.coley.cafedude.classfile.constant.CpLong;\nimport software.coley.cafedude.classfile.constant.CpMethodHandle;\nimport software.coley.cafedude.classfile.constant.CpMethodType;\nimport software.coley.cafedude.classfile.constant.CpModule;\nimport software.coley.cafedude.classfile.constant.CpNameType;\nimport software.coley.cafedude.classfile.constant.CpPackage;\nimport software.coley.cafedude.classfile.constant.CpString;\nimport software.coley.cafedude.classfile.constant.CpUtf8;\nimport software.coley.cafedude.classfile.instruction.BasicInstruction;\nimport software.coley.cafedude.classfile.instruction.CpRefInstruction;\nimport software.coley.cafedude.classfile.instruction.IincInstruction;\nimport software.coley.cafedude.classfile.instruction.Instruction;\nimport software.coley.cafedude.classfile.instruction.IntOperandInstruction;\nimport software.coley.cafedude.classfile.instruction.LookupSwitchInstruction;\nimport software.coley.cafedude.classfile.instruction.MultiANewArrayInstruction;\nimport software.coley.cafedude.classfile.instruction.Opcodes;\nimport software.coley.cafedude.classfile.instruction.TableSwitchInstruction;\nimport software.coley.cafedude.classfile.instruction.WideInstruction;\nimport software.coley.cafedude.io.ClassFileReader;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.navigation.ClassNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.config.MemberDisplayFormatConfig;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.tree.TreeItems;\nimport software.coley.recaf.ui.pane.editing.binary.hex.HexUtil;\nimport software.coley.recaf.util.AccessFlag;\nimport software.coley.recaf.util.AsmInsnUtil;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.util.SVG;\nimport software.coley.recaf.util.Types;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.ArrayDeque;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Queue;\nimport java.util.function.Function;\n\nimport static software.coley.cafedude.classfile.constant.CpMethodHandle.*;\nimport static software.coley.recaf.util.AccessFlag.sortAndToString;\n\n/**\n * Displays a {@link JvmClassInfo} as a {@link TreeView} that closely aligns to the class file specification.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class JvmLowLevelPane extends BorderPane implements ClassNavigable, UpdatableNavigable {\n\tprivate static final Logger logger = Logging.get(JvmLowLevelPane.class);\n\tprivate final MemberDisplayFormatConfig memberDisplay;\n\tprivate final CellConfigurationService configurationService;\n\tprivate final Workspace workspace;\n\tprivate ClassPathNode path;\n\n\t@Inject\n\tpublic JvmLowLevelPane(@Nonnull WorkspaceManager workspaceManager,\n\t                       @Nonnull MemberDisplayFormatConfig memberDisplay,\n\t                       @Nonnull CellConfigurationService configurationService) {\n\t\tthis.workspace = workspaceManager.getCurrent();\n\t\tthis.memberDisplay = memberDisplay;\n\t\tthis.configurationService = configurationService;\n\t}\n\n\tprivate void updateDisplay(@Nonnull JvmClassInfo info) throws InvalidClassException {\n\t\tClassFileReader reader = new ClassFileReader();\n\t\tClassFile klass = reader.read(info.getBytecode());\n\n\t\t// Build tree\n\t\tClassItem root = buildRoot(klass);\n\t\tTreeView<ClassElement> tree = new TreeView<>();\n\t\ttree.setCellFactory(_ -> new TreeCell<>() {\n\t\t\t@Override\n\t\t\tprotected void updateItem(ClassElement item, boolean empty) {\n\t\t\t\tsuper.updateItem(item, empty);\n\n\t\t\t\tif (empty || item == null) {\n\t\t\t\t\tsetText(null);\n\t\t\t\t\tsetGraphic(null);\n\t\t\t\t\tsetOnContextMenuRequested(null);\n\t\t\t\t} else {\n\t\t\t\t\titem.configureDisplay(this);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\ttree.getStyleClass().addAll(Tweaks.EDGE_TO_EDGE, Styles.DENSE);\n\t\ttree.setShowRoot(false);\n\t\ttree.setRoot(root);\n\n\t\t// TODO: Add search bar (like workspace filter pane) (prefix + text)\n\t\tsetCenter(tree);\n\t}\n\n\t@Nonnull\n\tprivate ClassItem buildRoot(@Nonnull ClassFile klass) {\n\t\tClassItem root = item(\"Root\", klass,\n\t\t\t\ti -> \"\",\n\t\t\t\ti -> null,\n\t\t\t\ti -> null);\n\t\troot.item(\"Major version\", klass,\n\t\t\t\ti -> String.valueOf(i.getVersionMajor()),\n\t\t\t\ti -> new FontIconView(CarbonIcons.NUMBER_0),\n\t\t\t\ti -> null);\n\t\troot.item(\"Minor version\", klass,\n\t\t\t\ti -> String.valueOf(i.getVersionMinor()),\n\t\t\t\ti -> new FontIconView(CarbonIcons.NUMBER_SMALL_0),\n\t\t\t\ti -> null);\n\t\troot.item(\"Access\", klass,\n\t\t\t\ti -> sortAndToString(AccessFlag.Type.CLASS, i.getAccess()),\n\t\t\t\ti -> Icons.getVisibilityIcon(i.getAccess()),\n\t\t\t\ti -> null);\n\t\troot.item(\"This\", klass,\n\t\t\t\ti -> i.getName(),\n\t\t\t\ti -> classGraphic(i.getName()),\n\t\t\t\ti -> null);\n\t\troot.item(\"Super\", klass,\n\t\t\t\ti -> i.getSuperName(),\n\t\t\t\ti -> classGraphic(Objects.requireNonNullElse(i.getSuperName(), \"java/lang/Object\")),\n\t\t\t\ti -> null);\n\t\tClassItem interfaces = root.item(\"Interfaces\", klass.getInterfaceClasses(),\n\t\t\t\ti -> \"[\" + i.size() + \"]\",\n\t\t\t\ti -> Icons.getIconView(Icons.ARRAY),\n\t\t\t\ti -> null\n\t\t);\n\t\tfor (CpClass interfaceClass : klass.getInterfaceClasses())\n\t\t\tinterfaces.item(\"Interface\", interfaceClass,\n\t\t\t\t\ti -> i.getName().getText(),\n\t\t\t\t\ti -> classGraphic(i),\n\t\t\t\t\ti -> null);\n\t\tClassItem pool = root.item(\"Constant pool\", klass.getPool(),\n\t\t\t\ti -> \"[\" + i.size() + \"]\",\n\t\t\t\ti -> Icons.getIconView(Icons.ARRAY),\n\t\t\t\ti -> null\n\t\t);\n\t\tfor (CpEntry entry : klass.getPool())\n\t\t\tpool.item(\"[\" + entry.getIndex() + \"] \" + entry.getClass().getSimpleName(), entry,\n\t\t\t\t\ti -> cpToString(i),\n\t\t\t\t\ti -> cpToGraphic(i),\n\t\t\t\t\ti -> null);\n\t\tClassItem fields = root.item(\"Fields\", klass.getFields(),\n\t\t\t\ti -> \"[\" + i.size() + \"]\",\n\t\t\t\ti -> memberGraphic(null, false),\n\t\t\t\ti -> null\n\t\t);\n\t\tfor (Field field : klass.getFields()) {\n\t\t\tClassItem fieldItem = fields.item(\"Field\", field,\n\t\t\t\t\ti -> {\n\t\t\t\t\t\tString access = sortAndToString(AccessFlag.Type.FIELD, i.getAccess());\n\t\t\t\t\t\tif (!access.isEmpty())\n\t\t\t\t\t\t\taccess += ' ';\n\t\t\t\t\t\tString fieldName = i.getName().getText();\n\t\t\t\t\t\tString fieldType = i.getType().getText();\n\t\t\t\t\t\tString nameType = memberDisplay.getFieldDisplay(fieldName, fieldType);\n\t\t\t\t\t\treturn access + nameType;\n\t\t\t\t\t},\n\t\t\t\t\ti -> {\n\t\t\t\t\t\tString fieldName = i.getName().getText();\n\t\t\t\t\t\tString fieldType = i.getType().getText();\n\t\t\t\t\t\treturn memberGraphic(path.child(fieldName, fieldType), false);\n\t\t\t\t\t},\n\t\t\t\t\ti -> null\n\t\t\t);\n\t\t\tfieldItem.item(\"Access\", field,\n\t\t\t\t\ti -> sortAndToString(AccessFlag.Type.FIELD, i.getAccess()),\n\t\t\t\t\ti -> Icons.getVisibilityIcon(i.getAccess()),\n\t\t\t\t\ti -> null);\n\t\t\taddAttributes(fieldItem, field);\n\t\t}\n\t\tClassItem methods = root.item(\"Methods\", klass.getMethods(),\n\t\t\t\ti -> \"[\" + i.size() + \"]\",\n\t\t\t\ti -> memberGraphic(null, true),\n\t\t\t\ti -> null\n\t\t);\n\t\tfor (Method method : klass.getMethods()) {\n\t\t\tClassItem methodItem = methods.item(\"Method\", method,\n\t\t\t\t\ti -> {\n\t\t\t\t\t\tString access = sortAndToString(AccessFlag.Type.METHOD, i.getAccess());\n\t\t\t\t\t\tif (!access.isEmpty())\n\t\t\t\t\t\t\taccess += ' ';\n\t\t\t\t\t\tString fieldName = i.getName().getText();\n\t\t\t\t\t\tString fieldType = i.getType().getText();\n\t\t\t\t\t\tString nameType = memberDisplay.getFieldDisplay(fieldName, fieldType);\n\t\t\t\t\t\treturn access + nameType;\n\t\t\t\t\t},\n\t\t\t\t\ti -> {\n\t\t\t\t\t\tString fieldName = i.getName().getText();\n\t\t\t\t\t\tString fieldType = i.getType().getText();\n\t\t\t\t\t\treturn memberGraphic(path.child(fieldName, fieldType), true);\n\t\t\t\t\t},\n\t\t\t\t\ti -> null\n\t\t\t);\n\t\t\tmethodItem.item(\"Access\", method,\n\t\t\t\t\ti -> sortAndToString(AccessFlag.Type.METHOD, i.getAccess()),\n\t\t\t\t\ti -> Icons.getVisibilityIcon(i.getAccess()),\n\t\t\t\t\ti -> null);\n\t\t\taddAttributes(methodItem, method);\n\t\t}\n\t\taddAttributes(root, klass);\n\t\treturn root;\n\t}\n\n\t@Nonnull\n\tprivate ClassItem addAttributes(@Nonnull ClassItem parent, @Nonnull AttributeHolder holder) {\n\t\tClassItem child = parent.item(\"Attributes\", holder,\n\t\t\t\ti -> \"[\" + holder.getAttributes().size() + \"]\",\n\t\t\t\ti -> Icons.getIconView(Icons.ARRAY),\n\t\t\t\ti -> null);\n\t\tfor (Attribute attribute : holder.getAttributes())\n\t\t\taddAttribute(child, holder, attribute);\n\t\treturn child;\n\t}\n\n\t@Nonnull\n\tprivate ClassItem addAttribute(@Nonnull ClassItem parent, @Nonnull AttributeHolder holder, @Nonnull Attribute attribute) {\n\t\tClassItem child = parent.item(attribute.getName().getText(), attribute,\n\t\t\t\ti -> switch (i) {\n\t\t\t\t\tcase AnnotationDefaultAttribute attr -> elementValueToString(attr.getElementValue());\n\t\t\t\t\tcase AnnotationsAttribute attr -> \"[\" + attr.getAnnotations().size() + \"]\";\n\t\t\t\t\tcase BootstrapMethodsAttribute attr -> \"[\" + attr.getBootstrapMethods().size() + \"]\";\n\t\t\t\t\tcase CharacterRangeTableAttribute attr -> \"[\" + attr.getCharacterRangeTable().size() + \"]\";\n\t\t\t\t\tcase CodeAttribute attr -> \"maxLocals=\" + attr.getMaxLocals() + \", maxStack=\" + attr.getMaxStack()\n\t\t\t\t\t\t\t+ \", instructions[\" + attr.getInstructions().size() + \"]\";\n\t\t\t\t\tcase CompilationIdAttribute attr -> attr.getCompilationId().getText();\n\t\t\t\t\tcase ConstantValueAttribute attr -> cpToString(attr.getConstantValue());\n\t\t\t\t\tcase DefaultAttribute attr -> {\n\t\t\t\t\t\tbyte[] slice = Arrays.copyOf(attr.getData(), Math.min(16, attr.getData().length));\n\t\t\t\t\t\tStringBuilder sb = new StringBuilder();\n\t\t\t\t\t\tfor (byte b : slice)\n\t\t\t\t\t\t\tsb.append(HexUtil.strFormat00(b)).append(' ');\n\t\t\t\t\t\tyield sb.toString().trim();\n\t\t\t\t\t}\n\t\t\t\t\tcase DeprecatedAttribute attr -> \"\";\n\t\t\t\t\tcase EnclosingMethodAttribute attr -> {\n\t\t\t\t\t\tCpClass classEntry = attr.getClassEntry();\n\t\t\t\t\t\tCpNameType methodEntry = attr.getMethodEntry();\n\t\t\t\t\t\tString owner = classEntry.getName().getText();\n\t\t\t\t\t\tif (methodEntry != null) {\n\t\t\t\t\t\t\tString methodName = methodEntry.getName().getText();\n\t\t\t\t\t\t\tString methodType = methodEntry.getType().getText();\n\t\t\t\t\t\t\tyield owner + \".\" + memberDisplay.getMethodDisplay(methodName, methodType);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield owner;\n\t\t\t\t\t}\n\t\t\t\t\tcase ExceptionsAttribute attr -> \"[\" + attr.getExceptionTable().size() + \"]\";\n\t\t\t\t\tcase InnerClassesAttribute attr -> \"[\" + attr.getInnerClasses().size() + \"]\";\n\t\t\t\t\tcase LineNumberTableAttribute attr -> \"[\" + attr.getEntries().size() + \"]\";\n\t\t\t\t\tcase LocalVariableTableAttribute attr -> \"[\" + attr.getEntries().size() + \"]\";\n\t\t\t\t\tcase LocalVariableTypeTableAttribute attr -> \"[\" + attr.getEntries().size() + \"]\";\n\t\t\t\t\tcase MethodParametersAttribute attr -> \"[\" + attr.getParameters().size() + \"]\";\n\t\t\t\t\tcase ModuleAttribute attr -> attr.getModule().getName().getText();\n\t\t\t\t\tcase ModuleHashesAttribute attr -> attr.getAlgorithmName().getText();\n\t\t\t\t\tcase ModuleMainClassAttribute attr -> attr.getMainClass().getName().getText();\n\t\t\t\t\tcase ModulePackagesAttribute attr -> \"[\" + attr.getPackages().size() + \"]\";\n\t\t\t\t\tcase ModuleResolutionAttribute attr -> \"mask=\" + Integer.toBinaryString(attr.getFlags());\n\t\t\t\t\tcase ModuleTargetAttribute attr -> attr.getPlatformName().getText();\n\t\t\t\t\tcase NestHostAttribute attr -> attr.getHostClass().getName().getText();\n\t\t\t\t\tcase NestMembersAttribute attr -> \"[\" + attr.getMemberClasses().size() + \"]\";\n\t\t\t\t\tcase ParameterAnnotationsAttribute attr -> \"[\" + attr.getParameterAnnotations().size() + \"]\";\n\t\t\t\t\tcase PermittedClassesAttribute attr -> \"[\" + attr.getClasses().size() + \"]\";\n\t\t\t\t\tcase RecordAttribute attr -> \"[\" + attr.getComponents().size() + \"]\";\n\t\t\t\t\tcase SignatureAttribute attr -> attr.getSignature().getText();\n\t\t\t\t\tcase SourceDebugExtensionAttribute attr -> {\n\t\t\t\t\t\tbyte[] slice = Arrays.copyOf(attr.getDebugExtension(), Math.min(16, attr.getDebugExtension().length));\n\t\t\t\t\t\tStringBuilder sb = new StringBuilder();\n\t\t\t\t\t\tfor (byte b : slice)\n\t\t\t\t\t\t\tsb.append(HexUtil.strFormat00(b)).append(' ');\n\t\t\t\t\t\tyield sb.toString().trim();\n\t\t\t\t\t}\n\t\t\t\t\tcase SourceFileAttribute attr -> attr.getSourceFilename().getText();\n\t\t\t\t\tcase SourceIdAttribute attr -> attr.getSourceId().getText();\n\t\t\t\t\tcase StackMapTableAttribute attr -> \"[\" + attr.getFrames().size() + \"]\";\n\t\t\t\t\tcase SyntheticAttribute attr -> \"\";\n\t\t\t\t},\n\t\t\t\ti -> switch (i) {\n\t\t\t\t\tcase AnnotationDefaultAttribute attr -> Icons.getIconView(Icons.ANNOTATION);\n\t\t\t\t\tcase AnnotationsAttribute attr -> Icons.getIconView(Icons.ANNOTATION);\n\t\t\t\t\tcase BootstrapMethodsAttribute attr -> new FontIconView(CarbonIcons.CODE);\n\t\t\t\t\tcase CharacterRangeTableAttribute attr -> new FontIconView(CarbonIcons.QUERY_QUEUE);\n\t\t\t\t\tcase CodeAttribute attr -> new FontIconView(CarbonIcons.CODE);\n\t\t\t\t\tcase CompilationIdAttribute attr -> new FontIconView(CarbonIcons.LICENSE_MAINTENANCE);\n\t\t\t\t\tcase ConstantValueAttribute attr -> new FontIconView(CarbonIcons.OPERATION);\n\t\t\t\t\tcase DefaultAttribute attr -> new FontIconView(CarbonIcons.UNKNOWN_FILLED);\n\t\t\t\t\tcase DeprecatedAttribute attr -> new FontIconView(CarbonIcons.WARNING_ALT_FILLED, Color.YELLOW);\n\t\t\t\t\tcase EnclosingMethodAttribute attr -> {\n\t\t\t\t\t\tCpClass classEntry = attr.getClassEntry();\n\t\t\t\t\t\tCpNameType methodEntry = attr.getMethodEntry();\n\t\t\t\t\t\tString owner = classEntry.getName().getText();\n\t\t\t\t\t\tif (methodEntry != null) {\n\t\t\t\t\t\t\tString methodName = methodEntry.getName().getText();\n\t\t\t\t\t\t\tString methodType = methodEntry.getType().getText();\n\t\t\t\t\t\t\tyield memberGraphic(owner, methodName, methodType);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tyield classGraphic(owner);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcase ExceptionsAttribute attr -> new FontIconView(CarbonIcons.ERROR_FILLED, Color.RED);\n\t\t\t\t\tcase InnerClassesAttribute attr -> new FontIconView(CarbonIcons.COPY);\n\t\t\t\t\tcase LineNumberTableAttribute attr -> new FontIconView(CarbonIcons.SPINE_LABEL);\n\t\t\t\t\tcase LocalVariableTableAttribute attr -> new FontIconView(CarbonIcons.SIGMA);\n\t\t\t\t\tcase LocalVariableTypeTableAttribute attr -> new FontIconView(CarbonIcons.SIGMA);\n\t\t\t\t\tcase MethodParametersAttribute attr -> new FontIconView(CarbonIcons.LETTER_PP);\n\t\t\t\t\tcase ModuleAttribute attr -> new FontIconView(CarbonIcons.CATEGORIES);\n\t\t\t\t\tcase ModuleHashesAttribute attr -> new FontIconView(CarbonIcons.LOCKED);\n\t\t\t\t\tcase ModuleMainClassAttribute attr -> classGraphic(attr.getMainClass().getName().getText());\n\t\t\t\t\tcase ModulePackagesAttribute attr -> Icons.getIconView(Icons.FOLDER_PACKAGE);\n\t\t\t\t\tcase ModuleResolutionAttribute attr -> new FontIconView(CarbonIcons.SEARCH);\n\t\t\t\t\tcase ModuleTargetAttribute attr -> new FontIconView(CarbonIcons.LAPTOP);\n\t\t\t\t\tcase NestHostAttribute attr -> classGraphic(attr.getHostClass().getName().getText());\n\t\t\t\t\tcase NestMembersAttribute attr -> new FontIconView(CarbonIcons.CATEGORIES);\n\t\t\t\t\tcase ParameterAnnotationsAttribute attr -> Icons.getIconView(Icons.ANNOTATION);\n\t\t\t\t\tcase PermittedClassesAttribute attr -> new FontIconView(CarbonIcons.CATEGORIES);\n\t\t\t\t\tcase RecordAttribute attr -> new FontIconView(CarbonIcons.LIST_BOXES);\n\t\t\t\t\tcase SignatureAttribute attr -> {\n\t\t\t\t\t\tString text = attr.getSignature().getText();\n\t\t\t\t\t\tyield memberGraphic(null, !text.isEmpty() && text.charAt(0) == '(');\n\t\t\t\t\t}\n\t\t\t\t\tcase SourceDebugExtensionAttribute attr -> new FontIconView(CarbonIcons.DEBUG);\n\t\t\t\t\tcase SourceFileAttribute attr -> new FontIconView(CarbonIcons.CHAT);\n\t\t\t\t\tcase SourceIdAttribute attr -> new FontIconView(CarbonIcons.LICENSE_MAINTENANCE);\n\t\t\t\t\tcase StackMapTableAttribute attr -> new FontIconView(CarbonIcons.CHART_STACKED);\n\t\t\t\t\tcase SyntheticAttribute attr -> new FontIconView(CarbonIcons.SETTINGS);\n\t\t\t\t},\n\t\t\t\ti -> null);\n\n\t\tswitch (attribute) {\n\t\t\tcase AnnotationDefaultAttribute attr -> {\n\t\t\t\taddElementValue(child, \"Default value\", attr.getElementValue());\n\t\t\t}\n\t\t\tcase AnnotationsAttribute attr -> {\n\t\t\t\tList<Annotation> annotations = attr.getAnnotations();\n\t\t\t\tfor (int i = 0; i < annotations.size(); i++) {\n\t\t\t\t\tAnnotation annotation = annotations.get(i);\n\t\t\t\t\taddAnnotation(child, \"[\" + i + \"]\", annotation);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase BootstrapMethodsAttribute attr -> {\n\t\t\t\tList<BootstrapMethodsAttribute.BootstrapMethod> bootstrapMethods = attr.getBootstrapMethods();\n\t\t\t\tfor (int j = 0; j < bootstrapMethods.size(); j++) {\n\t\t\t\t\tBootstrapMethodsAttribute.BootstrapMethod bsm = bootstrapMethods.get(j);\n\t\t\t\t\tClassItem bsmItem = child.item(\"[\" + j + \"]\", bsm,\n\t\t\t\t\t\t\ti -> cpToString(i.getBsmMethodRef()) + \" args[\" + i.getArgs().size() + \"]\",\n\t\t\t\t\t\t\ti -> cpToGraphic(i.getBsmMethodRef()),\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t\tbsmItem.item(\"Method reference\", bsm.getBsmMethodRef(),\n\t\t\t\t\t\t\ti -> cpToString(i),\n\t\t\t\t\t\t\ti -> cpToGraphic(i),\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t\tList<CpEntry> args = bsm.getArgs();\n\t\t\t\t\tClassItem arguments = bsmItem.item(\"Arguments\", args,\n\t\t\t\t\t\t\ti -> \"[\" + i + \"]\",\n\t\t\t\t\t\t\ti -> Icons.getIconView(Icons.ARRAY),\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t\tfor (int k = 0; k < args.size(); k++) {\n\t\t\t\t\t\tCpEntry arg = args.get(k);\n\t\t\t\t\t\targuments.item(\"[\" + k + \"]\", arg,\n\t\t\t\t\t\t\t\ti -> cpToString(i),\n\t\t\t\t\t\t\t\ti -> cpToGraphic(i),\n\t\t\t\t\t\t\t\ti -> null);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase CharacterRangeTableAttribute attr -> {\n\t\t\t\tList<CharacterRangeTableAttribute.CharacterRangeInfo> rangeInfos = attr.getCharacterRangeTable();\n\t\t\t\tfor (int j = 0; j < rangeInfos.size(); j++) {\n\t\t\t\t\tchild.item(\"[\" + j + \"]\", rangeInfos.get(j),\n\t\t\t\t\t\t\ti -> \"charRange=[\" + i.getCharacterRangeStart() + \"-\" + i.getCharacterRangeEnd() + \"] \" +\n\t\t\t\t\t\t\t\t\t\"codeRange=[\" + i.getStartPc() + \"-\" + i.getEndPc() + \"] \" +\n\t\t\t\t\t\t\t\t\t\"flags=\" + Integer.toBinaryString(i.getFlags()),\n\t\t\t\t\t\t\ti -> new FontIconView(CarbonIcons.NUMBER_0),\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase CodeAttribute attr -> {\n\t\t\t\tList<Instruction> instructions = attr.getInstructions();\n\t\t\t\tClassItem instructionsItem = child.item(\"Instructions\", instructions,\n\t\t\t\t\t\ti -> \"[\" + i.size() + \"]\",\n\t\t\t\t\t\ti -> Icons.getIconView(Icons.ARRAY),\n\t\t\t\t\t\ti -> null);\n\t\t\t\tfor (int j = 0; j < instructions.size(); j++) {\n\t\t\t\t\tInstruction instruction = instructions.get(j);\n\t\t\t\t\tString insnName = AsmInsnUtil.getInsnName(instruction.getOpcode());\n\t\t\t\t\tinstructionsItem.item(\"[\" + j + \"]\", instruction,\n\t\t\t\t\t\t\ti -> switch (i) {\n\t\t\t\t\t\t\t\tcase BasicInstruction insn -> insnName;\n\t\t\t\t\t\t\t\tcase CpRefInstruction insn -> insnName + \" \" + cpToString(insn.getEntry());\n\t\t\t\t\t\t\t\tcase IincInstruction insn ->\n\t\t\t\t\t\t\t\t\t\tinsnName + \" \" + insn.getVar() + \" += \" + insn.getIncrement();\n\t\t\t\t\t\t\t\tcase IntOperandInstruction insn -> insnName + \" \" + insn.getOperand();\n\t\t\t\t\t\t\t\tcase MultiANewArrayInstruction insn ->\n\t\t\t\t\t\t\t\t\t\tinsnName + \" \" + cpToString(insn.getDescriptor()) + \" x\" + insn.getDimensions();\n\t\t\t\t\t\t\t\tcase LookupSwitchInstruction insn -> insnName; // TODO: Flesh out\n\t\t\t\t\t\t\t\tcase TableSwitchInstruction insn -> insnName;\n\t\t\t\t\t\t\t\tcase WideInstruction insn -> insnName;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ti -> switch (i) {\n\t\t\t\t\t\t\t\tcase BasicInstruction insn -> {\n\t\t\t\t\t\t\t\t\tint op = i.getOpcode();\n\t\t\t\t\t\t\t\t\tif (op == Opcodes.NOP)\n\t\t\t\t\t\t\t\t\t\tyield new FontIconView(CarbonIcons.SMOOTHING);\n\n\t\t\t\t\t\t\t\t\t// Constant numbers\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.SIPUSH)\n\t\t\t\t\t\t\t\t\t\tyield new FontIconView(CarbonIcons.STRING_INTEGER);\n\n\t\t\t\t\t\t\t\t\t// Var/array loads\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.ALOAD_3)\n\t\t\t\t\t\t\t\t\t\tyield SVG.ofIconFile(SVG.REF_READ);\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.SALOAD)\n\t\t\t\t\t\t\t\t\t\tyield Icons.getIconView(Icons.ARRAY);\n\n\t\t\t\t\t\t\t\t\t// Var/array stores\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.ASTORE_3)\n\t\t\t\t\t\t\t\t\t\tyield SVG.ofIconFile(SVG.REF_WRITE);\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.SASTORE)\n\t\t\t\t\t\t\t\t\t\tyield Icons.getIconView(Icons.ARRAY);\n\n\t\t\t\t\t\t\t\t\t// Stack\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.SWAP)\n\t\t\t\t\t\t\t\t\t\tyield new FontIconView(CarbonIcons.STACKED_SCROLLING_1);\n\n\t\t\t\t\t\t\t\t\t// Math operations\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.LXOR)\n\t\t\t\t\t\t\t\t\t\tyield new FontIconView(CarbonIcons.CALCULATOR);\n\n\t\t\t\t\t\t\t\t\t// Primitive conversions\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.I2S)\n\t\t\t\t\t\t\t\t\t\tyield new FontIconView(CarbonIcons.DATA_SHARE);\n\n\t\t\t\t\t\t\t\t\t// Stack value comparisons\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.DCMPG)\n\t\t\t\t\t\t\t\t\t\tyield new FontIconView(CarbonIcons.CALCULATOR);\n\n\t\t\t\t\t\t\t\t\t// Return\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.RETURN)\n\t\t\t\t\t\t\t\t\t\tyield new FontIconView(CarbonIcons.EXIT, Color.STEELBLUE);\n\n\t\t\t\t\t\t\t\t\t// Exception\n\t\t\t\t\t\t\t\t\tif (op == Opcodes.ATHROW)\n\t\t\t\t\t\t\t\t\t\tyield Icons.getIconView(Icons.CLASS_EXCEPTION);\n\t\t\t\t\t\t\t\t\tif (op == Opcodes.ARRAYLENGTH)\n\t\t\t\t\t\t\t\t\t\tyield Icons.getIconView(Icons.ARRAY);\n\n\t\t\t\t\t\t\t\t\t// Monitor\n\t\t\t\t\t\t\t\t\tyield new FontIconView(CarbonIcons.MAGNIFY); // No eye icon?\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase CpRefInstruction insn -> cpToGraphic(insn.getEntry());\n\t\t\t\t\t\t\t\tcase IincInstruction insn -> new FontIconView(CarbonIcons.ADD);\n\t\t\t\t\t\t\t\tcase IntOperandInstruction insn -> {\n\t\t\t\t\t\t\t\t\tint op = i.getOpcode();\n\n\t\t\t\t\t\t\t\t\t// Constant numbers\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.SIPUSH)\n\t\t\t\t\t\t\t\t\t\tyield new FontIconView(CarbonIcons.STRING_INTEGER);\n\n\t\t\t\t\t\t\t\t\t// Var loads/stores\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.ALOAD_3)\n\t\t\t\t\t\t\t\t\t\tyield SVG.ofIconFile(SVG.REF_READ);\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.ASTORE_3)\n\t\t\t\t\t\t\t\t\t\tyield SVG.ofIconFile(SVG.REF_WRITE);\n\n\t\t\t\t\t\t\t\t\t// Control flow\n\t\t\t\t\t\t\t\t\tif (op <= Opcodes.RET)\n\t\t\t\t\t\t\t\t\t\tyield new FontIconView(CarbonIcons.BRANCH);\n\n\t\t\t\t\t\t\t\t\t// Array\n\t\t\t\t\t\t\t\t\tif (op == Opcodes.NEWARRAY)\n\t\t\t\t\t\t\t\t\t\tyield Icons.getIconView(Icons.ARRAY);\n\n\t\t\t\t\t\t\t\t\t// Remaining control flow\n\t\t\t\t\t\t\t\t\tyield new FontIconView(CarbonIcons.BRANCH);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcase MultiANewArrayInstruction insn -> Icons.getIconView(Icons.ARRAY);\n\t\t\t\t\t\t\t\tcase LookupSwitchInstruction insn -> new FontIconView(CarbonIcons.BRANCH);\n\t\t\t\t\t\t\t\tcase TableSwitchInstruction insn -> new FontIconView(CarbonIcons.BRANCH);\n\t\t\t\t\t\t\t\tcase WideInstruction insn -> new FontIconView(CarbonIcons.DRAG_HORIZONTAL);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t\tList<CodeAttribute.ExceptionTableEntry> exceptionTable = attr.getExceptionTable();\n\t\t\t\tClassItem exceptionTableItem = child.item(\"Exceptions\", exceptionTable,\n\t\t\t\t\t\ti -> \"[\" + i.size() + \"]\",\n\t\t\t\t\t\ti -> Icons.getIconView(Icons.ARRAY),\n\t\t\t\t\t\ti -> null);\n\t\t\t\tfor (int j = 0; j < exceptionTable.size(); j++) {\n\t\t\t\t\tCodeAttribute.ExceptionTableEntry exception = exceptionTable.get(j);\n\t\t\t\t\texceptionTableItem.item(\"[\" + j + \"]\", exception,\n\t\t\t\t\t\t\ti -> {\n\t\t\t\t\t\t\t\tString owner = i.getCatchType() == null ?\n\t\t\t\t\t\t\t\t\t\t\"java/lang/Throwable\" :\n\t\t\t\t\t\t\t\t\t\ti.getCatchType().getName().getText();\n\t\t\t\t\t\t\t\treturn owner + \"[\" + i.getStartPc() + \":\" + i.getEndPc() +\n\t\t\t\t\t\t\t\t\t\t\"] handler[\" + i.getHandlerPc() + \"]\";\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ti -> Icons.getIconView(Icons.CLASS_EXCEPTION),\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase CompilationIdAttribute attr -> { /* single value */ }\n\t\t\tcase ConstantValueAttribute attr -> { /* single value */ }\n\t\t\tcase DefaultAttribute attr -> { /* unknown */ }\n\t\t\tcase DeprecatedAttribute attr -> { /* empty */ }\n\t\t\tcase EnclosingMethodAttribute attr -> {\n\t\t\t\tCpClass classEntry = attr.getClassEntry();\n\t\t\t\tCpNameType methodEntry = attr.getMethodEntry();\n\t\t\t\tchild.item(\"Class\", classEntry,\n\t\t\t\t\t\ti -> cpToString(i),\n\t\t\t\t\t\ti -> cpToGraphic(i),\n\t\t\t\t\t\ti -> null);\n\t\t\t\tif (methodEntry != null)\n\t\t\t\t\tchild.item(\"Method\", methodEntry,\n\t\t\t\t\t\t\ti -> cpToString(i),\n\t\t\t\t\t\t\ti -> cpToGraphic(i),\n\t\t\t\t\t\t\ti -> null);\n\t\t\t}\n\t\t\tcase ExceptionsAttribute attr -> {\n\t\t\t\tList<CpClass> exceptionTable = attr.getExceptionTable();\n\t\t\t\tfor (int j = 0; j < exceptionTable.size(); j++) {\n\t\t\t\t\tchild.item(\"[\" + j + \"]\", exceptionTable.get(j),\n\t\t\t\t\t\t\ti -> cpToString(i),\n\t\t\t\t\t\t\ti -> cpToGraphic(i),\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase InnerClassesAttribute attr -> {\n\t\t\t\tList<InnerClassesAttribute.InnerClass> innerClasses = attr.getInnerClasses();\n\t\t\t\tfor (int j = 0; j < innerClasses.size(); j++) {\n\t\t\t\t\t// Technically more data to show, but eh...\n\t\t\t\t\tchild.item(\"[\" + j + \"]\", innerClasses.get(j),\n\t\t\t\t\t\t\ti -> cpToString(i.getInnerClassInfo()),\n\t\t\t\t\t\t\ti -> cpToGraphic(i.getInnerClassInfo()),\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase LineNumberTableAttribute attr -> {\n\t\t\t\tList<LineNumberTableAttribute.LineEntry> entries = attr.getEntries();\n\t\t\t\tfor (int j = 0; j < entries.size(); j++) {\n\t\t\t\t\tchild.item(\"[\" + j + \"]\", entries.get(j),\n\t\t\t\t\t\t\ti -> \"Line \" + i.getLine() + \" : offset=\" + i.getStartPc(),\n\t\t\t\t\t\t\ti -> new FontIconView(CarbonIcons.NUMBER_0),\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase LocalVariableTableAttribute attr -> {\n\t\t\t\tList<LocalVariableTableAttribute.VarEntry> entries = attr.getEntries();\n\t\t\t\tfor (int j = 0; j < entries.size(); j++) {\n\t\t\t\t\t// TODO: Children for:\n\t\t\t\t\t//  - index\n\t\t\t\t\t//  - name\n\t\t\t\t\t//  - desc\n\t\t\t\t\t//  - range (start + length)\n\t\t\t\t\tchild.item(\"[\" + j + \"]\", entries.get(j),\n\t\t\t\t\t\t\ti -> {\n\t\t\t\t\t\t\t\tString name = i.getName().getText();\n\t\t\t\t\t\t\t\tString desc = i.getDesc().getText();\n\t\t\t\t\t\t\t\tif (!Types.isValidDesc(desc))\n\t\t\t\t\t\t\t\t\tdesc = Types.OBJECT_TYPE.getDescriptor();\n\t\t\t\t\t\t\t\treturn i.getIndex() + \" : \" + memberDisplay.getFieldDisplay(name, desc);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ti -> {\n\t\t\t\t\t\t\t\tString desc = i.getDesc().getText();\n\t\t\t\t\t\t\t\tif (desc.isEmpty())\n\t\t\t\t\t\t\t\t\treturn Icons.getIconView(Icons.PRIMITIVE);\n\t\t\t\t\t\t\t\treturn switch (desc.charAt(0)) {\n\t\t\t\t\t\t\t\t\tcase '[' -> Icons.getIconView(Icons.ARRAY);\n\t\t\t\t\t\t\t\t\tcase 'L' -> Icons.getIconView(Icons.CLASS);\n\t\t\t\t\t\t\t\t\tdefault -> Icons.getIconView(Icons.PRIMITIVE);\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase LocalVariableTypeTableAttribute attr -> {\n\t\t\t\tList<LocalVariableTypeTableAttribute.VarTypeEntry> entries = attr.getEntries();\n\t\t\t\tfor (int j = 0; j < entries.size(); j++) {\n\t\t\t\t\t// TODO: Children for:\n\t\t\t\t\t//  - index\n\t\t\t\t\t//  - name\n\t\t\t\t\t//  - desc\n\t\t\t\t\t//  - range (start + length)\n\t\t\t\t\tchild.item(\"[\" + j + \"]\", entries.get(j),\n\t\t\t\t\t\t\ti -> {\n\t\t\t\t\t\t\t\tString name = i.getName().getText();\n\t\t\t\t\t\t\t\tString signature = i.getSignature().getText();\n\t\t\t\t\t\t\t\tif (!Types.isValidFieldSignature(signature))\n\t\t\t\t\t\t\t\t\tsignature = Types.OBJECT_TYPE.getDescriptor();\n\t\t\t\t\t\t\t\treturn i.getIndex() + \" : \" + memberDisplay.getFieldDisplay(name, signature);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ti -> {\n\t\t\t\t\t\t\t\tString signature = i.getSignature().getText();\n\t\t\t\t\t\t\t\tif (signature.isEmpty())\n\t\t\t\t\t\t\t\t\treturn Icons.getIconView(Icons.PRIMITIVE);\n\t\t\t\t\t\t\t\treturn switch (signature.charAt(0)) {\n\t\t\t\t\t\t\t\t\tcase '[' -> Icons.getIconView(Icons.ARRAY);\n\t\t\t\t\t\t\t\t\tcase 'L' -> Icons.getIconView(Icons.CLASS);\n\t\t\t\t\t\t\t\t\tdefault -> Icons.getIconView(Icons.PRIMITIVE);\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase MethodParametersAttribute attr -> {\n\t\t\t\tList<MethodParametersAttribute.Parameter> parameters = attr.getParameters();\n\t\t\t\tfor (int j = 0; j < parameters.size(); j++) {\n\t\t\t\t\tchild.item(\"[\" + j + \"]\", parameters.get(j),\n\t\t\t\t\t\t\ti -> i.getName().getText(),\n\t\t\t\t\t\t\ti -> null,\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase ModuleAttribute attr -> {}  // TODO: Load of crap\n\t\t\tcase ModuleHashesAttribute attr -> {}  // TODO: Display algo + bytes\n\t\t\tcase ModuleMainClassAttribute attr -> { /* single value */ }\n\t\t\tcase ModulePackagesAttribute attr -> {\n\t\t\t\tList<CpPackage> packages = attr.getPackages();\n\t\t\t\tfor (int j = 0; j < packages.size(); j++) {\n\t\t\t\t\tchild.item(\"[\" + j + \"]\", packages.get(j),\n\t\t\t\t\t\t\ti -> cpToString(i.getPackageName()),\n\t\t\t\t\t\t\ti -> Icons.getIconView(Icons.FOLDER_PACKAGE),\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase ModuleResolutionAttribute attr -> { /* single value */ }\n\t\t\tcase ModuleTargetAttribute attr -> { /* single value */ }\n\t\t\tcase NestHostAttribute attr -> { /* single value */ }\n\t\t\tcase NestMembersAttribute attr -> {\n\t\t\t\tList<CpClass> memberClasses = attr.getMemberClasses();\n\t\t\t\tfor (int j = 0; j < memberClasses.size(); j++) {\n\t\t\t\t\tchild.item(\"[\" + j + \"]\", memberClasses.get(j),\n\t\t\t\t\t\t\ti -> cpToString(i.getName()),\n\t\t\t\t\t\t\ti -> classGraphic(i.getName().getText()),\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase ParameterAnnotationsAttribute attr -> {\n\t\t\t\tattr.getParameterAnnotations().forEach((paramIndex, annotations) -> {\n\t\t\t\t\tClassItem param = child.item(\"[\" + paramIndex + \"]\", paramIndex,\n\t\t\t\t\t\t\ti -> \"Parameter \" + i + \" [\" + annotations.size() + \"]\",\n\t\t\t\t\t\t\ti -> null,\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t\tfor (int i = 0; i < annotations.size(); i++) {\n\t\t\t\t\t\tAnnotation annotation = annotations.get(i);\n\t\t\t\t\t\taddAnnotation(param, \"[\" + i + \"]\", annotation);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\tcase PermittedClassesAttribute attr -> {\n\t\t\t\tList<CpClass> permittedClasses = attr.getClasses();\n\t\t\t\tfor (int j = 0; j < permittedClasses.size(); j++) {\n\t\t\t\t\tchild.item(\"[\" + j + \"]\", permittedClasses.get(j),\n\t\t\t\t\t\t\ti -> cpToString(i.getName()),\n\t\t\t\t\t\t\ti -> classGraphic(i.getName().getText()),\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase RecordAttribute attr -> {\n\t\t\t\tList<RecordAttribute.RecordComponent> components = attr.getComponents();\n\t\t\t\tfor (int j = 0; j < components.size(); j++) {\n\t\t\t\t\tchild.item(\"[\" + j + \"]\", components.get(j),\n\t\t\t\t\t\t\ti -> cpToString(i.getName()),\n\t\t\t\t\t\t\ti -> memberGraphic(path.child(i.getName().getText(), i.getDesc().getText()), false),\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase SignatureAttribute attr -> { /* single value */ }\n\t\t\tcase SourceDebugExtensionAttribute attr -> { /* single value */ }\n\t\t\tcase SourceFileAttribute attr -> { /* single value */ }\n\t\t\tcase SourceIdAttribute attr -> { /* single value */ }\n\t\t\tcase StackMapTableAttribute attr -> {\n\t\t\t\tList<StackMapTableAttribute.StackMapFrame> frames = attr.getFrames();\n\t\t\t\tfor (int j = 0; j < frames.size(); j++) {\n\t\t\t\t\tchild.item(\"[\" + j + \"]\", frames.get(j),\n\t\t\t\t\t\t\ti -> switch (i) {\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.AppendFrame appendFrame -> \"APPEND: \" + i.getFrameType();\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.ChopFrame chopFrame -> \"CHOP: \" + i.getFrameType();\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.FullFrame fullFrame -> \"FULL: \" + i.getFrameType();\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.SameFrame sameFrame -> \"SAME: \" + i.getFrameType();\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.SameFrameExtended sameFrameExtended ->\n\t\t\t\t\t\t\t\t\t\t\"SAME_EXTENDED: \" + i.getFrameType();\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.SameLocalsOneStackItem sameLocalsOneStackItem ->\n\t\t\t\t\t\t\t\t\t\t\"SAME_LOCALS_ONE_STACK: \" + i.getFrameType();\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.SameLocalsOneStackItemExtended sameLocalsOneStackItemExtended ->\n\t\t\t\t\t\t\t\t\t\t\"SAME_LOCALS_ONE_STACK_EXTENDED: \" + i.getFrameType();\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ti -> switch (i) {\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.AppendFrame appendFrame ->\n\t\t\t\t\t\t\t\t\t\tnew FontIconView(CarbonIcons.SUB_VOLUME);\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.ChopFrame chopFrame ->\n\t\t\t\t\t\t\t\t\t\tnew FontIconView(CarbonIcons.CUT_IN_HALF);\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.FullFrame fullFrame -> new FontIconView(CarbonIcons.STOP);\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.SameFrame sameFrame -> new FontIconView(CarbonIcons.COPY);\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.SameFrameExtended sameFrameExtended ->\n\t\t\t\t\t\t\t\t\t\tnew FontIconView(CarbonIcons.COPY);\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.SameLocalsOneStackItem sameLocalsOneStackItem ->\n\t\t\t\t\t\t\t\t\t\tnew FontIconView(CarbonIcons.COPY);\n\t\t\t\t\t\t\t\tcase StackMapTableAttribute.SameLocalsOneStackItemExtended sameLocalsOneStackItemExtended ->\n\t\t\t\t\t\t\t\t\t\tnew FontIconView(CarbonIcons.COPY);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ti -> null);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcase SyntheticAttribute attr -> { /* empty */ }\n\t\t}\n\n\t\tif (attribute instanceof AttributeHolder nestedHolder)\n\t\t\taddAttributes(child, nestedHolder);\n\n\t\treturn child;\n\t}\n\n\t@Nonnull\n\tprivate ClassItem addElementValue(@Nonnull ClassItem parentItem, @Nonnull String prefix, @Nonnull ElementValue value) {\n\t\tClassItem child = parentItem.item(prefix, value,\n\t\t\t\ti -> elementValueToString(i),\n\t\t\t\ti -> switch (i) {\n\t\t\t\t\tcase AnnotationElementValue subValue -> Icons.getIconView(Icons.ANNOTATION);\n\t\t\t\t\tcase ArrayElementValue subValue -> Icons.getIconView(Icons.ARRAY);\n\t\t\t\t\tcase ClassElementValue subValue -> classGraphic(subValue.getClassEntry().getText());\n\t\t\t\t\tcase EnumElementValue subValue -> Icons.getIconView(Icons.ENUM);\n\t\t\t\t\tcase PrimitiveElementValue subValue -> cpToGraphic(subValue.getValue());\n\t\t\t\t\tcase Utf8ElementValue subValue -> cpToGraphic(subValue.getValue());\n\t\t\t\t},\n\t\t\t\ti -> null);\n\n\t\tif (value instanceof AnnotationElementValue subValue) {\n\t\t\tAnnotation annotation = subValue.getAnnotation();\n\t\t\taddAnnotation(child, \"Annotation\", annotation);\n\t\t} else if (value instanceof ArrayElementValue subValue) {\n\t\t\tList<ElementValue> array = subValue.getArray();\n\t\t\tfor (int i = 0; i < array.size(); i++) {\n\t\t\t\tElementValue arrayValue = array.get(i);\n\t\t\t\taddElementValue(child, \"Array [\" + i + \"]\", arrayValue);\n\t\t\t}\n\t\t}\n\n\t\treturn child;\n\t}\n\n\t@Nonnull\n\tprivate ClassItem addAnnotation(@Nonnull ClassItem parentItem, @Nonnull String prefix, @Nonnull Annotation annotation) {\n\t\tClassItem item = parentItem.item(prefix, annotation,\n\t\t\t\ti -> i.getType().getText(),\n\t\t\t\ti -> Icons.getIconView(Icons.ANNOTATION),\n\t\t\t\ti -> null);\n\t\tannotation.getValues().forEach((key, value) -> {\n\t\t\t// We *could* allow changing annotation value-pair names if we tweak this a little bit...\n\t\t\taddElementValue(item, key.getText(), value);\n\t\t});\n\t\treturn item;\n\t}\n\n\t@Override\n\tpublic void requestFocus(@Nonnull ClassMember member) {\n\t\tif (getCenter() instanceof TreeView<?> tv) {\n\t\t\tTreeItem<ClassElement> root = Unchecked.cast(tv.getRoot());\n\t\t\tQueue<TreeItem<ClassElement>> queue = new ArrayDeque<>();\n\t\t\tqueue.add(root);\n\t\t\twhile (!queue.isEmpty()) {\n\t\t\t\tTreeItem<ClassElement> item = queue.remove();\n\t\t\t\tClassElement element = item.getValue();\n\t\t\t\tif (element instanceof LazyClassElement<?> lazyElement\n\t\t\t\t\t\t&& lazyElement.getElement() instanceof software.coley.cafedude.classfile.ClassMember itemMember) {\n\t\t\t\t\tif (member.getName().equals(itemMember.getName().getText())\n\t\t\t\t\t\t\t&& member.getDescriptor().equals(itemMember.getType().getText())) {\n\t\t\t\t\t\tTreeItems.expandParents(item);\n\t\t\t\t\t\ttv.getSelectionModel().select(Unchecked.cast(item));\n\t\t\t\t\t\ttv.scrollTo(tv.getRow(Unchecked.cast(item)));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tqueue.addAll(item.getChildren());\n\t\t\t}\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassPathNode getClassPath() {\n\t\treturn path;\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic ClassPathNode getPath() {\n\t\treturn getClassPath();\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof ClassPathNode classPath) {\n\t\t\tthis.path = classPath;\n\t\t\ttry {\n\t\t\t\tupdateDisplay(classPath.getValue().asJvmClass());\n\t\t\t} catch (InvalidClassException e) {\n\t\t\t\tlogger.error(\"Failed to create low-level display for invalid class\", e);\n\t\t\t\t// TODO: This shouldn't happen, but better error handling/feedback would be nice just in case\n\t\t\t\tsetCenter(new Label(\"Invalid class\"));\n\t\t\t}\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tsetDisable(true);\n\t}\n\n\t@Nonnull\n\tprivate Node classGraphic(@Nonnull CpClass className) {\n\t\treturn classGraphic(className.getName().getText());\n\t}\n\n\t@Nonnull\n\tprivate Node classGraphic(@Nonnull String className) {\n\t\tClassPathNode path = workspace.findClass(className);\n\t\tif (path == null)\n\t\t\treturn Icons.getIconView(Icons.CLASS);\n\t\treturn configurationService.graphicOf(path);\n\t}\n\n\t@Nonnull\n\tprivate Node memberGraphic(@Nonnull String owner, @Nonnull String name, @Nonnull String type) {\n\t\tboolean isMethod = !type.isEmpty() && type.charAt(0) == '(';\n\t\tClassPathNode path = workspace.findClass(owner);\n\t\treturn memberGraphic(path == null ? null : path.child(name, type), isMethod);\n\t}\n\n\t@Nonnull\n\tprivate Node memberGraphic(@Nonnull CpClass owner, @Nonnull CpNameType nameType) {\n\t\tClassPathNode ownerPath = workspace.findClass(owner.getName().getText());\n\t\tString name = nameType.getName().getText();\n\t\tString type = nameType.getType().getText();\n\t\tboolean isMethod = !type.isEmpty() && type.charAt(0) == '(';\n\t\treturn memberGraphic(path == null ? null : path.child(name, type), isMethod);\n\t}\n\n\t@Nonnull\n\tprivate Node memberGraphic(@Nullable ClassMemberPathNode member, boolean isMethod) {\n\t\tif (member == null)\n\t\t\treturn isMethod ? Icons.getIconView(Icons.METHOD) : Icons.getIconView(Icons.FIELD);\n\t\treturn configurationService.graphicOf(member);\n\t}\n\n\t@Nonnull\n\tprivate String cpToString(@Nonnull CpEntry entry) {\n\t\treturn switch (entry) {\n\t\t\tcase ConstDynamic cp -> {\n\t\t\t\tCpNameType nameType = cp.getNameType();\n\t\t\t\tint bsmIndex = cp.getBsmIndex();\n\t\t\t\tString name = nameType.getName().getText();\n\t\t\t\tString desc = nameType.getType().getText();\n\t\t\t\tboolean isMethod = !desc.isEmpty() && desc.charAt(0) == '(';\n\t\t\t\tString ntSuffix = \"nameType=\" + (isMethod ?\n\t\t\t\t\t\tmemberDisplay.getMethodDisplay(name, desc) :\n\t\t\t\t\t\tmemberDisplay.getFieldDisplay(name, desc));\n\t\t\t\tyield \"bootstrapMethodIndex=\" + bsmIndex + \" \" + ntSuffix;\n\t\t\t}\n\t\t\tcase ConstRef cp -> {\n\t\t\t\tCpNameType nameType = cp.getNameType();\n\t\t\t\tString owner = cp.getClassRef().getName().getText();\n\t\t\t\tString name = nameType.getName().getText();\n\t\t\t\tString desc = nameType.getType().getText();\n\t\t\t\tyield owner + \".\" + memberDisplay.getFieldDisplay(name, desc);\n\t\t\t}\n\t\t\tcase CpClass cp -> cp.getName().getText();\n\t\t\tcase CpDouble cp -> String.valueOf(cp.getValue());\n\t\t\tcase CpFloat cp -> String.valueOf(cp.getValue());\n\t\t\tcase CpInt cp -> String.valueOf(cp.getValue());\n\t\t\tcase CpLong cp -> String.valueOf(cp.getValue());\n\t\t\tcase CpMethodHandle cp -> {\n\t\t\t\tString kind = switch (cp.getKind()) {\n\t\t\t\t\tcase REF_GET_FIELD -> \"getField\";\n\t\t\t\t\tcase REF_GET_STATIC -> \"getStatic\";\n\t\t\t\t\tcase REF_PUT_FIELD -> \"putField\";\n\t\t\t\t\tcase REF_PUT_STATIC -> \"putStatic\";\n\t\t\t\t\tcase REF_INVOKE_VIRTUAL -> \"invokeVirtual\";\n\t\t\t\t\tcase REF_INVOKE_STATIC -> \"invokeStatic\";\n\t\t\t\t\tcase REF_INVOKE_SPECIAL -> \"invokeSpecial\";\n\t\t\t\t\tcase REF_NEW_INVOKE_SPECIAL -> \"newInvokeSpecial\";\n\t\t\t\t\tcase REF_INVOKE_INTERFACE -> \"invokeInterface\";\n\t\t\t\t\tdefault -> \"unknown\";\n\t\t\t\t};\n\t\t\t\tCpNameType nameType = cp.getReference().getNameType();\n\t\t\t\tString owner = cp.getReference().getClassRef().getName().getText();\n\t\t\t\tString name = nameType.getName().getText();\n\t\t\t\tString desc = nameType.getType().getText();\n\t\t\t\tyield kind + \"(\" + owner + \".\" + memberDisplay.getFieldDisplay(name, desc) + \")\";\n\t\t\t}\n\t\t\tcase CpMethodType cp -> {\n\t\t\t\tString desc = cp.getDescriptor().getText();\n\t\t\t\tyield memberDisplay.getDescriptorDisplay(desc);\n\t\t\t}\n\t\t\tcase CpModule cp -> cp.getName().getText();\n\t\t\tcase CpNameType cp -> {\n\t\t\t\tString name = cp.getName().getText();\n\t\t\t\tString desc = cp.getType().getText();\n\t\t\t\tboolean isMethod = !desc.isEmpty() && desc.charAt(0) == '(';\n\t\t\t\tyield name + (isMethod ? \"\" : \".\") + memberDisplay.getDescriptorDisplay(desc);\n\t\t\t}\n\t\t\tcase CpPackage cp -> cp.getPackageName().getText();\n\t\t\tcase CpString cp -> cp.getString().getText();\n\t\t\tcase CpUtf8 cp -> cp.getText();\n\t\t\tcase CpInternal cp -> \"<internal>\";\n\t\t};\n\t}\n\n\t@Nonnull\n\tprivate Node cpToGraphic(@Nonnull CpEntry entry) {\n\t\treturn switch (entry) {\n\t\t\tcase ConstDynamic cp -> memberGraphic(null, cp.getNameType().getType().getText().startsWith(\"(\"));\n\t\t\tcase ConstRef cp -> memberGraphic(cp.getClassRef(), cp.getNameType());\n\t\t\tcase CpClass cp -> classGraphic(cp);\n\t\t\tcase CpDouble cp -> new FontIconView(CarbonIcons.STRING_INTEGER);\n\t\t\tcase CpFloat cp -> new FontIconView(CarbonIcons.STRING_INTEGER);\n\t\t\tcase CpInt cp -> new FontIconView(CarbonIcons.STRING_INTEGER);\n\t\t\tcase CpLong cp -> new FontIconView(CarbonIcons.STRING_INTEGER);\n\t\t\tcase CpMethodHandle cp -> memberGraphic(cp.getReference().getClassRef(), cp.getReference().getNameType());\n\t\t\tcase CpMethodType cp -> memberGraphic(null, true);\n\t\t\tcase CpModule cp -> new FontIconView(CarbonIcons.CATEGORIES);\n\t\t\tcase CpNameType cp -> memberGraphic(null, cp.getType().getText().startsWith(\"(\"));\n\t\t\tcase CpPackage cp -> Icons.getIconView(Icons.FOLDER_PACKAGE);\n\t\t\tcase CpString cp -> new FontIconView(CarbonIcons.QUOTES);\n\t\t\tcase CpUtf8 cp -> new FontIconView(CarbonIcons.STRING_TEXT);\n\t\t\tcase CpInternal cp -> new Label(\"<internal>\");\n\t\t};\n\t}\n\n\t@Nonnull\n\tprivate String elementValueToString(@Nonnull ElementValue value) {\n\t\treturn switch (value) {\n\t\t\tcase AnnotationElementValue subValue -> {\n\t\t\t\tAnnotation annotation = subValue.getAnnotation();\n\t\t\t\tyield memberDisplay.getDescriptorDisplay(annotation.getType().getText());\n\t\t\t}\n\t\t\tcase ArrayElementValue subValue -> \"[\" + subValue.getArray().size() + \"]\";\n\t\t\tcase ClassElementValue subValue -> subValue.getClassEntry().getText();\n\t\t\tcase EnumElementValue subValue -> {\n\t\t\t\tString ownerDesc = subValue.getType().getText();\n\t\t\t\tString owner = ownerDesc.length() > 2 ? ownerDesc.substring(1, ownerDesc.length() - 1) : ownerDesc;\n\t\t\t\tString name = subValue.getName().getText();\n\t\t\t\tyield owner + \".\" + memberDisplay.getFieldDisplay(name, ownerDesc);\n\t\t\t}\n\t\t\tcase PrimitiveElementValue subValue -> cpToString(subValue.getValue());\n\t\t\tcase Utf8ElementValue subValue -> cpToString(subValue.getValue());\n\t\t};\n\t}\n\n\tprivate static <E> ClassItem item(@Nonnull String prefix, @Nonnull E element,\n\t                                  @Nonnull Function<E, String> stringMapper,\n\t                                  @Nonnull Function<E, Node> graphicMapper,\n\t                                  @Nonnull Function<E, ContextMenu> menuMapper) {\n\t\treturn new ClassItem(new LazyClassElement<>(prefix, element, stringMapper, graphicMapper, menuMapper));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/lowlevel/LazyClassElement.java",
    "content": "package software.coley.recaf.ui.pane.editing.jvm.lowlevel;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.event.EventHandler;\nimport javafx.scene.Node;\nimport javafx.scene.control.ContextMenu;\nimport javafx.scene.input.ContextMenuEvent;\n\nimport java.util.function.Function;\n\n/**\n * An implementation of {@link ClassElement} that lazily computes values based on a given value.\n *\n * @param <E>\n * \t\tElement value type.\n *\n * @author Matt Coley\n */\npublic class LazyClassElement<E> implements ClassElement {\n\tprivate final String prefix;\n\tprivate final E element;\n\tprivate final Function<E, String> stringMapper;\n\tprivate final Function<E, Node> graphicMapper;\n\tprivate final Function<E, ContextMenu> menuMapper;\n\n\tpublic LazyClassElement(@Nonnull String prefix, @Nonnull E element,\n\t                        @Nonnull Function<E, String> stringMapper,\n\t                        @Nonnull Function<E, Node> graphicMapper,\n\t                        @Nonnull Function<E, ContextMenu> menuMapper) {\n\t\tthis.prefix = prefix;\n\t\tthis.element = element;\n\t\tthis.stringMapper = stringMapper;\n\t\tthis.graphicMapper = graphicMapper;\n\t\tthis.menuMapper = menuMapper;\n\t}\n\n\t@Nonnull\n\tpublic E getElement() {\n\t\treturn element;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String prefix() {\n\t\treturn prefix;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String content() {\n\t\treturn stringMapper.apply(element);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic Node graphic() {\n\t\treturn graphicMapper.apply(element);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic EventHandler<ContextMenuEvent> contextRequest() {\n\t\treturn e -> menuMapper.apply(element);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/media/AudioPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.media;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.canvas.Canvas;\nimport javafx.scene.canvas.GraphicsContext;\nimport javafx.scene.effect.Bloom;\nimport javafx.scene.effect.GaussianBlur;\nimport javafx.scene.paint.Color;\nimport javafx.scene.paint.LinearGradient;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.ui.control.ResizableCanvas;\nimport software.coley.recaf.ui.media.CombinedPlayer;\nimport software.coley.recaf.ui.media.FxPlayer;\nimport software.coley.recaf.ui.media.Player;\nimport software.coley.recaf.util.FxThreadUtil;\n\nimport java.io.IOException;\nimport java.util.Collections;\n\n/**\n * A pane for displaying and playing audio files.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class AudioPane extends MediaPane {\n\tprivate final Canvas canvas = new ResizableCanvas();\n\tprivate final Player player = new CombinedPlayer(Collections.singletonList(new FxPlayer()));\n\n\t/**\n\t * Setup the pane.\n\t */\n\t@Inject\n\tpublic AudioPane() {\n\t\tsetCenter(canvas);\n\n\t\t// Makes the spike slightly softer looking\n\t\t// The combination of the faint blur and bloom prevent the 'creeping glow' that occurs with just the bloom\n\t\t// when used on lower resolutions.\n\t\tGaussianBlur blur = new GaussianBlur(1);\n\t\tBloom bloom = new Bloom();\n\t\tbloom.setThreshold(0.6);\n\t\tblur.setInput(bloom);\n\t\tplayer.setSpectrumListener(event -> {\n\t\t\t// Clear the screen\n\t\t\tGraphicsContext g = canvas.getGraphicsContext2D();\n\t\t\tdouble width = canvas.getWidth() + 2;\n\t\t\tdouble height = canvas.getHeight() + 2;\n\t\t\tg.setFill(Color.BLACK);\n\t\t\tg.fillRect(0, 0, width, height);\n\t\t\t// Prepare for coloring spikes\n\t\t\tg.setFill(LinearGradient.valueOf(\"linear-gradient(to bottom, blue, white)\"));\n\t\t\t// Draw each magnitude as a spike shape\n\t\t\tfloat[] magnitudes = event.magnitudes();\n\t\t\tint max = magnitudes.length;\n\t\t\tfor (int i = 1; i < max - 1; i++) {\n\t\t\t\tdouble percent = (double) i / max;\n\t\t\t\tdouble nextPercent = (double) (i + 1) / max;\n\t\t\t\tdouble x = percent * width;\n\t\t\t\tdouble nx = nextPercent * width;\n\t\t\t\tfloat magnitude = -magnitudes[i];\n\t\t\t\tdouble magnitudeMax = 60;\n\t\t\t\tdouble magnitudePercent = magnitude / magnitudeMax;\n\t\t\t\tif (magnitudePercent < 0)\n\t\t\t\t\tmagnitudePercent = 0;\n\t\t\t\telse if (magnitudePercent > 1)\n\t\t\t\t\tmagnitudePercent = 1;\n\t\t\t\tdouble magnitudeHeight = magnitudePercent * height;\n\t\t\t\t// Draw spike for the sample\n\t\t\t\tg.beginPath();\n\t\t\t\tg.moveTo(x, height);\n\t\t\t\tg.lineTo((x + nx) / 2, magnitudeHeight);\n\t\t\t\tg.lineTo(nx, height);\n\t\t\t\tg.closePath();\n\t\t\t\tg.fill();\n\t\t\t}\n\t\t\tg.applyEffect(blur);\n\t\t\tdrawTimeText(g);\n\t\t});\n\t}\n\n\tprivate void onLoadFailure(@Nonnull IOException ex) {\n\t\tlogger.warn(\"Could not load audio from '{}'\", path.getValue().getName(), ex);\n\t\tFxThreadUtil.delayedRun(100, () -> {\n\t\t\tGraphicsContext g = canvas.getGraphicsContext2D();\n\t\t\tdouble w = canvas.getWidth();\n\t\t\tdouble h = canvas.getHeight();\n\t\t\tg.setFill(Color.BLACK);\n\t\t\tg.fillRect(0, 0, w, h);\n\t\t\tg.setFill(Color.WHITE);\n\t\t\tg.fillText(ex.getMessage(), 10, h / 2);\n\t\t});\n\t}\n\n\tprivate void initialDraw() {\n\t\tFxThreadUtil.delayedRun(100, () -> {\n\t\t\tGraphicsContext g = canvas.getGraphicsContext2D();\n\t\t\tdouble w = canvas.getWidth();\n\t\t\tdouble h = canvas.getHeight();\n\t\t\tg.setFill(Color.BLACK);\n\t\t\tg.fillRect(0, 0, w, h);\n\t\t\tdrawTimeText(g);\n\t\t});\n\t}\n\n\tprivate void drawTimeText(@Nonnull GraphicsContext g) {\n\t\tdouble w = canvas.getWidth();\n\t\tint max = (int) player.getMaxSeconds();\n\t\tint cur = (int) player.getCurrentSeconds();\n\t\tg.setFill(Color.WHITE);\n\t\tg.fillText(formatTime(cur) + \" / \" + formatTime(max), w - 80, 24);\n\t}\n\n\tprivate static String formatTime(int time) {\n\t\tint minutes = time / 60;\n\t\tint seconds = time - minutes * 60;\n\t\tString formattedTime = \"\";\n\t\tif (minutes < 10)\n\t\t\tformattedTime += \"0\";\n\t\tformattedTime += minutes + \":\";\n\t\tif (seconds < 10)\n\t\t\tformattedTime += \"0\";\n\t\tformattedTime += seconds;\n\t\treturn formattedTime;\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof FilePathNode filePath) {\n\t\t\tthis.path = filePath;\n\t\t\tFileInfo fileInfo = filePath.getValue();\n\t\t\ttry {\n\t\t\t\tplayer.stop();\n\t\t\t\tplayer.load(fileInfo.getName());\n\t\t\t\tinitialDraw();\n\t\t\t} catch (IOException ex) {\n\t\t\t\tonLoadFailure(ex);\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tprotected Player getPlayer() {\n\t\treturn player;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/media/ImagePane.java",
    "content": "package software.coley.recaf.ui.pane.editing.media;\n\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Group;\nimport javafx.scene.control.ContextMenu;\nimport javafx.scene.control.Slider;\nimport javafx.scene.image.Image;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.StackPane;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.ImageFileInfo;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.navigation.FileNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.ImageCanvas;\nimport software.coley.recaf.ui.control.PannableView;\nimport software.coley.recaf.util.*;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.util.Collection;\nimport java.util.Collections;\n\n/**\n * Displays a {@link ImageFileInfo} in a pannable view, with some extra utility.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class ImagePane extends StackPane implements FileNavigable, UpdatableNavigable {\n\tprivate static final Logger logger = Logging.get(ImagePane.class);\n\tprivate final ImageCanvas imageView = new ImageCanvas();\n\tprivate final PannableView imagePanner = new PannableView(imageView);\n\tprotected ContextMenu menu;\n\n\tprotected FilePathNode path;\n\n\t@Inject\n\tpublic ImagePane() {\n\t\timagePanner.setOnContextMenuRequested(e -> {\n\t\t\tif (menu == null) {\n\t\t\t\tmenu = new ContextMenu();\n\t\t\t\tmenu.setAutoHide(true);\n\t\t\t\tmenu.getItems().addAll(\n\t\t\t\t\t\tMenus.action(\"menu.image.resetscale\", CarbonIcons.ZOOM_RESET, imagePanner::resetZoom),\n\t\t\t\t\t\tMenus.action(\"menu.image.center\", CarbonIcons.ZOOM_PAN, imagePanner::resetTranslation)\n\t\t\t\t);\n\t\t\t}\n\t\t\tmenu.show(imagePanner, e.getScreenX(), e.getScreenY());\n\t\t});\n\t\tNodeEvents.addMousePressHandler(imagePanner, e -> {\n\t\t\tif (menu != null && menu.isShowing()) menu.hide();\n\t\t});\n\n\t\tColorAdjustmentControls colorAdjustmentControls = new ColorAdjustmentControls();\n\t\tStackPane.setAlignment(colorAdjustmentControls, Pos.BOTTOM_CENTER);\n\t\tStackPane.setMargin(colorAdjustmentControls, new Insets(7));\n\n\t\tgetChildren().addAll(imagePanner, colorAdjustmentControls);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic FilePathNode getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\t// no-op\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof FilePathNode filePath) {\n\t\t\tthis.path = filePath;\n\t\t\tFileInfo info = filePath.getValue();\n\t\t\tif (info.isImageFile()) {\n\t\t\t\tbyte[] content = info.getRawContent();\n\n\t\t\t\tImage image = null;\n\t\t\t\tif (\"ico\".equals(info.getFileExtension()) && ByteHeaderUtil.match(content, ByteHeaderUtil.ICO)) {\n\t\t\t\t\t// JavaFX doesn't directly support ICO files, so we need to adapt it.\n\t\t\t\t\ttry {\n\t\t\t\t\t\timage = Icons.convertIcoToFxImage(content);\n\t\t\t\t\t\tif (image == null) {\n\t\t\t\t\t\t\tlogger.error(\"Failed to decode ICO image, no bundled images in file\");\n\t\t\t\t\t\t\tsetDisable(true);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\t\tlogger.error(\"Failed to decode ICO image\", ex);\n\t\t\t\t\t\tsetDisable(true);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\timage = new Image(new ByteArrayInputStream(content));\n\t\t\t\t}\n\n\t\t\t\t// Update the image view and change the initial translation sizes so that it appears centered.\n\t\t\t\tif (image != null) {\n\t\t\t\t\timageView.setImage(image);\n\n\t\t\t\t\t// Note: The height property is updated after the width, so when we see the\n\t\t\t\t\t// height become a non-zero value we know we can compute the proper translations\n\t\t\t\t\t// to center the image.\n\t\t\t\t\tImage ref = image;\n\t\t\t\t\tNodeEvents.dispatchAndRemoveIf(imagePanner.heightProperty(), (ob, old, cur) -> {\n\t\t\t\t\t\tif (cur.intValue() > 0) {\n\t\t\t\t\t\t\tdouble wp = imagePanner.getWidth() / 2;\n\t\t\t\t\t\t\tdouble hp = imagePanner.getHeight() / 2;\n\n\t\t\t\t\t\t\tdouble wc = ref.getWidth() / 2;\n\t\t\t\t\t\t\tdouble hc = ref.getHeight() / 2;\n\n\t\t\t\t\t\t\tdouble x = wp - wc;\n\t\t\t\t\t\t\tdouble y = hp - hc;\n\n\t\t\t\t\t\t\timagePanner.setInitTranslation(x, y);\n\t\t\t\t\t\t\timagePanner.resetTranslation();\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Control to manipulate the image effect.\n\t */\n\tprivate class ColorAdjustmentControls extends Group {\n\t\tprivate ColorAdjustmentControls() {\n\t\t\tAnimations.setupShowOnHover(this);\n\n\t\t\tGridPane box = new GridPane();\n\t\t\tbox.setAlignment(Pos.CENTER);\n\t\t\tbox.setVgap(5);\n\t\t\tbox.setHgap(5);\n\t\t\tbox.setPadding(new Insets(5, 5, 5, 10));\n\n\t\t\tFontIconView brightnessIcon = new FontIconView(CarbonIcons.LIGHT);\n\t\t\tSlider brightnessSlider = new Slider(-1, 1, 0);\n\t\t\tbrightnessSlider.valueProperty().addListener((ob, old, cur) -> imageView.setBrightness(cur.doubleValue()));\n\n\t\t\tFontIconView contrastIcon = new FontIconView(CarbonIcons.BRIGHTNESS_CONTRAST);\n\t\t\tSlider contrastSlider = new Slider(-1, 1, 0);\n\t\t\tcontrastSlider.valueProperty().addListener((ob, old, cur) -> imageView.setContrast(cur.doubleValue()));\n\n\t\t\tbox.add(brightnessIcon, 0, 0);\n\t\t\tbox.add(brightnessSlider, 1, 0);\n\n\t\t\tbox.add(contrastIcon, 0, 1);\n\t\t\tbox.add(contrastSlider, 1, 1);\n\n\t\t\tbox.getStyleClass().addAll(Styles.BORDER_DEFAULT, Styles.BG_SUBTLE, \"round-container-multi\");\n\n\t\t\tgetChildren().add(box);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/media/MediaPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.media;\n\nimport atlantafx.base.controls.Spacer;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Slider;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.Region;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.services.navigation.FileNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.media.Player;\nimport software.coley.recaf.util.FxThreadUtil;\n\nimport java.util.Collection;\nimport java.util.Collections;\n\n/**\n * Common base for media players.\n *\n * @author Matt Coley\n * @see VideoPane\n * @see AudioPane\n */\npublic abstract class MediaPane extends BorderPane implements FileNavigable, UpdatableNavigable {\n\tprotected static final Logger logger = Logging.get(MediaPane.class);\n\tprotected FilePathNode path;\n\n\tprotected abstract Player getPlayer();\n\n\tprotected MediaPane() {\n\t\tsetBottom(interactionBar());\n\t\tsetStyle(\"-fx-background-color: black\");\n\t}\n\n\tprotected Region interactionBar() {\n\t\tHBox buttonBox = new HBox();\n\t\tButton btnPlay = new Button();\n\t\tButton btnPause = new Button();\n\t\tButton btnStop = new Button();\n\t\tbtnPause.setDisable(true);\n\t\tbtnStop.setDisable(true);\n\t\tbtnPlay.setGraphic(new FontIconView(CarbonIcons.PLAY_FILLED_ALT));\n\t\tbtnPause.setGraphic(new FontIconView(CarbonIcons.PAUSE_FILLED));\n\t\tbtnStop.setGraphic(new FontIconView(CarbonIcons.STOP_FILLED_ALT));\n\t\tbtnPlay.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.FLAT);\n\t\tbtnPause.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.FLAT);\n\t\tbtnStop.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.FLAT);\n\t\tbtnPlay.setOnAction(e -> {\n\t\t\tgetPlayer().play();\n\t\t\tbtnPlay.setDisable(true);\n\t\t\tbtnPause.setDisable(false);\n\t\t\tbtnStop.setDisable(false);\n\t\t});\n\t\tbtnPause.setOnAction(e -> {\n\t\t\tgetPlayer().pause();\n\t\t\tbtnPlay.setDisable(false);\n\t\t\tbtnPause.setDisable(true);\n\t\t\tbtnStop.setDisable(false);\n\t\t});\n\t\tbtnStop.setOnAction(e -> {\n\t\t\tgetPlayer().stop();\n\t\t\tbtnPlay.setDisable(false);\n\t\t\tbtnPause.setDisable(true);\n\t\t\tbtnStop.setDisable(true);\n\t\t});\n\t\tbuttonBox.getChildren().addAll(btnPlay, btnPause, btnStop);\n\t\tbuttonBox.setAlignment(Pos.CENTER);\n\t\tbuttonBox.setPadding(new Insets(1));\n\t\tSlider slider = new Slider();\n\t\tFxThreadUtil.delayedRun(250, () -> {\n\t\t\tPlayer player = getPlayer();\n\t\t\tdouble maxSeconds = player.getMaxSeconds() * 1000;\n\t\t\tslider.setMin(0);\n\t\t\tslider.setMax(maxSeconds);\n\t\t\tslider.setValue(0);\n\n\t\t\t// While scrubbing, seek the media playback\n\t\t\tslider.valueProperty().addListener((_, _, seek) -> {\n\t\t\t\tif (slider.isPressed())\n\t\t\t\t\tplayer.seek(seek.doubleValue());\n\t\t\t});\n\n\t\t\t// While not scrubbing, update the slider position\n\t\t\tplayer.addPlaybackListener(() -> {\n\t\t\t\tif (!slider.isPressed())\n\t\t\t\t\tslider.setValue(player.getCurrentSeconds() * 1000);\n\t\t\t});\n\t\t});\n\t\tHBox wrapper = new HBox(buttonBox, new Spacer(), slider);\n\t\tHBox.setHgrow(slider, Priority.ALWAYS);\n\t\twrapper.setAlignment(Pos.CENTER);\n\t\treturn wrapper;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic FilePathNode getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tgetPlayer().dispose();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/media/VideoPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.media;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.Label;\nimport javafx.scene.media.MediaView;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.ui.media.FxPlayer;\nimport software.coley.recaf.ui.media.Player;\nimport software.coley.recaf.util.FxThreadUtil;\n\nimport java.io.IOException;\n\n/**\n * A pane for displaying and playing video files.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class VideoPane extends MediaPane {\n\tprivate final FxPlayer player = new FxPlayer();\n\n\t@Inject\n\tpublic VideoPane() {\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof FilePathNode filePath) {\n\t\t\tthis.path = filePath;\n\t\t\tFileInfo fileInfo = filePath.getValue();\n\t\t\ttry {\n\t\t\t\tplayer.stop();\n\t\t\t\tplayer.load(fileInfo.getName());\n\t\t\t\tMediaView view = new MediaView(player.getPlayer());\n\t\t\t\tsetCenter(view);\n\t\t\t\tview.setPreserveRatio(true);\n\t\t\t\tview.fitHeightProperty().bind(heightProperty().subtract(40));\n\t\t\t} catch (IOException ex) {\n\t\t\t\tonLoadFailure(fileInfo, ex);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void onLoadFailure(@Nonnull FileInfo fileInfo, @Nonnull IOException ex) {\n\t\tlogger.warn(\"Could not load video from '{}'\", fileInfo.getName(), ex);\n\t\tFxThreadUtil.delayedRun(100, () -> {\n\t\t\tsetCenter(new Label(ex.getMessage()));\n\t\t});\n\t}\n\n\t@Override\n\tprotected Player getPlayer() {\n\t\treturn player;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/tabs/FieldsAndMethodsPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.tabs;\n\nimport atlantafx.base.theme.Styles;\nimport atlantafx.base.theme.Tweaks;\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.beans.value.ChangeListener;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.TextField;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\nimport javafx.scene.input.MouseButton;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.observables.ObservableObject;\nimport software.coley.recaf.info.Accessed;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.InnerClassInfo;\nimport software.coley.recaf.info.Named;\nimport software.coley.recaf.info.member.ClassMember;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.InnerClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.navigation.ClassNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.ui.config.KeybindingConfig;\nimport software.coley.recaf.ui.config.MemberDisplayFormatConfig;\nimport software.coley.recaf.ui.config.MemberDisplayFormatConfig.Display;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.BoundMultiToggleIcon;\nimport software.coley.recaf.ui.control.BoundToggleIcon;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.tree.TreeFiltering;\nimport software.coley.recaf.ui.control.tree.WorkspaceTreeCell;\nimport software.coley.recaf.ui.control.tree.WorkspaceTreeNode;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.Translatable;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.function.Function;\n\n/**\n * Displays fields, methods and other subcomponents of a {@link ClassInfo}.\n *\n * @author Amejonah\n * @author Matt Coley\n */\n@Dependent\npublic class FieldsAndMethodsPane extends BorderPane implements ClassNavigable, UpdatableNavigable {\n\tprivate final BooleanProperty isEmpty = new SimpleBooleanProperty();\n\tprivate final StringProperty nameFilter = new SimpleStringProperty();\n\tprivate final BooleanProperty nameFilterCaseSensitivity = new SimpleBooleanProperty();\n\tprivate final BooleanProperty showSynthetics = new SimpleBooleanProperty(true);\n\tprivate final ObjectProperty<MemberType> memberType = new SimpleObjectProperty<>(MemberType.ALL);\n\tprivate final ObjectProperty<Visibility> visibility = new SimpleObjectProperty<>(Visibility.ALL);\n\tprivate final BooleanProperty sortAlphabetically = new SimpleBooleanProperty();\n\tprivate final BooleanProperty sortByVisibility = new SimpleBooleanProperty();\n\tprivate final ObjectProperty<Display> nameTypeDisplay = new SimpleObjectProperty<>();\n\tprivate final TreeView<PathNode<?>> tree = new TreeView<>();\n\tprivate boolean navigationLock;\n\tprivate ClassPathNode path;\n\n\t@Inject\n\tpublic FieldsAndMethodsPane(@Nonnull CellConfigurationService configurationService,\n\t                            @Nonnull MemberDisplayFormatConfig displayFormatConfig,\n\t                            @Nonnull KeybindingConfig keys,\n\t                            @Nonnull Actions actions) {\n\t\t// Setup global toggle for name-type display.\n\t\t// - Does not immediately update other open panes which is unfortunate.\n\t\t//   The code displaying name-type in cells is a bit deep and\n\t\t//   wiring this local observable value into there feels wrong.\n\t\tObservableObject<Display> nameTypeDisplayConfig = displayFormatConfig.getNameTypeDisplay();\n\t\tnameTypeDisplay.set(nameTypeDisplayConfig.getValue());\n\t\tnameTypeDisplay.addListener((ob, old, cur) -> {\n\t\t\tnameTypeDisplayConfig.setValue(cur);\n\t\t\trefreshTreeFilter();\n\t\t});\n\n\t\t// Configure tree.\n\t\ttree.setShowRoot(false);\n\t\ttree.setCellFactory(param -> new WorkspaceTreeCell(ContextSource.DECLARATION, configurationService));\n\t\ttree.getStyleClass().addAll(Tweaks.EDGE_TO_EDGE, Styles.DENSE);\n\t\ttree.setOnKeyPressed(e -> {\n\t\t\tif (keys.getRename().match(e)) {\n\t\t\t\tTreeItem<PathNode<?>> selectedItem = tree.getSelectionModel().getSelectedItem();\n\t\t\t\tif (selectedItem != null)\n\t\t\t\t\tactions.rename(selectedItem.getValue());\n\t\t\t}\n\t\t});\n\n\t\t// Layout\n\t\tGridPane emptyOverlay = new GridPane();\n\t\temptyOverlay.setAlignment(Pos.CENTER);\n\t\temptyOverlay.setPadding(new Insets(20));\n\t\temptyOverlay.setVgap(10);\n\t\temptyOverlay.getStyleClass().addAll(Styles.ELEVATED_1, Styles.BG_INSET);\n\t\temptyOverlay.add(new BoundLabel(Lang.getBinding(\"fieldsandmethods.empty\")), 0, emptyOverlay.getRowCount());\n\t\temptyOverlay.visibleProperty().bind(isEmpty);\n\t\tStackPane wrapper = new StackPane(tree, emptyOverlay);\n\t\tVBox box = new VBox(createButtonBar(), createFilterBar());\n\t\tbox.setFillWidth(true);\n\t\tbox.disableProperty().bind(isEmpty);\n\t\tsetCenter(wrapper);\n\t\tsetBottom(box);\n\t}\n\n\t/**\n\t * Sets up a double click listener on the tree that will call {@link ClassNavigable#requestFocus(ClassMember)}\n\t * with the current selected item.\n\t *\n\t * @param navigableClass\n\t * \t\tParent class navigable component.\n\t */\n\tpublic void setupSelectionNavigationListener(@Nonnull ClassNavigable navigableClass) {\n\t\ttree.setOnMouseClicked(mouseEvent -> {\n\t\t\tif (mouseEvent.getClickCount() == 2 || (mouseEvent.isControlDown() && mouseEvent.getButton() == MouseButton.PRIMARY)) {\n\t\t\t\tTreeItem<PathNode<?>> selectedItem = tree.getSelectionModel().getSelectedItem();\n\t\t\t\tif (!navigationLock && selectedItem != null && selectedItem.getValue() instanceof ClassMemberPathNode memberPathNode) {\n\t\t\t\t\tnavigationLock = true;\n\t\t\t\t\tnavigableClass.requestFocus(memberPathNode.getValue());\n\t\t\t\t\tnavigationLock = false;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Called when a filter is updated, which should trigger re-computation of which cells in the tree are visible.\n\t */\n\tprivate void refreshTreeFilter() {\n\t\tif (tree.getRoot() instanceof WorkspaceTreeNode root) {\n\t\t\troot.predicateProperty().set(null); // Intermediate null step used to trigger cell redraw.\n\t\t\troot.predicateProperty().set(item -> {\n\t\t\t\tPathNode<?> path = item.getValue();\n\n\t\t\t\tAccessed accessed = (Accessed) path.getValue();\n\t\t\t\tif (!showSynthetics.get() && (accessed.hasSyntheticModifier() || accessed.hasBridgeModifier()))\n\t\t\t\t\treturn false;\n\t\t\t\tif (!memberType.get().shouldDisplay(accessed))\n\t\t\t\t\treturn false;\n\t\t\t\tif (!visibility.get().match(accessed))\n\t\t\t\t\treturn false;\n\n\t\t\t\tString name;\n\t\t\t\tif (path instanceof ClassMemberPathNode memberPath) {\n\t\t\t\t\tClassMember member = memberPath.getValue();\n\t\t\t\t\tname = member.getName();\n\t\t\t\t} else if (path instanceof InnerClassPathNode innerPath) {\n\t\t\t\t\tInnerClassInfo inner = innerPath.getValue();\n\t\t\t\t\tname = inner.getSimpleName();\n\t\t\t\t} else {\n\t\t\t\t\tthrow new IllegalStateException(\"Unsupported type in fields & methods tree\");\n\t\t\t\t}\n\n\t\t\t\tString filterText = nameFilter.getValue();\n\t\t\t\tif (!filterText.isBlank()) {\n\t\t\t\t\tif (nameFilterCaseSensitivity.get()) {\n\t\t\t\t\t\treturn name.contains(filterText);\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn name.toLowerCase().contains(filterText.toLowerCase());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Called when a sort input is updated, which should trigger re-computation of cell order in the tree.\n\t */\n\tprivate void refreshTreeSort() {\n\t\tif (tree.getRoot() instanceof WorkspaceTreeNode root) {\n\t\t\tComparator<WorkspaceTreeNode> comparator = (a, b) -> {\n\t\t\t\tObject valueA = a.getValue().getValue();\n\t\t\t\tObject valueB = b.getValue().getValue();\n\n\t\t\t\t// Sort by visibility first.\n\t\t\t\tint result = 0;\n\t\t\t\tif (sortByVisibility.get()) {\n\t\t\t\t\tif (valueA instanceof Accessed accessedA && valueB instanceof Accessed accessedB) {\n\t\t\t\t\t\tresult = Visibility.of(accessedA).compareTo(Visibility.of(accessedB));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Then by alphabetic order.\n\t\t\t\tif (result == 0 && sortAlphabetically.get()) {\n\t\t\t\t\tif (valueA instanceof Named namedA && valueB instanceof Named namedB) {\n\t\t\t\t\t\tresult = CaseInsensitiveSimpleNaturalComparator.getInstance().compare(namedA.getName(), namedB.getName());\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Then by default order.\n\t\t\t\tif (result == 0)\n\t\t\t\t\treturn a.compareTo(b);\n\n\t\t\t\treturn result;\n\t\t\t};\n\t\t\troot.sortChildren(comparator);\n\t\t}\n\t}\n\n\t/**\n\t * @return Search filter bar.\n\t */\n\t@Nonnull\n\tprivate Node createFilterBar() {\n\t\tTextField filter = new TextField();\n\t\tfilter.promptTextProperty().bind(Lang.getBinding(\"fieldsandmethods.filter.prompt\"));\n\t\tfilter.getStyleClass().add(\"filter-field\");\n\t\tfilter.setMaxWidth(Integer.MAX_VALUE);\n\t\tTreeFiltering.install(filter, tree);\n\t\tnameFilter.bind(filter.textProperty());\n\t\tfilter.textProperty().addListener((ob, old, cur) -> refreshTreeFilter());\n\t\tHBox box = new HBox(\n\t\t\t\tfilter,\n\t\t\t\tnew BoundToggleIcon(new FontIconView(CarbonIcons.LETTER_CC), nameFilterCaseSensitivity).withTooltip(\"misc.casesensitive\")\n\t\t);\n\t\tHBox.setHgrow(filter, Priority.ALWAYS);\n\t\tbox.setMaxWidth(Integer.MAX_VALUE);\n\t\treturn box;\n\t}\n\n\t/**\n\t * @return Box containing tree display options.\n\t */\n\t@SuppressWarnings({\"unchecked\", \"rawtypes\"})\n\t@Nonnull\n\tprivate Node createButtonBar() {\n\t\tChangeListener listenerFilter = (ob, old, cur) -> refreshTreeFilter();\n\t\tChangeListener listenerSort = (ob, old, cur) -> refreshTreeSort();\n\n\t\tmemberType.addListener(listenerFilter);\n\t\tvisibility.addListener(listenerFilter);\n\t\tshowSynthetics.addListener(listenerFilter);\n\t\tsortAlphabetically.addListener(listenerSort);\n\t\tsortByVisibility.addListener(listenerSort);\n\n\t\tButton[] buttons = {\n\t\t\t\t// Show synthetics\n\t\t\t\tnew BoundToggleIcon(Icons.SYNTHETIC, showSynthetics).withTooltip(\"fieldsandmethods.showoutlinedsynths\"),\n\t\t\t\t// Member type\n\t\t\t\tnew BoundMultiToggleIcon<>(MemberType.class, memberType, m -> Icons.getIconView(m.icon))\n\t\t\t\t\t\t.withTooltip(\"fieldsandmethods.showoutlinedmembertype\"),\n\t\t\t\t// Visibility\n\t\t\t\tnew BoundMultiToggleIcon<>(Visibility.class, visibility, v -> Icons.getIconView(v.icon))\n\t\t\t\t\t\t.withTooltip(\"fieldsandmethods.showoutlinedvisibility\"),\n\t\t\t\t// Sort alphabetically/visibility\n\t\t\t\tnew BoundToggleIcon(Icons.SORT_ALPHABETICAL, sortAlphabetically).withTooltip(\"fieldsandmethods.sortalphabetically\"),\n\t\t\t\tnew BoundToggleIcon(Icons.SORT_VISIBILITY, sortByVisibility).withTooltip(\"fieldsandmethods.sortbyvisibility\"),\n\t\t\t\t// Descriptor display\n\t\t\t\tnew BoundMultiToggleIcon<>(Display.class, nameTypeDisplay, d -> switch (d) {\n\t\t\t\t\tcase NAME_ONLY -> new FontIconView(CarbonIcons.TEXT_FOOTNOTE);\n\t\t\t\t\tcase NAME_AND_RAW_DESCRIPTOR -> new FontIconView(CarbonIcons.TEXT_ALL_CAPS);\n\t\t\t\t\tcase NAME_AND_PRETTY_DESCRIPTOR -> new FontIconView(CarbonIcons.TEXT_SMALL_CAPS);\n\t\t\t\t}).withTooltip(\"fieldsandmethods.nametypemode\")\n\t\t};\n\t\tfor (Button button : buttons)\n\t\t\tbutton.setFocusTraversable(false);\n\n\t\treturn new HBox(buttons);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassPathNode getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic ClassPathNode getClassPath() {\n\t\treturn path;\n\t}\n\n\t@Override\n\tpublic void requestFocus(@Nonnull ClassMember member) {\n\t\t// Skip if lock is active. Implies we are the source of the request.\n\t\tif (navigationLock) return;\n\n\t\t// Select the given member.\n\t\tTreeItem<PathNode<?>> root = tree.getRoot();\n\t\tif (root == null)\n\t\t\t// If the value is null, it's probably waiting on the initialization from the path update handling.\n\t\t\t// Request focus with a small delay.\n\t\t\tFxThreadUtil.delayedRun(100, () -> requestFocusInternal(member));\n\t\telse\n\t\t\trequestFocusInternal(member);\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof ClassPathNode classPath) {\n\t\t\tthis.path = classPath;\n\t\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(classPath);\n\t\t\tClassInfo classInfo = classPath.getValue();\n\t\t\tfor (InnerClassInfo innerClass : classInfo.getInnerClasses()) {\n\t\t\t\tif (innerClass.isExternalReference()) continue;\n\t\t\t\tInnerClassPathNode innerNode = classPath.child(innerClass);\n\t\t\t\troot.addAndSortChild(new WorkspaceTreeNode(innerNode));\n\t\t\t}\n\t\t\tfor (FieldMember field : classInfo.getFields()) {\n\t\t\t\tClassMemberPathNode memberNode = classPath.child(field);\n\t\t\t\troot.addAndSortChild(new WorkspaceTreeNode(memberNode));\n\t\t\t}\n\t\t\tfor (MethodMember method : classInfo.getMethods()) {\n\t\t\t\tClassMemberPathNode memberNode = classPath.child(method);\n\t\t\t\troot.addAndSortChild(new WorkspaceTreeNode(memberNode));\n\t\t\t}\n\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\tisEmpty.set(root.getSourceChildren().isEmpty());\n\t\t\t\ttree.setRoot(root);\n\n\t\t\t\t// Restore existing filter/sorting after the tree is built.\n\t\t\t\trefreshTreeFilter();\n\t\t\t\trefreshTreeSort();\n\t\t\t});\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tsetDisable(true);\n\t\ttree.setRoot(null);\n\t}\n\n\tprivate void requestFocusInternal(@Nonnull ClassMember member) {\n\t\tfor (TreeItem<PathNode<?>> child : tree.getRoot().getChildren()) {\n\t\t\tif (member.equals(child.getValue().getValue())) {\n\t\t\t\tvar selectionModel = tree.getSelectionModel();\n\t\t\t\tselectionModel.select(child);\n\t\t\t\ttree.getFocusModel().focus(selectionModel.getSelectedIndex());\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Enum for differentiating different member type filters for {@link FieldsAndMethodsPane}.\n\t *\n\t * @author Amejonah\n\t */\n\tpublic enum MemberType implements Translatable {\n\t\tALL(Icons.CLASS_N_FIELD_N_METHOD, \"misc.all\"),\n\t\tFIELD(Icons.FIELD, \"misc.member.field\"),\n\t\tMETHOD(Icons.METHOD, \"misc.member.method\"),\n\t\tFIELD_AND_METHOD(Icons.FIELD_N_METHOD, \"misc.member.field-n-method\"),\n\t\tINNER_CLASS(Icons.CLASS, \"misc.member.inner-class\");\n\n\t\tfinal String icon;\n\t\tfinal String key;\n\n\t\tMemberType(String icon, String key) {\n\t\t\tthis.icon = icon;\n\t\t\tthis.key = key;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String getTranslationKey() {\n\t\t\treturn key;\n\t\t}\n\n\t\tprivate boolean shouldDisplay(@Nonnull Object object) {\n\t\t\tif (object instanceof ClassMember member)\n\t\t\t\treturn shouldDisplay(member);\n\t\t\telse if (object instanceof InnerClassInfo inner)\n\t\t\t\treturn shouldDisplay(inner);\n\t\t\treturn true;\n\t\t}\n\n\t\tpublic boolean shouldDisplay(@Nonnull ClassMember member) {\n\t\t\treturn this == ALL ||\n\t\t\t\t\tthis == FIELD_AND_METHOD ||\n\t\t\t\t\t(this == FIELD && member.isField()) ||\n\t\t\t\t\t(this == METHOD && member.isMethod());\n\t\t}\n\n\t\tpublic boolean shouldDisplay(@Nonnull InnerClassInfo inner) {\n\t\t\treturn this == ALL ||\n\t\t\t\t\tthis == INNER_CLASS;\n\t\t}\n\t}\n\n\t/**\n\t * Enum for differentiating different visibility filter for {@link FieldsAndMethodsPane}.\n\t *\n\t * @author Amejonah\n\t */\n\tpublic enum Visibility implements Translatable {\n\t\tALL(Icons.ACCESS_ALL_VISIBILITY, acc -> true),\n\t\tPUBLIC(Icons.ACCESS_PUBLIC, Accessed::hasPublicModifier),\n\t\tPROTECTED(Icons.ACCESS_PROTECTED, Accessed::hasProtectedModifier),\n\t\tPACKAGE(Icons.ACCESS_PACKAGE, Accessed::hasPackagePrivateModifier),\n\t\tPRIVATE(Icons.ACCESS_PRIVATE, Accessed::hasPrivateModifier);\n\n\t\tprivate final Function<Accessed, Boolean> accCheck;\n\t\tpublic final String icon;\n\n\t\tVisibility(@Nonnull String icon, @Nonnull Function<Accessed, Boolean> accCheck) {\n\t\t\tthis.icon = icon;\n\t\t\tthis.accCheck = accCheck;\n\t\t}\n\n\t\tpublic static Visibility of(Accessed accessed) {\n\t\t\tif (accessed.hasPublicModifier())\n\t\t\t\treturn PUBLIC;\n\t\t\telse if (accessed.hasPackagePrivateModifier())\n\t\t\t\treturn PACKAGE;\n\t\t\telse if (accessed.hasProtectedModifier())\n\t\t\t\treturn PROTECTED;\n\t\t\telse if (accessed.hasPrivateModifier())\n\t\t\t\treturn PRIVATE;\n\t\t\treturn ALL;\n\t\t}\n\n\t\tpublic boolean match(Accessed accessed) {\n\t\t\treturn accCheck.apply(accessed);\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String getTranslationKey() {\n\t\t\treturn this == ALL ? \"misc.all\" : \"misc.accessflag.visibility.\" + name().toLowerCase();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/tabs/InheritancePane.java",
    "content": "package software.coley.recaf.ui.pane.editing.tabs;\n\nimport atlantafx.base.theme.Styles;\nimport atlantafx.base.theme.Tweaks;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\nimport javafx.scene.layout.StackPane;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.services.inheritance.InheritanceGraph;\nimport software.coley.recaf.services.inheritance.InheritanceGraphService;\nimport software.coley.recaf.services.inheritance.InheritanceVertex;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.ui.control.BoundMultiToggleIcon;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.tree.WorkspaceTreeCell;\nimport software.coley.recaf.ui.control.tree.WorkspaceTreeNode;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Objects;\nimport java.util.concurrent.ExecutorService;\nimport java.util.function.Supplier;\n\n/**\n * Displays parents and children of a {@link ClassInfo}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class InheritancePane extends StackPane implements UpdatableNavigable {\n\tprivate final SimpleObjectProperty<TreeContent> contentType = new SimpleObjectProperty<>(TreeContent.CHILDREN);\n\tprivate final TreeView<PathNode<?>> tree = new TreeView<>();\n\tprivate final Supplier<InheritanceGraph> inheritanceGraphLookup;\n\tprivate final ExecutorService service = ThreadPoolFactory.newSingleThreadExecutor(\"inherit-pane\");\n\tprivate Workspace workspace;\n\tprivate ClassPathNode path;\n\n\t@Inject\n\tpublic InheritancePane(@Nonnull InheritanceGraphService graphService,\n\t                       @Nonnull CellConfigurationService configurationService) {\n\t\tthis.inheritanceGraphLookup = () -> Objects.requireNonNull(graphService.getCurrentWorkspaceInheritanceGraph(), \"Graph not created\");\n\t\tcontentType.addListener((ob, old, cur) -> scheduleRegenerateTree());\n\n\t\t// Configure tree.\n\t\ttree.setShowRoot(true);\n\t\ttree.setCellFactory(param -> new WorkspaceTreeCell(p -> Objects.equals(p, path) ?\n\t\t\t\tContextSource.DECLARATION : ContextSource.REFERENCE, configurationService));\n\t\ttree.getStyleClass().addAll(Tweaks.EDGE_TO_EDGE, Styles.DENSE);\n\n\t\t// Configure toggle button between parent & child display.\n\t\tBoundMultiToggleIcon<TreeContent> toggle = new BoundMultiToggleIcon<>(TreeContent.class, contentType,\n\t\t\t\tc -> new FontIconView(c == TreeContent.CHILDREN ? CarbonIcons.ARROW_DOWN : CarbonIcons.ARROW_UP));\n\t\ttoggle.textProperty().bind(contentType.map(c -> c == TreeContent.CHILDREN ?\n\t\t\t\tLang.get(\"hierarchy.children\") : Lang.get(\"hierarchy.parents\")));\n\t\ttoggle.getStyleClass().add(Styles.ROUNDED);\n\t\ttoggle.setFocusTraversable(false);\n\t\tStackPane.setAlignment(toggle, Pos.BOTTOM_RIGHT);\n\t\tStackPane.setMargin(toggle, new Insets(10));\n\n\t\t// Layout\n\t\tgetChildren().addAll(tree, toggle);\n\t}\n\n\t/**\n\t * Schedule tree regeneration.\n\t */\n\tprivate void scheduleRegenerateTree() {\n\t\tif (!service.isShutdown())\n\t\t\tservice.submit(this::regenerateTree);\n\t}\n\n\t/**\n\t * Recreate tree contents based on whether we want to show child types, or parent types.\n\t */\n\tprivate void regenerateTree() {\n\t\t// Skip if path not set.\n\t\tif (path == null) {\n\t\t\tFxThreadUtil.run(() -> tree.setRoot(null));\n\t\t\treturn;\n\t\t}\n\n\t\t// Skip if root not found in the graph.\n\t\tInheritanceGraph graph = inheritanceGraphLookup.get();\n\t\tif (graph == null) {\n\t\t\tFxThreadUtil.run(() -> tree.setRoot(null));\n\t\t\treturn;\n\t\t}\n\t\tInheritanceVertex vertex = graph.getVertex(path.getValue().getName());\n\t\tif (vertex == null) {\n\t\t\tFxThreadUtil.run(() -> tree.setRoot(null));\n\t\t\treturn;\n\t\t}\n\n\t\t// Create the appropriate tree model and assign it.\n\t\tFxThreadUtil.run(() -> {\n\t\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(path);\n\t\t\tif (contentType.get() == TreeContent.CHILDREN)\n\t\t\t\tcreateChildren(root, vertex);\n\t\t\telse\n\t\t\t\tcreateParents(root, vertex);\n\t\t\ttree.setRoot(root);\n\t\t});\n\t}\n\n\t/**\n\t * Adds child tree nodes representing the class's parent types.\n\t *\n\t * @param node\n\t * \t\tNode to add children to.\n\t * @param vertex\n\t * \t\tVertex to operate off of.\n\t */\n\tprivate void createParents(@Nonnull WorkspaceTreeNode node, @Nonnull InheritanceVertex vertex) {\n\t\tnode.setExpanded(true);\n\t\tfor (InheritanceVertex parentVertex : vertex.getParents()) {\n\t\t\tif (parentVertex.isJavaLangObject())\n\t\t\t\tcontinue;\n\t\t\tClassPathNode parentPath = workspace.findClass(parentVertex.getName());\n\t\t\tif (parentPath != null) {\n\t\t\t\tWorkspaceTreeNode subItem = new WorkspaceTreeNode(parentPath);\n\t\t\t\tif (noLoops(node, subItem)) {\n\t\t\t\t\tnode.addAndSortChild(subItem);\n\t\t\t\t\tcreateParents(subItem, parentVertex);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Adds child tree nodes representing the class's child types.\n\t *\n\t * @param node\n\t * \t\tNode to add children to.\n\t * @param vertex\n\t * \t\tVertex to operate off of.\n\t */\n\tprivate void createChildren(@Nonnull WorkspaceTreeNode node, @Nonnull InheritanceVertex vertex) {\n\t\tnode.setExpanded(true);\n\t\tfor (InheritanceVertex childVertex : vertex.getChildren()) {\n\t\t\tClassPathNode childPath = workspace.findClass(childVertex.getName());\n\t\t\tif (childPath != null) {\n\t\t\t\tWorkspaceTreeNode subItem = new WorkspaceTreeNode(childPath);\n\t\t\t\tif (noLoops(node, subItem)) {\n\t\t\t\t\tnode.addAndSortChild(subItem);\n\t\t\t\t\tcreateChildren(subItem, childVertex);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @param item\n\t * \t\tItem to check against.\n\t * @param child\n\t * \t\tItem to add.\n\t *\n\t * @return {@code true} when no cycles are detected.\n\t */\n\tprivate boolean noLoops(@Nullable TreeItem<?> item, @Nonnull TreeItem<?> child) {\n\t\tif (item == null)\n\t\t\treturn true;\n\t\tif (item.getValue() == child.getValue())\n\t\t\treturn false;\n\t\treturn noLoops(item.getParent(), child);\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn path;\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\tif (path instanceof ClassPathNode classPath) {\n\t\t\tthis.path = classPath;\n\t\t\tworkspace = path.getValueOfType(Workspace.class);\n\t\t\tscheduleRegenerateTree();\n\t\t}\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tservice.shutdownNow();\n\t\tsetDisable(true);\n\t\ttree.setRoot(null);\n\t}\n\n\t/**\n\t * Enum for tree content options.\n\t */\n\tpublic enum TreeContent {\n\t\tCHILDREN,\n\t\tPARENTS\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/tabs/KotlinMetadataPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.tabs;\n\nimport atlantafx.base.theme.Styles;\nimport atlantafx.base.theme.Tweaks;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TreeCell;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.StackPane;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.ui.config.MemberDisplayFormatConfig;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.kotlin.model.KtClass;\nimport software.coley.recaf.util.kotlin.model.KtConstructor;\nimport software.coley.recaf.util.kotlin.model.KtElement;\nimport software.coley.recaf.util.kotlin.model.KtFunction;\nimport software.coley.recaf.util.kotlin.model.KtParameter;\nimport software.coley.recaf.util.kotlin.model.KtProperty;\nimport software.coley.recaf.util.kotlin.model.KtType;\nimport software.coley.recaf.util.kotlin.model.KtVariable;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Objects;\n\n/**\n * Displays fields, methods and other subcomponents of a {@link ClassInfo}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class KotlinMetadataPane extends BorderPane implements Navigable {\n\tprivate final TreeView<KtElement> tree = new TreeView<>();\n\tprivate boolean navigationLock;\n\tprivate ClassPathNode path;\n\n\t@Inject\n\tpublic KotlinMetadataPane(@Nonnull MemberDisplayFormatConfig memberFormatConfig,\n\t                          @Nonnull TextFormatConfig formatConfig,\n\t                          @Nonnull Actions actions) {\n\t\ttree.setShowRoot(true);\n\t\ttree.getStyleClass().addAll(Tweaks.EDGE_TO_EDGE, Styles.DENSE);\n\t\ttree.setCellFactory(param -> new TreeCell<>() {\n\t\t\t@Override\n\t\t\tprotected void updateItem(KtElement element, boolean empty) {\n\t\t\t\tsuper.updateItem(element, empty);\n\t\t\t\tif (empty || element == null) {\n\t\t\t\t\tsetText(null);\n\t\t\t\t\tsetGraphic(null);\n\t\t\t\t} else {\n\t\t\t\t\tswitch (element) {\n\t\t\t\t\t\tcase KtClass ktClass -> {\n\t\t\t\t\t\t\tsetText(formatConfig.filter(ktClass.getName()));\n\t\t\t\t\t\t\tsetGraphic(Icons.getIconView(Icons.CLASS));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase KtFunction ktFunction -> {\n\t\t\t\t\t\t\tString name = Objects.requireNonNullElse(ktFunction.getName(), \"?\");\n\t\t\t\t\t\t\tString descriptor = KtType.toDescriptor(ktFunction);\n\t\t\t\t\t\t\tsetText(memberFormatConfig.getMethodDisplay(name, descriptor));\n\t\t\t\t\t\t\tsetGraphic(Icons.getIconView(Icons.METHOD));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase KtVariable ktVariable -> {\n\t\t\t\t\t\t\tString name = Objects.requireNonNullElse(ktVariable.getName(), \"?\");\n\t\t\t\t\t\t\tString descriptor = KtType.toDescriptor(ktVariable);\n\t\t\t\t\t\t\tsetText(memberFormatConfig.getFieldDisplay(name, descriptor));\n\t\t\t\t\t\t\tif (ktVariable instanceof KtParameter) {\n\t\t\t\t\t\t\t\tsetGraphic(Icons.getIconView(Icons.PRIMITIVE));\n\t\t\t\t\t\t\t} else if (ktVariable instanceof KtProperty) {\n\t\t\t\t\t\t\t\tsetGraphic(Icons.getIconView(Icons.FIELD));\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsetGraphic(Icons.getIconView(Icons.FIELD));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tLabel label = new BoundLabel(Lang.getBinding(\"kotlinmetadata.orderwarning\"));\n\t\tlabel.getStyleClass().add(Styles.TEXT_SUBTLE);\n\t\tlabel.setPadding(new Insets(8));\n\t\tlabel.setWrapText(true);\n\t\tBorderPane labelWrapper = new BorderPane(label);\n\t\tlabelWrapper.getStyleClass().add(\"workspace-filter-pane\"); // Style used for top-border separator\n\t\tsetCenter(tree);\n\t\tsetBottom(labelWrapper);\n\t}\n\n\t/**\n\t * @param metadata Metadata contents to display.\n\t */\n\tpublic void setMetadata(@Nullable KtClass metadata) {\n\t\tif (metadata == null) {\n\t\t\ttree.setRoot(null);\n\t\t\treturn;\n\t\t}\n\t\tTreeItem<KtElement> root = new TreeItem<>(metadata);\n\t\tfor (KtProperty property : metadata.getProperties()) {\n\t\t\tTreeItem<KtElement> propertyTree = new TreeItem<>(property);\n\t\t\troot.getChildren().add(propertyTree);\n\t\t}\n\t\tfor (KtConstructor constructor : metadata.getConstructors()) {\n\t\t\tTreeItem<KtElement> constructorTree = new TreeItem<>(constructor);\n\t\t\tfor (KtVariable parameter : constructor.getParameters()) {\n\t\t\t\tTreeItem<KtElement> parameterTree = new TreeItem<>(parameter);\n\t\t\t\tconstructorTree.getChildren().add(parameterTree);\n\t\t\t}\n\t\t\troot.getChildren().add(constructorTree);\n\t\t}\n\t\tfor (KtFunction function : metadata.getFunctions()) {\n\t\t\tTreeItem<KtElement> constructorTree = new TreeItem<>(function);\n\t\t\tfor (KtVariable parameter : function.getParameters()) {\n\t\t\t\tTreeItem<KtElement> parameterTree = new TreeItem<>(parameter);\n\t\t\t\tconstructorTree.getChildren().add(parameterTree);\n\t\t\t}\n\t\t\troot.getChildren().add(constructorTree);\n\t\t}\n\t\troot.setExpanded(true);\n\t\ttree.setRoot(root);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tsetDisable(true);\n\t\ttree.setRoot(null);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/text/TextPane.java",
    "content": "package software.coley.recaf.ui.pane.editing.text;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.layout.BorderPane;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.TextFileInfo;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.info.association.FileTypeSyntaxAssociationService;\nimport software.coley.recaf.services.navigation.FileNavigable;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.navigation.UpdatableNavigable;\nimport software.coley.recaf.ui.config.KeybindingConfig;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.bracket.BracketMatchGraphicFactory;\nimport software.coley.recaf.ui.control.richtext.bracket.SelectedBracketTracking;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.util.Animations;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.threading.ThreadUtil;\nimport software.coley.recaf.workspace.model.bundle.Bundle;\nimport software.coley.recaf.workspace.model.bundle.FileBundle;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Displays a {@link TextFileInfo} via a configured {@link Editor}.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class TextPane extends BorderPane implements FileNavigable, UpdatableNavigable {\n\tprotected final AtomicBoolean updateLock = new AtomicBoolean();\n\tprivate final FileTypeSyntaxAssociationService languageAssociation;\n\tprotected final Editor editor;\n\tprotected FilePathNode path;\n\n\t@Inject\n\tpublic TextPane(@Nonnull FileTypeSyntaxAssociationService languageAssociation,\n\t                @Nonnull KeybindingConfig keys,\n\t                @Nonnull SearchBar searchBar) {\n\t\tthis.languageAssociation = languageAssociation;\n\n\t\t// Configure the editor\n\t\teditor = new Editor();\n\t\teditor.setSelectedBracketTracking(new SelectedBracketTracking());\n\t\teditor.getRootLineGraphicFactory().addLineGraphicFactory(new BracketMatchGraphicFactory());\n\t\tsearchBar.install(editor);\n\n\t\t// Setup keybindings\n\t\tsetOnKeyPressed(e -> {\n\t\t\tif (keys.getSave().match(e))\n\t\t\t\tThreadUtil.run(this::save);\n\t\t\telse if (keys.getUndo().match(e)) {\n\t\t\t\tBundle<?> bundle = path.getValueOfType(Bundle.class);\n\t\t\t\tif (bundle != null)\n\t\t\t\t\tbundle.decrementHistory(path.getValue().getName());\n\t\t\t}\n\t\t});\n\n\t\t// Layout\n\t\tsetCenter(editor);\n\t}\n\n\t/**\n\t * Called when {@link KeybindingConfig#getSave()} is pressed.\n\t * <br>\n\t * Updates the {@link FileInfo} in the containing {@link FileBundle}.\n\t */\n\tprivate void save() {\n\t\t// Pull data from path.\n\t\tTextFileInfo info = path.getValue().asTextFile();\n\t\tFileBundle bundle = path.getValueOfType(FileBundle.class);\n\t\tif (bundle == null)\n\t\t\tthrow new IllegalStateException(\"Bundle missing from file path node\");\n\n\t\t// Create updated info model.\n\t\tFileInfo newInfo = info.toTextBuilder()\n\t\t\t\t.withRawContent(editor.getText().getBytes(info.getCharset()))\n\t\t\t\t.build();\n\n\t\t// Update the file in the bundle.\n\t\tupdateLock.set(true);\n\t\tbundle.put(newInfo);\n\t\tupdateLock.set(false);\n\t\tFxThreadUtil.run(() -> Animations.animateSuccess(this, 1000));\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic FilePathNode getPath() {\n\t\treturn path;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tsetDisable(true);\n\t\tsetOnKeyPressed(null);\n\t\teditor.close();\n\t}\n\n\t@Override\n\tpublic void onUpdatePath(@Nonnull PathNode<?> path) {\n\t\t// Pass to children\n\t\tfor (Navigable navigableChild : getNavigableChildren())\n\t\t\tif (navigableChild instanceof UpdatableNavigable updatableNavigable)\n\t\t\t\tupdatableNavigable.onUpdatePath(path);\n\n\t\t// Handle updates to the text.\n\t\tif (!updateLock.get() && path instanceof FilePathNode filePath) {\n\t\t\tthis.path = filePath;\n\t\t\tFileInfo info = filePath.getValue();\n\t\t\tif (info.isTextFile()) {\n\t\t\t\tTextFileInfo textInfo = info.asTextFile();\n\n\t\t\t\t// Configure the editor to syntax-highlight based on the file extension.\n\t\t\t\tlanguageAssociation.configureEditorSyntax(info, editor);\n\n\t\t\t\t// Update the text.\n\t\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\t\teditor.setText(textInfo.getText());\n\n\t\t\t\t\t// Prevent undo from reverting to empty state.\n\t\t\t\t\teditor.getCodeArea().getUndoManager().forgetHistory();\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/search/AbstractMemberSearchPane.java",
    "content": "package software.coley.recaf.ui.pane.search;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.TextField;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.Priority;\nimport org.reactfx.EventStreams;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.search.SearchService;\nimport software.coley.recaf.services.search.match.StringPredicate;\nimport software.coley.recaf.services.search.match.StringPredicateProvider;\nimport software.coley.recaf.services.search.query.Query;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.BoundBiDiComboBox;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.util.ToStringConverter;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static software.coley.recaf.services.search.match.StringPredicateProvider.KEY_ANYTHING;\nimport static software.coley.recaf.services.search.match.StringPredicateProvider.KEY_NOTHING;\n\n/**\n * Base for member-based search panes.\n *\n * @author Matt Coley\n */\nabstract class AbstractMemberSearchPane extends AbstractSearchPane {\n\tprivate final StringProperty ownerPredicateId = new SimpleStringProperty();\n\tprivate final StringProperty namePredicateId = new SimpleStringProperty();\n\tprivate final StringProperty descPredicateId = new SimpleStringProperty();\n\tprivate final StringProperty ownerValue;\n\tprivate final StringProperty nameValue;\n\tprivate final StringProperty descValue;\n\tprivate final StringPredicateProvider stringPredicateProvider;\n\n\tprotected AbstractMemberSearchPane(@Nonnull WorkspaceManager workspaceManager,\n\t                                   @Nonnull SearchService searchService,\n\t                                   @Nonnull CellConfigurationService configurationService,\n\t                                   @Nonnull Actions actions,\n\t                                   @Nonnull StringPredicateProvider stringPredicateProvider) {\n\t\tsuper(workspaceManager, searchService, configurationService, actions);\n\n\t\tthis.stringPredicateProvider = stringPredicateProvider;\n\n\t\tList<String> stringPredicates = stringPredicateProvider.getBiStringMatchers().keySet().stream()\n\t\t\t\t.filter(s -> !KEY_ANYTHING.equals(s) && !KEY_NOTHING.equals(s))\n\t\t\t\t.sorted().toList();\n\t\tTextField textOwner = new TextField();\n\t\tTextField textName = new TextField();\n\t\tTextField textDesc = new TextField();\n\t\tComboBox<String> modeComboOwner = new BoundBiDiComboBox<>(ownerPredicateId, stringPredicates,\n\t\t\t\tToStringConverter.from(s -> Lang.get(StringPredicate.TRANSLATION_PREFIX + s)));\n\t\tmodeComboOwner.getSelectionModel().select(StringPredicateProvider.KEY_CONTAINS);\n\t\tComboBox<String> modeComboName = new BoundBiDiComboBox<>(namePredicateId, stringPredicates,\n\t\t\t\tToStringConverter.from(s -> Lang.get(StringPredicate.TRANSLATION_PREFIX + s)));\n\t\tmodeComboName.getSelectionModel().select(StringPredicateProvider.KEY_CONTAINS);\n\t\tComboBox<String> modeComboDesc = new BoundBiDiComboBox<>(descPredicateId, stringPredicates,\n\t\t\t\tToStringConverter.from(s -> Lang.get(StringPredicate.TRANSLATION_PREFIX + s)));\n\t\tmodeComboDesc.getSelectionModel().select(StringPredicateProvider.KEY_CONTAINS);\n\n\t\tGridPane input = new GridPane();\n\t\tColumnConstraints colTexts = new ColumnConstraints();\n\t\tColumnConstraints colCombos = new ColumnConstraints();\n\t\tcolTexts.setFillWidth(true);\n\t\tinput.setAlignment(Pos.CENTER);\n\t\tinput.getColumnConstraints().addAll(colTexts, colCombos);\n\t\tinput.setHgap(10);\n\t\tinput.setVgap(10);\n\t\tinput.setPadding(new Insets(10));\n\t\tinput.addRow(0, new BoundLabel(Lang.getBinding(\"dialog.search.member-owner\")), textOwner, modeComboOwner);\n\t\tinput.addRow(1, new BoundLabel(Lang.getBinding(\"dialog.search.member-name\")), textName, modeComboName);\n\t\tinput.addRow(2, new BoundLabel(Lang.getBinding(\"dialog.search.member-descriptor\")), textDesc, modeComboDesc);\n\t\tGridPane.setHgrow(textOwner, Priority.ALWAYS);\n\t\tGridPane.setHgrow(textName, Priority.ALWAYS);\n\t\tGridPane.setHgrow(textDesc, Priority.ALWAYS);\n\n\t\townerValue = textOwner.textProperty();\n\t\tnameValue = textName.textProperty();\n\t\tdescValue = textDesc.textProperty();\n\n\t\tEventStreams.changesOf(ownerValue)\n\t\t\t\t.or(EventStreams.changesOf(ownerPredicateId))\n\t\t\t\t.or(EventStreams.changesOf(nameValue))\n\t\t\t\t.or(EventStreams.changesOf(namePredicateId))\n\t\t\t\t.or(EventStreams.changesOf(descValue))\n\t\t\t\t.or(EventStreams.changesOf(descPredicateId))\n\t\t\t\t.reduceSuccessions(Collections::singletonList, Lists::add, Duration.ofMillis(Editor.SHORT_DELAY_MS))\n\t\t\t\t.addObserver(unused -> search());\n\n\t\tsetInputs(input);\n\t}\n\n\t/**\n\t * @return Predicate ID property within {@link StringPredicateProvider} used for {@link #ownerValueProperty()}.\n\t */\n\t@Nonnull\n\tpublic StringProperty ownerPredicateIdProperty() {\n\t\treturn ownerPredicateId;\n\t}\n\n\t/**\n\t * @return Predicate ID property within {@link StringPredicateProvider} used for {@link #nameValueProperty()}.\n\t */\n\t@Nonnull\n\tpublic StringProperty namePredicateIdProperty() {\n\t\treturn namePredicateId;\n\t}\n\n\t/**\n\t * @return Predicate ID property within {@link StringPredicateProvider} used for {@link #descValueProperty()}.\n\t */\n\t@Nonnull\n\tpublic StringProperty descPredicateIdProperty() {\n\t\treturn descPredicateId;\n\t}\n\n\t/**\n\t * @return Reference owner name value property to search with.\n\t */\n\t@Nonnull\n\tpublic StringProperty ownerValueProperty() {\n\t\treturn ownerValue;\n\t}\n\n\t/**\n\t * @return Reference member name value property to search with.\n\t */\n\t@Nonnull\n\tpublic StringProperty nameValueProperty() {\n\t\treturn nameValue;\n\t}\n\n\t/**\n\t * @return Reference member descriptor value property to search with.\n\t */\n\t@Nonnull\n\tpublic StringProperty descValueProperty() {\n\t\treturn descValue;\n\t}\n\n\t@Nullable\n\t@Override\n\tprotected Query buildQuery() {\n\t\tString ownerSearch = ownerValue.get();\n\t\tString ownerId = ownerPredicateId.get();\n\n\t\tString nameSearch = nameValue.get();\n\t\tString nameId = namePredicateId.get();\n\n\t\tString descSearch = descValue.get();\n\t\tString descId = descPredicateId.get();\n\n\t\tStringPredicate ownerPredicate = buildPredicate(ownerId, ownerSearch);\n\t\tStringPredicate namePredicate = buildPredicate(nameId, nameSearch);\n\t\tStringPredicate descPredicate = buildPredicate(descId, descSearch);\n\n\t\t// Skip if no predicates are provided\n\t\tif (ownerPredicate == null && namePredicate == null && descPredicate == null)\n\t\t\treturn null;\n\n\t\treturn newQuery(ownerPredicate, namePredicate, descPredicate);\n\t}\n\n\t@Nonnull\n\tprotected abstract Query newQuery(@Nullable StringPredicate ownerPredicate,\n\t                                  @Nullable StringPredicate namePredicate,\n\t                                  @Nullable StringPredicate descPredicate);\n\n\t@Nullable\n\tprivate StringPredicate buildPredicate(@Nonnull String id, @Nonnull String text) {\n\t\tStringPredicate namePredicate;\n\t\tif (text.isBlank())\n\t\t\treturn null;\n\n\t\t// Validate regex input\n\t\tif (id.contains(\"regex\") && !RegexUtil.validate(text).valid())\n\t\t\treturn null;\n\n\t\treturn stringPredicateProvider.newBiStringPredicate(id, text);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/search/AbstractSearchPane.java",
    "content": "package software.coley.recaf.ui.pane.search;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.animation.AnimationTimer;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.scene.Node;\nimport javafx.scene.layout.BorderPane;\nimport software.coley.recaf.path.IncompletePathException;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.path.WorkspacePathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.navigation.Navigable;\nimport software.coley.recaf.services.search.CancellableSearchFeedback;\nimport software.coley.recaf.services.search.SearchService;\nimport software.coley.recaf.services.search.query.Query;\nimport software.coley.recaf.services.search.result.Result;\nimport software.coley.recaf.services.search.result.Results;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.PathNodeTree;\nimport software.coley.recaf.ui.control.tree.TreeItems;\nimport software.coley.recaf.ui.control.tree.WorkspaceTreeNode;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.threading.Batch;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Objects;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.Consumer;\n\n/**\n * Common base capabilities for search panels.\n *\n * @author Matt Coley\n */\npublic abstract class AbstractSearchPane extends BorderPane implements Navigable {\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final SearchService searchService;\n\tprivate final CellConfigurationService configurationService;\n\tprivate final Actions actions;\n\tprivate final WorkspacePathNode workspacePath;\n\tprotected final PathNodeTree liveResultsTree;\n\tprotected final BooleanProperty liveResults = new SimpleBooleanProperty(true);\n\tprivate CancellableSearchFeedback lastSearchFeedback;\n\n\t/**\n\t * Create the base outline of a search panel capabilities.\n\t *\n\t * @param workspaceManager\n\t * \t\tManager to pull current workspace from.\n\t * @param searchService\n\t * \t\tSearch service to initiate searches with.\n\t * @param configurationService\n\t * \t\tCell configuration service to stylize the output tree model.\n\t * @param actions\n\t * \t\tAction service to assist stylizing the output tree model.\n\t */\n\tpublic AbstractSearchPane(@Nonnull WorkspaceManager workspaceManager,\n\t                          @Nonnull SearchService searchService,\n\t                          @Nonnull CellConfigurationService configurationService,\n\t                          @Nonnull Actions actions) {\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.searchService = searchService;\n\t\tthis.configurationService = configurationService;\n\t\tthis.actions = actions;\n\n\t\tliveResultsTree = newTree();\n\n\t\tworkspacePath = PathNodes.workspacePath(Objects.requireNonNull(workspaceManager.getCurrent(),\n\t\t\t\t\"Cannot open search if no workspace is open\"));\n\t}\n\n\t@Override\n\tpublic boolean isTrackable() {\n\t\t// We want this type to be navigable to benefit from automatic close support.\n\t\treturn false;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic PathNode<?> getPath() {\n\t\treturn workspacePath;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Collection<Navigable> getNavigableChildren() {\n\t\treturn Collections.emptyList();\n\t}\n\n\t@Override\n\tpublic void disable() {\n\t\tcancelLastSearch();\n\t\tliveResultsTree.setRoot(null);\n\t\tgetChildren().clear();\n\t\tsetDisable(true);\n\t}\n\n\t/**\n\t * Set up the UI with the given inputs.\n\t *\n\t * @param input\n\t * \t\tNode to handle user input.\n\t */\n\tprotected void setInputs(@Nonnull Node input) {\n\t\t// TODO: It would be nice to have additional 'advanced' options\n\t\t//  - Which could implement the search-feedback 'doVisitX' methods to allow skipping over unwanted content\n\n\t\tsetTop(input);\n\t\tsetCenter(liveResultsTree);\n\n\t\tliveResults.addListener((ob, old, cur) -> {\n\t\t\tif (cur) {\n\t\t\t\tsetCenter(liveResultsTree);\n\t\t\t} else {\n\t\t\t\tsetCenter(null);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * @return New path-node tree.\n\t */\n\t@Nonnull\n\tprotected PathNodeTree newTree() {\n\t\tPathNodeTree tree = new PathNodeTree(configurationService, actions);\n\t\ttree.contextSourceObjectPropertyProperty().set(SearchContextSource.SEARCH_INSTANCE);\n\t\ttree.setOnMousePressed(e -> {\n\t\t\tif (e.getClickCount() == 2 && e.isPrimaryButtonDown()) {\n\t\t\t\tvar item = tree.getSelectionModel().getSelectedItem();\n\t\t\t\tif (item != null && item.isLeaf()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tactions.gotoDeclaration(item.getValue());\n\t\t\t\t\t} catch (IncompletePathException ignored) {\n\t\t\t\t\t\t// ignored\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\treturn tree;\n\t}\n\n\t/**\n\t * @return The built query from current search inputs,\n\t * or {@code null} if the inputs were invalid for any reason.\n\t */\n\t@Nullable\n\tprotected abstract Query buildQuery();\n\n\t/**\n\t * Initiates the search with current search inputs. Updates the output display.\n\t */\n\tprotected final void search() {\n\t\t// Skip if the panel has been disabled (occurs when closing it).\n\t\t// Sometimes the delay between searching and the user closing will initiate a search after closing.\n\t\tif (isDisabled()) return;\n\n\t\t// Must have a current workspace to search in.\n\t\tif (!workspaceManager.hasCurrentWorkspace())\n\t\t\treturn;\n\n\t\t// Create a new root.\n\t\tWorkspace workspace = workspaceManager.getCurrent();\n\t\tPathNodeTree tree = liveResults.get() ? liveResultsTree : newTree();\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(PathNodes.workspacePath(workspace));\n\t\troot.setExpanded(true);\n\t\ttree.setRoot(root);\n\n\t\t// Cancel last search before we start a new one.\n\t\tcancelLastSearch();\n\n\t\t// Skip if the query couldn't be built (invalid inputs most likely)\n\t\tQuery query = buildQuery();\n\t\tif (query == null)\n\t\t\treturn;\n\n\t\t// Run new search.\n\t\tCancellableSearchFeedback feedback;\n\t\tif (liveResults.get()) {\n\t\t\tfeedback = new LiveOnlySearchFeedback(result -> {\n\t\t\t\tWorkspaceTreeNode node = WorkspaceTreeNode.getOrInsertIntoTree(root, result.getPath());\n\t\t\t\tTreeItems.expandParents(node);\n\t\t\t});\n\t\t\tCompletableFuture.runAsync(() -> searchService.search(workspace, query, feedback));\n\t\t} else {\n\t\t\tfeedback = new CancellableSearchFeedback();\n\t\t\tCompletableFuture.supplyAsync(() -> searchService.search(workspace, query, feedback))\n\t\t\t\t\t.thenAccept(this::handleSearchResults);\n\t\t}\n\t\tlastSearchFeedback = feedback;\n\t}\n\n\t/**\n\t * Called when a search completes that is not {@link #liveResults live}.\n\t *\n\t * @param results\n\t * \t\tResults of a non-live search.\n\t */\n\tprotected void handleSearchResults(@Nonnull Results results) {\n\t\t// TODO: Handle displaying the results for non-live search\n\t\t//  - put display in center, should be a PathNodeTree model, ideally dockable so user can move it around\n\t\t//  - maybe have the toggle for \"[x] live\" be an overlay like the \"(i)\" in decompile UI\n\t}\n\n\t/**\n\t * Stops the prior search.\n\t */\n\tprivate void cancelLastSearch() {\n\t\tif (lastSearchFeedback != null) {\n\t\t\tlastSearchFeedback.cancel();\n\t\t\tlastSearchFeedback = null;\n\t\t}\n\t}\n\n\t/**\n\t * Feedback that incrementally updates the search results tree.\n\t * <br>\n\t * Disables the collection of results into a single wrapper at the end of a search.\n\t * Since this is for live-only feedback, we won't use the resulting collection anyways, so we don't need to do\n\t * the extra work.\n\t */\n\tprivate static class LiveOnlySearchFeedback extends CancellableSearchFeedback {\n\t\tprivate final Batch batch = FxThreadUtil.batch();\n\t\tprivate final AnimationTimer batchTimer = new AnimationTimer() {\n\t\t\tprivate static final long BATCH_INTERVAL_MS = 1000 / 4;\n\t\t\tprivate long last;\n\n\t\t\t@Override\n\t\t\tpublic void handle(long now) {\n\t\t\t\tif (now - last > BATCH_INTERVAL_MS) {\n\t\t\t\t\tpublishResults();\n\t\t\t\t\tlast = now;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tprivate final Consumer<Result<?>> resultConsumer;\n\n\t\tprivate LiveOnlySearchFeedback(@Nonnull Consumer<Result<?>> resultConsumer) {\n\t\t\tthis.resultConsumer = resultConsumer;\n\t\t\tbatchTimer.start();\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean doAcceptResult(@Nonnull Result<?> result) {\n\t\t\tbatch.add(() -> resultConsumer.accept(result));\n\t\t\treturn false;\n\t\t}\n\n\t\t@Override\n\t\tpublic void onCompletion() {\n\t\t\tbatchTimer.stop();\n\t\t\tpublishResults();\n\t\t}\n\n\t\tprivate void publishResults() {\n\t\t\tbatch.execute();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/search/ClassReferenceSearchPane.java",
    "content": "package software.coley.recaf.ui.pane.search;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.TextField;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.Priority;\nimport org.reactfx.EventStreams;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.search.SearchService;\nimport software.coley.recaf.services.search.match.StringPredicate;\nimport software.coley.recaf.services.search.match.StringPredicateProvider;\nimport  static software.coley.recaf.services.search.match.StringPredicateProvider.*;\nimport software.coley.recaf.services.search.query.Query;\nimport software.coley.recaf.services.search.query.ReferenceQuery;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.BoundBiDiComboBox;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.util.ToStringConverter;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Class reference search pane.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class ClassReferenceSearchPane extends AbstractSearchPane {\n\tprivate final StringProperty typePredicateId = new SimpleStringProperty();\n\tprivate final StringProperty typeValue;\n\tprivate final StringPredicateProvider stringPredicateProvider;\n\n\t@Inject\n\tpublic ClassReferenceSearchPane(@Nonnull WorkspaceManager workspaceManager,\n\t\t\t\t\t\t\t\t\t@Nonnull SearchService searchService,\n\t\t\t\t\t\t\t\t\t@Nonnull CellConfigurationService configurationService,\n\t\t\t\t\t\t\t\t\t@Nonnull Actions actions,\n\t\t\t\t\t\t\t\t\t@Nonnull StringPredicateProvider stringPredicateProvider) {\n\t\tsuper(workspaceManager, searchService, configurationService, actions);\n\n\t\tthis.stringPredicateProvider = stringPredicateProvider;\n\n\t\tList<String> stringPredicates = stringPredicateProvider.getBiStringMatchers().keySet().stream()\n\t\t\t\t.filter(s -> !KEY_ANYTHING.equals(s) && !KEY_NOTHING.equals(s))\n\t\t\t\t.sorted().toList();\n\t\tTextField textField = new TextField();\n\t\tComboBox<String> modeCombo = new BoundBiDiComboBox<>(typePredicateId, stringPredicates,\n\t\t\t\tToStringConverter.from(s -> Lang.get(StringPredicate.TRANSLATION_PREFIX + s)));\n\t\tmodeCombo.getSelectionModel().select(StringPredicateProvider.KEY_CONTAINS);\n\n\t\tGridPane input = new GridPane();\n\t\tColumnConstraints colTexts = new ColumnConstraints();\n\t\tColumnConstraints colCombos = new ColumnConstraints();\n\t\tcolTexts.setFillWidth(true);\n\t\tinput.setAlignment(Pos.CENTER);\n\t\tinput.getColumnConstraints().addAll(colTexts, colCombos);\n\t\tinput.setHgap(10);\n\t\tinput.setPadding(new Insets(10));\n\t\tinput.addRow(0, textField, modeCombo);\n\t\tGridPane.setHgrow(textField, Priority.ALWAYS);\n\n\t\ttypeValue = textField.textProperty();\n\t\tEventStreams.changesOf(typeValue)\n\t\t\t\t.or(EventStreams.changesOf(typePredicateId))\n\t\t\t\t.reduceSuccessions(Collections::singletonList, Lists::add, Duration.ofMillis(Editor.SHORT_DELAY_MS))\n\t\t\t\t.addObserver(unused -> search());\n\n\t\tsetInputs(input);\n\t}\n\n\t/**\n\t * @return Predicate ID property within {@link StringPredicateProvider} used for {@link #typeValueProperty()}.\n\t */\n\t@Nonnull\n\tpublic StringProperty typePredicateIdProperty() {\n\t\treturn typePredicateId;\n\t}\n\n\t/**\n\t * @return Type name value property to search with.\n\t */\n\t@Nonnull\n\tpublic StringProperty typeValueProperty() {\n\t\treturn typeValue;\n\t}\n\n\t@Nullable\n\t@Override\n\tprotected Query buildQuery() {\n\t\tString search = typeValue.get();\n\t\tString id = typePredicateId.get();\n\n\t\t// Skip for blank input\n\t\tif (search.isBlank())\n\t\t\treturn null;\n\n\t\t// Validate regex input\n\t\tif (id.contains(\"regex\") && !RegexUtil.validate(search).valid())\n\t\t\treturn null;\n\n\t\t// Skip if unrecognized string predicate id\n\t\tStringPredicate predicate = stringPredicateProvider.newBiStringPredicate(id, search);\n\t\tif (predicate == null)\n\t\t\treturn null;\n\n\t\treturn new ReferenceQuery(predicate);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/search/InstructionSearchPane.java",
    "content": "package software.coley.recaf.ui.pane.search;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ListChangeListener;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.TextField;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.Priority;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.reactfx.EventStreams;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.search.SearchService;\nimport software.coley.recaf.services.search.match.StringPredicate;\nimport software.coley.recaf.services.search.match.StringPredicateProvider;\nimport software.coley.recaf.services.search.query.InstructionQuery;\nimport software.coley.recaf.services.search.query.Query;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.BoundBiDiComboBox;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.util.ToStringConverter;\n\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.UUID;\n\nimport static software.coley.recaf.services.search.match.StringPredicateProvider.KEY_ANYTHING;\nimport static software.coley.recaf.services.search.match.StringPredicateProvider.KEY_NOTHING;\n\n/**\n * Instruction disassembly search pane.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class InstructionSearchPane extends AbstractSearchPane {\n\tprivate static final UUID TOP_UID = UUID.randomUUID();\n\tprivate final StringPredicateProvider stringPredicateProvider;\n\tprivate final ObservableList<Line> lines = FXCollections.observableArrayList();\n\n\t@Inject\n\tpublic InstructionSearchPane(@Nonnull WorkspaceManager workspaceManager,\n\t                             @Nonnull SearchService searchService,\n\t                             @Nonnull CellConfigurationService configurationService,\n\t                             @Nonnull Actions actions,\n\t                             @Nonnull StringPredicateProvider stringPredicateProvider) {\n\t\tsuper(workspaceManager, searchService, configurationService, actions);\n\n\t\tthis.stringPredicateProvider = stringPredicateProvider;\n\n\t\tGridPane input = new GridPane();\n\t\tColumnConstraints colTexts = new ColumnConstraints();\n\t\tColumnConstraints colCombos = new ColumnConstraints();\n\t\tcolTexts.setFillWidth(true);\n\t\tinput.setAlignment(Pos.CENTER);\n\t\tinput.getColumnConstraints().addAll(colTexts, colCombos);\n\t\tinput.setHgap(10);\n\t\tinput.setPadding(new Insets(10));\n\n\t\tList<String> stringPredicates = stringPredicateProvider.getBiStringMatchers().keySet().stream()\n\t\t\t\t.filter(s -> !KEY_ANYTHING.equals(s) && !KEY_NOTHING.equals(s))\n\t\t\t\t.sorted().toList();\n\t\tlines.addListener((ListChangeListener<Line>) change -> {\n\t\t\twhile (change.next()) {\n\t\t\t\tfor (Line line : change.getAddedSubList()) {\n\t\t\t\t\tStringProperty stringValue = line.text();\n\t\t\t\t\tStringProperty stringPredicateId = line.predicateId();\n\t\t\t\t\tTextField textField = new TextField();\n\t\t\t\t\ttextField.setId(\"text-\" + line.uuid());\n\t\t\t\t\tstringValue.bind(textField.textProperty());\n\t\t\t\t\tComboBox<String> modeCombo = new BoundBiDiComboBox<>(stringPredicateId, stringPredicates,\n\t\t\t\t\t\t\tToStringConverter.from(s -> Lang.get(StringPredicate.TRANSLATION_PREFIX + s)));\n\t\t\t\t\tmodeCombo.setId(\"id-\" + line.uuid());\n\t\t\t\t\tmodeCombo.getSelectionModel().select(StringPredicateProvider.KEY_CONTAINS);\n\t\t\t\t\tEventStreams.changesOf(stringValue)\n\t\t\t\t\t\t\t.or(EventStreams.changesOf(stringPredicateId))\n\t\t\t\t\t\t\t.reduceSuccessions(Collections::singletonList, Lists::add, Duration.ofMillis(Editor.MEDIUM_DELAY_MS))\n\t\t\t\t\t\t\t.addObserver(unused -> search());\n\t\t\t\t\tGridPane.setHgrow(textField, Priority.ALWAYS);\n\t\t\t\t\tif (line.uuid() == TOP_UID) {\n\t\t\t\t\t\tinput.addRow(input.getRowCount(), textField, modeCombo);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tActionButton delete = new ActionButton(CarbonIcons.TRASH_CAN, () -> lines.remove(line));\n\t\t\t\t\t\tdelete.setId(\"delete-\" + line.uuid());\n\t\t\t\t\t\tinput.addRow(input.getRowCount(), textField, modeCombo, delete);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (Line line : change.getRemoved()) {\n\t\t\t\t\tString lineUid = line.uuid().toString();\n\t\t\t\t\tfor (Node child : new ArrayList<>(input.getChildrenUnmodifiable())) {\n\t\t\t\t\t\tString childId = child.getId();\n\t\t\t\t\t\tif (childId != null && childId.contains(lineUid)) {\n\t\t\t\t\t\t\tinput.getChildren().remove(child);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// Add an initial line, and a button to facilitate adding additional lines\n\t\tlines.add(new Line(TOP_UID, new SimpleStringProperty(), new SimpleStringProperty()));\n\t\tButton addLine = new ActionButton(CarbonIcons.ADD_ALT,\n\t\t\t\t() -> lines.add(new Line(UUID.randomUUID(), new SimpleStringProperty(), new SimpleStringProperty())));\n\t\tinput.addRow(0, addLine);\n\t\tsetInputs(input);\n\t}\n\n\t/**\n\t * @return List of models to match against lines of disassembled instructions.\n\t */\n\t@Nonnull\n\tpublic ObservableList<Line> getLinePredicates() {\n\t\treturn lines;\n\t}\n\n\t@Nullable\n\t@Override\n\tprotected Query buildQuery() {\n\t\tList<StringPredicate> predicates = lines.stream().map(line -> {\n\t\t\tString search = line.text().get();\n\t\t\tString id = line.predicateId().get();\n\n\t\t\t// Skip for blank input\n\t\t\tif (search.isBlank())\n\t\t\t\treturn null;\n\n\t\t\t// Validate regex input\n\t\t\tif (id.contains(\"regex\") && !RegexUtil.validate(search).valid())\n\t\t\t\treturn null;\n\n\t\t\t// May be null if no such id exists as a predicate, but we filter out nulls anyways.\n\t\t\treturn stringPredicateProvider.newBiStringPredicate(id, search);\n\t\t}).filter(Objects::nonNull).toList();\n\t\treturn new InstructionQuery(predicates);\n\t}\n\n\t/**\n\t * Model of a single line of text to search against disassembled code.\n\t *\n\t * @param uuid\n\t * \t\tUnique identifier for this input.\n\t * @param text\n\t * \t\tText to match on the line.\n\t * @param predicateId\n\t * \t\tPredicate identifier for {@link StringPredicateProvider} lookups.\n\t */\n\tpublic record Line(@Nonnull UUID uuid, @Nonnull StringProperty text, @Nonnull StringProperty predicateId) {}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/search/MemberDeclarationSearchPane.java",
    "content": "package software.coley.recaf.ui.pane.search;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.search.SearchService;\nimport software.coley.recaf.services.search.match.StringPredicate;\nimport software.coley.recaf.services.search.match.StringPredicateProvider;\nimport software.coley.recaf.services.search.query.DeclarationQuery;\nimport software.coley.recaf.services.search.query.Query;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\n\n/**\n * Member declaration search pane.\n *\n * @author Matt Coley\n * @see MemberReferenceSearchPane\n */\n@Dependent\npublic class MemberDeclarationSearchPane extends AbstractMemberSearchPane {\n\t@Inject\n\tpublic MemberDeclarationSearchPane(@Nonnull WorkspaceManager workspaceManager,\n\t                                   @Nonnull SearchService searchService,\n\t                                   @Nonnull CellConfigurationService configurationService,\n\t                                   @Nonnull Actions actions,\n\t                                   @Nonnull StringPredicateProvider stringPredicateProvider) {\n\t\tsuper(workspaceManager, searchService, configurationService, actions, stringPredicateProvider);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected Query newQuery(@Nullable StringPredicate ownerPredicate,\n\t                         @Nullable StringPredicate namePredicate,\n\t                         @Nullable StringPredicate descPredicate) {\n\t\treturn new DeclarationQuery(ownerPredicate, namePredicate, descPredicate);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/search/MemberReferenceSearchPane.java",
    "content": "package software.coley.recaf.ui.pane.search;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.search.SearchService;\nimport software.coley.recaf.services.search.match.StringPredicate;\nimport software.coley.recaf.services.search.match.StringPredicateProvider;\nimport software.coley.recaf.services.search.query.Query;\nimport software.coley.recaf.services.search.query.ReferenceQuery;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\n\n/**\n * Member declaration search pane.\n *\n * @author Matt Coley\n * @see MemberDeclarationSearchPane\n */\n@Dependent\npublic class MemberReferenceSearchPane extends AbstractMemberSearchPane {\n\t@Inject\n\tpublic MemberReferenceSearchPane(@Nonnull WorkspaceManager workspaceManager,\n\t                                 @Nonnull SearchService searchService,\n\t                                 @Nonnull CellConfigurationService configurationService,\n\t                                 @Nonnull Actions actions,\n\t                                 @Nonnull StringPredicateProvider stringPredicateProvider) {\n\t\tsuper(workspaceManager, searchService, configurationService, actions, stringPredicateProvider);\n\t}\n\n\t@Nonnull\n\t@Override\n\tprotected Query newQuery(@Nullable StringPredicate ownerPredicate,\n\t                         @Nullable StringPredicate namePredicate,\n\t                         @Nullable StringPredicate descPredicate) {\n\t\treturn new ReferenceQuery(ownerPredicate, namePredicate, descPredicate);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/search/NumberSearchPane.java",
    "content": "package software.coley.recaf.ui.pane.search;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.beans.value.ObservableValue;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.Priority;\nimport org.reactfx.EventStreams;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.search.SearchService;\nimport software.coley.recaf.services.search.match.NumberPredicate;\nimport software.coley.recaf.services.search.match.NumberPredicateProvider;\nimport software.coley.recaf.services.search.query.NumberQuery;\nimport software.coley.recaf.services.search.query.Query;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.BoundBiDiComboBox;\nimport software.coley.recaf.ui.control.DynamicNumericTextField;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.ToStringConverter;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Number search pane.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class NumberSearchPane extends AbstractSearchPane {\n\tprivate final StringProperty numericPredicateId = new SimpleStringProperty();\n\tprivate final ObjectProperty<Number> numericValueProperty = new SimpleObjectProperty<>(0);\n\tprivate final ObjectProperty<Class<? extends Number>> numericTypeProperty = new SimpleObjectProperty<>(Integer.class);\n\tprivate final ObservableValue<Boolean> isBlank;\n\tprivate final NumberPredicateProvider numberPredicateProvider;\n\n\t@Inject\n\tpublic NumberSearchPane(@Nonnull WorkspaceManager workspaceManager,\n\t\t\t\t\t\t\t@Nonnull SearchService searchService,\n\t\t\t\t\t\t\t@Nonnull CellConfigurationService configurationService,\n\t\t\t\t\t\t\t@Nonnull Actions actions,\n\t\t\t\t\t\t\t@Nonnull NumberPredicateProvider numberPredicateProvider) {\n\t\tsuper(workspaceManager, searchService, configurationService, actions);\n\n\t\tthis.numberPredicateProvider = numberPredicateProvider;\n\n\t\tList<String> biPredicates = numberPredicateProvider.getBiNumberMatchers().keySet().stream().sorted().toList();\n\t\tDynamicNumericTextField textField = new DynamicNumericTextField(numericValueProperty, numericTypeProperty);\n\t\tComboBox<String> modeCombo = new BoundBiDiComboBox<>(numericPredicateId, biPredicates,\n\t\t\t\tToStringConverter.from(s -> Lang.get(NumberPredicate.TRANSLATION_PREFIX + s)));\n\t\tmodeCombo.getSelectionModel().select(NumberPredicateProvider.KEY_EQUAL);\n\t\tisBlank = textField.textProperty().isEmpty();\n\n\t\tGridPane input = new GridPane();\n\t\tColumnConstraints colTexts = new ColumnConstraints();\n\t\tColumnConstraints colCombos = new ColumnConstraints();\n\t\tcolTexts.setFillWidth(true);\n\t\tinput.setAlignment(Pos.CENTER);\n\t\tinput.getColumnConstraints().addAll(colTexts, colCombos);\n\t\tinput.setHgap(10);\n\t\tinput.setPadding(new Insets(10));\n\t\tinput.addRow(0, textField, modeCombo);\n\t\tGridPane.setHgrow(textField, Priority.ALWAYS);\n\n\t\tEventStreams.changesOf(numericValueProperty)\n\t\t\t\t.or(EventStreams.changesOf(numericPredicateId))\n\t\t\t\t.reduceSuccessions(Collections::singletonList, Lists::add, Duration.ofMillis(Editor.SHORT_DELAY_MS))\n\t\t\t\t.addObserver(unused -> search());\n\n\t\tsetInputs(input);\n\t}\n\n\t/**\n\t * @return Predicate ID property within {@link NumberPredicateProvider} used for {@link #numericValuePropertyProperty()}.\n\t */\n\t@Nonnull\n\tpublic StringProperty numericPredicateIdProperty() {\n\t\treturn numericPredicateId;\n\t}\n\n\t/**\n\t * @return Number value property to search with.\n\t */\n\t@Nonnull\n\tpublic ObjectProperty<Number> numericValuePropertyProperty() {\n\t\treturn numericValueProperty;\n\t}\n\n\t/**\n\t * @return Type of {@link #numericValuePropertyProperty()} value.\n\t */\n\t@Nonnull\n\tpublic ObjectProperty<Class<? extends Number>> numericTypePropertyProperty() {\n\t\treturn numericTypeProperty;\n\t}\n\n\t@Nullable\n\t@Override\n\tprotected Query buildQuery() {\n\t\t// Skip if blank\n\t\tif (isBlank.getValue())\n\t\t\treturn null;\n\n\t\tNumber search = numericValueProperty.get();\n\t\tString id = numericPredicateId.get();\n\n\t\t// Skip if unrecognized number predicate id\n\t\tNumberPredicate predicate = numberPredicateProvider.newBiNumberPredicate(id, search);\n\t\tif (predicate == null)\n\t\t\treturn null;\n\n\t\treturn new NumberQuery(predicate);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/search/SearchContextSource.java",
    "content": "package software.coley.recaf.ui.pane.search;\n\nimport software.coley.recaf.services.cell.context.BasicContextSource;\n\n/**\n * Reference context source used by {@link AbstractSearchPane}.\n *\n * @author Matt Coley\n */\npublic class SearchContextSource extends BasicContextSource {\n\t/** Context singleton */\n\tpublic static final SearchContextSource SEARCH_INSTANCE = new SearchContextSource();\n\n\tprivate SearchContextSource() {\n\t\tsuper(false);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/pane/search/StringSearchPane.java",
    "content": "package software.coley.recaf.ui.pane.search;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.property.StringProperty;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.control.ComboBox;\nimport javafx.scene.control.TextField;\nimport javafx.scene.layout.ColumnConstraints;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.Priority;\nimport org.reactfx.EventStreams;\nimport software.coley.collections.Lists;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.search.SearchService;\nimport software.coley.recaf.services.search.match.StringPredicate;\nimport software.coley.recaf.services.search.match.StringPredicateProvider;\nimport software.coley.recaf.services.search.query.Query;\nimport software.coley.recaf.services.search.query.StringQuery;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.BoundBiDiComboBox;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.util.ToStringConverter;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static software.coley.recaf.services.search.match.StringPredicateProvider.KEY_ANYTHING;\nimport static software.coley.recaf.services.search.match.StringPredicateProvider.KEY_NOTHING;\n\n/**\n * String search pane.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class StringSearchPane extends AbstractSearchPane {\n\tprivate final StringProperty stringPredicateId = new SimpleStringProperty();\n\tprivate final StringProperty stringValue;\n\tprivate final StringPredicateProvider stringPredicateProvider;\n\n\t@Inject\n\tpublic StringSearchPane(@Nonnull WorkspaceManager workspaceManager,\n\t\t\t\t\t\t\t@Nonnull SearchService searchService,\n\t\t\t\t\t\t\t@Nonnull CellConfigurationService configurationService,\n\t\t\t\t\t\t\t@Nonnull Actions actions,\n\t\t\t\t\t\t\t@Nonnull StringPredicateProvider stringPredicateProvider) {\n\t\tsuper(workspaceManager, searchService, configurationService, actions);\n\n\t\tthis.stringPredicateProvider = stringPredicateProvider;\n\n\t\tList<String> stringPredicates = stringPredicateProvider.getBiStringMatchers().keySet().stream()\n\t\t\t\t.filter(s -> !KEY_ANYTHING.equals(s) && !KEY_NOTHING.equals(s))\n\t\t\t\t.sorted().toList();\n\t\tTextField textField = new TextField();\n\t\tComboBox<String> modeCombo = new BoundBiDiComboBox<>(stringPredicateId, stringPredicates,\n\t\t\t\tToStringConverter.from(s -> Lang.get(StringPredicate.TRANSLATION_PREFIX + s)));\n\t\tmodeCombo.getSelectionModel().select(StringPredicateProvider.KEY_CONTAINS);\n\n\t\tGridPane input = new GridPane();\n\t\tColumnConstraints colTexts = new ColumnConstraints();\n\t\tColumnConstraints colCombos = new ColumnConstraints();\n\t\tcolTexts.setFillWidth(true);\n\t\tinput.setAlignment(Pos.CENTER);\n\t\tinput.getColumnConstraints().addAll(colTexts, colCombos);\n\t\tinput.setHgap(10);\n\t\tinput.setPadding(new Insets(10));\n\t\tinput.addRow(0, textField, modeCombo);\n\t\tGridPane.setHgrow(textField, Priority.ALWAYS);\n\n\t\tstringValue = textField.textProperty();\n\t\tEventStreams.changesOf(stringValue)\n\t\t\t\t.or(EventStreams.changesOf(stringPredicateId))\n\t\t\t\t.reduceSuccessions(Collections::singletonList, Lists::add, Duration.ofMillis(Editor.SHORT_DELAY_MS))\n\t\t\t\t.addObserver(unused -> search());\n\n\t\tsetInputs(input);\n\t}\n\n\t/**\n\t * @return Predicate ID property within {@link StringPredicateProvider} used for {@link #stringValueProperty()}.\n\t */\n\t@Nonnull\n\tpublic StringProperty stringPredicateIdProperty() {\n\t\treturn stringPredicateId;\n\t}\n\n\t/**\n\t * @return String value property to search with.\n\t */\n\t@Nonnull\n\tpublic StringProperty stringValueProperty() {\n\t\treturn stringValue;\n\t}\n\n\t@Nullable\n\t@Override\n\tprotected Query buildQuery() {\n\t\tString search = stringValue.get();\n\t\tString id = stringPredicateId.get();\n\n\t\t// Skip for blank input\n\t\tif (search.isBlank())\n\t\t\treturn null;\n\n\t\t// Validate regex input\n\t\tif (id.contains(\"regex\") && !RegexUtil.validate(search).valid())\n\t\t\treturn null;\n\n\t\t// Skip if unrecognized string predicate id\n\t\tStringPredicate predicate = stringPredicateProvider.newBiStringPredicate(id, search);\n\t\tif (predicate == null)\n\t\t\treturn null;\n\n\t\treturn new StringQuery(predicate);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/window/AbstractIdentifiableStage.java",
    "content": "package software.coley.recaf.ui.window;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.stage.Stage;\nimport javafx.stage.StageStyle;\n\n/**\n * Base implementation of {@link IdentifiableStage}.\n *\n * @author Matt Coley\n */\npublic class AbstractIdentifiableStage extends RecafStage implements IdentifiableStage {\n\tprivate final String id;\n\n\t/**\n\t * Decorated stage.\n\t *\n\t * @param id\n\t * \t\tUnique stage identifier.\n\t */\n\tpublic AbstractIdentifiableStage(@Nonnull String id) {\n\t\tthis(StageStyle.DECORATED, id);\n\t}\n\n\t/**\n\t * Stage of the given style.\n\t *\n\t * @param id\n\t * \t\tUnique stage identifier.\n\t * @param style\n\t * \t\tSpecific stage style.\n\t */\n\tpublic AbstractIdentifiableStage(@Nonnull StageStyle style, @Nonnull String id) {\n\t\tsuper(style);\n\t\tthis.id = id;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic String getId() {\n\t\treturn id;\n\t}\n\n\t@Nonnull\n\t@Override\n\tpublic Stage asStage() {\n\t\treturn this;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/window/ConfigWindow.java",
    "content": "package software.coley.recaf.ui.window;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.layout.BorderPane;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.ui.pane.ConfigPane;\nimport software.coley.recaf.util.Lang;\n\n/**\n * Window wrapper for {@link ConfigPane}.\n *\n * @author Matt Coley\n * @see ConfigPane\n */\n@Dependent\npublic class ConfigWindow extends AbstractIdentifiableStage {\n\t@Inject\n\tpublic ConfigWindow(@Nonnull ConfigPane configPane) {\n\t\tsuper(WindowManager.WIN_CONFIG);\n\n\t\t// Layout\n\t\ttitleProperty().bind(Lang.getBinding(\"menu.config\"));\n\t\tsetMinWidth(750);\n\t\tsetMinHeight(450);\n\t\tsetScene(new RecafScene(new BorderPane(configPane), 750, 450));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/window/DeobfuscationWindow.java",
    "content": "package software.coley.recaf.ui.window;\n\nimport atlantafx.base.controls.ModalPane;\nimport atlantafx.base.controls.Spacer;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.enterprise.inject.Instance;\nimport jakarta.inject.Inject;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.IntegerProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleIntegerProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ListChangeListener;\nimport javafx.collections.ObservableList;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Group;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.CheckBox;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ListView;\nimport javafx.scene.control.ProgressBar;\nimport javafx.scene.control.SplitPane;\nimport javafx.scene.control.Tab;\nimport javafx.scene.control.TabPane;\nimport javafx.scene.control.TreeCell;\nimport javafx.scene.control.TreeItem;\nimport javafx.scene.control.TreeView;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.GridPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.StackPane;\nimport javafx.scene.layout.VBox;\nimport javafx.scene.text.TextAlignment;\nimport me.darknet.assembler.error.Error;\nimport org.kordamp.ikonli.Ikon;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.DebuggingLogger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.services.assembler.AssemblerPipelineManager;\nimport software.coley.recaf.services.assembler.JvmAssemblerPipeline;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.decompile.DecompilerManager;\nimport software.coley.recaf.services.deobfuscation.transform.generic.CallResultInliningTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.CycleClassRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.DeadCodeRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.DuplicateAnnotationRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.DuplicateCatchMergingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.EnumNameRestorationTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.FrameRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.GotoInliningTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.IllegalAnnotationRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.IllegalSignatureRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.IllegalVarargsRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.KotlinNameRestorationTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.LongAnnotationRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.LongExceptionRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.OpaqueConstantFoldingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.OpaquePredicateFoldingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.RedundantTryCatchRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.SourceNameRestorationTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.StaticValueInliningTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.UnknownAttributeRemovingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.VariableFoldingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.generic.VariableTableNormalizingTransformer;\nimport software.coley.recaf.services.deobfuscation.transform.specific.DashOpaqueSeedFoldingTransformer;\nimport software.coley.recaf.services.info.association.FileTypeSyntaxAssociationService;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.transform.ClassTransformer;\nimport software.coley.recaf.services.transform.JvmClassTransformer;\nimport software.coley.recaf.services.transform.JvmTransformResult;\nimport software.coley.recaf.services.transform.TransformationApplier;\nimport software.coley.recaf.services.transform.TransformationApplierService;\nimport software.coley.recaf.services.transform.TransformationException;\nimport software.coley.recaf.services.transform.TransformationFeedback;\nimport software.coley.recaf.services.transform.TransformationManager;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.LanguageStylesheets;\nimport software.coley.recaf.ui.config.WorkspaceExplorerConfig;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.BoundIntSpinner;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.ReorderableListCell;\nimport software.coley.recaf.ui.control.popup.ClassSelectionPopup;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.ui.control.richtext.bracket.SelectedBracketTracking;\nimport software.coley.recaf.ui.control.richtext.search.SearchBar;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.util.threading.Batch;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.util.threading.ThreadUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.ClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.stream.Collectors;\n\n/**\n * Pane for previewing deobfuscation transformers.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class DeobfuscationWindow extends RecafStage {\n\tprivate static final DebuggingLogger logger = Logging.get(DeobfuscationWindow.class);\n\tprivate final TransformationManager transformationManager;\n\tprivate final TransformationApplierService transformationApplierService;\n\tprivate final DecompilerManager decompilerManager;\n\tprivate final AssemblerPipelineManager assemblerPipelineManager;\n\tprivate final ObservableList<CategorizedTransformer> transformerOrder = FXCollections.observableArrayList();\n\tprivate final BooleanProperty hasSelection = new SimpleBooleanProperty();\n\tprivate final IntegerProperty maxPasses = new SimpleIntegerProperty(5);\n\tprivate final WorkspaceManager workspaceManager;\n\n\t@Inject\n\t@SuppressWarnings(\"unchecked\")\n\tpublic DeobfuscationWindow(@Nonnull TransformationManager transformationManager,\n\t                           @Nonnull TransformationApplierService transformationApplierService,\n\t                           @Nonnull WorkspaceManager workspaceManager,\n\t                           @Nonnull DecompilerManager decompilerManager,\n\t                           @Nonnull AssemblerPipelineManager assemblerPipelineManager,\n\t                           @Nonnull FileTypeSyntaxAssociationService languageAssociation,\n\t                           @Nonnull CellConfigurationService configurationService,\n\t                           @Nonnull Actions actions,\n\t                           @Nonnull WorkspaceExplorerConfig explorerConfig,\n\t                           @Nonnull Instance<SearchBar> searchBarProvider) {\n\t\tthis.transformationManager = transformationManager;\n\t\tthis.transformationApplierService = transformationApplierService;\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.decompilerManager = decompilerManager;\n\t\tthis.assemblerPipelineManager = assemblerPipelineManager;\n\n\t\tObjectProperty<FullFeedback> transformFeedback = new SimpleObjectProperty<>();\n\n\t\t// TODO: This UI could use some polish (Rewriting), but the core functionality is here.\n\t\t//  - The initial size often leads to the transform preview being very small.\n\t\t//  - The disassembler view in the preview pane isn't a full 'AssemblerPane' so it lacks features.\n\t\t//    - ControlFlowLines cannot be installed because that requires an AssemblerPane.\n\t\t//  - The preview pane's controls on the bottom are uniform sized which looks bad with text of different lengths.\n\t\t//  - Later it would make sense to make config objects for some transformers.\n\t\t//    - These would exist in the global config and would be editable there but should also be shown here.\n\t\t//    - Currently there is nowhere to display this config in the UI as we already are tight on space.\n\t\t//    - Examples:\n\t\t//      - The CallInlineTransformer needs to have the simulation step max be configurable.\n\t\tBorderPane transformerTreePane = new BorderPane();\n\t\t{\n\t\t\tBoundLabel title = new BoundLabel(Lang.getBinding(\"deobf.selection.title\"));\n\t\t\ttitle.getStyleClass().add(Styles.TITLE_4);\n\t\t\ttitle.setPadding(new Insets(0, 0, 5, 5));\n\n\t\t\t// TODO: It would be nice to have the tree be auto-generated in some fashion.\n\t\t\t//  But we may not want a flat category system, so an enum probably is too short-sighted.\n\t\t\t//  It also shouldn't be annoying to configure for each transformer.\n\t\t\tTreeItem<Selection> root = new TreeItem<>(new Selection.Root());\n\t\t\tTreeItem<Selection> generic = new TreeItem<>(new Selection.Category(\"deobf.tree.generic\", CarbonIcons.CIRCLE_DASH));\n\t\t\tTreeItem<Selection> anti = new TreeItem<>(new Selection.Category(\"deobf.tree.generic.anticrasher\", CarbonIcons.DEBUG));\n\t\t\tanti.getChildren().addAll(of(\n\t\t\t\t\tCycleClassRemovingTransformer.class,\n\t\t\t\t\tDuplicateAnnotationRemovingTransformer.class,\n\t\t\t\t\tLongAnnotationRemovingTransformer.class,\n\t\t\t\t\tLongExceptionRemovingTransformer.class,\n\t\t\t\t\tFrameRemovingTransformer.class,\n\t\t\t\t\tIllegalAnnotationRemovingTransformer.class,\n\t\t\t\t\tIllegalSignatureRemovingTransformer.class,\n\t\t\t\t\tIllegalVarargsRemovingTransformer.class,\n\t\t\t\t\tUnknownAttributeRemovingTransformer.class\n\t\t\t));\n\t\t\tTreeItem<Selection> optimize = new TreeItem<>(new Selection.Category(\"deobf.tree.generic.optimize\", CarbonIcons.CLEAN));\n\t\t\toptimize.getChildren().addAll(of(\n\t\t\t\t\tCallResultInliningTransformer.class,\n\t\t\t\t\tDeadCodeRemovingTransformer.class,\n\t\t\t\t\tDuplicateCatchMergingTransformer.class,\n\t\t\t\t\tGotoInliningTransformer.class,\n\t\t\t\t\tOpaqueConstantFoldingTransformer.class,\n\t\t\t\t\tOpaquePredicateFoldingTransformer.class,\n\t\t\t\t\tRedundantTryCatchRemovingTransformer.class,\n\t\t\t\t\tStaticValueInliningTransformer.class,\n\t\t\t\t\tVariableFoldingTransformer.class,\n\t\t\t\t\tVariableTableNormalizingTransformer.class\n\t\t\t));\n\t\t\tTreeItem<Selection> restoration = new TreeItem<>(new Selection.Category(\"deobf.tree.generic.restoration\", CarbonIcons.AI_RESULTS));\n\t\t\trestoration.getChildren().addAll(of(\n\t\t\t\t\tEnumNameRestorationTransformer.class,\n\t\t\t\t\tKotlinNameRestorationTransformer.class,\n\t\t\t\t\tSourceNameRestorationTransformer.class\n\t\t\t));\n\t\t\tgeneric.getChildren().addAll(\n\t\t\t\t\tanti,\n\t\t\t\t\toptimize,\n\t\t\t\t\trestoration\n\t\t\t);\n\t\t\tTreeItem<Selection> specific = new TreeItem<>(new Selection.Category(\"deobf.tree.specific\", CarbonIcons.CENTER_CIRCLE));\n\t\t\tspecific.getChildren().addAll(of(\n\t\t\t\t\t// TODO: When we have more specific transformers, separate into sub-sections instead of putting them all flat here.\n\t\t\t\t\tDashOpaqueSeedFoldingTransformer.class\n\t\t\t));\n\t\t\tTreeItem<Selection> pluginProvided = new TreeItem<>(new Selection.Category(\"deobf.tree.plugin-provided\", CarbonIcons.PLUG));\n\t\t\tpluginProvided.getChildren().addAll(ofCollection(Unchecked.cast(transformationManager.getThirdPartyJvmTransformers())));\n\t\t\troot.getChildren().addAll(\n\t\t\t\t\tgeneric,\n\t\t\t\t\tspecific,\n\t\t\t\t\tpluginProvided\n\t\t\t);\n\t\t\tgeneric.setExpanded(true);\n\t\t\tpluginProvided.setExpanded(true);\n\n\t\t\tTreeView<Selection> transformerTree = new TreeView<>();\n\t\t\ttransformerTree.setCellFactory(view -> new TreeCell<>() {\n\t\t\t\tprotected void updateItem(Selection item, boolean isEmpty) {\n\t\t\t\t\tsuper.updateItem(item, isEmpty);\n\n\t\t\t\t\tif (item == null || isEmpty) {\n\t\t\t\t\t\tsetText(null);\n\t\t\t\t\t\tsetGraphic(null);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tswitch (item) {\n\t\t\t\t\t\tcase Selection.Root ir -> {\n\t\t\t\t\t\t\t// Not intended to be shown.\n\t\t\t\t\t\t\tsetText(null);\n\t\t\t\t\t\t\tsetGraphic(null);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase Selection.Category ic -> {\n\t\t\t\t\t\t\tsetText(Lang.get(ic.key));\n\t\t\t\t\t\t\tsetGraphic(new FontIconView(ic.icon));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase Selection.Transformer it -> {\n\t\t\t\t\t\t\tClassTransformer transformer = it.transformer();\n\t\t\t\t\t\t\tClass<? extends ClassTransformer> transformerClass = transformer.getClass();\n\t\t\t\t\t\t\tCheckBox toggle = new CheckBox();\n\t\t\t\t\t\t\ttoggle.setSelected(transformerOrder.stream().anyMatch(i -> i.matches(transformerClass)));\n\t\t\t\t\t\t\ttoggle.selectedProperty().addListener((ob, old, cur) -> {\n\t\t\t\t\t\t\t\tif (cur) {\n\t\t\t\t\t\t\t\t\tSelection.Category parentCategory = Unchecked.cast(getTreeItem().getParent().getValue());\n\t\t\t\t\t\t\t\t\ttransformerOrder.add(new CategorizedTransformer(it, parentCategory));\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\ttransformerOrder.removeIf(i -> i.matches(transformerClass));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\thasSelection.set(!transformerOrder.isEmpty());\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tsetText(transformer.name());\n\t\t\t\t\t\t\tsetGraphic(toggle);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\ttransformerTree.setRoot(root);\n\t\t\ttransformerTree.setShowRoot(false);\n\t\t\ttransformerTreePane.setTop(title);\n\t\t\ttransformerTreePane.setCenter(transformerTree);\n\n\t\t\t// TODO: Having the ability to load/save presets would be nice.\n\t\t\t//  - We should offer a basic preset with *everything* turned on but with the most optimal order pre-defined\n\t\t}\n\t\tBorderPane transformerOrderPane = new BorderPane();\n\t\t{\n\t\t\tBoundLabel title = new BoundLabel(Lang.getBinding(\"deobf.order.title\"));\n\t\t\ttitle.getStyleClass().add(Styles.TITLE_4);\n\t\t\ttitle.setPadding(new Insets(0, 0, 5, 5));\n\n\t\t\tBoundLabel hint = new BoundLabel(Lang.getBinding(\"deobf.order.hint\"));\n\t\t\thint.setTextAlignment(TextAlignment.CENTER);\n\t\t\tListView<CategorizedTransformer> transformerOrderList = new ListView<>(transformerOrder);\n\t\t\ttransformerOrderList.setCellFactory(view -> new ReorderableListCell<>() {\n\t\t\t\t@Override\n\t\t\t\tpublic void updateIndex(int newIndex) {\n\t\t\t\t\tsuper.updateIndex(newIndex);\n\n\t\t\t\t\t// Refresh the UI so recommendation labels are updated.\n\t\t\t\t\tupdateItem(getItem(), isEmpty());\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tprotected void updateItem(CategorizedTransformer item, boolean isEmpty) {\n\t\t\t\t\tsuper.updateItem(item, isEmpty);\n\n\t\t\t\t\tif (item == null || isEmpty) {\n\t\t\t\t\t\tsetText(null);\n\t\t\t\t\t\tsetGraphic(null);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tint index = getIndex();\n\t\t\t\t\t\tList<? extends Class<? extends ClassTransformer>> transformerClasses = transformerOrder.stream()\n\t\t\t\t\t\t\t\t.map(t -> t.transformer().transformer().getClass())\n\t\t\t\t\t\t\t\t.toList();\n\t\t\t\t\t\tClassTransformer transformer = item.transformer().transformer();\n\t\t\t\t\t\tList<ClassTransformer> missingPredecessors = Unchecked.cast(transformer.recommendedPredecessors().stream()\n\t\t\t\t\t\t\t\t.filter(pre -> {\n\t\t\t\t\t\t\t\t\tint i = transformerClasses.indexOf(pre);\n\t\t\t\t\t\t\t\t\treturn i < 0 || i > index;\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.map(pre -> {\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tif (JvmClassTransformer.class.isAssignableFrom(pre))\n\t\t\t\t\t\t\t\t\t\t\treturn transformationManager.newJvmTransformer(Unchecked.cast(pre));\n\t\t\t\t\t\t\t\t\t} catch (TransformationException e) {\n\t\t\t\t\t\t\t\t\t\tlogger.error(\"Failed to initialize instance of {}\", pre, e);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.filter(Objects::nonNull)\n\t\t\t\t\t\t\t\t.toList());\n\t\t\t\t\t\tList<ClassTransformer> missingSuccessors = Unchecked.cast(transformer.recommendedSuccessors().stream()\n\t\t\t\t\t\t\t\t.filter(post -> {\n\t\t\t\t\t\t\t\t\tint i = transformerClasses.indexOf(post);\n\t\t\t\t\t\t\t\t\treturn i < 0 || i < index;\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.map(post -> {\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tif (JvmClassTransformer.class.isAssignableFrom(post))\n\t\t\t\t\t\t\t\t\t\t\treturn transformationManager.newJvmTransformer(Unchecked.cast(post));\n\t\t\t\t\t\t\t\t\t} catch (TransformationException e) {\n\t\t\t\t\t\t\t\t\t\tlogger.error(\"Failed to initialize instance of {}\", post, e);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.filter(Objects::nonNull)\n\t\t\t\t\t\t\t\t.toList());\n\n\t\t\t\t\t\tLabel name = new Label(item.transformer().name(), new FontIconView(item.category().icon));\n\t\t\t\t\t\tBorderPane wrapper = new BorderPane();\n\t\t\t\t\t\tVBox missing = new VBox(name);\n\t\t\t\t\t\tmissing.setPadding(new Insets(10));\n\t\t\t\t\t\tmissing.setSpacing(10);\n\t\t\t\t\t\tmissing.setAlignment(Pos.CENTER_LEFT);\n\t\t\t\t\t\tif (!missingPredecessors.isEmpty()) {\n\t\t\t\t\t\t\tString missingTransformers = Lang.get(\"deobf.order.pre\") + \":\\n - \" + missingPredecessors.stream().map(ClassTransformer::name).collect(Collectors.joining(\"\\n - \"));\n\t\t\t\t\t\t\tLabel missingLabel = new Label(missingTransformers);\n\t\t\t\t\t\t\tmissingLabel.setPadding(new Insets(0, 0, 0, 20));\n\t\t\t\t\t\t\tmissingLabel.getStyleClass().add(Styles.TEXT_SUBTLE);\n\t\t\t\t\t\t\tmissing.getChildren().add(missingLabel);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!missingSuccessors.isEmpty()) {\n\t\t\t\t\t\t\tString missingTransformers = Lang.get(\"deobf.order.suc\") + \":\\n - \" + missingSuccessors.stream().map(ClassTransformer::name).collect(Collectors.joining(\"\\n - \"));\n\t\t\t\t\t\t\tLabel missingLabel = new Label(missingTransformers);\n\t\t\t\t\t\t\tmissingLabel.setPadding(new Insets(0, 0, 0, 20));\n\t\t\t\t\t\t\tmissingLabel.getStyleClass().add(Styles.TEXT_SUBTLE);\n\t\t\t\t\t\t\tmissing.getChildren().add(missingLabel);\n\t\t\t\t\t\t}\n\t\t\t\t\t\twrapper.setBottom(missing);\n\t\t\t\t\t\tsetGraphic(wrapper);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\tStackPane wrapper = new StackPane(transformerOrderList, hint);\n\t\t\twrapper.setAlignment(Pos.CENTER);\n\t\t\ttransformerOrderPane.setTop(title);\n\t\t\ttransformerOrderPane.setCenter(wrapper);\n\t\t\thint.visibleProperty().bind(hasSelection.not());\n\t\t}\n\t\tBorderPane transformPreviewPane = new BorderPane();\n\t\t{\n\t\t\tBoundLabel title = new BoundLabel(Lang.getBinding(\"deobf.preview.title\"));\n\t\t\ttitle.getStyleClass().add(Styles.TITLE_4);\n\t\t\ttitle.setPadding(new Insets(0, 0, 5, 5));\n\n\t\t\tTab beforeTab = new Tab();\n\t\t\tTab afterTab = new Tab();\n\t\t\tbeforeTab.setClosable(false);\n\t\t\tafterTab.setClosable(false);\n\t\t\tTransformPreview beforePreview = new TransformPreview(languageAssociation, searchBarProvider, false);\n\t\t\tTransformPreview afterPreview = new TransformPreview(languageAssociation, searchBarProvider, true);\n\t\t\tbeforeTab.setContent(beforePreview);\n\t\t\tafterTab.setContent(afterPreview);\n\t\t\tbeforeTab.textProperty().bind(Lang.getBinding(\"misc.before\"));\n\t\t\tafterTab.textProperty().bind(Lang.getBinding(\"misc.after\"));\n\t\t\tbeforeTab.setGraphic(new FontIconView(CarbonIcons.LICENSE));\n\t\t\tafterTab.setGraphic(new FontIconView(CarbonIcons.LICENSE_MAINTENANCE));\n\t\t\tTabPane tabs = new TabPane(beforeTab, afterTab);\n\t\t\ttabs.getSelectionModel().select(afterTab);\n\n\t\t\tBoundIntSpinner maxPassesSpinner = new BoundIntSpinner(maxPasses, 1, 50);\n\t\t\tButton pickClass = new ActionButton(CarbonIcons.ADD, Lang.getBinding(\"deobf.preview.pick\"), () -> {\n\t\t\t\tnew ClassSelectionPopup(actions, configurationService, explorerConfig, workspaceManager.getCurrent(), path -> {\n\t\t\t\t\tClassInfo selection = path.getValue();\n\n\t\t\t\t\tbeforePreview.setClassInfo(selection);\n\t\t\t\t\tafterPreview.setClassInfo(selection);\n\n\t\t\t\t\tbeforePreview.updatePreview();\n\t\t\t\t\tafterPreview.updatePreview();\n\t\t\t\t}).showAndWait();\n\t\t\t});\n\t\t\tButton togglePreview = new ActionButton(CarbonIcons.ARROWS_HORIZONTAL, Lang.getBinding(\"deobf.preview.toggle-mode\"), () -> {\n\t\t\t\tbeforePreview.togglePreviewMode();\n\t\t\t\tafterPreview.togglePreviewMode();\n\t\t\t});\n\t\t\tButton applyToWorkspace = new ActionButton(CarbonIcons.PLAY, Lang.getBinding(\"deobf.apply\"), () -> {\n\t\t\t\tTransformationApplier applier = transformationApplierService.newApplierForCurrentWorkspace();\n\t\t\t\tif (applier == null)\n\t\t\t\t\treturn;\n\n\t\t\t\tList<Class<? extends JvmClassTransformer>> list = Unchecked.cast(transformerOrder.stream()\n\t\t\t\t\t\t.map(c -> c.transformer().type())\n\t\t\t\t\t\t.filter(JvmClassTransformer.class::isAssignableFrom)\n\t\t\t\t\t\t.toList());\n\t\t\t\ttry {\n\t\t\t\t\tFullFeedback feedback = new FullFeedback();\n\t\t\t\t\ttransformFeedback.set(feedback);\n\t\t\t\t\tapplier.setMaxPasses(maxPasses.get());\n\t\t\t\t\tJvmTransformResult result = applier.transformJvm(list, feedback);\n\t\t\t\t\tif (!feedback.hasRequestedCancellation()) {\n\t\t\t\t\t\tresult.apply();\n\t\t\t\t\t\tFxThreadUtil.run(this::hide);\n\t\t\t\t\t}\n\t\t\t\t} catch (TransformationException e) {\n\t\t\t\t\t// TODO: A tooltip or something showing would also be nice to have here since this is in a separate\n\t\t\t\t\t//  window which could mean the user cannot see the logging pane output.\n\t\t\t\t\tlogger.error(\"Failed applying transformations to workspace\", e);\n\t\t\t\t}\n\t\t\t\ttransformFeedback.set(null);\n\t\t\t}).async();\n\t\t\tapplyToWorkspace.disableProperty().bind(hasSelection.not().or(transformFeedback.isNotNull()));\n\t\t\ttransformerOrder.addListener((ListChangeListener<CategorizedTransformer>) change -> {\n\t\t\t\tbeforePreview.updatePreview();\n\t\t\t\tafterPreview.updatePreview();\n\t\t\t});\n\t\t\tmaxPasses.addListener((ob, old, cur) -> {\n\t\t\t\tbeforePreview.updatePreview();\n\t\t\t\tafterPreview.updatePreview();\n\t\t\t});\n\n\t\t\tBoundLabel passesLabel = new BoundLabel(Lang.getBinding(\"deobf.max-passes\"));\n\t\t\tpassesLabel.setTextAlignment(TextAlignment.CENTER);\n\t\t\tpassesLabel.setPadding(new Insets(5));\n\t\t\tHBox tools = new HBox(pickClass, new Spacer(), togglePreview, new Spacer(),\n\t\t\t\t\tmaxPassesSpinner, passesLabel, applyToWorkspace);\n\t\t\ttools.setAlignment(Pos.CENTER);\n\t\t\tStackPane wrapper = new StackPane(tabs, tools);\n\t\t\tStackPane.setMargin(tools, new Insets(20));\n\t\t\twrapper.setAlignment(Pos.BOTTOM_RIGHT);\n\t\t\ttransformPreviewPane.setTop(title);\n\t\t\ttransformPreviewPane.setCenter(wrapper);\n\t\t\ttransformPreviewPane.setBottom(tools);\n\t\t}\n\n\t\tSplitPane split = new SplitPane(transformerTreePane, transformerOrderPane, transformPreviewPane);\n\t\tSplitPane.setResizableWithParent(transformerTreePane, false);\n\t\tSplitPane.setResizableWithParent(transformerOrderPane, false);\n\t\tsplit.setDividerPositions(0.3, 0.6);\n\t\tsplit.setPadding(new Insets(4));\n\t\ttransformerTreePane.setPadding(new Insets(4));\n\t\ttransformerOrderPane.setPadding(new Insets(4));\n\t\ttransformPreviewPane.setPadding(new Insets(4));\n\t\tBorderPane inputPane = new BorderPane(split);\n\n\t\tModalPane modal = new ModalPane();\n\t\tmodal.setPersistent(true); // Prevent escape key from closing the dialog while transforming.\n\t\tmodal.setAlignment(Pos.CENTER);\n\t\ttransformFeedback.addListener((ob, oldFeedback, newFeedback) -> FxThreadUtil.run(() -> {\n\t\t\t// The 'apply' button is configured to run asynchronously, so this callback is not on the FX thread.\n\t\t\t// This is why we have the wrapping run call above.\n\t\t\tif (newFeedback != null) {\n\t\t\t\t// Basic progress bar + label to display how far along the transformation process is.\n\t\t\t\tButton cancel = new ActionButton(CarbonIcons.CLOSE, Lang.getBinding(\"dialog.cancel\"), newFeedback::cancel).once();\n\t\t\t\tLabel label = new Label();\n\t\t\t\tlabel.setMinWidth(200);\n\t\t\t\tProgressBar progressBar = new ProgressBar(0);\n\t\t\t\tprogressBar.setMinWidth(250);\n\n\t\t\t\t// Manually sizing the controls above is easier than messing with auto-sizing.\n\t\t\t\t// I've tried various approaches with the GridPane, and it refuses to expand the progress bar properly.\n\t\t\t\tGridPane content = new GridPane();\n\t\t\t\tcontent.getStyleClass().addAll(Styles.BORDER_DEFAULT, Styles.BG_DEFAULT);\n\t\t\t\tcontent.setAlignment(Pos.CENTER);\n\t\t\t\tcontent.setPadding(new Insets(20));\n\t\t\t\tcontent.setHgap(10);\n\t\t\t\tcontent.setVgap(20);\n\t\t\t\tcontent.add(progressBar, 0, 0, 2, 1);\n\t\t\t\tcontent.add(label, 0, 1);\n\t\t\t\tcontent.add(cancel, 1, 1);\n\t\t\t\tcontent.setMinSize(400, 100);\n\t\t\t\tnewFeedback.observer = new FullFeedback.FeedbackObserver() {\n\t\t\t\t\tlong lastUpdate = 0;\n\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic void update() {\n\t\t\t\t\t\tlong now = System.currentTimeMillis();\n\t\t\t\t\t\tif (now - lastUpdate > 100) {\n\t\t\t\t\t\t\tlastUpdate = now;\n\t\t\t\t\t\t\tFxThreadUtil.run(() -> {\n\t\t\t\t\t\t\t\tint classes = newFeedback.classesVisited.size();\n\t\t\t\t\t\t\t\tint maxClasses = newFeedback.maxClasses;\n\t\t\t\t\t\t\t\tprogressBar.setProgress((double) classes / maxClasses);\n\t\t\t\t\t\t\t\tlabel.setText(classes + \" / \" + maxClasses + \" (Pass: \" + newFeedback.currentPass + \")\");\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tmodal.show(new Group(content));\n\t\t\t} else {\n\t\t\t\tif (oldFeedback != null)\n\t\t\t\t\toldFeedback.observer = null;\n\t\t\t\tmodal.hide();\n\t\t\t}\n\t\t}));\n\n\t\t// Window setup\n\t\ttitleProperty().bind(Lang.getBinding(\"deobf\"));\n\t\tsetScene(new RecafScene(new StackPane(inputPane, modal)));\n\t\tsetWidth(900);\n\t\tsetHeight(600);\n\t}\n\n\t@SafeVarargs\n\tprivate List<TreeItem<Selection>> of(Class<? extends ClassTransformer>... transformerClasses) {\n\t\treturn ofCollection(List.of(transformerClasses));\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate List<TreeItem<Selection>> ofCollection(Collection<Class<? extends ClassTransformer>> transformerClasses) {\n\t\tList<TreeItem<Selection>> results = new ArrayList<>(transformerClasses.size());\n\t\tfor (Class<? extends ClassTransformer> transformerClass : transformerClasses) {\n\t\t\ttry {\n\t\t\t\tif (JvmClassTransformer.class.isAssignableFrom(transformerClass)) {\n\t\t\t\t\tClass<JvmClassTransformer> jvmTransformerClass = (Class<JvmClassTransformer>) transformerClass;\n\t\t\t\t\tJvmClassTransformer transformer = transformationManager.newJvmTransformer(jvmTransformerClass);\n\t\t\t\t\tresults.add(new TreeItem<>(new Selection.Transformer(transformer)));\n\t\t\t\t}\n\t\t\t\t// TODO: Android transformer case when we get to implementing that system\n\t\t\t} catch (TransformationException e) {\n\t\t\t\tlogger.error(\"Failed to initialize instance of {}\", transformerClass, e);\n\t\t\t}\n\t\t}\n\t\treturn results;\n\t}\n\n\tprivate class TransformPreview extends BorderPane {\n\t\tprivate final Batch deobfuscationBatch = ThreadUtil.batch(ThreadPoolFactory.newSingleThreadExecutor(\"deobfuscation-preview\"));\n\t\tprivate final boolean andApply;\n\t\tprivate final Editor editorDecompile;\n\t\tprivate final Editor editorAssembly;\n\t\tprivate ClassInfo classInfo;\n\n\t\tprivate TransformPreview(@Nonnull FileTypeSyntaxAssociationService languageAssociation,\n\t\t                         @Nonnull Instance<SearchBar> searchBarProvider,\n\t\t                         boolean andApply) {\n\t\t\tthis.andApply = andApply;\n\n\t\t\teditorDecompile = new Editor();\n\t\t\teditorDecompile.setSelectedBracketTracking(new SelectedBracketTracking());\n\t\t\teditorDecompile.getRootLineGraphicFactory().addDefaultCodeGraphicFactories();\n\t\t\teditorDecompile.getCodeArea().setEditable(false);\n\t\t\tlanguageAssociation.configureEditorSyntax(\"java\", editorDecompile);\n\n\t\t\teditorAssembly = new Editor();\n\t\t\teditorAssembly.getCodeArea().getStylesheets().add(LanguageStylesheets.getJasmStylesheet());\n\t\t\teditorAssembly.getRootLineGraphicFactory().addDefaultCodeGraphicFactories();\n\t\t\teditorAssembly.setSelectedBracketTracking(new SelectedBracketTracking());\n\t\t\teditorAssembly.getCodeArea().setEditable(false);\n\t\t\tlanguageAssociation.configureEditorSyntax(\"jasm\", editorAssembly);\n\n\t\t\tSearchBar searchBar1 = searchBarProvider.get();\n\t\t\tSearchBar searchBar2 = searchBarProvider.get();\n\t\t\tsearchBar1.install(editorDecompile);\n\t\t\tsearchBar2.install(editorAssembly);\n\n\t\t\t// TODO: Make it a preference which is shown first\n\t\t\tsetCenter(editorDecompile);\n\n\t\t\tupdatePreview();\n\t\t}\n\n\t\tpublic void setClassInfo(@Nonnull ClassInfo classInfo) {\n\t\t\tthis.classInfo = classInfo;\n\t\t}\n\n\t\tpublic void togglePreviewMode() {\n\t\t\tsetCenter(isDecompilePreview() ? editorAssembly : editorDecompile);\n\t\t\tupdatePreview();\n\t\t}\n\n\t\tpublic boolean isDecompilePreview() {\n\t\t\treturn getCenter() == editorDecompile;\n\t\t}\n\n\t\tprivate void updatePreview() {\n\t\t\t// TODO: We can't really stop expensive decompilations, they don't check for interrupts on the current thread\n\t\t\t//  and we have no good way of force killing them (rip Thread.stop) so the best we can really do is only\n\t\t\t//  allow one of these tasks to run at a time, with one next 'pending' task.\n\t\t\t//  While a task is executing we update what is the next 'pending' task when we call this method.\n\t\t\t//  Once the current task is done, or there is nothing running we make the 'pending' the current task and run it.\n\t\t\tdeobfuscationBatch.add(() -> {\n\t\t\t\tdeobfuscationBatch.clear();\n\t\t\t\tif (isDecompilePreview())\n\t\t\t\t\tdecompile();\n\t\t\t\telse\n\t\t\t\t\tdisassemble();\n\t\t\t});\n\t\t\tdeobfuscationBatch.executeNewest();\n\t\t}\n\n\t\tprivate void disassemble() {\n\t\t\tif (classInfo == null) {\n\t\t\t\tString text = \"// Preview: Disassembly\\n\" + Lang.get(\"deobf.preview.noselection\");\n\t\t\t\teditorAssembly.setText(text);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tJvmClassInfo jvmClass = getProcessedClass();\n\t\t\tif (jvmClass == null) return;\n\n\t\t\tWorkspace workspace = workspaceManager.getCurrent();\n\t\t\tString className = jvmClass.getName();\n\t\t\tClassPathNode path = workspace.findClass(className);\n\t\t\tif (path == null) {\n\t\t\t\tlogger.error(\"Couldn't find class {} in workspace for deobfuscation preview\", className);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpath = Objects.requireNonNull(path.getParent()).child(jvmClass);\n\t\t\tJvmAssemblerPipeline pipeline = assemblerPipelineManager.newJvmAssemblerPipeline(workspace);\n\t\t\tpipeline.disassemble(path)\n\t\t\t\t\t.ifOk(disassembly -> FxThreadUtil.run(() -> editorAssembly.setText(disassembly)))\n\t\t\t\t\t.ifErr((errors) -> {\n\t\t\t\t\t\tString errorListStr = errors.stream().map(Error::toString).collect(Collectors.joining(\"\\n - \"));\n\t\t\t\t\t\tlogger.warn(\"Errors processing {} for deobfuscation preview:\\n - {}\", className, errorListStr);\n\t\t\t\t\t});\n\t\t}\n\n\t\tprivate void decompile() {\n\t\t\tif (classInfo == null) {\n\t\t\t\tString text = \"// Preview: Decompile\\n\" + Lang.get(\"deobf.preview.noselection\");\n\t\t\t\teditorDecompile.setText(text);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// TODO: There are cases where the target class was not transformed, but an inner class was.\n\t\t\t//  In these cases we should still be able to show the decompilation of the target class while\n\t\t\t//  reflecting the inner class changes. However, our current decompiler API just pulls from the\n\t\t\t//  workspace, which won't have the transformed inner class until after we apply.\n\t\t\t//  - Will need to create a lightweight view of the workspace that overlays transformed classes for decompilation.\n\t\t\tJvmClassInfo jvmClass = getProcessedClass();\n\t\t\tif (jvmClass == null) return;\n\n\t\t\t// TODO: Pull out common decompile code with \"AbstractDecompilePane\" to do this more aesthetically\n\t\t\t//  - And by that I mean include the animation\n\t\t\t//  - And the error handling for decompilation failures\n\t\t\t//  - But not the rest of the unrelated \"editing\" capabilities of the \"AbstractDecompilePane\"\n\t\t\tdecompilerManager.decompile(workspaceManager.getCurrent(), jvmClass).whenCompleteAsync((result, error) -> {\n\t\t\t\tif (result != null) {\n\t\t\t\t\teditorDecompile.setText(result.getText());\n\t\t\t\t} else if (error != null) {\n\t\t\t\t\tString trace = StringUtil.traceToString(error);\n\t\t\t\t\teditorDecompile.setText(\"/*\\nDecompilation failure\\n\" + trace + \"\\n*/\");\n\t\t\t\t}\n\t\t\t}, FxThreadUtil.executor());\n\t\t}\n\n\t\t/**\n\t\t * @return Processed output class to preview.\n\t\t */\n\t\t@Nullable\n\t\tprivate JvmClassInfo getProcessedClass() {\n\t\t\tJvmClassInfo jvmClass = classInfo.asJvmClass();\n\n\t\t\tif (andApply) {\n\t\t\t\tList<Class<? extends JvmClassTransformer>> transformers = Unchecked.cast(transformerOrder.stream()\n\t\t\t\t\t\t.map(ct -> ct.transformer().type())\n\t\t\t\t\t\t.filter(JvmClassTransformer.class::isAssignableFrom)\n\t\t\t\t\t\t.toList());\n\n\t\t\t\ttry {\n\t\t\t\t\tTransformationApplier applier = transformationApplierService.newApplierForCurrentWorkspace();\n\t\t\t\t\tif (applier == null)\n\t\t\t\t\t\tthrow new TransformationException(\"No workspace is open\");\n\t\t\t\t\tapplier.setMaxPasses(maxPasses.get());\n\t\t\t\t\tJvmTransformResult result = applier\n\t\t\t\t\t\t\t.transformJvm(transformers, new PreviewFeedback(classInfo));\n\t\t\t\t\tresult.getTransformerFailures().forEach((_, map) -> map.forEach((transformer, error) -> {\n\t\t\t\t\t\tlogger.debugging(l -> l.warn(\"Transformer '{}' failure: \", transformer.getSimpleName(), error));\n\t\t\t\t\t}));\n\t\t\t\t\tfor (JvmClassInfo resultClass : result.getTransformedClasses().values()) {\n\t\t\t\t\t\tif (resultClass.getName().equals(classInfo.getName())) {\n\t\t\t\t\t\t\tjvmClass = resultClass;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (TransformationException e) {\n\t\t\t\t\tFxThreadUtil.run(() -> editorDecompile.setText(\"// Failed to transform: \" + e.getMessage() + \"\\n\"\n\t\t\t\t\t\t\t+ \"// \" + StringUtil.traceToString(e).replace(\"\\n\", \"\\n// \")));\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn jvmClass;\n\t\t}\n\t}\n\n\t/**\n\t * Transformation feedback that tracks all classes transformed.\n\t */\n\tprivate static class FullFeedback implements TransformationFeedback {\n\t\tprivate final Set<ClassBundle<?>> targetBundles = Collections.newSetFromMap(new IdentityHashMap<>());\n\t\tprivate final Set<String> classesVisited = Collections.newSetFromMap(new ConcurrentHashMap<>());\n\t\tprivate int currentPass;\n\t\tprivate int maxClasses;\n\t\tprivate boolean cancelled;\n\t\tprivate FeedbackObserver observer;\n\n\t\tpublic void cancel() {\n\t\t\tcancelled = true;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean hasRequestedCancellation() {\n\t\t\treturn cancelled;\n\t\t}\n\n\t\t@Override\n\t\tpublic void onTransformFailure(@Nonnull Workspace workspace, @Nonnull WorkspaceResource resource,\n\t\t                               @Nonnull ClassBundle<?> bundle, @Nonnull ClassInfo classInfo,\n\t\t                               @Nonnull ClassTransformer transformer,int pass,\n\t\t                               @Nullable Throwable error) {\n\t\t\t// TODO: In the UI we should show errors affecting classes grouped by transformer as they occur.\n\t\t\t//  - Mainly so that users can report bugs on specific transformers when they run into issues.\n\t\t\tonTransformed(workspace, resource, bundle, classInfo, transformer, pass);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onTransformedWithoutWork(@Nonnull Workspace workspace, @Nonnull WorkspaceResource resource,\n\t\t                                     @Nonnull ClassBundle<?> bundle, @Nonnull ClassInfo classInfo,\n\t\t                                     @Nonnull ClassTransformer transformer,int pass) {\n\t\t\tonTransformed(workspace, resource, bundle, classInfo, transformer, pass);\n\t\t}\n\n\t\t@Override\n\t\tpublic void onTransformed(@Nonnull Workspace workspace, @Nonnull WorkspaceResource resource,\n\t\t                          @Nonnull ClassBundle<?> bundle, @Nonnull ClassInfo classInfo,\n\t\t                          @Nonnull ClassTransformer transformer, int pass) {\n\t\t\t// Reset per-pass tracking.\n\t\t\tif (currentPass != pass) {\n\t\t\t\tcurrentPass = pass;\n\t\t\t\tclassesVisited.clear();\n\t\t\t}\n\n\t\t\t// Update max class count based on unique bundles seen.\n\t\t\tsynchronized (targetBundles) {\n\t\t\t\tif (targetBundles.add(bundle))\n\t\t\t\t\tmaxClasses += bundle.size();\n\t\t\t}\n\n\t\t\t// Track unique classes transformed this pass.\n\t\t\tclassesVisited.add(classInfo.getName());\n\n\t\t\t// Notify observer of progress.\n\t\t\tif (observer != null)\n\t\t\t\tobserver.update();\n\t\t}\n\n\t\tinterface FeedbackObserver {\n\t\t\tvoid update();\n\t\t}\n\t}\n\n\t/**\n\t * Transformation feedback that filters to only the preview class <i>(and its inner classes)</i>.\n\t */\n\tprivate static class PreviewFeedback implements TransformationFeedback {\n\t\tprivate final ClassInfo targetClass;\n\n\t\tpublic PreviewFeedback(@Nonnull ClassInfo targetClass) {\n\t\t\tthis.targetClass = targetClass;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean shouldTransform(@Nonnull Workspace workspace, @Nonnull WorkspaceResource resource,\n\t\t                               @Nonnull ClassBundle<?> bundle, @Nonnull ClassInfo classInfo,  @Nonnull ClassTransformer transformer,int pass) {\n\t\t\treturn classInfo.getName().equals(targetClass.getName()) || classInfo.isInnerClassOf(targetClass.getName());\n\t\t}\n\t}\n\n\tprivate sealed interface Selection {\n\t\trecord Root() implements Selection {}\n\n\t\trecord Category(@Nonnull String key, @Nonnull Ikon icon) implements Selection {}\n\n\t\trecord Transformer(@Nonnull ClassTransformer transformer) implements Selection {\n\t\t\t@Nonnull\n\t\t\tpublic String name() {\n\t\t\t\treturn transformer().name();\n\t\t\t}\n\n\t\t\t@Nonnull\n\t\t\tpublic Class<? extends ClassTransformer> type() {\n\t\t\t\treturn transformer.getClass();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate record CategorizedTransformer(@Nonnull Selection.Transformer transformer,\n\t                                      @Nonnull Selection.Category category) {\n\t\tpublic boolean matches(@Nonnull Class<? extends ClassTransformer> cls) {\n\t\t\treturn transformer.type() == cls;\n\t\t}\n\n\t\t@Nonnull\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn category().key() + ':' + transformer().type().getName();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/window/IdentifiableStage.java",
    "content": "package software.coley.recaf.ui.window;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.stage.Stage;\nimport software.coley.recaf.services.window.WindowManager;\n\n/**\n * Identifiable stage.\n * <br>\n * Implementations of this class are passed to the {@link WindowManager} upon CDI initialization.\n * Typically, these identifiable stages will {@link Inject} additional UI components often marked as {@link Dependent}.\n * Because these stages are managed by the window manager, if we operate with windows through the {@link WindowManager}\n * we can ensure we treat these stages with {@link Dependent} components as singletons.\n *\n * @author Matt Coley\n * @see AbstractIdentifiableStage Base class to use when creating stages.\n */\npublic interface IdentifiableStage {\n\t/**\n\t * @return Unique stage ID.\n\t */\n\t@Nonnull\n\tString getId();\n\n\t/**\n\t * @return Self as stage.\n\t */\n\t@Nonnull\n\tStage asStage();\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/window/MappingApplicationWindow.java",
    "content": "package software.coley.recaf.ui.window;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport software.coley.recaf.ui.pane.MappingApplicationPane;\nimport software.coley.recaf.util.Lang;\n\n/**\n * Window wrapper for {@link MappingApplicationPane}.\n *\n * @author Matt Coley\n * @see MappingApplicationPane\n */\n@Dependent\npublic class MappingApplicationWindow extends RecafStage {\n\tprivate final MappingApplicationPane generatorPane;\n\n\t@Inject\n\tpublic MappingApplicationWindow(@Nonnull MappingApplicationPane generatorPane) {\n\t\tthis.generatorPane = generatorPane;\n\t\tgeneratorPane.setApplyCallback(this::close);\n\n\t\t// Add event filter to handle closing the window when escape is pressed.\n\t\taddEventFilter(KeyEvent.KEY_PRESSED, e -> {\n\t\t\tif (e.getCode() == KeyCode.ESCAPE)\n\t\t\t\thide();\n\t\t});\n\n\t\t// Layout\n\t\ttitleProperty().bind(Lang.getBinding(\"mapapply\"));\n\t\tsetMinWidth(450);\n\t\tsetMinHeight(300);\n\t\tsetScene(new RecafScene(generatorPane, 800, 600));\n\t}\n\n\t@Nonnull\n\tpublic MappingApplicationPane getApplicationPane() {\n\t\treturn generatorPane;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/window/MappingGeneratorWindow.java",
    "content": "package software.coley.recaf.ui.window;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.layout.BorderPane;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.ui.pane.MappingGeneratorPane;\nimport software.coley.recaf.ui.pane.SystemInformationPane;\nimport software.coley.recaf.util.Lang;\n\n/**\n * Window wrapper for {@link MappingGeneratorPane}.\n *\n * @author Matt Coley\n * @see MappingGeneratorPane\n */\n@Dependent\npublic class MappingGeneratorWindow extends RecafStage {\n\tprivate final MappingGeneratorPane generatorPane;\n\n\t@Inject\n\tpublic MappingGeneratorWindow(@Nonnull MappingGeneratorPane generatorPane) {\n\t\tthis.generatorPane = generatorPane;\n\t\tgeneratorPane.setApplyCallback(this::close);\n\n\t\t// Layout\n\t\ttitleProperty().bind(Lang.getBinding(\"mapgen\"));\n\t\tsetMinWidth(450);\n\t\tsetMinHeight(300);\n\t\tsetScene(new RecafScene(generatorPane, 800, 600));\n\t}\n\n\t@Nonnull\n\tpublic MappingGeneratorPane getGeneratorPane() {\n\t\treturn generatorPane;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/window/MappingProgressWindow.java",
    "content": "package software.coley.recaf.ui.window;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.ui.pane.MappingProgressPane;\nimport software.coley.recaf.util.Lang;\n\n/**\n * Window wrapper for {@link MappingProgressPane}.\n *\n * @author Matt Coley\n * @see MappingProgressPane\n */\n@Dependent\npublic class MappingProgressWindow extends AbstractIdentifiableStage {\n\t@Inject\n\tpublic MappingProgressWindow(@Nonnull MappingProgressPane previewPane) {\n\t\tsuper(WindowManager.WIN_MAP_PROGRESS);\n\n\t\t// Bind mapping preview updates to the window visibility\n\t\tpreviewPane.activeProperty().bind(showingProperty());\n\n\t\t// Add event filter to handle closing the window when escape is pressed.\n\t\taddEventFilter(KeyEvent.KEY_PRESSED, e -> {\n\t\t\tif (e.getCode() == KeyCode.ESCAPE)\n\t\t\t\thide();\n\t\t});\n\n\t\t// Layout\n\t\ttitleProperty().bind(Lang.getBinding(\"mapprog\"));\n\t\tsetMinWidth(750);\n\t\tsetMinHeight(450);\n\t\tsetScene(new RecafScene(previewPane, 750, 450));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/window/QuickNavWindow.java",
    "content": "package software.coley.recaf.ui.window;\n\nimport atlantafx.base.controls.Popover;\nimport atlantafx.base.controls.Spacer;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.property.StringProperty;\nimport javafx.collections.FXCollections;\nimport javafx.collections.ObservableList;\nimport javafx.css.PseudoClass;\nimport javafx.scene.Node;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.ListCell;\nimport javafx.scene.control.OverrunStyle;\nimport javafx.scene.control.Tab;\nimport javafx.scene.control.TabPane;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.stage.Stage;\nimport org.fxmisc.flowless.Cell;\nimport org.fxmisc.flowless.VirtualFlow;\nimport org.fxmisc.flowless.VirtualizedScrollPane;\nimport org.kordamp.ikonli.carbonicons.CarbonIcons;\nimport org.slf4j.Logger;\nimport regexodus.Matcher;\nimport regexodus.Pattern;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.TextFileInfo;\nimport software.coley.recaf.info.member.FieldMember;\nimport software.coley.recaf.info.member.MethodMember;\nimport software.coley.recaf.path.ClassMemberPathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.DirectoryPathNode;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.IncompletePathException;\nimport software.coley.recaf.path.LineNumberPathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.services.cell.CellConfigurationService;\nimport software.coley.recaf.services.cell.context.ContextSource;\nimport software.coley.recaf.services.comment.ClassComments;\nimport software.coley.recaf.services.comment.CommentManager;\nimport software.coley.recaf.services.comment.DelegatingClassComments;\nimport software.coley.recaf.services.comment.WorkspaceComments;\nimport software.coley.recaf.services.navigation.Actions;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.services.workspace.WorkspaceCloseListener;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.ui.control.AbstractSearchBar;\nimport software.coley.recaf.ui.control.BoundTab;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.util.FxThreadUtil;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.RegexUtil;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\nimport java.util.stream.IntStream;\nimport java.util.stream.Stream;\n\n/**\n * Window for quickly opening classes, fields, methods, files, and other supported content.\n *\n * @author Matt Coley\n */\n@Dependent\npublic class QuickNavWindow extends AbstractIdentifiableStage {\n\tprivate static final Logger logger = Logging.get(QuickNavWindow.class);\n\n\t@Inject\n\tpublic QuickNavWindow(@Nonnull WorkspaceManager workspaceManager, @Nonnull CommentManager commentManager,\n\t                      @Nonnull Actions actions, @Nonnull TextFormatConfig formatConfig,\n\t                      @Nonnull CellConfigurationService configurationService) {\n\t\tsuper(WindowManager.WIN_QUICK_NAV);\n\n\t\tOneToOneContentPane<ClassPathNode> classContent = new OneToOneContentPane<>(actions, this, () -> {\n\t\t\tif (!workspaceManager.hasCurrentWorkspace())\n\t\t\t\treturn Stream.empty();\n\t\t\treturn workspaceManager.getCurrent().classesStream(false);\n\t\t}, classPath -> classPath.getValue().getName(), cell -> {\n\t\t\tClassPathNode classPath = cell.getItem();\n\t\t\tDirectoryPathNode packagePath = Objects.requireNonNull(classPath.getParent());\n\t\t\tString packageName = packagePath.getValue();\n\t\t\tpackageName = formatConfig.filterEscape(packageName);\n\t\t\tpackageName = formatConfig.filterMaxLength(packageName);\n\n\t\t\tLabel classDisplay = new Label();\n\t\t\tconfigurationService.configureStyle(classDisplay, classPath);\n\t\t\tclassDisplay.setText(configurationService.textOf(classPath));\n\t\t\tclassDisplay.setGraphic(configurationService.graphicOf(classPath));\n\n\t\t\tLabel packageDisplay = new Label();\n\t\t\tpackageDisplay.setText(packageName);\n\t\t\tpackageDisplay.setGraphic(Icons.getIconView(Icons.FOLDER_PACKAGE));\n\t\t\tpackageDisplay.setOpacity(0.5);\n\t\t\tpackageDisplay.setTextOverrun(OverrunStyle.CENTER_WORD_ELLIPSIS);\n\n\t\t\tSpacer spacer = new Spacer();\n\t\t\tHBox box = new HBox(classDisplay, spacer, packageDisplay);\n\t\t\tHBox.setHgrow(spacer, Priority.ALWAYS);\n\t\t\tcell.setText(null);\n\t\t\tcell.setGraphic(box);\n\n\t\t\tcell.setOnMouseClicked(configurationService.contextMenuHandlerOf(cell, classPath, ContextSource.REFERENCE));\n\t\t});\n\t\tOneToOneContentPane<ClassMemberPathNode> memberContent = new OneToOneContentPane<>(actions, this, () -> {\n\t\t\tif (!workspaceManager.hasCurrentWorkspace())\n\t\t\t\treturn Stream.empty();\n\t\t\treturn workspaceManager.getCurrent().classesStream(false).flatMap(p -> {\n\t\t\t\tClassInfo classInfo = p.getValue();\n\t\t\t\tStream<ClassMemberPathNode> fields = classInfo.getFields().stream().map(p::child);\n\t\t\t\tStream<ClassMemberPathNode> methods = classInfo.getMethods().stream().map(p::child);\n\t\t\t\treturn Stream.concat(fields, methods);\n\t\t\t});\n\t\t}, classMemberPath -> classMemberPath.getValue().getName(), cell -> {\n\t\t\tClassMemberPathNode memberPath = cell.getItem();\n\t\t\tClassPathNode classPath = Objects.requireNonNull(memberPath.getParent());\n\n\t\t\tLabel memberDisplay = new Label();\n\t\t\tmemberDisplay.setText(configurationService.textOf(memberPath));\n\t\t\tmemberDisplay.setGraphic(configurationService.graphicOf(memberPath));\n\n\t\t\tLabel classDisplay = new Label();\n\t\t\tclassDisplay.setText(configurationService.textOf(classPath));\n\t\t\tclassDisplay.setGraphic(configurationService.graphicOf(classPath));\n\t\t\tclassDisplay.setOpacity(0.5);\n\n\t\t\tSpacer spacer = new Spacer();\n\t\t\tHBox box = new HBox(memberDisplay, spacer, classDisplay);\n\t\t\tHBox.setHgrow(spacer, Priority.ALWAYS);\n\t\t\tcell.setText(null);\n\t\t\tcell.setGraphic(box);\n\n\t\t\tcell.setOnMouseClicked(configurationService.contextMenuHandlerOf(cell, memberPath, ContextSource.REFERENCE));\n\t\t});\n\t\tOneToOneContentPane<FilePathNode> fileContent = new OneToOneContentPane<>(actions, this, () -> {\n\t\t\tif (!workspaceManager.hasCurrentWorkspace())\n\t\t\t\treturn Stream.empty();\n\t\t\treturn workspaceManager.getCurrent().filesStream();\n\t\t}, filePath -> filePath.getValue().getName(), cell -> {\n\t\t\tFilePathNode filePath = cell.getItem();\n\t\t\tDirectoryPathNode directoryPath = Objects.requireNonNull(filePath.getParent());\n\t\t\tString directoryName = directoryPath.getValue();\n\t\t\tdirectoryName = formatConfig.filterEscape(directoryName);\n\t\t\tdirectoryName = formatConfig.filterMaxLength(directoryName);\n\n\t\t\tLabel fileDisplay = new Label();\n\t\t\tfileDisplay.setText(configurationService.textOf(filePath));\n\t\t\tfileDisplay.setGraphic(configurationService.graphicOf(filePath));\n\n\t\t\tLabel packageDisplay = new Label();\n\t\t\tpackageDisplay.setText(directoryName);\n\t\t\tpackageDisplay.setGraphic(Icons.getIconView(Icons.FOLDER_RES));\n\t\t\tpackageDisplay.setOpacity(0.5);\n\t\t\tpackageDisplay.setTextOverrun(OverrunStyle.CENTER_WORD_ELLIPSIS);\n\n\t\t\tSpacer spacer = new Spacer();\n\t\t\tHBox box = new HBox(fileDisplay, spacer, packageDisplay);\n\t\t\tHBox.setHgrow(spacer, Priority.ALWAYS);\n\t\t\tcell.setText(null);\n\t\t\tcell.setGraphic(box);\n\n\t\t\tcell.setOnMouseClicked(configurationService.contextMenuHandlerOf(cell, filePath, ContextSource.REFERENCE));\n\t\t});\n\t\tOneToManyContentPane<FilePathNode, LineNumberPathNode> textContent = new OneToManyContentPane<>(actions, this, () -> {\n\t\t\tif (!workspaceManager.hasCurrentWorkspace())\n\t\t\t\treturn Stream.empty();\n\t\t\treturn workspaceManager.getCurrent().filesStream()\n\t\t\t\t\t.filter(f -> f.getValue().isTextFile());\n\t\t}, filePath -> {\n\t\t\tTextFileInfo textFile = filePath.getValue().asTextFile();\n\t\t\tint lineCount = StringUtil.count('\\n', textFile.getText());\n\t\t\treturn IntStream.rangeClosed(1, lineCount).mapToObj(filePath::child);\n\t\t}, lineNumberPath -> {\n\t\t\tint index = lineNumberPath.getValue().intValue() - 1;\n\t\t\tString[] lines = lineNumberPath.getParent().getValue().asTextFile().getTextLines();\n\t\t\treturn lines[index];\n\t\t}, cell -> {\n\t\t\tLineNumberPathNode linePath = cell.getItem();\n\t\t\tFilePathNode filePath = linePath.getParent();\n\t\t\tTextFileInfo textFile = filePath.getValue().asTextFile();\n\t\t\tint line = linePath.getValue().intValue();\n\n\t\t\tLabel fileDisplay = new Label();\n\t\t\tfileDisplay.setText(configurationService.textOf(filePath) + \":\" + line);\n\t\t\tfileDisplay.setGraphic(configurationService.graphicOf(filePath));\n\n\t\t\tLabel textDisplay = new Label();\n\t\t\ttextDisplay.setText(textFile.getTextLines()[line - 1]);\n\t\t\ttextDisplay.setOpacity(0.5);\n\t\t\ttextDisplay.setTextOverrun(OverrunStyle.CENTER_WORD_ELLIPSIS);\n\n\t\t\tSpacer spacer = new Spacer();\n\t\t\tHBox box = new HBox(fileDisplay, spacer, textDisplay);\n\t\t\tHBox.setHgrow(spacer, Priority.ALWAYS);\n\t\t\tcell.setText(null);\n\t\t\tcell.setGraphic(box);\n\n\t\t\tcell.setOnMouseClicked(configurationService.contextMenuHandlerOf(cell, filePath, ContextSource.REFERENCE));\n\t\t});\n\t\tOneToOneContentPane<? extends PathNode<?>> commentContent = new OneToOneContentPane<>(actions, this, () -> {\n\t\t\tif (!workspaceManager.hasCurrentWorkspace())\n\t\t\t\treturn Stream.empty();\n\n\t\t\tWorkspace current = workspaceManager.getCurrent();\n\t\t\tWorkspaceComments comments = commentManager.getCurrentWorkspaceComments();\n\t\t\tif (comments == null)\n\t\t\t\treturn Stream.empty();\n\n\t\t\tList<PathNode<?>> paths = new ArrayList<>();\n\t\t\tfor (ClassComments classComments : comments) {\n\t\t\t\tif (classComments instanceof DelegatingClassComments delegatingClassComments) {\n\t\t\t\t\t// Add class comment\n\t\t\t\t\tClassPathNode classPath = delegatingClassComments.getPath();\n\t\t\t\t\tif (classComments.getClassComment() != null)\n\t\t\t\t\t\tpaths.add(classPath);\n\n\t\t\t\t\t// Add field/method comments\n\t\t\t\t\tClassInfo classInfo = classPath.getValue();\n\t\t\t\t\tfor (FieldMember field : classInfo.getFields())\n\t\t\t\t\t\tif (classComments.getFieldComment(field) != null)\n\t\t\t\t\t\t\tpaths.add(classPath.child(field));\n\t\t\t\t\tfor (MethodMember method : classInfo.getMethods())\n\t\t\t\t\t\tif (classComments.getMethodComment(method) != null)\n\t\t\t\t\t\t\tpaths.add(classPath.child(method));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn paths.stream();\n\t\t}, path -> {\n\t\t\tWorkspaceComments comments = commentManager.getCurrentWorkspaceComments();\n\t\t\tif (comments == null)\n\t\t\t\treturn null;\n\t\t\treturn comments.getComment(path);\n\t\t}, cell -> {\n\t\t\tPathNode<?> path = cell.getItem();\n\t\t\tWorkspaceComments comments = commentManager.getCurrentWorkspaceComments();\n\t\t\tString comment = (comments == null ? \"\" : comments.getComment(path));\n\t\t\tif (comment == null)\n\t\t\t\tcomment = \"\";\n\t\t\telse\n\t\t\t\tcomment = formatConfig.filterMaxLength(comment.replace('\\n', ' '));\n\n\t\t\tLabel pathDisplay = new Label();\n\t\t\tpathDisplay.setText(configurationService.textOf(path));\n\t\t\tpathDisplay.setGraphic(configurationService.graphicOf(path));\n\n\t\t\tLabel commentDisplay = new Label();\n\t\t\tcommentDisplay.setText(comment);\n\t\t\tcommentDisplay.setOpacity(0.7);\n\n\t\t\tSpacer spacer = new Spacer();\n\t\t\tHBox box = new HBox(pathDisplay, spacer, commentDisplay);\n\t\t\tHBox.setHgrow(spacer, Priority.ALWAYS);\n\t\t\tcell.setText(null);\n\t\t\tcell.setGraphic(box);\n\n\t\t\tcell.setOnMouseClicked(configurationService.contextMenuHandlerOf(cell, path, ContextSource.REFERENCE));\n\t\t});\n\t\tList<ContentPaneBase> contentPanes = List.of(classContent, memberContent, fileContent, textContent, commentContent);\n\t\tcontentPanes.forEach(workspaceManager::addWorkspaceCloseListener);\n\n\t\tBoundTab tabClasses = new BoundTab(Lang.getBinding(\"dialog.quicknav.tab.classes\"), Icons.getIconView(Icons.CLASS), classContent);\n\t\tBoundTab tabMembers = new BoundTab(Lang.getBinding(\"dialog.quicknav.tab.members\"), Icons.getIconView(Icons.FIELD_N_METHOD), memberContent);\n\t\tBoundTab tabFiles = new BoundTab(Lang.getBinding(\"dialog.quicknav.tab.files\"), new FontIconView(CarbonIcons.DOCUMENT), fileContent);\n\t\tBoundTab tabText = new BoundTab(Lang.getBinding(\"dialog.quicknav.tab.text\"), new FontIconView(CarbonIcons.STRING_TEXT), textContent);\n\t\tBoundTab tabCommented = new BoundTab(Lang.getBinding(\"dialog.quicknav.tab.commented\"), new FontIconView(CarbonIcons.CHAT), commentContent);\n\n\t\tTabPane tabs = new TabPane();\n\t\ttabs.getTabs().addAll(tabClasses, tabMembers, tabFiles, tabText, tabCommented);\n\t\ttabs.getTabs().forEach(tab -> tab.setClosable(false));\n\n\t\t// Add event filter to handle closing the window when escape is pressed.\n\t\taddEventFilter(KeyEvent.KEY_PRESSED, e -> {\n\t\t\tif (e.getCode() == KeyCode.ESCAPE)\n\t\t\t\thide();\n\t\t});\n\n\t\t// And event filter to tabs to handle \"a-z\" and \"0-9\" delegating to current displayed search input field.\n\t\tfor (Tab tab : tabs.getTabs()) {\n\t\t\ttabs.addEventFilter(KeyEvent.KEY_PRESSED, e -> {\n\t\t\t\tif (!tab.isSelected() || e.getTarget() != tabs)\n\t\t\t\t\treturn;\n\t\t\t\tKeyCode code = e.getCode();\n\t\t\t\tif ((code.isLetterKey() || code.isDigitKey()) && tab.getContent() instanceof ContentPaneBase content)\n\t\t\t\t\tcontent.focusSearchBar();\n\t\t\t});\n\t\t}\n\n\t\t// Layout\n\t\ttitleProperty().bind(Lang.getBinding(\"dialog.quicknav\"));\n\t\tsetMinWidth(300);\n\t\tsetMinHeight(300);\n\t\tsetScene(new RecafScene(tabs, 750, 550));\n\n\t}\n\n\t/**\n\t * Base for displaying the search bar + results pane for some content type.\n\t */\n\tprivate static class ContentPaneBase extends BorderPane implements WorkspaceCloseListener {\n\t\tprotected final PathResultsPane<?> results;\n\t\tprotected AbstractSearchBar searchBar;\n\n\t\tprotected ContentPaneBase(@Nonnull PathResultsPane<?> results) {\n\t\t\tthis.results = results;\n\t\t}\n\n\t\tprotected void focusSearchBar() {\n\t\t\tif (searchBar != null)\n\t\t\t\tsearchBar.requestSearchFocus();\n\t\t}\n\n\t\tprotected void setSearchBar(@Nonnull AbstractSearchBar searchBar) {\n\t\t\tthis.searchBar = searchBar;\n\t\t\tsetTop(searchBar);\n\n\t\t\t// Register event filter which will allow jumping from the search bar to other controls.\n\t\t\t// - UP ----> Select the containing tab-pane so users can switch tabs.\n\t\t\t// - DOWN --> Select the results so the user can navigate through results.\n\t\t\tsearchBar.addEventFilter(KeyEvent.KEY_PRESSED, e -> {\n\t\t\t\tif (e.getCode() == KeyCode.UP)\n\t\t\t\t\tgetParent().requestFocus();\n\t\t\t\telse if (e.getCode() == KeyCode.DOWN)\n\t\t\t\t\tresults.selectCell();\n\t\t\t});\n\t\t}\n\n\t\t@Override\n\t\tpublic void onWorkspaceClosed(@Nonnull Workspace workspace) {\n\t\t\tFxThreadUtil.run(results.list::clear);\n\t\t}\n\t}\n\n\t/**\n\t * Pane for displaying results that come from a one-to-one lookup.\n\t *\n\t * @param <T>\n\t * \t\tInput/Result type.\n\t */\n\tprivate static class OneToOneContentPane<T extends PathNode<?>> extends ContentPaneBase {\n\t\tprivate OneToOneContentPane(@Nonnull Actions actions,\n\t\t                            @Nonnull Stage stage,\n\t\t                            @Nonnull Supplier<Stream<T>> valueProvider,\n\t\t                            @Nonnull Function<T, String> valueTextMapper,\n\t\t                            @Nonnull Consumer<ListCell<T>> renderCell) {\n\t\t\tsuper(new PathResultsPane<>(actions, stage, renderCell));\n\t\t\tsetSearchBar(new OneToOneNavSearchBar<>(Unchecked.cast(results), valueProvider, valueTextMapper));\n\t\t\tsetCenter(results);\n\t\t}\n\t}\n\n\t/**\n\t * Pane for displaying results that come from a one-to-many lookup.\n\t *\n\t * @param <T>\n\t * \t\tInput type.\n\t * @param <R>\n\t * \t\tResult type.\n\t */\n\tprivate static class OneToManyContentPane<T extends PathNode<?>, R extends PathNode<?>> extends ContentPaneBase {\n\t\tprivate OneToManyContentPane(@Nonnull Actions actions,\n\t\t                             @Nonnull Stage stage,\n\t\t                             @Nonnull Supplier<Stream<T>> valueProvider,\n\t\t                             @Nonnull Function<T, Stream<R>> valueUnroller,\n\t\t                             @Nonnull Function<R, String> valueTextMapper,\n\t\t                             @Nonnull Consumer<ListCell<R>> renderCell) {\n\t\t\tsuper(new PathResultsPane<>(actions, stage, renderCell));\n\t\t\tsetSearchBar(new OneToManyNavSearchBar<>(Unchecked.cast(results), valueProvider, valueUnroller, valueTextMapper));\n\t\t\tsetCenter(results);\n\t\t}\n\t}\n\n\t/**\n\t * Pane for displaying the results of a search.\n\t *\n\t * @param <T>\n\t * \t\tResult type.\n\t */\n\tprivate static class PathResultsPane<T extends PathNode<?>> extends BorderPane {\n\t\tprivate static final PseudoClass PSEUDO_HOVER = PseudoClass.getPseudoClass(\"hover\");\n\t\tprivate final ObservableList<T> list = FXCollections.observableArrayList();\n\t\tprivate final VirtualFlow<T, Cell<T, Node>> flow;\n\n\t\tprivate PathResultsPane(@Nonnull Actions actions, @Nonnull Stage stage,\n\t\t                        @Nonnull Consumer<ListCell<T>> renderCell) {\n\t\t\tflow = VirtualFlow.createVertical(list, initial -> new ResultCell(initial, actions, stage, renderCell));\n\t\t\tsetCenter(new VirtualizedScrollPane<>(flow));\n\t\t\tlist.addListener((InvalidationListener) e -> {\n\t\t\t\tflow.setFocusTraversable(!list.isEmpty());\n\t\t\t});\n\t\t}\n\n\t\t/**\n\t\t * Select the first visible cell.\n\t\t */\n\t\tprivate void selectCell() {\n\t\t\tif (!list.isEmpty()) {\n\t\t\t\tint index = flow.getFirstVisibleIndex();\n\t\t\t\tflow.getCell(index).getNode().requestFocus();\n\t\t\t}\n\t\t}\n\n\t\tprivate class ResultCell implements Cell<T, Node> {\n\t\t\tprivate final ListCell<T> cell = new ListCell<>();\n\t\t\tprivate final Consumer<ListCell<T>> renderCell;\n\t\t\tprivate final Runnable select;\n\t\t\tprivate int index;\n\n\t\t\tprivate ResultCell(@Nullable T initial, @Nonnull Actions actions, @Nonnull Stage stage,\n\t\t\t                   @Nonnull Consumer<ListCell<T>> renderCell) {\n\t\t\t\tthis.renderCell = renderCell;\n\n\t\t\t\tselect = () -> {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tT item = cell.getItem();\n\t\t\t\t\t\tif (item != null) {\n\t\t\t\t\t\t\tactions.gotoDeclaration(item);\n\t\t\t\t\t\t\tstage.hide();\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (IncompletePathException ex) {\n\t\t\t\t\t\tlogger.error(\"Failed to open path\", ex);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tupdateItem(initial);\n\t\t\t\tcell.getStyleClass().add(\"search-result-list-cell\");\n\t\t\t\tcell.setOnMouseEntered(e -> {\n\t\t\t\t\tif (cell.getItem() != null)\n\t\t\t\t\t\tcell.pseudoClassStateChanged(PSEUDO_HOVER, true);\n\t\t\t\t});\n\t\t\t\tcell.setOnMouseExited(e -> cell.pseudoClassStateChanged(PSEUDO_HOVER, false));\n\t\t\t\tcell.setOnMousePressed(e -> {\n\t\t\t\t\tif (e.isPrimaryButtonDown() && e.getClickCount() == 2)\n\t\t\t\t\t\tselect.run();\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void updateIndex(int index) {\n\t\t\t\tthis.index = index;\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void updateItem(T item) {\n\t\t\t\tif (item == null) {\n\t\t\t\t\treset();\n\t\t\t\t} else {\n\t\t\t\t\tsetupNavigation(cell);\n\t\t\t\t\tcell.setItem(item);\n\t\t\t\t\trenderCell.accept(cell);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void reset() {\n\t\t\t\tcell.setItem(null);\n\t\t\t\tcell.setText(null);\n\t\t\t\tcell.setGraphic(null);\n\t\t\t\tcell.setOnMouseClicked(null);\n\t\t\t\tcell.setOnMouseEntered(null);\n\t\t\t\tcell.setOnMouseExited(null);\n\t\t\t\tclearNavigation(cell);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void dispose() {\n\t\t\t\treset();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic Node getNode() {\n\t\t\t\treturn cell;\n\t\t\t}\n\n\t\t\tprivate void setupNavigation(ListCell<T> cell) {\n\t\t\t\tcell.setFocusTraversable(true);\n\t\t\t\tcell.setOnKeyPressed(e -> {\n\t\t\t\t\tKeyCode code = e.getCode();\n\t\t\t\t\tint size = list.size();\n\t\t\t\t\tint bigInc = Math.max(1, size / 25);\n\t\t\t\t\tdouble cellHeight = cell.getHeight();\n\t\t\t\t\tif (code == KeyCode.DOWN) {\n\t\t\t\t\t\tint nextIndex = Math.min(size - 1, index + (e.isShiftDown() ? bigInc : 1));\n\t\t\t\t\t\tif (nextIndex != index) {\n\t\t\t\t\t\t\tif (nextIndex >= flow.getLastVisibleIndex()) {\n\t\t\t\t\t\t\t\tflow.scrollYBy(cellHeight);\n\t\t\t\t\t\t\t\tflow.showAtOffset(nextIndex, flow.getHeight() - cellHeight * 2);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tCell<T, Node> nextCell = flow.getCell(nextIndex);\n\t\t\t\t\t\t\tif (nextCell instanceof ResultCell resultCell)\n\t\t\t\t\t\t\t\tresultCell.cell.requestFocus();\n\t\t\t\t\t\t}\n\t\t\t\t\t\te.consume();\n\t\t\t\t\t} else if (code == KeyCode.UP) {\n\t\t\t\t\t\tint prevIndex = Math.max(0, index - (e.isShiftDown() ? bigInc : 1));\n\t\t\t\t\t\tif (prevIndex != index) {\n\t\t\t\t\t\t\tif (prevIndex <= flow.getFirstVisibleIndex()) {\n\t\t\t\t\t\t\t\tflow.scrollYBy(-cellHeight);\n\t\t\t\t\t\t\t\tflow.showAtOffset(prevIndex, cellHeight);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tCell<T, Node> nextCell = flow.getCell(prevIndex);\n\t\t\t\t\t\t\tif (nextCell instanceof ResultCell resultCell)\n\t\t\t\t\t\t\t\tresultCell.cell.requestFocus();\n\t\t\t\t\t\t}\n\t\t\t\t\t\te.consume();\n\t\t\t\t\t} else if (code == KeyCode.ENTER) {\n\t\t\t\t\t\tselect.run();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tprivate void clearNavigation(ListCell<T> cell) {\n\t\t\t\tcell.setOnKeyPressed(null);\n\t\t\t\tcell.setFocusTraversable(false);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Search bar base implementation, tied to a {@link PathResultsPane}.\n\t *\n\t * @param <T>\n\t * \t\tInput type.\n\t * @param <R>\n\t * \t\tResult type.\n\t */\n\tprivate abstract static class NavSearchBarBase<T extends PathNode<?>, R extends PathNode<?>> extends AbstractSearchBar {\n\t\tprotected final PathResultsPane<R> results;\n\t\tprotected final Supplier<Stream<T>> valueProvider;\n\n\t\tprivate NavSearchBarBase(@Nonnull PathResultsPane<R> results, @Nonnull Supplier<Stream<T>> valueProvider) {\n\t\t\tthis.results = results;\n\t\t\tthis.valueProvider = valueProvider;\n\n\t\t\tsetup();\n\t\t}\n\n\t\t@Override\n\t\tprotected void bindResultCountDisplay(@Nonnull StringProperty resultTextProperty) {\n\t\t\tresults.list.addListener((InvalidationListener) observable -> {\n\t\t\t\tint size = results.list.size();\n\t\t\t\tif (size > 0) {\n\t\t\t\t\thasResults.set(true);\n\t\t\t\t\tresultTextProperty.set(String.valueOf(size));\n\t\t\t\t} else {\n\t\t\t\t\thasResults.set(false);\n\t\t\t\t\tresultTextProperty.set(Lang.get(\"menu.search.noresults\"));\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\n\t\t@Override\n\t\tprotected void refreshResults() {\n\t\t\t// Skip when there is nothing\n\t\t\tString search = searchInput.getText();\n\t\t\tif (search == null || search.isBlank()) {\n\t\t\t\tresults.list.clear();\n\t\t\t\thasResults.set(false);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tList<R> tempResultsList = new ArrayList<>();\n\t\t\tif (regex.get()) {\n\t\t\t\t// Validate the regex.\n\t\t\t\tRegexUtil.RegexValidation validation = RegexUtil.validate(search);\n\t\t\t\tPopover popoverValidation = null;\n\t\t\t\tif (validation.valid()) {\n\t\t\t\t\t// It's valid, match against values\n\t\t\t\t\tPattern pattern = RegexUtil.pattern(search);\n\t\t\t\t\tregexSearch(pattern, tempResultsList);\n\t\t\t\t} else {\n\t\t\t\t\t// It's not valid. Tell the user what went wrong.\n\t\t\t\t\tpopoverValidation = new Popover(new Label(validation.message()));\n\t\t\t\t\tpopoverValidation.setHeaderAlwaysVisible(true);\n\t\t\t\t\tpopoverValidation.titleProperty().bind(Lang.getBinding(\"find.regexinvalid\"));\n\t\t\t\t\tpopoverValidation.show(searchInput);\n\t\t\t\t}\n\n\t\t\t\t// Hide the prior popover if any exists.\n\t\t\t\tObject old = searchInput.getProperties().put(\"regex-popover\", popoverValidation);\n\t\t\t\tif (old instanceof Popover oldPopover)\n\t\t\t\t\toldPopover.hide();\n\t\t\t} else {\n\t\t\t\tcontainmentSearch(search, tempResultsList);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\ttempResultsList.sort(Comparator.naturalOrder());\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.warn(\"Failed to sort search results, falling back to unsorted display\", t);\n\t\t\t}\n\t\t\tresults.list.setAll(tempResultsList);\n\t\t}\n\n\t\t/**\n\t\t * @param pattern\n\t\t * \t\tPattern to search with.\n\t\t * @param results\n\t\t * \t\tResults sink to add to.\n\t\t */\n\t\tprotected abstract void regexSearch(@Nonnull Pattern pattern, @Nonnull List<R> results);\n\n\t\t/**\n\t\t * @param search\n\t\t * \t\tThe {@link #searchInput}'s text content to search with.\n\t\t * @param results\n\t\t * \t\tResults sink to add to.\n\t\t */\n\t\tprotected abstract void containmentSearch(@Nonnull String search, @Nonnull List<R> results);\n\t}\n\n\t/**\n\t * Search bar implementation where we match against the content of {@code <T>} directly.\n\t *\n\t * @param <T>\n\t * \t\tInput/result type.\n\t */\n\tprivate static class OneToOneNavSearchBar<T extends PathNode<?>> extends NavSearchBarBase<T, T> {\n\t\tprivate final Function<T, String> valueTextMapper;\n\n\t\tprivate OneToOneNavSearchBar(@Nonnull PathResultsPane<T> results,\n\t\t                             @Nonnull Supplier<Stream<T>> valueProvider,\n\t\t                             @Nonnull Function<T, String> valueTextMapper) {\n\t\t\tsuper(results, valueProvider);\n\t\t\tthis.valueTextMapper = valueTextMapper;\n\t\t}\n\n\t\t@Override\n\t\tprotected void regexSearch(@Nonnull Pattern pattern, @Nonnull List<T> results) {\n\t\t\tvalueProvider.get().forEach(item -> {\n\t\t\t\tString text = valueTextMapper.apply(item);\n\t\t\t\tif (text == null)\n\t\t\t\t\treturn;\n\t\t\t\tMatcher matcher = pattern.matcher(text);\n\t\t\t\tif (matcher.find())\n\t\t\t\t\tresults.add(item);\n\t\t\t});\n\t\t}\n\n\t\t@Override\n\t\tprotected void containmentSearch(@Nonnull String search, @Nonnull List<T> results) {\n\t\t\t// Modify the text/search for case-insensitive searches.\n\t\t\tFunction<T, String> localValueTextMapper;\n\t\t\tif (!caseSensitivity.get()) {\n\t\t\t\tsearch = search.toLowerCase();\n\t\t\t\tlocalValueTextMapper = valueTextMapper.andThen(String::toLowerCase);\n\t\t\t} else {\n\t\t\t\tlocalValueTextMapper = valueTextMapper;\n\t\t\t}\n\n\t\t\tString finalSearch = search;\n\t\t\tvalueProvider.get().forEach(item -> {\n\t\t\t\tString text = localValueTextMapper.apply(item);\n\t\t\t\tif (text == null)\n\t\t\t\t\treturn;\n\t\t\t\tif (text.contains(finalSearch))\n\t\t\t\t\tresults.add(item);\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Search bar implementation where we match against multiple string values within a {@code <T>} value.\n\t *\n\t * @param <T>\n\t * \t\tInput type.\n\t * @param <R>\n\t * \t\tResult type.\n\t */\n\tprivate static class OneToManyNavSearchBar<T extends PathNode<?>, R extends PathNode<?>> extends NavSearchBarBase<T, R> {\n\t\tprivate final Function<T, Stream<R>> valueUnroller;\n\t\tprivate final Function<R, String> valueTextMapper;\n\n\t\tprivate OneToManyNavSearchBar(@Nonnull PathResultsPane<R> results,\n\t\t                              @Nonnull Supplier<Stream<T>> valueProvider,\n\t\t                              @Nonnull Function<T, Stream<R>> valueUnroller,\n\t\t                              @Nonnull Function<R, String> valueTextMapper) {\n\t\t\tsuper(results, valueProvider);\n\t\t\tthis.valueUnroller = valueUnroller;\n\t\t\tthis.valueTextMapper = valueTextMapper;\n\t\t}\n\n\t\t@Override\n\t\tprotected void regexSearch(@Nonnull Pattern pattern, @Nonnull List<R> results) {\n\t\t\tvalueProvider.get().forEach(item -> {\n\t\t\t\tvalueUnroller.apply(item).forEach(mappedItem -> {\n\t\t\t\t\tString text = valueTextMapper.apply(mappedItem);\n\t\t\t\t\tif (text == null)\n\t\t\t\t\t\treturn;\n\t\t\t\t\tMatcher matcher = pattern.matcher(text);\n\t\t\t\t\tif (matcher.find())\n\t\t\t\t\t\tresults.add(mappedItem);\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t@Override\n\t\tprotected void containmentSearch(@Nonnull String search, @Nonnull List<R> results) {\n\t\t\tvalueProvider.get().forEach(item -> {\n\t\t\t\tvalueUnroller.apply(item).forEach(mappedItem -> {\n\t\t\t\t\tString text = valueTextMapper.apply(mappedItem);\n\t\t\t\t\tif (text == null)\n\t\t\t\t\t\treturn;\n\t\t\t\t\tString localSearch = search;\n\t\t\t\t\tif (!caseSensitivity.get()) {\n\t\t\t\t\t\ttext = text.toLowerCase();\n\t\t\t\t\t\tlocalSearch = localSearch.toLowerCase();\n\t\t\t\t\t}\n\t\t\t\t\tif (text.contains(localSearch))\n\t\t\t\t\t\tresults.add(mappedItem);\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/window/RecafScene.java",
    "content": "package software.coley.recaf.ui.window;\n\nimport javafx.scene.Parent;\nimport javafx.scene.Scene;\n\n/**\n * Scene extension for adding additional style-sheets.\n *\n * @author Matt Coley\n */\npublic class RecafScene extends Scene {\n\t/**\n\t * @param root\n\t * \t\tRoot node of the scene graph\n\t */\n\tpublic RecafScene(Parent root) {\n\t\tsuper(root);\n\t\taddStyleSheets();\n\t}\n\n\t/**\n\t * @param root\n\t * \t\tRoot node of the scene graph\n\t * @param width\n\t * \t\tRoot node width.\n\t * @param height\n\t * \t\tRoot node height.\n\t */\n\tpublic RecafScene(Parent root, double width, double height) {\n\t\tsuper(root, width, height);\n\t\taddStyleSheets();\n\t}\n\n\tprivate void addStyleSheets() {\n\t\t// All of our defined stylesheets are added to all scenes. This is actually good for performance overall.\n\t\t// In some cases when a 'Node' updates and recomputes what styles it has, having local stylesheets results\n\t\t// in a lot of extra work. The library we use for our virtualized components falls victim to this making\n\t\t// the components very sluggish. To fix the issue, everything gets registered at the scene level.\n\t\t//\n\t\t// The 'recaf.css' stylesheet is the application baseline sheet and does not need to be referenced here.\n\t\tgetStylesheets().addAll(\n\t\t\t\t\"/style/code-editor.css\",\n\t\t\t\t\"/style/docking.css\",\n\t\t\t\t\"/style/hex.css\",\n\t\t\t\t\"/style/tweaks.css\"\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/window/RecafStage.java",
    "content": "package software.coley.recaf.ui.window;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.stage.Stage;\nimport javafx.stage.StageStyle;\nimport software.coley.recaf.util.Icons;\n\n/**\n * Base stage, adding common Recaf adjustments.\n *\n * @author Matt Coley\n */\npublic class RecafStage extends Stage {\n\t/**\n\t * Decorated stage.\n\t */\n\tpublic RecafStage() {\n\t\tthis(StageStyle.DECORATED);\n\t}\n\n\t/**\n\t * Stage of the given style.\n\t *\n\t * @param style\n\t * \t\tSpecific stage style.\n\t */\n\tpublic RecafStage(@Nonnull StageStyle style) {\n\t\tsuper(style);\n\t\tgetIcons().add(Icons.getImage(Icons.LOGO));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/window/RemoteVirtualMachinesWindow.java",
    "content": "package software.coley.recaf.ui.window;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport software.coley.recaf.services.attach.AttachManager;\nimport software.coley.recaf.services.attach.AttachManagerConfig;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.ui.pane.RemoteVirtualMachinesPane;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.threading.ThreadUtil;\n\n/**\n * Window wrapper for {@link RemoteVirtualMachinesPane}.\n *\n * @author Matt Coley\n * @see RemoteVirtualMachinesPane\n */\n@Dependent\npublic class RemoteVirtualMachinesWindow extends AbstractIdentifiableStage {\n\t@Inject\n\tpublic RemoteVirtualMachinesWindow(@Nonnull RemoteVirtualMachinesPane remoteVirtualMachinesPane,\n\t\t\t\t\t\t\t\t\t   @Nonnull AttachManager attachManager,\n\t\t\t\t\t\t\t\t\t   @Nonnull AttachManagerConfig attachManagerConfig) {\n\t\tsuper(WindowManager.WIN_REMOTE_VMS);\n\n\t\t// Bind attach manager scanning state to the visibility of this window.\n\t\tshowingProperty().addListener((ob, old, showing) -> {\n\t\t\t// When showing run a scan immediately.\n\t\t\t// We are already registered as a scan listener, so we can update the display after it finishes.\n\t\t\tif (showing)\n\t\t\t\tThreadUtil.run(attachManager::scan);\n\n\t\t\t// Bind scanning to only run when the UI is displayed.\n\t\t\tattachManagerConfig.getPassiveScanning().setValue(showing);\n\t\t});\n\n\t\t// Layout\n\t\ttitleProperty().bind(Lang.getBinding(\"menu.file.attach\"));\n\t\tsetMinWidth(750);\n\t\tsetMinHeight(450);\n\t\tsetScene(new RecafScene(remoteVirtualMachinesPane, 750, 450));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/window/ScriptManagerWindow.java",
    "content": "package software.coley.recaf.ui.window;\n\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.layout.BorderPane;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.ui.pane.ScriptManagerPane;\nimport software.coley.recaf.util.Lang;\n\n/**\n * Window wrapper for {@link ScriptManagerPane}.\n *\n * @author Matt Coley\n * @see ScriptManagerPane\n */\n@Dependent\npublic class ScriptManagerWindow extends AbstractIdentifiableStage {\n\t@Inject\n\tpublic ScriptManagerWindow(ScriptManagerPane scriptManagerPane) {\n\t\tsuper(WindowManager.WIN_SCRIPTS);\n\n\t\t// Layout\n\t\ttitleProperty().bind(Lang.getBinding(\"menu.scripting.manage\"));\n\t\tsetMinWidth(750);\n\t\tsetMinHeight(450);\n\t\tsetScene(new RecafScene(new BorderPane(scriptManagerPane), 750, 450));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/window/SystemInformationWindow.java",
    "content": "package software.coley.recaf.ui.window;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.Dependent;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.ScrollPane;\nimport javafx.scene.layout.BorderPane;\nimport software.coley.recaf.services.window.WindowManager;\nimport software.coley.recaf.ui.pane.SystemInformationPane;\nimport software.coley.recaf.util.Lang;\n\n/**\n * Window wrapper for {@link SystemInformationPane}.\n *\n * @author Matt Coley\n * @see SystemInformationPane\n */\n@Dependent\npublic class SystemInformationWindow extends AbstractIdentifiableStage {\n\t@Inject\n\tpublic SystemInformationWindow(@Nonnull SystemInformationPane infoPane) {\n\t\tsuper(WindowManager.WIN_INFO);\n\n\t\t// Layout\n\t\ttitleProperty().bind(Lang.getBinding(\"menu.help.sysinfo\"));\n\t\tsetMinWidth(450);\n\t\tsetMinHeight(300);\n\t\tScrollPane scroll = new ScrollPane(infoPane);\n\t\tscroll.setFitToWidth(true);\n\t\tsetScene(new RecafScene(new BorderPane(scroll), 540, 710));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/wizard/Wizard.java",
    "content": "package software.coley.recaf.ui.wizard;\n\nimport atlantafx.base.controls.Spacer;\nimport atlantafx.base.theme.Styles;\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.binding.Bindings;\nimport javafx.beans.binding.BooleanBinding;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.property.BooleanProperty;\nimport javafx.beans.property.ObjectProperty;\nimport javafx.beans.property.SimpleBooleanProperty;\nimport javafx.beans.property.SimpleObjectProperty;\nimport javafx.css.PseudoClass;\nimport javafx.geometry.Insets;\nimport javafx.geometry.Pos;\nimport javafx.scene.Node;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.Separator;\nimport javafx.scene.layout.BorderPane;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.Priority;\nimport javafx.scene.layout.VBox;\nimport software.coley.recaf.ui.control.ActionButton;\nimport software.coley.recaf.ui.control.BoundLabel;\nimport software.coley.recaf.util.Lang;\n\nimport java.awt.*;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Multi-page wizard. Shows one {@link WizardPage} at a time.\n *\n * @author Matt Coley\n * @see WizardPage\n */\npublic class Wizard extends VBox {\n\tprivate final List<WizardPage> pages;\n\tprivate Runnable onFinish;\n\n\t/**\n\t * @param pages\n\t * \t\tArray of wizard pages.\n\t */\n\tpublic Wizard(WizardPage... pages) {\n\t\tthis(List.of(pages));\n\t}\n\n\t/**\n\t * @param pages\n\t * \t\tList of wizard pages.\n\t */\n\tpublic Wizard(@Nonnull List<WizardPage> pages) {\n\t\tif (pages.isEmpty())\n\t\t\tthrow new IllegalArgumentException(\"Must have at least 1 item for wizard content.\");\n\t\tthis.pages = pages;\n\t\tWizardSteps steps = new WizardSteps();\n\n\t\t// Setup display with first page of content\n\t\tBorderPane content = new BorderPane();\n\t\tsteps.selectedPageProperty().addListener((obs, old, cur) -> content.setCenter(cur.getDisplay()));\n\t\tsteps.selectedPageProperty().set(pages.get(0));\n\n\t\t// [previous] button\n\t\tButton previous = new ActionButton(Lang.getBinding(\"dialog.previous\"), steps::backward);\n\t\tprevious.getStyleClass().addAll(Styles.FLAT);\n\t\tprevious.disableProperty().bind(steps.canGoBackProperty().not());\n\n\t\t// [next] button\n\t\tStringBinding nextBinding = Bindings.createStringBinding(\n\t\t\t\t() -> steps.canGoForwardProperty().get() ? Lang.get(\"dialog.next\") : Lang.get(\"dialog.finish\"),\n\t\t\t\tsteps.canGoForwardProperty()\n\t\t);\n\t\tButton next = new ActionButton(nextBinding, () -> {\n\t\t\t// Cannot go forward only for last page (finish) so handle finish callback here\n\t\t\tif (!steps.canGoForwardProperty().get() && onFinish != null) {\n\t\t\t\tonFinish.run();\n\t\t\t}\n\n\t\t\t// Standard progression\n\t\t\tif (steps.getSelectedPage().canProgressProperty().get()) {\n\t\t\t\tsteps.forward();\n\t\t\t} else {\n\t\t\t\tToolkit.getDefaultToolkit().beep();\n\t\t\t}\n\t\t});\n\t\tnext.setDefaultButton(true);\n\n\t\t// Layout\n\t\tHBox controls = new HBox(previous, new Spacer(), next);\n\t\tcontrols.setAlignment(Pos.CENTER_LEFT);\n\t\tVBox.setVgrow(content, Priority.ALWAYS);\n\t\tVBox.setVgrow(steps, Priority.NEVER);\n\t\tVBox.setVgrow(controls, Priority.NEVER);\n\t\tsetMinWidth(600);\n\t\tsetPadding(new Insets(15));\n\t\tgetChildren().addAll(steps, content, new Separator(), controls);\n\t}\n\n\t/**\n\t * @param onFinish\n\t * \t\tAction to run when the user presses the finish button on the last page.\n\t */\n\tpublic void setOnFinish(Runnable onFinish) {\n\t\tthis.onFinish = onFinish;\n\t}\n\n\t/**\n\t * Page content, with ability to prevent continuation until some input constraints\n\t * are met by binding {@link WizardPage#canProgressProperty()}.\n\t */\n\tpublic abstract static class WizardPage {\n\t\tprivate final BooleanProperty canProgress = new SimpleBooleanProperty();\n\t\tprivate final StringBinding name;\n\t\tprivate Node display;\n\n\t\t/**\n\t\t * @param name\n\t\t * \t\tPage name.\n\t\t */\n\t\tprotected WizardPage(StringBinding name) {\n\t\t\tthis.name = name;\n\t\t}\n\n\t\t/**\n\t\t * @return Page display node.\n\t\t */\n\t\tpublic Node getDisplay() {\n\t\t\tif (display == null) display = createDisplay();\n\t\t\treturn display;\n\t\t}\n\n\t\t/**\n\t\t * @return Page display node.\n\t\t */\n\t\tpublic StringBinding getName() {\n\t\t\treturn name;\n\t\t}\n\n\t\t/**\n\t\t * @param state\n\t\t * \t\tProgression allowed state.\n\t\t */\n\t\tpublic void setCanProgress(boolean state) {\n\t\t\tcanProgress.set(state);\n\t\t}\n\n\t\t/**\n\t\t * @return Progression property, typically bound by the implementation to some input field(s).\n\t\t */\n\t\tpublic BooleanProperty canProgressProperty() {\n\t\t\treturn canProgress;\n\t\t}\n\n\t\t/**\n\t\t * @return Node for {@link #getDisplay()}.\n\t\t */\n\t\tprotected abstract Node createDisplay();\n\t}\n\n\t/**\n\t * Step manager for the current wizard.\n\t * <br>\n\t * Largely lifted from AtlantaFX's widget sampler.\n\t */\n\tprivate class WizardSteps extends HBox {\n\t\tprivate static final PseudoClass SELECTED = PseudoClass.getPseudoClass(Styles.SUCCESS);\n\t\tprivate final ObjectProperty<WizardPage> selectedPage = new SimpleObjectProperty<>();\n\t\tprivate final BooleanBinding canGoBack;\n\t\tprivate final BooleanBinding canGoForward;\n\n\t\tprivate WizardSteps() {\n\t\t\tsetPadding(new Insets(20));\n\t\t\tsetSpacing(15);\n\t\t\tsetAlignment(Pos.CENTER);\n\n\t\t\tcanGoBack = Bindings.createBooleanBinding(() -> {\n\t\t\t\tif (selectedPage.get() == null) return false;\n\t\t\t\tint current = pages.indexOf(selectedPage.get());\n\t\t\t\treturn current > 0 && current <= pages.size() - 1;\n\t\t\t}, selectedPage);\n\n\t\t\tcanGoForward = Bindings.createBooleanBinding(() -> {\n\t\t\t\tif (selectedPage.get() == null) return false;\n\t\t\t\tint current = pages.indexOf(selectedPage.get());\n\t\t\t\treturn current >= 0 && current < pages.size() - 1;\n\t\t\t}, selectedPage);\n\n\t\t\tselectedPage.addListener((obs, old, cur) -> {\n\t\t\t\tif (old != null)\n\t\t\t\t\told.getDisplay().pseudoClassStateChanged(SELECTED, false);\n\t\t\t\tif (cur != null)\n\t\t\t\t\tcur.getDisplay().pseudoClassStateChanged(SELECTED, true);\n\t\t\t});\n\n\t\t\t// Populate from pages\n\t\t\tList<Node> children = new ArrayList<>();\n\t\t\tfor (int i = 0; i < pages.size(); i++) {\n\t\t\t\tWizardPage page = pages.get(i);\n\n\t\t\t\t// Create graphic to represent the page in the ordered list\n\t\t\t\tString indexName = String.valueOf(i + 1);\n\t\t\t\tLabel indexTitle = new BoundLabel(page.getName());\n\t\t\t\tint j = i;\n\t\t\t\tselectedPage.addListener((obs, old, val) -> {\n\t\t\t\t\tNode indexGraphic; // Hack to make the 'current' location more visible with default theme\n\t\t\t\t\tif (val == page) {\n\t\t\t\t\t\tindexGraphic = new Button(indexName);\n\t\t\t\t\t\tindexGraphic.getStyleClass().addAll(Styles.ROUNDED);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tindexGraphic = new Button(indexName);\n\t\t\t\t\t\tindexGraphic.getStyleClass().addAll(Styles.ROUNDED);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Fill the button if the page is 'done'\n\t\t\t\t\tif (j < pages.indexOf(selectedPage.get())) {\n\t\t\t\t\t\tindexGraphic.getStyleClass().addAll(Styles.ACCENT, Styles.SUCCESS);\n\t\t\t\t\t}\n\n\t\t\t\t\tindexGraphic.setMouseTransparent(true);\n\t\t\t\t\tindexGraphic.setFocusTraversable(false);\n\t\t\t\t\tindexTitle.setGraphic(indexGraphic);\n\t\t\t\t});\n\t\t\t\tchildren.add(indexTitle);\n\n\t\t\t\t// Add separator between pages\n\t\t\t\tif (i < pages.size() - 1) {\n\t\t\t\t\tSeparator sep = new Separator();\n\t\t\t\t\tHBox.setHgrow(sep, Priority.ALWAYS);\n\t\t\t\t\tchildren.add(sep);\n\t\t\t\t}\n\t\t\t}\n\t\t\tgetChildren().setAll(children);\n\t\t}\n\n\t\t/**\n\t\t * @return Current selected/displayed page.\n\t\t */\n\t\tpublic WizardPage getSelectedPage() {\n\t\t\treturn selectedPage.get();\n\t\t}\n\n\t\t/**\n\t\t * @return Property of current selected/displayed page.\n\t\t */\n\t\tpublic ObjectProperty<WizardPage> selectedPageProperty() {\n\t\t\treturn selectedPage;\n\t\t}\n\n\t\t/**\n\t\t * @return Binding of when user can go back a page.\n\t\t */\n\t\tpublic BooleanBinding canGoBackProperty() {\n\t\t\treturn canGoBack;\n\t\t}\n\n\t\t/**\n\t\t * @return Binding of when user can go forward a page.\n\t\t */\n\t\tpublic BooleanBinding canGoForwardProperty() {\n\t\t\treturn canGoForward;\n\t\t}\n\n\t\t/**\n\t\t * Go back a page if allowed.\n\t\t *\n\t\t * @return {@code true} when moved.\n\t\t */\n\t\tpublic boolean backward() {\n\t\t\tif (!canGoBack.get()) return false;\n\t\t\tint current = pages.indexOf(selectedPage.get());\n\t\t\tselectedPage.set(pages.get(current - 1));\n\t\t\treturn true;\n\t\t}\n\n\t\t/**\n\t\t * Go forward a page if allowed.\n\t\t *\n\t\t * @return {@code true} when moved.\n\t\t */\n\t\tpublic boolean forward() {\n\t\t\tif (!canGoForward.get()) return false;\n\t\t\tint current = pages.indexOf(selectedPage.get());\n\t\t\tselectedPage.set(pages.get(current + 1));\n\t\t\treturn true;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/ui/wizard/WizardStage.java",
    "content": "package software.coley.recaf.ui.wizard;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.layout.BorderPane;\nimport javafx.stage.Stage;\nimport software.coley.recaf.ui.window.RecafScene;\nimport software.coley.recaf.ui.window.RecafStage;\n\nimport java.util.List;\n\n/**\n * Stage that wraps a {@link Wizard}.\n *\n * @author Matt Coley\n * @see Wizard\n */\npublic class WizardStage extends RecafStage {\n\t/**\n\t * @param pages\n\t * \t\tWizard pages.\n\t * @param onFinish\n\t * \t\tAction to run when wizard finishes.\n\t */\n\tpublic WizardStage(@Nonnull List<Wizard.WizardPage> pages, @Nonnull Runnable onFinish) {\n\t\tWizard wizard = new Wizard(pages);\n\t\twizard.setOnFinish(() -> {\n\t\t\tonFinish.run();\n\t\t\thide();\n\t\t});\n\t\tsetScene(new RecafScene(new BorderPane(wizard)));\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/Animations.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.animation.FadeTransition;\nimport javafx.animation.KeyFrame;\nimport javafx.animation.KeyValue;\nimport javafx.animation.Timeline;\nimport javafx.beans.property.DoubleProperty;\nimport javafx.beans.property.SimpleDoubleProperty;\nimport javafx.scene.Node;\nimport javafx.scene.effect.BlurType;\nimport javafx.scene.effect.InnerShadow;\nimport javafx.scene.paint.Color;\nimport javafx.util.Duration;\n\n/**\n * Animation utilities.\n *\n * @author Matt Coley\n */\npublic class Animations {\n\t/**\n\t * Play an animation to visual clarity <i>(Thin blue border)</i>.\n\t *\n\t * @param node\n\t * \t\tNode to animate.\n\t * @param millis\n\t * \t\tDuration in milliseconds of fade.\n\t */\n\tpublic static void animateNotice(Node node, long millis) {\n\t\tanimate(node, millis, 100, 165, 255);\n\t}\n\n\t/**\n\t * Play an animation that indicates success <i>(Thin green border)</i>.\n\t *\n\t * @param node\n\t * \t\tNode to animate.\n\t * @param millis\n\t * \t\tDuration in milliseconds of fade.\n\t */\n\tpublic static void animateSuccess(Node node, long millis) {\n\t\tanimate(node, millis, 90, 255, 60);\n\t}\n\n\t/**\n\t * Play an animation that indicates a warning <i>(Thin yellow border)</i>.\n\t *\n\t * @param node\n\t * \t\tNode to animate.\n\t * @param millis\n\t * \t\tDuration in milliseconds of fade.\n\t */\n\tpublic static void animateWarn(Node node, long millis) {\n\t\tanimate(node, millis, 255, 255, 40);\n\t}\n\n\t/**\n\t * Play an animation that indicates failure <i>(Thin red border)</i>.\n\t *\n\t * @param node\n\t * \t\tNode to animate.\n\t * @param millis\n\t * \t\tDuration in milliseconds of fade.\n\t */\n\tpublic static void animateFailure(Node node, long millis) {\n\t\tanimate(node, millis, 255, 60, 40);\n\t}\n\n\tprivate static void animate(Node node, long millis, int r, int g, int b) {\n\t\tInnerShadow innerShadow = new InnerShadow();\n\t\tinnerShadow.setBlurType(BlurType.ONE_PASS_BOX);\n\t\tinnerShadow.setChoke(1);\n\t\tinnerShadow.setRadius(5);\n\t\tDoubleProperty dblProp = new SimpleDoubleProperty(1);\n\t\tdblProp.addListener((ob, o, n) -> {\n\t\t\tdouble opacity = n.doubleValue();\n\t\t\tif (opacity > 0.1) {\n\t\t\t\tinnerShadow.setColor(Color.rgb(r, g, b, opacity));\n\t\t\t} else {\n\t\t\t\tnode.setEffect(null);\n\t\t\t}\n\t\t});\n\t\tTimeline timeline = new Timeline(15 /* Reduced framerate for less scene updates */);\n\t\tKeyValue kv = new KeyValue(dblProp, 0);\n\t\tKeyFrame kf = new KeyFrame(Duration.millis(millis), kv);\n\t\tnode.setEffect(innerShadow);\n\t\ttimeline.getKeyFrames().add(kf);\n\t\ttimeline.play();\n\t}\n\n\t/**\n\t * Registers mouse-enter/exit events which transition the opacity of the given node.\n\t *\n\t * @param node\n\t * \t\tNot to install show-on-hover.\n\t */\n\tpublic static void setupShowOnHover(@Nonnull Node node) {\n\t\tdouble hiddenOpacity = 0.1;\n\t\tDoubleProperty opacity = node.opacityProperty();\n\t\topacity.set(hiddenOpacity);\n\n\t\tFadeTransition show = new FadeTransition(Duration.millis(250), node);\n\t\tshow.setToValue(1.0);\n\n\t\tFadeTransition hide = new FadeTransition(Duration.millis(250), node);\n\t\thide.setToValue(hiddenOpacity);\n\n\t\tnode.setOnMouseEntered(e -> {\n\t\t\tshow.setFromValue(opacity.doubleValue());\n\t\t\tshow.play();\n\t\t});\n\t\tnode.setOnMouseExited(e -> {\n\t\t\thide.setFromValue(opacity.doubleValue());\n\t\t\thide.play();\n\t\t});\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/ClipboardUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.input.Clipboard;\nimport javafx.scene.input.ClipboardContent;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.member.ClassMember;\n\n/**\n * Utils for clipboard actions.\n *\n * @author Matt Coley\n */\npublic class ClipboardUtil {\n\t/**\n\t * @param info\n\t * \t\tInfo to copy name/path of.\n\t */\n\tpublic static void copyString(@Nonnull Info info) {\n\t\tcopyString(info.getName());\n\t}\n\n\t/**\n\t * @param declaring\n\t * \t\tDeclaring class.\n\t * @param member\n\t * \t\tMember to copy name/path of.\n\t */\n\tpublic static void copyString(@Nonnull ClassInfo declaring, @Nonnull ClassMember member) {\n\t\tString memberName = member.isField() ? member.getName() + \" \" + member.getDescriptor() :\n\t\t\t\tmember.getName() + member.getDescriptor();\n\t\tString name = declaring.getName() + \".\" + memberName;\n\t\tcopyString(name);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tString to copy.\n\t */\n\tpublic static void copyString(@Nonnull String text) {\n\t\tClipboardContent clipboard = new ClipboardContent();\n\t\tclipboard.putString(text);\n\t\tClipboard.getSystemClipboard().setContent(clipboard);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/Colors.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.scene.paint.Color;\n\n/**\n * Utils for working with colors.\n *\n * @author Matt Coley\n */\npublic class Colors {\n\t/**\n\t * From: <a href=\"https://github.com/sfPlayer1/Matcher/blob/d7fc31b318ea86ac4422237ad204622160d08db8/src/matcher/gui/MatchPaneSrc.java#L315\">\n\t * Matcher</a>\n\t *\n\t * @param colorA\n\t * \t\tFirst color.\n\t * @param colorB\n\t * \t\tSecond color.\n\t * @param step\n\t * \t\tSome ratio of conversion, where {@code 0.0} is {@code colorA} and {@code 1.0} is {@code colorB}.\n\t *\n\t * @return Interpolated color from HSB.\n\t */\n\t@Nonnull\n\tpublic static Color interpolateHsb(@Nonnull Color colorA, @Nonnull Color colorB, float step) {\n\t\tfloat hueA = (float) colorA.getHue();\n\t\tfloat hueB = (float) colorB.getHue();\n\n\t\t// Hue interpolation\n\t\tfloat hue;\n\t\tfloat hueDiff = hueB - hueA;\n\n\t\tif (hueA > hueB) {\n\t\t\tfloat tempHue = hueB;\n\t\t\thueB = hueA;\n\t\t\thueA = tempHue;\n\n\t\t\tColor tempColor = colorB;\n\t\t\tcolorB = colorA;\n\t\t\tcolorA = tempColor;\n\n\t\t\thueDiff = -hueDiff;\n\t\t\tstep = 1 - step;\n\t\t}\n\n\t\tif (hueDiff <= 180) {\n\t\t\thue = hueA + step * hueDiff;\n\t\t} else {\n\t\t\thueA = hueA + 360;\n\t\t\thue = (hueA + step * (hueB - hueA)) % 360;\n\t\t}\n\n\t\t// Interpolate the rest\n\t\treturn Color.hsb(\n\t\t\t\thue,\n\t\t\t\tcolorA.getSaturation() + step * (colorB.getSaturation() - colorA.getSaturation()),\n\t\t\t\tcolorA.getBrightness() + step * (colorB.getBrightness() - colorA.getBrightness()));\n\t}\n\n\t/**\n\t * @param color\n\t * \t\tColor to convert.\n\t *\n\t * @return ARGB of color.\n\t */\n\tpublic static int argb(@Nonnull Color color) {\n\t\tint a = (int) (color.getOpacity() * 255);\n\t\tint r = (int) (color.getRed() * 255);\n\t\tint g = (int) (color.getGreen() * 255);\n\t\tint b = (int) (color.getBlue() * 255);\n\t\treturn a << 24 | r << 16 | g << 8 | b;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/DirectoryChooserBuilder.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.stage.DirectoryChooser;\nimport javafx.stage.Window;\nimport software.coley.observables.ObservableString;\n\nimport java.io.File;\nimport java.nio.file.Path;\n\n/**\n * Builder for {@link DirectoryChooser} which filters out invalid inputs.\n *\n * @author Matt Coley\n */\npublic class DirectoryChooserBuilder {\n\tprivate String title;\n\tprivate File initialDirectory;\n\n\t/**\n\t * @param title\n\t * \t\tDialog title.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic DirectoryChooserBuilder setTitle(@Nonnull String title) {\n\t\tthis.title = title;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param initialDirectory\n\t * \t\tDialog's initial directory to open within and have selected.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic DirectoryChooserBuilder setInitialDirectory(@Nonnull ObservableString initialDirectory) {\n\t\tFile file = initialDirectory.unboxingMap(File::new);\n\t\treturn setInitialDirectory(file);\n\t}\n\n\t/**\n\t * @param initialDirectory\n\t * \t\tDialog's initial directory to open within and have selected.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic DirectoryChooserBuilder setInitialDirectory(@Nonnull Path initialDirectory) {\n\t\treturn setInitialDirectory(initialDirectory.toFile());\n\t}\n\n\t/**\n\t * @param initialDirectory\n\t * \t\tDialog's initial directory to open within and have selected.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic DirectoryChooserBuilder setInitialDirectory(@Nonnull File initialDirectory) {\n\t\tthis.initialDirectory = initialDirectory;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @return New directory chooser from current settings.\n\t */\n\t@Nonnull\n\tpublic DirectoryChooser build() {\n\t\tDirectoryChooser chooser = new DirectoryChooser();\n\t\tif (initialDirectory != null && initialDirectory.isDirectory())\n\t\t\tchooser.setInitialDirectory(initialDirectory);\n\t\tif (title != null)\n\t\t\tchooser.setTitle(title);\n\t\treturn chooser;\n\t}\n\n\t/**\n\t * @param owner\n\t * \t\tThe owner window of the displayed directory dialog.\n\t *\n\t * @return Selected directory path, otherwise {@code null} if cancelled.\n\t */\n\t@Nullable\n\tpublic File pick(@Nullable Window owner) {\n\t\treturn build().showDialog(owner);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/Effects.java",
    "content": "package software.coley.recaf.util;\n\nimport javafx.scene.effect.BlurType;\nimport javafx.scene.effect.InnerShadow;\nimport javafx.scene.paint.Color;\n\n/**\n * Various effect constants.\n *\n * @author Matt Coley\n */\npublic class Effects {\n\t/**\n\t * Red border for controls with erroneous inputs.\n\t */\n\tpublic static final InnerShadow ERROR_BORDER = new InnerShadow(BlurType.ONE_PASS_BOX, Color.RED, 4, 2, 0, 0);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/ErrorDialogs.java",
    "content": "package software.coley.recaf.util;\n\nimport javafx.beans.binding.StringBinding;\nimport javafx.scene.control.Alert;\nimport javafx.scene.control.TextArea;\nimport javafx.stage.Stage;\n\n/**\n * Util for displaying error dialogs.\n *\n * @author Matt Coley\n */\npublic class ErrorDialogs {\n\t/**\n\t * @param title\n\t * \t\tTitle text.\n\t * @param header\n\t * \t\tHeader text.\n\t * @param content\n\t * \t\tContent text.\n\t * @param t\n\t * \t\tThe error to display.\n\t */\n\tpublic static void show(String title, String header, String content, Throwable t) {\n\t\tFxThreadUtil.run(() -> {\n\t\t\tAlert alert = alert(title, header, content);\n\t\t\tconfigure(alert, t);\n\t\t});\n\t}\n\n\t/**\n\t * @param title\n\t * \t\tTitle text.\n\t * @param header\n\t * \t\tHeader text.\n\t * @param content\n\t * \t\tContent text.\n\t * @param t\n\t * \t\tThe error to display.\n\t */\n\tpublic static void show(StringBinding title, StringBinding header, StringBinding content, Throwable t) {\n\t\tFxThreadUtil.run(() -> {\n\t\t\tAlert alert = alert(title, header, content);\n\t\t\tconfigure(alert, t);\n\t\t});\n\t}\n\n\tprivate static void configure(Alert alert, Throwable t) {\n\t\t// Create expandable Exception.\n\t\tString exceptionText = StringUtil.traceToString(t);\n\t\tTextArea textArea = new TextArea(exceptionText);\n\t\ttextArea.setEditable(false);\n\t\ttextArea.setWrapText(true);\n\t\ttextArea.setMaxWidth(Double.MAX_VALUE);\n\t\ttextArea.setMaxHeight(Double.MAX_VALUE);\n\n\t\t// Set expandable Exception into the dialog pane.\n\t\talert.getDialogPane().setExpandableContent(textArea);\n\t\talert.getDialogPane().setExpanded(true);\n\t\tStage stage = (Stage) alert.getDialogPane().getScene().getWindow();\n\t\tstage.getIcons().add(Icons.getImage(Icons.LOGO));\n\t\talert.showAndWait();\n\t}\n\n\tprivate static Alert alert(StringBinding title, StringBinding header, StringBinding content) {\n\t\tAlert alert = new Alert(Alert.AlertType.ERROR);\n\t\talert.titleProperty().bind(title);\n\t\talert.headerTextProperty().bind(header);\n\t\talert.contentTextProperty().bind(content);\n\t\tStage stage = (Stage) alert.getDialogPane().getScene().getWindow();\n\t\tstage.getIcons().add(Icons.getImage(Icons.LOGO));\n\t\treturn alert;\n\t}\n\n\tprivate static Alert alert(String title, String header, String content) {\n\t\tAlert alert = new Alert(Alert.AlertType.ERROR);\n\t\talert.setTitle(title);\n\t\talert.setHeaderText(header);\n\t\talert.setContentText(content);\n\t\tStage stage = (Stage) alert.getDialogPane().getScene().getWindow();\n\t\tstage.getIcons().add(Icons.getImage(Icons.LOGO));\n\t\treturn alert;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/FileChooserBuilder.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.stage.FileChooser;\nimport javafx.stage.Window;\nimport software.coley.observables.ObservableString;\n\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.util.List;\n\n/**\n * Builder for {@link FileChooser} which filters out invalid inputs.\n *\n * @author Matt Coley\n * @see FileChooserBundle\n */\npublic class FileChooserBuilder {\n\tprivate static final FileChooser.ExtensionFilter ALL_FILES_FILTER = new FileChooser.ExtensionFilter(\"All Files\", \"*.*\");\n\tprivate String title;\n\tprivate String initialFileName;\n\tprivate String fileExtensionFilterName;\n\tprivate String[] fileExtensions;\n\tprivate File initialDirectory;\n\n\t/**\n\t * @param title\n\t * \t\tDialog title.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic FileChooserBuilder setTitle(@Nonnull String title) {\n\t\tthis.title = title;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param initialFileName\n\t * \t\tDialog's initial file name.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic FileChooserBuilder setInitialFileName(@Nonnull String initialFileName) {\n\t\tthis.initialFileName = initialFileName;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param initialDirectory\n\t * \t\tDialog's initial directory to open within.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic FileChooserBuilder setInitialDirectory(@Nonnull ObservableString initialDirectory) {\n\t\tFile file = initialDirectory.unboxingMap(File::new);\n\t\treturn setInitialDirectory(file);\n\t}\n\n\t/**\n\t * @param initialDirectory\n\t * \t\tDialog's initial directory to open within.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic FileChooserBuilder setInitialDirectory(@Nonnull Path initialDirectory) {\n\t\treturn setInitialDirectory(initialDirectory.toFile());\n\t}\n\n\t/**\n\t * @param initialDirectory\n\t * \t\tDialog's initial directory to open within.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic FileChooserBuilder setInitialDirectory(@Nonnull File initialDirectory) {\n\t\tthis.initialDirectory = initialDirectory;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param fileExtensionFilterName\n\t * \t\tName of file extension filter.\n\t * @param fileExtensions\n\t * \t\tFile extensions to show. Example: {@code \"*.jar\", \"*.zip\"}.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic FileChooserBuilder setFileExtensionFilter(@Nonnull String fileExtensionFilterName, @Nonnull List<String> fileExtensions) {\n\t\treturn setFileExtensionFilterName(fileExtensionFilterName)\n\t\t\t\t.setFileExtensions(fileExtensions);\n\t}\n\n\t/**\n\t * @param fileExtensionFilterName\n\t * \t\tName of file extension filter.\n\t * @param fileExtensions\n\t * \t\tFile extensions to show. Example: {@code \"*.jar\", \"*.zip\"}.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic FileChooserBuilder setFileExtensionFilter(@Nonnull String fileExtensionFilterName, @Nonnull String... fileExtensions) {\n\t\treturn setFileExtensionFilterName(fileExtensionFilterName)\n\t\t\t\t.setFileExtensions(fileExtensions);\n\t}\n\n\t/**\n\t * @param fileExtensionFilterName\n\t * \t\tName of file extension filter.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic FileChooserBuilder setFileExtensionFilterName(@Nonnull String fileExtensionFilterName) {\n\t\tthis.fileExtensionFilterName = fileExtensionFilterName;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @param fileExtensions\n\t * \t\tFile extensions to show. Example: {@code \"*.jar\", \"*.zip\"}.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic FileChooserBuilder setFileExtensions(@Nonnull List<String> fileExtensions) {\n\t\treturn setFileExtensions(fileExtensions.toArray(new String[0]));\n\t}\n\n\t/**\n\t * @param fileExtensions\n\t * \t\tFile extensions to show. Example: {@code \"*.jar\", \"*.zip\"}.\n\t *\n\t * @return Self.\n\t */\n\t@Nonnull\n\tpublic FileChooserBuilder setFileExtensions(@Nonnull String... fileExtensions) {\n\t\tthis.fileExtensions = fileExtensions;\n\t\treturn this;\n\t}\n\n\t/**\n\t * @return New filer chooser from current settings.\n\t */\n\t@Nonnull\n\tpublic FileChooser build() {\n\t\tFileChooser chooser = new FileChooser();\n\t\tif (initialFileName != null)\n\t\t\tchooser.setInitialFileName(initialFileName);\n\t\tif (fileExtensionFilterName != null && fileExtensions != null) {\n\t\t\tFileChooser.ExtensionFilter filter = new FileChooser.ExtensionFilter(fileExtensionFilterName, fileExtensions);\n\t\t\tchooser.getExtensionFilters().addAll(filter, ALL_FILES_FILTER);\n\t\t\tchooser.setSelectedExtensionFilter(filter);\n\t\t} else {\n\t\t\tchooser.getExtensionFilters().add(ALL_FILES_FILTER);\n\t\t\tchooser.setSelectedExtensionFilter(ALL_FILES_FILTER);\n\t\t}\n\t\tif (initialDirectory != null && initialDirectory.isDirectory())\n\t\t\tchooser.setInitialDirectory(initialDirectory);\n\t\tif (title != null)\n\t\t\tchooser.setTitle(title);\n\t\treturn chooser;\n\t}\n\n\t/**\n\t * @param owner\n\t * \t\tThe owner window of the displayed file dialog.\n\t *\n\t * @return Selected file path, otherwise {@code null} if cancelled.\n\t */\n\t@Nullable\n\tpublic File save(@Nullable Window owner) {\n\t\treturn build().showSaveDialog(owner);\n\t}\n\n\t/**\n\t * @param owner\n\t * \t\tThe owner window of the displayed file dialog.\n\t *\n\t * @return Selected file path, otherwise {@code null} if cancelled.\n\t */\n\t@Nullable\n\tpublic File open(@Nullable Window owner) {\n\t\treturn build().showOpenDialog(owner);\n\t}\n\n\t/**\n\t * @param owner\n\t * \t\tThe owner window of the displayed file dialog.\n\t *\n\t * @return Selected file paths, otherwise {@code null} if cancelled.\n\t */\n\t@Nullable\n\tpublic List<File> openMultiple(@Nullable Window owner) {\n\t\treturn build().showOpenMultipleDialog(owner);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/FileChooserBundle.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.stage.DirectoryChooser;\nimport javafx.stage.FileChooser;\nimport javafx.stage.Window;\nimport software.coley.observables.ObservableString;\nimport software.coley.recaf.ui.config.RecentFilesConfig;\n\nimport java.io.File;\nimport java.util.function.Consumer;\n\n/**\n * Bundle of file choosers. Each chooser will remember its last open directory <i>(unless cancelled)</i>.\n *\n * @author Matt Coley\n * @see FileChooserBuilder\n */\npublic class FileChooserBundle {\n\tprivate final FileChooser fileOpen;\n\tprivate final FileChooser fileExport;\n\tprivate final DirectoryChooser dirOpen;\n\tprivate final DirectoryChooser dirExport;\n\tprivate Consumer<File> fileOpenListener;\n\tprivate Consumer<File> fileExportListener;\n\tprivate Consumer<File> dirOpenListener;\n\tprivate Consumer<File> dirExportListener;\n\n\t/**\n\t * @param fileOpen\n\t * \t\tFile open chooser.\n\t * @param fileExport\n\t * \t\tFile export chooser.\n\t * @param dirOpen\n\t * \t\tDirectory open chooser.\n\t * @param dirExport\n\t * \t\tDirectory export chooser.\n\t */\n\tpublic FileChooserBundle(@Nonnull FileChooser fileOpen,\n\t                         @Nonnull FileChooser fileExport,\n\t                         @Nonnull DirectoryChooser dirOpen,\n\t                         @Nonnull DirectoryChooser dirExport) {\n\t\tthis.fileOpen = fileOpen;\n\t\tthis.fileExport = fileExport;\n\t\tthis.dirOpen = dirOpen;\n\t\tthis.dirExport = dirExport;\n\t}\n\n\t/**\n\t * @param recentFiles\n\t * \t\tConfig to pull recent directories from.\n\t *\n\t * @return Bundle of choosers that initially open from recently interacted with directories.\n\t */\n\t@Nonnull\n\tpublic static FileChooserBundle fromRecent(@Nonnull RecentFilesConfig recentFiles) {\n\t\t// Use a shared file-chooser for mapping menu actions.\n\t\t// That way there is some continuity when working with mappings.\n\t\tObservableString lastOpenDirectory = recentFiles.getLastWorkspaceOpenDirectory();\n\t\tFileChooser fileOpen = new FileChooserBuilder()\n\t\t\t\t.setInitialDirectory(lastOpenDirectory)\n\t\t\t\t.setTitle(Lang.get(\"dialog.file.open\"))\n\t\t\t\t.build();\n\t\tFileChooser fileExport = new FileChooserBuilder()\n\t\t\t\t.setInitialDirectory(lastOpenDirectory)\n\t\t\t\t.setTitle(Lang.get(\"dialog.file.save\"))\n\t\t\t\t.build();\n\t\tDirectoryChooser dirOpen = new DirectoryChooserBuilder()\n\t\t\t\t.setInitialDirectory(lastOpenDirectory)\n\t\t\t\t.setTitle(Lang.get(\"dialog.file.open\"))\n\t\t\t\t.build();\n\t\tDirectoryChooser dirExport = new DirectoryChooserBuilder()\n\t\t\t\t.setInitialDirectory(lastOpenDirectory)\n\t\t\t\t.setTitle(Lang.get(\"dialog.file.save\"))\n\t\t\t\t.build();\n\t\tFileChooserBundle bundle = new FileChooserBundle(fileOpen, fileExport, dirOpen, dirExport);\n\t\tConsumer<File> fileConsumer = file -> {\n\t\t\tif (file != null && !file.isDirectory())\n\t\t\t\tfile = file.getParentFile();\n\t\t\tif (file == null)\n\t\t\t\treturn;\n\t\t\tlastOpenDirectory.setValue(file.getAbsolutePath());\n\t\t};\n\t\tbundle.fileOpenListener = fileConsumer;\n\t\tbundle.fileExportListener = fileConsumer;\n\t\tbundle.dirOpenListener = fileConsumer;\n\t\tbundle.dirExportListener = fileConsumer;\n\t\treturn bundle;\n\t}\n\n\t/**\n\t * @param window\n\t * \t\tWindow to re-focus when completed.\n\t *\n\t * @return Selected file, {@code null} when cancelled.\n\t */\n\t@Nullable\n\tpublic File showFileOpen(@Nullable Window window) {\n\t\tFile file = fileOpen.showOpenDialog(window);\n\t\tif (file != null)\n\t\t\tfileOpen.setInitialDirectory(file.getParentFile());\n\t\tif (fileOpenListener != null)\n\t\t\tfileOpenListener.accept(file);\n\t\treturn file;\n\t}\n\n\t/**\n\t * @param window\n\t * \t\tWindow to re-focus when completed.\n\t *\n\t * @return Selected file, {@code null} when cancelled.\n\t */\n\t@Nullable\n\tpublic File showFileExport(@Nullable Window window) {\n\t\tFile file = fileExport.showSaveDialog(window);\n\t\tif (file != null)\n\t\t\tfileExport.setInitialDirectory(file.getParentFile());\n\t\tif (fileExportListener != null)\n\t\t\tfileExportListener.accept(file);\n\t\treturn file;\n\t}\n\n\t/**\n\t * @param window\n\t * \t\tWindow to re-focus when completed.\n\t *\n\t * @return Selected directory, {@code null} when cancelled.\n\t */\n\t@Nullable\n\tpublic File showDirOpen(@Nullable Window window) {\n\t\tFile file = dirOpen.showDialog(window);\n\t\tif (file != null)\n\t\t\tdirOpen.setInitialDirectory(file.getParentFile());\n\t\tif (dirOpenListener != null)\n\t\t\tdirOpenListener.accept(file);\n\t\treturn file;\n\t}\n\n\t/**\n\t * @param window\n\t * \t\tWindow to re-focus when completed.\n\t *\n\t * @return Selected directory, {@code null} when cancelled.\n\t */\n\t@Nullable\n\tpublic File showDirExport(@Nullable Window window) {\n\t\tFile file = dirExport.showDialog(window);\n\t\tif (file != null)\n\t\t\tdirExport.setInitialDirectory(file.getParentFile());\n\t\tif (dirExportListener != null)\n\t\t\tdirExportListener.accept(file);\n\t\treturn file;\n\t}\n\n\t/**\n\t * @return File open chooser.\n\t */\n\t@Nonnull\n\tpublic FileChooser getFileOpen() {\n\t\treturn fileOpen;\n\t}\n\n\t/**\n\t * @return File export chooser.\n\t */\n\t@Nonnull\n\tpublic FileChooser getFileExport() {\n\t\treturn fileExport;\n\t}\n\n\t/**\n\t * @return Directory open chooser.\n\t */\n\t@Nonnull\n\tpublic DirectoryChooser getDirOpen() {\n\t\treturn dirOpen;\n\t}\n\n\t/**\n\t * @return Directory export chooser.\n\t */\n\t@Nonnull\n\tpublic DirectoryChooser getDirExport() {\n\t\treturn dirExport;\n\t}\n\n\t/**\n\t * @param fileOpenListener\n\t * \t\tListener to intercept items from {@link #getFileOpen()}.\n\t */\n\tpublic void setFileOpenListener(@Nullable Consumer<File> fileOpenListener) {\n\t\tthis.fileOpenListener = fileOpenListener;\n\t}\n\n\t/**\n\t * @param fileExportListener\n\t * \t\tListener to intercept items from {@link #getFileExport()}.\n\t */\n\tpublic void setFileExportListener(@Nullable Consumer<File> fileExportListener) {\n\t\tthis.fileExportListener = fileExportListener;\n\t}\n\n\t/**\n\t * @param dirOpenListener\n\t * \t\tListener to intercept items from {@link #getDirOpen()}.\n\t */\n\tpublic void setDirOpenListener(@Nullable Consumer<File> dirOpenListener) {\n\t\tthis.dirOpenListener = dirOpenListener;\n\t}\n\n\t/**\n\t * @param dirExportListener\n\t * \t\tListener to intercept items from {@link #getDirExport()}.\n\t */\n\tpublic void setDirExportListener(@Nullable Consumer<File> dirExportListener) {\n\t\tthis.dirExportListener = dirExportListener;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/FxThreadUtil.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.application.Platform;\nimport javafx.beans.property.Property;\nimport javafx.beans.value.ObservableValue;\nimport javafx.beans.value.WritableValue;\nimport software.coley.recaf.RecafApplication;\nimport software.coley.recaf.util.threading.Batch;\nimport software.coley.recaf.util.threading.DirectBatch;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.util.threading.ThreadUtil;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.Executor;\n\n/**\n * Utility for working with JavaFX threading.\n *\n * @author Matt Coley\n * @author xDark\n * @see ThreadUtil Common threading.\n * @see ThreadPoolFactory Thread pool building.\n */\npublic class FxThreadUtil {\n\tprivate static final Executor jfxExecutor = FxThreadUtil::run;\n\tprivate static final List<Runnable> preInitQueue = new ArrayList<>();\n\tprivate static boolean initialized;\n\n\t/**\n\t * Run action in JavaFX thread.\n\t *\n\t * @param action\n\t * \t\tRunnable to start in UI thread.\n\t */\n\tpublic static void run(@Nonnull Runnable action) {\n\t\t// Skip under test environment.\n\t\tif (TestEnvironment.isTestEnv()) return;\n\n\t\t// Hold for later if FX has not been initialized.\n\t\tif (!initialized) {\n\t\t\tpreInitQueue.add(action);\n\t\t\treturn;\n\t\t}\n\n\t\t// Wrap action so that if it fails we don't explode and kill the FX thread.\n\t\taction = ThreadUtil.wrap(action);\n\n\t\t// Run inline if on FX thread already, otherwise queue it up.\n\t\tif (Platform.isFxApplicationThread()) action.run();\n\t\telse Platform.runLater(action);\n\t}\n\n\t/**\n\t * Run action in JavaFX thread.\n\t *\n\t * @param delayMs\n\t * \t\tDelay to wait in milliseconds.\n\t * @param action\n\t * \t\tRunnable to start in UI thread.\n\t */\n\tpublic static void delayedRun(long delayMs, @Nonnull Runnable action) {\n\t\tThreadUtil.runDelayed(delayMs, () -> run(action));\n\t}\n\n\t/**\n\t * Assign a property value on the FX thread.\n\t *\n\t * @param property\n\t * \t\tProperty to update.\n\t * @param value\n\t * \t\tValue to assign to the property.\n\t * @param <T>\n\t * \t\tProperty value type.\n\t */\n\tpublic static <T> void set(@Nonnull WritableValue<T> property, @Nullable T value) {\n\t\trun(() -> property.setValue(value));\n\t}\n\n\t/**\n\t * Binds a property value on the FX thread.\n\t *\n\t * @param property\n\t * \t\tProperty to bind.\n\t * @param value\n\t * \t\tValue to bind the property to.\n\t * @param <T>\n\t * \t\tProperty value type.\n\t */\n\tpublic static <T> void bind(@Nonnull Property<T> property, @Nullable ObservableValue<T> value) {\n\t\t// Binding will fire off event handlers if the given value does not match the current property value.\n\t\t// When this is done off the FX thread it can be unsafe.\n\t\trun(() -> property.bind(value));\n\t}\n\n\t/**\n\t * @return JFX threaded executor.\n\t */\n\t@Nonnull\n\tpublic static Executor executor() {\n\t\treturn jfxExecutor;\n\t}\n\n\t/**\n\t * Called by {@link RecafApplication} when it is first initialized.\n\t */\n\tpublic static void onInitialize() {\n\t\tinitialized = true;\n\t\tfor (Runnable runnable : preInitQueue)\n\t\t\trun(runnable);\n\t\tpreInitQueue.clear();\n\t}\n\n\t/**\n\t * @return New task batch that executes all actions on the FX thread.\n\t */\n\t@Nonnull\n\tpublic static Batch batch() {\n\t\treturn ThreadUtil.batch(jfxExecutor);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/Icons.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.Node;\nimport javafx.scene.image.Image;\nimport javafx.scene.image.ImageView;\nimport javafx.scene.image.PixelBuffer;\nimport javafx.scene.image.PixelFormat;\nimport javafx.scene.image.WritableImage;\nimport software.coley.recaf.services.cell.icon.IconProvider;\nimport software.coley.recaf.ui.control.IconView;\n\nimport javax.imageio.ImageIO;\nimport javax.imageio.ImageReader;\nimport javax.imageio.stream.ImageInputStream;\nimport java.awt.image.BufferedImage;\nimport java.awt.image.DataBufferInt;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.lang.reflect.Modifier;\nimport java.nio.IntBuffer;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Icon and graphic utilities.\n *\n * @author Matt Coley\n */\n@SuppressWarnings(\"unused\")\npublic class Icons {\n\tprivate static final Image EMPTY_IMAGE = new WritableImage(1, 1);\n\t// Class definitions\n\tpublic static final String CLASS = \"icons/class/class.png\";\n\tpublic static final String CLASS_ANONYMOUS = \"icons/class/class_anonymous.png\";\n\tpublic static final String CLASS_ABSTRACT = \"icons/class/class_abstract.png\";\n\tpublic static final String CLASS_EXCEPTION = \"icons/class/class_exception.png\";\n\tpublic static final String CLASS_ABSTRACT_EXCEPTION = \"icons/class/class_abstract_exception.png\";\n\tpublic static final String ANNOTATION = \"icons/class/annotation.png\";\n\tpublic static final String INTERFACE = \"icons/class/interface.png\";\n\tpublic static final String ENUM = \"icons/class/enum.png\";\n\tpublic static final String NULL = \"icons/class/null.png\";\n\tpublic static final String PRIMITIVE = \"icons/class/prim.png\";\n\tpublic static final String UNINITIALIZED = \"icons/class/uninitialized.png\";\n\tpublic static final String INTERNAL = \"icons/class/internal.png\";\n\tpublic static final String ARRAY = \"icons/class/array.png\";\n\t// Member definitions\n\tpublic static final String FIELD = \"icons/member/field.png\";\n\tpublic static final String METHOD = \"icons/member/method.png\";\n\tpublic static final String FIELD_N_METHOD = \"icons/member/field_n_method.png\";\n\tpublic static final String CLASS_N_FIELD_N_METHOD = \"icons/member/class_n_field_n_method.png\";\n\tpublic static final String METHOD_ABSTRACT = \"icons/member/method_abstract.png\";\n\t// Access modifiers\n\tpublic static final String ACCESS_ALL_VISIBILITY = \"icons/modifier/all_visibility.png\";\n\tpublic static final String ACCESS_PUBLIC = \"icons/modifier/public.png\";\n\tpublic static final String ACCESS_PROTECTED = \"icons/modifier/protected.png\";\n\tpublic static final String ACCESS_PACKAGE = \"icons/modifier/package.png\";\n\tpublic static final String ACCESS_PRIVATE = \"icons/modifier/private.png\";\n\tpublic static final String ACCESS_FINAL = \"icons/modifier/final.png\";\n\tpublic static final String ACCESS_STATIC = \"icons/modifier/static.png\";\n\t// Folders\n\tpublic static final String FOLDER_SRC = \"icons/file/folder-source.png\";\n\tpublic static final String FOLDER_RES = \"icons/file/folder-resource.png\";\n\tpublic static final String FOLDER_PACKAGE = \"icons/file/folder-package.png\";\n\tpublic static final String FOLDER = \"icons/file/folder.png\";\n\t// Files\n\tpublic static final String FILE_BINARY = \"icons/file/binary.png\";\n\tpublic static final String FILE_TEXT = \"icons/file/text.png\";\n\tpublic static final String FILE_CODE = \"icons/file/text-code.png\";\n\tpublic static final String FILE_ZIP = \"icons/file/zip.png\";\n\tpublic static final String FILE_JAR = \"icons/file/jar.png\";\n\tpublic static final String FILE_CLASS = \"icons/file/class.png\";\n\tpublic static final String FILE_IMAGE = \"icons/file/image.png\";\n\tpublic static final String FILE_AUDIO = \"icons/file/audio.png\";\n\tpublic static final String FILE_VIDEO = \"icons/file/video.png\";\n\tpublic static final String FILE_PROGRAM = \"icons/file/program.png\";\n\tpublic static final String FILE_LIBRARY = \"icons/file/library.png\";\n\t// Misc\n\tpublic static final String LOGO = \"icons/logo.png\";\n\tpublic static final String ANDROID = \"icons/android.png\";\n\tpublic static final String SYNTHETIC = \"icons/synthetic.png\";\n\tpublic static final String DISCORD = \"icons/discord.png\";\n\tpublic static final String REGEX = \"icons/regex.png\";\n\tpublic static final String SORT_ALPHABETICAL = \"icons/sort-alphabetical.png\";\n\tpublic static final String SORT_VISIBILITY = \"icons/sort-visibility.png\";\n\tpublic static final String PHANTOM = \"icons/phantom.png\";\n\tpublic static final String KOTLIN_FLAT = \"icons/kotlin-flat.png\";\n\t// Duke\n\tpublic static final String DUKE_THINK = \"icons/duke_think.png\";\n\tpublic static final String DUKE_THUMBS = \"icons/duke_thumbs.png\";\n\tpublic static final String DUKE_TUMBLE = \"icons/duke_tumble.png\";\n\tpublic static final String DUKE_WAVE = \"icons/duke_wave.png\";\n\n\tprivate static final Map<String, Image> IMAGE_CACHE = new ConcurrentHashMap<>();\n\tprivate static final Map<String, Image> SCALED_IMAGE_CACHE = new ConcurrentHashMap<>();\n\n\t/**\n\t * @param extension\n\t * \t\tSome file extension name.\n\t *\n\t * @return Icon path to use with other methods in this class,\n\t */\n\t@Nonnull\n\tpublic static String getIconPathForFileExtension(@Nullable String extension) {\n\t\tif (extension == null)\n\t\t\treturn FILE_BINARY;\n\t\treturn switch (extension.toLowerCase()) {\n\t\t\tcase \"class\" -> FILE_CLASS;\n\t\t\tcase \"txt\", \"properties\", \"mf\" -> FILE_TEXT;\n\t\t\tcase \"java\", \"css\", \"xml\", \"json\", \"yml\" -> FILE_CODE;\n\t\t\tcase \"zip\" -> FILE_ZIP;\n\t\t\tcase \"apk\", \"dex\" -> ANDROID;\n\t\t\tcase \"jar\", \"war\" -> FILE_JAR;\n\t\t\tcase \"mp3\", \"wav\", \"ogg\" -> FILE_AUDIO;\n\t\t\tcase \"mov\", \"mp4\" -> FILE_VIDEO;\n\t\t\tcase \"png\", \"jpg\", \"jpeg\", \"gif\" -> FILE_IMAGE;\n\t\t\tcase \"exe\" -> FILE_PROGRAM;\n\t\t\tcase \"dll\", \"so\", \"dylib\" -> FILE_LIBRARY;\n\t\t\tdefault -> FILE_BINARY;\n\t\t};\n\t}\n\n\t/**\n\t * Returns {@link ImageView} that uses cached image for rendering.\n\t *\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link Icons}.\n\t *\n\t * @return Graphic of image.\n\t */\n\tpublic static ImageView getImageView(String path) {\n\t\treturn new ImageView(getImage(path));\n\t}\n\n\t/**\n\t * Returns {@link IconView} that uses cached image for rendering.\n\t *\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link Icons}.\n\t *\n\t * @return Graphic of image.\n\t */\n\tpublic static IconView getIconView(String path) {\n\t\treturn getIconView(path, IconView.DEFAULT_ICON_SIZE);\n\t}\n\n\t/**\n\t * Returns {@link IconView} that uses cached image for rendering.\n\t *\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link Icons}.\n\t * @param size\n\t * \t\tImage width/height.\n\t *\n\t * @return Graphic of image.\n\t */\n\tpublic static IconView getIconView(String path, int size) {\n\t\treturn new IconView(getImage(path), size);\n\t}\n\n\t/**\n\t * Returns {@link IconView} that uses cached image for rendering.\n\t * This also scales the image, giving it an anti-aliased look.\n\t *\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link Icons}.\n\t *\n\t * @return Graphic of image.\n\t */\n\tpublic static IconView getScaledIconView(String path) {\n\t\treturn getScaledIconView(path, IconView.DEFAULT_ICON_SIZE);\n\t}\n\n\t/**\n\t * Returns {@link IconView} that uses cached image for rendering.\n\t * This also scales the image, giving it an anti-aliased look.\n\t *\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link Icons}.\n\t * @param size\n\t * \t\tImage width/height.\n\t *\n\t * @return Graphic of image.\n\t */\n\tpublic static IconView getScaledIconView(String path, int size) {\n\t\treturn new IconView(getScaledImage(path, size), size);\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link Icons}.\n\t *\n\t * @return Cached image.\n\t */\n\tpublic static Image getImage(String path) {\n\t\treturn IMAGE_CACHE.computeIfAbsent(path, k -> safeCreateImage(path));\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link Icons}.\n\t * @param size\n\t * \t\tDesired image size.\n\t *\n\t * @return Cached image.\n\t */\n\tpublic static Image getScaledImage(String path, int size) {\n\t\tString key = path + \"-x\" + size;\n\t\tImage image = SCALED_IMAGE_CACHE.get(key);\n\t\tif (image == null) {\n\t\t\tInputStream stream = ResourceUtil.resource(path);\n\t\t\timage = new Image(stream, size, size, true, true);\n\t\t\tImage cached = SCALED_IMAGE_CACHE.putIfAbsent(key, image);\n\t\t\tif (cached != null) {\n\t\t\t\tIOUtil.closeQuietly(stream);\n\t\t\t\timage = cached;\n\t\t\t}\n\t\t}\n\t\treturn image;\n\t}\n\n\t/**\n\t * Get node to represent common visibility/access modifiers. Technically works on classes too.\n\t *\n\t * @param access\n\t * \t\tAccess modifiers.\n\t *\n\t * @return Node to represent the access modifier.\n\t */\n\tpublic static Node getVisibilityIcon(int access) {\n\t\tif (Modifier.isPrivate(access)) {\n\t\t\treturn getIconView(ACCESS_PRIVATE);\n\t\t} else if (Modifier.isProtected(access)) {\n\t\t\treturn getIconView(ACCESS_PROTECTED);\n\t\t} else if (Modifier.isPublic(access)) {\n\t\t\treturn getIconView(ACCESS_PUBLIC);\n\t\t}\n\t\treturn getIconView(ACCESS_PACKAGE);\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link Icons}.\n\t *\n\t * @return Lazy provider of the image.\n\t */\n\tpublic static IconProvider createProvider(String path) {\n\t\treturn () -> getIconView(path);\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link Icons}.\n\t *\n\t * @return Graphic of image, or an empty image if the path could not be resolved.\n\t */\n\tprivate static Image safeCreateImage(String path) {\n\t\ttry {\n\t\t\treturn new Image(ResourceUtil.resource(path));\n\t\t} catch (NullPointerException ex) {\n\t\t\treturn EMPTY_IMAGE;\n\t\t}\n\t}\n\n\t/**\n\t * Offers the same functionality as {@code SwingFXUtils.toFXImage} but without having to pull in the extra\n\t * javafx-swing dependency.\n\t *\n\t * @param image\n\t * \t\tBuffered image assumed to be in {@link BufferedImage#TYPE_INT_ARGB}.\n\t *\n\t * @return JavaFX image.\n\t *\n\t * @author <a href=\"https://stackoverflow.com/a/75703543\">Kevin Bähre</a>\n\t */\n\t@Nonnull\n\tpublic static Image convertToFxImage(@Nonnull BufferedImage image) {\n\t\tint[] type_int_agrb = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();\n\t\tIntBuffer buffer = IntBuffer.wrap(type_int_agrb);\n\t\tPixelFormat<IntBuffer> pixelFormat = PixelFormat.getIntArgbPreInstance();\n\t\tPixelBuffer<IntBuffer> pixelBuffer = new PixelBuffer<>(image.getWidth(), image.getHeight(), buffer, pixelFormat);\n\t\treturn new WritableImage(pixelBuffer);\n\t}\n\n\t/**\n\t * Converts an ICO file into a JavaFX image.\n\t *\n\t * @param ico\n\t * \t\tRaw bytes of ICO image.\n\t *\n\t * @return Highest resolution version of the icon image.\n\t *\n\t * @throws IOException\n\t * \t\tWhen the image file cannot be read.\n\t */\n\t@Nullable\n\tpublic static Image convertIcoToFxImage(@Nonnull byte[] ico) throws IOException {\n\t\tImageReader reader = null;\n\n\t\t// This relies on \"TwelveMonkeys\" being on the classpath to register an image reader\n\t\t// instance for ICO support.\n\t\ttry (ImageInputStream iis = ImageIO.createImageInputStream(new ByteArrayInputStream(ico))) {\n\t\t\tIterator<ImageReader> readers = ImageIO.getImageReaders(iis);\n\t\t\tif (!readers.hasNext())\n\t\t\t\tthrow new IOException(\"No supported readers for image type\");\n\n\t\t\treader = readers.next();\n\t\t\treader.setInput(iis);\n\t\t\tint count = reader.getNumImages(true);\n\n\t\t\t// Get the image with the highest resolution.\n\t\t\tImage result = null;\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tBufferedImage img = reader.read(i, null);\n\t\t\t\tif (result == null || result.getWidth() < img.getWidth())\n\t\t\t\t\tresult = convertToFxImage(img);\n\t\t\t}\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tif (reader != null) {\n\t\t\t\treader.dispose();\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/IntRange.java",
    "content": "package software.coley.recaf.util;\n\n/**\n * Representation of a range.\n *\n * @param start\n * \t\tStart value.\n * @param end\n * \t\tEnd value.\n *\n * @author Matt Coley\n */\npublic record IntRange(int start, int end) implements Comparable<IntRange> {\n\t/**\n\t * Constant range of 0 to 0.\n\t */\n\tpublic static final IntRange EMPTY = new IntRange(0, 0);\n\n\t/**\n\t * @return Length of the range.\n\t */\n\tpublic int length() {\n\t\treturn end() - start();\n\t}\n\n\t/**\n\t * @return {@code true} when {@link #length()} is {@code 0}.\n\t */\n\tpublic boolean empty() {\n\t\treturn length() == 0;\n\t}\n\n\t/**\n\t * @param startInclusive\n\t * \t\tFlag to indicate inclusive range check against {@link #start()}.\n\t * @param endInclusive\n\t * \t\tFlag to indicate inclusive range check against {@link #end()}.\n\t * @param value\n\t * \t\tPosition to check.\n\t *\n\t * @return {@code true} when the position is within this range.\n\t */\n\tpublic boolean isBetween(boolean startInclusive, boolean endInclusive, int value) {\n\t\tif (startInclusive ? value >= start : value > start)\n\t\t\treturn endInclusive ? value <= end : value < end;\n\t\treturn false;\n\t}\n\n\t/**\n\t * Shorthand for {@code text.substring(range.start(), range.end())}.\n\t *\n\t * @param text\n\t * \t\tText to cut section out of.\n\t *\n\t * @return Section of text of this range.\n\t */\n\tpublic String sectionOfText(String text) {\n\t\treturn text.substring(start, end);\n\t}\n\n\t/**\n\t * @param length\n\t * \t\tExtension size.\n\t *\n\t * @return Copy of range, with end offset forwards by length.\n\t */\n\tpublic IntRange extendForwards(int length) {\n\t\treturn new IntRange(start(), end() + length);\n\t}\n\n\t/**\n\t * @param length\n\t * \t\tExtension size.\n\t *\n\t * @return Copy of range, with start offset backwards by length.\n\t */\n\tpublic IntRange extendBackwards(int length) {\n\t\treturn new IntRange(Math.max(0, start() - length), end());\n\t}\n\n\t@Override\n\tpublic int compareTo(IntRange o) {\n\t\treturn Integer.compare(start, o.start);\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/JFXValidation.java",
    "content": "package software.coley.recaf.util;\n\nimport org.slf4j.Logger;\nimport regexodus.Matcher;\nimport software.coley.recaf.ExitCodes;\nimport software.coley.recaf.analytics.logging.Logging;\n\nimport java.lang.reflect.InaccessibleObjectException;\nimport java.lang.reflect.InvocationTargetException;\n\n/**\n * Validates JFX is on the classpath.\n *\n * @author Matt Coley\n */\npublic class JFXValidation {\n\tpublic static final int MIN_JFX_VERSION = 19;\n\tprivate static final Logger logger = Logging.get(JFXValidation.class);\n\n\t/**\n\t * Ensures that the JavaFX runtime is on the class path.\n\t */\n\tpublic static int validateJFX() {\n\t\ttry {\n\t\t\tString jfxVersionClass = \"com.sun.javafx.runtime.VersionInfo\";\n\t\t\tClass<?> versionClass = Class.forName(jfxVersionClass);\n\t\t\tReflectUtil.getDeclaredMethod(versionClass, \"setupSystemProperties\").invoke(null);\n\n\t\t\tString versionProperty = System.getProperty(\"javafx.version\");\n\t\t\tMatcher versionMatcher = RegexUtil.getMatcher(\"\\\\d+\", versionProperty);\n\n\t\t\tif (versionMatcher.find()) {\n\t\t\t\tint majorVersion = Integer.parseInt(versionMatcher.group());\n\t\t\t\tif (majorVersion < MIN_JFX_VERSION) {\n\t\t\t\t\tlogger.error(\"JavaFX version {} is present, but Recaf requires {}+\", majorVersion, MIN_JFX_VERSION);\n\t\t\t\t\treturn ExitCodes.ERR_FX_OLD_VERSION;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlogger.error(\"JavaFX version {} does not declare a major release version, cannot validate compatibility\", versionProperty);\n\t\t\t\treturn ExitCodes.ERR_FX_UNKNOWN_VERSION;\n\t\t\t}\n\n\t\t\tlogger.info(\"JavaFX successfully initialized: {}\", versionProperty);\n\t\t\treturn ExitCodes.SUCCESS;\n\t\t} catch (ClassNotFoundException ex) {\n\t\t\tlogger.error(\"JFX validation failed, could not find 'VersionInfo' class\", ex);\n\t\t\treturn ExitCodes.ERR_FX_CLASS_NOT_FOUND;\n\t\t} catch (NoSuchMethodException ex) {\n\t\t\tlogger.error(\"JFX validation failed, could not find 'setupSystemProperties' in 'VersionInfo'\", ex);\n\t\t\treturn ExitCodes.ERR_FX_NO_SUCH_METHOD;\n\t\t} catch (InvocationTargetException ex) {\n\t\t\tlogger.error(\"JFX validation failed, failed to invoke 'setupSystemProperties'\", ex);\n\t\t\treturn ExitCodes.ERR_FX_INVOKE_TARGET;\n\t\t} catch (IllegalAccessException | InaccessibleObjectException ex) {\n\t\t\tlogger.error(\"JFX validation failed, failed to invoke 'setupSystemProperties'\", ex);\n\t\t\treturn ExitCodes.ERR_FX_ACCESS_TARGET;\n\t\t} catch (Exception ex) {\n\t\t\tlogger.error(\"JFX validation failed due to unhandled exception\", ex);\n\t\t\treturn ExitCodes.ERR_FX_UNKNOWN;\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/Lang.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.property.StringProperty;\nimport javafx.beans.value.ObservableValue;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\n\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\n/**\n * Simple translation utility, tracking a bundle instance in the future may be a better choice.\n *\n * @author Matt Coley\n */\npublic class Lang {\n\tprivate static final String DEFAULT_TRANSLATIONS = \"en_US\";\n\tprivate static String SYSTEM_LANGUAGE;\n\tprivate static final List<String> translationKeys = new ArrayList<>();\n\tprivate static final Logger logger = Logging.get(Lang.class);\n\tprivate static final Map<String, Map<String, String>> translations = new ConcurrentHashMap<>();\n\tprivate static final Map<String, StringBinding> translationBindings = new ConcurrentHashMap<>();\n\tprivate static Map<String, String> currentTranslationMap;\n\tprivate static final StringProperty currentTranslation = new SynchronizedSimpleStringProperty(DEFAULT_TRANSLATIONS);\n\n\t/**\n\t * @return Provided translations, also keys for {@link #getTranslations()}.\n\t */\n\t@Nonnull\n\tpublic static List<String> getTranslationKeys() {\n\t\treturn translationKeys;\n\t}\n\n\t/**\n\t * @return Default translations, English, also key for {@link #getTranslations()}.\n\t */\n\t@Nonnull\n\tpublic static String getDefaultTranslations() {\n\t\treturn DEFAULT_TRANSLATIONS;\n\t}\n\n\t/**\n\t * @return Current translations, used as key in {@link #getTranslations()}.\n\t */\n\tpublic static String getCurrentTranslations() {\n\t\treturn currentTranslation.get();\n\t}\n\n\t/**\n\t * Sets the current translations. Should be called before UI is shown for text components to use new values.\n\t *\n\t * @param translationsKey\n\t * \t\tNew translations, used as key in {@link #getTranslations()}.\n\t */\n\tpublic static void setCurrentTranslations(String translationsKey) {\n\t\tif (translations.containsKey(translationsKey)) {\n\t\t\tcurrentTranslationMap = translations.getOrDefault(translationsKey, Collections.emptyMap());\n\t\t\tFxThreadUtil.set(currentTranslation, translationsKey);\n\t\t} else {\n\t\t\tlogger.warn(\"Tried to set translations to '{}', but no entries for the translations were found!\", translationsKey);\n\t\t\t// For case it fails to load, use default.\n\t\t\t// If for some reason the default translations are not loading, we got a problem...\n\t\t\tif (!DEFAULT_TRANSLATIONS.equals(translationsKey)) {\n\t\t\t\tsetCurrentTranslations(DEFAULT_TRANSLATIONS);\n\t\t\t} else {\n\t\t\t\tlogger.error(\"Could not load default translations: {}\", DEFAULT_TRANSLATIONS);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Sets the system language.\n\t *\n\t * @param translations\n\t * \t\tSystem language.\n\t */\n\tpublic static void setSystemLanguage(String translations) {\n\t\tSYSTEM_LANGUAGE = translations;\n\t}\n\n\t/**\n\t * @return System language, or {@link #getDefaultTranslations()} if not set.\n\t */\n\t@Nonnull\n\tpublic static String getSystemLanguage() {\n\t\treturn SYSTEM_LANGUAGE == null ? getDefaultTranslations() : SYSTEM_LANGUAGE;\n\t}\n\n\t/**\n\t * @return Map of supported translations and their key entries.\n\t */\n\t@Nonnull\n\tpublic static Map<String, Map<String, String>> getTranslations() {\n\t\treturn translations;\n\t}\n\n\t/**\n\t * @param translationKey\n\t * \t\tKey name.\n\t *\n\t * @return JavaFX string binding for specific translation key.\n\t */\n\t@Nonnull\n\tpublic static synchronized StringBinding getBinding(@Nonnull String translationKey) {\n\t\treturn translationBindings.computeIfAbsent(translationKey, k -> {\n\t\t\tStringProperty currentTranslation = Lang.currentTranslation;\n\t\t\treturn new SynchronizedStringBinding() {\n\t\t\t\t{\n\t\t\t\t\tbind(currentTranslation);\n\t\t\t\t}\n\n\t\t\t\t@Override\n\t\t\t\tprotected synchronized String computeValue() {\n\t\t\t\t\treturn Lang.get(currentTranslation.get(), translationKey);\n\t\t\t\t}\n\t\t\t};\n\t\t});\n\t}\n\n\t/**\n\t * @param format\n\t * \t\tString format.\n\t * @param args\n\t * \t\tFormat arguments.\n\t *\n\t * @return JavaFX string binding for specific translation key with arguments.\n\t */\n\t@Nonnull\n\tpublic static StringBinding formatBy(@Nonnull String format, ObservableValue<?>... args) {\n\t\treturn new SynchronizedStringBinding() {\n\t\t\t{\n\t\t\t\tbind(args);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tprotected synchronized String computeValue() {\n\t\t\t\treturn String.format(format, Arrays.stream(args)\n\t\t\t\t\t\t.map(ObservableValue::getValue).toArray());\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * @param translationKey\n\t * \t\tKey name.\n\t * @param args\n\t * \t\tFormat arguments.\n\t *\n\t * @return JavaFX string binding for specific translation key with arguments.\n\t */\n\t@Nonnull\n\tpublic static StringBinding format(@Nonnull String translationKey, ObservableValue<?>... args) {\n\t\tStringBinding root = getBinding(translationKey);\n\t\treturn new SynchronizedStringBinding() {\n\t\t\t{\n\t\t\t\tbind(root);\n\t\t\t\tbind(args);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tprotected synchronized String computeValue() {\n\t\t\t\treturn String.format(root.getValue(), Arrays.stream(args)\n\t\t\t\t\t\t.map(ObservableValue::getValue).toArray());\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * @param translationKey\n\t * \t\tKey name.\n\t * @param args\n\t * \t\tFormat arguments.\n\t *\n\t * @return JavaFX string binding for specific translation key with arguments.\n\t */\n\t@Nonnull\n\tpublic static StringBinding format(@Nonnull String translationKey, Object... args) {\n\t\tStringBinding root = getBinding(translationKey);\n\t\treturn new SynchronizedStringBinding() {\n\t\t\t{\n\t\t\t\tbind(root);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tprotected synchronized String computeValue() {\n\t\t\t\treturn String.format(root.getValue(), args);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * @param translation\n\t * \t\tTranslation value.\n\t * @param args\n\t * \t\tFormat arguments.\n\t *\n\t * @return JavaFX string binding for specific translation key with arguments.\n\t */\n\t@Nonnull\n\tpublic static StringBinding concat(@Nonnull ObservableValue<String> translation, String... args) {\n\t\treturn new SynchronizedStringBinding() {\n\t\t\t{\n\t\t\t\tbind(translation);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tprotected synchronized String computeValue() {\n\t\t\t\treturn translation.getValue() + String.join(\" \", args);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * @param translationKey\n\t * \t\tKey name.\n\t * @param args\n\t * \t\tFormat arguments.\n\t *\n\t * @return JavaFX string binding for specific translation key with arguments.\n\t */\n\t@Nonnull\n\tpublic static StringBinding concat(@Nonnull String translationKey, String... args) {\n\t\tStringBinding root = getBinding(translationKey);\n\t\treturn new SynchronizedStringBinding() {\n\t\t\t{\n\t\t\t\tbind(root);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tprotected synchronized String computeValue() {\n\t\t\t\treturn root.getValue() + String.join(\" \", args);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * @return Translations property.\n\t */\n\t@Nonnull\n\tpublic static StringProperty translationsProperty() {\n\t\treturn currentTranslation;\n\t}\n\n\t/**\n\t * @param translationKey\n\t * \t\tKey name.\n\t *\n\t * @return Translated value, based on {@link #getCurrentTranslations() current loaded mappings}.\n\t */\n\t@Nonnull\n\tpublic static String get(String translationKey) {\n\t\treturn get(getCurrentTranslations(), translationKey);\n\t}\n\n\t/**\n\t * @param translations\n\t * \t\tLanguage translations group to load from.\n\t * @param translationKey\n\t * \t\tKey name.\n\t *\n\t * @return Translated value, based on {@link #getCurrentTranslations() current loaded mappings}.\n\t */\n\t@Nonnull\n\tpublic static String get(@Nonnull String translations, @Nonnull String translationKey) {\n\t\tMap<String, String> map = Objects.requireNonNullElseGet(Lang.translations.getOrDefault(translations, currentTranslationMap), Collections::emptyMap);\n\t\tString value = map.get(translationKey);\n\t\tif (value == null) {\n\t\t\t// Fallback to English if possible.\n\t\t\tif (translations.equals(DEFAULT_TRANSLATIONS)) {\n\t\t\t\tlogger.error(\"Missing translation for '{}' in language '{}'\", translationKey, currentTranslation.get());\n\t\t\t\tvalue = translationKey;\n\t\t\t} else {\n\t\t\t\tvalue = get(DEFAULT_TRANSLATIONS, translationKey);\n\t\t\t}\n\t\t}\n\t\treturn value.replace(\"\\\\n\", \"\\n\");\n\t}\n\n\t/**\n\t * @param translations\n\t * \t\tLanguage translations group to load from.\n\t * @param translationKey\n\t * \t\tKey name.\n\t *\n\t * @return {@code true} when the translation is present in the given translations.\n\t */\n\tpublic static boolean has(String translations, String translationKey) {\n\t\treturn Lang.translations.getOrDefault(translations, currentTranslationMap).containsKey(translationKey);\n\t}\n\n\t/**\n\t * @param translationKey\n\t * \t\tKey name.\n\t *\n\t * @return {@code true} when the translation is present in the current translations.\n\t */\n\tpublic static boolean has(String translationKey) {\n\t\treturn has(getCurrentTranslations(), translationKey);\n\t}\n\n\t/**\n\t * Load the translations and initialize the default one.\n\t */\n\tpublic static void initialize() {\n\t\t// Get the actual locale for translations\n\t\tString userCountry = Locale.getDefault().getCountry();\n\t\tString userLanguage = Locale.getDefault().getLanguage();\n\t\tString userLanguageKey = userLanguage + \"_\" + userCountry;\n\t\tsetSystemLanguage(userLanguageKey);\n\n\t\t// Then set the jvm to use to avoid the locale bug\n\t\t//  - https://mattryall.net/blog/the-infamous-turkish-locale-bug\n\t\tLocale.setDefault(Locale.US);\n\n\t\t// Load provided translations\n\t\tSelfReferenceUtil.initializeFromContext(Lang.class);\n\t\tSelfReferenceUtil selfReferenceUtil = SelfReferenceUtil.getInstance();\n\t\tList<InternalPath> translations = selfReferenceUtil.getTranslations();\n\t\tif (!translations.isEmpty())\n\t\t\tlogger.debug(\"Found {} translations\", translations.size());\n\t\telse\n\t\t\tlogger.error(\"Translations could not be loaded! CodeSource: {}\",\n\t\t\t\t\tLang.class.getProtectionDomain().getCodeSource().getLocation());\n\t\tfor (InternalPath translationPath : translations) {\n\t\t\tString translationName = StringUtil.cutOffAtFirst(translationPath.getFileName(), \".\");\n\t\t\ttry {\n\t\t\t\tload(translationName, translationPath.getURL().openStream());\n\t\t\t\ttranslationKeys.add(translationName);\n\t\t\t\tlogger.info(\"Loaded translations '{}'\", translationName);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"Failed to load translations '{}'\", translationName, t);\n\n\t\t\t\t// Throw to kill the main thread so that we don't show the UI in this broken state.\n\t\t\t\tthrow new IllegalStateException(\"Failed to read translations, please check the integrity of the Recaf jar file\", t);\n\t\t\t}\n\t\t}\n\n\t\t// Set default translations\n\t\tsetCurrentTranslations(DEFAULT_TRANSLATIONS);\n\t}\n\n\t/**\n\t * Load translations from {@link InputStream}.\n\t *\n\t * @param translations\n\t * \t\tTarget translations identifier. The key for {@link #getTranslations()}.\n\t * @param in\n\t *        {@link InputStream} to load translations from.\n\t */\n\tpublic static void load(String translations, InputStream in) {\n\t\ttry {\n\t\t\tMap<String, String> translationsMap = Lang.translations.computeIfAbsent(translations, l -> new HashMap<>());\n\t\t\tString string = IOUtil.toString(in, UTF_8);\n\t\t\tString[] lines = string.split(\"[\\n\\r]+\");\n\t\t\tfor (String line : lines) {\n\t\t\t\t// Skip comment lines\n\t\t\t\tif (line.startsWith(\"#\")) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// Add each \"key=value\"\n\t\t\t\tif (line.contains(\"=\")) {\n\t\t\t\t\tString[] parts = line.split(\"=\", 2);\n\t\t\t\t\tString key = parts[0];\n\t\t\t\t\tString value = parts[1];\n\t\t\t\t\ttranslationsMap.put(key, value);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (Exception ex) {\n\t\t\tthrow new IllegalStateException(\"Failed to fetch language from input stream\", ex);\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/Menus.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.binding.StringBinding;\nimport javafx.scene.Node;\nimport javafx.scene.control.Menu;\nimport javafx.scene.control.MenuItem;\nimport javafx.scene.control.SeparatorMenuItem;\nimport javafx.scene.paint.Color;\nimport org.kordamp.ikonli.Ikon;\nimport software.coley.recaf.ui.control.ActionMenu;\nimport software.coley.recaf.ui.control.ActionMenuItem;\nimport software.coley.recaf.ui.control.FontIconView;\nimport software.coley.recaf.ui.control.IconView;\n\n/**\n * Menu utilities.\n *\n * @author Matt Coley\n */\npublic class Menus {\n\t/**\n\t * @param name\n\t * \t\tHeader text.\n\t * @param graphic\n\t * \t\tHeader graphic.\n\t * @param limit\n\t * \t\tText length limit.\n\t *\n\t * @return Header menu item.\n\t */\n\t@Nonnull\n\tpublic static MenuItem createHeader(@Nonnull String name, @Nullable Node graphic, int limit) {\n\t\tMenuItem header = new MenuItem(TextDisplayUtil.shortenEscapeLimit(name, limit));\n\t\theader.getStyleClass().add(\"context-menu-header\");\n\t\theader.setGraphic(graphic);\n\t\theader.setDisable(true);\n\t\treturn header;\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t *\n\t * @return Menu instance.\n\t */\n\t@Nonnull\n\tpublic static Menu menu(@Nonnull String textKey) {\n\t\treturn menu(textKey, (String) null);\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t * @param imagePath\n\t * \t\tPath to image for menu graphic.\n\t *\n\t * @return Menu instance, with optional graphic.\n\t */\n\t@Nonnull\n\tpublic static Menu menu(@Nonnull String textKey, @Nullable String imagePath) {\n\t\treturn menu(textKey, imagePath, false);\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t * @param imagePath\n\t * \t\tPath to image for menu graphic.\n\t * @param antialias\n\t * \t\tFlag to enable anti-aliasing of the image graphic.\n\t *\n\t * @return Menu instance, with optional graphic.\n\t */\n\t@Nonnull\n\tpublic static Menu menu(@Nonnull String textKey, @Nullable String imagePath, boolean antialias) {\n\t\tNode graphic = imagePath == null ? null :\n\t\t\t\tantialias ? Icons.getScaledIconView(imagePath) : Icons.getIconView(imagePath);\n\t\tMenu menu = new Menu();\n\t\tmenu.textProperty().bind(Lang.getBinding(textKey));\n\t\tmenu.setGraphic(graphic);\n\t\tmenu.setId(textKey);\n\t\treturn menu;\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t * @param icon\n\t * \t\tIkonli icon for the menu graphic.\n\t *\n\t * @return Menu instance, with graphic.\n\t */\n\t@Nonnull\n\tpublic static Menu menu(@Nonnull String textKey, @Nonnull Ikon icon) {\n\t\treturn menu(textKey, icon, null);\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t * @param icon\n\t * \t\tIkonli icon for the menu graphic.\n\t * @param color\n\t * \t\tColor for icon.\n\t *\n\t * @return Menu instance, with graphic.\n\t */\n\t@Nonnull\n\tpublic static Menu menu(@Nonnull String textKey, @Nonnull Ikon icon, @Nullable Color color) {\n\t\tFontIconView graphic = color == null ?\n\t\t\t\tnew FontIconView(icon) :\n\t\t\t\tnew FontIconView(icon, IconView.DEFAULT_ICON_SIZE, color);\n\t\treturn menu(textKey, graphic);\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t * @param graphic\n\t * \t\tOptional menu graphic.\n\t *\n\t * @return Menu instance, with optional graphic.\n\t */\n\t@Nonnull\n\tpublic static Menu menu(@Nonnull String textKey, @Nullable Node graphic) {\n\t\tMenu menu = new Menu();\n\t\tmenu.textProperty().bind(Lang.getBinding(textKey));\n\t\tmenu.setGraphic(graphic);\n\t\tmenu.setId(textKey);\n\t\treturn menu;\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t * @param imagePath\n\t * \t\tPath to image for menu graphic.\n\t * @param runnable\n\t * \t\tAction to run on click.\n\t *\n\t * @return Menu instance, with behavior on-click.\n\t */\n\t@Nonnull\n\tpublic static Menu actionMenu(@Nonnull String textKey, @Nullable String imagePath, @Nonnull Runnable runnable) {\n\t\treturn actionMenu(textKey, imagePath, runnable, false);\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t * @param imagePath\n\t * \t\tPath to image for menu graphic.\n\t * @param runnable\n\t * \t\tAction to run on click.\n\t * @param antialias\n\t * \t\tFlag to enable anti-aliasing of the image graphic.\n\t *\n\t * @return Menu instance, with behavior on-click.\n\t */\n\t@Nonnull\n\tpublic static Menu actionMenu(@Nonnull String textKey, @Nullable String imagePath,\n\t\t\t\t\t\t\t\t  @Nonnull Runnable runnable, boolean antialias) {\n\t\tNode graphic = imagePath == null ? null :\n\t\t\t\tantialias ? Icons.getScaledIconView(imagePath) : Icons.getIconView(imagePath);\n\t\treturn new ActionMenu(Lang.getBinding(textKey), graphic, runnable).withId(textKey);\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t * @param runnable\n\t * \t\tAction to run on click.\n\t *\n\t * @return Action menu item with behavior on-click.\n\t */\n\t@Nonnull\n\tpublic static ActionMenuItem action(@Nonnull String textKey, @Nonnull Runnable runnable) {\n\t\treturn action(textKey, (String) null, runnable);\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t * @param imagePath\n\t * \t\tPath to image for menu graphic.\n\t * @param runnable\n\t * \t\tAction to run on click.\n\t *\n\t * @return Action menu item with behavior on-click.\n\t */\n\t@Nonnull\n\tpublic static ActionMenuItem action(@Nonnull String textKey, @Nullable String imagePath, @Nonnull Runnable runnable) {\n\t\treturn action(textKey, imagePath, runnable, false);\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t * @param imagePath\n\t * \t\tPath to image for menu graphic.\n\t * @param runnable\n\t * \t\tAction to run on click.\n\t * @param antialias\n\t * \t\tFlag to enable anti-aliasing of the image graphic.\n\t *\n\t * @return Action menu item with behavior on-click.\n\t */\n\t@Nonnull\n\tpublic static ActionMenuItem action(@Nonnull String textKey, @Nullable String imagePath,\n\t\t\t\t\t\t\t\t\t\t@Nonnull Runnable runnable, boolean antialias) {\n\t\tNode graphic = imagePath == null ? null :\n\t\t\t\tantialias ? Icons.getScaledIconView(imagePath) : Icons.getIconView(imagePath);\n\t\treturn action(textKey, graphic, runnable);\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t * @param icon\n\t * \t\tIkonli icon for the menu graphic.\n\t * @param runnable\n\t * \t\tAction to run on click.\n\t *\n\t * @return Action menu item with behavior on-click.\n\t */\n\t@Nonnull\n\tpublic static ActionMenuItem action(@Nonnull String textKey, @Nonnull Ikon icon, @Nonnull Runnable runnable) {\n\t\treturn action(textKey, icon, null, runnable);\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t * @param icon\n\t * \t\tIkonli icon for the menu graphic.\n\t * @param runnable\n\t * \t\tAction to run on click.\n\t *\n\t * @return Action menu item with behavior on-click.\n\t */\n\t@Nonnull\n\tpublic static ActionMenuItem action(@Nonnull String textKey, @Nonnull Ikon icon, @Nullable Color color, @Nonnull Runnable runnable) {\n\t\tFontIconView graphic = color == null ?\n\t\t\t\tnew FontIconView(icon) :\n\t\t\t\tnew FontIconView(icon, IconView.DEFAULT_ICON_SIZE, color);\n\t\treturn action(textKey, graphic, runnable);\n\t}\n\n\t/**\n\t * @param textKey\n\t * \t\tTranslation key.\n\t * @param graphic\n\t * \t\tMenu graphic.\n\t * @param runnable\n\t * \t\tAction to run on click.\n\t *\n\t * @return Action menu item with behavior on-click.\n\t */\n\t@Nonnull\n\tpublic static ActionMenuItem action(@Nonnull String textKey, @Nullable Node graphic, @Nonnull Runnable runnable) {\n\t\treturn action(Lang.getBinding(textKey), graphic, runnable).withId(textKey);\n\t}\n\n\t/**\n\t * @param textBinding\n\t * \t\tMenu text binding.\n\t * @param graphic\n\t * \t\tMenu graphic.\n\t * @param runnable\n\t * \t\tAction to run on click.\n\t *\n\t * @return Action menu item with behavior on-click.\n\t */\n\t@Nonnull\n\tpublic static ActionMenuItem action(@Nonnull StringBinding textBinding, @Nullable Node graphic, @Nonnull Runnable runnable) {\n\t\treturn new ActionMenuItem(textBinding, graphic, runnable);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tMenu item text.\n\t * @param imagePath\n\t * \t\tPath to image for menu graphic.\n\t * @param runnable\n\t * \t\tAction to run on click.\n\t *\n\t * @return Action menu item with behavior on-click.\n\t */\n\t@Nonnull\n\tpublic static ActionMenuItem actionLiteral(@Nullable String text, @Nullable String imagePath, @Nonnull Runnable runnable) {\n\t\tNode graphic = imagePath == null ? null : Icons.getIconView(imagePath);\n\t\treturn new ActionMenuItem(text, graphic, runnable);\n\t}\n\n\t/**\n\t * @param text\n\t * \t\tMenu item text.\n\t * @param icon\n\t * \t\tIkonli icon for the menu graphic.\n\t * @param runnable\n\t * \t\tAction to run on click.\n\t *\n\t * @return Action menu item with behavior on-click.\n\t */\n\t@Nonnull\n\tpublic static ActionMenuItem actionLiteral(@Nullable String text, @Nonnull Ikon icon, @Nonnull Runnable runnable) {\n\t\tFontIconView graphic = new FontIconView(icon);\n\t\treturn new ActionMenuItem(text, graphic, runnable);\n\t}\n\n\t/**\n\t * @return New menu separator.\n\t */\n\t@Nonnull\n\tpublic static SeparatorMenuItem separator() {\n\t\treturn new SeparatorMenuItem();\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/NodeEvents.java",
    "content": "package software.coley.recaf.util;\n\nimport it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;\nimport it.unimi.dsi.fastutil.ints.Int2ObjectMap;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.event.Event;\nimport javafx.event.EventHandler;\nimport javafx.scene.Node;\nimport javafx.scene.input.KeyCode;\nimport javafx.scene.input.KeyEvent;\nimport javafx.scene.input.MouseEvent;\nimport software.coley.collections.Unchecked;\n\nimport java.util.function.BiConsumer;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\n\n/**\n * JavaFX node event handler utils.\n *\n * @author Matt Coley\n * @author xDark\n */\npublic class NodeEvents {\n\tprivate static final Int2ObjectMap<KeyCode> charToKeycode = new Int2ObjectArrayMap<>();\n\n\tstatic {\n\t\tfor (KeyCode code : KeyCode.values()) {\n\t\t\tString charStr = code.getChar();\n\t\t\tif (!charStr.isEmpty()) charToKeycode.put(charStr.charAt(0), code);\n\t\t}\n\t}\n\n\tprivate NodeEvents() {\n\t}\n\n\t/**\n\t * @param c\n\t * \t\tChar to look up.\n\t *\n\t * @return Keycode for char. May be {@code null} for unsupported characters.\n\t */\n\t@Nullable\n\tpublic static KeyCode getKeycode(char c) {\n\t\treturn charToKeycode.get(c);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to add to.\n\t * @param handler\n\t * \t\tHandler to add.\n\t */\n\tpublic static void addMousePressHandler(@Nonnull Node node, @Nonnull EventHandler<MouseEvent> handler) {\n\t\tFunction<Node, EventHandler<? super MouseEvent>> original = Node::getOnMousePressed;\n\t\taddHandler(node, handler, Unchecked.cast(original), Node::setOnMousePressed);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to add to.\n\t * @param handler\n\t * \t\tHandler to add.\n\t */\n\tpublic static void addMouseClickHandler(@Nonnull Node node, @Nonnull EventHandler<MouseEvent> handler) {\n\t\tFunction<Node, EventHandler<? super MouseEvent>> original = Node::getOnMouseClicked;\n\t\taddHandler(node, handler, Unchecked.cast(original), Node::setOnMouseClicked);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to add to.\n\t * @param handler\n\t * \t\tHandler to add.\n\t */\n\tpublic static void addMouseReleaseHandler(@Nonnull Node node, @Nonnull EventHandler<MouseEvent> handler) {\n\t\tFunction<Node, EventHandler<? super MouseEvent>> original = Node::getOnMouseReleased;\n\t\taddHandler(node, handler, Unchecked.cast(original), Node::setOnMouseReleased);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to add to.\n\t * @param handler\n\t * \t\tHandler to add.\n\t */\n\tpublic static void addMouseEnterHandler(@Nonnull Node node, @Nonnull EventHandler<MouseEvent> handler) {\n\t\tFunction<Node, EventHandler<? super MouseEvent>> original = Node::getOnMouseEntered;\n\t\taddHandler(node, handler, Unchecked.cast(original), Node::setOnMouseEntered);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to add to.\n\t * @param handler\n\t * \t\tHandler to add.\n\t */\n\tpublic static void addMouseExitHandler(@Nonnull Node node, @Nonnull EventHandler<MouseEvent> handler) {\n\t\tFunction<Node, EventHandler<? super MouseEvent>> original = Node::getOnMouseExited;\n\t\taddHandler(node, handler, Unchecked.cast(original), Node::setOnMouseExited);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to add to.\n\t * @param handler\n\t * \t\tHandler to add.\n\t */\n\tpublic static void addMouseMoveHandler(@Nonnull Node node, @Nonnull EventHandler<MouseEvent> handler) {\n\t\tFunction<Node, EventHandler<? super MouseEvent>> original = Node::getOnMouseMoved;\n\t\taddHandler(node, handler, Unchecked.cast(original), Node::setOnMouseMoved);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to add to.\n\t * @param handler\n\t * \t\tHandler to add.\n\t */\n\tpublic static void addMouseDraggedHandler(@Nonnull Node node, @Nonnull EventHandler<MouseEvent> handler) {\n\t\tFunction<Node, EventHandler<? super MouseEvent>> original = Node::getOnMouseDragged;\n\t\taddHandler(node, handler, Unchecked.cast(original), Node::setOnMouseDragged);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to add to.\n\t * @param handler\n\t * \t\tHandler to add.\n\t */\n\tpublic static void addKeyPressHandler(@Nonnull Node node, @Nonnull EventHandler<KeyEvent> handler) {\n\t\tFunction<Node, EventHandler<? super KeyEvent>> original = Node::getOnKeyPressed;\n\t\taddHandler(node, handler, Unchecked.cast(original), Node::setOnKeyPressed);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to remove from.\n\t * @param handler\n\t * \t\tHandler to remove.\n\t */\n\tpublic static void removeKeyPressHandler(@Nonnull Node node, @Nonnull EventHandler<KeyEvent> handler) {\n\t\tFunction<Node, EventHandler<? super KeyEvent>> original = Node::getOnKeyPressed;\n\t\tremoveHandler(node, handler, Unchecked.cast(original), Node::setOnKeyPressed);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to add to.\n\t * @param handler\n\t * \t\tHandler to add.\n\t */\n\tpublic static void addKeyReleaseHandler(@Nonnull Node node, @Nonnull EventHandler<KeyEvent> handler) {\n\t\tFunction<Node, EventHandler<? super KeyEvent>> original = Node::getOnKeyReleased;\n\t\taddHandler(node, handler, Unchecked.cast(original), Node::setOnKeyReleased);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to remove from.\n\t * @param handler\n\t * \t\tHandler to remove.\n\t */\n\tpublic static void removeKeyReleaseHandler(@Nonnull Node node, @Nonnull EventHandler<KeyEvent> handler) {\n\t\tFunction<Node, EventHandler<? super KeyEvent>> original = Node::getOnKeyPressed;\n\t\tremoveHandler(node, handler, Unchecked.cast(original), Node::setOnKeyPressed);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to add to.\n\t * @param handler\n\t * \t\tHandler to add.\n\t */\n\tpublic static void addKeyTypedHandler(@Nonnull Node node, @Nonnull EventHandler<KeyEvent> handler) {\n\t\tFunction<Node, EventHandler<? super KeyEvent>> original = Node::getOnKeyTyped;\n\t\taddHandler(node, handler, Unchecked.cast(original), Node::setOnKeyTyped);\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to remove from.\n\t * @param handler\n\t * \t\tHandler to remove.\n\t */\n\tpublic static void removeKeyTypedHandler(@Nonnull Node node, @Nonnull EventHandler<KeyEvent> handler) {\n\t\tFunction<Node, EventHandler<? super KeyEvent>> original = Node::getOnKeyTyped;\n\t\tremoveHandler(node, handler, Unchecked.cast(original), Node::setOnKeyTyped);\n\t}\n\n\tprivate static <T extends Event> void addHandler(@Nonnull Node node, @Nonnull EventHandler<T> handler,\n\t                                                 @Nonnull Function<Node, EventHandler<T>> handlerGetter,\n\t                                                 @Nonnull BiConsumer<Node, EventHandler<T>> handlerSetter) {\n\t\tEventHandler<T> oldHandler = handlerGetter.apply(node);\n\t\thandlerSetter.accept(node, new SplittingHandler<>(handler, oldHandler));\n\t}\n\n\tprivate static <T extends Event> void removeHandler(@Nonnull Node node, @Nonnull EventHandler<T> handler,\n\t                                                    @Nonnull Function<Node, EventHandler<T>> handlerGetter,\n\t                                                    @Nonnull BiConsumer<Node, EventHandler<T>> handlerSetter) {\n\t\tEventHandler<T> currentHandler = Unchecked.cast(handlerGetter.apply(node));\n\t\tif (currentHandler instanceof SplittingHandler<T> splittingHandler) {\n\t\t\tif (splittingHandler.primary == handler)\n\t\t\t\thandlerSetter.accept(node, splittingHandler.secondary);\n\t\t\telse\n\t\t\t\tsplittingHandler.remove(handler);\n\t\t} else if (currentHandler == handler)\n\t\t\thandlerSetter.accept(node, null);\n\t}\n\n\t/**\n\t * Runs an action on the given observable's value if it is present <i>(non-null)</i>,\n\t * or once when it changes to another value.\n\t *\n\t * @param value\n\t * \t\tValue to observe.\n\t * @param action\n\t * \t\tAction to run on the value.\n\t * @param <T>\n\t * \t\tValue type.\n\t */\n\tpublic static <T> void runOnceIfPresentOrOnChange(@Nonnull ObservableValue<T> value, @Nonnull Consumer<T> action) {\n\t\tT current = value.getValue();\n\t\tif (current != null) action.accept(current);\n\t\telse runOnceOnChange(value, action);\n\t}\n\n\t/**\n\t * Runs an action on the given observable once when it changes.\n\t *\n\t * @param value\n\t * \t\tValue to observe.\n\t * @param action\n\t * \t\tAction to run on new value.\n\t * @param <T>\n\t * \t\tValue type.\n\t */\n\tpublic static <T> void runOnceOnChange(@Nonnull ObservableValue<T> value, @Nonnull Consumer<T> action) {\n\t\tvalue.addListener(new ChangeListener<>() {\n\t\t\t@Override\n\t\t\tpublic void changed(ObservableValue<? extends T> observableValue, T old, T current) {\n\t\t\t\tvalue.removeListener(this);\n\t\t\t\taction.accept(current);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Registers an event listener and removes it,\n\t * as soon as {@link RemovalChangeListener} returns {@code true}.\n\t *\n\t * @param value\n\t * \t\tValue to register listener for.\n\t * @param listener\n\t * \t\tListener to register.\n\t * @param <T>\n\t * \t\tValue type.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static <T> void dispatchAndRemoveIf(@Nonnull ObservableValue<T> value, @Nonnull RemovalChangeListener<T> listener) {\n\t\tChangeListener<? super T>[] handle = new ChangeListener[1];\n\t\thandle[0] = (observable, oldValue, newValue) -> {\n\t\t\tif (listener.changed(observable, oldValue, newValue)) {\n\t\t\t\tFxThreadUtil.run(() -> observable.removeListener(handle[0]));\n\t\t\t}\n\t\t};\n\t\tvalue.addListener(handle[0]);\n\t}\n\n\t/**\n\t * Registers an event listener and removes it,\n\t * as soon as {@link RemovalChangeListener} returns {@code true}.\n\t *\n\t * @param value\n\t * \t\tValue to register listener for.\n\t * @param test\n\t * \t\tTest function for a new value.\n\t * @param <T>\n\t * \t\tValue type.\n\t */\n\tpublic static <T> void dispatchAndRemoveIf(@Nonnull ObservableValue<T> value, @Nonnull Predicate<? super T> test) {\n\t\tdispatchAndRemoveIf(value, (observable, oldValue, newValue) -> test.test(newValue));\n\t}\n\n\t/**\n\t * Value change listener. Key difference from {@link ChangeListener} is the return value of\n\t * {@link #changed(ObservableValue, Object, Object)}.\n\t *\n\t * @param <T>\n\t * \t\tValue type.\n\t *\n\t * @author xDark\n\t */\n\tpublic interface RemovalChangeListener<T> {\n\t\t/**\n\t\t * Called when the value of an {@link ObservableValue} changes.\n\t\t *\n\t\t * @param observable\n\t\t * \t\tThe {@code ObservableValue} which value changed.\n\t\t * @param oldValue\n\t\t * \t\tThe old value.\n\t\t * @param newValue\n\t\t * \t\tThe new value.\n\t\t *\n\t\t * @return {@code true} to remove the listener after this change.\n\t\t * {@code false} to keep the listener after this change.\n\t\t */\n\t\tboolean changed(ObservableValue<? extends T> observable, T oldValue, T newValue);\n\t}\n\n\t/**\n\t * Splitting handler to simplify calls like {@link #addKeyPressHandler(Node, EventHandler)} and\n\t * {@link #removeKeyPressHandler(Node, EventHandler)}.\n\t *\n\t * @author Matt Coley\n\t */\n\tprivate static class SplittingHandler<T extends Event> implements EventHandler<T> {\n\t\tprivate final EventHandler<T> primary;\n\t\tprivate EventHandler<T> secondary;\n\n\t\t/**\n\t\t * @param primary\n\t\t * \t\tHandler to invoke.\n\t\t * @param secondary\n\t\t * \t\tNext in the chain to invoke.\n\t\t */\n\t\tprivate SplittingHandler(@Nonnull EventHandler<T> primary,\n\t\t                         @Nullable EventHandler<T> secondary) {\n\t\t\tthis.primary = primary;\n\t\t\tthis.secondary = secondary;\n\t\t}\n\n\t\t@Override\n\t\tpublic void handle(T event) {\n\t\t\tif (secondary != null)\n\t\t\t\tsecondary.handle(event);\n\t\t\tprimary.handle(event);\n\t\t}\n\n\t\t/**\n\t\t * @param handler\n\t\t * \t\tHandler to remove from the wrapper chain.\n\t\t */\n\t\t@SuppressWarnings({\"rawtypes\", \"unchecked\"})\n\t\tpublic void remove(EventHandler<T> handler) {\n\t\t\tif (secondary == handler)\n\t\t\t\tsecondary = null;\n\t\t\telse if (secondary instanceof SplittingHandler nextWrapper)\n\t\t\t\tnextWrapper.remove(handler);\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/RecafURLStreamHandlerProvider.java",
    "content": "package software.coley.recaf.util;\n\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.cdi.EagerInitialization;\nimport software.coley.recaf.cdi.InitializationStage;\nimport software.coley.recaf.info.ClassInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.workspace.model.Workspace;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.*;\nimport java.net.spi.URLStreamHandlerProvider;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * A hack to allow URI's to point into the current workspace.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\n@EagerInitialization(InitializationStage.AFTER_UI_INIT)\npublic class RecafURLStreamHandlerProvider extends URLStreamHandlerProvider {\n\tprivate static final Logger logger = Logging.get(RecafURLStreamHandlerProvider.class);\n\tprivate static final String PREFIX = \"sun.net.www.protocol\";\n\tpublic static boolean installed ;\n\tpublic static final String recafClass = \"rclass\";\n\tpublic static final String recafFile = \"rfile\";\n\tprivate final WorkspaceManager workspaceManager;\n\n\t@Inject\n\tpublic RecafURLStreamHandlerProvider(@Nonnull WorkspaceManager workspaceManager) {\n\t\tthis.workspaceManager = workspaceManager;\n\t\tURL.setURLStreamHandlerFactory(this);\n\t\tlogger.trace(\"Installed Recaf URL stream handler\");\n\t\tinstalled = true;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tClass name.\n\t *\n\t * @return URI input string in the Recaf scheme.\n\t */\n\t@Nonnull\n\tpublic static String classUri(@Nonnull String name) {\n\t\tString path = URLEncoder.encode(name, StandardCharsets.UTF_8);\n\t\treturn recafClass + \":///\" + path;\n\t}\n\n\t/**\n\t * @param name\n\t * \t\tFile name.\n\t *\n\t * @return URI input string in the Recaf scheme.\n\t */\n\t@Nonnull\n\tpublic static String fileUri(@Nonnull String name) {\n\t\tString path = URLEncoder.encode(name, StandardCharsets.UTF_8);\n\t\treturn recafFile + \":///\" + path;\n\t}\n\n\t@Override\n\t@SuppressWarnings(\"deprecation\")\n\tpublic URLStreamHandler createURLStreamHandler(String protocol) {\n\t\tif (protocol.equals(recafClass) || protocol.equals(recafFile))\n\t\t\treturn new URLStreamHandler() {\n\t\t\t\t@Override\n\t\t\t\tprotected URLConnection openConnection(URL url) {\n\t\t\t\t\treturn new ConnectionImpl(url);\n\t\t\t\t}\n\t\t\t};\n\t\t// Default implementation (See implementation in URL class)\n\t\tString name = PREFIX + \".\" + protocol + \".Handler\";\n\t\ttry {\n\t\t\treturn (URLStreamHandler) Class.forName(name).newInstance();\n\t\t} catch (Exception ignored) {\n\t\t\t// Ignored\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * Connection impl to actually pull from the current workspace.\n\t */\n\tprivate class ConnectionImpl extends URLConnection {\n\t\tprivate byte[] content;\n\n\t\tpublic ConnectionImpl(URL url) {\n\t\t\tsuper(url);\n\t\t}\n\n\t\t@Override\n\t\tpublic void connect() {\n\t\t\t// no-op\n\t\t}\n\n\t\t@Override\n\t\tpublic InputStream getInputStream() throws IOException {\n\t\t\tif (content == null)\n\t\t\t\tloadContent();\n\t\t\treturn new ByteArrayInputStream(content);\n\t\t}\n\n\n\t\t@Override\n\t\tpublic String getContentType() {\n\t\t\treturn guessContentTypeFromName(url.getFile());\n\t\t}\n\n\t\t@Override\n\t\tpublic long getContentLengthLong() {\n\t\t\treturn getContentLength();\n\t\t}\n\n\t\t@Override\n\t\tpublic int getContentLength() {\n\t\t\tif (content == null)\n\t\t\t\ttry {\n\t\t\t\t\tloadContent();\n\t\t\t\t} catch (IOException ex) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\treturn content.length;\n\t\t}\n\n\t\tprivate void loadContent() throws IOException {\n\t\t\t// Validate state\n\t\t\tif (!workspaceManager.hasCurrentWorkspace())\n\t\t\t\tthrow new IOException(\"No workspace currently open!\");\n\n\t\t\t// Transform path\n\t\t\tString path = getURL().getPath();\n\t\t\tif (path.charAt(0) == '/')\n\t\t\t\tpath = path.substring(1);\n\t\t\tpath = URLDecoder.decode(path, StandardCharsets.UTF_8);\n\n\t\t\t// Handle protocol implementations\n\t\t\tWorkspace workspace = workspaceManager.getCurrent();\n\t\t\tString protocol = getURL().getProtocol();\n\t\t\tswitch (protocol) {\n\t\t\t\tcase recafClass:\n\t\t\t\t\tClassPathNode classPath = workspace.findClass(path);\n\t\t\t\t\tif (classPath == null)\n\t\t\t\t\t\tthrow new IOException(\"No class in current workspace: \" + path);\n\t\t\t\t\tClassInfo classInfo = classPath.getValue();\n\t\t\t\t\tcontent = classInfo.asJvmClass().getBytecode();\n\t\t\t\t\tbreak;\n\t\t\t\tcase recafFile:\n\t\t\t\t\tFilePathNode filePath = workspace.findFile(path);\n\t\t\t\t\tif (filePath == null)\n\t\t\t\t\t\tthrow new IOException(\"No file in current workspace: \" + path);\n\t\t\t\t\tFileInfo fileInfo = filePath.getValue();\n\t\t\t\t\tcontent = fileInfo.getRawContent();\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new IOException(\"Unknown protocol: \" + protocol);\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/SVG.java",
    "content": "package software.coley.recaf.util;\n\nimport com.github.weisj.jsvg.SVGDocument;\nimport com.github.weisj.jsvg.parser.LoaderContext;\nimport com.github.weisj.jsvg.parser.SVGLoader;\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.scene.Node;\nimport javafx.scene.image.ImageView;\nimport software.coley.recaf.ui.control.IconView;\n\nimport java.awt.Graphics2D;\nimport java.awt.RenderingHints;\nimport java.awt.image.BufferedImage;\nimport java.io.ByteArrayInputStream;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Graphic utilities for SVG.\n *\n * @author Matt Coley\n */\npublic class SVG {\n\tprivate static final Map<RenderingHints.Key, Object> STROKE_HINTS = Map.of(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);\n\tprivate static final SVGDocument EMPTY_DOCUMENT;\n\tpublic static final String EXCEPTION_BREAKPOINT = \"icons/exception-breakpoint.svg\";\n\tpublic static final String REF_READ = \"icons/read-access.svg\";\n\tpublic static final String REF_WRITE = \"icons/write-access.svg\";\n\tpublic static final String TYPE_CONVERSION = \"icons/type-conversion.svg\";\n\tpublic static final String METHOD_IMPLEMENTING = \"icons/implementingMethod.svg\";\n\tpublic static final String METHOD_IMPLEMENTED = \"icons/implementedMethod.svg\";\n\tpublic static final String METHOD_OVERRIDDEN = \"icons/overriddenMethod.svg\";\n\tpublic static final String METHOD_OVERRIDING = \"icons/overridingMethod.svg\";\n\tprivate static final Map<String, SVGDocument> DOCUMENT_CACHE = new ConcurrentHashMap<>();\n\n\tstatic {\n\t\tEMPTY_DOCUMENT = read(new ByteArrayInputStream((\"<svg width=\\\"0\\\" height=\\\"0\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"></svg>\").getBytes(StandardCharsets.UTF_8)));\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link SVG}.\n\t *\n\t * @return SVG node of file contents sized to an icon.\n\t */\n\t@Nonnull\n\tpublic static Node ofIconFile(@Nonnull String path) {\n\t\tNode graphic = ofFile(path, IconView.DEFAULT_ICON_SIZE, STROKE_HINTS);\n\t\tgraphic.prefWidth(IconView.DEFAULT_ICON_SIZE);\n\t\treturn graphic;\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link SVG}.\n\t * @param size\n\t * \t\tDesired image width/height.\n\t *\n\t * @return SVG node of file contents.\n\t */\n\t@Nonnull\n\tpublic static Node ofFile(@Nonnull String path, int size) {\n\t\treturn ofFile(path, size, size);\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link SVG}.\n\t * @param size\n\t * \t\tDesired image width/height.\n\t * @param renderingHints\n\t * \t\tKV pairs for {@link RenderingHints} entries.\n\t *\n\t * @return SVG node of file contents.\n\t */\n\t@Nonnull\n\tpublic static Node ofFile(@Nonnull String path, int size,\n\t                          @Nullable Map<RenderingHints.Key, Object> renderingHints) {\n\t\treturn ofFile(path, size, size, renderingHints);\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link SVG}.\n\t * @param width\n\t * \t\tDesired image width.\n\t * @param height\n\t * \t\tDesired image height.\n\t *\n\t * @return SVG node of file contents.\n\t */\n\t@Nonnull\n\tpublic static Node ofFile(@Nonnull String path, int width, int height) {\n\t\treturn ofFile(path, width, height, null);\n\t}\n\n\t/**\n\t * @param path\n\t * \t\tPath to local image. See constants defined in {@link SVG}.\n\t * @param width\n\t * \t\tDesired image width.\n\t * @param height\n\t * \t\tDesired image height.\n\t * @param renderingHints\n\t * \t\tKV pairs for {@link RenderingHints} entries.\n\t *\n\t * @return SVG node of file contents.\n\t */\n\t@Nonnull\n\tpublic static Node ofFile(@Nonnull String path, int width, int height,\n\t                          @Nullable Map<RenderingHints.Key, Object> renderingHints) {\n\t\tSVGDocument document = DOCUMENT_CACHE.computeIfAbsent(path, SVG::read);\n\n\t\t// Render to image\n\t\tBufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);\n\t\tGraphics2D g = image.createGraphics();\n\t\tif (renderingHints != null) // Apply user-provided rendering hints\n\t\t\trenderingHints.forEach(g::setRenderingHint);\n\t\tdocument.render(null, g);\n\t\tg.dispose();\n\n\t\t// Convert to FX compatible image\n\t\treturn new ImageView(Icons.convertToFxImage(image));\n\t}\n\n\t@Nonnull\n\tprivate static SVGDocument read(@Nonnull String path) {\n\t\ttry (InputStream is = ResourceUtil.resource(path)) {\n\t\t\treturn read(is);\n\t\t} catch (Exception ex) {\n\t\t\treturn EMPTY_DOCUMENT;\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate static SVGDocument read(@Nullable InputStream is) {\n\t\tif (is == null)\n\t\t\tthrow new IllegalStateException(\"No input stream for SVG input content\");\n\t\t// In case this bugs out again when building with Java 24+, just use reflection to call 'load'\n\t\t// See: https://github.com/Col-E/Recaf/issues/933\n\t\tSVGDocument document = new SVGLoader().load(is, null, LoaderContext.createDefault());\n\t\treturn Objects.requireNonNull(document, \"Load failed to yield SVG document instance\");\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/SceneUtils.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.annotation.Nullable;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.scene.Node;\nimport javafx.scene.Parent;\nimport javafx.scene.Scene;\nimport javafx.stage.Stage;\nimport javafx.stage.Window;\n\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\n\n/**\n * Scene utilities.\n *\n * @author Matt Coley\n */\npublic class SceneUtils {\n\tprivate SceneUtils() {}\n\n\t/**\n\t * @param scene\n\t * \t\tScene to bring to front/focus.\n\t */\n\tpublic static void focus(@Nullable Scene scene) {\n\t\tif (scene == null)\n\t\t\treturn;\n\n\t\tWindow window = scene.getWindow();\n\t\tif (window.isFocused()) return;\n\n\t\tif (window instanceof Stage stage) {\n\t\t\t// If minified, unminify it.\n\t\t\tstage.setIconified(false);\n\t\t\tstage.show();\n\n\t\t\t// The method 'stage.toFront()' does not work as you'd expect so this hack is how we\n\t\t\t// force the window to the front.\n\t\t\tstage.setAlwaysOnTop(true);\n\t\t\tstage.setAlwaysOnTop(false);\n\t\t}\n\t\twindow.requestFocus();\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to search hierarchy of.\n\t * @param parentType\n\t * \t\tParent type to check for.\n\t * @param <T>\n\t * \t\tParent type.\n\t *\n\t * @return Matching parent node in hierarchy, or {@code null} if nothing matched.\n\t */\n\t@Nullable\n\t@SuppressWarnings(\"unchecked\")\n\tpublic static <T extends Node> T getParentOfType(@Nonnull Node node, @Nonnull Class<T> parentType) {\n\t\tParent parent = node.getParent();\n\t\twhile (parent != null) {\n\t\t\tif (parent.getClass().isAssignableFrom(parentType)) return (T) parent;\n\t\t\tparent = parent.getParent();\n\t\t}\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to search hierarchy of.\n\t * @param parentType\n\t * \t\tParent type to check for.\n\t * @param <T>\n\t * \t\tParent type.\n\t *\n\t * @return Future containing the matching parent node in hierarchy, or {@code null} if nothing matched.\n\t */\n\t@Nonnull\n\tpublic static <T extends Node> CompletableFuture<T> getParentOfTypeLater(@Nonnull Node node, @Nonnull Class<T> parentType) {\n\t\treturn whenAddedToSceneMap(node, (n) -> getParentOfType(n, parentType));\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to initiate query with.\n\t * @param function\n\t * \t\tFunction taking in the node and yielding some value. To be run when the node has a {@link Scene} associated with it.\n\t * @param <T>\n\t * \t\tFunction return type.\n\t * @param <N>\n\t * \t\tNode type.\n\t *\n\t * @return Future of function lookup.\n\t */\n\t@Nonnull\n\tpublic static <T, N extends Node> CompletableFuture<T> whenAddedToSceneMap(@Nonnull N node, @Nonnull Function<N, T> function) {\n\t\t// If added to the UI, immediately look up value.\n\t\tif (node.getScene() != null) return CompletableFuture.completedFuture(function.apply(node));\n\n\t\t// When there is no scene it is not added to the UI yet.\n\t\t// We want to wait for it to be added before calling the function.\n\t\tCompletableFuture<T> future = new CompletableFuture<>();\n\t\tnode.sceneProperty().addListener(new ChangeListener<>() {\n\t\t\t@Override\n\t\t\tpublic void changed(ObservableValue<? extends Scene> observable, Scene prior, Scene current) {\n\t\t\t\tnode.sceneProperty().removeListener(this);\n\t\t\t\tfuture.complete(function.apply(node));\n\t\t\t}\n\t\t});\n\t\treturn future;\n\t}\n\n\t/**\n\t * @param node\n\t * \t\tNode to initiate query with.\n\t * @param consumer\n\t * \t\tConsumer taking in the node. To be run when the node has a {@link Scene} associated with it.\n\t * @param <N>\n\t * \t\tNode type.\n\t */\n\tpublic static <N extends Node> void whenAddedToSceneConsume(@Nonnull N node, @Nonnull Consumer<N> consumer) {\n\t\t// If added to the UI, immediately call the consumer.\n\t\tif (node.getScene() != null) {\n\t\t\tconsumer.accept(node);\n\t\t\treturn;\n\t\t}\n\n\t\t// When there is no scene it is not added to the UI yet.\n\t\t// We want to wait for it to be added before calling the consumer.\n\t\tnode.sceneProperty().addListener(new ChangeListener<>() {\n\t\t\t@Override\n\t\t\tpublic void changed(ObservableValue<? extends Scene> observable, Scene prior, Scene current) {\n\t\t\t\tnode.sceneProperty().removeListener(this);\n\t\t\t\tconsumer.accept(node);\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/SynchronizedSimpleStringProperty.java",
    "content": "package software.coley.recaf.util;\n\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.property.Property;\nimport javafx.beans.property.SimpleStringProperty;\nimport javafx.beans.value.ChangeListener;\nimport javafx.beans.value.ObservableValue;\nimport javafx.util.StringConverter;\n\nimport java.text.Format;\n\n/**\n * Synchronized implementation of {@link SimpleStringProperty}.\n *\n * @author xDark\n */\npublic class SynchronizedSimpleStringProperty extends SimpleStringProperty {\n\t/**\n\t * @param initialValue\n\t * \t\tInitial value.\n\t */\n\tpublic SynchronizedSimpleStringProperty(String initialValue) {\n\t\tsuper(initialValue);\n\t}\n\n\t@Override\n\tpublic synchronized void addListener(ChangeListener<? super String> listener) {\n\t\tsuper.addListener(listener);\n\t}\n\n\t@Override\n\tpublic synchronized void addListener(InvalidationListener listener) {\n\t\tsuper.addListener(listener);\n\t}\n\n\t@Override\n\tpublic synchronized void removeListener(ChangeListener<? super String> listener) {\n\t\tsuper.removeListener(listener);\n\t}\n\n\t@Override\n\tpublic synchronized void removeListener(InvalidationListener listener) {\n\t\tsuper.removeListener(listener);\n\t}\n\n\t@Override\n\tpublic synchronized void bind(ObservableValue<? extends String> newObservable) {\n\t\tsuper.bind(newObservable);\n\t}\n\n\t@Override\n\tpublic synchronized void bindBidirectional(Property<String> other) {\n\t\tsuper.bindBidirectional(other);\n\t}\n\n\t@Override\n\tpublic synchronized void bindBidirectional(Property<?> other, Format format) {\n\t\tsuper.bindBidirectional(other, format);\n\t}\n\n\t@Override\n\tprotected synchronized void invalidated() {\n\t\tsuper.invalidated();\n\t}\n\n\t@Override\n\tpublic synchronized String get() {\n\t\treturn super.get();\n\t}\n\n\t@Override\n\tpublic synchronized void set(String newValue) {\n\t\tsuper.set(newValue);\n\t}\n\n\t@Override\n\tpublic synchronized void unbind() {\n\t\tsuper.unbind();\n\t}\n\n\t@Override\n\tpublic synchronized <T> void bindBidirectional(Property<T> other, StringConverter<T> converter) {\n\t\tsuper.bindBidirectional(other, converter);\n\t}\n\n\t@Override\n\tpublic synchronized void unbindBidirectional(Property<String> other) {\n\t\tsuper.unbindBidirectional(other);\n\t}\n\n\t@Override\n\tpublic synchronized void unbindBidirectional(Object other) {\n\t\tsuper.unbindBidirectional(other);\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/SynchronizedStringBinding.java",
    "content": "package software.coley.recaf.util;\n\nimport javafx.beans.InvalidationListener;\nimport javafx.beans.binding.StringBinding;\nimport javafx.beans.value.ChangeListener;\n\n/**\n * Synchronized implementation of {@link StringBinding}.\n *\n * @author xDark\n */\npublic abstract class SynchronizedStringBinding extends StringBinding {\n\t@Override\n\tpublic synchronized String getValue() {\n\t\treturn super.getValue();\n\t}\n\n\t@Override\n\tprotected synchronized void onInvalidating() {\n\t\tsuper.onInvalidating();\n\t}\n\n\t@Override\n\tpublic synchronized void addListener(ChangeListener<? super String> listener) {\n\t\tsuper.addListener(listener);\n\t}\n\n\t@Override\n\tpublic synchronized void addListener(InvalidationListener listener) {\n\t\tsuper.addListener(listener);\n\t}\n\n\t@Override\n\tpublic synchronized void removeListener(ChangeListener<? super String> listener) {\n\t\tsuper.removeListener(listener);\n\t}\n\n\t@Override\n\tpublic synchronized void removeListener(InvalidationListener listener) {\n\t\tsuper.removeListener(listener);\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/TextDisplayUtil.java",
    "content": "package software.coley.recaf.util;\n\n/**\n * Combines {@link EscapeUtil} and {@link StringUtil}.\n *\n * @author Matt Coley\n */\npublic class TextDisplayUtil {\n\t/**\n\t * @param string\n\t * \t\tInput.\n\t *\n\t * @return Output, shortened by path name, escaping common ascii values.\n\t */\n\tpublic static String shortenEscape(String string) {\n\t\tif (string == null)\n\t\t\treturn null;\n\t\treturn EscapeUtil.escapeStandardAndUnicodeWhitespace(StringUtil.shortenPath(string));\n\t}\n\n\t/**\n\t * @param string\n\t * \t\tInput.\n\t * @param limit\n\t * \t\tLimit length.\n\t *\n\t * @return Output, shortened by path name and then by config display length, escaping common ascii values.\n\t */\n\tpublic static String escapeLimit(String string, int limit) {\n\t\tif (string == null)\n\t\t\treturn null;\n\t\tString text = EscapeUtil.escapeStandardAndUnicodeWhitespace(string);\n\t\tint max = Math.min(text.length(), limit);\n\t\ttext = StringUtil.limit(text, \"...\", max);\n\t\treturn text;\n\t}\n\n\t/**\n\t * @param string\n\t * \t\tInput.\n\t * @param limit\n\t * \t\tLimit length.\n\t *\n\t * @return Output, shortened by path name and then by config display length, escaping common ascii values.\n\t */\n\tpublic static String shortenEscapeLimit(String string, int limit) {\n\t\tif (string == null)\n\t\t\treturn null;\n\t\tString text = shortenEscape(string);\n\t\tint max = Math.min(text.length(), limit);\n\t\ttext = StringUtil.limit(text, \"...\", max);\n\t\treturn text;\n\t}\n\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/ToStringConverter.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.util.StringConverter;\n\nimport java.util.function.Function;\n\n/**\n * A {@link StringConverter} supporting only the {@code T --> String} conversion.\n *\n * @param <T>\n * \t\tType to convert to String.\n *\n * @author Matt Coley\n */\npublic abstract class ToStringConverter<T> extends StringConverter<T> {\n\t/**\n\t * @param func\n\t * \t\tConversion func.\n\t * @param <T>\n\t * \t\tType to convert.\n\t *\n\t * @return Converter from function.\n\t */\n\t@Nonnull\n\tpublic static <T> ToStringConverter<T> from(@Nonnull Function<T, String> func) {\n\t\treturn new ToStringConverter<>() {\n\t\t\t@Override\n\t\t\tpublic String toString(T t) {\n\t\t\t\treturn func.apply(t);\n\t\t\t}\n\t\t};\n\t}\n\n\t@Override\n\tpublic T fromString(String s) {\n\t\tthrow new UnsupportedOperationException();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/util/Translatable.java",
    "content": "package software.coley.recaf.util;\n\nimport jakarta.annotation.Nonnull;\n\n/**\n * Functional interface returning\n * translation key.\n *\n * @author xDark\n */\npublic interface Translatable {\n\t/**\n\t * @return translation key.\n\t */\n\t@Nonnull\n\tString getTranslationKey();\n}"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/workspace/PathExportingManager.java",
    "content": "package software.coley.recaf.workspace;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport javafx.scene.control.Alert;\nimport javafx.scene.control.ButtonType;\nimport javafx.stage.DirectoryChooser;\nimport javafx.stage.FileChooser;\nimport javafx.stage.Stage;\nimport org.slf4j.Logger;\nimport software.coley.observables.ObservableString;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.Info;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.services.workspace.io.PathWorkspaceExportConsumer;\nimport software.coley.recaf.services.workspace.io.WorkspaceCompressType;\nimport software.coley.recaf.services.workspace.io.WorkspaceExportOptions;\nimport software.coley.recaf.services.workspace.io.WorkspaceExporter;\nimport software.coley.recaf.services.workspace.io.WorkspaceOutputType;\nimport software.coley.recaf.ui.config.ExportConfig;\nimport software.coley.recaf.ui.config.RecentFilesConfig;\nimport software.coley.recaf.util.DirectoryChooserBuilder;\nimport software.coley.recaf.util.ErrorDialogs;\nimport software.coley.recaf.util.FileChooserBuilder;\nimport software.coley.recaf.util.IOUtil;\nimport software.coley.recaf.util.Icons;\nimport software.coley.recaf.util.Lang;\nimport software.coley.recaf.util.StringUtil;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceDirectoryResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\n/**\n * Manager module handle exporting {@link Info} and {@link Workspace} instances to {@link Path}s.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class PathExportingManager {\n\tprivate static final Logger logger = Logging.get(PathExportingManager.class);\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final ExportConfig exportConfig;\n\tprivate final RecentFilesConfig recentFilesConfig;\n\n\t@Inject\n\tpublic PathExportingManager(WorkspaceManager workspaceManager,\n\t                            ExportConfig exportConfig,\n\t                            RecentFilesConfig recentFilesConfig) {\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.exportConfig = exportConfig;\n\t\tthis.recentFilesConfig = recentFilesConfig;\n\t}\n\n\t/**\n\t * Export the current workspace.\n\t */\n\tpublic void exportCurrent() {\n\t\t// Validate current workspace.\n\t\tif (!workspaceManager.hasCurrentWorkspace())\n\t\t\tthrow new IllegalStateException(\"Tried to export when no workspace was active!\");\n\n\t\t// And export it.\n\t\texport(workspaceManager.getCurrent());\n\t}\n\n\t/**\n\t * Export the given workspace.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to export.\n\t */\n\tpublic void export(@Nonnull Workspace workspace) {\n\t\texport(workspace, \"workspace\", true);\n\t}\n\n\t/**\n\t * Export the given workspace.\n\t *\n\t * @param workspace\n\t * \t\tWorkspace to export.\n\t * @param contentDescription\n\t * \t\tDescription string for what is within the workspace.\n\t * @param alertWhenEmpty\n\t *        {@code true} to warn users before exporting that no changes are present in the workspace.\n\t *        {@code false} to skip the warning.\n\t */\n\tpublic void export(@Nonnull Workspace workspace, @Nonnull String contentDescription, boolean alertWhenEmpty) {\n\t\tWorkspaceResource primaryResource = workspace.getPrimaryResource();\n\n\t\t// Check if the user hasn't made any changes. Plenty of people have not understood that their changes weren't\n\t\t// saved for one reason or another (the amount of people seeing a red flash thinking that is fine is crazy)\n\t\tif (alertWhenEmpty) {\n\t\t\tboolean noChangesFound = exportConfig.getWarnNoChanges().getValue() && primaryResource.bundleStreamRecursive()\n\t\t\t\t\t.allMatch(b -> b.getDirtyKeys().isEmpty());\n\t\t\tif (noChangesFound) {\n\t\t\t\tAlert alert = new Alert(Alert.AlertType.CONFIRMATION, Lang.get(\"dialog.file.nochanges\"), ButtonType.YES, ButtonType.NO);\n\t\t\t\talert.setTitle(Lang.get(\"dialog.title.nochanges\"));\n\t\t\t\tStage stage = (Stage) alert.getDialogPane().getScene().getWindow();\n\t\t\t\tstage.getIcons().add(Icons.getImage(Icons.LOGO));\n\t\t\t\tif (alert.showAndWait().orElse(ButtonType.NO) != ButtonType.YES)\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Prompt a path for the user to write to.\n\t\tObservableString lastWorkspaceExportDir = recentFilesConfig.getLastWorkspaceExportDirectory();\n\t\tFile lastExportDir = lastWorkspaceExportDir.unboxingMap(File::new);\n\t\tFile selectedPath;\n\t\tif (primaryResource instanceof WorkspaceDirectoryResource) {\n\t\t\tDirectoryChooser chooser = new DirectoryChooserBuilder()\n\t\t\t\t\t.setInitialDirectory(lastExportDir)\n\t\t\t\t\t.setTitle(Lang.get(\"dialog.file.export\"))\n\t\t\t\t\t.build();\n\t\t\tselectedPath = chooser.showDialog(null);\n\t\t} else {\n\t\t\tFileChooserBuilder builder = new FileChooserBuilder()\n\t\t\t\t\t.setInitialDirectory(lastExportDir)\n\t\t\t\t\t.setTitle(Lang.get(\"dialog.file.export\"));\n\n\t\t\tif (primaryResource instanceof WorkspaceFileResource primaryFileResource) {\n\t\t\t\tFileInfo backingFile = primaryFileResource.getFileInfo();\n\n\t\t\t\t// Use original file name as initial prompt name.\n\t\t\t\tString initialName = StringUtil.shortenPath(backingFile.getName());\n\t\t\t\tbuilder.setInitialFileName(initialName);\n\n\t\t\t\t// Mirror the original file type as an extension filter.\n\t\t\t\tString extension = IOUtil.getExtension(initialName);\n\t\t\t\tif (extension == null)\n\t\t\t\t\textension = IOUtil.getExtensionFromFileInfo(backingFile);\n\t\t\t\tif (extension != null)\n\t\t\t\t\tbuilder.setFileExtensionFilter(extension.toUpperCase(), \"*.\" + extension.toLowerCase());\n\t\t\t}\n\n\t\t\tFileChooser chooser = builder.build();\n\t\t\tselectedPath = chooser.showSaveDialog(null);\n\t\t}\n\n\t\t// Convert selected file to nio path, update last export directory.\n\t\tPath exportPath;\n\t\tif (selectedPath != null) {\n\t\t\tString parent = selectedPath.getParent();\n\t\t\tif (parent != null) lastWorkspaceExportDir.setValue(parent);\n\t\t\texportPath = selectedPath.toPath();\n\t\t} else {\n\t\t\t// Selected path is null, meaning user closed out of file chooser.\n\t\t\t// Cancel export.\n\t\t\treturn;\n\t\t}\n\n\t\t// Create export options from the resource type and export the workspace to the selected path.\n\t\tWorkspaceExporter exporter = createResourceExporter(primaryResource, exportPath);\n\t\ttry {\n\t\t\texporter.export(workspace);\n\t\t\tlogger.info(\"Exported \" + contentDescription + \" to path '{}'\", exportPath);\n\t\t} catch (IOException ex) {\n\t\t\tlogger.error(\"Failed to export \" + contentDescription + \" to path '{}'\", exportPath, ex);\n\t\t\tErrorDialogs.show(\n\t\t\t\t\tLang.getBinding(\"dialog.error.exportworkspace.title\"),\n\t\t\t\t\tLang.getBinding(\"dialog.error.exportworkspace.header\"),\n\t\t\t\t\tLang.getBinding(\"dialog.error.exportworkspace.content\"),\n\t\t\t\t\tex\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Export the given class.\n\t *\n\t * @param classInfo\n\t * \t\tClass to export.\n\t */\n\tpublic void export(@Nonnull JvmClassInfo classInfo) {\n\t\t// Prompt a path for the user to write to.\n\t\tObservableString lastClassExportDir = recentFilesConfig.getLastClassExportDirectory();\n\t\tFile lastExportDir = lastClassExportDir.unboxingMap(File::new);\n\t\tFileChooser chooser = new FileChooserBuilder()\n\t\t\t\t.setInitialFileName(StringUtil.shortenPath(classInfo.getName()) + \".class\")\n\t\t\t\t.setInitialDirectory(lastExportDir)\n\t\t\t\t.setFileExtensionFilter(\"Java Class\", \"*.class\")\n\t\t\t\t.setTitle(Lang.get(\"dialog.file.export\"))\n\t\t\t\t.build();\n\t\tFile selectedPath = chooser.showSaveDialog(null);\n\n\t\t// Selected path is null, meaning user closed out of file chooser.\n\t\t// Cancel export.\n\t\tif (selectedPath == null)\n\t\t\treturn;\n\n\t\t// Update last export dir for classes.\n\t\tlastClassExportDir.setValue(selectedPath.getParent());\n\n\t\t// Write to path.\n\t\ttry {\n\t\t\tPath exportPath = selectedPath.toPath();\n\t\t\tFiles.write(exportPath, classInfo.getBytecode());\n\t\t} catch (IOException ex) {\n\t\t\tlogger.error(\"Failed to export class to path '{}'\", selectedPath, ex);\n\t\t\tErrorDialogs.show(\n\t\t\t\t\tLang.getBinding(\"dialog.error.exportclass.title\"),\n\t\t\t\t\tLang.getBinding(\"dialog.error.exportclass.header\"),\n\t\t\t\t\tLang.getBinding(\"dialog.error.exportclass.content\"),\n\t\t\t\t\tex\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Export the given file.\n\t *\n\t * @param fileInfo\n\t * \t\tFile to export.\n\t */\n\tpublic void export(@Nonnull FileInfo fileInfo) {\n\t\t// Prompt a path for the user to write to.\n\t\tObservableString lastClassExportDir = recentFilesConfig.getLastClassExportDirectory();\n\t\tFile lastExportDir = lastClassExportDir.unboxingMap(File::new);\n\t\tFileChooser chooser = new FileChooserBuilder()\n\t\t\t\t.setInitialFileName(StringUtil.shortenPath(fileInfo.getName()))\n\t\t\t\t.setInitialDirectory(lastExportDir)\n\t\t\t\t.setTitle(Lang.get(\"dialog.file.export\"))\n\t\t\t\t.build();\n\t\tFile selectedPath = chooser.showSaveDialog(null);\n\n\t\t// Selected path is null, meaning user closed out of file chooser.\n\t\t// Cancel export.\n\t\tif (selectedPath == null)\n\t\t\treturn;\n\n\t\t// Write to path.\n\t\ttry {\n\t\t\tPath exportPath = selectedPath.toPath();\n\t\t\tFiles.write(exportPath, fileInfo.getRawContent());\n\t\t} catch (IOException ex) {\n\t\t\tlogger.error(\"Failed to export file to path '{}'\", selectedPath, ex);\n\t\t\tErrorDialogs.show(\n\t\t\t\t\tLang.getBinding(\"dialog.error.exportfile.title\"),\n\t\t\t\t\tLang.getBinding(\"dialog.error.exportfile.header\"),\n\t\t\t\t\tLang.getBinding(\"dialog.error.exportfile.content\"),\n\t\t\t\t\tex\n\t\t\t);\n\t\t}\n\t}\n\n\t@Nonnull\n\tprivate WorkspaceExporter createResourceExporter(@Nonnull WorkspaceResource resource, @Nonnull Path path) {\n\t\tWorkspaceCompressType compression = exportConfig.getCompression().getValue();\n\t\tWorkspaceExportOptions options;\n\t\tPathWorkspaceExportConsumer exportConsumer = new PathWorkspaceExportConsumer(path);\n\t\tif (resource instanceof WorkspaceDirectoryResource) {\n\t\t\toptions = new WorkspaceExportOptions(WorkspaceOutputType.DIRECTORY, exportConsumer);\n\t\t} else if (resource instanceof WorkspaceFileResource) {\n\t\t\toptions = new WorkspaceExportOptions(compression, WorkspaceOutputType.FILE, exportConsumer);\n\t\t} else {\n\t\t\toptions = new WorkspaceExportOptions(compression, WorkspaceOutputType.FILE, exportConsumer);\n\t\t}\n\t\toptions.setBundleSupporting(exportConfig.getBundleSupportingResources().getValue());\n\t\toptions.setCreateZipDirEntries(exportConfig.getCreateZipDirEntries().getValue());\n\t\treturn options.create();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/workspace/PathLoadingManager.java",
    "content": "package software.coley.recaf.workspace;\n\nimport jakarta.annotation.Nonnull;\nimport jakarta.enterprise.context.ApplicationScoped;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.analytics.logging.Logging;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.services.workspace.WorkspaceManager;\nimport software.coley.recaf.services.workspace.io.ResourceImporter;\nimport software.coley.recaf.util.threading.ThreadPoolFactory;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.ExecutorService;\nimport java.util.function.Consumer;\n\n/**\n * Manager module handle loading {@link Workspace} instances from {@link Path}s\n * and handling pushing events to tracked {@link WorkspacePreLoadListener} instances across the application.\n *\n * @author Matt Coley\n */\n@ApplicationScoped\npublic class PathLoadingManager {\n\tprivate static final Logger logger = Logging.get(PathLoadingManager.class);\n\tprivate final ExecutorService loadPool = ThreadPoolFactory.newSingleThreadExecutor(\"path-loader\");\n\tprivate final List<WorkspacePreLoadListener> preLoadListeners = new CopyOnWriteArrayList<>();\n\tprivate final WorkspaceManager workspaceManager;\n\tprivate final ResourceImporter resourceImporter;\n\n\t@Inject\n\tpublic PathLoadingManager(WorkspaceManager workspaceManager, ResourceImporter resourceImporter) {\n\t\tthis.workspaceManager = workspaceManager;\n\t\tthis.resourceImporter = resourceImporter;\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to add.\n\t */\n\tpublic void addPreLoadListener(@Nonnull WorkspacePreLoadListener listener) {\n\t\tPrioritySortable.add(preLoadListeners, listener);\n\t}\n\n\t/**\n\t * @param listener\n\t * \t\tListener to remove.\n\t */\n\tpublic void removePreLoadListener(@Nonnull WorkspacePreLoadListener listener) {\n\t\tpreLoadListeners.remove(listener);\n\t}\n\n\t/**\n\t * @param primaryPath\n\t * \t\tPath to the primary resource file.\n\t * @param supportingPaths\n\t * \t\tPaths to the supporting resource files.\n\t * @param errorHandling\n\t * \t\tError handling for invalid input.\n\t *\n\t * @return Future of the created workspace.\n\t */\n\t@Nonnull\n\tpublic CompletableFuture<Workspace> asyncNewWorkspace(@Nonnull Path primaryPath, @Nonnull List<Path> supportingPaths,\n\t                                                      @Nonnull Consumer<Throwable> errorHandling) {\n\t\t// Invoke listeners, new content is being loaded.\n\t\tUnchecked.checkedForEach(preLoadListeners,\n\t\t\t\tlistener -> listener.onPreLoad(primaryPath, supportingPaths),\n\t\t\t\t(listener, t) -> logger.error(\"Exception thrown opening workspace from '{}'\", primaryPath, t));\n\n\t\t// Load resources from paths.\n\t\tCompletableFuture<Workspace> future = new CompletableFuture<>();\n\t\tloadPool.submit(() -> {\n\t\t\ttry {\n\t\t\t\tList<WorkspaceResource> supportingResources = new ArrayList<>();\n\t\t\t\tWorkspaceResource primaryResource = resourceImporter.importResource(primaryPath);\n\t\t\t\tfor (Path supportingPath : supportingPaths) {\n\t\t\t\t\tWorkspaceResource supportResource = resourceImporter.importResource(supportingPath);\n\t\t\t\t\tsupportingResources.add(supportResource);\n\t\t\t\t}\n\n\t\t\t\t// Wrap into workspace and assign it\n\t\t\t\tWorkspace workspace = new BasicWorkspace(primaryResource, supportingResources);\n\t\t\t\tfuture.complete(workspace);\n\t\t\t\tworkspaceManager.setCurrent(workspace);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tfuture.completeExceptionally(t);\n\t\t\t\terrorHandling.accept(t);\n\t\t\t}\n\t\t});\n\t\treturn future;\n\t}\n\n\t/**\n\t * @param workspace\n\t * \t\tWorkspace to add to.\n\t * @param supportingPaths\n\t * \t\tPaths to the supporting resource files.\n\t * @param errorHandling\n\t * \t\tError handling for invalid input.\n\t *\n\t * @return Future of added supporting resources.\n\t */\n\t@Nonnull\n\tpublic CompletableFuture<List<WorkspaceResource>> asyncAddSupportingResourcesToWorkspace(@Nonnull Workspace workspace,\n\t                                                                                         @Nonnull List<Path> supportingPaths,\n\t                                                                                         @Nonnull Consumer<IOException> errorHandling) {\n\t\t// Load resources from paths.\n\t\tCompletableFuture<List<WorkspaceResource>> future = new CompletableFuture<>();\n\t\tloadPool.submit(() -> {\n\t\t\ttry {\n\t\t\t\tList<WorkspaceResource> loadedResources = new ArrayList<>(supportingPaths.size());\n\t\t\t\tfor (Path supportingPath : supportingPaths) {\n\t\t\t\t\tWorkspaceResource supportResource = resourceImporter.importResource(supportingPath);\n\t\t\t\t\tloadedResources.add(supportResource);\n\t\t\t\t\tworkspace.addSupportingResource(supportResource);\n\t\t\t\t}\n\t\t\t\tfuture.complete(loadedResources);\n\t\t\t} catch (IOException ex) {\n\t\t\t\tfuture.completeExceptionally(ex);\n\t\t\t\terrorHandling.accept(ex);\n\t\t\t}\n\t\t});\n\t\treturn future;\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/main/java/software/coley/recaf/workspace/WorkspacePreLoadListener.java",
    "content": "package software.coley.recaf.workspace;\n\nimport jakarta.annotation.Nonnull;\nimport software.coley.recaf.behavior.PrioritySortable;\nimport software.coley.recaf.util.io.ByteSource;\n\nimport java.net.URL;\nimport java.nio.file.Path;\nimport java.util.List;\n\n/**\n * Listener for when a user has selected some input to load as a workspace.\n * This is triggered when they:\n * <ul>\n *     <li>Press 'finish' on the workspace open wizard</li>\n *     <li>Press an entry in the recent workspace menu</li>\n *     <li>Drag and drop one or more files into Recaf</li>\n * </ul>\n * Handles input as {@link Path} items since most other importable data types\n * are already in memory like {@link ByteSource} and others like {@link URL}\n * get locally fetched, meaning the temp file can be accessed as a {@link Path}\n * anyways.\n *\n * @author Matt Coley\n */\npublic interface WorkspacePreLoadListener extends PrioritySortable {\n\t/**\n\t * @param primaryPath\n\t * \t\tPath to the primary resource file.\n\t * @param supportingPaths\n\t * \t\tPaths to the supporting resource files.\n\t */\n\tvoid onPreLoad(@Nonnull Path primaryPath, @Nonnull List<Path> supportingPaths);\n}\n"
  },
  {
    "path": "recaf-ui/src/main/resources/icons/Jetbrains-LICENSE.txt",
    "content": "This LICENSE is applied to all images taken from https://jetbrains.design/intellij/resources/icons_list/\nand used in the Recaf project.\n\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2000-2020 JetBrains s.r.o.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "recaf-ui/src/main/resources/style/code-editor.css",
    "content": ".code-area {\n\t-fx-background-color: -color-bg-default;\n\t-fx-accent: -color-accent-7;\n    -fx-cursor: text;\n}\n.code-area .text {\n    -fx-fill: -color-base-3;\n    -fx-font-family: 'JetBrains Mono';\n    -fx-font-size: 12px;\n}\n\n/*\n=================================== Paragraph box ==================================\nStyle for handling paragraph boxes, which in RichTextFX are considered to be the\ncomplete line, spanning the full width of the text-area.\n*/\n.code-area .paragraph-box {\n    -fx-padding: -0.5 0 -0.5 0;\n}\n.code-area .paragraph-box:has-caret {\n    -fx-background-color: -color-base-8;\n}\n.code-area .paragraph-box .paragraph-text {\n    -fx-line-spacing: 1;\n    -fx-tab-size: 4;\n}\n\n/*\n=================================== Caret / Cursor ==================================\nStyle for caret, which is the vertical line indicating where in the text you are.\n*/\n.code-area .caret {\n    -fx-stroke: -color-fg-default;\n}\n\n/*\n====================================== Selection =====================================\nStyle for handling selection color.\n*/\n.code-area .main-selection {\n    -fx-fill: -color-accent-6;\n}\n\n/*\n==================================== Line numbers ====================================\nStyle for line numbers added next to paragraphs. Simply shows what lines text is on.\n*/\n.code-area .lineno {\n\t-fx-background-color: -color-bg-overlay;\n\t-fx-max-height: 1000px;\n}\n.code-area .lineno .bg {\n    /* Used by the line number text label. Giving it a background allows hiding other line number graphics\n       under the background if they are rendered before the line number graphic. */\n    -fx-background-color: -color-bg-overlay;\n    -fx-background-insets: -2px -2px -2px -10px\n}\n.code-area .lineno .text {\n    -fx-fill: -color-fg-muted;\n}\n\n/*\n================================ Brace matching lines ================================\nStyle for the vertical line added next to paragraphs to indicate which lines contain\nthe content for the selected brace pair.\n\nConsider the following case:\n1:  ...\n2:  {<CARET>\n3:   ...\n4:  }\n5:  ...\n\nThe caret is next to the brace opener on line 2, which is closed on line 4.\nThus, lines [2-4] will be matched lines, and 1, 5+ will be unmatched.\n*/\n.code-area .matched-brace-line .line {\n    -fx-border-color: transparent transparent transparent -color-accent-3;\n    -fx-border-width: 0 0 0 2px;\n    -fx-pref-width: 1;\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/style/docking.css",
    "content": ".container-leaf {\n    -fx-border-width: 1px;\n    -fx-border-color: -color-border-default;\n}\n\n.container-branch {\n    -fx-background-insets: 0;\n    -fx-padding: 0;\n}\n\n.container-branch > .split-pane-divider {\n    -fx-background-color: transparent;\n    -fx-padding: 0 2px 0 2px;\n}\n\n.container-branch > .split-pane-divider > .horizontal-grabber {\n    -fx-background-color: -color-base-6;\n    -fx-padding: 5px 1px 5px 1px;\n}\n\n.container-branch > .split-pane-divider > .vertical-grabber {\n    -fx-background-color: -color-base-6;\n    -fx-padding: 1px 5px 1px 5px;\n}\n\n.container-branch > .split-pane-divider:hover {\n}\n\n.container-branch > .split-pane-divider:pressed {\n}\n\n.container-branch > .split-pane-divider:disabled {\n}\n\n.container-branch > .split-pane-divider:hover > .horizontal-grabber,\n.container-branch > .split-pane-divider:hover > .vertical-grabber {\n    -fx-background-color: -color-accent-emphasis;\n}\n\n.container-branch > .split-pane-divider:pressed > .horizontal-grabber,\n.container-branch > .split-pane-divider:pressed > .vertical-grabber {\n    -fx-background-color: -color-accent-fg;\n}\n\n/* HeaderPane */\n.header-pane {\n}\n\n/* The \"x\" button on closable headers */\n.header-pane .close-button {\n    -fx-effect: none;\n    -fx-opacity: 0.5;\n    -fx-background-insets: 0;\n    -fx-background-radius: 50px;\n    -fx-background-color: transparent;\n    -fx-border-width: 0px;\n    -fx-padding: 0 2px 0 2px;\n}\n\n.header-pane .header:selected .close-button:hover {\n    -fx-background-color: -color-bg-subtle;\n}\n\n.header-pane .header .close-button:hover {\n    -fx-background-color: -color-bg-default;\n}\n\n/* The \"▼\" and \"≡\" buttons in HeaderView */\n.header-pane .corner-button {\n    -fx-effect: none;\n    -fx-background-insets: 0, 1px;\n    -fx-background-radius: 0px;\n    -fx-background-color: -color-border-default, -color-bg-overlay;\n    -fx-border-width: 0px;\n}\n\n.header-pane .corner-button:hover {\n    -fx-background-color: -color-border-default, -color-bg-default;\n}\n\n.header-pane:top .corner-button {\n    -fx-background-insets: 0, 0 0 1px 1px;\n}\n\n.header-pane:bottom .corner-button {\n    -fx-background-insets: 0, 1px 0 0 1px;\n}\n\n.header-pane:left .corner-button {\n    -fx-background-insets: 0, 1px 1px 0 0;\n}\n\n.header-pane:right .corner-button {\n    -fx-background-insets: 0, 1px 0 0 1px;\n}\n\n.button-bar {\n    -fx-spacing: -1;\n    -fx-padding: 0;\n}\n\n.button-bar:vertical {\n    -fx-fill-height: true;\n}\n\n.button-bar:horizontal {\n    -fx-fill-width: true;\n}\n\n/* HeaderRegion (Wrapper) */\n.header-region-wrapper {\n}\n\n.header-pane:top .header-region-wrapper {\n}\n\n.header-pane:bottom .header-region-wrapper {\n}\n\n.header-pane:left .header-region-wrapper {\n}\n\n.header-pane:right .header-region-wrapper {\n}\n\n/* HeaderRegion */\n.header-region {\n    -fx-background-color: -color-border-default, -color-bg-overlay;\n}\n\n.header-region:top {\n    -fx-background-insets: 0, 0 0 1px 0;\n}\n\n.header-region:bottom {\n    -fx-background-insets: 0, 1px 0 0 0;\n}\n\n.header-region:left {\n    -fx-background-insets: 0, 0 1px 0 0;\n}\n\n.header-region:right {\n    -fx-background-insets: 0, 0 0 0 1px;\n}\n\n.header-region .node-wrapper {\n}\n\n/* Header */\n.header {\n    -fx-background-color: -color-border-default, -color-bg-overlay;\n}\n\n.header:top {\n    -fx-background-insets: 0, 0 0 1px 0;\n}\n\n.header:bottom {\n    -fx-background-insets: 0, 1px 0 0 0;\n}\n\n.header:left {\n    -fx-background-insets: 0, 0 1px 0 0;\n}\n\n.header:right {\n    -fx-background-insets: 0, 0 0 0 1px;\n}\n\n.header:hover {\n    -fx-background-color: -color-border-subtle, -color-bg-subtle;\n}\n\n.header-pane .header:selected {\n    -fx-background-color: -color-border-default, -color-bg-default;\n}\n\n.header-pane:active .header:selected {\n    -fx-background-color: -color-accent-4, -color-bg-default;\n}\n\n.header:selected:top {\n    -fx-background-insets: 0, 0 0 2px 0;\n}\n\n.header:selected:bottom {\n    -fx-background-insets: 0, 2px 0 0 0;\n}\n\n.header:selected:left {\n    -fx-background-insets: 0, 0 2px 0 0;\n}\n\n.header:selected:right {\n    -fx-background-insets: 0, 0 0 0 2px;\n}\n\n/* Collapsed */\n.container:collapsed .header {\n    -fx-background-color: -color-bg-overlay;\n}\n\n.container:collapsed .header-pane {\n    -fx-background-color: -color-bg-overlay;\n}\n\n.container:collapsed .header-region {\n    -fx-background-color: -color-bg-overlay;\n}\n\n.container:collapsed .corner-button {\n    -fx-background-color: -color-bg-overlay;\n    -fx-border-width: 0;\n}\n\n/* Misc */\n.dock-ghost-zone {\n    -fx-opacity: 0.3;\n}\n\n/* Embedded context */\n.embedded-bento .container-leaf {\n    -fx-border-width: 0px;\n}\n\n.embedded-bento:top > .split-pane-divider {\n    -fx-opacity: 1.0;\n    -fx-background-color: -color-border-default, -color-bg-default;\n    -fx-background-insets: 0, 0 0 1px 0;\n}\n\n.embedded-bento:bottom > .split-pane-divider {\n    -fx-opacity: 1.0;\n    -fx-background-color: -color-border-default, -color-bg-default;\n    -fx-background-insets: 0, 1px 0 0 0;\n}\n\n.embedded-bento:left > .split-pane-divider {\n    -fx-opacity: 1.0;\n    -fx-background-color: -color-border-default, -color-bg-default;\n    -fx-background-insets: 0, 0 1px 0 0;\n}\n\n.embedded-bento:right > .split-pane-divider {\n    -fx-opacity: 1.0;\n    -fx-background-color: -color-border-default, -color-bg-default;\n    -fx-background-insets: 0, 0 0 0 1px;\n}\n\n.embedded-bento .header,\n.embedded-bento .header:top,\n.embedded-bento .header:bottom,\n.embedded-bento .header:left,\n.embedded-bento .header:right,\n.embedded-bento .header-region:top,\n.embedded-bento .header-region:bottom,\n.embedded-bento .header-region:left,\n.embedded-bento .header-region:right,\n.embedded-bento .container:collapsed .header,\n.embedded-bento .container:collapsed .header-region {\n    -fx-background-color: -color-bg-overlay;\n    -fx-background-insets: 0;\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/style/hex.css",
    "content": "/* Set common text color/family for all child nodes */\n.hex-view {\n    -fx-text-fill: -color-base-3;\n    -fx-font-family: 'JetBrains Mono';\n}\n\n/* Labels with data equal to '0' will have a slightly faded display */\n.hex-view .label:zero {\n    -fx-text-fill: -color-fg-muted;\n}\n.hex-view .label:edited {\n    -fx-text-fill: -color-danger-5;\n}\n.hex-view .label:selected {\n    -fx-text-fill: -color-accent-3;\n}\n\n.hex-view .header {\n    -fx-padding: 1 0 1 0;\n    -fx-background-color: -color-bg-default;\n    -fx-border-color: -color-border-default;\n    -fx-border-width: 0 0 1 0;\n}\n\n.hex-view .text-field {\n    -fx-padding: 0;\n    -fx-margin: 0;\n    -fx-background-color: -color-bg-inset;\n    -fx-highlight-fill: -color-accent-5;\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/style/recaf.css",
    "content": "/*\nThis file is *mostly* auto-generated.\nPlease maintain changes on the original SCSS files: https://github.com/Col-E/atlantafx\n - Branch: recaf-theme-2x (default)\n*/\n@font-face {\n  font-family: 'JetBrains Mono';\n  font-style: normal;\n  font-weight: normal;\n  src: local('JetBrains Mono'), url('../fonts/JetBrainsMono.ttf') format('truetype');\n}\n@font-face {\n  font-family: 'JetBrains Mono';\n  font-style: normal;\n  font-weight: bold;\n  src: local('JetBrains Mono'), url('../fonts/JetBrainsMono-Bold.ttf') format('truetype');\n}\n@font-face {\n  font-family: 'JetBrains Mono';\n  font-style: italic;\n  font-weight: normal;\n  src: local('JetBrains Mono'), url('../fonts/JetBrainsMono-Italic.ttf') format('truetype');\n}\n@font-face {\n  font-family: 'JetBrains Mono';\n  font-style: italic;\n  font-weight: bold;\n  src: local('JetBrains Mono'), url('../fonts/JetBrainsMono-BoldItalic.ttf') format('truetype');\n}\n.root {\n  -color-dark: rgb(0, 0, 0);\n  -color-light: rgb(255, 255, 255);\n  -color-base-0: white;\n  -color-base-1: #dddddd;\n  -color-base-2: #d0d0d0;\n  -color-base-3: #c4c4c4;\n  -color-base-4: rgb(170, 170, 170);\n  -color-base-5: #6a6a6a;\n  -color-base-6: #5e5e5e;\n  -color-base-7: #444444;\n  -color-base-8: #373737;\n  -color-base-9: #373737;\n  -color-accent-0: #bddeff;\n  -color-accent-1: #a3d1ff;\n  -color-accent-2: #7abcff;\n  -color-accent-3: #57aaff;\n  -color-accent-4: #3d9eff;\n  -color-accent-5: rgb(10, 132, 255);\n  -color-accent-6: #006bd6;\n  -color-accent-7: #005bb7;\n  -color-accent-8: #00448a;\n  -color-accent-9: #002b57;\n  -color-success-0: #ccf5d2;\n  -color-success-1: #adefb7;\n  -color-success-2: #8ee99c;\n  -color-success-3: #70e381;\n  -color-success-4: #51dd66;\n  -color-success-5: rgb(50, 215, 75);\n  -color-success-6: #2bb740;\n  -color-success-7: #239735;\n  -color-success-8: #1c7629;\n  -color-success-9: #14561e;\n  -color-warning-0: #ffe7c2;\n  -color-warning-1: #ffd99d;\n  -color-warning-2: #ffca78;\n  -color-warning-3: #ffbc54;\n  -color-warning-4: #ffad2f;\n  -color-warning-5: rgb(255, 159, 10);\n  -color-warning-6: #d98709;\n  -color-warning-7: #b36f07;\n  -color-warning-8: #8c5706;\n  -color-warning-9: #664004;\n  -color-danger-0: #ffd1ce;\n  -color-danger-1: #ffb5b0;\n  -color-danger-2: #ff9993;\n  -color-danger-3: #ff7d75;\n  -color-danger-4: #ff6158;\n  -color-danger-5: rgb(255, 69, 58);\n  -color-danger-6: #d93b31;\n  -color-danger-7: #b33029;\n  -color-danger-8: #8c2620;\n  -color-danger-9: #661c17;\n  -color-fg-default: rgb(255, 255, 255);\n  -color-fg-muted: #6a6a6a;\n  -color-fg-subtle: rgb(159, 159, 165);\n  -color-fg-emphasis: rgb(255, 255, 255);\n  -color-bg-default: rgb(44, 44, 44);\n  -color-bg-overlay: rgb(35, 35, 35);\n  -color-bg-subtle: rgb(44, 44, 44);\n  -color-bg-inset: rgb(28, 28, 28);\n  -color-border-default: #5e5e5e;\n  -color-border-muted: #444444;\n  -color-border-subtle: rgb(75, 75, 75);\n  -color-shadow-default: rgb(0, 0, 0);\n  -color-neutral-emphasis-plus: rgb(170, 170, 170);\n  -color-neutral-emphasis: rgb(170, 170, 170);\n  -color-neutral-muted: rgba(106, 106, 106, 0.4);\n  -color-neutral-subtle: rgba(106, 106, 106, 0.1);\n  -color-accent-fg: #3d9eff;\n  -color-accent-emphasis: rgb(10, 132, 255);\n  -color-accent-muted: rgba(10, 132, 255, 0.4);\n  -color-accent-subtle: rgba(10, 132, 255, 0.15);\n  -color-warning-fg: rgb(255, 159, 10);\n  -color-warning-emphasis: #d98709;\n  -color-warning-muted: rgba(255, 159, 10, 0.4);\n  -color-warning-subtle: rgba(255, 159, 10, 0.15);\n  -color-success-fg: rgb(50, 215, 75);\n  -color-success-emphasis: #2bb740;\n  -color-success-muted: rgba(50, 215, 75, 0.4);\n  -color-success-subtle: rgba(50, 215, 75, 0.15);\n  -color-danger-fg: rgb(255, 69, 58);\n  -color-danger-emphasis: rgb(255, 69, 58);\n  -color-danger-muted: rgba(255, 69, 58, 0.4);\n  -color-danger-subtle: rgba(255, 69, 58, 0.15);\n  -color-chart-1: #f3622d;\n  -color-chart-2: #fba71b;\n  -color-chart-3: #57b757;\n  -color-chart-4: #41a9c9;\n  -color-chart-5: #4258c9;\n  -color-chart-6: #9a42c8;\n  -color-chart-7: #c84164;\n  -color-chart-8: #888888;\n  -color-chart-1-alpha70: rgba(243, 98, 45, 0.7);\n  -color-chart-2-alpha70: rgba(251, 167, 27, 0.7);\n  -color-chart-3-alpha70: rgba(87, 183, 87, 0.7);\n  -color-chart-4-alpha70: rgba(65, 169, 201, 0.7);\n  -color-chart-5-alpha70: rgba(66, 88, 201, 0.7);\n  -color-chart-6-alpha70: rgba(154, 66, 200, 0.7);\n  -color-chart-7-alpha70: rgba(200, 65, 100, 0.7);\n  -color-chart-8-alpha70: rgba(136, 136, 136, 0.7);\n  -color-chart-1-alpha20: rgba(243, 98, 45, 0.2);\n  -color-chart-2-alpha20: rgba(251, 167, 27, 0.2);\n  -color-chart-3-alpha20: rgba(87, 183, 87, 0.2);\n  -color-chart-4-alpha20: rgba(65, 169, 201, 0.2);\n  -color-chart-5-alpha20: rgba(66, 88, 201, 0.2);\n  -color-chart-6-alpha20: rgba(154, 66, 200, 0.2);\n  -color-chart-7-alpha20: rgba(200, 65, 100, 0.2);\n  -color-chart-8-alpha20: rgba(136, 136, 136, 0.2);\n  -fx-background-color: -color-bg-default;\n  -fx-font-size: 14px;\n  -fx-background-radius: inherit;\n  -fx-background-insets: inherit;\n  -fx-padding: inherit;\n}\n.root.popup {\n  -fx-background-color: transparent;\n}\n\n.ikonli-font-icon {\n  -fx-icon-color: -color-fg-default;\n  -fx-fill: -color-fg-default;\n  -fx-icon-size: 18px;\n}\n\n.mnemonic-underline {\n  -fx-stroke: transparent;\n}\n\n.text {\n  -fx-font-smoothing-type: gray;\n  -fx-bounds-type: logical_vertical_center;\n}\n\nText {\n  -fx-fill: -color-fg-default;\n}\n\n.title-1 {\n  -fx-font-size: 2em;\n  -fx-font-weight: bolder;\n}\n\n.title-2 {\n  -fx-font-size: 1.75em;\n  -fx-font-weight: bolder;\n}\n\n.title-3 {\n  -fx-font-size: 1.5em;\n  -fx-font-weight: bolder;\n}\n\n.title-4 {\n  -fx-font-size: 1.25em;\n  -fx-font-weight: normal;\n}\n\n.text-caption {\n  -fx-font-size: 1em;\n  -fx-font-weight: bold;\n}\n\n.text-small {\n  -fx-font-size: 0.8em;\n}\n\n.text.accent {\n  -fx-fill: -color-accent-fg;\n}\n\n.text.success {\n  -fx-fill: -color-success-fg;\n}\n\n.text.warning {\n  -fx-fill: -color-warning-fg;\n}\n\n.text.danger {\n  -fx-fill: -color-danger-fg;\n}\n\n.text-muted {\n  -fx-fill: -color-fg-muted;\n}\n\n.text-subtle {\n  -fx-fill: -color-fg-subtle;\n}\n\n.text-on-emphasis {\n  -fx-fill: -color-fg-emphasis;\n}\n\n.text-bold {\n  -fx-font-weight: bold;\n}\n\n.text-bolder {\n  -fx-font-weight: bolder;\n}\n\n.text-normal {\n  -fx-font-weight: normal;\n}\n\n.text-lighter {\n  -fx-font-weight: lighter;\n}\n\n.text-italic {\n  -fx-font-style: italic;\n}\n\n.text-oblique {\n  -fx-font-style: oblique;\n}\n\n.text-underlined {\n  -fx-underline: true;\n}\n\n.text-strikethrough {\n  -fx-strikethrough: true;\n}\n\n.bb-code .sub {\n  -fx-translate-y: 0.3em;\n}\n.bb-code .sup {\n  -fx-translate-y: -0.3em;\n}\n.bb-code .hr {\n  -fx-border-color: -color-border-default;\n  -fx-border-width: 0 0 1 0;\n  -fx-border-style: solid;\n  -fx-border-insets: 10px 0 10px 0;\n}\n.bb-code .code {\n  -fx-font-family: 'JetBrains Mono';\n  -fx-border-color: -color-border-default;\n  -fx-border-width: 1;\n  -fx-background-color: -color-bg-subtle;\n  -fx-text-fill: -color-danger-fg;\n  -fx-padding: 0 3 0 3;\n}\n.bb-code .abbr {\n  -fx-border-color: -color-fg-default;\n  -fx-border-width: 0 0 1 0;\n  -fx-border-style: dashed;\n}\n\n.ikonli-font-icon.text-on-emphasis {\n  -fx-fill: -color-fg-emphasis;\n  -fx-icon-color: -color-fg-emphasis;\n}\n\n.ikonli-font-icon.accent {\n  -fx-fill: -color-accent-emphasis;\n  -fx-icon-color: -color-accent-emphasis;\n}\n\n.ikonli-font-icon.success {\n  -fx-fill: -color-success-emphasis;\n  -fx-icon-color: -color-success-emphasis;\n}\n\n.ikonli-font-icon.warning {\n  -fx-fill: -color-warning-emphasis;\n  -fx-icon-color: -color-warning-emphasis;\n}\n\n.ikonli-font-icon.danger {\n  -fx-fill: -color-danger-emphasis;\n  -fx-icon-color: -color-danger-emphasis;\n}\n\n.ikonli-font-icon:accent {\n  -fx-fill: -color-accent-emphasis;\n  -fx-icon-color: -color-accent-emphasis;\n}\n\n.ikonli-font-icon:success {\n  -fx-fill: -color-success-emphasis;\n  -fx-icon-color: -color-success-emphasis;\n}\n\n.ikonli-font-icon:warning {\n  -fx-fill: -color-warning-emphasis;\n  -fx-icon-color: -color-warning-emphasis;\n}\n\n.ikonli-font-icon:danger {\n  -fx-fill: -color-danger-emphasis;\n  -fx-icon-color: -color-danger-emphasis;\n}\n\n.bg-default {\n  -fx-background-color: -color-bg-default;\n}\n\n.bg-inset {\n  -fx-background-color: -color-bg-inset !important;\n}\n\n.bg-subtle {\n  -fx-background-color: -color-bg-subtle;\n}\n\n.bg-neutral-emphasis-plus {\n  -fx-background-color: -color-neutral-emphasis-plus;\n}\n\n.bg-neutral-emphasis {\n  -fx-background-color: -color-neutral-emphasis;\n}\n\n.bg-neutral-muted {\n  -fx-background-color: -color-neutral-muted;\n}\n\n.bg-neutral-subtle {\n  -fx-background-color: -color-neutral-subtle;\n}\n\n.bg-accent-emphasis {\n  -fx-background-color: -color-accent-emphasis;\n}\n\n.bg-accent-muted {\n  -fx-background-color: -color-accent-muted;\n}\n\n.bg-accent-subtle {\n  -fx-background-color: -color-accent-subtle;\n}\n\n.bg-warning-emphasis {\n  -fx-background-color: -color-warning-emphasis;\n}\n\n.bg-warning-muted {\n  -fx-background-color: -color-warning-muted;\n}\n\n.bg-warning-subtle {\n  -fx-background-color: -color-warning-subtle;\n}\n\n.bg-success-emphasis {\n  -fx-background-color: -color-success-emphasis;\n}\n\n.bg-success-muted {\n  -fx-background-color: -color-success-muted;\n}\n\n.bg-success-subtle {\n  -fx-background-color: -color-success-subtle;\n}\n\n.bg-danger-emphasis {\n  -fx-background-color: -color-danger-emphasis;\n}\n\n.bg-danger-muted {\n  -fx-background-color: -color-danger-muted;\n}\n\n.bg-danger-subtle {\n  -fx-background-color: -color-danger-subtle;\n}\n\n.border-default {\n  -fx-border-color: -color-border-default;\n  -fx-border-width: 1px;\n}\n\n.border-muted {\n  -fx-border-color: -color-border-muted;\n  -fx-border-width: 1px;\n}\n\n.border-subtle {\n  -fx-border-color: -color-border-subtle;\n  -fx-border-width: 1px;\n}\n\n.elevated-1 {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 2px, 0.5, 0, 2);\n}\n\n.elevated-2 {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0.5, 0, 2);\n}\n\n.elevated-3 {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 16px, 0.5, 0, 2);\n}\n\n.elevated-4 {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 20px, 0.5, 0, 2);\n}\n\n.interactive:hover {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0.5, 0, 2);\n}\n\n.button {\n  -color-button-bg: rgb(28, 28, 28);\n  -color-button-fg: -color-fg-default;\n  -color-button-border: -color-border-default;\n  -color-button-bg-hover: -color-base-6;\n  -color-button-fg-hover: -color-button-fg;\n  -color-button-border-hover: -color-button-border;\n  -color-button-bg-focused: -color-button-bg;\n  -color-button-fg-focused: -color-button-fg;\n  -color-button-border-focused: -color-accent-emphasis;\n  -color-button-bg-pressed: -color-bg-subtle;\n  -color-button-fg-pressed: -color-button-fg;\n  -color-button-border-pressed: transparent;\n  -color-button-shadow: -color-shadow-default;\n  -fx-background-color: -color-button-border, -color-button-bg;\n  -fx-background-insets: 0, 1px;\n  -fx-background-radius: 0px, 0;\n  -fx-graphic-text-gap: 6px;\n  -fx-text-fill: -color-button-fg;\n  -fx-alignment: CENTER;\n  -fx-padding: 6px 12px 6px 12px;\n}\n.button .font-icon, .button .ikonli-font-icon {\n  -fx-icon-color: -color-button-fg;\n  -fx-fill: -color-button-fg;\n}\n.button:disabled {\n  -fx-opacity: 0.4;\n  -fx-effect: none;\n}\n.button:show-mnemonics > .mnemonic-underline {\n  -fx-stroke: -color-button-fg;\n}\n.button.button-icon {\n  -fx-padding: 6px;\n  -fx-content-display: graphic-only;\n}\n.button.button-circle {\n  -fx-background-radius: 50;\n  -fx-padding: 6px 8px 6px 8px;\n  -fx-content-display: graphic-only;\n  -fx-effect: none;\n}\n.button.left-pill {\n  -fx-background-radius: 0px 0 0 0px, 0 0 0 0;\n  -fx-background-insets: 0, 1px 0 1px 1px;\n  -fx-effect: none;\n}\n.button.center-pill {\n  -fx-background-radius: 0;\n  -fx-background-insets: 0, 1px 0 1px 0;\n  -fx-effect: none;\n}\n.button.right-pill {\n  -fx-background-radius: 0 0px 0px 0, 0 0 0 0;\n  -fx-background-insets: 0, 1px 1px 1px 0;\n  -fx-effect: none;\n}\n.button:hover {\n  -fx-background-color: -color-button-border-hover, -color-button-bg-hover;\n  -fx-text-fill: -color-button-fg-hover;\n}\n.button:hover:focused {\n  -fx-background-color: -color-button-border-focused, -color-button-bg-hover;\n}\n.button:hover .font-icon, .button:hover .ikonli-font-icon {\n  -fx-icon-color: -color-button-fg-hover;\n  -fx-fill: -color-button-fg-hover;\n}\n.button:focused {\n  -fx-background-color: -color-button-border-focused, -color-button-bg-focused;\n  -fx-text-fill: -color-button-fg-focused;\n}\n.button:focused .font-icon, .button:focused .ikonli-font-icon {\n  -fx-icon-color: -color-button-fg-focused;\n  -fx-fill: -color-button-fg-focused;\n}\n.button:armed, .button:focused:armed {\n  -fx-background-color: -color-button-border-pressed, -color-button-bg-pressed;\n  -fx-text-fill: -color-button-fg-pressed;\n}\n.button:armed .font-icon, .button:armed .ikonli-font-icon, .button:focused:armed .font-icon, .button:focused:armed .ikonli-font-icon {\n  -fx-icon-color: -color-button-fg-pressed;\n  -fx-fill: -color-button-fg-pressed;\n}\n.button.button-outlined {\n  -color-button-bg: -color-bg-default;\n  -color-button-fg: -color-fg-default;\n  -color-button-bg-hover: -color-base-6;\n  -color-button-fg-hover: -color-button-fg;\n}\n.button:default, .button.accent {\n  -color-button-bg: -color-accent-emphasis;\n  -color-button-fg: -color-fg-emphasis;\n  -color-button-border: -color-accent-emphasis;\n  -color-button-bg-hover: -color-accent-emphasis;\n  -color-button-fg-hover: -color-fg-emphasis;\n  -color-button-border-hover: -color-accent-emphasis;\n  -color-button-bg-focused: -color-accent-6;\n  -color-button-fg-focused: -color-fg-emphasis;\n  -color-button-border-focused: -color-accent-emphasis;\n  -color-button-bg-pressed: -color-accent-emphasis;\n  -color-button-fg-pressed: -color-fg-emphasis;\n  -color-button-border-pressed: transparent;\n}\n.button:default.button-outlined, .button.accent.button-outlined {\n  -color-button-bg: -color-bg-default;\n  -color-button-fg: -color-accent-fg;\n  -color-button-bg-hover: -color-accent-emphasis;\n  -color-button-fg-hover: -color-fg-emphasis;\n}\n.button:default.flat, .button.accent.flat {\n  -color-button-fg: -color-accent-fg;\n  -color-button-bg-hover: -color-accent-subtle;\n}\n.button.success {\n  -color-button-bg: -color-success-emphasis;\n  -color-button-fg: -color-fg-emphasis;\n  -color-button-border: -color-success-emphasis;\n  -color-button-bg-hover: -color-success-emphasis;\n  -color-button-fg-hover: -color-fg-emphasis;\n  -color-button-border-hover: -color-success-emphasis;\n  -color-button-bg-focused: -color-success-7;\n  -color-button-fg-focused: -color-fg-emphasis;\n  -color-button-border-focused: -color-success-emphasis;\n  -color-button-bg-pressed: -color-success-emphasis;\n  -color-button-fg-pressed: -color-fg-emphasis;\n  -color-button-border-pressed: transparent;\n}\n.button.success.button-outlined {\n  -color-button-bg: -color-bg-default;\n  -color-button-fg: -color-success-fg;\n  -color-button-bg-hover: -color-success-emphasis;\n  -color-button-fg-hover: -color-fg-emphasis;\n}\n.button.success.flat {\n  -color-button-fg: -color-success-fg;\n  -color-button-bg-hover: -color-success-subtle;\n}\n.button.danger {\n  -color-button-bg: -color-danger-emphasis;\n  -color-button-fg: -color-fg-emphasis;\n  -color-button-border: -color-danger-emphasis;\n  -color-button-bg-hover: -color-danger-emphasis;\n  -color-button-fg-hover: -color-fg-emphasis;\n  -color-button-border-hover: -color-danger-emphasis;\n  -color-button-bg-focused: -color-danger-6;\n  -color-button-fg-focused: -color-fg-emphasis;\n  -color-button-border-focused: -color-danger-emphasis;\n  -color-button-bg-pressed: -color-danger-emphasis;\n  -color-button-fg-pressed: -color-fg-emphasis;\n  -color-button-border-pressed: transparent;\n}\n.button.danger.button-outlined {\n  -color-button-bg: -color-bg-default;\n  -color-button-fg: -color-danger-fg;\n  -color-button-bg-hover: -color-danger-emphasis;\n  -color-button-fg-hover: -color-fg-emphasis;\n}\n.button.danger.flat {\n  -color-button-fg: -color-danger-fg;\n  -color-button-bg-hover: -color-danger-subtle;\n}\n.button.flat {\n  -color-button-bg: transparent;\n  -color-button-fg: -color-fg-default;\n  -color-button-border: transparent;\n  -color-button-bg-hover: -color-bg-subtle;\n  -color-button-fg-hover: -color-button-fg;\n  -color-button-border-hover: -color-bg-subtle;\n  -color-button-bg-focused: -color-button-bg;\n  -color-button-fg-focused: -color-button-fg;\n  -color-button-border-focused: -color-button-bg;\n  -color-button-bg-pressed: -color-button-bg;\n  -color-button-fg-pressed: -color-button-fg;\n  -color-button-border-pressed: transparent;\n  -fx-effect: none;\n}\n.button.flat:hover {\n  -fx-underline: true;\n}\n.button.small {\n  -fx-padding: 4.2857142857px 8.5714285714px 4.2857142857px 8.5714285714px;\n  -fx-font-size: 0.8em;\n}\n.button.large {\n  -fx-padding: 8.4px 16.8px 8.4px 16.8px;\n  -fx-font-size: 1.25em;\n}\n.button.rounded {\n  -fx-background-radius: 10em;\n}\n\n.check-box {\n  -fx-text-fill: -color-fg-default;\n  -fx-label-padding: 2px 2px 0 6px;\n}\n.check-box > .box {\n  -fx-background-color: -color-border-default, -color-bg-default;\n  -fx-background-insets: 0, 1.5px;\n  -fx-background-radius: 0px, 0;\n  -fx-padding: 3px 4px 3px 4px;\n  -fx-alignment: CENTER;\n}\n.check-box > .box > .mark {\n  -fx-background-color: -color-bg-default;\n  -fx-shape: \"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\";\n  -fx-scale-shape: true;\n  -fx-min-height: 0.85em;\n  -fx-max-height: 0.85em;\n  -fx-min-width: 0.85em;\n  -fx-max-width: 0.85em;\n}\n.check-box > .box:hover {\n  -fx-background-color: -color-fg-subtle, -color-bg-subtle;\n}\n.check-box:indeterminate > .box {\n  -fx-background-color: -color-accent-emphasis, -color-accent-emphasis;\n}\n.check-box:indeterminate > .box > .mark {\n  -fx-background-color: -color-fg-emphasis;\n  -fx-shape: \"M 17,13 H 7 v -2 h 10 z\";\n  -fx-scale-shape: false;\n}\n.check-box:disabled {\n  -fx-opacity: 0.4;\n}\n.check-box:disabled > .box {\n  -fx-opacity: 0.4;\n}\n.check-box:selected > .box {\n  -fx-background-color: -color-accent-emphasis, -color-accent-emphasis;\n}\n.check-box:selected > .box > .mark {\n  -fx-background-color: -color-fg-emphasis;\n}\n.check-box:show-mnemonics > .mnemonic-underline {\n  -fx-stroke: -color-fg-muted;\n}\n\n.combo-box-base {\n  -fx-background-color: -color-border-default, -color-bg-default;\n  -fx-background-insets: 0, 1px;\n  -fx-background-radius: 0px, 0;\n  -fx-text-fill: -color-fg-default;\n  -fx-alignment: CENTER;\n  -fx-content-display: LEFT;\n}\n.combo-box-base:disabled {\n  -fx-opacity: 0.4;\n}\n.combo-box-base:success, .combo-box-base:success:focused {\n  -fx-background-color: -color-success-emphasis, -color-bg-default;\n}\n.combo-box-base:danger, .combo-box-base:danger:focused {\n  -fx-background-color: -color-danger-emphasis, -color-bg-default;\n}\n.combo-box-base:focused {\n  -fx-background-color: -color-accent-emphasis, -color-bg-default;\n}\n.combo-box-base.left-pill {\n  -fx-background-radius: 0px 0 0 0px, 0 0 0 0;\n  -fx-background-insets: 0, 1px 0 1px 1px;\n}\n.combo-box-base.left-pill:focused {\n  -fx-background-insets: 0, 1px;\n}\n.combo-box-base.center-pill {\n  -fx-background-radius: 0;\n  -fx-background-insets: 0, 1px 0 1px 0;\n}\n.combo-box-base.center-pill:focused {\n  -fx-background-insets: 0, 1px;\n}\n.combo-box-base.right-pill {\n  -fx-background-radius: 0 0px 0px 0, 0 0 0 0;\n  -fx-background-insets: 0, 1px 1px 1px 0;\n}\n.combo-box-base.right-pill:focused {\n  -fx-background-insets: 0, 1px;\n}\n.combo-box-base > .arrow-button {\n  -fx-padding: 6px 12px 6px 12px;\n}\n.combo-box-base > .arrow-button > .arrow {\n  -fx-shape: \"M12 5.83L15.17 9l1.41-1.41L12 3 7.41 7.59 8.83 9 12 5.83zm0 12.34L8.83 15l-1.41 1.41L12 21l4.59-4.59L15.17 15 12 18.17z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-accent-fg;\n}\n.combo-box-base > .text-field {\n  -fx-background-insets: 0, 1px 0 1px 1px;\n  -fx-background-radius: 0px 0 0 0px, 0 0 0 0;\n}\n.combo-box-base:success > .arrow-button > .arrow {\n  -fx-background-color: -color-success-fg;\n}\n.combo-box-base:danger > .arrow-button > .arrow {\n  -fx-background-color: -color-danger-fg;\n}\n.combo-box-base.alt-icon > .arrow-button > .arrow {\n  -fx-shape: \"M7 10l5 5 5-5z\";\n  -fx-scale-shape: false;\n}\n\n.combo-box > .list-cell {\n  -fx-background-color: transparent;\n  -fx-text-fill: -color-fg-default;\n  -fx-padding: 6px 12px 6px 12px;\n  -fx-graphic-text-gap: 6px;\n}\n.combo-box:success > .list-cell {\n  -fx-text-fill: -color-success-fg;\n}\n.combo-box:danger > .list-cell {\n  -fx-text-fill: -color-danger-fg;\n}\n\n.combo-box-popup > .list-view {\n  -fx-background-color: -color-border-default, -color-bg-default;\n  -fx-background-insets: 0, 1;\n  -fx-border-radius: 0px, 0;\n  -fx-background-radius: 0px, 0;\n  -fx-padding: 5px 5px 5px 5px;\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0px, 0, 2);\n}\n.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell {\n  -fx-cell-size: 0;\n  -fx-background-color: -color-bg-default;\n  -fx-background-radius: 0px, 0;\n  -fx-padding: 8px 8px 8px 8px;\n  -fx-graphic-text-gap: 6px;\n  -fx-text-fill: -color-fg-default;\n}\n.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell .font-icon, .combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell .ikonli-font-icon {\n  -fx-icon-color: -color-fg-default;\n  -fx-fill: -color-fg-default;\n}\n.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:hover {\n  -fx-background-color: -color-accent-emphasis;\n  -fx-text-fill: -color-fg-emphasis;\n}\n.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:hover .font-icon, .combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:hover .ikonli-font-icon {\n  -fx-icon-color: -color-fg-emphasis;\n  -fx-fill: -color-fg-emphasis;\n}\n.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected, .combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected:hover {\n  -fx-background-color: -color-accent-emphasis;\n  -fx-text-fill: -color-fg-emphasis;\n}\n.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected .font-icon, .combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected .ikonli-font-icon, .combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected:hover .font-icon, .combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected:hover .ikonli-font-icon {\n  -fx-icon-color: -color-fg-emphasis;\n  -fx-fill: -color-fg-emphasis;\n}\n.combo-box-popup > .list-view > .placeholder > .label {\n  -fx-text-fill: -color-fg-muted;\n}\n\n.choice-box {\n  -fx-background-color: -color-border-default, -color-bg-default;\n  -fx-background-insets: 0, 1px;\n  -fx-background-radius: 0px, 0;\n  -fx-text-fill: -color-fg-default;\n  -fx-alignment: CENTER;\n  -fx-content-display: LEFT;\n  -fx-padding: 6px 12px 6px 12px;\n}\n.choice-box:disabled {\n  -fx-opacity: 0.4;\n}\n.choice-box:success, .choice-box:success:focused {\n  -fx-background-color: -color-success-emphasis, -color-bg-default;\n}\n.choice-box:danger, .choice-box:danger:focused {\n  -fx-background-color: -color-danger-emphasis, -color-bg-default;\n}\n.choice-box:focused {\n  -fx-background-color: -color-accent-emphasis, -color-bg-default;\n}\n.choice-box.left-pill {\n  -fx-background-radius: 0px 0 0 0px, 0 0 0 0;\n  -fx-background-insets: 0, 1px 0 1px 1px;\n}\n.choice-box.left-pill:focused {\n  -fx-background-insets: 0, 1px;\n}\n.choice-box.center-pill {\n  -fx-background-radius: 0;\n  -fx-background-insets: 0, 1px 0 1px 0;\n}\n.choice-box.center-pill:focused {\n  -fx-background-insets: 0, 1px;\n}\n.choice-box.right-pill {\n  -fx-background-radius: 0 0px 0px 0, 0 0 0 0;\n  -fx-background-insets: 0, 1px 1px 1px 0;\n}\n.choice-box.right-pill:focused {\n  -fx-background-insets: 0, 1px;\n}\n.choice-box > .label {\n  -fx-text-fill: -color-fg-default;\n}\n.choice-box > .open-button > .arrow {\n  -fx-shape: \"M12 5.83L15.17 9l1.41-1.41L12 3 7.41 7.59 8.83 9 12 5.83zm0 12.34L8.83 15l-1.41 1.41L12 21l4.59-4.59L15.17 15 12 18.17z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-accent-fg;\n}\n.choice-box:success > .label {\n  -fx-text-fill: -color-success-fg;\n}\n.choice-box:success > .open-button > .arrow {\n  -fx-background-color: -color-success-fg;\n}\n.choice-box:danger > .label {\n  -fx-text-fill: -color-danger-fg;\n}\n.choice-box:danger > .open-button > .arrow {\n  -fx-background-color: -color-danger-fg;\n}\n.choice-box:disabled > .label {\n  -fx-opacity: 1;\n}\n.choice-box.alt-icon > .open-button > .arrow {\n  -fx-shape: \"M7 10l5 5 5-5z\";\n  -fx-scale-shape: false;\n}\n\n.list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:hover,\n.tree-view > .virtual-flow > .clipped-container > .sheet > .tree-cell:filled:hover,\n.table-view > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:hover,\n.tree-table-view > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell:filled:hover {\n  -color-cell-fg: -color-cell-fg-hover;\n  -fx-background-color: -color-cell-border, -color-cell-bg-hover;\n}\n\n.list-view:focused > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:hover,\n.tree-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-cell:filled:hover,\n.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:hover,\n.tree-table-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell:filled:hover {\n  -color-cell-fg: -color-cell-fg-hover-focused;\n  -fx-background-color: -color-cell-border, -color-cell-bg-hover-focused;\n}\n\n.list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected,\n.tree-view > .virtual-flow > .clipped-container > .sheet > .tree-cell:filled:selected,\n.table-view > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected,\n.tree-table-view > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell:filled:selected {\n  -color-cell-fg: -color-cell-fg-selected;\n  -fx-background-color: -color-cell-border, -color-cell-bg-selected;\n}\n\n.list-view:focused > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected,\n.tree-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-cell:filled:selected,\n.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected,\n.tree-table-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell:filled:selected {\n  -color-cell-fg: -color-cell-fg-selected-focused;\n  -fx-background-color: -color-cell-border, -color-cell-bg-selected-focused;\n}\n\n.table-view > .virtual-flow > .clipped-container > .sheet > .table-row-cell .table-cell:selected,\n.tree-table-view > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell .tree-table-cell:selected {\n  -fx-background-color: -color-cell-bg-selected;\n  -fx-background-insets: 0 0 2 0;\n}\n\n.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell .table-cell:selected,\n.tree-table-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell .tree-table-cell:selected {\n  -fx-background-color: -color-cell-bg-selected-focused;\n}\n\n.cell .text-input {\n  -fx-background-color: transparent;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0;\n  -fx-padding: 0;\n}\n.cell .check-box {\n  -fx-padding: 0 6px 0 0;\n}\n.cell .choice-box {\n  -fx-background-color: transparent;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0;\n  -fx-padding: 0 12px 0 0;\n  -fx-alignment: CENTER_LEFT;\n  -fx-content-display: LEFT;\n}\n.cell .combo-box {\n  -fx-background-color: transparent;\n  -fx-alignment: CENTER_LEFT;\n  -fx-content-display: LEFT;\n  -fx-background-radius: 0;\n}\n.cell .combo-box .cell.list-cell {\n  -fx-background-color: transparent;\n  -fx-padding: 0;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0;\n}\n\n.table-view > .virtual-flow > .clipped-container > .sheet > .table-row-cell .text-field-table-cell:focus-within {\n  -fx-background-insets: 0, 1, 2;\n  -fx-background-color: -color-bg-default, -color-accent-emphasis, -color-bg-default;\n}\n\n.list-view {\n  -color-cell-bg: rgb(35, 35, 35);\n  -color-cell-fg: -color-fg-default;\n  -color-cell-bg-hover: rgb(50, 50, 50);\n  -color-cell-fg-hover: -color-fg-default;\n  -color-cell-bg-hover-focused: -color-base-9;\n  -color-cell-fg-hover-focused: -color-fg-default;\n  -color-cell-bg-selected: rgb(50, 50, 50);\n  -color-cell-fg-selected: -color-fg-default;\n  -color-cell-bg-selected-focused: -color-base-9;\n  -color-cell-fg-selected-focused: -color-fg-default;\n  -color-cell-bg-odd: -color-bg-subtle;\n  -color-cell-border: -color-border-default;\n  -color-disclosure: -color-fg-muted;\n  -fx-background-color: rgb(35, 35, 35);\n  -fx-border-color: -color-cell-border;\n  -fx-border-width: 1px;\n  -fx-border-radius: 0;\n}\n.list-view > .virtual-flow > .corner {\n  -fx-background-color: -color-cell-border;\n  -fx-opacity: 0.4;\n}\n.list-view > .virtual-flow:disabled {\n  -fx-opacity: 0.4;\n}\n.list-view.edge-to-edge {\n  -fx-border-width: 0;\n}\n.list-view .list-cell {\n  -fx-background-color: -color-cell-bg;\n  -fx-text-fill: -color-cell-fg;\n  -fx-padding: 0 0.5em 0 0.5em;\n  -fx-cell-size: 2.8em;\n  -fx-border-width: 0 0 1 0;\n  -fx-border-color: transparent;\n}\n.list-view.bordered .list-cell {\n  -fx-border-color: -color-cell-border;\n}\n.list-view.bordered .list-cell:empty {\n  -fx-border-color: transparent;\n}\n.list-view.dense .list-cell {\n  -fx-cell-size: 2em;\n}\n.list-view.striped .list-cell {\n  -fx-border-width: 0;\n}\n.list-view.striped .list-cell:filled:odd {\n  -fx-background-color: -color-cell-bg-odd;\n}\n\n.table-view {\n  -color-cell-bg: rgb(35, 35, 35);\n  -color-cell-fg: -color-fg-default;\n  -color-cell-bg-hover: rgb(50, 50, 50);\n  -color-cell-fg-hover: -color-fg-default;\n  -color-cell-bg-hover-focused: -color-base-9;\n  -color-cell-fg-hover-focused: -color-fg-default;\n  -color-cell-bg-selected: rgb(50, 50, 50);\n  -color-cell-fg-selected: -color-fg-default;\n  -color-cell-bg-selected-focused: -color-base-9;\n  -color-cell-fg-selected-focused: -color-fg-default;\n  -color-cell-bg-odd: -color-bg-subtle;\n  -color-cell-border: -color-border-default;\n  -color-disclosure: -color-fg-muted;\n  -fx-background-color: rgb(35, 35, 35);\n  -fx-border-color: -color-cell-border;\n  -fx-border-width: 1px;\n  -fx-border-radius: 0;\n  -color-header-bg: -color-bg-default;\n  -color-header-fg: -color-fg-muted;\n}\n.table-view > .virtual-flow > .corner {\n  -fx-background-color: -color-cell-border;\n  -fx-opacity: 0.4;\n}\n.table-view > .virtual-flow:disabled {\n  -fx-opacity: 0.4;\n}\n.table-view.edge-to-edge {\n  -fx-border-width: 0;\n}\n.table-view.bordered > .column-header-background .column-header {\n  -fx-background-color: -color-cell-border, -color-header-bg;\n  -fx-background-insets: 0, 0 1 0 0;\n}\n.table-view > .column-header-background {\n  -fx-background-color: -color-cell-border, -color-header-bg;\n  -fx-background-insets: 0, 0 0 1 0;\n}\n.table-view > .column-header-background .column-header {\n  -fx-background-color: transparent;\n  -fx-background-insets: 0;\n  -fx-size: 40px;\n  -fx-padding: 0;\n  -fx-font-weight: bold;\n  -fx-border-color: -color-cell-border;\n  -fx-border-width: 0 1 1 0;\n}\n.table-view > .column-header-background .column-header .label {\n  -fx-text-fill: -color-header-fg;\n  -fx-alignment: CENTER_LEFT;\n  -fx-padding: 0 0.5em 0 0.5em;\n}\n.table-view > .column-header-background .column-header GridPane {\n  -fx-padding: 0 4px 0 0;\n}\n.table-view > .column-header-background .column-header .arrow {\n  -fx-background-color: -color-header-fg;\n  -fx-padding: 3px 4px 3px 4px;\n  -fx-shape: \"M 0 0 h 7 l -3.5 4 z\";\n}\n.table-view > .column-header-background .column-header .sort-order-dots-container {\n  -fx-padding: 2px 0 2px 0;\n}\n.table-view > .column-header-background .column-header .sort-order-dots-container > .sort-order-dot {\n  -fx-background-color: -color-header-fg;\n  -fx-padding: 0.115em;\n  -fx-background-radius: 0.115em;\n}\n.table-view > .column-header-background .column-header .sort-order {\n  -fx-padding: 0 0 0 2px;\n}\n.table-view > .column-header-background > .filler {\n  -fx-background-color: transparent;\n  -fx-border-color: -color-cell-border;\n  -fx-border-width: 0 0 1 0;\n}\n.table-view > .column-header-background > .show-hide-columns-button {\n  -fx-border-color: -color-cell-border;\n  -fx-border-width: 0 0 1 0;\n  -fx-cursor: hand;\n}\n.table-view > .column-header-background > .show-hide-columns-button > .show-hide-column-image {\n  -fx-background-color: -color-header-fg;\n  -fx-shape: \"M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\";\n  -fx-scale-shape: true;\n  -fx-padding: 0.4em 0.115em 0.4em 0.115em;\n}\n.table-view .column-resize-line {\n  -fx-background-color: -color-accent-emphasis;\n  -fx-padding: 0 1 0 1;\n}\n.table-view .column-drag-header {\n  -fx-background-color: -color-accent-muted;\n}\n.table-view .column-overlay {\n  -fx-background-color: -color-accent-muted;\n}\n.table-view .placeholder > .label {\n  -fx-font-size: 1.25em;\n}\n.table-view.bordered .table-row-cell > .table-cell {\n  -fx-border-color: transparent -color-cell-border transparent transparent;\n}\n.table-view.bordered .table-row-cell > .table-cell:empty {\n  -fx-border-color: transparent;\n}\n.table-view.dense > .column-header-background .column-header {\n  -fx-size: 34px;\n}\n.table-view.dense .table-row-cell {\n  -fx-cell-size: 2em;\n}\n.table-view.striped .table-row-cell {\n  -fx-background-insets: 0;\n}\n.table-view.striped.bordered .table-row-cell {\n  -fx-background-insets: 0, 0 0 1 0;\n}\n.table-view.striped .table-row-cell:filled:odd {\n  -fx-background-color: -color-cell-border, -color-cell-bg-odd;\n}\nAdd support for NO_HEADER tweak in tables .table-view.no-header > .column-header-background {\n  -fx-max-height: 0;\n  -fx-pref-height: 0;\n  -fx-min-height: 0;\n}\n.table-view .table-row-cell {\n  -fx-background-color: -color-cell-border, -color-cell-bg;\n  -fx-background-insets: 0, 0 0 1 0;\n  -fx-padding: 0;\n  -fx-cell-size: 2.8em;\n}\n.table-view .table-row-cell:empty {\n  -fx-background-color: transparent;\n  -fx-background-insets: 0;\n}\n.table-view .table-row-cell:empty > .table-cell {\n  -fx-border-color: transparent;\n}\n.table-view .table-row-cell > .table-cell {\n  -fx-padding: 0 0.5em 0 0.5em;\n  -fx-text-fill: -color-cell-fg;\n  -fx-alignment: BASELINE_LEFT;\n}\n.table-view .table-row-cell > .table-cell.table-column.align-left {\n  -fx-alignment: BASELINE_LEFT;\n}\n.table-view .table-row-cell > .table-cell.table-column.align-center {\n  -fx-alignment: BASELINE_CENTER;\n}\n.table-view .table-row-cell > .table-cell.table-column.align-right {\n  -fx-alignment: BASELINE_RIGHT;\n}\n\n.table-view:constrained-resize > .virtual-flow > .clipped-container > .sheet > .table-row-cell > .table-cell:last-visible,\n.tree-table-view:constrained-resize > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell > .tree-table-cell:last-visible {\n  -fx-border-color: transparent;\n}\n\n.table-view .table-row-cell > .table-cell.check-box-table-cell,\n.table-view .table-row-cell > .table-cell.font-icon-table-cell,\n.tree-table-view .tree-table-row-cell > .tree-table-cell.check-box-tree-table-cell {\n  -fx-alignment: BASELINE_LEFT;\n}\n\n.tree-view {\n  -color-cell-bg: rgb(35, 35, 35);\n  -color-cell-fg: -color-fg-default;\n  -color-cell-bg-hover: rgb(50, 50, 50);\n  -color-cell-fg-hover: -color-fg-default;\n  -color-cell-bg-hover-focused: -color-base-9;\n  -color-cell-fg-hover-focused: -color-fg-default;\n  -color-cell-bg-selected: rgb(50, 50, 50);\n  -color-cell-fg-selected: -color-fg-default;\n  -color-cell-bg-selected-focused: -color-base-9;\n  -color-cell-fg-selected-focused: -color-fg-default;\n  -color-cell-bg-odd: -color-bg-subtle;\n  -color-cell-border: -color-border-default;\n  -color-disclosure: -color-fg-muted;\n  -fx-background-color: rgb(35, 35, 35);\n  -fx-border-color: -color-cell-border;\n  -fx-border-width: 1px;\n  -fx-border-radius: 0;\n}\n.tree-view > .virtual-flow > .corner {\n  -fx-background-color: -color-cell-border;\n  -fx-opacity: 0.4;\n}\n.tree-view > .virtual-flow:disabled {\n  -fx-opacity: 0.4;\n}\n.tree-view.edge-to-edge {\n  -fx-border-width: 0;\n}\n.tree-view.dense .tree-cell {\n  -fx-padding: 0.25em 0 0.25em 0;\n}\n.tree-view.alt-icon .tree-cell > .tree-disclosure-node {\n  -fx-padding: 0.3em 0.5em 0 0.5em;\n}\n.tree-view.alt-icon .tree-cell > .tree-disclosure-node > .arrow {\n  -fx-shape: \"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\";\n  -fx-scale-shape: false;\n  -fx-padding: 0.333333em;\n}\n.tree-view.alt-icon .tree-cell:expanded > .tree-disclosure-node > .arrow {\n  -fx-shape: \"M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z\";\n  -fx-scale-shape: false;\n  -fx-padding: 0.333333em;\n}\n\n.tree-cell {\n  -fx-background-color: -color-cell-bg;\n  -fx-text-fill: -color-cell-fg;\n  -fx-padding: 0.5em 0 0.5em 0;\n  -fx-indent: 1.2em;\n}\n.tree-cell > .tree-disclosure-node {\n  -fx-padding: 0.35em 0.5em 0 0.5em;\n  -fx-background-color: transparent;\n}\n\n.tree-cell > .tree-disclosure-node > .arrow,\n.tree-table-row-cell > .tree-disclosure-node > .arrow {\n  -fx-shape: \"M10 17l5-5-5-5v10z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-disclosure;\n  -fx-padding: 0.333333em 0.229em 0.333333em 0.229em;\n}\n\n.tree-cell:expanded > .tree-disclosure-node > .arrow,\n.tree-table-row-cell:expanded > .tree-disclosure-node > .arrow {\n  -fx-shape: \"M7 10l5 5 5-5z\";\n  -fx-scale-shape: false;\n}\n\n.tree-table-view {\n  -color-cell-bg: rgb(35, 35, 35);\n  -color-cell-fg: -color-fg-default;\n  -color-cell-bg-hover: rgb(50, 50, 50);\n  -color-cell-fg-hover: -color-fg-default;\n  -color-cell-bg-hover-focused: -color-base-9;\n  -color-cell-fg-hover-focused: -color-fg-default;\n  -color-cell-bg-selected: rgb(50, 50, 50);\n  -color-cell-fg-selected: -color-fg-default;\n  -color-cell-bg-selected-focused: -color-base-9;\n  -color-cell-fg-selected-focused: -color-fg-default;\n  -color-cell-bg-odd: -color-bg-subtle;\n  -color-cell-border: -color-border-default;\n  -color-disclosure: -color-fg-muted;\n  -fx-background-color: rgb(35, 35, 35);\n  -fx-border-color: -color-cell-border;\n  -fx-border-width: 1px;\n  -fx-border-radius: 0;\n  -color-header-bg: -color-bg-default;\n  -color-header-fg: -color-fg-muted;\n}\n.tree-table-view > .virtual-flow > .corner {\n  -fx-background-color: -color-cell-border;\n  -fx-opacity: 0.4;\n}\n.tree-table-view > .virtual-flow:disabled {\n  -fx-opacity: 0.4;\n}\n.tree-table-view.edge-to-edge {\n  -fx-border-width: 0;\n}\n.tree-table-view.bordered > .column-header-background .column-header {\n  -fx-background-color: -color-cell-border, -color-header-bg;\n  -fx-background-insets: 0, 0 1 0 0;\n}\n.tree-table-view > .column-header-background {\n  -fx-background-color: -color-cell-border, -color-header-bg;\n  -fx-background-insets: 0, 0 0 1 0;\n}\n.tree-table-view > .column-header-background .column-header {\n  -fx-background-color: transparent;\n  -fx-background-insets: 0;\n  -fx-size: 40px;\n  -fx-padding: 0;\n  -fx-font-weight: bold;\n  -fx-border-color: -color-cell-border;\n  -fx-border-width: 0 1 1 0;\n}\n.tree-table-view > .column-header-background .column-header .label {\n  -fx-text-fill: -color-header-fg;\n  -fx-alignment: CENTER_LEFT;\n  -fx-padding: 0 0.5em 0 0.5em;\n}\n.tree-table-view > .column-header-background .column-header GridPane {\n  -fx-padding: 0 4px 0 0;\n}\n.tree-table-view > .column-header-background .column-header .arrow {\n  -fx-background-color: -color-header-fg;\n  -fx-padding: 3px 4px 3px 4px;\n  -fx-shape: \"M 0 0 h 7 l -3.5 4 z\";\n}\n.tree-table-view > .column-header-background .column-header .sort-order-dots-container {\n  -fx-padding: 2px 0 2px 0;\n}\n.tree-table-view > .column-header-background .column-header .sort-order-dots-container > .sort-order-dot {\n  -fx-background-color: -color-header-fg;\n  -fx-padding: 0.115em;\n  -fx-background-radius: 0.115em;\n}\n.tree-table-view > .column-header-background .column-header .sort-order {\n  -fx-padding: 0 0 0 2px;\n}\n.tree-table-view > .column-header-background > .filler {\n  -fx-background-color: transparent;\n  -fx-border-color: -color-cell-border;\n  -fx-border-width: 0 0 1 0;\n}\n.tree-table-view > .column-header-background > .show-hide-columns-button {\n  -fx-border-color: -color-cell-border;\n  -fx-border-width: 0 0 1 0;\n  -fx-cursor: hand;\n}\n.tree-table-view > .column-header-background > .show-hide-columns-button > .show-hide-column-image {\n  -fx-background-color: -color-header-fg;\n  -fx-shape: \"M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\";\n  -fx-scale-shape: true;\n  -fx-padding: 0.4em 0.115em 0.4em 0.115em;\n}\n.tree-table-view .column-resize-line {\n  -fx-background-color: -color-accent-emphasis;\n  -fx-padding: 0 1 0 1;\n}\n.tree-table-view .column-drag-header {\n  -fx-background-color: -color-accent-muted;\n}\n.tree-table-view .column-overlay {\n  -fx-background-color: -color-accent-muted;\n}\n.tree-table-view .placeholder > .label {\n  -fx-font-size: 1.25em;\n}\n.tree-table-view.bordered .tree-table-row-cell > .tree-table-cell {\n  -fx-border-color: transparent -color-cell-border transparent transparent;\n}\n.tree-table-view.bordered .tree-table-row-cell > .tree-table-cell:empty {\n  -fx-border-color: transparent;\n}\n.tree-table-view.dense > .column-header-background .column-header {\n  -fx-size: 34px;\n}\n.tree-table-view.dense .tree-table-row-cell {\n  -fx-cell-size: 2em;\n}\n.tree-table-view.dense .tree-table-row-cell > .tree-disclosure-node {\n  -fx-padding: 0.6em 0.5em 0 0.5em;\n}\n.tree-table-view.striped .tree-table-row-cell {\n  -fx-background-insets: 0;\n}\n.tree-table-view.striped.bordered .tree-table-row-cell {\n  -fx-background-insets: 0, 0 0 1 0;\n}\n.tree-table-view.striped .tree-table-row-cell:filled:odd {\n  -fx-background-color: -color-cell-border, -color-cell-bg-odd;\n}\n.tree-table-view.no-header > .column-header-background {\n  -fx-max-height: 0;\n  -fx-pref-height: 0;\n  -fx-min-height: 0;\n}\n.tree-table-view .tree-table-row-cell {\n  -fx-background-color: -color-cell-border, -color-cell-bg;\n  -fx-background-insets: 0, 0 0 1 0;\n  -fx-padding: 0;\n  -fx-cell-size: 2.8em;\n  -fx-indent: 1em;\n}\n.tree-table-view .tree-table-row-cell:empty {\n  -fx-background-color: transparent;\n  -fx-background-insets: 0;\n}\n.tree-table-view .tree-table-row-cell > .tree-disclosure-node {\n  -fx-padding: 1em 0.5em 0 0.5em;\n  -fx-background-color: transparent;\n}\n.tree-table-view .tree-table-row-cell > .tree-table-cell {\n  -fx-padding: 0 0.5em 0 0.5em;\n  -fx-text-fill: -color-cell-fg;\n  -fx-alignment: BASELINE_LEFT;\n}\n.tree-table-view .tree-table-row-cell > .tree-table-cell.table-column.align-left {\n  -fx-alignment: BASELINE_LEFT;\n}\n.tree-table-view .tree-table-row-cell > .tree-table-cell.table-column.align-center {\n  -fx-alignment: BASELINE_CENTER;\n}\n.tree-table-view .tree-table-row-cell > .tree-table-cell.table-column.align-right {\n  -fx-alignment: BASELINE_RIGHT;\n}\n.tree-table-view.alt-icon .tree-table-row-cell > .tree-disclosure-node > .arrow {\n  -fx-shape: \"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\";\n  -fx-scale-shape: false;\n  -fx-padding: 0.333333em;\n}\n.tree-table-view.alt-icon .tree-table-row-cell:expanded > .tree-disclosure-node > .arrow {\n  -fx-shape: \"M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z\";\n  -fx-scale-shape: false;\n  -fx-padding: 0.333333em;\n}\n\n.combo-box-base.date-picker > .arrow-button {\n  -fx-cursor: hand;\n}\n.combo-box-base.date-picker > .arrow-button > .arrow {\n  -fx-shape: \"M20 3h-1V1h-2v2H7V1H5v2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 18H4V10h16v11zm0-13H4V5h16v3z\";\n  -fx-scale-shape: true;\n  -fx-background-color: -color-accent-fg;\n  -fx-padding: 0.416667em;\n}\n.combo-box-base.date-picker > .text-field:readonly {\n  -fx-background-color: -color-input-border, -color-input-bg;\n}\n.combo-box-base.date-picker > .text-field:readonly:focused {\n  -fx-background-color: -color-input-border-focused, -color-input-bg-focused;\n}\n\n.combo-box-popup > .date-picker-popup {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0px, 0, 2);\n  -fx-background-radius: 0px, 0;\n}\n\n.date-picker-popup {\n  -color-date-bg: -color-bg-default;\n  -color-date-border: -color-border-default;\n  -color-date-month-year-bg: -color-bg-default;\n  -color-date-month-year-fg: -color-fg-default;\n  -color-date-day-bg: -color-bg-default;\n  -color-date-day-bg-hover: -color-bg-subtle;\n  -color-date-day-bg-selected: -color-accent-emphasis;\n  -color-date-day-fg: -color-fg-default;\n  -color-date-day-fg-hover: -color-fg-default;\n  -color-date-day-fg-selected: -color-fg-emphasis;\n  -color-date-week-bg: -color-bg-subtle;\n  -color-date-week-fg: -color-fg-muted;\n  -color-date-today-bg: -color-accent-subtle;\n  -color-date-today-fg: -color-accent-fg;\n  -color-date-other-month-fg: -color-fg-subtle;\n  -color-date-chrono-fg: -color-success-fg;\n  -fx-background-color: -color-date-border, -color-date-bg;\n  -fx-background-insets: 0, 1;\n  -fx-background-radius: 0px, 0;\n  -fx-alignment: CENTER;\n  -fx-spacing: 0;\n  -fx-padding: 1px;\n}\n.date-picker-popup > .month-year-pane {\n  -fx-padding: 8px 8px 8px 8px;\n  -fx-background-color: -color-date-month-year-bg;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0px 0px 0 0;\n}\n.date-picker-popup > .month-year-pane > .spinner {\n  -fx-spacing: 4px;\n  -fx-alignment: CENTER;\n  -fx-fill-height: false;\n  -fx-background-color: transparent;\n  -fx-border-color: transparent;\n  -fx-font-size: 1.1em;\n}\n.date-picker-popup > .month-year-pane > .spinner > .button {\n  -fx-background-color: transparent;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0;\n  -fx-cursor: hand;\n}\n.date-picker-popup > .month-year-pane > .spinner > .button > .left-arrow {\n  -fx-shape: \"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-date-month-year-fg;\n}\n.date-picker-popup > .month-year-pane > .spinner > .button > .right-arrow {\n  -fx-shape: \"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-date-month-year-fg;\n}\n.date-picker-popup > .month-year-pane > .spinner > .label {\n  -fx-alignment: CENTER;\n  -fx-text-fill: -color-date-month-year-fg;\n}\n.date-picker-popup > .month-year-pane > .secondary-label {\n  -fx-alignment: BASELINE_CENTER;\n  -fx-padding: 0.5em 0 0 0;\n  -fx-text-fill: -color-date-month-year-fg;\n}\n.date-picker-popup > .calendar-grid {\n  -fx-background-color: -color-date-bg;\n  -fx-padding: 8px;\n  -fx-hgap: 5px;\n  -fx-vgap: 0;\n  -fx-background-radius: 0px;\n}\n.date-picker-popup > .calendar-grid > .date-cell {\n  -fx-background-color: transparent;\n  -fx-padding: 0;\n  -fx-alignment: BASELINE_CENTER;\n  -fx-opacity: 1;\n  -fx-text-fill: -color-date-day-fg;\n  -fx-cell-size: 2.5em;\n  -fx-font-size: 1em;\n}\n.date-picker-popup > .calendar-grid > .week-number-cell {\n  -fx-padding: 8px 4px 8px 4px;\n  -fx-background-color: -color-date-week-bg;\n  -fx-text-fill: -color-date-week-fg;\n  -fx-font-size: 1em;\n}\n.date-picker-popup > .calendar-grid > .day-cell {\n  -fx-padding: 8px 4px 8px 4px;\n  -fx-background-color: -color-date-day-bg;\n}\n.date-picker-popup > .calendar-grid > .day-cell > .secondary-text {\n  -fx-fill: -color-date-chrono-fg;\n}\n.date-picker-popup > .calendar-grid > .day-cell:disabled {\n  -fx-opacity: 0.4;\n}\n.date-picker-popup > .calendar-grid .day-name-cell {\n  -fx-padding: 8px 4px 8px 4px;\n  -fx-font-size: 0.9em;\n}\n.date-picker-popup > .calendar-grid > .hijrah-day-cell {\n  -fx-alignment: TOP_LEFT;\n  -fx-padding: 0.083333em 4px 0.083333em 0.333333em;\n  -fx-cell-size: 2.75em;\n}\n.date-picker-popup > .calendar-grid > .today {\n  -fx-background-color: -color-date-today-bg;\n  -fx-text-fill: -color-date-today-fg;\n  -fx-font-weight: bold;\n}\n\n.calendar {\n  -fx-effect: none;\n}\n.calendar > .top-node,\n.calendar > .bottom-node {\n  -fx-padding: 8px 16px 8px 16px;\n}\n.calendar > .month-year-pane {\n  -fx-padding: 8px 16px 8px 16px;\n  -fx-alignment: CENTER_LEFT;\n  -fx-spacing: 6px;\n}\n.calendar > .month-year-pane > .button {\n  -fx-background-color: transparent;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0;\n  -fx-cursor: hand;\n}\n.calendar > .month-year-pane > .back-button {\n  -fx-padding: 0 1em 0 0;\n}\n.calendar > .month-year-pane > .back-button > .left-arrow {\n  -fx-shape: \"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-date-month-year-fg;\n}\n.calendar > .month-year-pane > .forward-button > .right-arrow {\n  -fx-shape: \"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-date-month-year-fg;\n}\n.calendar > .month-year-pane > .label {\n  -fx-text-fill: -color-date-month-year-fg;\n  -fx-font-size: 1.1em;\n}\n.calendar:disabled > .calendar-grid {\n  -fx-opacity: 0.4;\n}\n.calendar:disabled > .calendar-grid > .day-cell:disabled {\n  -fx-opacity: 1;\n}\n\n.edge-to-edge > .calendar.date-picker-popup {\n  -fx-background-color: -color-date-bg;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0;\n}\n\n.date-picker-popup > .calendar-grid > .selected,\n.date-picker-popup > .calendar-grid > .selected > .secondary-text,\n.date-picker-popup > .calendar-grid > .previous-month.selected,\n.date-picker-popup > .calendar-grid > .previous-month.today.selected,\n.date-picker-popup > .calendar-grid > .next-month.today.selected,\n.date-picker-popup > .calendar-grid > .next-month.selected {\n  -fx-background-color: -color-date-day-bg-selected;\n  -fx-text-fill: -color-date-day-fg-selected;\n  -fx-fill: -color-date-day-fg-selected;\n  -fx-font-weight: normal;\n}\n\n.date-picker-popup > .calendar-grid > .day-cell:hover {\n  -fx-background-color: -color-date-day-bg-hover;\n}\n\n.date-picker-popup > .calendar-grid > .today:hover {\n  -fx-background-color: -color-date-today-bg;\n  -fx-text-fill: -color-date-today-fg;\n}\n\n.date-picker-popup > .calendar-grid > .selected:hover {\n  -fx-background-color: -color-date-day-bg-selected;\n  -fx-text-fill: -color-date-day-fg-selected;\n  -fx-fill: -color-date-day-fg-selected;\n}\n\n.date-picker-popup > .calendar-grid > .previous-month,\n.date-picker-popup > .calendar-grid > .next-month,\n.date-picker-popup > .calendar-grid > .previous-month.today,\n.date-picker-popup > .calendar-grid > .next-month.today,\n.date-picker-popup > .calendar-grid > .previous-month > .secondary-text,\n.date-picker-popup > .calendar-grid > .next-month > .secondary-text {\n  -fx-text-fill: -color-date-other-month-fg;\n  -fx-fill: -color-date-other-month-fg;\n  -fx-font-weight: normal;\n}\n\n.menu-bar {\n  -fx-background-color: transparent;\n  -fx-background-insets: 0 0 1 0;\n  -fx-background-radius: 0;\n  -fx-padding: 0;\n}\n.menu-bar > .container {\n  -fx-padding: 0;\n}\n.menu-bar > .container > .menu-button {\n  -fx-background-color: transparent;\n  -fx-background-insets: 0 0 1px 0;\n  -fx-background-radius: 0px;\n  -fx-padding: 6 9 6 9;\n  -fx-effect: none;\n}\n.menu-bar > .container > .menu-button > .label {\n  -fx-padding: 0;\n  -fx-text-fill: -color-fg-default;\n}\n.menu-bar > .container > .menu-button > .arrow-button {\n  -fx-padding: 0;\n}\n.menu-bar > .container > .menu-button > .arrow-button > .arrow {\n  -fx-padding: 0;\n  -fx-background-color: transparent;\n}\n.menu-bar > .container > .menu-button:hover, .menu-bar > .container > .menu-button:focused, .menu-bar > .container > .menu-button:showing {\n  -fx-background-color: -color-base-8, -color-base-8;\n}\n\n.menu {\n  -fx-background-color: transparent;\n}\n.menu > .right-container > .arrow {\n  -fx-shape: \"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-fg-muted;\n}\n\n.menu-up-arrow {\n  -fx-shape: \"M7 14l5-5 5 5z\";\n  -fx-scale-shape: true;\n  -fx-background-color: -color-fg-muted;\n  -fx-padding: 3px 4px 3px 4px;\n}\n\n.menu-down-arrow {\n  -fx-shape: \"M7 10l5 5 5-5z\";\n  -fx-scale-shape: true;\n  -fx-background-color: -color-fg-muted;\n  -fx-padding: 3px 4px 3px 4px;\n}\n\n.menu-item {\n  -fx-background-color: transparent;\n  -fx-padding: 8px 8px 8px 8px;\n  -fx-background-radius: 0px;\n}\n.menu-item > .graphic-container {\n  -fx-padding: 0 6px 0 0;\n}\n.menu-item > .label {\n  -fx-padding: 0 1em 0 0;\n  -fx-text-fill: -color-fg-default;\n}\n.menu-item > .left-container {\n  -fx-padding: 0 1em 0 0;\n}\n.menu-item > .right-container {\n  -fx-padding: 0 0 0 0.5em;\n}\n.menu-item:focused {\n  -fx-background-color: -color-base-8;\n}\n.menu-item:focused > .label {\n  -fx-text-fill: -color-fg-emphasis;\n}\n.menu-item:focused > .right-container > .arrow {\n  -fx-background-color: -color-fg-emphasis;\n}\n.menu-item:focused .font-icon, .menu-item:focused .ikonli-font-icon {\n  -fx-icon-color: -color-fg-emphasis;\n  -fx-fill: -color-fg-emphasis;\n}\n.menu-item:disabled {\n  -fx-opacity: 0.4;\n}\n\n.radio-menu-item:checked > .left-container > .radio,\n.check-menu-item:checked > .left-container > .check {\n  -fx-shape: \"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\";\n  -fx-scale-shape: true;\n  -fx-background-color: -color-fg-muted;\n  -fx-min-height: 0.85em;\n  -fx-min-width: 0.85em;\n  -fx-max-height: 0.85em;\n  -fx-max-width: 0.85em;\n}\n\n.radio-menu-item:hover:checked > .left-container > .radio,\n.radio-menu-item:focused:checked > .left-container > .radio,\n.check-menu-item:hover:checked > .left-container > .check,\n.check-menu-item:focused:checked > .left-container > .check {\n  -fx-background-color: -color-fg-emphasis;\n}\n\n.caption-menu-item {\n  -fx-padding: 8px 8px 8px 8px;\n}\n.caption-menu-item:hover, .caption-menu-item:focused, .caption-menu-item:pressed {\n  -fx-background-color: transparent;\n}\n.caption-menu-item:hover > .label, .caption-menu-item:focused > .label, .caption-menu-item:pressed > .label {\n  -fx-text-fill: -color-fg-muted;\n}\n.caption-menu-item > .label {\n  -fx-text-fill: -color-fg-muted;\n}\n.caption-menu-item > .label > .text {\n  -fx-font-weight: bold;\n}\n\n.context-menu {\n  -fx-background-color: -color-border-default, -color-bg-default;\n  -fx-background-insets: 0, 1;\n  -fx-padding: 1;\n  -fx-background-radius: 0px, 0;\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0px, 0, 2);\n}\n.context-menu > .scroll-arrow {\n  -fx-padding: 0.5em;\n  -fx-background-color: transparent;\n}\n.context-menu > .scroll-arrow:hover {\n  -fx-background-color: -color-base-8;\n  -fx-text-fill: -color-fg-emphasis;\n}\n.context-menu .separator:horizontal {\n  -fx-padding: 0.25em 0 0.25em 0;\n}\n.context-menu .separator:horizontal .line {\n  -fx-border-color: -color-border-muted transparent transparent transparent;\n  -fx-border-insets: 1px 0.5em 0 0.5em;\n}\n\n.context-menu:show-mnemonics > .mnemonic-underline,\n.menu:show-mnemonics > .mnemonic-underline,\n.menu-bar:show-mnemonics > .mnemonic-underline,\n.menu-item > .label:show-mnemonics > .mnemonic-underline {\n  -fx-stroke: -color-fg-default;\n}\n\n.menu-button,\n.split-menu-button {\n  -color-button-bg: rgb(28, 28, 28);\n  -color-button-fg: -color-fg-default;\n  -color-button-border: -color-border-default;\n  -color-button-bg-hover: -color-base-6;\n  -color-button-fg-hover: -color-button-fg;\n  -color-button-border-hover: -color-button-border;\n  -color-button-bg-focused: -color-button-bg;\n  -color-button-fg-focused: -color-button-fg;\n  -color-button-border-focused: -color-accent-emphasis;\n  -color-button-bg-pressed: -color-bg-subtle;\n  -color-button-fg-pressed: -color-button-fg;\n  -color-button-border-pressed: transparent;\n  -color-button-shadow: -color-shadow-default;\n  -fx-background-color: -color-button-border, -color-button-bg;\n  -fx-background-insets: 0, 1px;\n  -fx-background-radius: 0px, 0;\n  -fx-graphic-text-gap: 6px;\n  -fx-text-fill: -color-button-fg;\n  -fx-alignment: CENTER;\n  -fx-effect: dropshadow(gaussian, -color-button-shadow, 3px, -2, 0, 1);\n  -fx-padding: 0;\n  -fx-alignment: CENTER_LEFT;\n}\n.menu-button .font-icon, .menu-button .ikonli-font-icon,\n.split-menu-button .font-icon,\n.split-menu-button .ikonli-font-icon {\n  -fx-icon-color: -color-button-fg;\n  -fx-fill: -color-button-fg;\n}\n.menu-button:disabled,\n.split-menu-button:disabled {\n  -fx-opacity: 0.4;\n  -fx-effect: none;\n}\n.menu-button:show-mnemonics > .mnemonic-underline,\n.split-menu-button:show-mnemonics > .mnemonic-underline {\n  -fx-stroke: -color-button-fg;\n}\n.menu-button.button-icon,\n.split-menu-button.button-icon {\n  -fx-padding: 6px;\n  -fx-content-display: graphic-only;\n}\n.menu-button.button-circle,\n.split-menu-button.button-circle {\n  -fx-background-radius: 50;\n  -fx-padding: 6px 8px 6px 8px;\n  -fx-content-display: graphic-only;\n  -fx-effect: none;\n}\n.menu-button.left-pill,\n.split-menu-button.left-pill {\n  -fx-background-radius: 0px 0 0 0px, 0 0 0 0;\n  -fx-background-insets: 0, 1px 0 1px 1px;\n  -fx-effect: none;\n}\n.menu-button.center-pill,\n.split-menu-button.center-pill {\n  -fx-background-radius: 0;\n  -fx-background-insets: 0, 1px 0 1px 0;\n  -fx-effect: none;\n}\n.menu-button.right-pill,\n.split-menu-button.right-pill {\n  -fx-background-radius: 0 0px 0px 0, 0 0 0 0;\n  -fx-background-insets: 0, 1px 1px 1px 0;\n  -fx-effect: none;\n}\n.menu-button > .label,\n.split-menu-button > .label {\n  -fx-padding: 6px 12px 6px 12px;\n  -fx-text-fill: -color-button-fg;\n}\n.menu-button > .arrow-button,\n.split-menu-button > .arrow-button {\n  -fx-padding: 6px 12px 6px 0;\n}\n.menu-button > .arrow-button > .arrow,\n.split-menu-button > .arrow-button > .arrow {\n  -fx-shape: \"M10 17l5-5-5-5v10z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-button-fg;\n  -fx-min-width: 0.5em;\n}\n.menu-button:openvertically > .arrow-button > .arrow,\n.split-menu-button:openvertically > .arrow-button > .arrow {\n  -fx-shape: \"M7 10l5 5 5-5z\";\n  -fx-scale-shape: false;\n}\n.menu-button:show-mnemonics > .label > .mnemonic-underline,\n.split-menu-button:show-mnemonics > .label > .mnemonic-underline {\n  -fx-stroke: -color-button-fg;\n}\n.menu-button.button-icon,\n.split-menu-button.button-icon {\n  -fx-padding: 0;\n}\n.menu-button:hover,\n.split-menu-button:hover {\n  -fx-background-color: -color-button-border-hover, -color-button-bg-hover;\n  -fx-opacity: 0.9;\n}\n.menu-button:hover > .label,\n.split-menu-button:hover > .label {\n  -fx-text-fill: -color-button-fg-hover;\n}\n.menu-button:hover > .arrow-button > .arrow,\n.split-menu-button:hover > .arrow-button > .arrow {\n  -fx-background-color: -color-button-fg-hover;\n}\n.menu-button:hover .font-icon, .menu-button:hover .ikonli-font-icon,\n.split-menu-button:hover .font-icon,\n.split-menu-button:hover .ikonli-font-icon {\n  -fx-icon-color: -color-button-fg-hover;\n  -fx-fill: -color-button-fg-hover;\n}\n.menu-button:focused,\n.split-menu-button:focused {\n  -fx-background-color: -color-button-border-focused, -color-button-bg-focused;\n}\n.menu-button:focused > .label,\n.split-menu-button:focused > .label {\n  -fx-text-fill: -color-button-fg-focused;\n}\n.menu-button:focused > .arrow-button > .arrow,\n.split-menu-button:focused > .arrow-button > .arrow {\n  -fx-background-color: -color-button-fg-focused;\n}\n.menu-button:focused .font-icon, .menu-button:focused .ikonli-font-icon,\n.split-menu-button:focused .font-icon,\n.split-menu-button:focused .ikonli-font-icon {\n  -fx-icon-color: -color-button-fg-focused;\n  -fx-fill: -color-button-fg-focused;\n}\n.menu-button:armed, .menu-button:focused:armed,\n.split-menu-button:armed,\n.split-menu-button:focused:armed {\n  -fx-background-color: -color-button-border-pressed, -color-button-bg-pressed;\n  -fx-text-fill: -color-button-fg-pressed;\n}\n.menu-button:armed > .label, .menu-button:focused:armed > .label,\n.split-menu-button:armed > .label,\n.split-menu-button:focused:armed > .label {\n  -fx-text-fill: -color-button-fg-pressed;\n}\n.menu-button:armed > .arrow-button > .arrow, .menu-button:focused:armed > .arrow-button > .arrow,\n.split-menu-button:armed > .arrow-button > .arrow,\n.split-menu-button:focused:armed > .arrow-button > .arrow {\n  -fx-background-color: -color-button-fg-pressed;\n}\n.menu-button:armed .font-icon, .menu-button:armed .ikonli-font-icon, .menu-button:focused:armed .font-icon, .menu-button:focused:armed .ikonli-font-icon,\n.split-menu-button:armed .font-icon,\n.split-menu-button:armed .ikonli-font-icon,\n.split-menu-button:focused:armed .font-icon,\n.split-menu-button:focused:armed .ikonli-font-icon {\n  -fx-icon-color: -color-button-fg-pressed;\n  -fx-fill: -color-button-fg-pressed;\n}\n.menu-button.button-outlined,\n.split-menu-button.button-outlined {\n  -color-button-bg: -color-bg-default;\n  -color-button-fg: -color-fg-default;\n  -color-button-bg-hover: -color-base-6;\n  -color-button-fg-hover: -color-button-fg;\n}\n.menu-button.accent,\n.split-menu-button.accent {\n  -color-button-bg: -color-accent-emphasis;\n  -color-button-fg: -color-fg-emphasis;\n  -color-button-border: -color-accent-emphasis;\n  -color-button-bg-hover: -color-accent-emphasis;\n  -color-button-fg-hover: -color-fg-emphasis;\n  -color-button-border-hover: -color-accent-emphasis;\n  -color-button-bg-focused: -color-accent-6;\n  -color-button-fg-focused: -color-fg-emphasis;\n  -color-button-border-focused: -color-accent-emphasis;\n  -color-button-bg-pressed: -color-accent-emphasis;\n  -color-button-fg-pressed: -color-fg-emphasis;\n  -color-button-border-pressed: transparent;\n}\n.menu-button.accent.button-outlined,\n.split-menu-button.accent.button-outlined {\n  -color-button-bg: -color-bg-default;\n  -color-button-fg: -color-accent-fg;\n  -color-button-bg-hover: -color-accent-emphasis;\n  -color-button-fg-hover: -color-fg-emphasis;\n}\n.menu-button.accent.flat,\n.split-menu-button.accent.flat {\n  -color-button-fg: -color-accent-fg;\n  -color-button-bg-hover: -color-accent-subtle;\n}\n.menu-button.success,\n.split-menu-button.success {\n  -color-button-bg: -color-success-emphasis;\n  -color-button-fg: -color-fg-emphasis;\n  -color-button-border: -color-success-emphasis;\n  -color-button-bg-hover: -color-success-emphasis;\n  -color-button-fg-hover: -color-fg-emphasis;\n  -color-button-border-hover: -color-success-emphasis;\n  -color-button-bg-focused: -color-success-7;\n  -color-button-fg-focused: -color-fg-emphasis;\n  -color-button-border-focused: -color-success-emphasis;\n  -color-button-bg-pressed: -color-success-emphasis;\n  -color-button-fg-pressed: -color-fg-emphasis;\n  -color-button-border-pressed: transparent;\n}\n.menu-button.success.button-outlined,\n.split-menu-button.success.button-outlined {\n  -color-button-bg: -color-bg-default;\n  -color-button-fg: -color-success-fg;\n  -color-button-bg-hover: -color-success-emphasis;\n  -color-button-fg-hover: -color-fg-emphasis;\n}\n.menu-button.success.flat,\n.split-menu-button.success.flat {\n  -color-button-fg: -color-success-fg;\n  -color-button-bg-hover: -color-success-subtle;\n}\n.menu-button.danger,\n.split-menu-button.danger {\n  -color-button-bg: -color-danger-emphasis;\n  -color-button-fg: -color-fg-emphasis;\n  -color-button-border: -color-danger-emphasis;\n  -color-button-bg-hover: -color-danger-emphasis;\n  -color-button-fg-hover: -color-fg-emphasis;\n  -color-button-border-hover: -color-danger-emphasis;\n  -color-button-bg-focused: -color-danger-6;\n  -color-button-fg-focused: -color-fg-emphasis;\n  -color-button-border-focused: -color-danger-emphasis;\n  -color-button-bg-pressed: -color-danger-emphasis;\n  -color-button-fg-pressed: -color-fg-emphasis;\n  -color-button-border-pressed: transparent;\n}\n.menu-button.danger.button-outlined,\n.split-menu-button.danger.button-outlined {\n  -color-button-bg: -color-bg-default;\n  -color-button-fg: -color-danger-fg;\n  -color-button-bg-hover: -color-danger-emphasis;\n  -color-button-fg-hover: -color-fg-emphasis;\n}\n.menu-button.danger.flat,\n.split-menu-button.danger.flat {\n  -color-button-fg: -color-danger-fg;\n  -color-button-bg-hover: -color-danger-subtle;\n}\n.menu-button.flat,\n.split-menu-button.flat {\n  -color-button-bg: transparent;\n  -color-button-fg: -color-fg-default;\n  -color-button-border: transparent;\n  -color-button-bg-hover: -color-bg-subtle;\n  -color-button-fg-hover: -color-button-fg;\n  -color-button-border-hover: -color-bg-subtle;\n  -color-button-bg-focused: -color-button-bg;\n  -color-button-fg-focused: -color-button-fg;\n  -color-button-border-focused: -color-button-bg;\n  -color-button-bg-pressed: -color-button-bg;\n  -color-button-fg-pressed: -color-button-fg;\n  -color-button-border-pressed: transparent;\n  -fx-effect: none;\n}\n.menu-button:disabled > .label,\n.split-menu-button:disabled > .label {\n  -fx-opacity: 1;\n}\n\n.menu-button.no-arrow > .arrow-button {\n  -fx-padding: 0;\n}\n.menu-button.no-arrow > .arrow-button > .arrow {\n  -fx-shape: none;\n  -fx-scale-shape: false;\n  -fx-min-width: -1;\n}\n\n.split-menu-button > .label {\n  -fx-padding: 6px 6px 6px 12px;\n}\n.split-menu-button:hover > .arrow-button, .split-menu-button:focused:hover > .arrow-button {\n  -fx-background-color: -color-accent-emphasis;\n  -fx-background-insets: 1px;\n  -fx-background-radius: 0;\n  -fx-border-color: transparent;\n  -fx-opacity: 1;\n}\n.split-menu-button:hover > .arrow-button > .arrow, .split-menu-button:focused:hover > .arrow-button > .arrow {\n  -fx-background-color: -color-fg-emphasis;\n  -fx-opacity: 1;\n}\n.split-menu-button:default:hover > .arrow-button, .split-menu-button.accent:hover > .arrow-button, .split-menu-button.success:hover > .arrow-button, .split-menu-button.danger:hover > .arrow-button {\n  -fx-background-color: -color-fg-emphasis;\n}\n.split-menu-button:default:hover > .arrow-button > .arrow, .split-menu-button.accent:hover > .arrow-button > .arrow, .split-menu-button.success:hover > .arrow-button > .arrow, .split-menu-button.danger:hover > .arrow-button > .arrow {\n  -fx-background-color: -color-button-bg-hover;\n}\n.split-menu-button.button-outlined:hover > .arrow-button, .split-menu-button.button-outlined:focused > .arrow-button {\n  -color-button-fg: -color-fg-default;\n}\n.split-menu-button.button-outlined:hover:default > .arrow-button, .split-menu-button.button-outlined:hover.accent > .arrow-button, .split-menu-button.button-outlined:hover.success > .arrow-button, .split-menu-button.button-outlined:hover.danger > .arrow-button, .split-menu-button.button-outlined:focused:default > .arrow-button, .split-menu-button.button-outlined:focused.accent > .arrow-button, .split-menu-button.button-outlined:focused.success > .arrow-button, .split-menu-button.button-outlined:focused.danger > .arrow-button {\n  -color-button-fg: -color-fg-emphasis;\n}\n.split-menu-button > .arrow-button {\n  -fx-padding: 6px 12px 6px 12px;\n  -fx-background-radius: 0 0 0 0;\n  -fx-border-color: -color-button-fg;\n  -fx-border-width: 0 0 0 0.75px;\n  -fx-border-insets: 7px 0 7px 0;\n}\n\n.radio-button {\n  -fx-background-color: -color-bg-default;\n  -fx-text-fill: -color-fg-default;\n  -fx-label-padding: 2px 2px 0 6px;\n}\n.radio-button > .radio {\n  -fx-background-color: -color-border-default, -color-bg-default;\n  -fx-background-insets: 0, 1.5px;\n  -fx-background-radius: 1em;\n  -fx-padding: 3px;\n  -fx-alignment: CENTER;\n}\n.radio-button > .radio > .dot {\n  -fx-background-color: transparent, transparent;\n  -fx-background-insets: 0, 1px;\n  -fx-background-radius: 1em;\n  -fx-min-height: 0.85em;\n  -fx-max-height: 0.85em;\n  -fx-min-width: 0.85em;\n  -fx-max-width: 0.85em;\n}\n.radio-button > .radio:hover {\n  -fx-background-color: -color-fg-muted, -color-bg-subtle;\n}\n.radio-button:disabled {\n  -fx-opacity: 0.4;\n}\n.radio-button:disabled > .radio {\n  -fx-opacity: 0.4;\n}\n.radio-button:selected > .radio {\n  -fx-background-color: -color-accent-emphasis, -color-accent-emphasis;\n}\n.radio-button:selected > .radio > .dot {\n  -fx-background-color: -color-accent-emphasis, -color-fg-emphasis;\n  -fx-background-insets: 0, 2px;\n}\n.radio-button:show-mnemonics > .mnemonic-underline {\n  -fx-stroke: -color-fg-muted;\n}\n\n.slider {\n  -color-slider-thumb: -color-fg-default;\n  -color-slider-thumb-border: -color-fg-default;\n  -color-slider-track: -color-border-muted;\n  -color-slider-track-progress: -color-accent-emphasis;\n  -color-slider-tick: -color-fg-muted;\n}\n.slider.large {\n  -color-slider-thumb: -color-fg-default;\n  -color-slider-thumb-border: -color-fg-default;\n}\n.slider > .thumb {\n  -fx-background-color: -color-slider-thumb-border, -color-slider-thumb;\n  -fx-background-insets: 0, 0.5px;\n  -fx-background-radius: 50px;\n  -fx-effect: dropshadow(gaussian, -color-border-default, 3px, 0.25, 0, 1);\n}\n.slider > .track {\n  -fx-background-color: transparent, -color-slider-track;\n  -fx-background-radius: 0px;\n}\n.slider > .axis {\n  -fx-tick-label-fill: -color-slider-tick;\n  -fx-tick-length: 5px;\n  -fx-minor-tick-length: 3px;\n}\n.slider > .axis > .axis-tick-mark,\n.slider > .axis > .axis-minor-tick-mark {\n  -fx-stroke: -color-slider-tick;\n}\n.slider:disabled {\n  -fx-opacity: 0.4;\n}\n.slider:horizontal > .thumb {\n  -fx-padding: 10px 10px 10px 10px;\n}\n.slider:horizontal > .track {\n  -fx-padding: 10px 0 10px 0;\n  -fx-background-insets: 0, 6px 0 6px 0;\n}\n.slider.small:horizontal > .thumb {\n  -fx-padding: 8px 8px 8px 8px;\n}\n.slider.small:horizontal > .track {\n  -fx-padding: 8px 0 8px 0;\n  -fx-background-insets: 0, 6px 0 6px 0;\n}\n.slider.large:horizontal > .thumb {\n  -fx-padding: 12px 12px 12px 12px;\n  -fx-effect: none;\n}\n.slider.large:horizontal > .track {\n  -fx-padding: 12px 0 12px 0;\n  -fx-background-insets: 0, 0px 0 0px 0;\n}\n.slider:vertical > .thumb {\n  -fx-padding: 10px 10px 10px 10px;\n}\n.slider:vertical > .track {\n  -fx-padding: 0 10px 0 10px;\n  -fx-background-insets: 0, 0 6px 0 6px;\n}\n.slider.small:vertical > .thumb {\n  -fx-padding: 8px 8px 8px 8px;\n}\n.slider.small:vertical > .track {\n  -fx-padding: 0 8px 0 8px;\n  -fx-background-insets: 0, 0 6px 0 6px;\n}\n.slider.large:vertical > .thumb {\n  -fx-padding: 12px 12px 12px 12px;\n}\n.slider.large:vertical > .track {\n  -fx-padding: 0 12px 0 12px;\n  -fx-background-insets: 0, 0 0px 0 0px;\n}\n.slider.progress-slider > .progress {\n  -fx-background-color: transparent, -color-slider-track-progress;\n}\n.slider.progress-slider:horizontal > .progress {\n  -fx-background-insets: 0, 6px 0 6px 0;\n  -fx-background-radius: 0px 0 0 0px;\n}\n.slider.progress-slider.small:horizontal > .progress {\n  -fx-padding: 8px 0 8px 0;\n  -fx-background-insets: 0, 6px 0 6px 0;\n}\n.slider.progress-slider.large:horizontal > .progress {\n  -fx-padding: 12px 0 12px 0;\n  -fx-background-insets: 0, 0px 0 0px 0;\n}\n.slider.progress-slider:vertical > .progress {\n  -fx-background-radius: 0 0 0px 0px;\n  -fx-background-insets: 0, 0 6px 0 6px;\n}\n.slider.progress-slider.small:vertical > .progress {\n  -fx-padding: 8px 0 8px 0;\n  -fx-background-insets: 0, 0 6px 0 6px;\n}\n.slider.progress-slider.large:vertical > .progress {\n  -fx-padding: 0 12px 0 12px;\n  -fx-background-insets: 0, 0 0px 0 0px;\n}\n\n.spinner {\n  -fx-background-color: -color-bg-default;\n  -fx-border-color: -color-border-default;\n  -fx-border-radius: 0px;\n  -fx-border-width: 1px;\n}\n.spinner > .text-field {\n  -fx-background-radius: 0px 0 0 0px;\n  -fx-background-insets: 0;\n  -fx-padding: 5px 11px 5px 11px;\n}\n.spinner > .text-field:readonly {\n  -fx-background-color: -color-input-border, -color-input-bg;\n}\n.spinner > .text-field:readonly:focused {\n  -fx-background-color: -color-input-border-focused, -color-input-bg-focused;\n}\n.spinner > .increment-arrow-button {\n  -fx-background-color: -color-base-9;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0 0px 0 0;\n  -fx-padding: 10px;\n}\n.spinner > .increment-arrow-button:hover {\n  -fx-background-color: -color-accent-3;\n}\n.spinner > .increment-arrow-button > .increment-arrow {\n  -fx-background-color: -color-fg-emphasis;\n  -fx-background-insets: 0;\n  -fx-padding: 0 0.25em 0 0.25em;\n  -fx-shape: \"M7 14l5-5 5 5z\";\n  -fx-scale-shape: false;\n}\n.spinner > .decrement-arrow-button {\n  -fx-background-color: -color-base-9;\n  -fx-background-insets: -1 0 0 0;\n  -fx-background-radius: 0 0 0px 0;\n  -fx-padding: 10px;\n}\n.spinner > .decrement-arrow-button:hover {\n  -fx-background-color: -color-accent-3;\n}\n.spinner > .decrement-arrow-button > .decrement-arrow {\n  -fx-background-color: -color-fg-emphasis;\n  -fx-background-insets: 0;\n  -fx-padding: 0 0.25em 0 0.25em;\n  -fx-shape: \"M7 10l5 5 5-5z\";\n  -fx-scale-shape: false;\n}\n.spinner:disabled {\n  -fx-opacity: 0.4;\n}\n.spinner:focused:focused, .spinner:contains-focus:focused {\n  -fx-border-color: -color-accent-emphasis;\n}\n.spinner.arrows-on-left-vertical > .text-field {\n  -fx-background-radius: 0 0px 0px 0;\n  -fx-alignment: CENTER_RIGHT;\n}\n.spinner.arrows-on-left-vertical > .increment-arrow-button {\n  -fx-background-radius: 0px 0 0 0;\n}\n.spinner.arrows-on-left-vertical > .decrement-arrow-button {\n  -fx-background-radius: 0 0 0 0px;\n}\n.spinner.arrows-on-right-horizontal > .increment-arrow-button {\n  -fx-background-radius: 0 0px 0px 0;\n  -fx-background-insets: 0;\n}\n.spinner.arrows-on-right-horizontal > .increment-arrow-button > .increment-arrow {\n  -fx-shape: \"M 18,12.857142 H 12.857142 V 18 H 11.142858 V 12.857142 H 6 v -1.714284 h 5.142858 V 6 h 1.714284 v 5.142858 H 18 Z\";\n  -fx-scale-shape: false;\n}\n.spinner.arrows-on-right-horizontal > .decrement-arrow-button {\n  -fx-background-radius: 0;\n  -fx-background-insets: 0;\n}\n.spinner.arrows-on-right-horizontal > .decrement-arrow-button > .decrement-arrow {\n  -fx-shape: \"M 17,13 H 7 v -2 h 10 z\";\n  -fx-scale-shape: false;\n}\n.spinner.arrows-on-left-horizontal > .text-field {\n  -fx-background-radius: 0 0px 0px 0;\n  -fx-alignment: CENTER_RIGHT;\n}\n.spinner.arrows-on-left-horizontal > .increment-arrow-button {\n  -fx-background-radius: 0;\n  -fx-background-insets: 0;\n}\n.spinner.arrows-on-left-horizontal > .increment-arrow-button > .increment-arrow {\n  -fx-shape: \"M 18,12.857142 H 12.857142 V 18 H 11.142858 V 12.857142 H 6 v -1.714284 h 5.142858 V 6 h 1.714284 v 5.142858 H 18 Z\";\n  -fx-scale-shape: false;\n}\n.spinner.arrows-on-left-horizontal > .decrement-arrow-button {\n  -fx-background-radius: 0px 0 0 0px;\n  -fx-background-insets: 0;\n}\n.spinner.arrows-on-left-horizontal > .decrement-arrow-button > .decrement-arrow {\n  -fx-shape: \"M 17,13 H 7 v -2 h 10 z\";\n  -fx-scale-shape: false;\n}\n.spinner.split-arrows-horizontal > .text-field {\n  -fx-background-radius: 0;\n  -fx-alignment: CENTER;\n}\n.spinner.split-arrows-horizontal > .increment-arrow-button {\n  -fx-background-radius: 0 0px 0px 0;\n  -fx-background-insets: 0;\n}\n.spinner.split-arrows-horizontal > .increment-arrow-button > .increment-arrow {\n  -fx-shape: \"M 18,12.857142 H 12.857142 V 18 H 11.142858 V 12.857142 H 6 v -1.714284 h 5.142858 V 6 h 1.714284 v 5.142858 H 18 Z\";\n  -fx-scale-shape: false;\n}\n.spinner.split-arrows-horizontal > .decrement-arrow-button {\n  -fx-background-radius: 0px 0 0 0px;\n  -fx-background-insets: 0;\n}\n.spinner.split-arrows-horizontal > .decrement-arrow-button > .decrement-arrow {\n  -fx-shape: \"M 17,13 H 7 v -2 h 10 z\";\n  -fx-scale-shape: false;\n}\n.spinner.split-arrows-vertical > .text-field {\n  -fx-background-radius: 0;\n  -fx-alignment: CENTER;\n}\n.spinner.split-arrows-vertical > .increment-arrow-button {\n  -fx-background-radius: 0px 0px 0 0;\n  -fx-background-insets: 0;\n}\n.spinner.split-arrows-vertical > .increment-arrow-button > .increment-arrow {\n  -fx-shape: \"M 18,12.857142 H 12.857142 V 18 H 11.142858 V 12.857142 H 6 v -1.714284 h 5.142858 V 6 h 1.714284 v 5.142858 H 18 Z\";\n  -fx-scale-shape: false;\n  -fx-padding: 0.25em 0 0.25em 0;\n}\n.spinner.split-arrows-vertical > .decrement-arrow-button {\n  -fx-background-radius: 0 0 0px 0px;\n  -fx-background-insets: 0;\n}\n.spinner.split-arrows-vertical > .decrement-arrow-button > .decrement-arrow {\n  -fx-shape: \"M 17,13 H 7 v -2 h 10 z\";\n  -fx-scale-shape: false;\n  -fx-padding: 0.25em 0 0.25em 0;\n}\n\n.split-pane {\n  -color-split-divider: -color-border-subtle;\n  -color-split-divider-pressed: -color-accent-emphasis;\n  -color-split-grabber: -color-fg-muted;\n  -color-split-grabber-pressed: -color-accent-emphasis;\n  -fx-background-color: transparent;\n  -fx-background-insets: 0;\n  -fx-padding: 0;\n}\n.split-pane > .split-pane-divider {\n  -fx-background-color: -color-split-divider;\n  -fx-padding: 0 1px 0 1px;\n  -fx-opacity: 0.5;\n}\n.split-pane > .split-pane-divider > .horizontal-grabber {\n  -fx-background-color: -color-split-grabber;\n  -fx-padding: 5px 1px 5px 1px;\n}\n.split-pane > .split-pane-divider > .vertical-grabber {\n  -fx-background-color: -color-split-grabber;\n  -fx-padding: 1px 5px 1px 5px;\n}\n.split-pane > .split-pane-divider:pressed {\n  -fx-background-color: -color-split-divider-pressed;\n}\n.split-pane > .split-pane-divider:pressed > .horizontal-grabber,\n.split-pane > .split-pane-divider:pressed > .vertical-grabber {\n  -fx-background-color: -color-split-grabber-pressed;\n}\n.split-pane > .split-pane-divider:hover {\n  -fx-opacity: 1;\n}\n.split-pane > .split-pane-divider:disabled {\n  -fx-opacity: 0.25;\n}\n\n.tab-pane {\n  -color-tab-bg-selected: -color-bg-default;\n  -color-tab-fg-selected: -color-fg-default;\n  -color-tab-border-selected: -color-accent-4;\n}\n.tab-pane > .tab-header-area {\n  -fx-background-insets: 0;\n  -fx-background-color: rgb(35, 35, 35);\n  -fx-alignment: CENTER;\n}\n.tab-pane > .tab-header-area > .tab-header-background {\n  -fx-background-insets: 0 0 0 0, 0 0 1px 0;\n  -fx-background-color: -color-border-default, rgb(35, 35, 35);\n}\n.tab-pane > .tab-header-area > .headers-region > .tab {\n  -fx-background-insets: 0 0 0 0, 0 0 1px 0;\n  -fx-background-color: transparent, transparent;\n  -fx-padding: 0.3em 0.6em 0.3em 0.6em;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {\n  -fx-alignment: CENTER;\n  -fx-text-fill: -color-fg-default;\n  -fx-padding: 0.4em 0.4em 0.4em 0.4em;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label > * {\n  -fx-fill: -color-fg-default;\n  -fx-icon-color: -color-fg-default;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-close-button {\n  -fx-background-color: -color-fg-default;\n  -fx-shape: \"M 0,0 H1 L 4,3 7,0 H8 V1 L 5,4 8,7 V8 H7 L 4,5 1,8 H0 V7 L 3,4 0,1 Z\";\n  -fx-scale-shape: false;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-close-button:hover {\n  -fx-cursor: hand;\n  -fx-scale-x: 1.1;\n  -fx-scale-y: 1.1;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:hover {\n  -fx-background-color: -color-base-4, -color-bg-subtle;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:top:selected, .tab-pane > .tab-header-area > .headers-region > .tab:bottom:selected {\n  -fx-background-color: -color-tab-border-selected, -color-tab-bg-selected;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:top:selected > .tab-container > .tab-label, .tab-pane > .tab-header-area > .headers-region > .tab:bottom:selected > .tab-container > .tab-label {\n  -fx-fill: -color-tab-fg-selected;\n  -fx-text-fill: -color-tab-fg-selected;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:top:selected > .tab-container > .tab-label > *, .tab-pane > .tab-header-area > .headers-region > .tab:bottom:selected > .tab-container > .tab-label > * {\n  -fx-fill: -color-tab-fg-selected;\n  -fx-icon-color: -color-tab-fg-selected;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:top:selected > .tab-container > .tab-close-button, .tab-pane > .tab-header-area > .headers-region > .tab:bottom:selected > .tab-container > .tab-close-button {\n  -fx-background-color: -color-tab-fg-selected;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:disabled {\n  -fx-background-color: -color-border-default, rgb(35, 35, 35);\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:disabled > .tab-container {\n  -fx-opacity: 0.4;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:left > .tab-container > .tab-label, .tab-pane > .tab-header-area > .headers-region > .tab:right > .tab-container > .tab-label {\n  -fx-padding: 0.2em 0.4em 0.2em 0.4em;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:left:hover, .tab-pane > .tab-header-area > .headers-region > .tab:right:hover {\n  -fx-background-color: -color-border-default, -color-bg-subtle;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:left:hover > .tab-container > .tab-label, .tab-pane > .tab-header-area > .headers-region > .tab:right:hover > .tab-container > .tab-label {\n  -fx-text-fill: -color-fg-default;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:left:hover > .tab-container > .tab-close-button, .tab-pane > .tab-header-area > .headers-region > .tab:right:hover > .tab-container > .tab-close-button {\n  -fx-background-color: -color-fg-default;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:left:selected, .tab-pane > .tab-header-area > .headers-region > .tab:right:selected {\n  -fx-background-color: -color-tab-border-selected, -color-tab-bg-selected;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:left:selected > .tab-container > .tab-label, .tab-pane > .tab-header-area > .headers-region > .tab:right:selected > .tab-container > .tab-label {\n  -fx-fill: -color-tab-fg-selected;\n  -fx-text-fill: -color-tab-fg-selected;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:left:selected > .tab-container > .tab-label > *, .tab-pane > .tab-header-area > .headers-region > .tab:right:selected > .tab-container > .tab-label > * {\n  -fx-fill: -color-tab-fg-selected;\n  -fx-icon-color: -color-tab-fg-selected;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:left:selected > .tab-container > .tab-close-button, .tab-pane > .tab-header-area > .headers-region > .tab:right:selected > .tab-container > .tab-close-button {\n  -fx-background-color: -color-tab-fg-selected;\n}\n.tab-pane > .tab-header-area > .headers-region > .tab:left:disabled, .tab-pane > .tab-header-area > .headers-region > .tab:right:disabled {\n  -fx-background-color: transparent;\n}\n.tab-pane > .tab-header-area > .control-buttons-tab > .container > .tab-down-button {\n  -fx-padding: 1em;\n}\n.tab-pane > .tab-header-area > .control-buttons-tab > .container > .tab-down-button:disabled {\n  -fx-opacity: 0.4;\n}\n.tab-pane > .tab-header-area > .control-buttons-tab > .container > .tab-down-button > .arrow {\n  -fx-shape: \"M7 10l5 5 5-5z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-fg-default;\n}\n.tab-pane.dense > .tab-header-area > .headers-region > .tab {\n  -fx-padding: 0.2em 0.3em 0.2em 0.3em;\n}\n.tab-pane.dense > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {\n  -fx-padding: 0.2em 0.2em 0.2em 0.2em;\n}\n.tab-pane.dense > .tab-header-area > .headers-region > .tab:left > .tab-container > .tab-label, .tab-pane.dense > .tab-header-area > .headers-region > .tab:right > .tab-container > .tab-label {\n  -fx-padding: 0.15em 0.3em 0.15em 0.3em;\n}\n.tab-pane.floating {\n  -color-tab-bg-selected: -color-base-6;\n  -color-tab-fg-selected: -color-fg-default;\n  -color-tab-border-selected: -color-border-default;\n}\n.tab-pane.floating > .tab-header-area {\n  -fx-background-color: -color-border-default, -color-bg-inset;\n  -fx-background-insets: 0, 0 0 1px 0;\n}\n.tab-pane.floating > .tab-header-area > .headers-region > .tab {\n  -fx-background-insets: 0;\n  -fx-background-color: transparent;\n  -fx-padding: 0.3em 0 0.3em 3px;\n}\n.tab-pane.floating > .tab-header-area > .headers-region > .tab > .tab-container {\n  -fx-background-color: -color-border-subtle, -color-base-9;\n  -fx-background-insets: 0, 1;\n  -fx-background-radius: 0px;\n  -fx-border-radius: 0px;\n  -fx-border-width: 1px, 0 3px 0 0;\n  -fx-border-color: transparent, transparent;\n}\n.tab-pane.floating > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {\n  -fx-padding: 0.6em 0.6em 0.6em 0.6em;\n  -fx-min-width: 150px;\n  -fx-pref-width: 150px;\n  -fx-alignment: CENTER_LEFT;\n}\n.tab-pane.floating > .tab-header-area > .headers-region > .tab:hover > .tab-container {\n  -fx-background-color: -color-tab-bg-selected;\n  -fx-border-color: -color-tab-border-selected, transparent;\n}\n.tab-pane.floating > .tab-header-area > .headers-region > .tab:hover > .tab-container > .tab-label {\n  -fx-fill: -color-tab-fg-selected;\n  -fx-text-fill: -color-tab-fg-selected;\n}\n.tab-pane.floating > .tab-header-area > .headers-region > .tab:hover > .tab-container > .tab-label > * {\n  -fx-fill: -color-tab-fg-selected;\n  -fx-icon-color: -color-tab-fg-selected;\n}\n.tab-pane.floating > .tab-header-area > .headers-region > .tab:hover > .tab-container > .tab-close-button {\n  -fx-background-color: -color-tab-fg-selected;\n}\n.tab-pane.floating > .tab-header-area > .headers-region > .tab:selected > .tab-container {\n  -fx-background-color: -color-tab-bg-selected;\n  -fx-border-color: -color-accent-2, transparent;\n}\n.tab-pane.floating > .tab-header-area > .headers-region > .tab:selected > .tab-container > .tab-label {\n  -fx-fill: -color-tab-fg-selected;\n  -fx-text-fill: -color-tab-fg-selected;\n}\n.tab-pane.floating > .tab-header-area > .headers-region > .tab:selected > .tab-container > .tab-label > * {\n  -fx-fill: -color-tab-fg-selected;\n  -fx-icon-color: -color-tab-fg-selected;\n}\n.tab-pane.floating > .tab-header-area > .headers-region > .tab:selected > .tab-container > .tab-close-button {\n  -fx-background-color: -color-tab-fg-selected;\n}\n.tab-pane.floating.dense > .tab-header-area > .headers-region > .tab {\n  -fx-padding: 0.2em 0 0.2em 3px;\n}\n.tab-pane.floating.dense > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {\n  -fx-padding: 0.4em 0.4em 0.4em 0.4em;\n}\n.tab-pane.classic {\n  -color-tab-bg-selected: -color-bg-default;\n  -color-tab-fg-selected: -color-fg-default;\n  -color-tab-border-selected: -color-border-muted;\n}\n.tab-pane.classic > .tab-header-area {\n  -fx-padding: 5px 0 0 5px;\n}\n.tab-pane.classic > .tab-header-area > .tab-header-background {\n  -fx-background-insets: 0 0 0 0, 0 0 1px 0;\n  -fx-background-color: -color-border-muted, -color-bg-subtle;\n}\n.tab-pane.classic > .tab-header-area > .headers-region > .tab {\n  -fx-background-insets: 0;\n  -fx-background-color: transparent;\n}\n.tab-pane.classic > .tab-header-area > .headers-region > .tab > .tab-container {\n  -fx-padding: 0;\n}\n.tab-pane.classic > .tab-header-area > .headers-region > .tab:top:selected, .tab-pane.classic > .tab-header-area > .headers-region > .tab:bottom:selected {\n  -fx-background-insets: 0 0 0 0, 1px 1px 0 1px;\n  -fx-background-color: -color-tab-border-selected, -color-tab-bg-selected;\n}\n.tab-pane.classic > .tab-header-area > .headers-region > .tab:left:selected, .tab-pane.classic > .tab-header-area > .headers-region > .tab:right:selected {\n  -fx-background-insets: 0 0 0 0, 1px 1px 0 1px;\n  -fx-background-color: -color-tab-border-selected, -color-tab-bg-selected;\n}\n.tab-pane.classic > .tab-header-area > .headers-region > .tab:hover > .tab-container, .tab-pane.classic > .tab-header-area > .headers-region > .tab:selected > .tab-container {\n  -fx-border-color: none;\n}\n.tab-pane.classic:bottom > .tab-header-area {\n  -fx-padding: 0 0 5px 5px;\n}\n.tab-pane.classic:right > .tab-header-area {\n  -fx-padding: 5px 5px 0 0;\n}\n\n.toggle-button {\n  -color-button-bg: rgb(28, 28, 28);\n  -color-button-fg: -color-fg-default;\n  -color-button-border: -color-border-default;\n  -color-button-bg-hover: -color-base-6;\n  -color-button-fg-hover: -color-button-fg;\n  -color-button-border-hover: -color-button-border;\n  -color-button-bg-focused: -color-button-bg;\n  -color-button-fg-focused: -color-button-fg;\n  -color-button-border-focused: -color-accent-emphasis;\n  -color-button-bg-pressed: -color-bg-subtle;\n  -color-button-fg-pressed: -color-button-fg;\n  -color-button-border-pressed: transparent;\n  -color-button-shadow: -color-shadow-default;\n  -fx-background-color: -color-button-border, -color-button-bg;\n  -fx-background-insets: 0, 1px;\n  -fx-background-radius: 0px, 0;\n  -fx-graphic-text-gap: 6px;\n  -fx-text-fill: -color-button-fg;\n  -fx-alignment: CENTER;\n  -fx-effect: dropshadow(gaussian, -color-button-shadow, 3px, -2, 0, 1);\n  -color-button-bg-selected: -color-accent-emphasis;\n  -color-button-fg-selected: -color-fg-emphasis;\n  -fx-padding: 6px 12px 6px 12px;\n}\n.toggle-button .font-icon, .toggle-button .ikonli-font-icon {\n  -fx-icon-color: -color-button-fg;\n  -fx-fill: -color-button-fg;\n}\n.toggle-button:disabled {\n  -fx-opacity: 0.4;\n  -fx-effect: none;\n}\n.toggle-button:show-mnemonics > .mnemonic-underline {\n  -fx-stroke: -color-button-fg;\n}\n.toggle-button.button-icon {\n  -fx-padding: 6px;\n  -fx-content-display: graphic-only;\n}\n.toggle-button.button-circle {\n  -fx-background-radius: 50;\n  -fx-padding: 6px 8px 6px 8px;\n  -fx-content-display: graphic-only;\n  -fx-effect: none;\n}\n.toggle-button.left-pill {\n  -fx-background-radius: 0px 0 0 0px, 0 0 0 0;\n  -fx-background-insets: 0, 1px 0 1px 1px;\n  -fx-effect: none;\n}\n.toggle-button.center-pill {\n  -fx-background-radius: 0;\n  -fx-background-insets: 0, 1px 0 1px 0;\n  -fx-effect: none;\n}\n.toggle-button.right-pill {\n  -fx-background-radius: 0 0px 0px 0, 0 0 0 0;\n  -fx-background-insets: 0, 1px 1px 1px 0;\n  -fx-effect: none;\n}\n.toggle-button:selected, .toggle-button:selected:focused {\n  -fx-background-color: -color-base-2, -color-button-bg-selected;\n  -fx-text-fill: -color-button-fg-selected;\n  -fx-background-insets: 0, 1;\n}\n.toggle-button:selected .font-icon, .toggle-button:selected .ikonli-font-icon, .toggle-button:selected:focused .font-icon, .toggle-button:selected:focused .ikonli-font-icon {\n  -fx-fill: -color-button-fg-selected;\n  -fx-icon-color: -color-button-fg-selected;\n}\n.toggle-button:show-mnemonics:selected > .mnemonic-underline {\n  -fx-stroke: -color-button-fg-selected;\n}\n.toggle-button.left-pill, .toggle-button.center-pill, .toggle-button.right-pill {\n  -fx-effect: dropshadow(gaussian, -color-button-shadow, 3px, -2, 0, 1);\n}\n.toggle-button:selected.left-pill:focused {\n  -fx-background-insets: 0, 1px;\n}\n.toggle-button:selected.center-pill:focused {\n  -fx-background-insets: 0, 1px;\n}\n.toggle-button:selected.right-pill:focused {\n  -fx-background-insets: 0, 1px;\n}\n\n.accordion > .titled-pane.first-titled-pane > .title {\n  -fx-background-insets: 0, 1px;\n  -fx-background-radius: 0px 0px 0 0;\n}\n.accordion > .titled-pane > .title {\n  -fx-background-insets: 0, 0 1px 1px 1px;\n  -fx-background-radius: 0;\n}\n\n/**\n== Structure ==\n.breadcrumbs {\n  >.crumb[:first|:last] { ... }\n  >.divider { ... }\n}\n*/\n.breadcrumbs {\n  -fx-padding: 6px 12px 6px 12px;\n}\n.breadcrumbs > .hyperlink {\n  -color-link-fg-visited: -color-link-fg;\n}\n.breadcrumbs > .label.divider {\n  -fx-padding: 0 0.5em 0 0.5em;\n}\n\n.card > .container {\n  -fx-background-color: -color-bg-default;\n  -fx-alignment: TOP_LEFT;\n  -fx-padding: 1em 0 1em 0;\n  -fx-spacing: 1em;\n  -fx-border-color: -color-border-default;\n  -fx-border-width: 1px;\n  -fx-border-radius: 0px;\n}\n.card > .container > .header {\n  -fx-alignment: TOP_LEFT;\n  -fx-padding: 0 0.75em 0 0.75em 0;\n}\n.card > .container > .sub-header {\n  -fx-alignment: TOP_LEFT;\n  -fx-padding: 0 0.75em 0 0.75em 0;\n}\n.card > .container > .body {\n  -fx-padding: 0 0.75em 0 0.75em;\n  -fx-alignment: TOP_LEFT;\n}\n.card > .container > .footer {\n  -fx-alignment: TOP_LEFT;\n  -fx-padding: 0 0.75em 0 0.75em 0;\n}\n.card > .container.elevated-1 {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 2px, 0.5, 0, 2);\n}\n.card > .container.elevated-2 {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0.5, 0, 2);\n}\n.card > .container.elevated-3 {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 16px, 0.5, 0, 2);\n}\n.card > .container.elevated-4 {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 20px, 0.5, 0, 2);\n}\n.card > .container.interactive:hover {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0.5, 0, 2);\n}\n.card > .container TextFlow Text:disabled {\n  -fx-opacity: 0.4;\n}\n.card:has-image > .container > .sub-header {\n  -fx-padding: 0;\n}\n.card:has-image > .container > .sub-header:disabled {\n  -fx-opacity: 0.4;\n}\n.card.edge-to-edge > .container {\n  -fx-border-width: 0;\n  -fx-border-radius: 0;\n  -fx-effect: none;\n}\n.card .tile > .container {\n  -fx-padding: 0;\n  -fx-background-radius: 0;\n}\n.card .tile > .container > .header > .title {\n  -fx-font-size: 1.25em;\n}\n\n.chart {\n  -fx-padding: 4px;\n}\n.chart > .chart-title {\n  -fx-font-size: 1.25em;\n}\n.chart > .chart-content {\n  -fx-padding: 10px;\n}\n.chart > .chart-content > .chart-plot-background {\n  -fx-background-color: -color-bg-default;\n}\n.chart:disabled > .chart-content {\n  -fx-opacity: 0.4;\n}\n.chart:disabled > .chart-content .label {\n  -fx-opacity: 1;\n}\n.chart > .chart-legend {\n  -fx-padding: 6px;\n}\n.chart .axis {\n  -fx-axis-color: -color-border-default;\n  -fx-tick-label-font-size: 0.8em;\n  -fx-tick-label-fill: -color-fg-default;\n}\n.chart .axis:top {\n  -fx-border-color: transparent transparent -fx-axis-color transparent;\n}\n.chart .axis:right {\n  -fx-border-color: transparent transparent transparent -fx-axis-color;\n}\n.chart .axis:bottom {\n  -fx-border-color: -fx-axis-color transparent transparent transparent;\n}\n.chart .axis:left {\n  -fx-border-color: transparent -fx-axis-color transparent transparent;\n}\n.chart .axis:top > .axis-label, .chart .axis:left > .axis-label {\n  -fx-padding: 0 0 4px 0;\n}\n.chart .axis:bottom > .axis-label, .chart .axis:right > .axis-label {\n  -fx-padding: 4px 0 0 0;\n}\n.chart .axis > .axis-tick-mark,\n.chart .axis > .axis-minor-tick-mark {\n  -fx-fill: none;\n  -fx-stroke: -fx-axis-color;\n}\n.chart .chart-horizontal-grid-lines,\n.chart .chart-vertical-grid-lines {\n  -fx-stroke: -color-border-muted;\n  -fx-stroke-dash-array: 0.25em, 0.25em;\n}\n.chart .chart-alternative-row-fill,\n.chart .chart-alternative-column-fill {\n  -fx-fill: none;\n  -fx-stroke: none;\n}\n.chart .chart-vertical-zero-line,\n.chart .chart-horizontal-zero-line {\n  -fx-stroke: -color-fg-default;\n}\n\n.chart-symbol {\n  -fx-background-color: -color-chart-1;\n  -fx-background-radius: 5px;\n  -fx-padding: 5px;\n}\n\n.default-color1.chart-symbol {\n  -fx-background-color: -color-chart-2;\n  -fx-background-radius: 0;\n}\n\n.default-color2.chart-symbol {\n  -fx-background-color: -color-chart-3;\n  -fx-background-radius: 0;\n  -fx-padding: 7px 5px 7px 5px;\n  -fx-shape: \"M5,0 L10,9 L5,18 L0,9 Z\";\n}\n\n.default-color3.chart-symbol {\n  -fx-background-color: -color-chart-4;\n  -fx-background-radius: 0;\n  -fx-background-insets: 0;\n  -fx-shape: \"M2,0 L5,4 L8,0 L10,0 L10,2 L6,5 L10,8 L10,10 L8,10 L5,6 L2,10 L0,10 L0,8 L4,5 L0,2 L0,0 Z\";\n}\n\n.default-color4.chart-symbol {\n  -fx-background-color: -color-chart-5;\n  -fx-background-radius: 0;\n  -fx-background-insets: 0;\n  -fx-shape: \"M5,0 L10,8 L0,8 Z\";\n}\n\n.default-color5.chart-symbol {\n  -fx-background-color: -color-chart-6, white;\n  -fx-background-insets: 0, 2;\n  -fx-background-radius: 5px;\n  -fx-padding: 5px;\n}\n\n.default-color6.chart-symbol {\n  -fx-background-color: -color-chart-7, white;\n  -fx-background-insets: 0, 2;\n  -fx-background-radius: 0;\n}\n\n.default-color7.chart-symbol {\n  -fx-background-color: -color-chart-8, white;\n  -fx-background-radius: 0;\n  -fx-background-insets: 0, 2.5;\n  -fx-padding: 7px 5px 7px 5px;\n  -fx-shape: \"M5,0 L10,9 L5,18 L0,9 Z\";\n}\n\n.chart-line-symbol {\n  -fx-background-color: -color-chart-1, white;\n  -fx-background-insets: 0, 2;\n  -fx-background-radius: 5px;\n  -fx-padding: 5px;\n}\n\n.chart-series-line {\n  -fx-stroke: -color-chart-1;\n  -fx-stroke-width: 3px;\n}\n\n.default-color0.chart-line-symbol {\n  -fx-background-color: -color-chart-1, white;\n}\n\n.default-color1.chart-line-symbol {\n  -fx-background-color: -color-chart-2, white;\n}\n\n.default-color2.chart-line-symbol {\n  -fx-background-color: -color-chart-3, white;\n}\n\n.default-color3.chart-line-symbol {\n  -fx-background-color: -color-chart-4, white;\n}\n\n.default-color4.chart-line-symbol {\n  -fx-background-color: -color-chart-5, white;\n}\n\n.default-color5.chart-line-symbol {\n  -fx-background-color: -color-chart-6, white;\n}\n\n.default-color6.chart-line-symbol {\n  -fx-background-color: -color-chart-7, white;\n}\n\n.default-color7.chart-line-symbol {\n  -fx-background-color: -color-chart-8, white;\n}\n\n.default-color0.chart-series-line {\n  -fx-stroke: -color-chart-1;\n}\n\n.default-color1.chart-series-line {\n  -fx-stroke: -color-chart-2;\n}\n\n.default-color2.chart-series-line {\n  -fx-stroke: -color-chart-3;\n}\n\n.default-color3.chart-series-line {\n  -fx-stroke: -color-chart-4;\n}\n\n.default-color4.chart-series-line {\n  -fx-stroke: -color-chart-5;\n}\n\n.default-color5.chart-series-line {\n  -fx-stroke: -color-chart-6;\n}\n\n.default-color6.chart-series-line {\n  -fx-stroke: -color-chart-7;\n}\n\n.default-color7.chart-series-line {\n  -fx-stroke: -color-chart-8;\n}\n\n.chart-area-symbol {\n  -fx-background-color: -color-chart-1, white;\n  -fx-background-insets: 0, 1;\n  -fx-background-radius: 4px;\n  -fx-padding: 3px;\n}\n\n.default-color0.chart-area-symbol {\n  -fx-background-color: -color-chart-1, white;\n}\n\n.default-color1.chart-area-symbol {\n  -fx-background-color: -color-chart-2, white;\n}\n\n.default-color2.chart-area-symbol {\n  -fx-background-color: -color-chart-3, white;\n}\n\n.default-color3.chart-area-symbol {\n  -fx-background-color: -color-chart-4, white;\n}\n\n.default-color4.chart-area-symbol {\n  -fx-background-color: -color-chart-5, white;\n}\n\n.default-color5.chart-area-symbol {\n  -fx-background-color: -color-chart-6, white;\n}\n\n.default-color6.chart-area-symbol {\n  -fx-background-color: -color-chart-7, white;\n}\n\n.default-color7.chart-area-symbol {\n  -fx-background-color: -color-chart-8, white;\n}\n\n.chart-series-area-line {\n  -fx-stroke: -color-chart-1;\n  -fx-stroke-width: 1px;\n}\n\n.default-color0.chart-series-area-line {\n  -fx-stroke: -color-chart-1;\n}\n\n.default-color1.chart-series-area-line {\n  -fx-stroke: -color-chart-2;\n}\n\n.default-color2.chart-series-area-line {\n  -fx-stroke: -color-chart-3;\n}\n\n.default-color3.chart-series-area-line {\n  -fx-stroke: -color-chart-4;\n}\n\n.default-color4.chart-series-area-line {\n  -fx-stroke: -color-chart-5;\n}\n\n.default-color5.chart-series-area-line {\n  -fx-stroke: -color-chart-6;\n}\n\n.default-color6.chart-series-area-line {\n  -fx-stroke: -color-chart-7;\n}\n\n.default-color7.chart-series-area-line {\n  -fx-stroke: -color-chart-8;\n}\n\n.chart-series-area-fill {\n  -fx-stroke: none;\n  -fx-fill: -color-chart-1-alpha20;\n}\n\n.default-color0.chart-series-area-fill {\n  -fx-fill: -color-chart-1-alpha20;\n}\n\n.default-color1.chart-series-area-fill {\n  -fx-fill: -color-chart-2-alpha20;\n}\n\n.default-color2.chart-series-area-fill {\n  -fx-fill: -color-chart-3-alpha20;\n}\n\n.default-color3.chart-series-area-fill {\n  -fx-fill: -color-chart-4-alpha20;\n}\n\n.default-color4.chart-series-area-fill {\n  -fx-fill: -color-chart-5-alpha20;\n}\n\n.default-color5.chart-series-area-fill {\n  -fx-fill: -color-chart-6-alpha20;\n}\n\n.default-color6.chart-series-area-fill {\n  -fx-fill: -color-chart-7-alpha20;\n}\n\n.default-color7.chart-series-area-fill {\n  -fx-fill: -color-chart-8-alpha20;\n}\n\n.area-legend-symbol {\n  -fx-padding: 6px;\n  -fx-background-radius: 6px;\n  -fx-background-insets: 0, 3;\n}\n\n.bubble-legend-symbol {\n  -fx-background-radius: 8px;\n  -fx-padding: 8px;\n}\n\n.chart-bubble {\n  -fx-bubble-fill: -color-chart-1-alpha70;\n  -fx-background-color: radial-gradient(center 50% 50%, radius 80%, derive(-fx-bubble-fill, 20%), derive(-fx-bubble-fill, -30%));\n}\n\n.default-color0.chart-bubble {\n  -fx-bubble-fill: -color-chart-1-alpha70;\n}\n\n.default-color1.chart-bubble {\n  -fx-bubble-fill: -color-chart-2-alpha70;\n}\n\n.default-color2.chart-bubble {\n  -fx-bubble-fill: -color-chart-3-alpha70;\n}\n\n.default-color3.chart-bubble {\n  -fx-bubble-fill: -color-chart-4-alpha70;\n}\n\n.default-color4.chart-bubble {\n  -fx-bubble-fill: -color-chart-5-alpha70;\n}\n\n.default-color5.chart-bubble {\n  -fx-bubble-fill: -color-chart-6-alpha70;\n}\n\n.default-color6.chart-bubble {\n  -fx-bubble-fill: -color-chart-7-alpha70;\n}\n\n.default-color7.chart-bubble {\n  -fx-bubble-fill: -color-chart-8-alpha70;\n}\n\n.chart-bar {\n  -fx-bar-fill: -color-chart-1;\n  -fx-background-color: linear-gradient(to right, derive(-fx-bar-fill, -4%), derive(-fx-bar-fill, -1%), derive(-fx-bar-fill, 0%), derive(-fx-bar-fill, -1%), derive(-fx-bar-fill, -6%));\n  -fx-background-insets: 0;\n}\n\n.chart-bar.danger {\n  -fx-background-insets: 1 0 0 0;\n}\n\n.bar-chart:horizontal .chart-bar {\n  -fx-background-insets: 0 0 0 1;\n}\n\n.bar-chart:horizontal .chart-bar,\n.stacked-bar-chart:horizontal .chart-bar {\n  -fx-background-color: linear-gradient(to bottom, derive(-fx-bar-fill, -4%), derive(-fx-bar-fill, -1%), derive(-fx-bar-fill, 0%), derive(-fx-bar-fill, -1%), derive(-fx-bar-fill, -6%));\n}\n\n.default-color0.chart-bar {\n  -fx-bar-fill: -color-chart-1;\n}\n\n.default-color1.chart-bar {\n  -fx-bar-fill: -color-chart-2;\n}\n\n.default-color2.chart-bar {\n  -fx-bar-fill: -color-chart-3;\n}\n\n.default-color3.chart-bar {\n  -fx-bar-fill: -color-chart-4;\n}\n\n.default-color4.chart-bar {\n  -fx-bar-fill: -color-chart-5;\n}\n\n.default-color5.chart-bar {\n  -fx-bar-fill: -color-chart-6;\n}\n\n.default-color6.chart-bar {\n  -fx-bar-fill: -color-chart-7;\n}\n\n.default-color7.chart-bar {\n  -fx-bar-fill: -color-chart-8;\n}\n\n.bar-legend-symbol {\n  -fx-padding: 8px;\n}\n\n.chart-pie {\n  -fx-pie-color: -color-chart-1;\n  -fx-background-color: radial-gradient(radius 100%, derive(-fx-pie-color, 20%), derive(-fx-pie-color, -10%));\n  -fx-background-insets: 1;\n  -fx-border-color: -color-bg-default;\n}\n\n.chart-pie-label {\n  -fx-padding: 3px;\n  -fx-fill: -color-fg-default;\n}\n\n.chart-pie-label-line {\n  -fx-stroke: derive(-color-bg-default, -20%);\n}\n\n.default-color0.chart-pie {\n  -fx-pie-color: -color-chart-1;\n}\n\n.default-color1.chart-pie {\n  -fx-pie-color: -color-chart-2;\n}\n\n.default-color2.chart-pie {\n  -fx-pie-color: -color-chart-3;\n}\n\n.default-color3.chart-pie {\n  -fx-pie-color: -color-chart-4;\n}\n\n.default-color4.chart-pie {\n  -fx-pie-color: -color-chart-5;\n}\n\n.default-color5.chart-pie {\n  -fx-pie-color: -color-chart-6;\n}\n\n.default-color6.chart-pie {\n  -fx-pie-color: -color-chart-7;\n}\n\n.default-color7.chart-pie {\n  -fx-pie-color: -color-chart-8;\n}\n\n.danger.chart-pie {\n  -fx-pie-color: transparent;\n  -fx-background-color: white;\n}\n\n.pie-legend-symbol.chart-pie {\n  -fx-background-radius: 8px;\n  -fx-padding: 8px;\n  -fx-border-color: none;\n}\n\n.color-picker > .color-picker-label {\n  -fx-padding: 6px 12px 6px 12px;\n}\n.color-picker > .color-picker-label > .label {\n  -fx-text-fill: -color-fg-default;\n}\n.color-picker > .color-picker-label > .picker-color > .picker-color-rect {\n  -fx-stroke: -color-border-default;\n}\n.color-picker.button > .color-picker-label {\n  -fx-padding: 0;\n}\n\n.color-palette {\n  -fx-background-color: -color-border-default, -color-bg-default;\n  -fx-background-insets: 0, 1px;\n  -fx-background-radius: 0px, 0;\n  -fx-spacing: 10px;\n  -fx-padding: 1em;\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0px, 0, 2);\n}\n.color-palette > .color-picker-grid {\n  -fx-padding: 0.5px;\n  -fx-snap-to-pixel: false;\n}\n.color-palette > .color-picker-grid > .color-square {\n  -fx-background-color: transparent;\n  -fx-padding: 0.5px;\n}\n\n.color-palette-region {\n  -fx-effect: dropshadow(gaussian, transparent, 6, 0, 0, 8);\n}\n.color-palette-region > .color-square.hover-square {\n  -fx-background-color: -color-accent-fg, -color-bg-default;\n  -fx-background-insets: -2, -1;\n  -fx-background-radius: 5, 0;\n  -fx-scale-x: 1.5;\n  -fx-scale-y: 1.5;\n  -fx-border-color: -color-accent-fg;\n  -fx-border-insets: -1, -1;\n}\n\n.custom-color-dialog {\n  -fx-background-color: -color-bg-default;\n  -fx-padding: 1.25em;\n  -fx-spacing: 1.25em;\n}\n.custom-color-dialog > .color-rect-pane {\n  -fx-spacing: 1em;\n  -fx-pref-height: 16em;\n  -fx-alignment: TOP-LEFT;\n  -fx-fill-height: true;\n}\n.custom-color-dialog > .color-rect-pane > .color-rect {\n  -fx-min-width: 16em;\n  -fx-min-height: 16em;\n}\n.custom-color-dialog > .color-rect-pane > .color-rect .color-rect-border {\n  -fx-border-color: -color-border-default;\n}\n.custom-color-dialog > .color-rect-pane > .color-rect #color-rect-indicator {\n  -fx-background-color: none;\n  -fx-border-color: white;\n  -fx-border-radius: 0.4166667em;\n  -fx-pref-width: 0.833333em;\n  -fx-pref-height: 0.833333em;\n  -fx-translate-x: -0.4166667em;\n  -fx-translate-y: -0.4166667em;\n  -fx-effect: dropshadow(three-pass-box, black, 2, 0, 0, 1);\n}\n.custom-color-dialog > .color-rect-pane > .color-bar {\n  -fx-min-width: 1.666667em;\n  -fx-min-height: 16.666667em;\n  -fx-max-width: 1.666667em;\n  -fx-border-color: -color-border-default;\n}\n.custom-color-dialog > .color-rect-pane > .color-bar #color-bar-indicator {\n  -fx-border-radius: 0.333333em;\n  -fx-border-color: white;\n  -fx-pref-width: 2em;\n  -fx-pref-height: 0.833333em;\n  -fx-translate-x: -0.1666667em;\n  -fx-translate-y: -0.4166667em;\n  -fx-effect: dropshadow(three-pass-box, black, 2, 0, 0, 1);\n}\n.custom-color-dialog > .controls-pane > .current-new-color-grid > .label {\n  -fx-padding: 0 0 0 2px;\n}\n.custom-color-dialog > .controls-pane > .current-new-color-grid > #current-new-color-border {\n  -fx-border-color: -color-border-default;\n  -fx-border-width: 1px;\n}\n.custom-color-dialog > .controls-pane > .current-new-color-grid > .color-rect {\n  -fx-min-width: 10em;\n  -fx-pref-width: 10em;\n  -fx-min-height: 1.75em;\n  -fx-pref-height: 1.75em;\n}\n.custom-color-dialog > .controls-pane > .current-new-color-grid > #spacer1 {\n  -fx-min-height: 5px;\n  -fx-pref-height: 5px;\n  -fx-max-height: 5px;\n}\n.custom-color-dialog > .controls-pane > .current-new-color-grid > #spacer2 {\n  -fx-min-height: 1em;\n  -fx-pref-height: 1em;\n  -fx-max-height: 1em;\n}\n.custom-color-dialog > .controls-pane #settings-pane {\n  -fx-hgap: 6px;\n  -fx-vgap: 6px;\n}\n.custom-color-dialog > .controls-pane #settings-pane > .customcolor-controls-background {\n  -fx-background-color: -color-border-default, -color-bg-default;\n  -fx-background-insets: 13px 0 5px 0, 14px 1px 6px 1px;\n  -fx-background-radius: 0px, 0;\n}\n.custom-color-dialog > .controls-pane #settings-pane > .settings-label {\n  -fx-min-width: 5.75em;\n}\n.custom-color-dialog > .controls-pane #settings-pane > .settings-unit {\n  -fx-min-width: 1.5em;\n  -fx-pref-width: 1.5em;\n  -fx-max-width: 1.5em;\n}\n.custom-color-dialog > .controls-pane #settings-pane > .slider {\n  -fx-pref-width: 10em;\n}\n.custom-color-dialog > .controls-pane #settings-pane > .color-input-field {\n  -fx-max-width: 4em;\n  -fx-pref-width: 4em;\n  -fx-min-width: 4em;\n  -fx-pref-column-count: 3;\n}\n.custom-color-dialog > .controls-pane #settings-pane > #spacer-side {\n  -fx-min-width: 0.5em;\n  -fx-pref-width: 0.5em;\n}\n.custom-color-dialog > .controls-pane #settings-pane > #spacer-bottom {\n  -fx-min-height: 1em;\n  -fx-pref-height: 1em;\n}\n.custom-color-dialog > .controls-pane #settings-pane > .web-field {\n  -fx-pref-column-count: 6;\n  -fx-pref-width: 8em;\n}\n.custom-color-dialog > .controls-pane #settings-pane > .webcolor-field:dir(rtl) > .text-field:dir(ltr) {\n  -fx-alignment: BASELINE_RIGHT;\n}\n.custom-color-dialog > .controls-pane > #buttons-hbox {\n  -fx-spacing: 10px;\n  -fx-padding: 1em 0 0 0;\n  -fx-alignment: BOTTOM_RIGHT;\n}\n.custom-color-dialog > .controls-pane .transparent-pattern {\n  -fx-background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpCOTBEQkE1RjJFMjA2ODExOUExMUM5NDhFOTUyQzM3MCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGRkE3MDZERThFNUYxMUUxQjU5RUNFQTE3OTA1RDFFMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGRkE3MDZERDhFNUYxMUUxQjU5RUNFQTE3OTA1RDFFMSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDk4MDExNzQwNzIwNjgxMTg3MUZDMUExNDFCMTYwNzkiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjkwREJBNUYyRTIwNjgxMTlBMTFDOTQ4RTk1MkMzNzAiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz71FDCdAAAAKElEQVR42mI8c+YMAwwYGxvD2UwMOADpEoz///+Hc86ePUsLOwACDABC1ghwV8TLOQAAAABJRU5ErkJggg==\");\n  -fx-background-repeat: repeat;\n  -fx-background-size: auto;\n}\n\n.custom-text-field:left-node-visible {\n  -fx-padding: 6px 12px 6px 0;\n}\n.custom-text-field:left-node-visible .left-pane {\n  -fx-padding: 0 4px 0 6px;\n}\n.custom-text-field:right-node-visible {\n  -fx-padding: 6px 0 6px 12px;\n}\n.custom-text-field:right-node-visible .right-pane {\n  -fx-padding: 0 6px 0 4px;\n}\n.custom-text-field:left-node-visible:right-node-visible {\n  -fx-padding: 6px 0 6px 0;\n}\n.custom-text-field:success .font-icon, .custom-text-field:success .ikonli-font-icon {\n  -fx-icon-color: -color-success-fg;\n  -fx-fill: -color-success-fg;\n}\n.custom-text-field:danger .font-icon, .custom-text-field:danger .ikonli-font-icon {\n  -fx-icon-color: -color-danger-fg;\n  -fx-fill: -color-danger-fg;\n}\n\n.dialog-pane {\n  -fx-background-color: -color-bg-default;\n  -fx-padding: 0;\n  -fx-max-width: 600px;\n}\n.dialog-pane > .expandable-content {\n  -fx-padding: 1em 1em 1em 1em;\n}\n.dialog-pane > .button-bar > .container {\n  -fx-padding: 2em 1em 1em 1em;\n}\n.dialog-pane > .button-bar > .container > .details-button {\n  -fx-padding: 0;\n  -fx-alignment: BASELINE_LEFT;\n  -fx-focus-traversable: false;\n  -fx-text-fill: -color-fg-default;\n}\n.dialog-pane > .button-bar > .container > .details-button:hover {\n  -fx-underline: true;\n}\n.dialog-pane > .content {\n  -fx-padding: 1em 1em 0 1em;\n}\n.dialog-pane > .content.label {\n  -fx-alignment: TOP_LEFT;\n}\n.dialog-pane:header > .header-panel {\n  -fx-padding: 1em 1em 1em 1em;\n  -fx-background-color: -color-border-default, -color-bg-inset;\n  -fx-background-insets: 0, 0 0 1px 0;\n}\n.dialog-pane:header > .header-panel > .label {\n  -fx-wrap-text: true;\n}\n.dialog-pane:header > .header-panel > .graphic-container {\n  -fx-padding: 0 0 0 1em;\n}\n.dialog-pane:no-header > .content {\n  -fx-padding: 1em 1em 0 0;\n}\n.dialog-pane:no-header > * > .graphic-container {\n  -fx-padding: 1em 1em 0 1em;\n}\n.dialog-pane.information > .header-panel {\n  -fx-background-color: -color-accent-fg, -color-bg-subtle;\n}\n.dialog-pane.information > .header-panel > .label {\n  -fx-text-fill: -color-fg-default;\n}\n.dialog-pane.warning > .header-panel {\n  -fx-background-color: -color-warning-fg, -color-bg-subtle;\n}\n.dialog-pane.warning > .header-panel > .label {\n  -fx-text-fill: -color-fg-default;\n}\n.dialog-pane.error > .header-panel {\n  -fx-background-color: -color-danger-fg, -color-bg-subtle;\n}\n.dialog-pane.error > .header-panel > .label {\n  -fx-text-fill: -color-fg-default;\n}\n\n.alert.information.dialog-pane {\n  -fx-graphic: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAKQWlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUU9kWh8+9N73QEiIgJfQaegkg0jtIFQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFHhyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZmBEf4RALU/L09mZmoSMaz9u4ugGS72yy/UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PFGTL/BMr0lSkyhjEyFqEJoqwi48SvbPan5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjAShXJgl4GejfAdlvVRJmgDl9yjT0/icTAAwFJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5OWieAHimZ+SKBIlJYqYR15hp5ejIZvrxs1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQElWW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3IevtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+lVUAtG0GQOXhrE/vIADyBQC03pzzHoZsXpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y595nL7vtWO6YXP4EjSRUzZUXlpqemS0TMzAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR6JQJhIlou4U8gViQLmQKhH/V4X8YNicHGX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtcim7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOWAy5IAmlABLJBPtgACkEx2AF2g2pwANSBetAEToI2cAZcBFfADXALDIBHQAqGwUswAd6BaQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQDxUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7HAhHwsvgRHgVnAcXwNvhSrgWPg63whfhG/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREha5EipAKpRZqQDqQbuY1IkXHkAwaHoWGYGBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQM4H5gqVi1bGmWCesP3YJNhGbjS3EVmCPYFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4Etw/XjLuA68MN4SbxeLwq3hTvgg/Bc/BifCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwkVBAaCOcI/YQRwjRRgahPdCKGEHnEXGIpsY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0mPSa9IZPJOmRHchhZQF5PriSfIF8lD5I/UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRYqpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uRa5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHBQMFTgaOwVqFG4bTCPYVJRZqilWKIYppiiWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOledK4tE20Otpl2jAdRzek+9OT6cX0H+i99AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyTjLuMj/M05rnP48/bNq9pXv+8KZX5Km4qfJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmphatlq+9Uuq43Pp893ns+dXzT/5PyH6rC6iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpumsma5ZrnNMe0aFoLtQRa5VrntV4wlZnuzFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NOs84TXZIuWzdBt1y3U3dCT0svWC9fr1HvoT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgmNKs1u8eisNxZWaxG1qA5wzzIfKN5m/krCz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw+sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3fdr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iHvQ732HR2KLuEfdUR6+jhuM7xjOMHJ3snsdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgccpEuZC6MX3hwodRV25XjWuv6zE3Xjed2xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16Il69XkVevt5L3Yu9q76c+Oj6JPo0+E752vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegKpARGBFYHPgsyCRIFdQTDwQHBu4IfL9JfJFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4VXh+eHcELWJFREPEu0iPyNLIR4uNFksWd0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCmPRYfGxV7JHZyqffS3UuH4+ziCuPuLjNclrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPCqeVMrvRfuXflBNeTu4f7kufGK+eN8V34ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1sl/ygeSplJCUoykzqdGpzWmEtPi000IlYYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFMKHNZZruYjv5M9UiMJJslg1kLs2qy3mdHZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752/ob8wTXuaw6thdauXNu5Tnddwbrh9b7rj20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv5sZCuUJR4b0tzlsObMVsFWzt3WazrWrblyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l9qX7d+B2CHfc3em681iZYlle2dCu4F2t5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqvakfVp+qk6oEaj5rmvep7t+2d2sfb17/fbX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azDz+ui6rq/Z39ff0TtSPGRz0eFR6XHwo911TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQM6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn/Z/2ttBailqh1tzWibakNml7THvf6YDTnR3OHS0/m/989Iz2mZqzymdLz5HOFZybOZ93fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZevXvG5cqnbvfv8VZerZ645XTt9nX297Yb9jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n+l37L972un3ljv+dGwOLBvruLr57/17cPel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6qP6391fjXZqm99Oyg12DPs4hnj4a4Qy//lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXwy4yX0+OFvyn+tveV0auffnf7vWdiycTwa9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6rvj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn88ngmbWbm3/eE8/syOll+AAALPUlEQVRoBe1Z22+UxxWf9bJe29iYyxpM7RqS0EKDcR5KG1dcntoqChIRVRP1oarapFLU0r60UiuBqqSq4C+oSCKVtKr6UBWpBSQalPSJi5KUi6gvKRcJsMFAfMMG23h3vbv9/c6ZM/t9tgHbPPSFsb+d+WbOnPP7nTkzOzPr3NP01ANP5IHEE/WOdL548WLb1NTUzlKptKmioqIFeROaG7zIQCKR6CsWi73IOxctWnR0w4YNHZHuCy4+EQGCzufzbwDUqwBdt3Tp0lR1dXU6lUo5PgAqwEDMQU6eiYmJ7OjoaL5QKNxH4yE8Bzdt2rRgMgsi0N3d3QIA+wF6VyaTSdfX1yerqqrm5cVsNutApDAwMJDFyPwjmUzu2bhxY++8lEB4XgQQFqmOjo598PjulStXphoaGlIgMV+bMXmAdyCRx5ND+UBbW9te6M/HhB7xMmcCly5dyuRyuWO1tbWtzc3NNRYepnsyV3SffjbsPu4adrcGJ93gaM4N3stKc2ZJpcvUp11Tpsq1ty53Lz6/3FVVxokzzG7evDkxNjbWVVlZuWP9+vWDpvtR+ZwIdHZ2tsH7xxEuK1avXl0ZVXjlxpj70we97pPuYZedKjp4zzcjZ7FUfnWlEv5LLr2owrWDxA9fbnFf+mJtVJ27fft2bnBwcAh6XprL3DBrMSXRF4LH0J5qaWmpxSQN8p8PZ917R6+5j84MKFAPPJGAZwU4kaMgZWokeGtimzL71lcb3JuvPONWLU9TSNLIyEipt7d3DOG59XEkAiDrHM0ZNg8ePOhYs2ZNYxQ8vf32+xfd2OSUS8gcSMDz6MkPPMplmmqiR+IICBOWEf/ksbiqwr39+ldc+8blIsMPkrhx48addDrd9qhwigdi6C6GUgB/DJN1RRT8X/910/363W43ni0IeHq8IlkhZeYyqVGXqAARkNMcZbQ5q0smlSjJo248W3K/eqfbUbcl2mTIEgMXD6ufnj+UwIULF/bX1dW1RmOeBn7/96uuWFJPEywBitcFjAISQgDJtgrJjSDrjBRyT5xhhrEQ3VEStE0MwLJvOnB7n5UA13kI/ARxX2OCDJsDh6/GATNcCMhGQMApWE7mBD0t7QpciFJWRgimpb/X4XUdOHxNFgSz6zH81GOy6pDPSmBycnL/qlWrKm2p5IR96+B/xfNqnEbhNvOmgDVASiidTrovN9fIU40yQ0VHDHIoS2iRiDxKQkYCc4LzizaZiIFYiCmgjhRmEDh37lwbDO1CpxB37x25pjEPoBo9MOgNqxc9eHhXwgdyTZlq9+7PNsjz3BcWSygJYbQZEXWCgpcRk5FJuLEHU442LRELMRGb1Vk+gwAUvdHY2JimESau8x+e6UdJvSb1ABEd/pllhhFkLLFM4AwfPDYC0g+gZalFu/Vh/tHZAbFNFbQJEmliM5WWzyCAGf8qVgCMuaY//rNHCqacxlgWj5GkNyxzQcoEmXTD4wV34IPb7p3jd1z/6JSAV8DqcZZ1tKiChL0u1LONy63ZJoBly5YliU1RlT9jBM6fP/8CNlV1tjHj9oCTl2CZxIi6S7xIQ3ymhxPlx3LOHT074g6fuevuPsAaEwFpfaw/RyQks4X8U9gmBiZiIjZiDLIoxAjgG3cnmIbY594mly/Kd6aQELw61IEUCXBE4EF7NESg2upEptzOkNARNQeoDiWmdSxza0IMloiNGO2deYwA3lsXL14cvtM/7hryHvZK+SXqezMnCQGCXN7pSfwTYEsm7Q79Yp079Mt1bn1zNeq0TQYQfWP9vU6okaTOwdYDb4LBt2MjSWyt/lUyPXGUa1qwEwxvfQOTAijAJmBaCXkQVTJsA3iOQBKTta5ap5JNfA1BasOmD3+Mc89EFFF3iTpYTxv4EwzeDA9JkOF3VEjTR6CJQpYGR7AWq6ukirqlgnU0gqTeYsFEKYRHZCmBonjfk9cqFWabdFTAHrdKqHonGHwfYkMI8agaUowATlkN0REYuoeZSCBemWAmC/4LGw0d00Yw4tXQ7lvYH3VsE8B8VW+oarRJ8jJWpgzPFZaIDQTsnC3VMQImaLkA9sbNhngeDRKhFCAogpEihZHYXvRleWcdC2yioM+1Rkl5XcJUBKMyIjjrR4wAYnUAp64gmKn388EbFON0pSUWp4EJBEu6/FGUMc+ts2yfhQk6WQr9I3qljTIlnOTKc5LY4CwcQMopRgDD0xcjsDQsSNJD8NK75kXxsnpbALJNgAqqshXKRfrI6IgsQbKJOkFYxKyvEspEMHhsfWXFM5fRXl5/WGpqqBLF6nk1RCtGhGVt80YBlOCKhSJiVcFRF+vCCJAI64SQ9o/5Xsh4vRAlBkvEhn699s48NgKYxF3379/XbSAav9G6IsSnkcCWVIxLOBC3gTPDcspCA3OfoqOCUS6TYTveWWd6odx6iR1isERskO20d+YxApgDR4aGhsIQ8PagEgdwJgHqlRsZJUEWbPfASMSXpSP7sq5QUKDSrn2EjMcrOr1+kqC9dCopNximB4f9PObAUXtnHiPQ3t7egeuN+7g9ExlefbyIc2pQTsU0AuXmMQFHD7INoWPgCSAktKuX2U5wKkfvK1jNhRD7+a68grHrF2LibR4xBr0oxAiwAUKHhoeHCyb0+o41Yd0Jxj0RAyIkCN6TIxECC0lAYW4QOEZCQFOHJyMjSHnponKcFz96eU1QQUzQz6vIWJpBADu+g319fYw1EeS9zbe/vlK8JqBoJ3gQLwQhkxbyqC8CINutP5UUscIIKSNiI8V6IYKc9nyZOW3anRF1ERPC52AMPV5iC4A1nj59+i9NTU2v4TpF9hU83v3gd2f0GiWyLZZdKL/E8Mg+R7SxnHAIX/dMY40Y6Pl80k3m6Xn+y4cC5puMAutQ9kRrqxe5P//ma+GuCHdEedza/W3Lli3fN4yWzxgBNsAre27duoVVS+czL51+++PnsUfDVgHeUG+px8TT4jl4nqHDdoxCDvv4yzjNXewdw55+StoMINvV8yRAPaqT/blrpS276CIGeD9HTAY6ms9KYOvWrb0AduDKlSs6m9GDl067v/Oc9hXAut5LCAkpBSMESQSg+H3Ad8kFJL2sjxJlO/vpCFA5bUQvuC5fvjxBLMSkxuOfsxKgCE5Ae3A71tXT0xP2Ft/7ZrP7+XfXMWIEGD44XFIW76PMESFIAy+EPEGZHzZHSC7SnzqpmzYs0Tau4LuAZa/VTc8laqdX2vvZs2flahG/pjTilizI8pj51h8+k9sDBjmPlEyybZaCF4VjudEjIU30Pkv0OLnLh2PMM2yinseaX8IPKHfwg0nb5s2bH3pTHUB5CzOyEydO8JrlFO4na6Mk5HL3yFX34b/7ywABloBDYpGALeerB00ZynK1efOVZ0PMs57gcS87htHcun379ti6z/ZoiliLVsfLJAFjx/G7wIq1a9eWt4cQ47XL+8d65ACe5UpjiWRQJn7vdmuRb1h+SXGdt6XSGq9fv57DijMEoi89Djz7zIkABRlO+DY8hoN1K0ajJnpyY3v5B44hOQbyJGWHEW6Juavkxox7m9l+4OBqA69P3L17t6umpmbHo8KG9izNmQA7gEQKv6DsQ0jtxmikcG+Zwhef6VpQjm9+59d5+YkJB/e9AB/2Y49TOi8CpuzUqVPyIx/A7wKRNOZGErcZ1jynfHx8nLFeQLhkQUJ+5HvYUvkohQsiYAo5NxCr4WdW3mEuWbIkzbMrfphwdr7mQYS/SjK/d+9etr+/P89NI/Qcwtw6OJdYN5vT8yciEFV28uTJF+DJnQDUihBrwQrShPYGrjqo4zGwD2V+GXVh5I5u27btP9H+T8tPPfB/8sD/AKsOfq+d8tgaAAAAAElFTkSuQmCC\");\n}\n\n.alert.warning.dialog-pane {\n  -fx-graphic: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAKQWlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUU9kWh8+9N73QEiIgJfQaegkg0jtIFQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFHhyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZmBEf4RALU/L09mZmoSMaz9u4ugGS72yy/UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PFGTL/BMr0lSkyhjEyFqEJoqwi48SvbPan5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjAShXJgl4GejfAdlvVRJmgDl9yjT0/icTAAwFJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5OWieAHimZ+SKBIlJYqYR15hp5ejIZvrxs1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQElWW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3IevtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+lVUAtG0GQOXhrE/vIADyBQC03pzzHoZsXpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y595nL7vtWO6YXP4EjSRUzZUXlpqemS0TMzAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR6JQJhIlou4U8gViQLmQKhH/V4X8YNicHGX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtcim7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOWAy5IAmlABLJBPtgACkEx2AF2g2pwANSBetAEToI2cAZcBFfADXALDIBHQAqGwUswAd6BaQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQDxUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7HAhHwsvgRHgVnAcXwNvhSrgWPg63whfhG/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREha5EipAKpRZqQDqQbuY1IkXHkAwaHoWGYGBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQM4H5gqVi1bGmWCesP3YJNhGbjS3EVmCPYFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4Etw/XjLuA68MN4SbxeLwq3hTvgg/Bc/BifCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwkVBAaCOcI/YQRwjRRgahPdCKGEHnEXGIpsY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0mPSa9IZPJOmRHchhZQF5PriSfIF8lD5I/UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRYqpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uRa5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHBQMFTgaOwVqFG4bTCPYVJRZqilWKIYppiiWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOledK4tE20Otpl2jAdRzek+9OT6cX0H+i99AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyTjLuMj/M05rnP48/bNq9pXv+8KZX5Km4qfJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmphatlq+9Uuq43Pp893ns+dXzT/5PyH6rC6iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpumsma5ZrnNMe0aFoLtQRa5VrntV4wlZnuzFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NOs84TXZIuWzdBt1y3U3dCT0svWC9fr1HvoT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgmNKs1u8eisNxZWaxG1qA5wzzIfKN5m/krCz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw+sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3fdr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iHvQ732HR2KLuEfdUR6+jhuM7xjOMHJ3snsdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgccpEuZC6MX3hwodRV25XjWuv6zE3Xjed2xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16Il69XkVevt5L3Yu9q76c+Oj6JPo0+E752vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegKpARGBFYHPgsyCRIFdQTDwQHBu4IfL9JfJFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4VXh+eHcELWJFREPEu0iPyNLIR4uNFksWd0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCmPRYfGxV7JHZyqffS3UuH4+ziCuPuLjNclrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPCqeVMrvRfuXflBNeTu4f7kufGK+eN8V34ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1sl/ygeSplJCUoykzqdGpzWmEtPi000IlYYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFMKHNZZruYjv5M9UiMJJslg1kLs2qy3mdHZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752/ob8wTXuaw6thdauXNu5Tnddwbrh9b7rj20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv5sZCuUJR4b0tzlsObMVsFWzt3WazrWrblyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l9qX7d+B2CHfc3em681iZYlle2dCu4F2t5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqvakfVp+qk6oEaj5rmvep7t+2d2sfb17/fbX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azDz+ui6rq/Z39ff0TtSPGRz0eFR6XHwo911TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQM6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn/Z/2ttBailqh1tzWibakNml7THvf6YDTnR3OHS0/m/989Iz2mZqzymdLz5HOFZybOZ93fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZevXvG5cqnbvfv8VZerZ645XTt9nX297Yb9jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n+l37L972un3ljv+dGwOLBvruLr57/17cPel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6qP6391fjXZqm99Oyg12DPs4hnj4a4Qy//lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXwy4yX0+OFvyn+tveV0auffnf7vWdiycTwa9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6rvj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn88ngmbWbm3/eE8/syOll+AAAJUklEQVRoBe1ZW2ycRxU+u/b6Hidx7FiO7SROVAQhpFWh6lt4DQ8RqQKqBAgekAoqRaLiAXETRZQGqjYpqSqFqqiqgDbQAi+oqkQfojiOL7EdcnFs6ibgOF5fdn1Zr9f2rnf35/vO/DNeJ3Z9TUCQWf2e+c+cmfm+c87cfovcT/ct8P9tgcDdoN/V1VXged7T6PtoIBCYRvnU/v37T9+NsTacQH9/f/HExMT7ZWVlD1ZVVZVmMhkZHBycSqVSbxw4cOCpu0FiQ/u8cuXKqb6+PlrdJZDwenp64levXv3ahg620Z0hdB4FyCkCvj3NzMx4ly9fjl2/fn3zRo4b3MjOAPylHTt2lASDd3ZbVFQkW7ZsCSUSiZ9s5Jh3jrTG3mHdI/n5+fu3bt3q5lVy4obMJYZdjzU1NcXwzDe6u7t3O+E6CxtCAKCCeI7X1dWVWTyTNxul+/QRuXb6sMyCCBMISnV1dSiZTL5g9dabbwiBS5cuPYEQqdq0aZPi8bJpudX4rIiXFS81IwONzzmc27dvDyHEDqHNo064jsK6CYTD4RJY/+f19fXO+qNXT0s6HpYAfkEEVGKgVeL9TQoT+4LU1tayzcl14HZN101gZGTke5ichcXFxdppdi4hI52vSsAjeKD3RIkMt76EerwgVVRUBEKh0D544agK1vFnXQSw5lfDkt/FylNqMUQuvi7Z2ZjQ0pzNeYGg5rPRf8jEB3+1akKPZbPZ42if74RrKKyLQDqd/lllZWV+QUGBDp2eGZWxK78Xdkrw9MB8HpDIhVcwLeZUl/OltLS04uLFi0+qYI1/1kygvb3947DeV7A0FtqxI+2nxJubVusreD98GE4kMhcflLGrb1p19QL6+Glvb2+5E66ysGYCWEleROgU5OXl6ZCp2E2J9fzFWB3A8/IKpbjqk3j2SV6oGPKg1o12vCbZVFzbcHPDfCicnJz88SpxO/U1Eejs7DwIAp/FkmjQo7tI28sSyGZcyBSU18vuo2/qU7TtYyqnH7zUlEQ7X3MAsCJx9j8Jj+50wlUU1kQA/Z/EJCzlRGWajXTJ1I33HUg7gbUSfziN+RhtkVjXHyQ9NaTVWI0EYRhCKD1v9VeTr5pAR0fH4xh0L1zvxom0/sqVFShCiCGTm+Yns0g2ndQJbeu5OyMUD2NCP2JlK80XjrJMK3/Je3HXrl1u00rcapbpgQu+hWFlXXlobWvv3BXJeIIk473vSnKsV0fk4Q/HkCKsavOWWAaLrV4VAcTpUyUlJZvtkYGdRGF9A9UDeAjU+rnwTQipBHU2eThmRHRzMxJcfoJYjj+FMY5YnZXkKybApQ4eeCbX+pMfvisz0R5/HGN1kiCPhRSsF5DTQ6oTkMTN8zIdvuBw+n2fwDhucXCVSxRWTGBsbOxH27ZtK7BHBh7YIm2vOKAELR68oPD1xQ1pJ7vSgI45UdAdnoy0nHB6mzdvFvRfCS980wmXKayIQFtbWz36+RZWHnPgwcv4tbclNTmwoHsChfVUNg+avGzsIKf5kUx9QGYi1yT24Xsq45/du3eXQf9Z3O7cPHOVixRWRAAdHuNSxyWPKYvdNtrxKkqe/mhZtSeBAp8rq7b/R3Gz0rQhKSUG5ciFl3HESKsi5hg3t4J4PP7D3OZLlZclAOs/BGs9hg3HoEdP0b+/LunZMWNNoiVkH7gdCDcBW9RSFoCzvB9YIqylx/BLxm7JWNcfnT48zWvpt33PO/lihWUJ8MS4c+fOIi51TDywRS79loY0FlTgvjVRT6D8KSM28JPSIWC28zXUA5BRMtLxa3g2odo8HGJv4LelY7b9UvlHEoAFDuEa+Ag6c3ojOLBl09PzILRnP4RQJlAlAWvbRG8YuS0ZElpPRnBfenZcIhd/Y5twX6DHH2tubn7YCRcpOGC314F9AF8ZTjQ0NLjJlJrsl2j3O6qq46JEK2poMPdpMUxMqJhe1eJsoCFjSLKd6kHFEIYXLv9O0tMRbUSPY1ktQvh+5Oa2JIGWlpavYkLV5h4Zwq0nJZtJ64Am5o1lvVxgPqkkTqd9f/oyni/J7OgHSk6JgqQC1nDyPaJhCCLpGRnEncEmeh4Lx0PwwmEruz1flAA2rUJY6HlY39zS0Woay934dS53ak+1PI3KAOCuSikBmjkgkskksUR2YaO7JhkAU4tra78HesC+syOkbNaTaM+fZXbcfMWgjBGAttzcFsW6qHB4ePhp3HNLc48M4ZbjfriYweh+FzJwh5ECHOQZxj/uA4WVn9BH8otUTnKWoIInCT7qAUiQg4VwLJvwnYk3t+qmpqYnrCw3v4PA+fPnKxB3PwBzd8+N48A22d+s/XMDUuAc2P9Zy3PamjARCW2qk4YvvCUNR9+SgooHHHAObkhwzlgPGmKsI6GJf52RxGAnXzXt3bu3DOM+h48ADpOtu4MAls1neLzlbcmmAViEwA1gGirH4jCcWt+3oloWDUnSJkcUMiUPXZJgcn2if03IWLrV/IJ5x194wN7cvu+EfmEBgcbGxj0A+nWu+1ZxnAe2SLdaRs85zvIGuA0J5s6yCKE5rCbhpl9K+NwvJIVvRLl6GjY+dMqZlLiWjXGmhy9L7MbfLAzZs2dPCbB959y5czucEIUFBPB+DLtggT0yYFmQcCu2eXbvW4ieMD8MSjIBa18/hypL6eSERLAsRvCVIpWIsAeVz2v7Xpg3vJpeX31SYV6UvIzi5eaGOzhvbvjkN58cAcR+LcAdxpHBfaeJ46jLtd8EjQ+AoCEheJLij+/OwgqTE9kPlxzY1NFnkfbsT/tijn45ysz4P2Uq3A5tk2hc1D1+9uzZKitzBBD7h7DmZ+yRgQrBUIl2BrTaIQcwoJnpmy+Hsm9JY2nTvXoIRQOaNUh+u6Xas17VSAflIFYwm/hxGKtjBvLPOZkt4DpXi41rwSwvrX5QHvj8GzI11IloSmnf4GLA+jmHc7KcKjBzpKwOsTndZdoHgyEpq/m0lABDbsKELo1Go3VW5sIFlr+JI+wUKtzmRaXSmof1sQ3+0zkwJuAJdxFxIYRzzzu4dU0PDQ3RNv+VidjGx8enscGaAxlQmoDz4WJy7EPcvo1PHLvKy8vz8J/GQsTbAp17zQx4PPxbKhmLxTKYp30Y/4sHDx68ZnEsCu7MmTP8PvMZtN2G3IWZbXSP8zRsOIoQ7wDwtns89v3h/vct8G+O/84lPnmZfwAAAABJRU5ErkJggg==\");\n}\n\n.alert.error.dialog-pane {\n  -fx-graphic: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAKQWlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUU9kWh8+9N73QEiIgJfQaegkg0jtIFQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFHhyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZmBEf4RALU/L09mZmoSMaz9u4ugGS72yy/UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PFGTL/BMr0lSkyhjEyFqEJoqwi48SvbPan5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjAShXJgl4GejfAdlvVRJmgDl9yjT0/icTAAwFJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5OWieAHimZ+SKBIlJYqYR15hp5ejIZvrxs1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQElWW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3IevtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+lVUAtG0GQOXhrE/vIADyBQC03pzzHoZsXpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y595nL7vtWO6YXP4EjSRUzZUXlpqemS0TMzAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR6JQJhIlou4U8gViQLmQKhH/V4X8YNicHGX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtcim7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOWAy5IAmlABLJBPtgACkEx2AF2g2pwANSBetAEToI2cAZcBFfADXALDIBHQAqGwUswAd6BaQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQDxUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7HAhHwsvgRHgVnAcXwNvhSrgWPg63whfhG/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREha5EipAKpRZqQDqQbuY1IkXHkAwaHoWGYGBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQM4H5gqVi1bGmWCesP3YJNhGbjS3EVmCPYFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4Etw/XjLuA68MN4SbxeLwq3hTvgg/Bc/BifCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwkVBAaCOcI/YQRwjRRgahPdCKGEHnEXGIpsY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0mPSa9IZPJOmRHchhZQF5PriSfIF8lD5I/UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRYqpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uRa5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHBQMFTgaOwVqFG4bTCPYVJRZqilWKIYppiiWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOledK4tE20Otpl2jAdRzek+9OT6cX0H+i99AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyTjLuMj/M05rnP48/bNq9pXv+8KZX5Km4qfJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmphatlq+9Uuq43Pp893ns+dXzT/5PyH6rC6iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpumsma5ZrnNMe0aFoLtQRa5VrntV4wlZnuzFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NOs84TXZIuWzdBt1y3U3dCT0svWC9fr1HvoT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgmNKs1u8eisNxZWaxG1qA5wzzIfKN5m/krCz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw+sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3fdr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iHvQ732HR2KLuEfdUR6+jhuM7xjOMHJ3snsdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgccpEuZC6MX3hwodRV25XjWuv6zE3Xjed2xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16Il69XkVevt5L3Yu9q76c+Oj6JPo0+E752vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegKpARGBFYHPgsyCRIFdQTDwQHBu4IfL9JfJFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4VXh+eHcELWJFREPEu0iPyNLIR4uNFksWd0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCmPRYfGxV7JHZyqffS3UuH4+ziCuPuLjNclrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPCqeVMrvRfuXflBNeTu4f7kufGK+eN8V34ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1sl/ygeSplJCUoykzqdGpzWmEtPi000IlYYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFMKHNZZruYjv5M9UiMJJslg1kLs2qy3mdHZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752/ob8wTXuaw6thdauXNu5Tnddwbrh9b7rj20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv5sZCuUJR4b0tzlsObMVsFWzt3WazrWrblyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l9qX7d+B2CHfc3em681iZYlle2dCu4F2t5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqvakfVp+qk6oEaj5rmvep7t+2d2sfb17/fbX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azDz+ui6rq/Z39ff0TtSPGRz0eFR6XHwo911TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQM6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn/Z/2ttBailqh1tzWibakNml7THvf6YDTnR3OHS0/m/989Iz2mZqzymdLz5HOFZybOZ93fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZevXvG5cqnbvfv8VZerZ645XTt9nX297Yb9jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n+l37L972un3ljv+dGwOLBvruLr57/17cPel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6qP6391fjXZqm99Oyg12DPs4hnj4a4Qy//lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXwy4yX0+OFvyn+tveV0auffnf7vWdiycTwa9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6rvj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn88ngmbWbm3/eE8/syOll+AAAJdklEQVRoBe1ZTWxcVxU+82eb1IkTe+zEonX6Q4tiHFAhLCAr8rOMhAQSEotC3EYsg9ggpXFiO1WR2HWTBUkdxAIJCSSkrKB2UCIhAbVkW7GiqEnbxE4Jtmec2Pnx2OOZ6fedc++b917GY1st0EXuaN6999xzz/m+c8+97808kaflaQQ+UwQS9WZfu3atp1QqHYPOkUQi0Yl2E+p6Uz6XsWQyWahUKndh7GIqlTrX3d09uZbhmmgwOTM5OfkOJvV2dHSkt23blmpoaBAYXsvO5yovl8uysrIii4uLpbm5uVXgGerp6TmO4BXjjp4gQPBXr14d2bJly7e7urqaMplMfM7/tF8sFmVqaqrw+PHj9/fu3XuwFokIIIA/e/PmzSUQ+UIVYpqYmDgbAYtOZAWY82A8umfPnsZw5BeuTcqdP/9JHnx4Q5bn5+M2gr7uj0pFbJ9UTSMSkImgUo/sr1UaW1tl60svy7Pf/4G0dPcEalyJ69evL6fT6X3hPZEONNCA0rGdO3emw+Cn/vgHufX738H52k6TwcZOSIL7xKl60MmkkakoCT9IQk/aZICW5/8p+dF/yfM/fk26fvgjhUhM3I8zMzM8VI573PFdeaSlpSXlBxn5euAZaYLnJ5lIapt9AvbyFAiZBq9iepCZvo15f+Ga5OibGHzhYYL2Ed9nHSGASZ08bXxh2tSKkk4MAScRT0JrmCVwglSS2k5IypMkYZ1BQpiBb61C38TgS2NjI5udvs86kkI4vprCRyVzPl7oKkFgWptzgvZ921bIeX7CuNAhIMoqrg2KyLaKkqlIWTMvHrAwBmLjvSiMKUIgPjm+YX2kLOKWDjRmYAmY6KpydUSRy3WOKwkMlDkPqcYzXydpCNiK7os4Bm9LbeMSIeCF9WpNCyj4WlcDwBjsMEHaMCkAOeCEpiQAkqlTAXjaIeiyUrI5cZC0tVbZMAECtLx1YAkPQgJh0RrHDLskpYWRd+MGjVKApRitsh+DHolwNWid4rJbNbUTusTJxTdxSLXaZNTCkfYbj85avv4NaWrvUMd2+tj+oA5zlsD4tU2t8LTf+p3vSvqZZxxgg0E/PiB+Nasoarc2RABBgyOLoRkmEJHtr35TvvLLE/LK6UFpyLYrMI540KTCtsF2cpDKHj4sL/z8F/LVvn7JKImwbWvXhlvdT348QsALw7VFHxC4rigG0PqNbe2SSKelsaNDXukfcCQ8aNsnuhK6Cpwp0nbosDz3+jG1l966VVJbvqRtXVVPlb64YTZQ1iWgp4ZhD0jQLuHkLw3L9PlzerI0II1eOnVKWJOsrgKiTT2e/6wJ/su9r+v48uys3BgYkGJuXsc8Vh8oJeSC5sdq1esS0LhpCqEFg+EvDc6PDMsn754PSLzQ14eVyBoBQNMUQgBaDx2Szt5enb8C8B8NDshqPoc+ghHYZWBYVKit+GXTm1gjol5gKnwyoO0dz4+MyL+H3nUk2uX5kycl05bVyJLAjoOHZNfRowH4j88MSjGf1/HgxFLYDj4rptAG0iiyAnF2MGHFG2OkIOFVibHH1YH83sgl+c+FC0oi094uXSdP6EpsP3hAOn76kwD8rbfeklU8sFlgaJ53bbMJobeuTe1SpU7ZwH3AIk0bNO8LOak7enGrcQ8rQdlORJsknkOkk804KqFTnJ2T6bffRtrkLbJuNRkMe4jARLWDewBuFDZMa9FlsDs3xK7UXQEfZRpjW426sNha0IpzAF/UuX/pbzJ74bfQrUhqa7OBB2iCL+aQNpzvdD2IoFYfPjUDad1GhEBc01KKBm1EawDjR4FrG00OQBSkoJ8QN8hZnONtcA4+QTAgpy0brzG5hqguAdO3yNPoE7d370wBKQfZfuB7Qc6XHjxQMJm2Nnn2zROSamuNQmBgHAkOoGlF5UbUi3xNHOESIRDPLypqtP0K+L46xViFT5Kmw/YObFjmP9OER+Wtk30y4zZ2A/bEbpxOKZDRCNOGRtxqD0xrYuQqOr/qZI1LhEAtHXNmrL0TGlY5JtiIyA6e80er5zxPm5VcDveJEbk7NKT6JMH7RKY96+a7KNOeOvdXJ/fGQ8ACDE4WIRAfpI5uOl0HWAunjFtKztlx4GBwh2XkPz5zBud8Diln50v8PvFi36kgnTQQPHX0Y4HxeCmLlzjGCIG4Mvt+AmvuAZpUZ4gaAeqzzRv2bMPHgw8H+xH5OX00JnwjUZH88LDcCd2xXz49IOlWpBPtaaqg5e2zxrdWsYBWR9YlQFUFrgbNqO+3vPot6QrAz8iN/tOyjLRR4ASP53sS8N/88Htyxz072QPgoGSy3BOYAdMWIA/c11WwbMWJbYiATnR5TzDaB8D7E2OyMD4my7Mz8kH/KVnJz0FqwOlIP6y1bQBnh/8qU+d/o7Li4oIUHz2CHleVtF3L6aujdS6RO3GtU8jPVxDYA1xtkuAP8vJqUT749a8kg8fi1Xv39TcuBnXfJBwI6msseXGpMvveX2RlcVEWJsZldemRrpTRhW23H7zfeB1fgQiBuHK8b9HnLyzc/kkCD2rl4oqs3HPPNooUs1AznqSr+e3kGgRtV2T+H3/XtDHgpmAr5ZTjzl3/MxGgDSNBaACnHwtswgHjJuMIi4JHRP3pZavHMUsW6nDVqcd9wD2w2bKpFfDGvZsSnBJwkgJND9RoE56eFkwjjjlgCpQKKuIJ5UKgc1S86UtkE8eXh3+01irU40poHF27pCdORUiKENnn1yDaNX4q6XxoqC1HMu4vjiGOMUIAkwvhjcx/iesVGjPAIZiaClXgjLIno1Il7PRdu56PMAZiw8oWwvoRAvgb5O7S0lIwzr+4NRUCSe0GQTKK5XJJI05SfkX8Svkoq56SNBK1LZqUvonBF2KDjK+eghIhgIhezOVyJT/K/+f5F/dGSHAOU0fzGoSCj4K1FTEy3nr9mj7pO/yOIJ/Pl4gxPDOyifFC7fz09PTPdu3alXL/BOv/8y3dX9vQC46wYbbj+Rofr9Vf6wXH8vKy3L59e5Uv/WrNC2RXrlw5Oz4+/oV7xURMxBYAdY1IClGGl3vHFxYW3h8bGyuQ9f+7EAOxEBOxxfH40zsiHx0dzTx8+PAd5GHv7t2709lsNtXU1CRYvojef6uDdwBSKBSE+5Fpg1Qcam5uPr5v3771X7OGQV2+fHkvJr8BIkdQd+IYC14ucJMxx1nXKn6M9WYLTsMC7N7F3It4N3Zu//791fdMmzX2VP9pBOpH4FN/okTWoDWjjQAAAABJRU5ErkJggg==\");\n}\n\n.alert.confirmation.dialog-pane,\n.text-input-dialog.dialog-pane,\n.choice-dialog.dialog-pane {\n  -fx-graphic: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAKQWlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUU9kWh8+9N73QEiIgJfQaegkg0jtIFQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFHhyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZmBEf4RALU/L09mZmoSMaz9u4ugGS72yy/UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PFGTL/BMr0lSkyhjEyFqEJoqwi48SvbPan5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjAShXJgl4GejfAdlvVRJmgDl9yjT0/icTAAwFJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5OWieAHimZ+SKBIlJYqYR15hp5ejIZvrxs1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQElWW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3IevtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+lVUAtG0GQOXhrE/vIADyBQC03pzzHoZsXpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y595nL7vtWO6YXP4EjSRUzZUXlpqemS0TMzAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR6JQJhIlou4U8gViQLmQKhH/V4X8YNicHGX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtcim7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOWAy5IAmlABLJBPtgACkEx2AF2g2pwANSBetAEToI2cAZcBFfADXALDIBHQAqGwUswAd6BaQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQDxUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7HAhHwsvgRHgVnAcXwNvhSrgWPg63whfhG/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREha5EipAKpRZqQDqQbuY1IkXHkAwaHoWGYGBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQM4H5gqVi1bGmWCesP3YJNhGbjS3EVmCPYFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4Etw/XjLuA68MN4SbxeLwq3hTvgg/Bc/BifCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwkVBAaCOcI/YQRwjRRgahPdCKGEHnEXGIpsY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0mPSa9IZPJOmRHchhZQF5PriSfIF8lD5I/UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRYqpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uRa5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHBQMFTgaOwVqFG4bTCPYVJRZqilWKIYppiiWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOledK4tE20Otpl2jAdRzek+9OT6cX0H+i99AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyTjLuMj/M05rnP48/bNq9pXv+8KZX5Km4qfJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmphatlq+9Uuq43Pp893ns+dXzT/5PyH6rC6iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpumsma5ZrnNMe0aFoLtQRa5VrntV4wlZnuzFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NOs84TXZIuWzdBt1y3U3dCT0svWC9fr1HvoT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgmNKs1u8eisNxZWaxG1qA5wzzIfKN5m/krCz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw+sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3fdr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iHvQ732HR2KLuEfdUR6+jhuM7xjOMHJ3snsdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgccpEuZC6MX3hwodRV25XjWuv6zE3Xjed2xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16Il69XkVevt5L3Yu9q76c+Oj6JPo0+E752vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegKpARGBFYHPgsyCRIFdQTDwQHBu4IfL9JfJFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4VXh+eHcELWJFREPEu0iPyNLIR4uNFksWd0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCmPRYfGxV7JHZyqffS3UuH4+ziCuPuLjNclrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPCqeVMrvRfuXflBNeTu4f7kufGK+eN8V34ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1sl/ygeSplJCUoykzqdGpzWmEtPi000IlYYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFMKHNZZruYjv5M9UiMJJslg1kLs2qy3mdHZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752/ob8wTXuaw6thdauXNu5Tnddwbrh9b7rj20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv5sZCuUJR4b0tzlsObMVsFWzt3WazrWrblyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l9qX7d+B2CHfc3em681iZYlle2dCu4F2t5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqvakfVp+qk6oEaj5rmvep7t+2d2sfb17/fbX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azDz+ui6rq/Z39ff0TtSPGRz0eFR6XHwo911TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQM6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn/Z/2ttBailqh1tzWibakNml7THvf6YDTnR3OHS0/m/989Iz2mZqzymdLz5HOFZybOZ93fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZevXvG5cqnbvfv8VZerZ645XTt9nX297Yb9jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n+l37L972un3ljv+dGwOLBvruLr57/17cPel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6qP6391fjXZqm99Oyg12DPs4hnj4a4Qy//lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXwy4yX0+OFvyn+tveV0auffnf7vWdiycTwa9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6rvj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn88ngmbWbm3/eE8/syOll+AAAMQElEQVRoBe1Za2xUxxU+u+vd9Qsw2ItNbGya8PbakVra0MjQP2mVlJSUtkipVFUpVIoa1Ep9RQr8SKIAkZJfldJEqQrpQ2krJQ0ClSYqlaqCW/IASu01YCyCX2vAL/Db6/Xu9vvO3Lm+a4Ox4Uf/MPa98z7zfeecmTszK3Iv3NPAXWnAd1e9PZ0vXLhQOzk5uTWTydT4/f5KxOWojjhNenw+XzydTrcjbszJyTmydu3aBk/3O07eFQGCTiaTOwFqO0AvKCoqCubl5YWDwaDwAVAFBmKCdvqMjo4mBgYGkqlUagiV7+A5UFNTc8dk7ohAU1NTJQDsB+htJSUl4UWLFgVyc3PnpcVEIiEgkurp6UnAMocCgcDu6urq9nkJQeN5EYBbBBsaGvZB47uWLl0ajEQiQZCY75hZ7QFeQCKJZwLp12tra/dAfjKr0SyZORNobm4umZiYOFpYWBitqKjIt+5hZY9PpOWjc/1yMtYvXb3j0jswgSeh1cULQxIpCkt5JE82Vi+Wh9YvkdxQNnG6WWdn5+jw8HAsFAptWbNmTa+VPVs8JwKNjY210P4HcJfiZcuWhbwCWzqG5Tfvt8uHTf2SmEzDpBDp84rNoDnLEKUzwr9QwCdfjBbLU49VyqrlhV5xcuXKlYne3t4+WOHRucwN70hZgmyG4GHa+srKykJMUrf9tf6EvHmkVY59cg2QANvPKsQa4ZXxAHeEZeAuACYZEDH1Il/+fESe3voZKV0StkPKjRs3Mu3t7cNwz7rbkXABub09CbrN2NhYQ1VVVZkXPLX9wsELMjw+CeB0BQ9wAFQOIKQcWEs+BI0AS2rMShICeynI9csLO9bBvZaYOrxJoqOj42o4HK6dzZ2yHdHtrgMFAf4oJmuxF/yf/t4pz77RBPApgA8oAX+O38QBv+iktqSQdwkijUrN01os9wXQH+UjibTKpGwbOCZdlhi4eNjy6fEtCZw9e3b/ggULol6f5wCvvXdJMtQyQPgJhOpVF3LAIu93gLHOADV1JKd5EmQd+zoP7fLae5+KlwTHJgZg2TcduM1z9BmB6zxWnHPRaLTArjZ0m2ffiMHy6ELNOgP7PWkS0X8Quw8+HSkKSSjHJ/HehHT1JYz7eFwoTbeCG9GtzLzgIiDyyg+qXXfi6hSLxUawMq2/2XfCfCqnURgfH99P9hY8J+zzB85jEaF2p7Sn2nSIWEIPVxfJt79UKtVV2atLc+eI/Or9uPzn0rDxfYzpo/3xyqQAHOlM2gc+aZ1fv93zOZ3YxFBaWhrC6rQfrb8zDapQRFY4ffp0LUy9DZ1cv3vz8GUZhZ8qSLRmTP+15ieRQNAvz2xdLnu/+8AM8BxgTUWBvLpzlWx7OGJczHEjyrBWtO7FxYFj2kAsxERstszGMwgA3M6ysrKwTka04jp/7FS3rt86+RwLWFexmqd1coMghdDUPiJ7ft8qX9sbk8f3NsmLf2yVOFyIc+aZxytkVXkeNG7mgFEKXc+ZEyrBhzF7dGxmiQUkwsSm1Z7XDAKo244VwCBB5q2/tnHFg3w0pbswbQeHYK4imkf6F3/pkpf/3CE/feuyfAJXSUz6ZCIl8q/mEfnJwcvSM5DE9PHBCktVlk52Aqc8WgTBWMG4Ese2YfHixQHMle02b+MsAmfOnHkQm6oFdmPG7cGH2BoYLTtdSIIPg8bUnFldWPSPxkFJwzPtEql1IHl9NCX/bh5kE1l9HyxA4FmyUGHlwrzU+kdYOIiBgZiIjRi1wHllEcAE2grtu77Pvc0EJpguDRxQNYWYf3Yw8vFahGkAVu26FjJ5ziMGtgdCdUuCVq2znGmVCzOjTWIyo/sr7YQXrBAkRptnnEUA+WhBQYH7TT8Z60MRtW0E05X4mCKWIUnTa4JpA8yvIEydO0FRVlNVgM4iHb0TGqt1nLZWhoqnTB0rg80hMZiAjSSxRW2e8fRltBLrrVsf7xl30hRLnNQWCWmGCE3aFGi9AkEbSwwp1Kalbk2+RCvzteXxpgFtm2F3RWxkGkL4JqiWOJ5Il4tB9JCEHpUqxHl5EbConCcpG7glNrMWkjCQKstwQZ4JbOMcDTJmkQNFG2sdClYuy5UfPVaqYi9dHZfj53AYY0N0UIIOYAJ3xJlxUdtzw2zJ2ZnY0IZHVTdkEcApK+K1QJ/u5wneINPIIHTYkAKCWzaVJzASWF+RKy8/uUwKcwMyNJaSve92antjGZB2oRiRDhdXft+gcTc2IzbMAXvO1p5ZBDyyNGmE8+0MRy5uI6SY4Yj8d2KtxhYhk0nLiuIceembZVIQDsgAVqHn/tAhXf1J/RIbN1EBLlgXPIWwipETm9zMdxYBLF092AO5rUpwkiJ4BsrhZ35KIsrVbZwRSE5JIM8YbZ97olRPXv3Dk/Kz37VLS9eYI8nIpFwTKEMFIHbkaZyRkkVTc5LYYNUep5NGWQQAMJ5FAMdADRwPoNQOVj43YXhYxtjs7c2mjEQ2ryuU8iUhJfXSu3GsPMaXuWmjddjGEIZobuoI2OXANAf1SYnFgJyDLY6kG7IIoLSd1x82lEdyHXAGtQ5D7Zp/JaUg2MFUavt0KiW1lXkqpqFtVM51jBo5KLegebxEBv9mj2XKqSQWTymCGGwgNtS12zzjLAKojA0NDbnTnudWs7pQwxDuaM1qUAVRm44FdGDkGSILzAp9uRvbaAJXwKhAWwWuMZIoV9dEzLRakgJUURk9OzPLQGxo22hy5p1FAEWH+/r6XBPw9iDE0xb+CJr7d9UQY9WScQW1iCWBLze3x/XnB+Xtf3bLyfMDAIN/kKBlFDBiBut2VjFaRktCPjuFsTkkBhtw2E9CoUdsnnEWgY0bNzbAz4Zwe6ZtePXxEM6pBK/BtQBN7zwpR2sKckq7Rz/uk7eOXZHTLdgbgZBZACBFibOvsRzzerDRctOfWkJWNkanrl+Iibd5xGjAmHcWARYB2Dv9/f1GRcjv2FKlFlAtegfRtAUCENSugjKE1pXnymfvL5DlxVhFXHcxVmA+o8TZllalHGodwRmDlv7eV6u0iC9iIja3wEnMIIAd34F4PE5f0ya8t/nKF5a65mahcQNHW1mkgJVEoPEff325vLJjpTz1SJlT5swD9mcfktWY5I01mE9rueiY9s6IWHDplYD7HFBQntcMAjQRTHUIVxruXHj6ifulMC9HSRAcICgJnbx2cGiSfm40CK36jEa5dXInOYCYeUALQA4toY9pa8oy+tXmmDYAfBIkDk13H9bTUjNCfX19JT5q5zds2JBv90Y81P/8l43qr7p91isV091u3ChIN3vAE1kc0hPajeGkXsHoAqBkDFgS5WPnhiXJU9uru2rcQz2XzlOnTo2g3fq6urr26WBnWIAN2BAdXm9paTGzGWW8dNr1jQdMf47t1abVJF2CFkHccz0h7ddGZXAkqS5iLGPczoJ1rYH+SgjSOYb3guvixYujxHIz8ARzUwKswAloN27HYm1tbe7e4slHKuSH31qpH0mC4MRm0PWb7uMhpX7N+WDJsQ53p3aOaDn7qyHocqKyOYYNHHtwcDAGLHts2fT4pi5kG8F0JbhiacDVXhluydy2dKfnf31Ohscmtal1If360yvZUsnhxUK6i+OttI5W01osx8P59eL312dpHmt+Bj+gXMUPJrVw5VveVFPWrOH48eO1WJnqV69eXegloZe7hz+Vv32MGwsCcYAqGU5yFvFkxcCMMjL7Ji3Di195rnCcsN7LXYLHvewwXKdu8+bNWeu+7Wvj2xJgQ5LAYB/gd4HiFStWTG0PUcdrl4NH2/QAnki6nw8FZwdRgjaDmF9YfqS4ztul0la3trZOYNXpQ59HbweefeZEgA3pTvgaHsXBOgqXclcn1jFM/cDRJzyK9uIkZU50olti7iq5MeP+6mY/cHC1gdZHr1+/HsvPz98ym9uYEc17zgTYHCSC+AVlH5bYXbBGEL8ZBOFeXnnzTuObI/gtIAmt609MOLjvAXj3G3Q7gfMiYIXxO4GB9wP8NhAJY24EcJthq+cUj4yMCHw9xS8sP5yQtftWS+VsAu+IgBXIuQFfdX9m5R3mwoULwzy74ocJPcOyLQ8i/FWSMZbFRHd3dxK3zvozK7cHc/F1O+b0+K4IeIWdOHHiQWhyKwBF4WKVWEHKUR/hBEYZj4FxpPkljUHbRzZt2vRfb/976Xsa+D9p4H/Tf9nwWj/0kwAAAABJRU5ErkJggg==\");\n}\n\n.html-editor {\n  -fx-background-color: -color-border-default, -color-bg-default;\n  -fx-background-insets: 0, 1px;\n  -fx-padding: 2px;\n}\n.html-editor:contains-focus {\n  -fx-background-color: -color-accent-emphasis, -color-bg-default;\n}\n.html-editor .tool-bar {\n  -fx-padding: 4px;\n}\n.html-editor .button,\n.html-editor .toggle-button {\n  -fx-background-insets: 0;\n}\n.html-editor .toggle-button {\n  -color-button-bg-selected: -color-base-6;\n  -color-button-border-focused: transparent;\n}\n\n.color-picker.html-editor-foreground {\n  -fx-color-rect-x: 0;\n  -fx-color-rect-y: -4px;\n  -fx-color-rect-width: 8px;\n  -fx-color-rect-height: 8px;\n  -fx-color-label-visible: false;\n}\n.color-picker.html-editor-background {\n  -fx-color-rect-x: 0;\n  -fx-color-rect-y: -4px;\n  -fx-color-rect-width: 8px;\n  -fx-color-rect-height: 8px;\n  -fx-color-label-visible: false;\n}\n.color-picker.html-editor-foreground > .color-picker-label > .picker-color > .picker-color-rect, .color-picker.html-editor-background > .color-picker-label > .picker-color > .picker-color-rect {\n  -fx-stroke: none;\n}\n\n.color-picker.html-editor-foreground {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Text-Color.png\");\n}\n\n.color-picker.html-editor-background {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Background-Color.png\");\n}\n\n.html-editor-cut {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Cut.png\");\n}\n\n.html-editor-copy {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Copy.png\");\n}\n\n.html-editor-paste {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Paste.png\");\n}\n\n.html-editor-align-left {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Left.png\");\n}\n\n.html-editor-align-center {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Center.png\");\n}\n\n.html-editor-align-right {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Right.png\");\n}\n\n.html-editor-align-justify {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Justify.png\");\n}\n\n.html-editor-outdent {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Outdent.png\");\n}\n\n.html-editor-outdent:dir(rtl) {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Outdent-rtl.png\");\n}\n\n.html-editor-indent {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Indent.png\");\n}\n\n.html-editor-indent:dir(rtl) {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Indent-rtl.png\");\n}\n\n.html-editor-bullets {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Bullets.png\");\n}\n\n.html-editor-bullets:dir(rtl) {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Bullets-rtl.png\");\n}\n\n.html-editor-numbers {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Numbered.png\");\n}\n\n.html-editor-numbers:dir(rtl) {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Numbered-rtl.png\");\n}\n\n.html-editor-bold {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Bold.png\");\n}\n\n.html-editor-italic {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Italic.png\");\n}\n\n.html-editor-underline {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Underline.png\");\n}\n\n.html-editor-strike {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Strikethrough.png\");\n}\n\n.html-editor-hr {\n  -fx-graphic: url(\"/com/sun/javafx/scene/control/skin/modena/HTMLEditor-Break.png\");\n}\n\n.hyperlink {\n  -color-link-fg: -color-accent-fg;\n  -color-link-fg-visited: -color-fg-default;\n  -color-link-fg-armed: -color-fg-default;\n  -fx-cursor: hand;\n  -fx-underline: true;\n  -fx-text-fill: -color-link-fg;\n}\n.hyperlink:visited {\n  -fx-text-fill: -color-link-fg-visited;\n}\n.hyperlink:armed {\n  -fx-text-fill: -color-link-fg-armed;\n  -fx-underline: false;\n}\n.hyperlink:disabled {\n  -fx-opacity: 0.4;\n}\n.hyperlink:show-mnemonics > .mnemonic-underline {\n  -fx-stroke: -fx-text-fill;\n}\n\n.label {\n  -fx-text-fill: -color-fg-default;\n}\n.label:disabled {\n  -fx-opacity: 0.4;\n}\n.label:show-mnemonics > .mnemonic-underline {\n  -fx-stroke: -color-fg-default;\n}\n.label.left-pill, .label.center-pill, .label.right-pill {\n  -fx-padding: 6px 12px 6px 12px;\n  -fx-background-color: -color-border-default, -color-bg-subtle;\n  -fx-background-insets: 0, 1px;\n}\n.label.left-pill {\n  -fx-background-radius: 0px 0 0 0px, 0 0 0 0;\n}\n.label.center-pill {\n  -fx-background-radius: 0;\n}\n.label.right-pill {\n  -fx-background-radius: 0 0px 0px 0, 0 0 0 0;\n}\n.label.accent {\n  -fx-text-fill: -color-accent-fg;\n}\n.label.accent .font-icon, .label.accent .ikonli-font-icon {\n  -fx-icon-color: -color-accent-fg;\n  -fx-fill: -color-accent-fg;\n}\n.label.success {\n  -fx-text-fill: -color-success-fg;\n}\n.label.success .font-icon, .label.success .ikonli-font-icon {\n  -fx-icon-color: -color-success-fg;\n  -fx-fill: -color-success-fg;\n}\n.label.warning {\n  -fx-text-fill: -color-warning-fg;\n}\n.label.warning .font-icon, .label.warning .ikonli-font-icon {\n  -fx-icon-color: -color-warning-fg;\n  -fx-fill: -color-warning-fg;\n}\n.label.danger {\n  -fx-text-fill: -color-danger-fg;\n}\n.label.danger .font-icon, .label.danger .ikonli-font-icon {\n  -fx-icon-color: -color-danger-fg;\n  -fx-fill: -color-danger-fg;\n}\n.label.text-muted {\n  -fx-text-fill: -color-fg-muted;\n}\n.label.text-muted .font-icon, .label.text-muted .ikonli-font-icon {\n  -fx-icon-color: -color-fg-muted;\n  -fx-fill: -color-fg-muted;\n}\n.label.text-subtle {\n  -fx-text-fill: -color-fg-subtle;\n}\n.label.text-subtle .font-icon, .label.text-subtle .ikonli-font-icon {\n  -fx-icon-color: -color-fg-subtle;\n  -fx-fill: -color-fg-subtle;\n}\n.label.text-on-emphasis {\n  -fx-text-fill: -color-fg-emphasis;\n}\n.label.text-on-emphasis .font-icon, .label.text-on-emphasis .ikonli-font-icon {\n  -fx-icon-color: -color-fg-emphasis;\n  -fx-fill: -color-fg-emphasis;\n}\n.label:accent {\n  -fx-text-fill: -color-accent-emphasis;\n}\n.label:accent .font-icon, .label:accent .ikonli-font-icon {\n  -fx-icon-color: -color-accent-fg;\n  -fx-fill: -color-accent-fg;\n}\n.label:success {\n  -fx-text-fill: -color-success-emphasis;\n}\n.label:success .font-icon, .label:success .ikonli-font-icon {\n  -fx-icon-color: -color-success-fg;\n  -fx-fill: -color-success-fg;\n}\n.label:warning {\n  -fx-text-fill: -color-warning-emphasis;\n}\n.label:warning .font-icon, .label:warning .ikonli-font-icon {\n  -fx-icon-color: -color-warning-fg;\n  -fx-fill: -color-warning-fg;\n}\n.label:danger {\n  -fx-text-fill: -color-danger-emphasis;\n}\n.label:danger .font-icon, .label:danger .ikonli-font-icon {\n  -fx-icon-color: -color-danger-fg;\n  -fx-fill: -color-danger-fg;\n}\n\n.message {\n  -color-message-bg: -color-bg-subtle;\n  -color-message-fg-primary: -color-fg-default;\n  -color-message-fg-secondary: -color-fg-default;\n  -color-message-border: -color-border-muted;\n  -color-message-button-hover: -color-bg-default;\n  -color-message-border-interactive: -color-border-default;\n}\n.message.accent {\n  -color-message-bg: -color-accent-subtle;\n  -color-message-fg-primary: -color-accent-fg;\n  -color-message-fg-secondary: -color-fg-default;\n  -color-message-border: -color-accent-muted;\n  -color-message-button-hover: -color-accent-muted;\n  -color-message-border-interactive: -color-accent-emphasis;\n}\n.message.success {\n  -color-message-bg: -color-success-subtle;\n  -color-message-fg-primary: -color-success-fg;\n  -color-message-fg-secondary: -color-fg-default;\n  -color-message-border: -color-success-muted;\n  -color-message-button-hover: -color-success-muted;\n  -color-message-border-interactive: -color-success-emphasis;\n}\n.message.warning {\n  -color-message-bg: -color-warning-subtle;\n  -color-message-fg-primary: -color-warning-fg;\n  -color-message-fg-secondary: -color-fg-default;\n  -color-message-border: -color-warning-muted;\n  -color-message-button-hover: -color-warning-muted;\n  -color-message-border-interactive: -color-warning-emphasis;\n}\n.message.danger {\n  -color-message-bg: -color-danger-subtle;\n  -color-message-fg-primary: -color-danger-fg;\n  -color-message-fg-secondary: -color-fg-default;\n  -color-message-border: -color-danger-muted;\n  -color-message-button-hover: -color-danger-muted;\n  -color-message-border-interactive: -color-danger-emphasis;\n}\n.message.tile-base > .container {\n  -fx-background-color: -color-message-bg;\n  -fx-alignment: CENTER_LEFT;\n  -fx-border-color: -color-message-border;\n  -fx-border-width: 1px;\n  -fx-border-radius: 0px;\n}\n.message.tile-base > .container > .graphic:disabled {\n  -fx-opacity: 0.4;\n}\n.message.tile-base > .container > .header > .title {\n  -fx-text-fill: -color-message-fg-secondary;\n}\n.message.tile-base > .container > .header > .description Text {\n  -fx-fill: -color-message-fg-secondary;\n}\n.message.tile-base > .container > .header > .description Text:disabled {\n  -fx-opacity: 0.4;\n}\n.message.tile-base > .container .font-icon, .message.tile-base > .container .ikonli-font-icon {\n  -fx-icon-color: -color-message-fg-primary;\n  -fx-fill: -color-message-fg-primary;\n  -fx-icon-size: 24px;\n}\n.message.tile-base:hover:interactive > .container {\n  -fx-background-color: -color-message-bg;\n  -fx-border-color: -color-message-border-interactive;\n  -fx-cursor: hand;\n}\n.message.tile-base:has-title:has-description > .container > .header > .title {\n  -fx-text-fill: -color-message-fg-primary;\n}\n.message.tile-base:has-title:has-description > .container > .header > .description Text {\n  -fx-fill: -color-message-fg-secondary;\n}\n.message > .close-button {\n  -fx-background-radius: 100px;\n  -fx-padding: 0.5em;\n}\n.message > .close-button > .icon {\n  -fx-shape: \"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\";\n  -fx-scale-shape: true;\n  -fx-background-color: -color-message-fg-primary;\n  -fx-padding: 0.3em;\n}\n.message > .close-button:hover {\n  -fx-background-color: -color-message-border, -color-message-button-hover;\n  -fx-background-insets: 0, 1;\n}\n.message > .close-button:disabled {\n  -fx-opacity: 0.4;\n}\n.message:closeable > .container > .header > .title,\n.message:closeable > .container > .header > .description {\n  -fx-padding: 0 1.5em 0 0;\n}\n\n.modal-pane {\n  -color-modal-pane-overlay: rgba(0, 0, 0, 0.25);\n}\n.modal-pane > .scroll-pane > .viewport > * > .scrollable-content {\n  -fx-background-color: -color-modal-pane-overlay;\n}\n\n.modal-box {\n  -color-modal-box-bg: -color-bg-default;\n  -color-modal-box-close-fg: -color-fg-default;\n  -color-modal-box-close-bg-hover: -color-bg-subtle;\n  -fx-background-color: -color-modal-box-bg;\n}\n.modal-box > .close-button {\n  -fx-background-radius: 100px;\n  -fx-padding: 0.6em;\n}\n.modal-box > .close-button > .icon {\n  -fx-shape: \"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\";\n  -fx-scale-shape: true;\n  -fx-background-color: -color-modal-box-close-fg;\n  -fx-padding: 0.3em;\n}\n.modal-box > .close-button:hover {\n  -fx-background-color: -color-border-muted, -color-modal-box-close-bg-hover;\n  -fx-background-insets: 0, 1;\n}\n.modal-box .tile {\n  -fx-padding: 0;\n  -fx-background-radius: 0;\n}\n.modal-box .tile > .container {\n  -fx-padding: 0;\n  -fx-background-radius: 0;\n}\n\n.notification {\n  -color-notify-bg: -color-bg-subtle;\n  -color-notify-fg: -color-fg-default;\n  -color-notify-bg-hover: -color-bg-default;\n  -color-notify-fg-hover: -color-fg-default;\n  -color-notify-border: -color-border-default;\n  -color-notify-border-intent: -color-accent-emphasis;\n}\n.notification > .container {\n  -fx-background-color: -color-notify-border, -color-notify-bg;\n  -fx-background-insets: 0, 1px;\n  -fx-background-radius: 0px;\n  -fx-spacing: 1em;\n  -fx-padding: 0 0 1em 0;\n}\n.notification > .container > .header {\n  -fx-padding: 1em 1em 0 1em;\n  -fx-spacing: 0.75em;\n}\n.notification > .container > .header > .graphic .font-icon, .notification > .container > .header > .graphic .ikonli-font-icon {\n  -fx-icon-size: 24px;\n}\n.notification > .container > .header > .message Text {\n  -fx-fill: -color-notify-fg;\n}\n.notification > .container > .header > .actions {\n  -fx-alignment: CENTER_RIGHT;\n  -fx-spacing: 5px;\n}\n.notification > .container > .header > .actions > .secondary-menu-button {\n  -fx-background-radius: 100px;\n  -fx-padding: 0.5em 0.75em 0.5em 0.75em;\n}\n.notification > .container > .header > .actions > .secondary-menu-button > .icon {\n  -fx-shape: \"M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\";\n  -fx-scale-shape: true;\n  -fx-background-color: -color-notify-fg;\n  -fx-background-insets: 0;\n  -fx-padding: 0.3em 0.1em 0.3em 0.1em;\n}\n.notification > .container > .header > .actions > .secondary-menu-button:hover {\n  -fx-background-color: -color-border-default, -color-notify-bg-hover;\n  -fx-background-insets: 0, 1;\n}\n.notification > .container > .header > .actions > .secondary-menu-button:hover > .icon {\n  -fx-background-color: -color-notify-fg-hover;\n}\n.notification > .container > .header > .actions > .close-button {\n  -fx-background-radius: 100px;\n  -fx-padding: 0.5em;\n}\n.notification > .container > .header > .actions > .close-button > .icon {\n  -fx-shape: \"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\";\n  -fx-scale-shape: true;\n  -fx-background-color: -color-notify-fg;\n  -fx-padding: 0.3em;\n}\n.notification > .container > .header > .actions > .close-button:hover {\n  -fx-background-color: -color-border-default, -color-notify-bg-hover;\n  -fx-background-insets: 0, 1;\n}\n.notification > .container > .header > .actions > .close-button:hover > .icon {\n  -fx-background-color: -color-notify-fg-hover;\n}\n.notification > .container > .header:disabled {\n  -fx-opacity: 0.4;\n}\n.notification > .container > .button-bar {\n  -fx-padding: 0 1em 0 1em;\n}\n.notification > .container.elevated-1 > .container {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 2px, 0.5, 0, 2);\n}\n.notification > .container.elevated-2 > .container {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0.5, 0, 2);\n}\n.notification > .container.elevated-3 > .container {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 16px, 0.5, 0, 2);\n}\n.notification > .container.elevated-4 > .container {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 20px, 0.5, 0, 2);\n}\n.notification > .container.interactive:hover > .container {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0.5, 0, 2);\n}\n.notification.accent {\n  -color-notify-border-intent: -color-accent-emphasis;\n}\n.notification.accent > .container {\n  -fx-background-color: -color-notify-border, -color-notify-bg, -color-notify-border-intent -color-notify-bg -color-notify-bg -color-notify-bg, -color-notify-bg;\n  -fx-background-insets: 0, 1, 1, 1 1 1 6px;\n  -fx-background-radius: 0px;\n}\n.notification.accent > .container > .header > .graphic .font-icon, .notification.accent > .container > .header > .graphic .ikonli-font-icon {\n  -fx-fill: -color-accent-emphasis;\n  -fx-icon-color: -color-accent-emphasis;\n}\n.notification.success {\n  -color-notify-border-intent: -color-success-emphasis;\n}\n.notification.success > .container {\n  -fx-background-color: -color-notify-border, -color-notify-bg, -color-notify-border-intent -color-notify-bg -color-notify-bg -color-notify-bg, -color-notify-bg;\n  -fx-background-insets: 0, 1, 1, 1 1 1 6px;\n  -fx-background-radius: 0px;\n}\n.notification.success > .container > .header > .graphic .font-icon, .notification.success > .container > .header > .graphic .ikonli-font-icon {\n  -fx-fill: -color-success-emphasis;\n  -fx-icon-color: -color-success-emphasis;\n}\n.notification.warning {\n  -color-notify-border-intent: -color-warning-emphasis;\n}\n.notification.warning > .container {\n  -fx-background-color: -color-notify-border, -color-notify-bg, -color-notify-border-intent -color-notify-bg -color-notify-bg -color-notify-bg, -color-notify-bg;\n  -fx-background-insets: 0, 1, 1, 1 1 1 6px;\n  -fx-background-radius: 0px;\n}\n.notification.warning > .container > .header > .graphic .font-icon, .notification.warning > .container > .header > .graphic .ikonli-font-icon {\n  -fx-fill: -color-warning-emphasis;\n  -fx-icon-color: -color-warning-emphasis;\n}\n.notification.danger {\n  -color-notify-border-intent: -color-danger-emphasis;\n}\n.notification.danger > .container {\n  -fx-background-color: -color-notify-border, -color-notify-bg, -color-notify-border-intent -color-notify-bg -color-notify-bg -color-notify-bg, -color-notify-bg;\n  -fx-background-insets: 0, 1, 1, 1 1 1 6px;\n  -fx-background-radius: 0px;\n}\n.notification.danger > .container > .header > .graphic .font-icon, .notification.danger > .container > .header > .graphic .ikonli-font-icon {\n  -fx-fill: -color-danger-emphasis;\n  -fx-icon-color: -color-danger-emphasis;\n}\n\n.pagination {\n  -fx-padding: 0;\n  -fx-arrow-button-gap: 4;\n  -fx-arrows-visible: true;\n  -fx-tooltip-visible: false;\n  -fx-page-information-visible: true;\n  -fx-page-information-alignment: bottom;\n}\n.pagination > .page {\n  -fx-background-color: transparent;\n}\n.pagination > .pagination-control {\n  -fx-background-color: transparent;\n  -fx-font-size: 1em;\n}\n.pagination > .pagination-control > .control-box {\n  -fx-padding: 0.5em 0 0 0;\n  -fx-spacing: 2px;\n  -fx-alignment: CENTER;\n}\n.pagination > .pagination-control > .control-box .number-button {\n  -fx-padding: 0;\n}\n.pagination > .pagination-control > .control-box > .left-arrow-button > .left-arrow {\n  -fx-shape: \"M14 7l-5 5 5 5V7z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-fg-default;\n}\n.pagination > .pagination-control > .control-box > .right-arrow-button > .right-arrow {\n  -fx-shape: \"M10 17l5-5-5-5v10z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-fg-default;\n}\n.pagination > .pagination-control > .page-information {\n  -fx-padding: 0.5em 0 0 0;\n}\n.pagination.bullet > .pagination-control > .control-box {\n  -fx-spacing: 0;\n}\n.pagination.bullet > .pagination-control > .control-box > .left-arrow-button {\n  -fx-background-radius: 10em;\n  -fx-padding: 0 0.25em 0 0.083em;\n}\n.pagination.bullet > .pagination-control > .control-box > .right-arrow-button {\n  -fx-background-radius: 10em;\n  -fx-padding: 0 0.083em 0 0.25em;\n}\n.pagination.bullet > .pagination-control > .control-box > .bullet-button {\n  -fx-background-radius: 0, 10em, 10em;\n  -fx-background-color: transparent, -color-border-default, -color-bg-subtle;\n  -fx-background-insets: 0, 5, 6;\n}\n.pagination.bullet > .pagination-control > .control-box > .bullet-button:selected {\n  -fx-background-color: transparent, -color-accent-emphasis;\n}\n\n.popover {\n  -fx-background-color: -color-bg-overlay;\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0px, 0, 2);\n}\n.popover > .border {\n  -fx-stroke: -color-border-default;\n  -fx-stroke-width: 1px;\n  -fx-fill: -color-bg-overlay;\n}\n.popover > .content {\n  -fx-padding: 10px 10px 10px 10px;\n}\n.popover > .content > .title {\n  -fx-padding: 0 0 1em 0;\n}\n.popover > .content > .title > .text {\n  -fx-text-fill: -color-fg-default;\n  -fx-font-size: 1.25em;\n  -fx-alignment: CENTER_LEFT;\n}\n.popover > .content > .title > .icon > .graphics > .circle {\n  -fx-fill: transparent;\n}\n.popover > .content > .title > .icon > .graphics > .line {\n  -fx-stroke: -color-fg-default;\n  -fx-stroke-width: 1px;\n}\n\n.progress-bar {\n  -color-progress-bar-track: -color-bg-subtle;\n  -color-progress-bar-fill: -color-accent-emphasis;\n  -fx-indeterminate-bar-length: 60;\n  -fx-indeterminate-bar-escape: true;\n  -fx-indeterminate-bar-flip: true;\n  -fx-indeterminate-bar-animation-time: 2;\n}\n.progress-bar > .track {\n  -fx-background-color: -color-progress-bar-track;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0px;\n}\n.progress-bar > .bar {\n  -fx-background-color: -color-progress-bar-fill;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0px;\n  -fx-padding: 0.4em;\n}\n.progress-bar.small > .bar {\n  -fx-padding: 2px;\n}\n.progress-bar.medium > .bar {\n  -fx-padding: 0.4em;\n}\n.progress-bar.large > .bar {\n  -fx-padding: 0.8em;\n}\n.progress-bar:disabled {\n  -fx-opacity: 0.4;\n}\n\n.progress-indicator {\n  -fx-indeterminate-segment-count: 12;\n  -fx-spin-enabled: true;\n}\n.progress-indicator > .determinate-indicator > .indicator {\n  -fx-background-color: -color-border-default, -color-bg-default;\n  -fx-background-insets: 0, 1;\n}\n.progress-indicator > .determinate-indicator > .progress {\n  -fx-background-color: -color-accent-emphasis;\n  -fx-padding: 0.6em;\n}\n.progress-indicator > .determinate-indicator > .tick {\n  -fx-background-color: -color-fg-emphasis;\n  -fx-background-insets: 0;\n  -fx-shape: \"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\";\n  -fx-scale-shape: true;\n}\n.progress-indicator > .determinate-indicator > .percentage {\n  -fx-font-size: 0.8em;\n  -fx-fill: -color-fg-default;\n}\n.progress-indicator > .determinate-indicator:disabled {\n  -fx-opacity: 0.4;\n}\n.progress-indicator:indeterminate > .spinner {\n  -fx-background-color: transparent;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0;\n  -fx-border-color: transparent;\n  -fx-border-width: 0;\n  -fx-border-radius: 0;\n  -fx-padding: 0;\n}\n.progress-indicator:indeterminate .segment {\n  -fx-background-color: -color-accent-emphasis;\n}\n.progress-indicator:indeterminate .segment0 {\n  -fx-shape: \"M41.98 14.74 a3.5,3.5 0 1,1 0,1 Z\";\n}\n.progress-indicator:indeterminate .segment1 {\n  -fx-shape: \"M33.75 6.51 a3.5,3.5 0 1,1 0,1 Z\";\n}\n.progress-indicator:indeterminate .segment2 {\n  -fx-shape: \"M22.49 3.5 a3.5,3.5 0 1,1 0,1 Z\";\n}\n.progress-indicator:indeterminate .segment3 {\n  -fx-shape: \"M11.24 6.51 a3.5,3.5 0 1,1 0,1 Z\";\n}\n.progress-indicator:indeterminate .segment4 {\n  -fx-shape: \"M3.01 14.74 a3.5,3.5 0 1,1 0,1 Z\";\n}\n.progress-indicator:indeterminate .segment5 {\n  -fx-shape: \"M0.0 26.0 a3.5,3.5 0 1,1 0,1 Z\";\n}\n.progress-indicator:indeterminate .segment6 {\n  -fx-shape: \"M3.01 37.25 a3.5,3.5 0 1,1 0,1 Z\";\n}\n.progress-indicator:indeterminate .segment7 {\n  -fx-shape: \"M11.25 45.48 a3.5,3.5 0 1,1 0,1 Z\";\n}\n.progress-indicator:indeterminate .segment8 {\n  -fx-shape: \"M22.5 48.5 a3.5,3.5 0 1,1 0,1 Z\";\n}\n.progress-indicator:indeterminate .segment9 {\n  -fx-shape: \"M33.75 45.48 a3.5,3.5 0 1,1 0,1 Z\";\n}\n.progress-indicator:indeterminate .segment10 {\n  -fx-shape: \"M41.98 37.25 a3.5,3.5 0 1,1 0,1 Z\";\n}\n.progress-indicator:indeterminate .segment11 {\n  -fx-shape: \"M45.0 26.0 a3.5,3.5 0 1,1 0,1 Z\";\n}\n\n.ring-progress-indicator {\n  -fx-indeterminate-animation-time: 3;\n  -color-progress-indicator-track: -color-bg-subtle;\n  -color-progress-indicator-fill: -color-accent-emphasis;\n}\n.ring-progress-indicator > .container {\n  -fx-min-width: 4em;\n}\n.ring-progress-indicator > .container > .track {\n  -fx-stroke: -color-progress-indicator-track;\n  -fx-stroke-width: 5px;\n}\n.ring-progress-indicator > .container > .ring {\n  -fx-stroke: -color-progress-indicator-fill;\n  -fx-stroke-width: 5px;\n}\n.ring-progress-indicator:indeterminate > .container {\n  -fx-min-width: 1.5em;\n}\n.ring-progress-indicator:indeterminate > .container > .track {\n  visibility: hidden;\n}\n.ring-progress-indicator:indeterminate > .container > .ring {\n  -fx-stroke: linear-gradient(-color-bg-default, -color-progress-indicator-fill);\n  -fx-stroke-width: 2px;\n}\n\n.scroll-bar {\n  -fx-background-color: -color-border-subtle;\n  -fx-opacity: 0.5;\n}\n.scroll-bar > .thumb {\n  -fx-background-color: -color-fg-muted;\n  -fx-background-radius: 0px;\n}\n.scroll-bar > .track {\n  -fx-background-color: transparent;\n  -fx-border-radius: 0;\n}\n.scroll-bar > .increment-button {\n  visibility: hidden;\n  -fx-managed: false;\n}\n.scroll-bar > .increment-button > .increment-arrow {\n  -fx-shape: \" \";\n  -fx-padding: 0;\n}\n.scroll-bar > .decrement-button {\n  visibility: hidden;\n  -fx-managed: false;\n}\n.scroll-bar > .decrement-button > .decrement-arrow {\n  -fx-shape: \" \";\n  -fx-padding: 0;\n}\n.scroll-bar:horizontal {\n  -fx-pref-height: 8px;\n}\n.scroll-bar:vertical {\n  -fx-pref-width: 8px;\n}\n.scroll-bar:hover, .scroll-bar:pressed, .scroll-bar:focused {\n  -fx-opacity: 1;\n}\n\n.scroll-pane {\n  -fx-background-color: transparent;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0;\n  -fx-padding: 0;\n}\n.scroll-pane > .viewport {\n  -fx-background-color: transparent;\n}\n.scroll-pane > .corner {\n  -fx-background-color: -color-border-subtle;\n  -fx-opacity: 0.5;\n}\n.scroll-pane:disabled > .scroll-bar {\n  -fx-opacity: 0.25;\n}\n\n.separator {\n  -color-separator: -color-border-muted;\n}\n.separator:horizontal {\n  -fx-padding: 0.75em 0 0.75em 0;\n}\n.separator:horizontal > .line {\n  -fx-border-color: -color-separator transparent transparent transparent;\n  -fx-border-insets: 1px 0 0 0;\n}\n.separator:vertical {\n  -fx-padding: 0 0.75em 0 0.75em;\n}\n.separator:vertical > .line {\n  -fx-border-color: transparent transparent transparent -color-separator;\n  -fx-border-insets: 0 0 0 1px;\n}\n.separator.small:horizontal {\n  -fx-padding: 0.25em 0 0.25em 0;\n}\n.separator.small:vertical {\n  -fx-padding: 0 0.25em 0 0.25em;\n}\n.separator.medium:horizontal {\n  -fx-padding: 0.75em 0 0.75em 0;\n}\n.separator.medium:vertical {\n  -fx-padding: 0 0.75em 0 0.75em;\n}\n.separator.large:horizontal {\n  -fx-padding: 1.5em 0 1.5em 0;\n}\n.separator.large:vertical {\n  -fx-padding: 0 1.5em 0 1.5em;\n}\n\n.text-input {\n  -color-input-bg: -color-bg-default;\n  -color-input-fg: -color-fg-default;\n  -color-input-border: -color-border-default;\n  -color-input-bg-focused: -color-bg-default;\n  -color-input-border-focused: -color-accent-emphasis;\n  -color-input-bg-readonly: -color-bg-subtle;\n  -color-input-bg-highlight: -color-accent-muted;\n  -color-input-fg-highlight: -color-fg-default;\n  -fx-background-color: -color-input-border, -color-input-bg;\n  -fx-background-insets: 0, 1px;\n  -fx-background-radius: 0px, 0;\n  -fx-text-fill: -color-input-fg;\n  -fx-highlight-fill: -color-input-bg-highlight;\n  -fx-highlight-text-fill: -color-input-fg-highlight;\n  -fx-prompt-text-fill: -color-fg-subtle;\n  -fx-padding: 6px 12px 6px 12px;\n  -fx-cursor: text;\n}\n.text-input:focused {\n  -fx-background-color: -color-input-border-focused, -color-input-bg-focused;\n  -fx-prompt-text-fill: transparent;\n}\n.text-input:disabled {\n  -fx-opacity: 0.4;\n}\n.text-input:disabled > .scroll-pane {\n  -fx-opacity: 1;\n}\n.text-input:success {\n  -color-input-bg: -color-bg-default;\n  -color-input-fg: -color-success-fg;\n  -color-input-border: -color-success-emphasis;\n  -color-input-border-focused: -color-success-emphasis;\n}\n.text-input:danger {\n  -color-input-bg: -color-bg-default;\n  -color-input-fg: -color-danger-fg;\n  -color-input-border: -color-danger-emphasis;\n  -color-input-border-focused: -color-danger-emphasis;\n}\n.text-input:readonly {\n  -fx-background-color: -color-input-border, -color-input-bg-readonly;\n}\n.text-input:readonly:focused {\n  -fx-background-color: -color-input-border-focused, -color-input-bg-readonly;\n}\n.text-input.edge-to-edge {\n  -fx-background-color: -color-input-bg;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0;\n}\n.text-input.left-pill {\n  -fx-background-radius: 0px 0 0 0px, 0 0 0 0;\n  -fx-background-insets: 0, 1px 0 1px 1px;\n}\n.text-input.left-pill:focused {\n  -fx-background-insets: 0, 1px;\n}\n.text-input.center-pill {\n  -fx-background-radius: 0;\n  -fx-background-insets: 0, 1px 0 1px 0;\n}\n.text-input.center-pill:focused {\n  -fx-background-insets: 0, 1px;\n}\n.text-input.right-pill {\n  -fx-background-radius: 0 0px 0px 0, 0 0 0 0;\n  -fx-background-insets: 0, 1px 1px 1px 0;\n}\n.text-input.right-pill:focused {\n  -fx-background-insets: 0, 1px;\n}\n.text-input .context-menu {\n  -fx-font-size: 14px;\n  -fx-font-weight: normal;\n}\n.text-input .context-menu .menu-item {\n  -fx-cursor: default;\n}\n\n.text-field.small {\n  -fx-padding: 4.2857142857px 8.5714285714px 4.2857142857px 8.5714285714px;\n  -fx-font-size: 0.8em;\n}\n.text-field.large {\n  -fx-padding: 8.4px 16.8px 8.4px 16.8px;\n  -fx-font-size: 1.25em;\n}\n.text-field.rounded {\n  -fx-background-radius: 10em;\n}\n\n.text-area {\n  -fx-padding: 2px;\n  -fx-cursor: default;\n}\n.text-area .content {\n  -fx-cursor: text;\n  -fx-padding: 6px 12px 6px 12px;\n}\n\n.password-field {\n  -fx-text-fill: -color-fg-muted;\n}\n\n.tile-base > .container {\n  -fx-padding: 1em;\n  -fx-alignment: CENTER_LEFT;\n  -fx-background-radius: 0px;\n  -fx-spacing: 1em;\n}\n.tile-base > .container > .graphic:disabled {\n  -fx-opacity: 0.4;\n}\n.tile-base > .container > .header {\n  -fx-alignment: CENTER_LEFT;\n  -fx-padding: 0;\n}\n.tile-base > .container > .header > .title {\n  -fx-font-size: 1.05em;\n}\n.tile-base > .container > .header > .description Text:disabled {\n  -fx-opacity: 0.4;\n}\n.tile-base:has-title:has-description > .container > .header {\n  -fx-spacing: 0.5em;\n  -fx-alignment: TOP_LEFT;\n}\n.tile-base:has-title:has-description > .container > .header > .description Text {\n  -fx-fill: -color-fg-muted;\n}\n\n.tile:hover:interactive > .container {\n  -fx-background-color: -color-bg-subtle;\n  -fx-cursor: hand;\n}\n\n.titled-pane {\n  -fx-background-color: -color-bg-default;\n  -fx-text-fill: -color-fg-default;\n  -fx-effect: none;\n}\n.titled-pane.elevated-1 {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 2px, 0.5, 0, 2);\n}\n.titled-pane.elevated-2 {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0.5, 0, 2);\n}\n.titled-pane.elevated-3 {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 16px, 0.5, 0, 2);\n}\n.titled-pane.elevated-4 {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 20px, 0.5, 0, 2);\n}\n.titled-pane > .title {\n  -fx-background-color: -color-border-default, -color-bg-default;\n  -fx-padding: 10px 20px 10px 20px;\n}\n.titled-pane > .title > .text {\n  -fx-font-size: 1.25em;\n}\n.titled-pane > .title > .arrow-button {\n  -fx-background-color: none;\n  -fx-background-insets: 0;\n  -fx-background-radius: 0;\n  -fx-padding: 0 10px 0 0;\n}\n.titled-pane > .title > .arrow-button > .arrow {\n  -fx-shape: \"M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-fg-default;\n  -fx-padding: 4px 5px 4px 5px;\n}\n.titled-pane > .content {\n  -fx-border-color: -color-border-default;\n  -fx-border-width: 0 1px 1px 1px;\n  -fx-border-radius: 0 0 0px 0px;\n  -fx-background-radius: 0 0 0px 0px;\n  -fx-background-color: -color-bg-default;\n  -fx-padding: 20px 20px 10px 20px;\n  -fx-alignment: TOP_LEFT;\n}\n.titled-pane:disabled > .title > *,\n.titled-pane:disabled > .content > * {\n  -fx-opacity: 0.4;\n}\n.titled-pane:expanded > .title {\n  -fx-background-radius: 0px 0px 0 0, 0 0 0 0;\n  -fx-background-insets: 0, 1px 1px 0 1px;\n}\n.titled-pane:collapsed > .title {\n  -fx-background-insets: 0, 1px;\n  -fx-background-radius: 0px, 0;\n}\n.titled-pane.interactive:hover {\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0.5, 0, 2);\n}\n.titled-pane:show-mnemonics > .mnemonic-underline {\n  -fx-stroke: -color-fg-default;\n}\n.titled-pane.dense > .title {\n  -fx-padding: 5px 10px 5px 10px;\n}\n.titled-pane.dense > .title > .text {\n  -fx-font-size: 1.1em;\n}\n.titled-pane.dense > .title > .arrow-button {\n  -fx-padding: 0 10px 0 0;\n}\n.titled-pane.dense > .content {\n  -fx-padding: 10px 10px 5px 10px;\n}\n.titled-pane.alt-icon > .title > .arrow-button > .arrow {\n  -fx-shape: \"M7 10l5 5 5-5z\";\n  -fx-scale-shape: false;\n}\n\n.toggle-switch {\n  -fx-thumb-move-animation-time: 200;\n}\n.toggle-switch > .label-container > .label {\n  -fx-font-size: 1em;\n  -fx-text-fill: -color-fg-default;\n  -fx-padding: 2px 6px 2px 0;\n}\n.toggle-switch > .thumb {\n  -fx-background-color: -color-border-default, -color-fg-emphasis;\n  -fx-background-insets: 0, 2px;\n  -fx-background-radius: 10em;\n  -fx-padding: 0.85em;\n  -fx-alignment: CENTER;\n  -fx-content-display: LEFT;\n  -fx-opacity: 0.8;\n}\n.toggle-switch > .thumb-area {\n  -fx-background-radius: 1em;\n  -fx-background-color: -color-border-default, -color-bg-subtle;\n  -fx-background-insets: 0, 1px;\n  -fx-padding: 0.85em 1.4em 0.85em 1.4em;\n}\n.toggle-switch:selected > .thumb {\n  -fx-background-color: -color-accent-emphasis, -color-fg-emphasis;\n  -fx-opacity: 1;\n}\n.toggle-switch:selected > .thumb-area {\n  -fx-background-color: -color-accent-emphasis, -color-accent-emphasis;\n}\n.toggle-switch:selected:success > .thumb {\n  -fx-background-color: -color-success-emphasis, -color-fg-emphasis;\n}\n.toggle-switch:selected:success > .thumb-area {\n  -fx-background-color: -color-success-emphasis, -color-success-emphasis;\n}\n.toggle-switch:selected:danger > .thumb {\n  -fx-background-color: -color-danger-emphasis, -color-fg-emphasis;\n}\n.toggle-switch:selected:danger > .thumb-area {\n  -fx-background-color: -color-danger-emphasis, -color-danger-emphasis;\n}\n.toggle-switch:right > .label-container > .label {\n  -fx-padding: 2px 0 2px 6px;\n}\n.toggle-switch:disabled {\n  -fx-opacity: 0.4;\n}\n\n.tool-bar {\n  -fx-background-color: -color-border-muted, -color-bg-subtle;\n  -fx-background-insets: 0, 0 0 1px 0;\n  -fx-padding: 4px 0.3em 4px 0.3em;\n  -fx-spacing: 4px;\n  -fx-alignment: CENTER_LEFT;\n}\n.tool-bar > .container > .button,\n.tool-bar > .container > .menu-button,\n.tool-bar > .container > .split-menu-button {\n  -color-button-bg: -color-bg-subtle;\n  -color-button-bg-hover: -color-base-6;\n  -color-button-border-hover: -color-accent-muted;\n  -color-button-border-focused: -color-accent-muted;\n  -fx-background-insets: 0;\n  -fx-effect: none;\n}\n.tool-bar > .container > .button:hover,\n.tool-bar > .container > .menu-button:hover,\n.tool-bar > .container > .split-menu-button:hover {\n  -fx-background-insets: 0, 1px;\n}\n.tool-bar > .container > .button.button-icon,\n.tool-bar > .container > .menu-button.button-icon,\n.tool-bar > .container > .split-menu-button.button-icon {\n  -fx-padding: 6px 12px 6px 12px;\n}\n.tool-bar > .container .toggle-button {\n  -color-button-bg: -color-bg-subtle;\n  -color-button-border-hover: -color-accent-muted;\n  -color-button-bg-selected: -color-base-6;\n  -color-button-fg-selected: -color-fg-default;\n  -fx-background-insets: 0;\n  -fx-effect: none;\n}\n.tool-bar > .container .toggle-button:hover {\n  -fx-background-insets: 0, 1px;\n  -fx-background-color: -color-button-border-hover, -color-bg-subtle;\n}\n.tool-bar > .container .toggle-button:hover:selected {\n  -fx-background-color: -color-button-border-hover, -color-base-6;\n}\n.tool-bar > .container .breadcrumbs {\n  -fx-background-color: -color-border-default, -color-bg-default;\n  -fx-background-insets: 0, 1px;\n  -fx-background-radius: 0px, 0;\n  -fx-padding: 4px 1em 4px 1em;\n}\n.tool-bar > .container .breadcrumbs > .hyperlink {\n  -color-link-fg: -color-fg-default;\n  -color-link-fg-visited: -color-fg-default;\n  -color-link-fg-armed: -color-fg-default;\n  -fx-padding: 2px 0.5em 2px 0.5em;\n  -fx-underline: false;\n  -fx-cursor: default;\n}\n.tool-bar > .container .breadcrumbs > .hyperlink:hover {\n  -fx-background-color: -color-base-6;\n  -fx-background-radius: 0px, 0;\n}\n.tool-bar > .tool-bar-overflow-button {\n  -fx-padding: 0 0.3em 0 4px;\n}\n.tool-bar > .tool-bar-overflow-button > .arrow {\n  -fx-shape: \"M5.06 5 4 6.06 7.94 10 4 13.94 5.06 15l5-5z M11 5 9.94 6.06 13.88 10l-3.94 3.94L11 15l5-5z\";\n  -fx-scale-shape: false;\n  -fx-background-color: -color-fg-default;\n}\n.tool-bar > .tool-bar-overflow-button .menu-item:hover {\n  -fx-background-color: transparent;\n}\n.tool-bar:vertical {\n  -fx-background-insets: 0, 0 1px 0 0;\n  -fx-padding: 0.3em 4px 0.3em 4px;\n  -fx-alignment: TOP_LEFT;\n}\n.tool-bar:vertical > .container > .separator {\n  -fx-orientation: horizontal;\n}\n.tool-bar:vertical > .tool-bar-overflow-button {\n  -fx-padding: 4px 0 0.3em 0;\n}\n.tool-bar:vertical.right {\n  -fx-background-insets: 0, 0 0 0 1px;\n}\n.tool-bar.bottom {\n  -fx-background-insets: 0, 1px 0 0 0;\n}\n\n.tooltip {\n  -fx-background-color: -color-border-default, -color-bg-overlay;\n  -fx-background-insets: 0, 1px;\n  -fx-background-radius: 0px, 0;\n  -fx-text-fill: -color-fg-default;\n  -fx-font-size: 14px;\n  -fx-font-weight: normal;\n  -fx-padding: 6px 12px 6px 12px;\n  -fx-opacity: 0.85;\n  -fx-effect: dropshadow(three-pass-box, -color-shadow-default, 8px, 0px, 0, 2);\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/style/tweaks.css",
    "content": "/* Applies common 5px padding to component */\n.padded {\n    -fx-padding: 5px;\n}\n\n.dark-scroll-pane > .viewport {\n   -fx-background-color: -color-bg-inset;\n}\n\n/* For the file menu items with nested nodes. We don't want any padding so the whole menu is clickable */\n.closable-menu-item {\n    -fx-padding: 0;\n}\n\n/* AtlantaFX tweaks */\n.dark-combo-box {\n    -fx-background-color: -color-border-default, -color-bg-inset;\n}\n\n/* AtlantaFX flat-accent buttons do not have clear focus display */\n.button.accent.flat:focused {\n    -fx-background-color: -color-border-default;\n}\n\n/* AtlantaFX tweaks to allow borderless text-fields */\n.text-field.accent {\n    -fx-background-insets: 0;\n    -fx-background-color: -color-input-bg;\n}\n.text-field.accent:focused {\n    -fx-background-color: -color-input-bg;\n}\n\n/* Makes text red, typically for error displays */\n.error-text {\n    -fx-text-fill: red;\n}\n.warn-text {\n    -fx-text-fill: yellow;\n}\n.mono-text {\n    -fx-font-family: 'JetBrains Mono';\n}\n\n/* Clearly separates filter from the workspace tree */\n.workspace-filter-pane {\n    -fx-border-color: -color-border-muted;\n    -fx-border-width: 1px 0 0 0;\n}\n.workspace-filter-text {\n    -fx-border-width: 0;\n    -fx-background-insets: 0;\n}\n\n/* Indicates a class in the workspace tree has been edited */\n.modified-class-cell {\n    -fx-text-fill: rgb(75, 175, 210);\n}\n\n/* Applies background colors */\n.background {\n    -fx-background-color: -color-bg-default;\n}\n.background-light {\n    -fx-background-color: -color-border-subtle;\n}\n.background-dark {\n    -fx-background-color: -color-bg-overlay !important;\n}\n\n/* Tweak the larger titles to not be bold (which looks rather ugly in most cases) */\n.title-1 {\n    -fx-font-size: 2em;\n    -fx-font-weight: normal;\n}\n.title-2 {\n    -fx-font-size: 1.75em;\n    -fx-font-weight: normal;\n}\n.title-3 {\n    -fx-font-size: 1.5em;\n    -fx-font-weight: normal;\n}\n\n/* Rounds the display of a container by its bg/border colors */\n.round-container-single {\n    -fx-border-radius: 10em;\n    -fx-background-radius: 10em;\n}\n.round-container-multi {\n    -fx-border-radius: 1em;\n    -fx-background-radius: 1em;\n}\n\n/* Used at top of editor control */\n.search-bar {\n    -fx-background-color: -color-bg-inset;\n    -fx-border-color: -color-base-6;\n    -fx-border-width: 0 0 1px 0;\n}\n.search-bar .text-field {\n    -fx-border-color: -color-base-6;\n    -fx-border-width: 0 1px 0 0;\n}\n.search-result-list-cell {\n    -fx-text-fill: -color-fg-default;\n    -fx-padding: 4 4 4 4;\n}\n.search-result-list-cell:focused,\n.search-result-list-cell:hover {\n    -fx-background-color: -color-base-7;\n}\n\n.variable-table .table-row-cell {\n    -fx-cell-size: 24px;\n}\n.analysis-value-changed {\n    -fx-background-color: -color-accent-muted;\n}\n\n.transparent-tree {\n    -fx-background-color: transparent;\n    -fx-border-width: 0 0 1 0;\n}\n.transparent-cell {\n    -fx-background-color: transparent;\n}\n.transparent-cell:selected {\n    -fx-background-color: rgb(54, 58, 65);\n}\n\n/* Removes borders from controls */\n.borderless {\n    -fx-border-width: 0px;\n    -fx-border-insets: 0px;\n}\n.border-muted {\n    -fx-border-color: -color-border-muted;\n    -fx-border-width: 1px;\n}\n\n.graphic-button {\n    -fx-border-width: 0px;\n    -fx-background-radius: 5px;\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/syntax/enigma.css",
    "content": ".code-area .keyword {\n    -fx-fill: rgb(0, 175, 255);\n}\n\n.code-area .comment {\n\t-fx-fill: rgb(175, 175, 215);\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/syntax/enigma.json",
    "content": "{\n  \"name\": \"Enigma mappings\",\n  \"regex\": \"\",\n  \"classes\": [],\n  \"sub-rules\": [\n    {\n      \"name\": \"SingleLineComment\",\n      \"regex\": \"(?:COMMENT|#).+\",\n      \"classes\": [\n        \"comment\"\n      ],\n      \"sub-rules\": []\n    },\n    {\n      \"name\": \"Keywords\",\n      \"regex\": \"(?:^|\\\\n)\\\\s*(?:CLASS|FIELD|METHOD|ARG)\",\n      \"classes\": [\n        \"keyword\"\n      ],\n      \"sub-rules\": []\n    }\n  ]\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/syntax/jasm.css",
    "content": ".code-area .keyword {\n    -fx-fill: rgb(0, 175, 255);\n}\n\n.code-area .string {\n    -fx-fill: rgb(175, 215, 223);\n}\n\n.code-area .constant {\n\t-fx-fill: rgb(125, 215, 215);\n}\n\n.code-area .comment-line {\n\t-fx-fill: rgb(175, 175, 215);\n}\n\n.code-area .comment-multi {\n    -fx-fill: rgb(155, 155, 215);\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/syntax/jasm.json",
    "content": "{\n  \"name\": \"Java bytecode Language\",\n  \"regex\": \"\",\n  \"classes\": [],\n  \"sub-rules\": [\n    {\n      \"name\": \"SingleLineComment\",\n      \"regex\": \"//[^\\\\n]*\",\n      \"classes\": [\n        \"comment-line\"\n      ],\n      \"sub-rules\": []\n    },\n    {\n      \"name\": \"MultiLineComment\",\n      \"regex\": \"/[*](?:.|\\\\n)+?\\\\*/\",\n      \"backtrack-mark\": \"/*\",\n      \"advance-mark\": \"*/\",\n      \"classes\": [\n        \"comment-multi\"\n      ],\n      \"sub-rules\": []\n    },\n    {\n      \"name\": \"Constants\",\n      \"regex\": \"\\\\b0(?:[xX][0-9a-fA-F]+|b[01]+|[0-7]+)\\\\b|\\\\b(?:[\\\\d_]+\\\\.\\\\d+|[\\\\d_]+)(?:[eE]-?[\\\\d_]+)?[fFdDlL]?\\\\b|\\\\b(?:true|false|null|NaN|\\\\+Infinity|\\\\-Infinity)\\\\b|'[\\\\\\\\]?.'\",\n      \"classes\": [\n        \"constant\"\n      ],\n      \"sub-rules\": []\n    },\n    {\n      \"name\": \"Strings\",\n      \"regex\": \"(?:\\\\\\\"(?:[^\\\\n\\\"\\\\\\\\]|\\\\\\\\.)*?\\\\\\\")|(?:\\\\'(?:[^'\\\\n\\\\\\\\]|\\\\\\\\.)*?\\\\')\",\n      \"classes\": [\n        \"string\"\n      ],\n      \"sub-rules\": []\n    },\n    {\n      \"name\": \"Keywords\",\n      \"regex\": \"\\\\b(?:(?:outer-)?(?:method|class)|field|implements|inner|(?:(?:type-)?(?:(?:in)?visible-))?annotation|code|parameter(?:s|-annotations)|exceptions|sourcefile|signature|default-value|nest-(?:host|member)|public|private|native|abstract|interface|synthetic|strictfp|enum|super|module|synchronized|bridge|varargs|volatile|transient|static|final|protected|aconst_null|anewarray|areturn|arraylength|athrow|bipush|checkcast|d2(?:f|i|l)|dadd|dcmpg|dcmpl|dconst_(?:1|0)|ddiv|dmul|dneg|drem|dreturn|dsub|dup(?:2|_x1|_x2|2_x1|2_x2)?|f2(?:d|i|l)|fadd|fcmpg|fcmpl|fconst_(?:0|1|2)|fdiv|fmul|fneg|frem|freturn|fsub|get(?:field|static)|goto|i2(?:b|c|d|f|l|s)|iadd|iand|iconst_(?:0|1|2|3|4|5|m1)|idiv|if_(?:acmp(?:ne|eq)|icmp(?:le|eq|ne|lt|ge|gt|le))|if(?:eq|ne|lt|ge|gt|le|nonnull|null)|iinc|imul|ineg|instanceof|invoke(?:dynamic|interface|special(?:interface)?|static(?:interface)?|virtual(?:interface)?)|ior|irem|ireturn|ishl|ishr|(?:aa?|i|f|d|l)store|(?:aa?|i|f|d|l)load|(?:a|c|l|b|s|d|f|i)astore|(?:c|l|b|s|d|f|i)aload|isub|iushr|ixor|jsr|l2(?:d|f|i)|ladd|land|lcmp|lconst_(?:0|1)|ldc|ldiv|lmul|lneg|lookupswitch|lor|lrem|line|lreturn|lshl|lshr|lstore|lsub|lushr|lxor|monitor(?:enter|exit)|multianewarray|new(?:array)?|nop|pop(?:2)?|put(?:field|static)|ret(?:urn)?|sipush|swap|tableswitch|wide)\\\\b\",\n      \"classes\": [\n        \"keyword\"\n      ],\n      \"sub-rules\": []\n    }\n  ]\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/syntax/java.css",
    "content": ".code-area .keyword {\n    -fx-fill: rgb(0, 175, 255);\n}\n\n.code-area .string {\n    -fx-fill: rgb(175, 215, 223);\n}\n\n.code-area .constant {\n\t-fx-fill: rgb(125, 215, 215);\n}\n\n.code-area .annotation {\n    -fx-fill: rgb(0, 215, 215);\n}\n\n.code-area .comment-javadoc {\n\t-fx-fill: rgb(105, 135, 215);\n}\n\n.code-area .comment-javadoc-link {\n    -fx-fill: rgb(210, 220, 235);\n}\n\n.code-area .comment-javadoc-tag {\n    -fx-font-weight: bold;\n}\n\n.code-area .comment-javadoc-name {\n    -fx-fill: rgb(165, 200, 255);\n}\n\n.code-area .comment-multi {\n\t-fx-fill: rgb(155, 155, 215);\n}\n\n.code-area .comment-line {\n\t-fx-fill: rgb(175, 175, 215);\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/syntax/java.json",
    "content": "{\n  \"name\": \"Java language\",\n  \"regex\": \"\",\n  \"classes\": [],\n  \"sub-rules\": [\n    {\n      \"name\": \"JavadocComment\",\n      \"regex\": \"\\\\/\\\\*\\\\*[\\\\S\\\\s]+?\\\\*\\\\/\",\n      \"backtrack-mark\": \"/*\",\n      \"advance-mark\": \"*/\",\n      \"classes\": [\n        \"comment-javadoc\"\n      ],\n      \"sub-rules\": [\n        {\n          \"name\": \"JavadocLink\",\n          \"regex\": \"(?<=\\\\{\\\\@link)\\\\s+\\\\S+?[\\\\s\\\\S]*?(?=})\",\n          \"classes\": [\n            \"comment-javadoc-link\"\n          ],\n          \"sub-rules\": []\n        },\n        {\n          \"name\": \"JavadocTag\",\n          \"regex\": \"\\\\B(?:@[\\\\w]+)\\\\b\",\n          \"classes\": [\n            \"comment-javadoc-tag\"\n          ],\n          \"sub-rules\": []\n        },\n        {\n          \"name\": \"JavadocParamName\",\n          \"regex\": \"(?<=@param)\\\\s+\\\\b(?:\\\\w+)\\\\b\",\n          \"classes\": [\n            \"comment-javadoc-name\"\n          ],\n          \"sub-rules\": []\n        }\n      ]\n    },\n    {\n      \"name\": \"MultiLineComment\",\n      \"regex\": \"/[*](?:.|\\\\n)+?\\\\*/\",\n      \"backtrack-mark\": \"/*\",\n      \"advance-mark\": \"*/\",\n      \"classes\": [\n        \"comment-multi\"\n      ],\n      \"sub-rules\": []\n    },\n    {\n      \"name\": \"SingleLineComment\",\n      \"regex\": \"//[^\\\\n]*\",\n      \"classes\": [\n        \"comment-line\"\n      ],\n      \"sub-rules\": []\n    },\n    {\n      \"name\": \"Annotations\",\n      \"regex\": \"\\\\B(?:@[\\\\w.$]+)\\\\b\",\n      \"classes\": [\n        \"annotation\"\n      ],\n      \"sub-rules\": []\n    },\n    {\n      \"name\": \"Constants\",\n      \"regex\": \"\\\\b0(?:[xX][0-9a-fA-F]+|b[01]+|[0-7]+)[fFdDlL]?\\\\b|\\\\b(?:[\\\\d_]+\\\\.\\\\d+|[\\\\d_]+)(?:[eE]-?[\\\\d_]+)?[fFdDlL]?\\\\b|\\\\b(?:true|false|null)\\\\b|'[\\\\\\\\]?.'\",\n      \"classes\": [\n        \"constant\"\n      ],\n      \"sub-rules\": []\n    },\n    {\n      \"name\": \"MultiLineString\",\n      \"regex\": \"\\\"{3}(?:.|\\\\n)+?\\\"{3}\",\n      \"backtrack-mark\": \"\\\"\\\"\\\"\",\n      \"advance-mark\": \"\\\"\\\"\\\"\",\n      \"classes\": [\n        \"string\"\n      ],\n      \"sub-rules\": []\n    },\n    {\n      \"name\": \"Strings\",\n      \"regex\": \"(?:\\\\\\\"(?:[^\\\\n\\\"\\\\\\\\]|\\\\\\\\.)*?\\\\\\\")|(?:\\\\'(?:[^'\\\\n\\\\\\\\]|\\\\\\\\.)*?\\\\')\",\n      \"classes\": [\n        \"string\"\n      ],\n      \"sub-rules\": []\n    },\n    {\n      \"name\": \"Keywords\",\n      \"regex\": \"\\\\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|native|new|package|private|protected|public|record|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|transient|try|var|void|volatile|while|yield)\\\\b\",\n      \"classes\": [\n        \"keyword\"\n      ],\n      \"sub-rules\": []\n    }\n  ]\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/syntax/json.css",
    "content": ".code-area .string {\n    -fx-fill: rgb(175, 215, 223);\n}\n\n.code-area .constant {\n    -fx-fill: rgb(125, 215, 215);\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/syntax/json.json",
    "content": "{\n  \"name\": \"JSON language\",\n  \"regex\": \"\",\n  \"classes\": [],\n  \"sub-rules\": [\n    {\n      \"name\": \"Constants\",\n      \"regex\": \"\\\\b0(?:[xX][0-9a-fA-F]+|b[01]+|[0-7]+)\\\\b|\\\\b(?:[\\\\d_]+\\\\.\\\\d+|[\\\\d_]+)(?:[eE]-?[\\\\d_]+)?[fFdDlL]?\\\\b|\\\\b(?:true|false|null)\\\\b|'[\\\\\\\\]?.'\",\n      \"classes\": [\n        \"constant\"\n      ],\n      \"sub-rules\": []\n    },\n    {\n      \"name\": \"Strings\",\n      \"regex\": \"(?:\\\\\\\"(?:[^\\\\n\\\"\\\\\\\\]|\\\\\\\\.)*?\\\\\\\")|(?:\\\\'(?:[^'\\\\n\\\\\\\\]|\\\\\\\\.)*?\\\\')\",\n      \"classes\": [\n        \"string\"\n      ],\n      \"sub-rules\": []\n    }\n  ]\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/syntax/xml.css",
    "content": ".code-area .tag-name {\n    -fx-fill: rgb(90, 160, 220);\n}\n\n.code-area .tag-param {\n    -fx-fill: rgb(140, 190, 220);\n}\n\n.code-area .comment-multi {\n\t-fx-fill: rgb(120, 120, 120);\n}\n\n.code-area .string {\n    -fx-fill: rgb(150, 200, 105);\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/syntax/xml.json",
    "content": "{\n  \"name\": \"Extensible markup language\",\n  \"regex\": \"\",\n  \"classes\": [],\n  \"sub-rules\": [\n    {\n      \"name\": \"MultiLineComment\",\n      \"regex\": \"<!--[\\\\w\\\\W]*?-->\",\n      \"backtrack-mark\": \"<!--\",\n      \"advance-mark\": \"-->\",\n      \"classes\": [\n        \"comment-multi\"\n      ],\n      \"sub-rules\": []\n    },\n    {\n      \"name\": \"Tag\",\n      \"regex\": \"<[\\\\s\\\\S]+?>\",\n      \"classes\": [],\n      \"sub-rules\": [\n        {\n          \"name\": \"TagName\",\n          \"regex\": \"(?:<|<\\\\/)[\\\\w\\\\-?]+>?\",\n          \"classes\": [\n            \"tag-name\"\n          ],\n          \"sub-rules\": []\n        },\n        {\n          \"name\": \"TagNameEnd\",\n          \"regex\": \"\\\\B\\\\??/?>\",\n          \"classes\": [\n            \"tag-name\"\n          ],\n          \"sub-rules\": []\n        },\n        {\n          \"name\": \"TagParameter\",\n          \"regex\": \"[\\\\w-:]+(?==)\",\n          \"classes\": [\n            \"tag-param\"\n          ],\n          \"sub-rules\": []\n        },\n        {\n          \"name\": \"Strings\",\n          \"regex\": \"\\\"(?:[^\\\"\\\\\\\\]|\\\\\\\\.)*\\\"\",\n          \"classes\": [\n            \"string\"\n          ],\n          \"sub-rules\": []\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "recaf-ui/src/main/resources/translations/cs_CZ.lang",
    "content": "## Language name\nlang.name=Čeština\n\n##### General items\n## Main and context menus\nmenu.association.override=Přepsat jazyk\nmenu.association.none=Žádná přiřazení nakonfigurována\nmenu.config=Nastavení\nmenu.file=Soubor\nmenu.file.addtoworkspace=Přidat do pracovního prostoru\nmenu.file.openworkspace=Otevřít pracovní prostor\nmenu.file.exportapp=Exportovat aplikaci\nmenu.file.exportworkspace=Exportovat konfiguraci pracovního prostoru\nmenu.file.modifications=Zobrazit úpravy\nmenu.file.recent=Nedávné\nmenu.file.close=Zavřít\nmenu.file.quit=Ukončit\nmenu.goto.class=Jít na třídu\nmenu.goto.field=Jít na pole\nmenu.goto.method=Jít na metodu\nmenu.goto.instruction=Jít na instrukci\nmenu.goto.file=Jít na soubor\nmenu.goto.label=Jít na značku\nmenu.edit.assemble.class=Upravit třídu\nmenu.edit.assemble.field=Upravit pole\nmenu.edit.assemble.method=Upravit metodu\nmenu.edit.remove=Odstranit\nmenu.edit.copy=Kopírovat\nmenu.edit.delete=Vymazat\nmenu.help=Nápověda\nmenu.help.discord=Discord\nmenu.help.docs=Online dokumentace\nmenu.help.github=GitHub\nmenu.help.issues=Sledování chyb\nmenu.help.sysinfo=Informace o systému\nmenu.refactor=Refaktorovat\nmenu.refactor.move=Přesunout\nmenu.refactor.rename=Přejmenovat\nmenu.search=Hledat\nmenu.search.string=Stringy\nmenu.search.number=Čísla\nmenu.search.references=Reference\nmenu.search.declarations=Deklarace\nmenu.search.noresults=Žádné výsledky\nmenu.log.clear=Vymazat\nmenu.log.export=Exportovat\nmenu.mappings=Mapování\nmenu.mappings.apply=Aplikovat\nmenu.mappings.export=Exportovat\nmenu.mappings.generate=Vygenerovat\nmenu.scripting=Scriptování\nmenu.scripting.none-found=Nebyly nalezeny žádné scripty\nmenu.scripting.manage=Spravovat scripty\nmenu.scripting.new=Nový script\nmenu.scripting.edit=Upravit\nmenu.scripting.browse=Procházet scripty\nmenu.scripting.refresh=Obnovit scripty\nmenu.scripting.save=Uložit script\nmenu.scripting.execute=Spustit\nmenu.scripting.editor=Editor scriptů\nmenu.scripting.author=Autor\nmenu.scripting.version=Verze\nmenu.view=Zobrazit\nmenu.view.hierarchy=Hierarchie tříd\nmenu.view.hierarchy.children=Děti\nmenu.view.hierarchy.parents=Rodiče\nmenu.view.methodcfg=Graf řídícího toku\nmenu.tab.close=Zavřít\nmenu.tab.closeothers=Zavřít ostatní\nmenu.tab.closeall=Zavřít vše\nmenu.tab.copypath=Kopírovat cestu\nmenu.image.resetscale=Resetovat zoom\nmenu.image.center=Vycentrovat obrázek\nmenu.hex.copyas=Kopírovat jako...\nmenu.mode=Změnit zobrazení\nmenu.mode.class.auto=Automaticky\nmenu.mode.class.decompile=Dekompilovat\nmenu.mode.file.auto=Automaticky\nmenu.mode.file.text=Text\nmenu.mode.file.hex=Hex\nmenu.mode.diff.decompile=Dekompilovat\nmenu.mode.diff.disassemble=Rozebrat\nmenu.vm=Virtualizovat\nmenu.vm.optimize=Optimalizovat\nmenu.vm.run=Spustit\nmenu.plugin=Pluginy\nmenu.plugin.manage=Spravovat pluginy\nmenu.plugin.installed=Nainstalované\nmenu.plugin.remote=Vzdálené\nmenu.plugin.browse=Prohlížet pluginy\nmenu.plugin.enabled=Aktivovaný\nmenu.plugin.uninstall=Odinstalovat\nmenu.plugin.uninstall.warning=Opravdu chcete tento plugin smazat?\n\n##### Dialog texts\ndialog.cancel=Zrušit\ndialog.close=Zavřít\ndialog.confirm=Potvrdit\ndialog.finish=Dokončit\ndialog.next=Další\ndialog.previous=Předchozí\ndialog.dismiss=Zavřít\ndialog.configure=Nakonfigurovat\ndialog.warning=Upozornění\ndialog.restart=Chcete-li tuto možnost konfigurace změnit, doporučujeme Recaf restartovat.\\nJste si jisti, že ji chcete použít?\ndialog.unknownextension=Neznámá přípona souboru. Chcete nakonfigurovat přiřazení k jazyku?\n\n## File chooser\ndialog.file.open=Otevřít\ndialog.file.export=Exportovat\ndialog.filefilter.any=Jakýkoliv typ\ndialog.filefilter.mapping=Mapování\ndialog.filefilter.input=Aplikace\ndialog.filefilter.workspace=Pracovní prostory\n\n## File drop items\ndialog.title.create-workspace=Nový pracovní prostor\ndialog.title.update-workspace=Zpracovat vstup pracovního prostoru\ndialog.title.close-workspace=Zavřít pracovní prostor?\ndialog.option.create-workspace=Nový pracovní prostor\ndialog.option.update-workspace=Přidat do pracovního prostoru\n\n## Copy class/file\ndialog.title.copy-class=Kopírovat třídu\ndialog.title.copy-directory=Kopírovat složku\ndialog.title.copy-field=Kopírovat pole\ndialog.title.copy-file=Kopírovat soubor\ndialog.title.copy-method=Kopírovat metodu\ndialog.header.copy-class=Zadejte nový název zkopírované třídy.\ndialog.header.copy-directory=Zadejte nový název zkopírované složky.\ndialog.header.copy-field=Zadejte nový název zkopírovaného pole.\ndialog.header.copy-file=Zadejte nový název zkopírovaného souboru.\ndialog.header.copy-method=Zadejte nový název zkopírované metody.\n\n## Delete class/file\ndialog.title.delete-class=Smazat třídu\ndialog.title.delete-directory=Smazat složku\ndialog.title.delete-field=Smazat pole\ndialog.title.delete-file=Smazat soubor\ndialog.title.delete-method=Smazat metodu\ndialog.title.delete-package=Smazat package\ndialog.title.delete-resource=Smazat resource\ndialog.header.delete-class=Určitě chcete odstranit %s?\ndialog.header.delete-directory=Určitě chcete odstranit: %s?\ndialog.header.delete-field=Určitě chcete odstranit %s?\ndialog.header.delete-file=Určitě chcete odstranit %s?\ndialog.header.delete-method=Určitě chcete odstranit %s?\ndialog.header.delete-package=Určitě chcete odstranit %s?\ndialog.header.delete-resource=Určitě chcete odstranit %s?\n\n## Rename class/file\ndialog.title.rename-class=Přejmenovat třídu\ndialog.title.rename-class-warning=Upozornění\ndialog.title.rename-directory=Přejmenovat složku\ndialog.title.rename-field=Přejmenovat pole\ndialog.title.rename-file=Přejmenovat soubor\ndialog.title.rename-file-warning=Upozornění\ndialog.title.rename-method=Přejmenovat metodu\ndialog.title.rename-package=Přejmenovat package\ndialog.header.rename-class=Zadejte nový název třídy. To může mít vliv na více souborů.\ndialog.header.rename-class-warning=Vámi zvolený název třídy je již používán. Opravdu chcete třídu přejmenovat?\ndialog.header.rename-directory=Zadejte nový název složky.\ndialog.header.rename-field=Zadejte nový název pole.\ndialog.header.rename-file=Zadejte nový název souboru.\ndialog.header.rename-file-warning=Soubor již existuje. Chcete jej přepsat?\ndialog.header.rename-method=Zadejte nový název metody.\ndialog.header.rename-package=Zadejte nový název package.\n\n## Move class/file\ndialog.title.move-class=Přesunout třídu\ndialog.title.move-directory=Přesunout složku\ndialog.title.move-file=Přesunout soubor\ndialog.title.move-package=Přesunout package\ndialog.header.move-class=Přesun třídy do nového package.\ndialog.header.move-directory=Přesun složky do nové nadřazené složky.\ndialog.header.move-file=Přesun souboru do nové složky.\ndialog.header.move-package=Přesun package do nového nadřazeného package.\n\n## VM actions\ndialog.title.vm-invoke-args=Virtualizovat volání metody\ndialog.title.vm-peephole-invoke-args=Virtualizovaná peephole optimalizace\ndialog.vm.execute=Spustit\ndialog.vm.optimize=Optimalizovat\ndialog.vm.create-dummy=Použít atrapu\ndialog.vm.create-null=Použít null\n\n## Hex dialogs\ndialog.hex.title.insertcount=Vložit\ndialog.hex.header.insertcount=Kolik bajtů se má vložit?\n\n# Base Converter dialog\ndialog.conv.title.literal=Doslovné čislo (literal)\ndialog.conv.title.expression=Vyjádření čísla (expression)\n\n## Quick nav\ndialog.quicknav=Rychlá navigace\n\n## Wizard\nwizard.chooseaction=Vyberte akci\nwizard.selectprimary=Vyberte hlavní resource\nwizard.currentworkspace=Aktuální pracovní prostor\n\n\n##### Panels\n## Welcome\nwelcome.title=Vítejte\n\n## Workspace\nworkspace.title=Pracovní prostor\n\n## Changes view\nmodifications.none=Žádná historie úprav položky\nmodifications.title=Úpravy\n\n## Java area\njava.decompiling=Dekompiluji třídu...\njava.unparsable=JavaParser nedokázal interpretovat zdroj, kontextové akce jsou dostupné pouze na kartě 'Pole a metody'\n\n## Outline\nfieldsandmethods.title=Pole a metody\n\n## Hierarchy\nhierarchy.title=Rodokmen\nhierarchy.children=Děti\nhierarchy.parents=Rodiče\n\n## Logging\nlogging.title=Záznamy\n\n## Assembler\nassembler.title=Assembler\nassembler.analysis.title=Proměnné a stack\nassembler.analysis.stack=Stack\nassembler.analysis.name-column=Jméno\nassembler.analysis.value-column=Hodnota\nassembler.playground.title=Java do bytecodu\nassembler.playground.comment=Zde můžete psát běžnou Javu a nechat si ji přeložit do bytecodu.\nassembler.variables.title=Proměnné\nassembler.variables.empty=<Vyžaduje alespoň jednu kompilaci>\n\n## Search\nsearch.run=Hledat\nsearch.results=Výsledky\nsearch.text=Textový obsah\nsearch.textmode=Režim textové shody\nsearch.number=Číselná hodnota\nsearch.numbermode=Režim číselné shody\nsearch.refowner=Vlastník člena\nsearch.refname=Jméno člena\nsearch.refdesc=Typový descriptor člena\n\n## Help\nhelp.system=Systém\nhelp.system.sub=Informace o operačním systému\nhelp.java=Java\nhelp.java.sub=Informace o JVM\nhelp.javafx=JavaFX\nhelp.javafx.sub=Informace o JavaFX UI\nhelp.recaf=Recaf\nhelp.recaf.sub=Informace o Recafu\nhelp.copy=Kopírovat informace do schránky\nhelp.opendir=Otevřít složku Recafu\n\n## Mapping generator\nmapgen.genimpl=Konvence pojmenování\nmapgen.filters=Filtry\nmapgen.filters.add=Přidat filtr\nmapgen.filters.delete=Smazat vybrané\nmapgen.newfilter.excludealreadymapped=Vynechat již zmapované\nmapgen.newfilter.modifierexclude=Vynechat modifiery\nmapgen.newfilter.modifierexcludeclass=Vynechat modifiery na třídách\nmapgen.newfilter.modifierexcludefield=Vynechat modifiery na polích\nmapgen.newfilter.modifierexcludemethod=Vynechat modifiery na metodách\nmapgen.newfilter.modifierinclude=Zahrnout modifiery\nmapgen.newfilter.modifierincludeclass=Zahrnout modifiery na třídách\nmapgen.newfilter.modifierincludefield=Zahrnout modifiery na polích\nmapgen.newfilter.modifierincludemethod=Zahrnout modifiery na metodách\nmapgen.newfilter.pathexclude=Blacklist cest\nmapgen.newfilter.pathinclude=Whitelist cest\nmapgen.title.newfilter=Nový filtr\nmapgen.header.newfilter=Obsah vstupního filtru\nmapgen.copy=Kopírovat do schránky\nmapgen.apply=Použít\n\n##### Tree\ntree.classes=Třídy\ntree.files=Soubory\ntree.prompt=Sem přetáhněte své soubory\ntree.hidelibs=Skrýt knihovny\n\n##### Config\nconf.display=Vzhled\nconf.display.general=Obecné\nconf.display.general.flashopentabs=Bliknout otevřenou kartu při opětovném zaostření\nconf.display.general.language=Jazyk\nconf.display.tree=Zobrazení stromu\nconf.display.tree.maxtreedirectorydepth=Maximální hloubka stromu složek\nconf.display.tree.maxtreetextlength=Maximální délka textu položky\nconf.display.workspace=Chování pracovního prostoru\nconf.display.workspace.showfilterbuttons=Zobrazit tlačítka pokročilého filtru\nconf.display.workspace.onfiledrop=Akce při přetažení souboru\nconf.display.workspace.onfiledrop.choose=Nech mě vybrat\nconf.display.workspace.onfiledrop.createnew=Vytvořit nový pracovní prostor\nconf.display.workspace.onfiledrop.addlibrary=Přidat jako knihovnu\nconf.display.workspace.promptcloseworkspace=Výzva při zavírání pracovního prostoru\nconf.display.workspace.promptdeleteitem=Výzva při mazání položek\nconf.display.workspace.showselectionnavbar=Zobrazit navigační panel výběru\n\nconf.editor=Komponenty editoru\nconf.editor.general=Obecné\nconf.editor.general.classmode=Výchozí zobrazení třídy\nconf.editor.general.filemode=Výchozí zobrazení souboru\nconf.editor.general.errorindicatorpos=Poloha chybového indikátoru\nconf.editor.assoc=Přidružení přípon souborů\nconf.editor.assoc.fileextassociations=Přidružení\nfieldsandmethods.title=Osnova třídy\nfieldsandmethods.showoutlinedsynths=Zobrazit syntetické (kompilátorem generované) členy\nconf.editor.text=Textový editor\nconf.editor.text.showbracketfolds=Zobrazit záhyby závorek\nconf.editor.hex=Hex editor\nconf.editor.hex.classhints=Zobrazit nápovědy k souborům tříd\nconf.editor.hex.hexcolumns=Počet sloupců\nconf.editor.hex.highlightcurrent=Zvýraznit aktuální výběr\nconf.editor.diff=Editor rozdílů\nconf.editor.diff.diff-view-mode=Režim zobrazení rozdílů tříd\n\nconf.binding=Klávesové zkratky\nconf.binding.navigation=Navigace\nconf.binding.navigation.closetab=Zavřít kartu\nconf.binding.navigation.find=Hledat\nconf.binding.navigation.fullscreen=Celá obrazovka\nconf.binding.navigation.quicknav=Rychlá navigace\nconf.binding.edit=Editace\nconf.binding.edit.save=Uložit změny\nconf.binding.edit.undo=Vrátit změnu zpět\nconf.binding.inputprompt.initial=<čekám>\nconf.binding.inputprompt.finish=<ENTER pro ukončení>\nconf.binding.code=Kódové interakce\nconf.binding.code.gotodef=Jít na definici\nconf.binding.code.rename=Přejmenovat vybrané\nconf.binding.code.searchref=Hledat reference\n\nconf.assembler=Assembler\nconf.assembler.build.delay=Zpoždění před aktualizací bytecodu\nconf.assembler.format=Formát\nconf.assembler.format.prefix=Použití tečkové předpony pro klíčová slova\nconf.assembler.parse=Parsování\nconf.assembler.parse.recover=Pokusit se o obnovení chyby\nconf.assembler.validation=Ověření\nconf.assembler.validation.ast=Povolit ověřování abstraktního stromu syntaxe\nconf.assembler.validation.bytecode=Povolit analýzu bytecodu\nconf.assembler.debug=Ladění\nconf.assembler.debug.ast-debug=Zobrazit ladicí informace AST\n\nconf.compiler=Kompilace\nconf.compiler.general=Obecné\nconf.compiler.general.impl=Implementace kompilátoru\nconf.compiler.general.phantoms=Vygenerovat chybějící třídy\nconf.compiler.debug=Ladění\nconf.compiler.debug.vars=Zahrnout ladicí symboly\nconf.compiler.debug.lines=Zahrnout čísla řádků\nconf.compiler.debug.sourcefile=Zahrnout zdrojový soubor\n\nconf.decompiler=Dekompilace\nconf.decompiler.general=Obecné\nconf.decompiler.general.impl=Implementace dekompilátoru\nconf.decompiler.general.enabletimeout=Povolit časový limit dekompilátoru\nconf.decompiler.general.timeout=Časový limit v milisekundách\nconf.decompiler.filter=Filtrování\nconf.decompiler.filter.escapeunicode=Escapovat Unicode\nconf.decompiler.filter.generics=Odstranit typové argumenty\nconf.decompiler.filter.vars=Odstranit informace o proměnných\nconf.decompiler.filter.synthetics=Odstranit syntetické členy\n\nconf.dialog=Dialogy\nconf.dialog.fileprompt=Souborové výzvy\nconf.dialog.fileprompt.load=Umístění nedávných načtení\nconf.dialog.fileprompt.export=Umístění nedávných exportů\nconf.dialog.fileprompt.loadmap=Umístění nedávných vstupních mapování\nconf.dialog.fileprompt.exportmap=Umístění nedávných výstupních mapování\n\nconf.export=Exportování\nconf.export.general=Obecné\nconf.export.general.shadeLibs=Přidat knihovny do výstupu\nconf.export.general.compress=Používat kompresi v archivech\n\nconf.plugin=Pluginy\nconf.plugin.general=Obecné\nconf.plugin.general.enabled=Povoleno\nconf.plugin.general.remote=Vzdálené\nconf.plugin.general.remotetime=Zakešovat časové razítko\n\nconf.recent=Nedávné\nconf.recent.max=Maximální počet sledovaných položek\nconf.recent.workspaces=Nedávné pracovní prostory\n\nconf.ssvm=SSVM\nconf.ssvm.access=Přístup k souborovému IO\nconf.ssvm.access.read=Povolit čtení\nconf.ssvm.access.write=Povolit psaní\nconf.ssvm.access.read.warn=Povolení této možnosti umožní akcím SSVM číst soubory v systému.\\nPři povolování této možnosti buďte opatrní.\nconf.ssvm.access.write.warn=Povolení této možnosti umožní akcím SSVM zapisovat soubory do systému.\\nPři povolování této možnosti buďte VELMI opatrní.\nconf.ssvm.general=Obecné\nconf.ssvm.general.active=Inicializovat SSVM při načítání pracovních prostorů\nconf.ssvm.remote=Vzdálená VM\nconf.ssvm.remote.active=Aktivní\nconf.ssvm.remote.path=Cesta ke spustitelnému souboru VM\nconf.undefined=Neurčeno\n"
  },
  {
    "path": "recaf-ui/src/main/resources/translations/de_DE.lang",
    "content": "## Language name\nlang.name=Deutsch\n\n##### General items\n## Main and context menus\nmenu.association.override=Sprache überschreiben\nmenu.association.none=Keine Assoziation\nmenu.config=Konfiguration\nmenu.file=Datei\nmenu.file.attach=An Remote anschließen\nmenu.file.addtoworkspace=Zur Arbeitsumgebung hinzufügen\nmenu.file.openworkspace=Arbeitsumgebung öffnen\nmenu.file.exportapp=Applikation Exportieren\nmenu.file.exportworkspace=Arbeitsumgebung exportieren\nmenu.file.modifications=Änderungen anzeigen\nmenu.file.recent=Zuletzt geöffnete Arbeitsumgebungen\nmenu.file.close=Schließen\nmenu.file.quit=Beenden\nmenu.goto.class=Zu Klasse springen\nmenu.goto.field=Zu Feld springen\nmenu.goto.method=Zu Methode springen\nmenu.goto.instruction=Zu Befehl springen\nmenu.goto.file=Zu Datei springen\nmenu.goto.label=Zu Label springen\nmenu.edit.assemble.class=Klasse bearbeiten\nmenu.edit.assemble.field=Feld bearbeiten\nmenu.edit.assemble.method=Methode bearbeiten\nmenu.edit.remove=Entfernen\nmenu.edit.copy=Kopieren\nmenu.edit.delete=Löschen\nmenu.help=Hilfe\nmenu.help.discord=Discord\nmenu.help.docs=Online-Dokumentation\nmenu.help.github=Github\nmenu.help.issues=Problem melden\nmenu.help.sysinfo=Systeminformationen\nmenu.refactor=Refaktorieren\nmenu.refactor.move=Verschieben\nmenu.refactor.rename=Umbenennen\nmenu.search=Suchen\nmenu.search.string=String\nmenu.search.number=Zahl\nmenu.search.references=Referenzen\nmenu.search.declarations=Deklarationen\nmenu.search.noresults=Keine Ergebnisse\nmenu.log.clear=Log löschen\nmenu.log.export=Log exportieren\nmenu.mappings=Mappings\nmenu.mappings.apply=Anwenden\nmenu.mappings.export=Exportieren\nmenu.mappings.generate=Generieren\nmenu.mappings.view=Aktuelle Mappings\nmenu.scripting=Skripting\nmenu.scripting.list=Skripte\nmenu.scripting.none-found=Keine Skripts\nmenu.scripting.manage=Skripts verwalten\nmenu.scripting.new=Neues Skript\nmenu.scripting.edit=Skript bearbeiten\nmenu.scripting.browse=Skripts durchsuchen\nmenu.scripting.refresh=Skripts aktualisieren\nmenu.scripting.save=Skript speichern\nmenu.scripting.execute=Skript ausführen\nmenu.scripting.editor=Skripteditor\nmenu.scripting.author=Autor/-in\nmenu.scripting.version=Version\nmenu.view=Ansicht\nmenu.view.hierarchy=Klassenhierarchie\nmenu.view.hierarchy.children=Klassen und Unterklassen\nmenu.view.hierarchy.parents=Übergeordnete Klassen\nmenu.view.methodcfg=Kontrollflussdiagramm\nmenu.tab.close=Schließen\nmenu.tab.closeothers=Andere schließen\nmenu.tab.closeall=Alle schließen\nmenu.tab.copypath=Pfad kopieren\nmenu.image.resetscale=Skalierung zurücksetzen\nmenu.image.center=Zentrieren\nmenu.hex.copyas=Kopieren als...\nmenu.mode=Modus\nmenu.mode.class.auto=Automatisch\nmenu.mode.class.decompile=Dekompilieren\nmenu.mode.file.auto=Automatisch\nmenu.mode.file.text=Text\nmenu.mode.file.hex=Hexadezimal\nmenu.mode.diff.decompile=Dekompilieren\nmenu.mode.diff.disassemble=Disassemblen\nmenu.vm=Virtualisieren\nmenu.vm.optimize=Optimieren\nmenu.vm.run=Starten\nmenu.plugin=Plugins\nmenu.plugin.manage=Plugins verwalten\nmenu.plugin.installed=Installiert\nmenu.plugin.remote=Remote\nmenu.plugin.browse=Plugins durchsuchen\nmenu.plugin.enabled=Aktiviert\nmenu.plugin.uninstall=Deinstallieren\nmenu.plugin.uninstall.warning=Wollen Sie dieses Plugin wirklich deinstallieren?\n\n##### Dialog texts\ndialog.cancel=Abbrechen\ndialog.close=Schließen\ndialog.confirm=Bestätigen\ndialog.finish=Fertig\ndialog.next=Weiter\ndialog.previous=Zurück\ndialog.dismiss=Verwerfen\ndialog.configure=Konfigurieren\ndialog.warning=Warnung\ndialog.restart=Um die Änderungen zu übernehmen, muss die Anwendung neu gestartet werden.\\nMöchten Sie die Anwendung neu starten?\ndialog.unknownextension=Unbekannte Dateiendung\n\n## File chooser\ndialog.file.open=Öffnen\ndialog.file.export=Exportieren\ndialog.filefilter.any=Alle Dateien\ndialog.filefilter.mapping=Mapping-Dateien\ndialog.filefilter.input=Applikationsdateien\ndialog.filefilter.workspace=Arbeitsumgebungen\n\n## File drop items\ndialog.title.create-workspace=Arbeitsumgebung erstellen\ndialog.title.update-workspace=Arbeitsumgebung aktualisieren\ndialog.title.close-workspace=Arbeitsumgebung schließen?\ndialog.option.create-workspace=Neue Arbeitsumgebung erstellen\ndialog.option.update-workspace=Zur aktuellen Arbeitsumgebung hinzufügen\n\n## Copy class/file\ndialog.title.copy-class=Klasse kopieren\ndialog.title.copy-directory=Verzeichnis kopieren\ndialog.title.copy-field=Feld kopieren\ndialog.title.copy-file=Datei kopieren\ndialog.title.copy-method=Methode kopieren\ndialog.header.copy-class=Geben Sie einen neuen Klassennamen ein\ndialog.header.copy-directory=Geben Sie einen neuen Verzeichnisnamen ein\ndialog.header.copy-field=Geben Sie einen neuen Feldnamen ein\ndialog.header.copy-file=Geben Sie einen neuen Dateinamen ein\ndialog.header.copy-method=Geben Sie einen neuen Methodennamen ein\n\n## Delete class/file\ndialog.title.delete-class=Klasse löschen\ndialog.title.delete-directory=Verzeichnis löschen\ndialog.title.delete-field=Feld löschen\ndialog.title.delete-file=Datei löschen\ndialog.title.delete-method=Methode löschen\ndialog.title.delete-package=Package löschen\ndialog.title.delete-resource=Ressource löschen\ndialog.header.delete-class=Sind Sie sicher, dass Sie %s löschen wollen?\ndialog.header.delete-directory=Sind Sie sicher, dass Sie %s löschen wollen?\ndialog.header.delete-field=Sind Sie sicher, dass Sie %s löschen wollen?\ndialog.header.delete-file=Sind Sie sicher, dass Sie %s löschen wollen?\ndialog.header.delete-method=Sind Sie sicher, dass Sie %s löschen wollen?\ndialog.header.delete-package=Sind Sie sicher, dass Sie %s löschen wollen?\ndialog.header.delete-resource=Sind Sie sicher, dass Sie %s löschen wollen?\n\n## Rename class/file\ndialog.title.rename-class=Klasse umbenennen\ndialog.title.rename-class-warning=Warnung\ndialog.title.rename-directory=Verzeichnis umbenennen\ndialog.title.rename-field=Feld umbenennen\ndialog.title.rename-file=Datei umbenennen\ndialog.title.rename-file-warning=Warnung\ndialog.title.rename-method=Methode umbenennen\ndialog.title.rename-package=Package umbenennen\ndialog.header.rename-class=Geben Sie einen neuen Klassennamen ein. Dies kann mehrere Dateien ändern.\ndialog.header.rename-class-warning=Der Name, den Sie gewählt haben, ist bereits vergeben. Sind Sie sicher, dass Sie die Klasse umbenennen wollen?\ndialog.header.rename-directory=Geben Sie einen neuen Verzeichnisnamen ein\ndialog.header.rename-field=Geben Sie einen neuen Feldnamen ein\ndialog.header.rename-file=Geben Sie einen neuen Dateinamen ein\ndialog.header.rename-file-warning=Der Name, den Sie gewählt haben, ist bereits vergeben. Sind Sie sicher, dass Sie die Datei umbenennen wollen?\ndialog.header.rename-method=Geben Sie einen neuen Methodennamen ein\ndialog.header.rename-package=Geben Sie einen neuen Packagenamen ein\n\n## Move class/file\ndialog.title.move-class=Klasse verschieben\ndialog.title.move-directory=Verzeichnis verschieben\ndialog.title.move-file=Datei verschieben\ndialog.title.move-package=Package verschieben\ndialog.header.move-class=Klasse zu einem anderen Package verschieben\ndialog.header.move-directory=Verzeichnis zu einem anderen Verzeichnis verschieben\ndialog.header.move-file=Datei zu einem anderen Verzeichnis verschieben\ndialog.header.move-package=Package zu einem anderen Package verschieben\n\n## VM actions\ndialog.title.vm-invoke-args=Methodenaufruf virtualisieren\ndialog.title.vm-peephole-invoke-args=Virtualisierte Peephole-Optimierung\ndialog.vm.execute=Ausführen\ndialog.vm.optimize=Optimieren\ndialog.vm.create-dummy=Dummy benutzen\ndialog.vm.create-null=Null benutzen\n\n## Hex dialogs\ndialog.hex.title.insertcount=Einfügen\ndialog.hex.header.insertcount=Wie viele Bytes sollen eingefügt werden?\n\n# Base Converter dialog\ndialog.conv.title.literal=Zahlenliteral\ndialog.conv.title.expression=Zahlenausdruck\n\n## Quick nav\ndialog.quicknav=Schnellnavigation\n\n## Wizard\nwizard.chooseaction=Wählen Sie eine Aktion\nwizard.selectprimary=Wählen Sie eine Primärressource\nwizard.currentworkspace=Aktuelle Arbeitsumgebung\n\n\n##### Panels\n## Welcome\nwelcome.title=Willkommen\n\n## Workspace\nworkspace.title=Arbeitsumgebung\n\n## Changes view\nmodifications.none=Kein Änderungsverlauf für Objekt\nmodifications.title=Änderungen\n\n## Java area\njava.decompiling=Klasse wird dekompiliert...\njava.unparsable=JavaParser konnte die Quelle nicht interpretieren, Kontextaktionen sind nur auf dem \"Felder und Methoden\"-Tab verfügbar\n\n## Outline\nfieldsandmethods.title=Felder und Methoden\n\n## Hierarchy\nhierarchy.title=Hieararchie\nhierarchy.children=Subelemente\nhierarchy.parents=Übergeordnete Elemente\n\n## Logging\nlogging.title=Logging\n\n## Assembler\nassembler.title=Assembler\nassembler.analysis.title=Variablen & Stack\nassembler.analysis.stack=Stack\nassembler.analysis.name-column=Variable name\nassembler.analysis.value-column=Wert\nassembler.playground.title=Java zu Bytecode\nassembler.playground.comment=Sie können hier Java-Code schreiben, um es zu Bytecode zu konvertieren.\nassembler.variables.title=Variablen\nassembler.variables.empty=<Muss mindestens einmal kompiliert werden>\nassembler.suggestions.none=Keine Vorschläge\n\n## Search\nsearch.run=Suchen\nsearch.results=Suchergebnisse\nsearch.text=Textsuchbegriff\nsearch.textmode=Textsuchmodus\nsearch.number=Nummersuchbegriff\nsearch.numbermode=Nummersuchmodus\nsearch.refowner=Referenz-Besitzer\nsearch.refname=Referenz-Name\nsearch.refdesc=Referenz-Descriptor\n\n## Help\nhelp.system=System\nhelp.system.sub=Informationen über das System\nhelp.java=Java\nhelp.java.sub=Informationen über Java\nhelp.javafx=JavaFX\nhelp.javafx.sub=Information über das JavaFX UI\nhelp.recaf=Recaf\nhelp.recaf.sub=Informationen über Recaf\nhelp.copy=In die Zwischenablage kopieren\nhelp.opendir=Recaf-Verzeichnis öffnen\n\n## Mapping generator\nmapgen.genimpl=Namenskonvention\nmapgen.filters=Filter\nmapgen.filters.add=Filter hinzufügen\nmapgen.filters.delete=Ausgewählte löschen\nmapgen.newfilter.excludealreadymapped=Bereits gemappte ausschließen\nmapgen.newfilter.modifierexclude=Modifier ausschließen\nmapgen.newfilter.modifierexcludeclass=Klassen-Modifier ausschließen\nmapgen.newfilter.modifierexcludefield=Feld-Modifier ausschließen\nmapgen.newfilter.modifierexcludemethod=Methoden-Modifier ausschließen\nmapgen.newfilter.modifierinclude=Modifier einschließen\nmapgen.newfilter.modifierincludeclass=Klassen-Modifier einschließen\nmapgen.newfilter.modifierincludefield=Feld-Modifier einschließen\nmapgen.newfilter.modifierincludemethod=Methoden-Modifier einschließen\nmapgen.newfilter.includewhitespacenames=Namen mit ungültigem Lehrzeichen einschließen\nmapgen.newfilter.includenonasciinames=Namen mit Nicht-ASCII-Zeichen einschließen\nmapgen.newfilter.pathexclude=Pfad-Blacklist\nmapgen.newfilter.pathinclude=Pfad-Whitelist\nmapgen.title.newfilter=Neuer Filter\nmapgen.header.newfilter=Filter-Inhalt eingeben\nmapgen.copy=In die Zwischenablage kopieren\nmapgen.apply=Anwenden\n\n##### Tree\ntree.classes=Klassen\ntree.files=Dateien\ntree.prompt=Dateien hier ablegen\ntree.hidelibs=Bibliotheken ausblenden\n\n##### Config\nconf.display=Darstellung\nconf.display.general=Allgemeines\nconf.display.general.flashopentabs=Aktiviere das Blinken von neuen Tabs\nconf.display.general.language=Sprache\nconf.display.tree=Tree-Darstellungs-Einstellungen\nconf.display.tree.maxtreedirectorydepth=Maximale Anzahl der Ebenen im Tree\nconf.display.tree.maxtreetextlength=Maximale Textlänge im Tree\nconf.display.workspace=Arbeitsumgebungs-Einstellungen\nconf.display.workspace.showfilterbuttons=Zeige Filter-Buttons\nconf.display.workspace.onfiledrop=Wähle Arbeitsumgebung bei Datei-Drop\nconf.display.workspace.onfiledrop.choose=Lass mich wählen\nconf.display.workspace.onfiledrop.createnew=Erstelle eine neue Arbeitsumgebung\nconf.display.workspace.onfiledrop.addlibrary=Füge Datei als Bibliothek hinzu\nconf.display.workspace.promptcloseworkspace=Aufforderung beim Schließen der Arbeitsumgebung\nconf.display.workspace.promptdeleteitem=Aufforderung beim Löschen eines Elements\nconf.display.workspace.showselectionnavbar=Zeige Navigationsleiste\nconf.display.text=Text\nconf.display.text.fontsize=Schriftgröße\n\nconf.editor=Editor-Komponenten\nconf.editor.general=Allgemein\nconf.editor.general.classmode=Klassenmodus\nconf.editor.general.filemode=Dateimodus\nconf.editor.general.errorindicatorpos=Fehlerindikator-Position\nconf.editor.assoc=Dateiendungs-Assoziationen\nconf.editor.assoc.fileextassociations=Dateiendungs-Assoziationen\nfieldsandmethods.title=Klassen-Outline\nfieldsandmethods.showoutlinedsynths=Synthetische (vom Compiler generierte) Typen anzeigen\nfieldsandmethods.showoutlinedvisibility=Nach Member-Sichtbarkeit filtern\nfieldsandmethods.showoutlinedmembertype=Nach Member-Typ filtern\nfieldsandmethods.sortalphabetically=Alphabetisch sortieren\nfieldsandmethods.sortbyvisibility=Nach Sichbarkeit sortieren\nfieldsandmethods.filter.prompt=Filter: Feld-/Methodename...\nconf.editor.text=Texteditor\nconf.editor.text.showbracketfolds=Zeige Einzugsfächer für Klammern\nconf.editor.hex=Hex-Editor\nconf.editor.hex.classhints=Klassen-Hints anzeigen\nconf.editor.hex.hexcolumns=Anzahl der Hex-Spalten\nconf.editor.hex.highlightcurrent=Aktuellen Byte hervorheben\nconf.editor.diff=Differenz-Editor\nconf.editor.diff.diff-view-mode=Klassendifferenz-Ansichtsmodus\n\nconf.binding=Tastenbelegung\nconf.binding.navigation=Navigation\nconf.binding.navigation.closetab=Tab schließen\nconf.binding.navigation.find=Finden\nconf.binding.navigation.fullscreen=Vollbild\nconf.binding.navigation.quicknav=Schnellnavigation\nconf.binding.edit=Bearbeiten\nconf.binding.edit.save=Speichern\nconf.binding.edit.undo=Rückgängig\nconf.binding.inputprompt.initial=<Taste>\nconf.binding.inputprompt.finish=<ENTER zum bestätigen>\nconf.binding.code=Code-Interaktionen\nconf.binding.code.gotodef=Zu Definition springen\nconf.binding.code.rename=Ausgewähltes umbenennen\nconf.binding.code.searchref=Referenzen suchen\nconf.binding.code.suggest=Vorschläge anzeigen\nconf.binding.appearance=Erscheinungsbild\nconf.binding.appearance.fontsize.up=Schrift vergrößern\nconf.binding.appearance.fontsize.down=Schrift verkleinern\n\nconf.assembler=Assembler\nconf.assembler.build=Build\nconf.assembler.build.delay=Verzögerung vor dem Aktualisieren des Bytecodes\nconf.assembler.format=Format\nconf.assembler.format.prefix=Punkt-Präfix für Schlüssenwörter benutzen\nconf.assembler.parse=Parse\nconf.assembler.parse.recover=Fehlerbehebung versuchen\nconf.assembler.validation=Validierung\nconf.assembler.validation.ast=AST-Validierung aktivieren\nconf.assembler.validation.bytecode=Bytecode-Analyse aktivieren\nconf.assembler.debug=Debugging\nconf.assembler.debug.ast-debug=AST-Debuginformationen anzeigen\n\nconf.compiler=Compiler\nconf.compiler.general=Allgemein\nconf.compiler.general.impl=Implementierung\nconf.compiler.general.phantoms=Generiere Fehlende Phantoms\nconf.compiler.debug=Debug\nconf.compiler.debug.vars=Debugging-Symbole einschließen\nconf.compiler.debug.lines=Zeilennummern einschließen\nconf.compiler.debug.sourcefile=Quelldatei einschließen\n\nconf.decompiler=Decompiler\nconf.decompiler.general=Allgemein\nconf.decompiler.general.impl=Decompiler-Implementierung\nconf.decompiler.general.enabletimeout=Decompiler-Timeout aktivieren\nconf.decompiler.general.timeout=Timeout in Millisekunden\nconf.decompiler.filter=Filterung\nconf.decompiler.filter.escapeunicode=Unicode escapen\nconf.decompiler.filter.generics=Entferne generische Typen\nconf.decompiler.filter.vars=Entferne Variablen-Informationen\nconf.decompiler.filter.synthetics=Entferne synthetische Typen\nconf.decompiler.classfile=Klassen-Datei\nconf.decompiler.classfile.maxouterdepth=Maximale Breadcrumbs-Tiefe der äußeren Klasse\\n(-1: Prüfung deaktivieren, 0: Breadcrumbs deaktivieren)\n\nconf.dialog=Dialog\nconf.dialog.fileprompt=Datei-Aufforderung\nconf.dialog.fileprompt.load=Datei laden\nconf.dialog.fileprompt.export=Exportiere Datei\nconf.dialog.fileprompt.loadmap=Map laden\nconf.dialog.fileprompt.exportmap=Letzte Map exportieren\n\nconf.export=Export\nconf.export.general=Allgemein\nconf.export.general.shadeLibs=Biliotheken zum Output hinzufügen\nconf.export.general.compress=Dateien komprimieren\n\nconf.plugin=Plugins\nconf.plugin.general=Allgemein\nconf.plugin.general.enabled=Aktiviert\nconf.plugin.general.remote=Remote\nconf.plugin.general.remotetime=Cache-Zeitstempel\n\nconf.recent=Zuletzt verwendete\nconf.recent.max=Maximale Anzahl der zuletzt verwendeten Dateien\nconf.recent.workspaces=Maximale Anzahl der zuletzt verwendeten Arbeitsumgebungen\n\nconf.ssvm=SSVM\nconf.ssvm.access=File-IO-Zugriff\nconf.ssvm.access.read=Lesen erlauben\nconf.ssvm.access.write=Schreiben erlauben\nconf.ssvm.access.read.warn=Wenn Sie dies aktivieren, können SSVM-Aktionen Dateien auf Ihrem System lesen.\\nSeien Sie vorsichtig, wenn Sie diese Option aktivieren.\nconf.ssvm.access.write.warn=Wenn Sie dies aktivieren, können SSVM-Aktionen Dateien auf Ihrem System schreiben.\\nSeien Sie vorsichtig, wenn Sie diese Option aktivieren.\nconf.ssvm.general=Allgemein\nconf.ssvm.general.active=SSVM beim Laden von Arbeitsumgebungen initialisieren\nconf.ssvm.remote=Remote-VM\nconf.ssvm.remote.active=Aktiv\nconf.ssvm.remote.path=Pfad zur ausführbaren VM-Datei\nconf.undefined=Undefiniert\n\nmisc.all=Alle\nmisc.none=Keine\nmisc.casesensitive=Groß- und Kleinschreibung beachten\nmisc.member.field=Feld\nmisc.member.method=Methode\nmisc.member.field-n-method=Feld und Methode\nmisc.member.inner-class=Innere Klasse\nmisc.member.inner-interface=Inneres Interface\nmisc.member.inner-enum=Inneres Enum\nmisc.member.inner-annotation=Innere Annotation\nmisc.accessflag.visibility.public=Public\nmisc.accessflag.visibility.protected=Protected\nmisc.accessflag.visibility.private=Private\nmisc.accessflag.visibility.package=Package\nmisc.direction.up=Hoch\nmisc.direction.down=Nach unten\nmisc.direction.left=Links\nmisc.direction.right=Rechts\nmisc.direction.top=Nach oben\nmisc.direction.bottom=Nach unten\nmisc.position.top=Oben\nmisc.position.bottom=Unten\nmisc.position.left=Links\nmisc.position.right=Rechts\nmisc.position.center=Mitte\nmisc.position.middle=Mitte"
  },
  {
    "path": "recaf-ui/src/main/resources/translations/en_US.lang",
    "content": "## Language name\nlang.name=English\n\n##### General items\n## Main and context menus\nmenu.analysis=Analysis\nmenu.analysis.summary=View summary\nmenu.analysis.deobfuscation=Deobfuscation\nmenu.analysis.list-comments=View comments\nmenu.analysis.comment=Comment\nmenu.association.override=Override language\nmenu.association.none=No associations configured\nmenu.config=Config\nmenu.config.edit=Edit\nmenu.config.export=Export\nmenu.config.import=Import\nmenu.file=File\nmenu.file.attach=Attach to remote\nmenu.file.addtoworkspace=Add to workspace\nmenu.file.decompileall=Decompile all classes\nmenu.file.decompileall.path=Output path:\nmenu.file.openworkspace=Open workspace\nmenu.file.openurl=Open from URL\nmenu.file.exportapp=Export application\nmenu.file.exportworkspace=Export workspace config\nmenu.file.modifications=View modifications\nmenu.file.recent=Recent\nmenu.file.close=Close\nmenu.file.quit=Quit\nmenu.goto.class=Go to class\nmenu.goto.field=Go to field\nmenu.goto.method=Go to method\nmenu.goto.instruction=Go to instruction\nmenu.goto.file=Go to file\nmenu.goto.label=Go to label\nmenu.edit=Edit\nmenu.edit.add.field=Add field\nmenu.edit.add.method=Add method\nmenu.edit.add.annotation=Add annotation\nmenu.edit.override.method=Override method\nmenu.edit.remove.field=Remove fields\nmenu.edit.remove.method=Remove methods\nmenu.edit.remove.annotation=Remove annotations\nmenu.edit.assemble.class=Edit class in assembler\nmenu.edit.assemble.field=Edit field in assembler\nmenu.edit.assemble.method=Edit method in assembler\nmenu.edit.remove=Remove\nmenu.edit.copy=Copy\nmenu.edit.delete=Delete\nmenu.edit.noop=Make no-op\nmenu.edit.removevars=Prune variable info\nmenu.edit.changeversion=Change class versions\nmenu.edit.changeversion.up=Upgrade\nmenu.edit.changeversion.down=Downgrade\nmenu.edit.newclass=New class\nmenu.export.class=Export class\nmenu.export.file=Export file\nmenu.export.classes=Export classes\nmenu.export.files=Export files\nmenu.export.package=Export package\nmenu.export.directory=Export directory\nmenu.help=Help\nmenu.help.discord=Discord\nmenu.help.docs=Online user documentation\nmenu.help.docsdev=Online developer documentation\nmenu.help.github=Github\nmenu.help.issues=Issue tracker\nmenu.help.sysinfo=System information\nmenu.help.tutorial=Tutorial\nmenu.refactor=Refactor\nmenu.refactor.move=Move\nmenu.refactor.rename=Rename\nmenu.search=Search\nmenu.search.string=Strings\nmenu.search.number=Numbers\nmenu.search.class.member-declarations=Member declarations\nmenu.search.class.member-references=Member references\nmenu.search.class.type-references=Type references\nmenu.search.class.instruction=Instruction disassembly\nmenu.search.method-overrides=Method overrides\nmenu.search.method-references=Method references\nmenu.search.field-references=Field references\nmenu.search.noresults=No results\nmenu.mappings=Mappings\nmenu.mappings.apply=Apply\nmenu.mappings.apply-advanced=Advanced apply\nmenu.mappings.export=Export\nmenu.mappings.export.unsupported=%s (Unsupported)\nmenu.mappings.generate=Generate\nmenu.mappings.view=Current mappings\nmenu.scripting=Scripting\nmenu.scripting.list=Scripts\nmenu.scripting.none-found=No scripts found\nmenu.scripting.manage=Manage scripts\nmenu.scripting.new=New script\nmenu.scripting.edit=Edit\nmenu.scripting.browse=Browse scripts\nmenu.scripting.save=Save script\nmenu.scripting.execute=Execute\nmenu.scripting.editor=Script editor\nmenu.scripting.author=Author\nmenu.scripting.version=Version\nmenu.view=View\nmenu.view.hierarchy=Class hierarchy\nmenu.view.hierarchy.children=Children\nmenu.view.hierarchy.parents=Parents\nmenu.view.methodcfg=Control flow graph\nmenu.view.methodcallgraph=Call Graph\nmenu.view.methodcallgraph.calls=Calls\nmenu.view.methodcallgraph.callers=Callers\nmenu.view.methodcallgraph.focus=Focus on Method\nmenu.tab.close=Close\nmenu.tab.closeothers=Close others\nmenu.tab.closeall=Close all\nmenu.tab.copypath=Copy path\nmenu.image.resetscale=Reset zoom\nmenu.image.center=Center image\nmenu.hex.copyas=Copy as...\nmenu.mode=Change view\nmenu.mode.class.auto=Automatic\nmenu.mode.class.decompile=Decompile\nmenu.mode.class.low-level=Low level\nmenu.mode.file.auto=Automatic\nmenu.mode.file.text=Text\nmenu.mode.file.hex=Hex\nmenu.mode.file.image=Image\nmenu.mode.file.audio=Audio\nmenu.mode.file.video=Video\nmenu.mode.file.pe=Windows PE\nmenu.mode.file.elf=ELF\nmenu.mode.diff.decompile=Decompile\nmenu.mode.diff.disassemble=Disassemble\nmenu.vm=Virtualize\nmenu.vm.optimize=Optimize\nmenu.vm.run=Run\nmenu.plugin=Plugins\nmenu.plugin.manage=Manage plugins\nmenu.plugin.installed=Installed\nmenu.plugin.remote=Remote\nmenu.plugin.browse=Browse plugins\nmenu.plugin.enabled=Enabled\nmenu.plugin.uninstall=Uninstall\nmenu.plugin.uninstall.warning=Are you sure you want to delete this plugin?\n\n##### Keybinds\nbind.inputprompt.initial=<waiting>\nbind.inputprompt.finish=<ENTER to finish>\nbind.editor.find=Find\nbind.editor.goto=Goto\nbind.editor.rename=Rename\nbind.editor.replace=Replace\nbind.editor.save=Save\nbind.editor.undo=Undo\nbind.editor.closetab=Close current tab\nbind.quicknav=Quick nav\nbind.workspace.export=Export workspace\n\n##### Dialog texts\ndialog.cancel=Cancel\ndialog.close=Close\ndialog.confirm=Confirm\ndialog.finish=Finish\ndialog.next=Next\ndialog.previous=Previous\ndialog.dismiss=Dismiss\ndialog.configure=Configure\ndialog.warning=Warning\ndialog.restart=To change this configuration option, a restart is recommended.\\nAre you sure you want to apply?\ndialog.unknownextension=Unknown file extension. Do you want to configure a language association?\n\n## Search\ndialog.search.type=Type name\ndialog.search.member-owner=Member owner type\ndialog.search.member-name=Member name\ndialog.search.member-descriptor=Member descriptor\n\n## File chooser\ndialog.title.primary=Primary resource\ndialog.title.supporting=Supporting resources\ndialog.title.nochanges=Export without changes?\ndialog.file.primary=Primary\ndialog.file.open=Open\ndialog.file.open.directory=Directories\ndialog.file.open.file=Files\ndialog.file.export=Export\ndialog.file.save=Save\ndialog.file.nothing=Nothing selected\ndialog.file.nochanges=Do you want to export the application even though no changes were made?\ndialog.filefilter.any=Any type\ndialog.filefilter.mapping=Mappings\ndialog.filefilter.input=Applications\ndialog.filefilter.workspace=Workspaces\n\n## File drop items\ndialog.title.create-workspace=Create workspace\ndialog.title.update-workspace=Handle workspace input\ndialog.title.close-workspace=Close workspace?\ndialog.option.create-workspace=Create new workspace\ndialog.option.update-workspace=Add to workspace\n\n## Select class/file\ndialog.title.select-class=Select class\ndialog.title.select-file=Select file\n\n## Create class/file\ndialog.title.create-class=Create class\ndialog.header.create-class-error=The class name already exists.\\nPlease choose a different name.\n\n\n## Copy class/file\ndialog.title.copy-class=Copy class\ndialog.title.copy-directory=Copy directory\ndialog.title.copy-package=Copy package\ndialog.title.copy-field=Copy field\ndialog.title.copy-file=Copy file\ndialog.title.copy-method=Copy method\ndialog.header.copy-class=Provide a new name for the copied class.\ndialog.header.copy-directory=Provide a new name for the copied directory.\ndialog.header.copy-package=Provide a new name for the copied package.\ndialog.header.copy-field=Provide a new name for the copied field.\ndialog.header.copy-field-error=The field name already exists.\\nPlease choose a different name.\ndialog.header.copy-file=Provide a new name for the copied file.\ndialog.header.copy-method=Provide a new name for the copied method.\ndialog.header.copy-method-error=The method name already exists.\\nPlease choose a different name.\n\n## Delete class/file\ndialog.title.delete-class=Delete class\ndialog.title.delete-directory=Delete directory\ndialog.title.delete-field=Delete field\ndialog.title.delete-file=Delete file\ndialog.title.delete-method=Delete method\ndialog.title.delete-package=Delete package\ndialog.title.delete-resource=Delete resource\ndialog.header.delete-class=Are you sure you want to delete: %s?\ndialog.header.delete-directory=Are you sure you want to delete: %s?\ndialog.header.delete-field=Are you sure you want to delete: %s?\ndialog.header.delete-file=Are you sure you want to delete: %s?\ndialog.header.delete-method=Are you sure you want to delete: %s?\ndialog.header.delete-package=Are you sure you want to delete: %s?\ndialog.header.delete-resource=Are you sure you want to delete: %s?\n\n## Rename class/file\ndialog.title.rename-class=Rename class\ndialog.title.rename-class-warning=Warning\ndialog.title.rename-directory=Rename directory\ndialog.title.rename-field=Rename field\ndialog.title.rename-file=Rename file\ndialog.title.rename-file-warning=Warning\ndialog.title.rename-method=Rename method\ndialog.title.rename-package=Rename package\ndialog.header.rename-class=Provide a new name for the class.\ndialog.header.rename-class-error=The class name already exists.\\nPlease choose a different name.\ndialog.header.rename-package=Provide a new name for the package.\ndialog.header.rename-package-error=The package name already exists.\\nPlease choose a different name.\ndialog.header.rename-package-warning=The package name already exists.\\nThis may overwrite some classes.\ndialog.header.rename-directory=Provide a new name for the directory.\ndialog.header.rename-directory-error=The directory name already exists.\\nPlease choose a different name.\ndialog.header.rename-directory-warning=The directory name already exists.\\nThis may overwrite some files.\ndialog.header.rename-field=Provide a new name for the field.\ndialog.header.rename-field-error=The field name already exists.\\nPlease choose a different name.\ndialog.header.rename-file=Provide a new name for the file.\ndialog.header.rename-file-error=The file name already exists.\\nPlease choose a different name.\ndialog.header.rename-method=Provide a new name for the method.\ndialog.header.rename-method-error=The method name already exists.\\nPlease choose a different name.\n\n## Move class/file\ndialog.title.move-class=Select a destination package\ndialog.title.move-directory=Select a destination (parent) directory\ndialog.title.move-file=Select a destination directory\ndialog.title.move-package=Select a destination (parent) package\ndialog.header.move-class=Move the class to a new package.\ndialog.header.move-directory=Move the directory into to a new parent directory.\ndialog.header.move-file=Move the file to a new directory.\ndialog.header.move-package=Move the package into to a new parent package.\n\n## Add members\ndialog.title.add-field=Add field\ndialog.title.add-method=Add method\ndialog.title.override-method=Override method\ndialog.input.name=Name\ndialog.input.desc=Descriptor\ndialog.warn.illegal-name=Illegal name\ndialog.warn.illegal-desc=Illegal descriptor format\ndialog.warn.field-conflict=The field name already exists.\\nPlease choose a different name.\ndialog.warn.method-conflict=The method name already exists.\\nPlease choose a different name.\n\n## VM actions\ndialog.title.vm-invoke-args=Virtualize method call\ndialog.title.vm-peephole-invoke-args=Virtualized peephole optimization\ndialog.vm.execute=Execute\ndialog.vm.optimize=Optimize\ndialog.vm.create-dummy=Use dummy\ndialog.vm.create-null=Use null\n\n## Hex dialogs\ndialog.hex.title.insertcount=Insert\ndialog.hex.header.insertcount=How many bytes to insert?\n\n# Base Converter dialog\ndialog.conv.title.literal=Number literal\ndialog.conv.title.expression=Number expression\n\n## Quick nav\ndialog.quicknav=Quick navigation\ndialog.quicknav.tab.classes=Classes\ndialog.quicknav.tab.members=Members\ndialog.quicknav.tab.files=Files\ndialog.quicknav.tab.text=Text\ndialog.quicknav.tab.commented=Comments\n\n## Error dialog\ndialog.error.exportclass.title=Failed to export class\ndialog.error.exportclass.header=An error occurred when writing to the destination\ndialog.error.exportclass.content=The error was:\ndialog.error.exportfile.title=Failed to export file\ndialog.error.exportfile.header=An error occurred when writing to the destination\ndialog.error.exportfile.content=The error was:\ndialog.error.exportworkspace.title=Failed to export workspace\ndialog.error.exportworkspace.header=An error occurred when writing to the destination\ndialog.error.exportworkspace.content=The error was:\ndialog.error.loadworkspace.title=Failed to load workspace\ndialog.error.loadworkspace.header=An error occurred when reading from the selected files\ndialog.error.loadworkspace.content=The error was:\ndialog.error.loadsupport.title=Failed to load supporting resources\ndialog.error.loadsupport.header=An error occurred when reading from the selected files\ndialog.error.loadsupport.content=The error was:\ndialog.error.attach.title=Failed to attach to JVM\ndialog.error.attach.header=An error occurred when connecting to the remote JVM\ndialog.error.attach.content=The error was:\n\n##### Panels\n## Welcome\nwelcome.title=Welcome\nwelcome.links=Links\nwelcome.links.home=Recaf: Home\nwelcome.links.docs-user=User Documentation\nwelcome.links.docs-dev=Developer Documentation\nwelcome.links.github=Github: Source + Bug/Feature Tracker\nwelcome.links.discord=Discord\nwelcome.links.jvms=Java Virtual Machine Specification\nwelcome.links.jvms.class=Class File Format\nwelcome.links.jvms.instructions=JVM Instruction Set\nwelcome.dnd=You can drag & drop files on this screen to open them.\nwelcome.tutorial=You haven't completed the tutorial. Click here to start!\nwelcome.dayssince=days since build\nwelcome.norecent=No recent workspaces\n\n\n## Workspace\nworkspace.title=Workspace\nworkspace.filter-prompt=Filter: Class/file name...\nworkspace.info=Information\nworkspace.info-progress=Analyzing workspace contents...\n\n## Attach\nattach.unsupported=Attach failed to initialize\nattach.unsupported.detail=The attach agent failed to self-extract.\nattach.no-vms=No attachable JVMs found\nattach.no-vms.detail=There are currently no Java virtual machines that are available to attach to.\nattach.problem.disable-attach=If you are attempting to connect to a Java process that uses -XX:+DisableAttachMechanism please see:\nattach.problem.java-21=If you are attempting to connect to a Java 21+ process please see:\nattach.connect=Connect\nattach.tab.properties=Properties\nattach.tab.classloading=Classes\nattach.tab.compilation=Compilation\nattach.tab.system=System\nattach.tab.runtime=Runtime\nattach.tab.thread=Threads\n\n## Changes view\nmodifications.none=No modification history for item\nmodifications.title=Modifications\n\n## Java area\njava.decompiling=Decompiling class...\njava.unparsable=SourceSolver failed to interpret source, context actions only available 'Fields & Methods' side tab\njava.parse-state.error=Parser error\njava.parse-state.error-details=Right click context actions are not available since the parse failed.\\nYou can use the 'Fields & Methods' side tab in the meantime.\njava.parse-state.initial=Parsing in progress...\njava.parse-state.initial-details=Right click context actions are not available until the parse completes.\\nYou can use the 'Fields & Methods' side tab in the meantime.\njava.parse-state.new-progress=Reparsing...\njava.parse-state.new-progress-details=Changes have been made so a new parse is in-progress.\\nThe old model will be used while the new one is constructed.\\nYou can alternatively use the 'Fields & Methods' side tab.\njava.parse-state.none=No content to parse\njava.decompile-failure=Class failed to decompile. Some options:\\n- Switch decompilers\\n- Open the class in the assembler or another view\\n- Try deobfuscating the class and trying again\njava.decompile-failure.brief=Class failed to decompile\njava.savewitherrors=It seems like this is your first time making changes that resulted in errors.\\nThese are typically a result of the decompiled code not being semantically correct Java.\\nYou must resolve these errors before changes will be saved.\\n\\nSome suggestions:\\n - Change decompilers in 'config' or bottom right (i)\\n - Hover over the red error boxes, or click the error box at the top to see what the errors are\\n - Use the assembler instead of recompilation to make changes\njava.savewitherrors.title=Regarding recompile errors\njava.decompiler=Decompiler\njava.targetversion=Compile target version\njava.targetversion.notice.down=Downgrading will add stub classes to emulate missing APIs\njava.targetversion.auto=Match class file version\njava.targetdownsampleversion=Downsample target version\njava.targetdownsampleversion.disabled=Disabled\njava.targetdebug=Compile with debug info\njava.info=Class information\njava.info.version=Class version\njava.info.sourcefile=Source file name\n\n## Search bar\nfind.replace=Replace\nfind.replaceall=Replace all\nfind.regexinvalid=Invalid Regex\nfind.regexreplace=Replacement text\n\n## Fields and methods\nfieldsandmethods.title=Fields & methods\nfieldsandmethods.empty=Class has no members\nfieldsandmethods.showoutlinedsynths=Show synthetic (compiler-generated) members\nfieldsandmethods.showoutlinedvisibility=Filter by member visibility\nfieldsandmethods.showoutlinedmembertype=Filter by member type\nfieldsandmethods.nametypemode=Name/type display mode\nfieldsandmethods.sortalphabetically=Sort alphabetically\nfieldsandmethods.sortbyvisibility=Sort by visibility\nfieldsandmethods.filter.prompt=Filter: Field/Method name...\n\n## Hierarchy\nhierarchy.title=Inheritance\nhierarchy.children=Children\nhierarchy.parents=Parents\n\n## Kotlin Metadata\nkotlinmetadata.title=@Metadata\nkotlinmetadata.orderwarning=Important: Items do not appear in the same order as defined in the class file\n\n## Logging\nlogging.title=Logging\n\n## Assembler\nassembler.problem.0=No problems\nassembler.problem.1=1 problem\nassembler.problem.N=N problems\nassembler.title=Assembler\nassembler.analysis.title=Analysis\nassembler.analysis.stack=Stack\nassembler.analysis.variables=Variables\nassembler.analysis.type=Type\nassembler.analysis.value=Value\nassembler.playground.title=Java to Bytecode\nassembler.playground.comment=// Write some Java code here to convert it to bytecode automatically\\n// You can access the current class's fields/methods,\\n// and the current method's parameters/variables.\nassembler.snippets.title=Snippets\nassembler.variables.title=Declared Variables\nassembler.variables.name=Variable name\nassembler.variables.type=Type\nassembler.variables.usage=Usages\nassembler.variables.value=Value\nassembler.variables.empty=<Requires compiling at least once>\nassembler.variables.read-before-write=Read before written to\nassembler.suggestions.none=No suggestions\n\n## Comments\ncomments.search.prompt=Search for comment...\n\n## Search\nsearch.run=Search\nsearch.results=Results\nsearch.text=Text content\nsearch.textmode=Text match mode\nsearch.number=Number value\nsearch.numbermode=Number match mode\nsearch.refowner=Member owner\nsearch.refname=Member name\nsearch.refdesc=Member type descriptor\n\n## Help\nhelp.system=System\nhelp.system.sub=Information about the OS\nhelp.java=Java\nhelp.java.sub=Information about the JVM\nhelp.javafx=JavaFX\nhelp.javafx.sub=Information about the JavaFX UI\nhelp.recaf=Recaf\nhelp.recaf.sub=Information about Recaf\nhelp.copy=Copy information to clipboard\nhelp.opendir=Open Recaf directory\n\n## Deobfuscation\ndeobf=Deobfuscation\ndeobf.selection.title=Select transformers\ndeobf.order.title=Order transformers\ndeobf.order.hint=Select transformers on the left\\nChange their order via dragging here\ndeobf.order.pre=Missing recommended predecessors\ndeobf.order.suc=Missing recommended successors\ndeobf.max-passes=Max transform passes\ndeobf.preview.title=Preview\ndeobf.preview.pick=Pick preview class\ndeobf.preview.toggle-mode=Switch preview mode\ndeobf.preview.noselection=// No class is selected, pick a class to preview\\n// the transformation before & after states\ndeobf.tree.generic=Generic\ndeobf.tree.generic.anticrasher=Anti-Crasher\ndeobf.tree.generic.optimize=Optimization\ndeobf.tree.generic.restoration=Restoration\ndeobf.tree.specific=Specific\ndeobf.tree.plugin-provided=Plugin\ndeobf.apply=Transform workspace\n\n## Mapping application\nmapapply=Apply mappings\nmapapply.pick.file=Choose mappings file\nmapapply.pick.dir=Choose mappings directory\nmapapply.settings.unique=Assume unique keys, ignore inheritance\n\n## Mapping generator\nmapgen=Mapping generator\nmapgen.genimpl=Naming convention\nmapgen.filter.name=Name\nmapgen.filter.class-name=Class name\nmapgen.filter.owner-name=Owner name\nmapgen.filter.field-name=Field name\nmapgen.filter.method-name=Method name\nmapgen.filter.variable-name=Variable name\nmapgen.filters=Filters\nmapgen.filters.add=Add filter\nmapgen.filters.edit=Edit selected\nmapgen.filters.delete=Delete selected\nmapgen.filters.type=Filter type\nmapgen.filter.modifiers.tooltip=Modifiers are separated by spaces\nmapgen.filter.excludealreadymapped=Exclude already mapped\nmapgen.filter.excludemodifier=Exclude modifiers\nmapgen.filter.excludeclasses=Exclude classes\nmapgen.filter.excludename=Exclude names\nmapgen.filter.excludeclass=Exclude on classes\nmapgen.filter.excludefield=Exclude on fields\nmapgen.filter.excludemethod=Exclude on methods\nmapgen.filter.includemodifier=Include modifiers\nmapgen.filter.includeclass=Include on classes\nmapgen.filter.includefield=Include on fields\nmapgen.filter.includemethod=Include on methods\nmapgen.filter.includevariable=Include on variables\nmapgen.filter.includewhitespacenames=Include whitespaces\nmapgen.filter.includenonasciinames=Include non-ascii\nmapgen.filter.includekeywords=Include keywords\nmapgen.filter.includenonjavaidentifiers=Include non-java identifiers\nmapgen.filter.includelong=Include long names\nmapgen.filter.includename=Include names\nmapgen.filter.includeclasses=Include classes\nmapgen.title.newfilter=New filter\nmapgen.header.newfilter=Input filter content\nmapgen.preview.empty=Statistics of generated mappings will appear here\\n\\n\\n\nmapgen.configure=Configure\nmapgen.configure.nothing=Nothing to configure\nmapgen.generate=Generate\nmapgen.apply=Apply\n\n## Mapping view\nmapprog=Mapping progress\nmapprog.metric.size=Class file size\nmapprog.metric.membercount=Class fields & methods\n\n##### Tree\ntree.classes=Classes\ntree.files=Files\ntree.defaultpackage=(Default package)\ntree.defaultdirectory=(Root directory)\ntree.prompt=Drag your files here\ntree.hidelibs=Hide libraries\ntree.phantoms=Generated phantoms\ntree.embedded-resources=Embedded\n\n##### Services\nservice=All services\nservice.analysis=Analysis\nservice.analysis.comments-config=Comments\nservice.analysis.comments-config.enable-display=Display comments in decompilation\nservice.analysis.comments-config.word-wrapping-limit=Word wrap limit\nservice.analysis.info-summary-config=Workspace summarization\nservice.analysis.info-summary-config.summarize-on-open=Summarize workspace contents when opened\nservice.analysis.graph-calls-config=Call graph\nservice.analysis.graph-inheritance-config=Inheritance graph\nservice.analysis.jphantom-generator-config=JPhantom\nservice.analysis.jphantom-generator-config.generate-workspace-phantoms=Generate and append phantoms to workspaces\nservice.analysis.search-config=Search\nservice.analysis.entry-points=Entry points\nservice.analysis.entry-points.none=No entries found\nservice.analysis.hashing=Hashing\nservice.analysis.hashing.type=Type\nservice.analysis.hashing.value=Hash\nservice.analysis.anti-decompile=Anti-Decompilation\nservice.analysis.anti-decompile.illegal-attr=Illegal attributes\nservice.analysis.anti-decompile.illegal-name=Illegal names\nservice.analysis.anti-decompile.label-patch=Patch %d affected classes\nservice.analysis.signature-info=Signature information\nservice.assembler=Assembler\nservice.assembler.assembler-pipeline.general-config=Common\nservice.assembler.assembler-pipeline.general-config.disassembly-ast-parse-delay=AST parse delay\nservice.assembler.assembler-pipeline.general-config.disassembly-indent=Indentation\nservice.assembler.assembler-pipeline.general-config.disassembly-whole-floating=Standard notation floating points\nservice.assembler.dalvik-assembler-config=Dalvik\nservice.assembler.dalvik-assembler-config.value-analysis=Enable value analysis\nservice.assembler.dalvik-assembler-config.simulate-jvm-calls=Simulate common JVM calls\nservice.assembler.jvm-assembler-config=JVM\nservice.assembler.jvm-assembler-config.value-analysis=Enable value analysis\nservice.assembler.jvm-assembler-config.simulate-jvm-calls=Simulate common JVM calls\nservice.assembler.jvm-assembler-config.try-range-comments=Emit try-catch range comments\nservice.assembler.flow-lines-config=Control flow lines\nservice.assembler.flow-lines-config.connection-mode=Line mode\nservice.assembler.flow-lines-config.render-mode=Render mode\nservice.compile=Compilation\nservice.compile.java-compiler-config=Javac\nservice.compile.java-compiler-config.generate-phantoms=Generate missing classes\nservice.compile.java-compiler-config.default-emit-debug=Default to include debug\nservice.compile.java-compiler-config.default-compile-target-version=Default class version target\nservice.compile.java-compiler-config.default-downsample-target-version=Default downsample class version target\nservice.debug=Attach/Debug\nservice.debug.attach-config=Attach config\nservice.debug.attach-config.attach-jmx-bean-agent=Attach JMX bean agent\nservice.debug.attach-config.passive-scanning=Passive scanning state\nservice.config-manager-config=Config manager\nservice.decompile=Decompilation\nservice.decompile.decompilers-config=Decompile manager\nservice.decompile.decompilers-config.pref-android-decompiler=Preferred Android decompiler\nservice.decompile.decompilers-config.pref-jvm-decompiler=Preferred Java decompiler\nservice.decompile.decompilers-config.cache-decompilations=Cache decompilations\nservice.decompile.decompilers-config.filter-annotations-duplicate=Filter duplicate annotations\nservice.decompile.decompilers-config.filter-annotations-illegal=Filter illegal annotations\nservice.decompile.decompilers-config.filter-annotations-long=Filter long annotations\nservice.decompile.decompilers-config.filter-annotations-long-limit=Long annotation limit\nservice.decompile.decompilers-config.filter-exceptions-long=Filter long exceptions\nservice.decompile.decompilers-config.filter-exceptions-long-limit=Long exceptions limit\nservice.decompile.decompilers-config.filter-hollow=Filter class content (hollow)\nservice.decompile.decompilers-config.filter-illegal-signatures=Filter illegal signatures\nservice.decompile.decompilers-config.filter-synthetics=Filter synthetic flags\nservice.decompile.decompilers-config.filter-names-ascii=Filter non-ascii names\nservice.decompile.decompilers-config.filter-strip-debug=Filter debug data (vars, generics)\nservice.decompile.impl=Implementations\nservice.decompile.impl.decompiler-cfr-config=CFR\nservice.decompile.impl.decompiler-cfr-config.aexagg=Try to extend and merge exceptions more aggressively\nservice.decompile.impl.decompiler-cfr-config.aexagg2=Try to extend and merge exceptions more aggressively (may change semantics)\nservice.decompile.impl.decompiler-cfr-config.aggressivedocopy=Clone code from impossible jumps into loops with 'first' test\nservice.decompile.impl.decompiler-cfr-config.aggressivedoextension=Fold impossible jumps into do loops with 'first' test\nservice.decompile.impl.decompiler-cfr-config.aggressiveduff=Fold duff device style switches with additional control.\nservice.decompile.impl.decompiler-cfr-config.aggressivesizethreshold=Opcode count at which to trigger aggressive reductions\nservice.decompile.impl.decompiler-cfr-config.allowmalformedswitch=Allow potentially malformed switch statements\nservice.decompile.impl.decompiler-cfr-config.antiobf=Undo various obfuscations\nservice.decompile.impl.decompiler-cfr-config.arrayiter=Re-sugar array based iteration\nservice.decompile.impl.decompiler-cfr-config.collectioniter=Re-sugar collection based iteration\nservice.decompile.impl.decompiler-cfr-config.commentmonitors=Replace monitors with comments - useful if we're completely confused\nservice.decompile.impl.decompiler-cfr-config.comments=Output comments describing decompiler status, fallback flags etc.\nservice.decompile.impl.decompiler-cfr-config.constobf=Undo constant obfuscation\nservice.decompile.impl.decompiler-cfr-config.decodeenumswitch=Re-sugar switch on enum\nservice.decompile.impl.decompiler-cfr-config.decodefinally=Re-sugar finally statements\nservice.decompile.impl.decompiler-cfr-config.decodelambdas=Re-build lambda functions\nservice.decompile.impl.decompiler-cfr-config.decodestringswitch=Re-sugar switch on String\nservice.decompile.impl.decompiler-cfr-config.eclipse=Enable transformations to handle Eclipse code better\nservice.decompile.impl.decompiler-cfr-config.elidescala=Elide things which aren't helpful in scala output (serialVersionUID, @ScalaSignature)\nservice.decompile.impl.decompiler-cfr-config.forbidanonymousclasses=Don't allow anonymous classes.\nservice.decompile.impl.decompiler-cfr-config.forbidmethodscopedclasses=Don't allow method scoped classes.\nservice.decompile.impl.decompiler-cfr-config.forceclassfilever=Force the version of the classfile (and hence java) that classfiles are decompiled as.\nservice.decompile.impl.decompiler-cfr-config.forcecondpropagate=Pull results of deterministic jumps back through some constant assignments\nservice.decompile.impl.decompiler-cfr-config.forceexceptionprune=Remove nested exception handlers if they don't change semantics\nservice.decompile.impl.decompiler-cfr-config.forcereturningifs=Move return up to jump site\nservice.decompile.impl.decompiler-cfr-config.forcetopsort=Force basic block sorting.  Usually only useful when obfuscation is present.\nservice.decompile.impl.decompiler-cfr-config.forcetopsortaggress=Force extra aggressive topsort options\nservice.decompile.impl.decompiler-cfr-config.forcetopsortnopull=Force topsort not to pull try blocks\nservice.decompile.impl.decompiler-cfr-config.forloopaggcapture=Allow for loops to aggressively roll mutations into update section, even if they don't appear to be involved with the predicate\nservice.decompile.impl.decompiler-cfr-config.hidebridgemethods=Hide bridge methods\nservice.decompile.impl.decompiler-cfr-config.hidelangimports=Hide imports from java.lang.\nservice.decompile.impl.decompiler-cfr-config.hidelongstrings=Hide very long strings - useful if obfuscators have placed fake code in strings\nservice.decompile.impl.decompiler-cfr-config.hideutf=Hide UTF8 characters - quote them instead of showing the raw characters\nservice.decompile.impl.decompiler-cfr-config.ignoreexceptions=Drop exception information if completely stuck (WARNING : changes semantics, dangerous!)\nservice.decompile.impl.decompiler-cfr-config.ignoreexceptionsalways=Drop exception information (WARNING : changes semantics, dangerous!)\nservice.decompile.impl.decompiler-cfr-config.innerclasses=Decompile inner classes\nservice.decompile.impl.decompiler-cfr-config.instanceofpattern=Re-sugar instanceof pattern matches\nservice.decompile.impl.decompiler-cfr-config.j14classobj=Reverse java 1.4 class object construction\nservice.decompile.impl.decompiler-cfr-config.labelledblocks=Allow code to be emitted which uses labelled blocks, (handling odd forward gotos)\nservice.decompile.impl.decompiler-cfr-config.lenient=Be a bit more lenient in situations where we'd normally throw an exception\nservice.decompile.impl.decompiler-cfr-config.liftconstructorinit=Lift initialisation code common to all constructors into member initialisation\nservice.decompile.impl.decompiler-cfr-config.obfattr=Undo attribute obfuscation\nservice.decompile.impl.decompiler-cfr-config.obfcontrol=Undo control flow obfuscation\nservice.decompile.impl.decompiler-cfr-config.override=Generate @Override annotations (if method is seen to implement interface method, or override a base class method)\nservice.decompile.impl.decompiler-cfr-config.previewfeatures=Decompile preview features if class was compiled with 'javac --enable-preview'\nservice.decompile.impl.decompiler-cfr-config.pullcodecase=Pull code into case statements agressively\nservice.decompile.impl.decompiler-cfr-config.recordtypes=Re-sugar record types\nservice.decompile.impl.decompiler-cfr-config.recover=Allow more and more aggressive options to be set if decompilation fails\nservice.decompile.impl.decompiler-cfr-config.recovertypeclash=Split lifetimes where analysis caused type clash\nservice.decompile.impl.decompiler-cfr-config.recovertypehints=Recover type hints for iterators from first pass\nservice.decompile.impl.decompiler-cfr-config.reducecondscope=Reduce the scope of conditionals, possibly generating more anonymous blocks\nservice.decompile.impl.decompiler-cfr-config.relinkconst=Relink constants - if there is an inlined reference to a field, attempt to de-inline.\nservice.decompile.impl.decompiler-cfr-config.relinkconststring=Relink constant strings - if there is a local reference to a string which matches a static final, use the static final.\nservice.decompile.impl.decompiler-cfr-config.removebadgenerics=Hide generics where we've obviously got it wrong, and fallback to non-generic\nservice.decompile.impl.decompiler-cfr-config.removeboilerplate=Remove boilderplate functions - constructor boilerplate, lambda deserialisation etc.\nservice.decompile.impl.decompiler-cfr-config.removedeadconditionals=Remove code that can't be executed.\nservice.decompile.impl.decompiler-cfr-config.removedeadmethods=Remove pointless methods - default constructor etc.\nservice.decompile.impl.decompiler-cfr-config.removeinnerclasssynthetics=Remove (where possible) implicit outer class references in inner classes\nservice.decompile.impl.decompiler-cfr-config.renamedupmembers=Rename ambiguous/duplicate fields.\nservice.decompile.impl.decompiler-cfr-config.renameenumidents=Rename ENUM identifiers which do not match their 'expected' string names.\nservice.decompile.impl.decompiler-cfr-config.renameillegalidents=Rename identifiers which are not valid java identifiers.\nservice.decompile.impl.decompiler-cfr-config.renamesmallmembers=Rename small members.  Note - this WILL break reflection based access, so is not automatically enabled.\nservice.decompile.impl.decompiler-cfr-config.sealed=Decompile 'sealed' constructs\nservice.decompile.impl.decompiler-cfr-config.showinferrable=Decorate methods with explicit types if not implied by arguments\nservice.decompile.impl.decompiler-cfr-config.showversion=Show used CFR version in header (handy to turn off when regression testing)\nservice.decompile.impl.decompiler-cfr-config.skipbatchinnerclasses=When processing many files, skip inner classes, as they will be processed as part of outer classes anyway.\nservice.decompile.impl.decompiler-cfr-config.staticinitreturn=Try to remove return from static init\nservice.decompile.impl.decompiler-cfr-config.stringbuffer=Convert new StringBuffer().append.append.append to string + string + string\nservice.decompile.impl.decompiler-cfr-config.stringbuilder=Convert new StringBuilder().append.append.append to string + string + string\nservice.decompile.impl.decompiler-cfr-config.stringconcat=Convert usages of StringConcatFactor to string + string + string\nservice.decompile.impl.decompiler-cfr-config.sugarasserts=Re-sugar assert calls\nservice.decompile.impl.decompiler-cfr-config.sugarboxing=Where possible, remove pointless boxing wrappers\nservice.decompile.impl.decompiler-cfr-config.sugarenums=Re-sugar enums\nservice.decompile.impl.decompiler-cfr-config.sugarretrolambda=Where possible, resugar uses of retro lambda\nservice.decompile.impl.decompiler-cfr-config.switchexpression=Re-sugar switch expression\nservice.decompile.impl.decompiler-cfr-config.tidymonitors=Remove support code for monitors - e.g. catch blocks just to exit a monitor\nservice.decompile.impl.decompiler-cfr-config.tryresources=Reconstruct try-with-resources\nservice.decompile.impl.decompiler-cfr-config.usenametable=Use local variable name table if present\nservice.decompile.impl.decompiler-cfr-config.usesignatures=Use signatures in addition to descriptors (when they are not obviously incorrect)\nservice.decompile.impl.decompiler-cfr-config.version=Show the current CFR version\nservice.decompile.impl.decompiler-procyon-config=Procyon\nservice.decompile.impl.decompiler-procyon-config.alwaysGenerateExceptionVariableForCatchBlocks=Always generate catch block\nservice.decompile.impl.decompiler-procyon-config.arePreviewFeaturesEnabled=Enable language preview features\nservice.decompile.impl.decompiler-procyon-config.bytecodeOutputOptions=Bytecode output options\nservice.decompile.impl.decompiler-procyon-config.disableForEachTransforms=Disable forEach(...) transforms\nservice.decompile.impl.decompiler-procyon-config.excludeNestedTypes=Exclude nested types\nservice.decompile.impl.decompiler-procyon-config.flattenSwitchBlocks=Flatten switch blocks\nservice.decompile.impl.decompiler-procyon-config.forceExplicitImports=Force explicit imports\nservice.decompile.impl.decompiler-procyon-config.forceExplicitTypeArguments=Force explicit type arguments\nservice.decompile.impl.decompiler-procyon-config.forceFullyQualifiedReferences=Force fully qualified references\nservice.decompile.impl.decompiler-procyon-config.forcedCompilerTarget=Target language version\nservice.decompile.impl.decompiler-procyon-config.includeErrorDiagnostics=Include error diagnostics\nservice.decompile.impl.decompiler-procyon-config.includeLineNumbersInBytecode=Show debug line numbers (bytecode)\nservice.decompile.impl.decompiler-procyon-config.isUnicodeOutputEnabled=Enable unicode in output\nservice.decompile.impl.decompiler-procyon-config.languageTarget=Language target\nservice.decompile.impl.decompiler-procyon-config.mergeVariables=Merge variables when possible\nservice.decompile.impl.decompiler-procyon-config.retainPointlessSwitches=Retain useless switches\nservice.decompile.impl.decompiler-procyon-config.retainRedundantCasts=Retain useless casts\nservice.decompile.impl.decompiler-procyon-config.showDebugLineNumbers=Show debug line numbers\nservice.decompile.impl.decompiler-procyon-config.showSyntheticMembers=Show synthetic members\nservice.decompile.impl.decompiler-procyon-config.simplifyMemberReferences=Simplify member references\nservice.decompile.impl.decompiler-procyon-config.textBlockLineMinimum=Text block minimum lines\nservice.decompile.impl.decompiler-vineflower-config=Vineflower\nservice.decompile.impl.decompiler-vineflower-config.logging-level=Logging level\nservice.decompile.impl.decompiler-vineflower-config.remove-bridge=Remove Bridge Methods\nservice.decompile.impl.decompiler-vineflower-config.remove-synthetic=Remove Synthetic Methods And Fields\nservice.decompile.impl.decompiler-vineflower-config.decompile-inner=Decompile Inner Classes\nservice.decompile.impl.decompiler-vineflower-config.decompile-java4=Decompile Java 4 class references\nservice.decompile.impl.decompiler-vineflower-config.decompile-assert=Decompile Assertions\nservice.decompile.impl.decompiler-vineflower-config.hide-empty-super=Hide Empty super()\nservice.decompile.impl.decompiler-vineflower-config.hide-default-constructor=Hide Default Constructor\nservice.decompile.impl.decompiler-vineflower-config.decompile-generics=Decompile Generics\nservice.decompile.impl.decompiler-vineflower-config.incorporate-returns=Incorporate returns in try-catch blocks\nservice.decompile.impl.decompiler-vineflower-config.ensure-synchronized-monitors=Ensure synchronized ranges are complete\nservice.decompile.impl.decompiler-vineflower-config.decompile-enums=Decompile Enums\nservice.decompile.impl.decompiler-vineflower-config.decompile-preview=Decompile Preview Features\nservice.decompile.impl.decompiler-vineflower-config.remove-getclass=Remove reference getClass()\nservice.decompile.impl.decompiler-vineflower-config.keep-literals=Keep Literals As Is\nservice.decompile.impl.decompiler-vineflower-config.boolean-as-int=Represent boolean as 0/1\nservice.decompile.impl.decompiler-vineflower-config.ascii-strings=ASCII String Characters\nservice.decompile.impl.decompiler-vineflower-config.synthetic-not-set=Synthetic Not Set\nservice.decompile.impl.decompiler-vineflower-config.undefined-as-object=Treat Undefined Param Type As Object\nservice.decompile.impl.decompiler-vineflower-config.use-lvt-names=Use LVT Names\nservice.decompile.impl.decompiler-vineflower-config.use-method-parameters=Use Method Parameters\nservice.decompile.impl.decompiler-vineflower-config.remove-empty-try-catch=Remove Empty try-catch blocks\nservice.decompile.impl.decompiler-vineflower-config.decompile-finally=Decompile Finally\nservice.decompile.impl.decompiler-vineflower-config.lambda-to-anonymous-class=Decompile Lambdas as Anonymous Classes\nservice.decompile.impl.decompiler-vineflower-config.bytecode-source-mapping=Bytecode to Source Mapping\nservice.decompile.impl.decompiler-vineflower-config.__dump_original_lines__=Dump Code Lines\nservice.decompile.impl.decompiler-vineflower-config.ignore-invalid-bytecode=Ignore Invalid Bytecode\nservice.decompile.impl.decompiler-vineflower-config.verify-anonymous-classes=Verify Anonymous Classes\nservice.decompile.impl.decompiler-vineflower-config.ternary-constant-simplification=Ternary Constant Simplification\nservice.decompile.impl.decompiler-vineflower-config.pattern-matching=Pattern Matching\nservice.decompile.impl.decompiler-vineflower-config.try-loop-fix=Try-Loop fix\nservice.decompile.impl.decompiler-vineflower-config.ternary-in-if=[Experimental] Ternary In If Conditions\nservice.decompile.impl.decompiler-vineflower-config.decompile-switch-expressions=Decompile Switch Expressions\nservice.decompile.impl.decompiler-vineflower-config.show-hidden-statements=[Debug] Show hidden statements\nservice.decompile.impl.decompiler-vineflower-config.override-annotation=Override Annotation\nservice.decompile.impl.decompiler-vineflower-config.simplify-stack=Second-Pass Stack Simplification\nservice.decompile.impl.decompiler-vineflower-config.verify-merges=[Experimental] Verify Variable Merges\nservice.decompile.impl.decompiler-vineflower-config.include-classpath=Include Entire Classpath\nservice.decompile.impl.decompiler-vineflower-config.include-runtime=Include Java Runtime\nservice.decompile.impl.decompiler-vineflower-config.explicit-generics=Explicit Generic Arguments\nservice.decompile.impl.decompiler-vineflower-config.inline-simple-lambdas=Inline Simple Lambdas\nservice.decompile.impl.decompiler-vineflower-config.log-level=Logging Level\nservice.decompile.impl.decompiler-vineflower-config.max-time-per-method=[DEPRECATED] Max time to process method\nservice.decompile.impl.decompiler-vineflower-config.rename-members=Rename Members\nservice.decompile.impl.decompiler-vineflower-config.user-renamer-class=User Renamer Class\nservice.decompile.impl.decompiler-vineflower-config.new-line-separator=[DEPRECATED] New Line Seperator\nservice.decompile.impl.decompiler-vineflower-config.indent-string=Indent String\nservice.decompile.impl.decompiler-vineflower-config.preferred-line-length=Preferred line length\nservice.decompile.impl.decompiler-vineflower-config.banner=Banner\nservice.decompile.impl.decompiler-vineflower-config.error-message=Error Message\nservice.decompile.impl.decompiler-vineflower-config.thread-count=Thread Count\nservice.decompile.impl.decompiler-vineflower-config.skip-extra-files=Skip Extra Files\nservice.decompile.impl.decompiler-vineflower-config.warn-inconsistent-inner-attributes=Warn about inconsistent inner attributes\nservice.decompile.impl.decompiler-vineflower-config.dump-bytecode-on-error=Dump Bytecode On Error\nservice.decompile.impl.decompiler-vineflower-config.dump-exception-on-error=Dump Exceptions On Error\nservice.decompile.impl.decompiler-vineflower-config.decompiler-comments=Decompiler Comments\nservice.decompile.impl.decompiler-vineflower-config.sourcefile-comments=SourceFile comments\nservice.decompile.impl.decompiler-vineflower-config.decompile-complex-constant-dynamic=Decompile complex constant-dynamic expressions\nservice.decompile.impl.decompiler-vineflower-config.force-jsr-inline=Force JSR inline\nservice.decompile.impl.decompiler-vineflower-config.dump-text-tokens=Dump Text Tokens\nservice.decompile.impl.decompiler-vineflower-config.remove-imports=Remove Imports\nservice.decompile.impl.decompiler-vineflower-config.mark-corresponding-synthetics=Mark Corresponding Synthetics\nservice.io=IO\nservice.io.directories-config=Directories\nservice.io.export-config=Exporting\nservice.io.export-config.bundle-supporting-resources=Bundle supporting resources into output\nservice.io.export-config.compression=Compression strategy for contents of output\nservice.io.export-config.create-zip-dir-entries=Create ZIP 'directory' entries in output\nservice.io.export-config.warn-no-changes=Warn on exporting without any changes made\nservice.io.gson-provider-config=Json\nservice.io.gson-provider-config.pretty-print=Pretty printing\nservice.io.info-importer-config=Content importing\nservice.io.info-importer-config.class-patch-mode=Class patch mode\nservice.io.recent-workspaces-config=Recent workspaces\nservice.io.recent-workspaces-config.last-workspace-export-path=Last workspace export path\nservice.io.recent-workspaces-config.last-workspace-open-path=Last workspace open path\nservice.io.recent-workspaces-config.max-recent-workspaces=Maximum record of recent paths\nservice.io.recent-workspaces-config.recent-workspaces=Recent workspace\nservice.io.recent-workspaces-config.last-class-export-path=Last class export path\nservice.io.resource-importer-config=Archive importing\nservice.io.resource-importer-config.zip-strategy=ZIP parsing strategy\nservice.io.resource-importer-config.allow-basic-base-offset-zero-check=Default to check 0 as zip beginning with JVM strategy\nservice.io.resource-importer-config.skip-revisited-cen-to-local-links=Skip duplicate CEN-to-LFH entries with JVM strategy\nservice.io.resource-importer-config.ignore-file-lengths=Ignore reported file lengths with Naive/Standard strategy\nservice.io.resource-importer-config.adapt-standard-cen-file-names=Adopt CEN file names with Standard strategy\nservice.io.resource-importer-config.max-embedded-zip-depth=Max embedded zip traversal depth\nservice.io.resource-importer-config.parallelize=Enable multi-core input reading\nservice.mapping=Mapping\nservice.mapping.mapping-aggregator-config=Mapping aggregation\nservice.mapping.mapping-formats-config=Mapping formats\nservice.mapping.mapping-generator-config=Mapping generator\nservice.mapping.name-gen-provider=Name generators\nservice.mapping.name-gen-provider.alphabet=Alphabet\nservice.mapping.name-gen-provider.alphabet.alphabet=Alphabet characters\nservice.mapping.name-gen-provider.alphabet.length=Minimum length\nservice.plugin=Plugins\nservice.plugin.plugin-manager-config=Plugin manager\nservice.plugin.plugin-manager-config.scan-on-start=Load on startup\nservice.plugin.script-manager-config=Script manager\nservice.plugin.script-manager-config.file-watching=Passively scan scripts directory for changes\nservice.transform=Transform\nservice.transform.transformation-applier-config=Transformation Application\nservice.transform.transformation-applier-config.parallelize=Enable multi-core transformer application\nservice.ui=User interface\nservice.ui.bind-config=Bindings\nservice.ui.bind-config.bundle=Binding map bundle\nservice.ui.class-editing-config=Class editing\nservice.ui.class-editing-config.default-android-editor=Default editor for Android classes\nservice.ui.class-editing-config.default-jvm-editor=Default editor for JVM classes\nservice.ui.decompile-pane-config=Decompilation panel\nservice.ui.decompile-pane-config.timeout-seconds=Decompiler timeout (seconds)\nservice.ui.decompile-pane-config.mapping-acceleration=Accelerate remapping operations\nservice.ui.hex-config=Hex editor\nservice.ui.hex-config.row-length=Columns\nservice.ui.hex-config.row-split-interval=Column split interval\nservice.ui.hex-config.show-address=Show address\nservice.ui.hex-config.show-ascii=Show ascii\nservice.ui.member-format-config=Field & method format\nservice.ui.member-format-config.name-type-display=Name & type display\nservice.ui.tab-completion-config=Tab Completion\nservice.ui.text-format-config=Text format\nservice.ui.tab-completion-config.enabled-in-assembler=Enabled in assembler\nservice.ui.tab-completion-config.max-completion-length=Max completion length\nservice.ui.tab-completion-config.max-completion-rows=Displayed completion rows\nservice.ui.tab-completion-config.popup-position=Preferred completion popup position in relation to cursor\nservice.ui.text-format-config.escape=Enable text escapes\nservice.ui.text-format-config.max-length=Maximum text display length\nservice.ui.text-format-config.shorten=Enable text shortening\nservice.ui.file-type-syntax-association-config=File type associations\nservice.ui.file-type-syntax-association-config.extensions-to-langs=Extension to language map\nservice.ui.snippets-config=Snippets\nservice.ui.window-manager-config=Window manager\nservice.ui.window-scale-config=Window scaling\nservice.ui.window-scale-config.scale=Scale\nservice.ui.workspace-explorer-config=Workspace explorer\nservice.ui.workspace-explorer-config.drag-drop-action=Drag & drop behavior\nservice.ui.workspace-explorer-config.max-tree-dir-depth=Max tree depth\nservice.ui.language-config=Language\nservice.ui.language-config.current=Current language\n\n### Matcher translations\nnumber.match.equal=value == n\nnumber.match.not=value != n\nnumber.match.gt=value > n\nnumber.match.gte=value >= n\nnumber.match.lt=value < n\nnumber.match.lte=value <= n\nnumber.match.gt-lt=min < value < max\nnumber.match.gte-lt=min <= value < max\nnumber.match.gt-lte=min < value <= max\nnumber.match.gte-lte=min < value <= max\nnumber.match.any-of=numbers.contains(value)\nstring.match.anything=Anything\nstring.match.zilch=Nothing\nstring.match.contains=str.contains(value)\nstring.match.contains-ic=str.containsIgnoreCase(value)\nstring.match.ends=str.endsWith(value)\nstring.match.ends-ic=str.endsWithIgnoreCase(value)\nstring.match.equal=str.equals(value)\nstring.match.equal-ic=str.equalsIgnoreCase(value)\nstring.match.regex-full=str.matches(value)\nstring.match.regex-partial=str.matchesPartially(value)\nstring.match.starts=str.startsWith(value)\nstring.match.starts-ic=str.startsWithIgnoreCase(value)\n\n### Misc stuff\nmisc.acknowledge=Acknowledge\nmisc.all=All\nmisc.none=None\nmisc.done=Done\nmisc.ignored=Ignored\nmisc.enabled=Enabled\nmisc.disabled=Disabled\nmisc.download=Download\nmisc.download.invalid-url=Invalid URL\nmisc.before=Before\nmisc.after=After\nmisc.load=Load\nmisc.clear=Clear\nmisc.export=Export\nmisc.remove=Remove\nmisc.removed=Removed\nmisc.casesensitive=Case sensitive\nmisc.path=Path\nmisc.regex=Regex\nmisc.member.field=Field\nmisc.member.method=Method\nmisc.member.field-n-method=Field and method\nmisc.member.inner-class=Inner class\nmisc.member.inner-interface=Inner interface\nmisc.member.inner-enum=Inner enum\nmisc.member.inner-annotation=Inner annotation\nmisc.accessflag.visibility.public=Public\nmisc.accessflag.visibility.protected=Protected\nmisc.accessflag.visibility.private=Private\nmisc.accessflag.visibility.package=Package\nmisc.direction.up=Up\nmisc.direction.down=Down\nmisc.direction.left=Left\nmisc.direction.right=Right\nmisc.direction.top=Top\nmisc.direction.bottom=Bottom\nmisc.position.top=Top\nmisc.position.bottom=Bottom\nmisc.position.left=Left\nmisc.position.right=Right\nmisc.position.center=Center\nmisc.position.middle=Middle\n\n### Tutorial messages\nservice.ui.tutorial-config=Tutorial\ntutorial.1.class=Welcome to the Recaf tutorial!\\n\\nFollow along with the comments in this class (and others) to learn about Recaf's features.\ntutorial.1.field=Currently you are looking at decompiled code. There is no 'source code' available, but for a simple class like this the decompiler should give you a near perfect representation of what the source code would have looked like.\\n\\nRecaf has multiple decompilers built in:\\n\\n - CFR (The default)\\n - Procyon\\n - Vineflower (A fork of FernFlower, the decompiler bundled in IntelliJ)\\n\\nYou can switch between these decompilers by clicking the gear button in the bottom right.\ntutorial.1.main=If you are looking at plain Java code (With no obfuscation, no 3rd party languages like Kotlin/Groovy/etc) then you can edit this decompiled code and press [Control + S] to make changes to the class. Recaf will not automatically replace the file on your local machine when you do this, the changes are only within Recaf when you successfully save. To persist any changes you make, you need to go to the 'File' menu and select 'Export Application'.\ntutorial.1.run=This method takes the 'String message' field defined above and prints it.\\n\\nTry changing the string assigned to the field to say something other than 'Hello World' and save your changes.\\n\\nYou should see the borders of this area flash green when you've succeeded. Once this occurs the next chapter will automatically open.\ntutorial.2.class=This class has been modified to hide a secret method. Some methods can be hidden if they are marked as auto-generated by the compiler. This is quite common with code that uses lambda expressions. Additionally, obfuscators can also mark things as auto-generated to hide things.\ntutorial.2.field=There are more things in this class than just this field.\\n\\nIn the top right there is a 'Fields & Methods' tab. Click on it to view the declared fields and methods of this class. You should be able to find the hidden method there.\\nTry double clicking on it!\ntutorial.2.method=Next to the 'Fields & Methods' tab is the 'Inheritance' tab. By default it shows you what classes extend or implement the current class. There is a button on the bottom of the panel which tells you what kind of content is currently shown. You can click on it to toggle between showing parent and children classes. This panel will show you the full inheritance tree, so if you have 'A extends B, B extends C, C extends D' you can see the entire chain in the panel.\\n\\nTry finding the next chapter in the 'Inheritance' panel.\\nRight click on it and select 'Go to class'.\ntutorial.3.class=This class also has been modified to hide a secret value. You can find it by right-clicking this class's tab (above, it it should be underlined blue) and 'Change view' to 'Low level'. This low level view shows more 'low level' details about the class file. It will not abstract details away like other features of Recaf. Take a look around for the secret key and when you find it come back and type it in the 'answer' field below.\ntutorial.3.field=Put the secret text here and save via [Control + S].\\n\\nIf your answer is wrong, this will overwrite the class's contents and erase the key. Oh no!\\n\\nDon't worry though, you can revert to older versions of classes and files via [Control + U]. This will revert a file to the last state it had before you saved, meaning the secret will be back to its original hiding place. This is not the same as [Control + Z] which is just a regular text undo.\ntutorial.3.method=If you need a hint, the secret is not referenced by any executable code. Where would a constant like that be defined?\ntutorial.4.class=No secrets this time. All you need to do is solve this math problem.\\n\\nWhat is the final value passed to the 'consume' method?\ntutorial.4.field=Put the integer value that gets passed to 'consume' here.\ntutorial.4.method=This looks like a lot of work to do manually. Don't worry, Recaf makes this very easy!\\n\\nThere are utilities in Recaf's bytecode assembler which can help. To open the assembler, right click on the name of some declared class, field, or method then select 'Edit > Edit with assembler'. You can also right click on entries in the 'Fields & Methods' tab in the top right.\\n\\nFor this example, right click on the name of this method (where it says \"main\").\\nThen select 'Edit > Edit in assembler'.\\n\\nThe assembler will show you the instructions of this method along with any other relevant metadata. There are some tools at the bottom of the assembler that can be opened by clicking on them. Open the 'Analysis' tool and then click on different lines of code in the method. You should be able to figure out the value that gets passed to 'consume' rather quickly.\ntutorial.5.class=This class is obfuscated! If you try to save using [Control + S] with this decompiled code you will see the editor flash red and errors will appear on the left hand side as overlays on top of the line numbers.\\n\\nYou cannot save when errors are present.\\n\\nIn obfuscated code, you should not be using [Control + S] on decompiled code.\\nInstead, use the assembler and make your changes there. Saving in the assembler allows you to make changes that are not normally allowed in Java source code. Plus, it ensures you are only changing what you really intend to. When you save decompiled code, you may unintentionally save changes made by the decompiler that do not correctly represent how the application logic works.\ntutorial.5.main=In this chapter you will need to open the assembler on the obfuscated method.\\n\\nUse the 'Fields & Methods' tab and right click on the obfuscated method, then open the assembler.\\n\\nThe obfuscated method is currently not implemented properly. The correct implementation can be found in the comment below in the next method.\\n\\nCopy the code in that comment.\\n\\nIn the assembler for the obfuscated method click 'Java to bytecode'. On the left side is an editor which takes in Java source code. On the right side is an editor which will show you the equivalent Java bytecode. Paste the code you have copied into the left hand side (replacing the existing code in there, and removing the '*' before each line).\\n\\nWhen done correctly you should see the right side display a method that contains the bytecode generated from compiling the code on the left side. You can copy the instructions in this generated method and paste it into the assembler above. To be clear, you are only copying the instructions, not the whole generated method. If you have correctly copied the decryption logic and saved the changes in the assembler the next chapter will be revealed.\ntutorial.5.decrypt=int key = Thread.currentThread().getStackTrace()[1].getMethodName().hashCode();\\nchar[] chars = text.toCharArray();\\nfor (int i = 0; i < chars.length; i++) chars[i] ^= key;\\nreturn String.valueOf(chars);\ntutorial.5.finished=Right click on this method's return type \"Chapter6\" and select 'Go to class'.\\n\\nYou can alternatively use [Control + Click] on references to avoid having to go through the context menu every time.\ntutorial.6.class=When you right click on a class, field, or method you can use see what other code references the thing you've selected.\\n\\n - Classes: Search > Type/member references\\n - Fields: Field references\\n - Methods: Method references\\n\\nYou can find these search options in the menu bar's 'Search' menu at the top of the UI, along with other things like searching for Strings and number literals.\ntutorial.6.method=Try searching for what references this method.\ntutorial.7.class=Congrats, this is the end of the tutorial!\\n\\nPress [Control + S] one last time to mark the tutorial as completed.\\n\\nMore will be added to the tutorial later, but this should cover all of the most important features for your average use case. In the meantime, feel free to explore other features on your own!"
  },
  {
    "path": "recaf-ui/src/main/resources/translations/ja_JP.lang",
    "content": "## Language name\nlang.name=日本語\n\n##### General items\n## Main and context menus\nmenu.analysis=解析\nmenu.analysis.summary=概要\nmenu.analysis.deobfuscation=難読化を解除\nmenu.analysis.list-comments=コメントの一覧表示\nmenu.analysis.comment=コメント\nmenu.association.override=言語をオーバライド\nmenu.association.none=構成の割り当てなし\nmenu.config=設定\nmenu.config.edit=編集\nmenu.config.export=エクスポート\nmenu.config.import=インポート\nmenu.file=ファイル\nmenu.file.attach=リモートにアタッチ\nmenu.file.addtoworkspace=ワークスペースに追加\nmenu.file.decompileall=全てのクラスを逆コンパイル\nmenu.file.decompileall.path=出力パス:\nmenu.file.openworkspace=ワークスペースを開く\nmenu.file.openurl=URL から開く\nmenu.file.exportapp=アプリケーションをエクスポート\nmenu.file.exportworkspace=ワークスペースの設定をエクスポート\nmenu.file.modifications=変更を表示\nmenu.file.recent=最近のファイル\nmenu.file.close=ファイルを閉じる\nmenu.file.quit=終了\nmenu.goto.class=クラスに移動\nmenu.goto.field=フィールドに移動\nmenu.goto.method=メソッドに移動\nmenu.goto.instruction=命令に移動\nmenu.goto.file=ファイルに移動\nmenu.goto.label=ラベルに移動\nmenu.edit=編集\nmenu.edit.add.field=フィールドを追加\nmenu.edit.add.method=メソッドを追加\nmenu.edit.add.annotation=アノテーションを追加\nmenu.edit.override.method=メソッドをオーバライド\nmenu.edit.remove.field=フィールドを削除\nmenu.edit.remove.method=メソッドを削除\nmenu.edit.remove.annotation=アノテーションを削除\nmenu.edit.assemble.class=アセンブラでクラスを編集\nmenu.edit.assemble.field=アセンブラでフィールドを編集\nmenu.edit.assemble.method=アセンブラでメソッドを編集\nmenu.edit.remove=削除\nmenu.edit.copy=コピー\nmenu.edit.delete=削除\nmenu.edit.noop=No-OP にする\nmenu.edit.changeversion=クラスのバージョンを変更\nmenu.edit.changeversion.up=アップグレード\nmenu.edit.changeversion.down=ダウングレード\nmenu.export.class=クラスをエクスポート\nmenu.export.file=ファイルをエクスポート\nmenu.export.classes=すべてのクラスをエクスポート\nmenu.export.files=すべてのファイルをエクスポート\nmenu.export.package=パッケージをエクスポート\nmenu.export.directory=ディレクトリをエクスポート\nmenu.help=ヘルプ\nmenu.help.discord=Discord\nmenu.help.docs=オンラインのユーザ向けドキュメント\nmenu.help.docsdev=オンラインの開発者向けドキュメント\nmenu.help.github=Github\nmenu.help.issues=Issue トラッカー\nmenu.help.sysinfo=システム情報\nmenu.refactor=リファクタリング\nmenu.refactor.move=移動\nmenu.refactor.rename=名前の変更\nmenu.search=検索\nmenu.search.string=文字列\nmenu.search.number=数値\nmenu.search.class.member-declarations=メンバの定義\nmenu.search.class.member-references=メンバの参照\nmenu.search.class.type-references=型の参照\nmenu.search.method-overrides=メソッドのオーバライド\nmenu.search.method-references=参照を表示\nmenu.search.field-references=フィールドの参照\nmenu.search.noresults=結果がありませんでした\nmenu.mappings=マッピング\nmenu.mappings.apply=適用\nmenu.mappings.export=エクスポート\nmenu.mappings.export.unsupported=%s (サポートされていません)\nmenu.mappings.generate=生成\nmenu.mappings.view=現在のマッピング\nmenu.scripting=スクリプト\nmenu.scripting.list=スクリプトを実行\nmenu.scripting.none-found=スクリプトがありません\nmenu.scripting.manage=スクリプトを管理\nmenu.scripting.new=新規作成\nmenu.scripting.edit=編集\nmenu.scripting.browse=参照\nmenu.scripting.save=保存\nmenu.scripting.execute=実行\nmenu.scripting.editor=スクリプト・エディタ\nmenu.scripting.author=作成者\nmenu.scripting.version=バージョン\nmenu.view=表示\nmenu.view.hierarchy=クラスの階層\nmenu.view.hierarchy.children=子\nmenu.view.hierarchy.parents=親\nmenu.view.methodcfg=制御フローのグラフ\nmenu.view.methodcallgraph=呼び出しグラフ\nmenu.view.methodcallgraph.calls=呼び出し先\nmenu.view.methodcallgraph.callers=呼び出し元\nmenu.view.methodcallgraph.focus=メソッドにフォーカス\nmenu.tab.close=現在のタブを閉じる\nmenu.tab.closeothers=このタブ以外を閉じる\nmenu.tab.closeall=すべてのタブを閉じる\nmenu.tab.copypath=パスをコピー\nmenu.image.resetscale=拡大・縮小をリセット\nmenu.image.center=中央に拡大\nmenu.hex.copyas=コピー\nmenu.mode=表示を変更\nmenu.mode.class.auto=自動\nmenu.mode.class.decompile=逆コンパイル\nmenu.mode.file.auto=自動\nmenu.mode.file.text=テキスト\nmenu.mode.file.hex=１６進数\nmenu.mode.diff.decompile=逆コンパイル\nmenu.mode.diff.disassemble=逆アセンブル\nmenu.vm=仮想化\nmenu.vm.optimize=最適化\nmenu.vm.run=実行\nmenu.plugin=プラグイン\nmenu.plugin.manage=プラグインを管理\nmenu.plugin.installed=インストール済み\nmenu.plugin.remote=リモート\nmenu.plugin.browse=プラグインを閲覧\nmenu.plugin.enabled=有効\nmenu.plugin.uninstall=アンインストール\nmenu.plugin.uninstall.warning=本当にこのプラグインをアンインストールしますか？\n\n##### Keybinds\nbind.inputprompt.initial=<入力待ち>\nbind.inputprompt.finish=<エンタ・キーで確定>\nbind.editor.find=探す\nbind.editor.goto=移動\nbind.editor.rename=名前の変更\nbind.editor.replace=置換\nbind.editor.save=保存\nbind.editor.undo=もとに戻す\nbind.editor.closetab=現在のタブを閉じる\nbind.quicknav=クイック・ナビゲーション\nbind.workspace.export=ワークスペースのエクスポート\n\n##### Dialog texts\ndialog.cancel=キャンセル\ndialog.close=閉じる\ndialog.confirm=確認\ndialog.finish=完了\ndialog.next=次へ\ndialog.previous=前へ\ndialog.dismiss=閉じる\ndialog.configure=設定\ndialog.warning=警告\ndialog.restart=この設定を有効化するには Recaf を再起動する必要があります。\\n本当に適用しますか？\ndialog.unknownextension=不明な拡張子です。ファイルの種類の割り当てを変更しますか？\n\n## Search\ndialog.search.type=型の名前\ndialog.search.member-owner=メンバの所有者の名前\ndialog.search.member-name=メンバの名前\ndialog.search.member-descriptor=メンバのディスクリプタ\n\n## File chooser\ndialog.title.primary=プライマリ・リソース\ndialog.title.supporting=任意のリソース\ndialog.title.nochanges=変更がない状態でのエクスポート\ndialog.file.primary=プライマリ\ndialog.file.open=開く\ndialog.file.open.directory=ディレクトリ\ndialog.file.open.file=ファイル\ndialog.file.export=エクスポート\ndialog.file.save=保存\ndialog.file.nothing=何も選択されていません\ndialog.file.nochanges=何も変更がありませんが，本当にエクスポートしますか？\ndialog.filefilter.any=すべての種類\ndialog.filefilter.mapping=マッピング\ndialog.filefilter.input=アプリケーション\ndialog.filefilter.workspace=ワークスペース\n\n## File drop items\ndialog.title.create-workspace=ワークスペースの作成\ndialog.title.update-workspace=ワークスペースの更新\ndialog.title.close-workspace=ワークスペースを閉じますか？\ndialog.option.create-workspace=新規作成\ndialog.option.update-workspace=ワークスペースに追加\n\n## Select class/file\ndialog.title.select-class=クラスを選択\ndialog.title.select-file=ファイルを選択\n\n## Copy class/file\ndialog.title.copy-class=クラスをコピー\ndialog.title.copy-directory=ディレクトリをコピー\ndialog.title.copy-package=パッケージをコピー\ndialog.title.copy-field=フィールドをコピー\ndialog.title.copy-file=ファイルをコピー\ndialog.title.copy-method=メソッドをコピー\ndialog.header.copy-class=コピーしたクラスに新しい名前を付けてください。\ndialog.header.copy-directory=コピーしたディレクトリに新しい名前を付けてください。\ndialog.header.copy-package=コピーしたパッケージに新しい名前を付けてください。\ndialog.header.copy-field=コピーしたフィールドに新しい名前を付けてください。\ndialog.header.copy-field-error=同じ名前のフィールドが既に存在します。\\n別の名前を選択してください。\ndialog.header.copy-file=コピーしたファイルに新しい名前を付けてください。\ndialog.header.copy-method=コピーしたメソッドに新しい名前を付けてください。\ndialog.header.copy-method-error=同じ名前のメソッドが既に存在します。\\n別の名前を選択してください。\n\n## Delete class/file\ndialog.title.delete-class=クラスを削除\ndialog.title.delete-directory=ディレクトリを削除\ndialog.title.delete-field=フィールドを削除\ndialog.title.delete-file=ファイルを削除\ndialog.title.delete-method=メソッドを削除\ndialog.title.delete-package=パッケージを削除\ndialog.title.delete-resource=リソースを削除\ndialog.header.delete-class=クラス %s を本当に削除しますか？\ndialog.header.delete-directory=ディレクトリ %s を本当に削除しますか？\ndialog.header.delete-field=フィールド %s を本当に削除しますか？\ndialog.header.delete-file=ファイル %s を本当に削除しますか？\ndialog.header.delete-method=メソッド %s を本当に削除しますか？\ndialog.header.delete-package=パッケージ %s を本当に削除しますか？\ndialog.header.delete-resource=本当にリソース %s を本当に削除しますか？\n\n## Rename class/file\ndialog.title.rename-class=クラスの名前を変更\ndialog.title.rename-class-warning=警告\ndialog.title.rename-directory=ディレクトリの名前を変更\ndialog.title.rename-field=フィールドの名前を変更\ndialog.title.rename-file=ファイルの名前を変更\ndialog.title.rename-file-warning=警告\ndialog.title.rename-method=メソッドの名前を変更\ndialog.title.rename-package=パッケージの名前を変更\ndialog.header.rename-class=クラスの新しい名前を入力してください。\ndialog.header.rename-class-error=その名前のクラスは既に存在します。\\n別の名前を選択してください。\ndialog.header.rename-package=パッケージの新しい名前を入力してください。\ndialog.header.rename-package-error=その名前のパッケージは既に存在します。\\n別の名前を選択してください。\ndialog.header.rename-package-warning=その名前のパッケージは既に存在します。\\n一部のファイルを上書きする可能性があります。\ndialog.header.rename-directory=ディレクトリの新しい名前を入力してください。\ndialog.header.rename-directory-error=その名前のディレクトリは既に存在します。\\n別の名前を選択してください。\ndialog.header.rename-directory-warning=その名前のディレクトリは既に存在します。\\n一部のファイルを上書きする可能性があります。\ndialog.header.rename-field=フィールドの新しい名前を入力してください。\ndialog.header.rename-field-error=その名前のフィールドは既に存在します。\\n別の名前を選択してください。\ndialog.header.rename-file=ファイルの新しい名前を入力してください。\ndialog.header.rename-file-error=その名前のファイルは既に存在します。\\n別の名前を選択してください。\ndialog.header.rename-method=メソッドの新しい名前を入力してください。\ndialog.header.rename-method-error=その名前のメソッドは既に存在します。\\n別の名前を選択してください。\n\n## Move class/file\ndialog.title.move-class=移動先のパッケージを選択\ndialog.title.move-directory=移動先の親ディレクトリを選択\ndialog.title.move-file=移動先のディレクトリを選択\ndialog.title.move-package=移動先の親パッケージを選択\ndialog.header.move-class=クラスを別のパッケージに移動します。\ndialog.header.move-directory=ディレクトリを別の親ディレクトリに移動します。\ndialog.header.move-file=ファイルを別のディレクトリに移動します。\ndialog.header.move-package=パッケージを別の親パッケージに移動します。\n\n## Add members\ndialog.title.add-field=フィールドを追加\ndialog.title.add-method=メソッドを追加\ndialog.title.override-method=メソッドをオーバライド\ndialog.input.name=名前\ndialog.input.desc=ディスクリプタ\ndialog.warn.illegal-name==不正な名前\ndialog.warn.illegal-desc=不正なディスクリプタ\ndialog.warn.field-conflict=その名前のフィールドは既に存在します。\\n別の名前を選択してください。\ndialog.warn.method-conflict=その名前のメソッドは既に存在します。\\n別の名前を選択してください。\n\n## VM actions\ndialog.title.vm-invoke-args=メソッド呼び出しの仮想化\ndialog.title.vm-peephole-invoke-args=仮想化された詳細な最適化\ndialog.vm.execute=実行\ndialog.vm.optimize=最適化\ndialog.vm.create-dummy=ダミーを使う\ndialog.vm.create-null=null を使う\n\n## Hex dialogs\ndialog.hex.title.insertcount=挿入\ndialog.hex.header.insertcount=挿入するバイト数を入力してください\n\n# Base Converter dialog\ndialog.conv.title.literal=数値リテラル\ndialog.conv.title.expression=数値の式\n\n## Quick nav\ndialog.quicknav=クイック・ナビゲーション\ndialog.quicknav.tab.classes=クラス\ndialog.quicknav.tab.members=メンバ\ndialog.quicknav.tab.files=ファイル\ndialog.quicknav.tab.text=文字列\ndialog.quicknav.tab.commented=コメント\n\n## Error dialog\ndialog.error.exportclass.title=クラスのエクスポートに失敗しました\ndialog.error.exportclass.header=エクスポート先への書き込み中にエラーが発生しました\ndialog.error.exportclass.content=エラーの内容:\ndialog.error.exportfile.title=ファイルのエクスポートに失敗しました\ndialog.error.exportfile.header=エクスポート先への書き込み中にエラーが発生しました\ndialog.error.exportfile.content=エラーの内容:\ndialog.error.exportworkspace.title=ワークスペースのエクスポートに失敗しました\ndialog.error.exportworkspace.header=エクスポート先への書き込み中にエラーが発生しました\ndialog.error.exportworkspace.content=エラーの内容:\ndialog.error.loadworkspace.title=ワークスペースの読み込みに失敗しました\ndialog.error.loadworkspace.header=選択したファイルの読み込み中にエラーが発生しました\ndialog.error.loadworkspace.content=エラーの内容:\ndialog.error.loadsupport.title=サポートファイルの読み込みに失敗しました\ndialog.error.loadsupport.header=選択したサポートファイルの読み込み中にエラーが発生しました\ndialog.error.loadsupport.content=エラーの内容:\ndialog.error.attach.title=リモート JVM への接続に失敗しました\ndialog.error.attach.header=リモート JVM への接続中にエラーが発生しました\ndialog.error.attach.content=エラーの内容:\n\n##### Panels\n## Welcome\nwelcome.title=ようこそ\n\n## Workspace\nworkspace.title=ワークスペース\nworkspace.filter-prompt=フィルタ: クラスやファイルの名前を入力してください…\nworkspace.info=情報\nworkspace.info-progress=ワークスペースの内容を解析しています…\n\n## Attach\nattach.unsupported=アタッチの初期化に失敗しました。\nattach.unsupported.detail=アタッチ・エージェントの自己展開に失敗しました。\nattach.no-vms=アタッチ可能な JVM が見つかりません\nattach.no-vms.detail=現在アタッチが可能な Java Virtual Machine はありません。\nattach.problem.disable-attach=-XX:+DisableAttachMechanism オプションが有効になっている Java プロセスにアタッチしようとしている場合は，以下を参照してください:\nattach.problem.java-21=Java 21 以降を使用しているプロセスにアタッチしようとしている場合は，以下を参照してください:\nattach.connect=接続\nattach.tab.properties=プロパティ\nattach.tab.classloading=クラス\nattach.tab.compilation=コンパイル情報\nattach.tab.system=システム\nattach.tab.runtime=ランタイム\nattach.tab.thread=スレッド\n\n## Changes view\nmodifications.none=アイテムの変更履歴がありません\nmodifications.title=変更の一覧\n\n## Java area\njava.decompiling=クラスを逆コンパイルしています…\njava.unparsable=SourceSolver はソース・コードの解釈に失敗しました。コンテキスト・アクションは「フィールドとメソッド」細部タブでのみ確認できます。\njava.parse-state.error=解析エラー\njava.parse-state.error-details=解析に失敗したため，右クリックのコンテキスト・アクションは使用できません。\\n代わりに「フィールドとメソッド」細部タブを確認できます。\njava.parse-state.initial=解析しています…\njava.parse-state.initial-details=解析をしている間は，右クリックのコンテキスト・アクションを使用できません。\\nその間は代わりに「フィールドとメソッド」細部タブを確認できます。\njava.parse-state.new-progress=再解析中…\njava.parse-state.new-progress-details=変更を検出したため，再解析を行っています。\\n新しいモデルが構築されている間は，古いモデルを使用できます。\\nその間は代わりに「フィールドとメソッド」細部タブを確認できます。\njava.parse-state.none=解析する内容がありません\njava.decompile-failure=クラスの逆コンパイルに失敗しました。次のいずれかの操作お試しください：\\n- 逆コンパイラを切り替える\\n- アセンブラまたは別のビューでクラスを開く\\n- クラスの難読化を解除して再試行する\njava.decompile-failure.brief=クラスの逆コンパイルに失敗しました。\njava.savewitherrors=エラーが発生する変更が初めて検出されました。\\nこれらは通常，逆コンパイルされたコードが正しい Java ソース・コードではないことが原因です。\\n変更を保存する前に，これらのエラーを解決しなければなりません。\\n\\n次の操作をお試しください：\\n- 「設定」または画面右下の (i) で逆コンパイラを変更する。\\n- 赤いエラー・ボックスにマウスを合わせるか，画面上部のエラー・ボックスをクリックして，エラーの内容を確認する。\\nクラスを変更するには，再コンパイルではなくアセンブラを使用します。\njava.savewitherrors.title=再コンパイルのエラー\njava.decompiler=逆コンパイラ\njava.targetversion=ターゲット・バージョン\njava.targetversion.auto=クラス・ファイルに合わせる\njava.targetdownsampleversion=後方互換バージョン\njava.targetdownsampleversion.disabled=無効\njava.targetdebug=デバッグ情報を含めてコンパイル\njava.info=クラス情報\njava.info.version=クラスのバージョン\njava.info.sourcefile=ソース・ファイル\n\n## Search bar\nfind.replace=置換\nfind.replaceall=すべて置換\nfind.regexinvalid=無効な正規表現です。\nfind.regexreplace=置換するテキスト\n\n## Fields and methods\nfieldsandmethods.title=フィールドとメソッド\nfieldsandmethods.empty=クラスにメンバがありません\nfieldsandmethods.showoutlinedsynths=コンパイラによって生成されたメンバを表示\nfieldsandmethods.showoutlinedvisibility=メンバの可視性でフィルタする\nfieldsandmethods.showoutlinedmembertype=メンバの種類でフィルタする\nfieldsandmethods.sortalphabetically=アルファベット順でソート\nfieldsandmethods.sortbyvisibility=可視性順でソート\nfieldsandmethods.filter.prompt=フィルタ: メンバの名前や型を入力してください…\n\n## Hierarchy\nhierarchy.title=継承\nhierarchy.children=子\nhierarchy.parents=親\n\n## Kotlin Metadata\nkotlinmetadata.title=@メタデータ\nkotlinmetadata.orderwarning=重要：各項目はクラスファイルで定義された順序通りには表示されません。\n\n## Logging\nlogging.title=ログ\n\n## Assembler\nassembler.problem.0=エラーなし\nassembler.problem.1=１つのエラー\nassembler.problem.N=N 個のエラー\nassembler.title=アセンブラ\nassembler.analysis.title=解析\nassembler.analysis.stack=スタック\nassembler.analysis.variables=変数\nassembler.analysis.type=型\nassembler.analysis.value=値\nassembler.playground.title=バイト・コードに変換\nassembler.playground.comment=// ここに Java ソース・コードを入力して，自動的にバイト・コードに変換します。\\n// 現在のクラスのフィールドやメソッド，および現在のメソッドの引数や変数にアクセスできます。\nassembler.snippets.title=スニペット\nassembler.variables.title=定義された変数\nassembler.variables.name=変数の名前\nassembler.variables.type=型\nassembler.variables.usage=使用箇所\nassembler.variables.value=値\nassembler.variables.empty=<少なくとも１度はコンパイルしてください>\nassembler.variables.read-before-write=書き込む前に読み込む必要があります。\nassembler.suggestions.none=提案はありません\n\n## Comments\ncomments.search.prompt=コメントを検索…\n\n## Search\nsearch.run=検索\nsearch.results=結果\nsearch.text=テキストの出現箇所\nsearch.textmode=テキストの検索モード\nsearch.number=数値\nsearch.numbermode=数値の検索モード\nsearch.refowner=メンバの所有者\nsearch.refname=メンバの名前\nsearch.refdesc=メンバのディスクリプタ\n\n## Help\nhelp.system=システム\nhelp.system.sub=OS についての情報です。\nhelp.java=Java\nhelp.java.sub=JVM についての情報です。\nhelp.javafx=JavaFX\nhelp.javafx.sub=JavaFX UI についての情報です。\nhelp.recaf=Recaf\nhelp.recaf.sub=Recaf についての情報です。\nhelp.copy=情報をクリップ・ボードにコピー\nhelp.opendir=Recaf のインストール先を開く\n\n## Deobfuscation\ndeobf=難読化の解除\ndeobf.selection.title=トランスフォーマを選択\ndeobf.order.title=適用するトランスフォーマの順序\ndeobf.order.hint=左側のトランスフォーマを選択してください。\\nここにドラックして適用する順序を変更します。\ndeobf.order.pre=推奨されている先行タスクを追加してください\ndeobf.order.suc=後=推奨されている後続タスクを追加してください\ndeobf.preview.title=プレビュー\ndeobf.preview.pick=プレビューをするクラスを選択\ndeobf.preview.toggle-mode=プレビュー・モードを切り替え\ndeobf.preview.noselection=// クラスが選択されていません。プレビューをするクラスを選択してください。\\n// 変換前後の状態\ndeobf.tree.generic=一般\ndeobf.tree.generic.anticrasher=クラッシャー対策\ndeobf.tree.generic.optimize=最適化\ndeobf.tree.generic.restoration=復元\ndeobf.tree.specific=詳細な操作\ndeobf.apply=ワークスペースに適用\n\n## Mapping generator\nmapgen=マッピングの生成\nmapgen.genimpl=命名規則\nmapgen.filter.name=名前\nmapgen.filter.class-name=クラス名\nmapgen.filter.owner-name=所有者名\nmapgen.filter.field-name=フィールド名\nmapgen.filter.method-name=メソッド名\nmapgen.filter.variable-name=変数名\nmapgen.filters=フィルタ\nmapgen.filters.add=フィルタを追加\nmapgen.filters.edit=選択したフィルタを編集\nmapgen.filters.delete=選択したフィルタを削除\nmapgen.filters.type=フィルタ…\nmapgen.filter.modifiers.tooltip=修飾子は空白で区切ります。\nmapgen.filter.excludealreadymapped=すでにマッピングされているものを除外\nmapgen.filter.excludemodifier=修飾子を除外する\nmapgen.filter.excludeclasses=クラスを除外する\nmapgen.filter.excludename=名前を除外する\nmapgen.filter.excludeclass=クラスで除外\nmapgen.filter.excludefield=フィールドで除外\nmapgen.filter.excludemethod=メソッドで除外\nmapgen.filter.includemodifier=修飾子を含める\nmapgen.filter.includeclass=クラスを含める\nmapgen.filter.includefield=フィールドを含める\nmapgen.filter.includemethod=メソッドを含める\nmapgen.filter.includevariable=変数を含める\nmapgen.filter.includewhitespacenames=空白の名前を含める\nmapgen.filter.includenonasciinames=非 ASCII の名前を含める\nmapgen.filter.includekeywords=キーワードを含める\nmapgen.filter.includelong=長い名前を含める\nmapgen.filter.includename=名前を含める\nmapgen.filter.includeclasses=クラスを含める\nmapgen.title.newfilter=新しいフィルタ\nmapgen.header.newfilter=新しい入力内容\nmapgen.preview.empty=生成されたマッピングの統計がここに表示されます\\n\\n\\n\nmapgen.configure=構成\nmapgen.configure.nothing=構成するものは何もありません\nmapgen.generate=生成\nmapgen.apply=適用\n\n## Mapping view\nmapprog=マッピング進行度\nmapprog.metric.size=クラス・ファイルの数\nmapprog.metric.membercount=フィールドとメソッドの数\n\n##### Tree\ntree.classes=クラス\ntree.files=ファイル\ntree.defaultpackage=(デフォルト・パッケージ)\ntree.defaultdirectory=(ルート・ディレクトリ)\ntree.prompt=ファイルをドラッグ＆ドロップしてください\ntree.hidelibs=ライブラリを非表示\ntree.phantoms=生成されたファントム\ntree.embedded-resources=埋め込まれています\n\n##### Services\nservice=サービス\nservice.analysis=解析\nservice.analysis.comments-config=コメント\nservice.analysis.comments-config.enable-display=デコンパイル結果にコメントを表示する\nservice.analysis.comments-config.word-wrapping-limit=折り返しの文字数制限\nservice.analysis.info-summary-config=ワークスペースの概要\nservice.analysis.info-summary-config.summarize-on-open=ワークスペースを開いたときに概要を表示する\nservice.analysis.graph-calls-config=呼び出しグラフ\nservice.analysis.graph-inheritance-config=継承グラフ\nservice.analysis.jphantom-generator-config=JPhantom\nservice.analysis.jphantom-generator-config.generate-workspace-phantoms=ファントムを生成してワークスペースに追加する\nservice.analysis.search-config=検索\nservice.analysis.entry-points=エントリ・ポイント\nservice.analysis.entry-points.none=エントリ・ポイントがみつかりません\nservice.analysis.anti-decompile=逆コンパイル対策\nservice.analysis.anti-decompile.illegal-attr=不正な属性\nservice.analysis.anti-decompile.illegal-name=不正な名前\nservice.analysis.anti-decompile.label-patch=影響を受ける %d クラスにパッチを適用する\nservice.analysis.signature-info=シグニチャ情報\nservice.assembler=アセンブラ\nservice.assembler.assembler-pipeline.general-config=一般\nservice.assembler.assembler-pipeline.general-config.disassembly-ast-parse-delay=AST 解析の遅延\nservice.assembler.assembler-pipeline.general-config.disassembly-indent=インデント\nservice.assembler.assembler-pipeline.general-config.disassembly-whole-floating=浮動小数点数の標準表記\nservice.assembler.dalvik-assembler-config=Dalvik\nservice.assembler.dalvik-assembler-config.value-analysis=値の解析を有効化する\nservice.assembler.dalvik-assembler-config.simulate-jvm-calls=一般的な JVM の呼び出しをシュミレーション\nservice.assembler.jvm-assembler-config=JVM\nservice.assembler.jvm-assembler-config.value-analysis=値の解析を有効化する\nservice.assembler.jvm-assembler-config.simulate-jvm-calls=一般的な JVM の呼び出しをシュミレーション\nservice.assembler.jvm-assembler-config.try-range-comments=try-catch の範囲をコメントで表示する\nservice.assembler.flow-lines-config=制御フローライン\nservice.assembler.flow-lines-config.connection-mode=接続モード\nservice.assembler.flow-lines-config.render-mode=描画モード\nservice.compile=コンパイル\nservice.compile.java-compiler-config=javac\nservice.compile.java-compiler-config.generate-phantoms=欠落しているクラスを生成する\nservice.compile.java-compiler-config.default-emit-debug=デバッグ情報をデフォルトで含める\nservice.compile.java-compiler-config.default-compile-target-version=デフォルトのターゲット・クラス・バージョン\nservice.compile.java-compiler-config.default-downsample-target-version=デフォルトの後方互換・クラス・バージョン\nservice.debug=アタッチ/デバッグ\nservice.debug.attach-config=アタッチの設定\nservice.debug.attach-config.attach-jmx-bean-agent=JMX Bean エージェントをアタッチする\nservice.debug.attach-config.passive-scanning=パッシブ・スキャンを有効化する\nservice.config-manager-config=設定管理\nservice.decompile=逆コンパイル\nservice.decompile.decompilers-config=逆コンパイルの管理\nservice.decompile.decompilers-config.pref-android-decompiler=アンドロイド用に使う逆コンパイラ\nservice.decompile.decompilers-config.pref-jvm-decompiler=Java クラス用に使う逆コンパイラ\nservice.decompile.decompilers-config.cache-decompilations=逆コンパイル結果をキャッシュする\nservice.decompile.decompilers-config.filter-annotations-duplicate=重複したアノテーションを表示しない\nservice.decompile.decompilers-config.filter-annotations-illegal=不正なアノテーションを表示しない\nservice.decompile.decompilers-config.filter-annotations-long=長いアノテーションを表示しない\nservice.decompile.decompilers-config.filter-annotations-long-limit=アノテーションの文字数制限\nservice.decompile.decompilers-config.filter-hollow=空のクラスを表示しない\nservice.decompile.decompilers-config.filter-illegal-signatures=間違ったシグニチャを持つメンバを表示しない\nservice.decompile.decompilers-config.filter-names-ascii=非 ASCII の名前を表示しない\nservice.decompile.decompilers-config.filter-strip-debug=変数やジェネリクスなどのデバッグ情報を表示しない\nservice.decompile.impl=逆コンパイラ\nservice.decompile.impl.decompiler-cfr-config=CFR\nservice.decompile.impl.decompiler-cfr-config.aexagg=可能な限り例外を拡張して統合する\nservice.decompile.impl.decompiler-cfr-config.aexagg2=可能な限り例外を拡張して統合する（セマンティクスが変わる可能性があります）\nservice.decompile.impl.decompiler-cfr-config.aggressivedocopy=不可能なジャンプからループへのコードを最初にクローンする\nservice.decompile.impl.decompiler-cfr-config.aggressivedoextension=不可能なジャンプを最初に do ループで畳む\nservice.decompile.impl.decompiler-cfr-config.aggressiveduff=Duff's Device 風 switch 文を追加制御込みで折りたたむ\nservice.decompile.impl.decompiler-cfr-config.aggressivesizethreshold=簡略化を積極的に行う命令数のしきい値\nservice.decompile.impl.decompiler-cfr-config.allowmalformedswitch=不正な形式の switch 文を許可する\nservice.decompile.impl.decompiler-cfr-config.antiobf=さまざまな難読化を解除する\nservice.decompile.impl.decompiler-cfr-config.arrayiter=配列ループを読みやすくする\nservice.decompile.impl.decompiler-cfr-config.collectioniter=コレクションのループを読みやすくする\nservice.decompile.impl.decompiler-cfr-config.commentmonitors=モニタをコメントに置換する - 全く分からない場合に便利です\nservice.decompile.impl.decompiler-cfr-config.comments=逆コンパイラの状態やフォールバックフラグ等をコメントで表示する\nservice.decompile.impl.decompiler-cfr-config.constobf=定数の難読化を解除する\nservice.decompile.impl.decompiler-cfr-config.decodeenumswitch=列挙型の switch 文を読みやすくする\nservice.decompile.impl.decompiler-cfr-config.decodefinally=finally ブロックを読みやすくする\nservice.decompile.impl.decompiler-cfr-config.decodelambdas=ラムダ式を読みやすくする\nservice.decompile.impl.decompiler-cfr-config.decodestringswitch=文字列の switch 文を読みやすくする\nservice.decompile.impl.decompiler-cfr-config.eclipse=Eclipse 用の適切な処理の変換を有効化する\nservice.decompile.impl.decompiler-cfr-config.elidescala=Scala が生成した無意味な出力を表示しない\nservice.decompile.impl.decompiler-cfr-config.forbidanonymousclasses=匿名クラスを許可しない\nservice.decompile.impl.decompiler-cfr-config.forbidmethodscopedclasses=メソッド内のクラスを許可しない\nservice.decompile.impl.decompiler-cfr-config.forceclassfilever=逆コンパイルされるクラス・ファイルのバージョンを強制する\nservice.decompile.impl.decompiler-cfr-config.forcecondpropagate=決定論的ジャンプの結果を定数割り当てに強制する\nservice.decompile.impl.decompiler-cfr-config.forceexceptionprune=セマンティクスが変わらない場合に，ネストされた try-catch を削除する\nservice.decompile.impl.decompiler-cfr-config.forcereturningifs=if 文のジャンプ地点まで強制的に移動する\nservice.decompile.impl.decompiler-cfr-config.forcetopsort=基本ブロック・ソートを強制する - 通常は難読化が行われている場合にのみ有効です。\nservice.decompile.impl.decompiler-cfr-config.forcetopsortaggress=非常に積極的に基本ブロック・ソートを強制する\nservice.decompile.impl.decompiler-cfr-config.forcetopsortnopull=トップ・ソートに try ブロックを使わないように矯正する\nservice.decompile.impl.decompiler-cfr-config.forloopaggcapture=ブロックの中身に関係していないように見えたとしても，ループ内で積極的に変数を更新することを強制する\nservice.decompile.impl.decompiler-cfr-config.hidebridgemethods=ブリッジしているメソッドを非表示にする\nservice.decompile.impl.decompiler-cfr-config.hidelangimports=java.lang.* のインポートを非表示にする\nservice.decompile.impl.decompiler-cfr-config.hidelongstrings=非常に長い文字列を非表示にする - 難読化ツールが文字列に偽のコードを埋め込んだ場合に便利です。\nservice.decompile.impl.decompiler-cfr-config.hideutf=UTF-8 文字を非表示にする - 生の文字を表示する代わりに引用符で囲みます。\nservice.decompile.impl.decompiler-cfr-config.ignoreexceptions=完全に無視される例外を非表示にする - 警告：セマンティクスが変わるため危険です！\nservice.decompile.impl.decompiler-cfr-config.ignoreexceptionsalways=例外情報を完全に無視する - 警告：セマンティクスが変わるため危険です！\nservice.decompile.impl.decompiler-cfr-config.innerclasses=内部クラスを逆コンパイルする\nservice.decompile.impl.decompiler-cfr-config.instanceofpattern=instanfeof パターンを再構築する\nservice.decompile.impl.decompiler-cfr-config.j14classobj=java 1.4 のクラス・オブジェクトを再構築する\nservice.decompile.impl.decompiler-cfr-config.labelledblocks=ラベル付きブロック（不明瞭な goto 文など）の生成を許可する\nservice.decompile.impl.decompiler-cfr-config.lenient=例外を通常スローするような状況を許容する\nservice.decompile.impl.decompiler-cfr-config.liftconstructorinit=すべてのコンストラクタに共通する変数の初期化を，メンバの初期化に集約する\nservice.decompile.impl.decompiler-cfr-config.obfattr=属性の難読化を解除する\nservice.decompile.impl.decompiler-cfr-config.obfcontrol=制御フローの難読化を解除する\nservice.decompile.impl.decompiler-cfr-config.override=適切な @Override アノテーションを自動で付与する\nservice.decompile.impl.decompiler-cfr-config.previewfeatures=クラスが 'javac --enable-preview' でコンパイルされている場合に，プレビュー機能を逆コンパイルする\nservice.decompile.impl.decompiler-cfr-config.pullcodecase=積極的にコードを case 文に引き出す\nservice.decompile.impl.decompiler-cfr-config.recordtypes=レコード型を再構築する\nservice.decompile.impl.decompiler-cfr-config.recover=逆コンパイルが失敗した場合に，可能な限り復元する\nservice.decompile.impl.decompiler-cfr-config.recovertypeclash=分析によって型の衝突が検出された場合に，可能な限り復元する\nservice.decompile.impl.decompiler-cfr-config.recovertypehints=最初のパスからイテレータの型を可能な限り復元する\nservice.decompile.impl.decompiler-cfr-config.reducecondscope=if 文のスコープを可能な限り縮小する\nservice.decompile.impl.decompiler-cfr-config.relinkconst=定数を可能な限り再リンクする - フィールドへのインライン参照がある場合は，その解除を試みる\nservice.decompile.impl.decompiler-cfr-config.relinkconststring=定数文字列を再リンクする - static final な定数と一致する文字列へのローカルな参照がある場合は，その定数を使用する\nservice.decompile.impl.decompiler-cfr-config.removebadgenerics=明らかに不正なジェネリクスを非表示にする\nservice.decompile.impl.decompiler-cfr-config.removeboilerplate=定型的な関数を削除する - デフォルト・コンストラクタや，ラムダのデシリアライズなど\nservice.decompile.impl.decompiler-cfr-config.removedeadconditionals=到達不能なコードを削除する\nservice.decompile.impl.decompiler-cfr-config.removedeadmethods=必要のないメソッドを削除する - デフォルト・コンストラクタなど\nservice.decompile.impl.decompiler-cfr-config.removeinnerclasssynthetics=内部クラス内の暗黙的な外部クラスへの参照を削除する\nservice.decompile.impl.decompiler-cfr-config.renamedupmembers=あいまいな・重複しているフィールドの名前を変更する\nservice.decompile.impl.decompiler-cfr-config.renameenumidents=通常の名前と一致しない Enum 識別子の名前を変更する\nservice.decompile.impl.decompiler-cfr-config.renameillegalidents=有効な Java 識別子ではない名前を変更する\nservice.decompile.impl.decompiler-cfr-config.renamesmallmembers=小さいメンバの名前を変更する - 注意：これによりリフレクションを使用しているコードが壊れる可能性があります。\nservice.decompile.impl.decompiler-cfr-config.sealed=シールドされたクラスを再構築する\nservice.decompile.impl.decompiler-cfr-config.showinferrable=引数で暗示されていない場合に，明示的な型引数を表示する\nservice.decompile.impl.decompiler-cfr-config.showversion=使っている CFR のバージョンをヘッダーに表示する（デグレードのテスト時にオフにすると便利です）\nservice.decompile.impl.decompiler-cfr-config.skipbatchinnerclasses=多数のファイルを処理するときに，（内部クラスは外部クラスの一部として処理するので）内部クラスをスキップする\nservice.decompile.impl.decompiler-cfr-config.staticinitreturn=静的イニシャライザの戻り値を削除する\nservice.decompile.impl.decompiler-cfr-config.stringbuffer=new StringBuffer().append.append.append を string + string + string に変換する\nservice.decompile.impl.decompiler-cfr-config.stringbuilder=new StringBuilder().append.append.append を string + string + string に変換する\nservice.decompile.impl.decompiler-cfr-config.stringconcat=StringConcatFactor の使用箇所を string + string + string に変換する\nservice.decompile.impl.decompiler-cfr-config.sugarasserts=assert 文を再構築する\nservice.decompile.impl.decompiler-cfr-config.sugarboxing=ボクシングされた型をプリミティブ型に可能な限り変換する\nservice.decompile.impl.decompiler-cfr-config.sugarenums=列挙型の switch 文を再構築する\nservice.decompile.impl.decompiler-cfr-config.sugarretrolambda=古いラムダ式を再構築する\nservice.decompile.impl.decompiler-cfr-config.switchexpression=switch 式を再構築する\nservice.decompile.impl.decompiler-cfr-config.tidymonitors=モニタのサポート用コードを削除する（e.g. モニタを終了するためだけの catch ブロックなど）\nservice.decompile.impl.decompiler-cfr-config.tryresources=try-with-resources を再構築する\nservice.decompile.impl.decompiler-cfr-config.usenametable=ローカル変数の名前をテーブルから取得する\nservice.decompile.impl.decompiler-cfr-config.usesignatures=明らかに誤っている場合に，ディスクリプタに加えてシグニチャを使用する\nservice.decompile.impl.decompiler-cfr-config.version=現在の CFR のバージョンを表示する\nservice.decompile.impl.decompiler-procyon-config=Procyon\nservice.decompile.impl.decompiler-procyon-config.alwaysGenerateExceptionVariableForCatchBlocks=catch ブロックの例外変数を常に生成する\nservice.decompile.impl.decompiler-procyon-config.arePreviewFeaturesEnabled=プレビュー機能を有効にする\nservice.decompile.impl.decompiler-procyon-config.bytecodeOutputOptions=バイト・コード出力オプション\nservice.decompile.impl.decompiler-procyon-config.disableForEachTransforms=forEach の変換を無効にする\nservice.decompile.impl.decompiler-procyon-config.excludeNestedTypes=ネストされた型を除外する\nservice.decompile.impl.decompiler-procyon-config.flattenSwitchBlocks=フラットな switch ブロックを生成する\nservice.decompile.impl.decompiler-procyon-config.forceExplicitImports=明示的なインポートを強制する\nservice.decompile.impl.decompiler-procyon-config.forceExplicitTypeArguments=明示的な型引数を強制する\nservice.decompile.impl.decompiler-procyon-config.forceFullyQualifiedReferences=完全修飾名を強制する\nservice.decompile.impl.decompiler-procyon-config.forcedCompilerTarget=ターゲット・バージョンを強制する\nservice.decompile.impl.decompiler-procyon-config.includeErrorDiagnostics=エラー診断を含める\nservice.decompile.impl.decompiler-procyon-config.includeLineNumbersInBytecode=デバッグ用の行番号をバイト・コードに含める\nservice.decompile.impl.decompiler-procyon-config.isUnicodeOutputEnabled=出力に Unicode を使用する\nservice.decompile.impl.decompiler-procyon-config.languageTarget=言語・ターゲット\nservice.decompile.impl.decompiler-procyon-config.mergeVariables=変数をマージする\nservice.decompile.impl.decompiler-procyon-config.retainPointlessSwitches=無意味な switch 文を保持する\nservice.decompile.impl.decompiler-procyon-config.retainRedundantCasts=冗長なキャストを保持する\nservice.decompile.impl.decompiler-procyon-config.showDebugLineNumbers=デバッグ用の行番号を表示する\nservice.decompile.impl.decompiler-procyon-config.showSyntheticMembers=synthetic メンバを表示する\nservice.decompile.impl.decompiler-procyon-config.simplifyMemberReferences=メンバ参照を簡略化する\nservice.decompile.impl.decompiler-procyon-config.textBlockLineMinimum=テキスト・ブロックの最小行数\nservice.decompile.impl.decompiler-vineflower-config=Vineflower\nservice.decompile.impl.decompiler-vineflower-config.logging-level=ログ・レベル\nservice.decompile.impl.decompiler-vineflower-config.remove-bridge=ブリッジ・メソッドを削除する\nservice.decompile.impl.decompiler-vineflower-config.remove-synthetic=synthetic メソッドとフィールドを削除する\nservice.decompile.impl.decompiler-vineflower-config.decompile-inner=内部クラスを逆コンパイルsる\nservice.decompile.impl.decompiler-vineflower-config.decompile-java4=Java 4 以前のクラス参照を逆コンパイルする\nservice.decompile.impl.decompiler-vineflower-config.decompile-assert=assert 文を逆コンパイルする\nservice.decompile.impl.decompiler-vineflower-config.hide-empty-super=空の super() 呼び出しを非表示にする\nservice.decompile.impl.decompiler-vineflower-config.hide-default-constructor=デフォルト・コンストラクタを非表示にする\nservice.decompile.impl.decompiler-vineflower-config.decompile-generics=ジェネリクスを逆コンパイルする\nservice.decompile.impl.decompiler-vineflower-config.incorporate-returns=try-catch ブロックに return 文を組み込む\nservice.decompile.impl.decompiler-vineflower-config.ensure-synchronized-monitors=synchronized ブロックのモニタを確実に組み込む\nservice.decompile.impl.decompiler-vineflower-config.decompile-enums=列挙型を逆コンパイルする\nservice.decompile.impl.decompiler-vineflower-config.decompile-preview=プレビュー機能を逆コンパイルする\nservice.decompile.impl.decompiler-vineflower-config.remove-getclass=getClass() 呼び出しを削除する\nservice.decompile.impl.decompiler-vineflower-config.keep-literals=リテラルをありのままに保持する\nservice.decompile.impl.decompiler-vineflower-config.boolean-as-int=真偽値を 0 または 1 として扱う\nservice.decompile.impl.decompiler-vineflower-config.ascii-strings=ASCII 文字列を使用する\nservice.decompile.impl.decompiler-vineflower-config.synthetic-not-set=synthetic フィールドを設定しない\nservice.decompile.impl.decompiler-vineflower-config.undefined-as-object=未定義のパラメタを Object として扱う\nservice.decompile.impl.decompiler-vineflower-config.use-lvt-names=LVT 名を使用する\nservice.decompile.impl.decompiler-vineflower-config.use-method-parameters=メソッド・パラメータを使用する\nservice.decompile.impl.decompiler-vineflower-config.remove-empty-try-catch=空の try-catch ブロックを削除する\nservice.decompile.impl.decompiler-vineflower-config.decompile-finally=finally ブロックを逆コンパイルする\nservice.decompile.impl.decompiler-vineflower-config.lambda-to-anonymous-class=ラムダ式を匿名クラスに変換する\nservice.decompile.impl.decompiler-vineflower-config.bytecode-source-mapping=バイト・コードからソースへのマッピングを生成する\nservice.decompile.impl.decompiler-vineflower-config.__dump_original_lines__=コードの元の行をダンプする\nservice.decompile.impl.decompiler-vineflower-config.ignore-invalid-bytecode=間違ったバイト・コードを無視する\nservice.decompile.impl.decompiler-vineflower-config.verify-anonymous-classes=匿名クラスを検証する\nservice.decompile.impl.decompiler-vineflower-config.ternary-constant-simplification=三項演算子の簡略化\nservice.decompile.impl.decompiler-vineflower-config.pattern-matching=パターン・マッチング\nservice.decompile.impl.decompiler-vineflower-config.try-loop-fix=try-loop を修正する\nservice.decompile.impl.decompiler-vineflower-config.ternary-in-if=[実験的] 三項演算子を if 文に変換する\nservice.decompile.impl.decompiler-vineflower-config.decompile-switch-expressions=switch 式を逆コンパイルする\nservice.decompile.impl.decompiler-vineflower-config.show-hidden-statements=[デバッグ用] 隠されたステートメントを表示する\nservice.decompile.impl.decompiler-vineflower-config.override-annotation=アノテーションを上書きする\nservice.decompile.impl.decompiler-vineflower-config.simplify-stack=2 つのスタックを 1 つにまとめる\nservice.decompile.impl.decompiler-vineflower-config.verify-merges=[実験的] 変数のマージを検証する\nservice.decompile.impl.decompiler-vineflower-config.include-classpath=クラス・パス全体を含める\nservice.decompile.impl.decompiler-vineflower-config.include-runtime=JVM ランタイムを含める\nservice.decompile.impl.decompiler-vineflower-config.explicit-generics=明示的なジェネリクスを使用する\nservice.decompile.impl.decompiler-vineflower-config.inline-simple-lambdas=シンプルなラムダ式をインライン化する\nservice.decompile.impl.decompiler-vineflower-config.log-level=ログ・レベル\nservice.decompile.impl.decompiler-vineflower-config.max-time-per-method=[非推奨] メソッドごとの最大時間\nservice.decompile.impl.decompiler-vineflower-config.rename-members=メンバの名前を変更する\nservice.decompile.impl.decompiler-vineflower-config.user-renamer-class=ユーザ・再命名クラス\nservice.decompile.impl.decompiler-vineflower-config.new-line-separator=[非推奨] 改行文字\nservice.decompile.impl.decompiler-vineflower-config.indent-string=インデント文字列\nservice.decompile.impl.decompiler-vineflower-config.preferred-line-length=１行の長さ\nservice.decompile.impl.decompiler-vineflower-config.banner=バナー\nservice.decompile.impl.decompiler-vineflower-config.error-message=エラー・メッセージ\nservice.decompile.impl.decompiler-vineflower-config.thread-count=使用するスレッド数\nservice.decompile.impl.decompiler-vineflower-config.skip-extra-files=追加のファイルをスキップする\nservice.decompile.impl.decompiler-vineflower-config.warn-inconsistent-inner-attributes=内部属性の不整合を警告する\nservice.decompile.impl.decompiler-vineflower-config.dump-bytecode-on-error=エラー時にバイト・コードをダンプする\nservice.decompile.impl.decompiler-vineflower-config.dump-exception-on-error=エラー時に例外をダンプする\nservice.decompile.impl.decompiler-vineflower-config.decompiler-comments=逆コンパイラのコメント\nservice.decompile.impl.decompiler-vineflower-config.sourcefile-comments=ソース・ファイルのコメント\nservice.decompile.impl.decompiler-vineflower-config.decompile-complex-constant-dynamic=複雑な ConstantDynamic を逆コンパイルする\nservice.decompile.impl.decompiler-vineflower-config.force-jsr-inline=JSR 命令をインライン化する\nservice.decompile.impl.decompiler-vineflower-config.dump-text-tokens=テキスト・トークンをダンプする\nservice.decompile.impl.decompiler-vineflower-config.remove-imports=インポートを削除する\nservice.decompile.impl.decompiler-vineflower-config.mark-corresponding-synthetics=対応する synthetic をマークする\nservice.io=入出力\nservice.io.directories-config=ディレクトリ\nservice.io.export-config=エクスポート\nservice.io.export-config.bundle-supporting-resources=サポート・ファイルをバンドルに含める\nservice.io.export-config.compression=出力内容の圧縮方法\nservice.io.export-config.create-zip-dir-entries=出力に ZIP ディレクトリ・エントリを作成する\nservice.io.export-config.warn-no-changes=変更を加えずにエクスポートしたときに警告を表示する\nservice.io.gson-provider-config=Json\nservice.io.gson-provider-config.pretty-print=出力を整形する\nservice.io.info-importer-config=内容のインポート\nservice.io.info-importer-config.skip-class-asm-validation=クラス・ファイルの検証をスキップする\nservice.io.info-importer-config.skip-class-code=クラス・ファイルのコードをスキップする\nservice.io.recent-workspaces-config=最近のワークスペース\nservice.io.recent-workspaces-config.last-workspace-export-path=最後のワークスペースのエクスポート先\nservice.io.recent-workspaces-config.last-workspace-open-path=最後のワークスペースのオープン先\nservice.io.recent-workspaces-config.max-recent-workspaces=最近のワークスペースの最大数\nservice.io.recent-workspaces-config.recent-workspaces=最近のワークスペース\nservice.io.recent-workspaces-config.last-class-export-path=最後のクラスのエクスポート先\nservice.io.resource-importer-config=アーカイブ形式のインポート\nservice.io.resource-importer-config.zip-strategy=ZIP の読み込み方法\nservice.io.resource-importer-config.allow-basic-base-offset-zero-check=JVM 方式での読み込み時に基本オフセットが 0 のチェックを許可する\nservice.io.resource-importer-config.skip-revisited-cen-to-local-links=JVM 方式での読み込み時に，CEN からローカルへのリンクをスキップする\nservice.io.resource-importer-config.ignore-file-lengths=Naive 方式での読み込み時に，ファイル長のチェックを無視する\nservice.mapping=マッピング\nservice.mapping.mapping-aggregator-config=マッピングの集約\nservice.mapping.mapping-formats-config=マッピングの形式\nservice.mapping.mapping-generator-config=マッピングの生成\nservice.mapping.name-gen-provider=名前の生成\nservice.mapping.name-gen-provider.alphabet=アルファベット\nservice.mapping.name-gen-provider.alphabet.alphabet=使用するアルファベット文字\nservice.mapping.name-gen-provider.alphabet.length=最小の長さ\nservice.plugin=プラグイン\nservice.plugin.plugin-manager-config=プラグインの管理\nservice.plugin.plugin-manager-config.scan-on-start=起動時にプラグインを読み込む\nservice.plugin.script-manager-config=スクリプトの管理\nservice.plugin.script-manager-config.file-watching=スクリプト・ディレクトリの変更を監視する\nservice.ui=ユーザ・インタフェース\nservice.ui.bind-config=バインディング\nservice.ui.bind-config.bundle=バインディング・マップ\nservice.ui.class-editing-config=クラスの編集\nservice.ui.class-editing-config.default-android-editor=デフォルトの Android クラスのエディタ\nservice.ui.class-editing-config.default-jvm-editor=デフォルトの JVM クラスのエディタ\nservice.ui.decompile-pane-config=逆コンパイル・パネル\nservice.ui.decompile-pane-config.timeout-seconds=逆コンパイラのタイムアウト（秒）\nservice.ui.decompile-pane-config.mapping-acceleration=再マッピング処理を高速化\nservice.ui.hex-config=HEX エディタ\nservice.ui.hex-config.row-length=カラム数\nservice.ui.hex-config.row-split-interval=行の分割間隔\nservice.ui.hex-config.show-address=アドレスを表示する\nservice.ui.hex-config.show-ascii=ASCII を表示する\nservice.ui.member-format-config=フィールドとメソッドの表示形式\nservice.ui.member-format-config.name-type-display=名前と型の表示方式\nservice.ui.tab-completion-config=タブ補完\nservice.ui.text-format-config=テキスト形式\nservice.ui.tab-completion-config.enabled-in-assembler=アセンブラで有効にする\nservice.ui.tab-completion-config.max-completion-length=最大の補完文字数\nservice.ui.tab-completion-config.max-completion-rows=最大の補完行数\nservice.ui.tab-completion-config.popup-position=カーソルに対するポップアップの位置\nservice.ui.text-format-config.escape=テキストのエスケープを有効にする\nservice.ui.text-format-config.max-length=表示する最大の文字数\nservice.ui.text-format-config.shorten=テキストを短縮する\nservice.ui.file-type-syntax-association-config=ファイル・タイプの割り当て\nservice.ui.file-type-syntax-association-config.extensions-to-langs=拡張子と言語\nservice.ui.snippets-config=スニペット\nservice.ui.window-manager-config=ウィンドウ管理\nservice.ui.window-scale-config=ウィンドウの拡大\nservice.ui.window-scale-config.scale=拡大率\nservice.ui.workspace-explorer-config=ワーク・スペース\nservice.ui.workspace-explorer-config.drag-drop-action=ドラッグ＆ドロップ時の挙動\nservice.ui.workspace-explorer-config.max-tree-dir-depth=最大ディレクトリ深度\nservice.ui.language-config=言語\nservice.ui.language-config.current=現在の言語\n\n### Matcher translations\nnumber.match.equal=value == n\nnumber.match.not=value != n\nnumber.match.gt=value > n\nnumber.match.gte=value >= n\nnumber.match.lt=value < n\nnumber.match.lte=value <= n\nnumber.match.gt-lt=min < value < max\nnumber.match.gte-lt=min <= value < max\nnumber.match.gt-lte=min < value <= max\nnumber.match.gte-lte=min < value <= max\nnumber.match.any-of=numbers.contains(value)\nstring.match.anything=すべて\nstring.match.zilch=なし\nstring.match.contains=str.contains(value)\nstring.match.contains-ic=str.containsIgnoreCase(value)\nstring.match.ends=str.endsWith(value)\nstring.match.ends-ic=str.endsWithIgnoreCase(value)\nstring.match.equal=str.equals(value)\nstring.match.equal-ic=str.equalsIgnoreCase(value)\nstring.match.regex-full=str.matches(value)\nstring.match.regex-partial=str.matchesPartially(value)\nstring.match.starts=str.startsWith(value)\nstring.match.starts-ic=str.startsWithIgnoreCase(value)\n\n### Misc stuff\nmisc.acknowledge=了解\nmisc.all=すべて\nmisc.none=なし\nmisc.done=完了\nmisc.ignored=無視\nmisc.enabled=有効\nmisc.disabled=無効\nmisc.download=ダウンロード\nmisc.download.invalid-url=不正な URL です。\nmisc.before=前へ\nmisc.after=次へ\nmisc.load=読み込む\nmisc.clear=消去\nmisc.export=エクスポート\nmisc.remove=削除\nmisc.removed=削除されました\nmisc.casesensitive=大文字と小文字を区別\nmisc.path=パス\nmisc.regex=正規表現\nmisc.member.field=フィールド\nmisc.member.method=メソッド\nmisc.member.field-n-method=フィールドとメソッド\nmisc.member.inner-class=内部クラス\nmisc.member.inner-interface=内部インタフェース\nmisc.member.inner-enum=内部列挙型\nmisc.member.inner-annotation=内部アノテーション\nmisc.accessflag.visibility.public=Public\nmisc.accessflag.visibility.protected=Protected\nmisc.accessflag.visibility.private=Private\nmisc.accessflag.visibility.package=Package\nmisc.direction.up=上方向\nmisc.direction.down=下方向\nmisc.direction.left=左方向\nmisc.direction.right=右方向\nmisc.direction.top=上方\nmisc.direction.bottom=下方\nmisc.position.top=上部\nmisc.position.bottom=下部\nmisc.position.left=左部\nmisc.position.right=右部\nmisc.position.center=中央\nmisc.position.middle=中間\n"
  },
  {
    "path": "recaf-ui/src/main/resources/translations/pl_PL.lang",
    "content": "menu.analysis=Analizy\nmenu.analysis.summary=Pokaż podsumowanie\nmenu.analysis.list-comments=Podejrzyj komentarze\nmenu.analysis.comment=Komentarz\nmenu.association.override=Nadpisz język\nmenu.association.none=Brak skonfigurowanych związków\nmenu.config=Konfiguracja\nmenu.config.edit=Edytuj\nmenu.config.export=Eksportuj\nmenu.config.import=Importuj\nmenu.file=Plik\nmenu.file.attach=Połącz z hostem zdalnym\nmenu.file.addtoworkspace=Dodaj do obszaru roboczego\nmenu.file.decompileall=Zdekompiluj wszystkie klasy\nmenu.file.decompileall.path=Ścieżka wyjściowa\nmenu.file.openworkspace=Otwórz obszar roboczy\nmenu.file.openurl=Otwórz z URL\nmenu.file.exportapp=Eksportuj aplikację\nmenu.file.exportworkspace=Eksportuj konfigurację obszaru roboczego\nmenu.file.modifications=Pokaż modyfikacje\nmenu.file.recent=Ostatnie\nmenu.file.close=Zamknij\nmenu.file.quit=Zakończ\nmenu.goto.class=Przejdź do klasy\nmenu.goto.field=Przejdź do pola\nmenu.goto.method=Przejdź do metody\nmenu.goto.instruction=Przejdź do instrukcji\nmenu.goto.file=Przejdź do pliku\nmenu.goto.label=Przejdź do etykiety\nmenu.edit=Edytuj\nmenu.edit.add.field=Dodaj pole\nmenu.edit.add.method=Dodaj metodę\nmenu.edit.add.annotation=Dodaj adnotację\nmenu.edit.remove.field=Usuń pola\nmenu.edit.remove.method=Usuń metody\nmenu.edit.remove.annotation=Usuń adnotacje\nmenu.edit.assemble.class=Edytuj klasę w asemblerze\nmenu.edit.assemble.field=Edytuj pole w asemblerze\nmenu.edit.assemble.method=Edytuj metodę w asemblerze\nmenu.edit.remove=Usuń\nmenu.edit.copy=Kopiuj\nmenu.edit.delete=Usuń\nmenu.edit.noop=Utwórz no-op\nmenu.edit.changeversion=Zmień wersję klasy\nmenu.edit.changeversion.up=Uaktualnij\nmenu.edit.changeversion.down=Wsteczna kompatybilność\nmenu.export.class=Eksportuj klasę\nmenu.help=Pomoc\nmenu.help.discord=Discord\nmenu.help.docs=Dokumentacja użytkownika online\nmenu.help.docsdev=Dokumentacja programisty online\nmenu.help.github=Github\nmenu.help.issues=Zgłoś problem\nmenu.help.sysinfo=Informacje o systemie\nmenu.refactor=Refaktoryzacja\nmenu.refactor.move=Przenieś\nmenu.refactor.rename=Zmień nazwę\nmenu.search=Szukaj\nmenu.search.string=Ciągi znaków\nmenu.search.number=Liczby\nmenu.search.class.member-references=Odwołania do członków\nmenu.search.class.type-references=Odwołania do typów\nmenu.search.method-overrides=Nadpisania metod\nmenu.search.method-references=Odwołania do metod\nmenu.search.field-references=Odwołania do pól\nmenu.search.noresults=Brak wyników\nmenu.mappings=Mapowania\nmenu.mappings.apply=Zastosuj\nmenu.mappings.export=Eksportuj\nmenu.mappings.export.unsupported=%s (Nieobsługiwane)\nmenu.mappings.generate=Generuj\nmenu.mappings.view=Aktualne mapowania\nmenu.scripting=Skrypty\nmenu.scripting.list=Lista skryptów\nmenu.scripting.none-found=Nie znaleziono skryptów\nmenu.scripting.manage=Zarządzaj skryptami\nmenu.scripting.new=Nowy skrypt\nmenu.scripting.edit=Edytuj\nmenu.scripting.browse=Przeglądaj skrypty\nmenu.scripting.save=Zapisz skrypt\nmenu.scripting.execute=Wykonaj\nmenu.scripting.editor=Edytor skryptów\nmenu.scripting.author=Autor\nmenu.scripting.version=Wersja\nmenu.view=Widok\nmenu.view.hierarchy=Hierarchia klas\nmenu.view.hierarchy.children=Potomne\nmenu.view.hierarchy.parents=Nadrzędne\nmenu.view.methodcfg=Graf przepływu sterowania\nmenu.tab.close=Zamknij\nmenu.tab.closeothers=Zamknij pozostałe\nmenu.tab.closeall=Zamknij wszystkie\nmenu.tab.copypath=Kopiuj ścieżkę\nmenu.image.resetscale=Resetuj skalę\nmenu.image.center=Wyśrodkuj obraz\nmenu.hex.copyas=Kopiuj jako...\nmenu.mode=Zmień widok\nmenu.mode.class.auto=Automatyczny\nmenu.mode.class.decompile=Dekompiluj\nmenu.mode.file.auto=Automatyczny\nmenu.mode.file.text=Tekst\nmenu.mode.file.hex=Heksadecymalny\nmenu.mode.diff.decompile=Dekompiluj\nmenu.mode.diff.disassemble=Dezassembluj\nmenu.vm=Wirtualizuj\nmenu.vm.optimize=Optymalizuj\nmenu.vm.run=Uruchom\nmenu.plugin=Wtyczki\nmenu.plugin.manage=Zarządzaj wtyczkami\nmenu.plugin.installed=Zainstalowane\nmenu.plugin.remote=Zdalne\nmenu.plugin.browse=Przeglądaj wtyczki\nmenu.plugin.enabled=Włączone\nmenu.plugin.uninstall=Odinstaluj\nmenu.plugin.uninstall.warning=Czy na pewno chcesz usunąć tę wtyczkę?\n\n# Skróty klawiszowe\nbind.inputprompt.initial=<oczekiwanie>\nbind.inputprompt.finish=<ENTER aby zakończyć>\nbind.editor.find=Znajdź\nbind.editor.rename=Zmień nazwę\nbind.editor.replace=Zamień\nbind.editor.save=Zapisz\nbind.quicknav=Szybka nawigacja\n\n# Teksty dialogów\ndialog.cancel=Anuluj\ndialog.close=Zamknij\ndialog.confirm=Potwierdź\ndialog.finish=Zakończ\ndialog.next=Dalej\ndialog.previous=Wstecz\ndialog.dismiss=Odrzuć\ndialog.configure=Konfiguruj\ndialog.warning=Ostrzeżenie\ndialog.restart=Aby zmienić tę opcję konfiguracji, zaleca się ponowne uruchomienie.\\nCzy na pewno chcesz zastosować?\ndialog.unknownextension=Nieznane rozszerzenie pliku. Czy chcesz skonfigurować powiązanie języka?\n\n## Szukaj\ndialog.search.type=Wpisz nazwę\ndialog.search.member-owner=Typ właściciela elementu\ndialog.search.member-name=Nazwa elementu\ndialog.search.member-descriptor=Deskryptor elementu\n\n## Wybór pliku\ndialog.title.primary=Podstawowy zasób\ndialog.title.supporting=Zasoby pomocnicze\ndialog.title.nochanges=Czy eksportować bez zmian?\ndialog.file.open=Otwórz\ndialog.file.open.directory=Katalogi\ndialog.file.open.file=Pliki\ndialog.file.export=Eksportuj\ndialog.file.save=Zapisz\ndialog.file.nothing=Nic nie wybrano\ndialog.file.nochanges=Czy chcesz eksportować aplikację, mimo że nie wprowadzono żadnych zmian?\ndialog.filefilter.any=Dowolny typ\ndialog.filefilter.mapping=Mapowania\ndialog.filefilter.input=Aplikacje\ndialog.filefilter.workspace=Obszary robocze\n\n## Przenoszenie pliku\ndialog.title.create-workspace=Utwórz obszar roboczy\ndialog.title.update-workspace=Obsłuż dane wejściowe obszaru roboczego\ndialog.title.close-workspace=Zamknąć obszar roboczy?\ndialog.option.create-workspace=Utwórz nowy obszar roboczy\ndialog.option.update-workspace=Dodaj do obszaru roboczego\n\n## Kopiuj klasę/plik\ndialog.title.copy-class=Kopiuj klasę\ndialog.title.copy-directory=Kopiuj katalog\ndialog.title.copy-package=Kopiuj pakiet\ndialog.title.copy-field=Kopiuj pole\ndialog.title.copy-file=Kopiuj plik\ndialog.title.copy-method=Kopiuj metodę\ndialog.header.copy-class=Podaj nową nazwę dla skskopiowanej klasy.\ndialog.header.copy-directory=Podaj nową nazwę dla skopiowanego katalogu.\ndialog.header.copy-package=Podaj nową nazwę dla skopiowanego pakietu.\ndialog.header.copy-field=Podaj nową nazwę dla skopiowanego pola.\ndialog.header.copy-field-error=Nazwa pola już istnieje.\\nProszę wybrać inną nazwę.\ndialog.header.copy-file=Podaj nową nazwę dla skopiowanego pliku.\ndialog.header.copy-method=Podaj nową nazwę dla skopiowanej metody.\ndialog.header.copy-method-error=Nazwa metody już istnieje.\\nProszę wybrać inną nazwę.\n\n## Usuń klasę/plik\ndialog.title.delete-class=Usuń klasę\ndialog.title.delete-directory=Usuń katalog\ndialog.title.delete-field=Usuń pole\ndialog.title.delete-file=Usuń plik\ndialog.title.delete-method=Usuń metodę\ndialog.title.delete-package=Usuń pakiet\ndialog.title.delete-resource=Usuń zasób\ndialog.header.delete-class=Czy na pewno chcesz usunąć: %s?\ndialog.header.delete-directory=Czy na pewno chcesz usunąć: %s?\ndialog.header.delete-field=Czy na pewno chcesz usunąć: %s?\ndialog.header.delete-file=Czy na pewno chcesz usunąć: %s?\ndialog.header.delete-method=Czy na pewno chcesz usunąć: %s?\ndialog.header.delete-package=Czy na pewno chcesz usunąć: %s?\ndialog.header.delete-resource=Czy na pewno chcesz usunąć: %s?\n\n## Zmień nazwę klasy/pliku\ndialog.title.rename-class=Zmień nazwę klasy\ndialog.title.rename-class-warning=Ostrzeżenie\ndialog.title.rename-directory=Zmień nazwę katalogu\ndialog.title.rename-field=Zmień nazwę pola\ndialog.title.rename-file=Zmień nazwę pliku\ndialog.title.rename-file-warning=Ostrzeżenie\ndialog.title.rename-method=Zmień nazwę metody\ndialog.title.rename-package=Zmień nazwę pakietu\ndialog.header.rename-class=Podaj nową nazwę dla klasy.\ndialog.header.rename-class-error=Nazwa klasy już istnieje.\\nProszę wybrać inną nazwę.\ndialog.header.rename-package=Podaj nową nazwę dla pakietu.\ndialog.header.rename-package-error=Nazwa pakietu już istnieje.\\nProszę wybrać inną nazwę.\ndialog.header.rename-package-warning=Nazwa pakietu już istnieje.\\nMoże to spowodować nadpisanie niektórych klas.\ndialog.header.rename-directory=Podaj nową nazwę dla katalogu.\ndialog.header.rename-directory-error=Nazwa katalogu już istnieje.\\nProszę wybrać inną nazwę.\ndialog.header.rename-directory-warning=Nazwa katalogu już istnieje.\\nMoże to spowodować nadpisanie niektórych plików.\ndialog.header.rename-field=Podaj nową nazwę dla pola.\ndialog.header.rename-field-error=Nazwa pola już istnieje.\\nProszę wybrać inną nazwę.\ndialog.header.rename-file=Podaj nową nazwę dla pliku.\ndialog.header.rename-file-error=Nazwa pliku już istnieje.\\nProszę wybrać inną nazwę.\ndialog.header.rename-method=Podaj nową nazwę dla metody.\ndialog.header.rename-method-error=Nazwa metody już istnieje.\\nProszę wybrać inną nazwę.\n\n## Przenieś klasę/plik\ndialog.title.move-class=Wybierz pakiet docelowy\ndialog.title.move-directory=Wybierz katalog docelowy (nadrzędny)\ndialog.title.move-file=Wybierz katalog docelowy\ndialog.title.move-package=Wybierz pakiet docelowy (nadrzędny)\ndialog.header.move-class=Przenieś klasę do nowego pakietu.\ndialog.header.move-directory=Przenieś katalog do nowego katalogu nadrzędnego.\ndialog.header.move-file=Przenieś plik do nowego katalogu.\ndialog.header.move-package=Przenieś pakiet do nowego pakietu nadrzędnego.\n\n## Dodaj elementy\ndialog.title.add-field=Dodaj pole\ndialog.title.add-method=Dodaj metodę\ndialog.input.name=Nazwa\ndialog.input.desc=Deskryptor\ndialog.warn.illegal-name=Nieprawidłowa nazwa\ndialog.warn.illegal-desc=Nieprawidłowy format deskryptora\ndialog.warn.field-conflict=Nazwa pola już istnieje.\\nProszę wybrać inną nazwę.\ndialog.warn.method-conflict=Nazwa metody już istnieje.\\nProszę wybrać inną nazwę.\n\n## Akcje VM\ndialog.title.vm-invoke-args=Wirtualizuj wywołanie metody\ndialog.title.vm-peephole-invoke-args=Zoptymalizuj wirtualizowane wywołanie\ndialog.vm.execute=Wykonaj\ndialog.vm.optimize=Optymalizuj\ndialog.vm.create-dummy=Użyj atrapy\ndialog.vm.create-null=Użyj null\n\n## Dialogi heksadecymalne\ndialog.hex.title.insertcount=Wstaw\ndialog.hex.header.insertcount=Ile bajtów wstawić?\n\n# Dialog konwertera podstaw\ndialog.conv.title.literal=Literał liczbowy\ndialog.conv.title.expression=Wyrażenie liczbowe\n\n## Szybka nawigacja\ndialog.quicknav=Szybka nawigacja\ndialog.quicknav.tab.classes=Klasy\ndialog.quicknav.tab.members=Elementy\ndialog.quicknav.tab.files=Pliki\ndialog.quicknav.tab.text=Tekst\ndialog.quicknav.tab.commented=Komentarze\n\n## Dialog błędu\ndialog.error.exportclass.title=Nie udało się wyeksportować klasy\ndialog.error.exportclass.header=Wystąpił błąd podczas zapisu do miejsca docelowego\ndialog.error.exportclass.content=Błąd:\ndialog.error.exportworkspace.title=Nie udało się wyeksportować obszaru roboczego\ndialog.error.exportworkspace.header=Wystąpił błąd podczas zapisu do miejsca docelowego\ndialog.error.exportworkspace.content=Błąd:\ndialog.error.loadworkspace.title=Nie udało się załadować obszaru roboczego\ndialog.error.loadworkspace.header=Wystąpił błąd podczas odczytu z wybranych plików\ndialog.error.loadworkspace.content=Błąd:\ndialog.error.loadsupport.title=Nie udało się załadować zasobów pomocniczych\ndialog.error.loadsupport.header=Wystąpił błąd podczas odczytu z wybranych plików\ndialog.error.loadsupport.content=Błąd:\ndialog.error.attach.title=Nie udało się dołączyć do JVM\ndialog.error.attach.header=Wystąpił błąd podczas łączenia ze zdalną maszyną wirtualną Java\ndialog.error.attach.content=Błąd:\n\n## Kreator\nwizard.chooseaction=Wybierz akcję\nwizard.selectprimary=Wybierz podstawowy zasób\nwizard.currentworkspace=Aktualny obszar roboczy\n\n\n# Panele\n## Powitanie\nwelcome.title=Witaj\n\n## Obszar roboczy\nworkspace.title=Obszar roboczy\nworkspace.filter-prompt=Filtruj: Nazwa klasy/pliku...\nworkspace.info=Informacje\n\n## Dołącz\nattach.unsupported=Dołączanie nie powiodło się\nattach.unsupported.detail=Agent dołączania nie mógł się samodzielnie wyodrębnić.\nattach.connect=Połącz\nattach.tab.properties=Właściwości\nattach.tab.classloading=Klasy\nattach.tab.compilation=Kompilacja\nattach.tab.system=System\nattach.tab.runtime=Runtime\nattach.tab.thread=Wątki\n\n## Widok zmian\nmodifications.none=Brak historii modyfikacji dla elementu\nmodifications.title=Modyfikacje\n\n## Obszar Javy\njava.decompiling=Dekompilowanie klasy...\njava.unparsable=SourceSolver nie zinterpretował źródła, akcje kontekstowe dostępne tylko na karcie bocznej \"Pola i metody\"\njava.parse-state.error=Błąd parsowania\njava.parse-state.error-details=Akcje kontekstowe (prawy przycisk myszy) nie są dostępne, ponieważ parsowanie nie powiodło się.\\nMożesz w międzyczasie użyć karty bocznej \"Pola i metody\".\njava.parse-state.initial=Trwa parsowanie...\njava.parse-state.initial-details=Akcje kontekstowe (prawy przycisk myszy) nie są dostępne, dopóki parsowanie nie zostanie zakończone.\\nMożesz w międzyczasie użyć karty bocznej \"Pola i metody\".\njava.parse-state.new-progress=Ponowne parsowanie...\njava.parse-state.new-progress-details=Wprowadzono zmiany, więc trwa nowe parsowanie.\\nPodczas konstruowania nowego modelu będzie używany stary model.\\nMożesz też użyć karty bocznej \"Pola i metody\".\njava.parse-state.none=Brak treści do parsowania\njava.decompile-failure=Nie udało się zdekompilować klasy. Kilka opcji:\\n- Zmień dekompilatory\\n- Otwórz klasę w asemblerze lub innym widoku\\n- Spróbuj zdeobfuskowac klasę i spróbować ponownie\njava.decompile-failure.brief=Nie udało się zdekompilować klasy\njava.savewitherrors=Wygląda na to, że po raz pierwszy wprowadzasz zmiany, które spowodowały błędy.\\nZazwyczaj są one wynikiem tego, że zdekompilowany kod nie jest semantycznie poprawnym kodem Java.\\nMusisz rozwiązać te błędy, zanim zmiany zostaną zapisane.\\n\\nKilka sugestii:\\n - Zmień dekompilatory\\n - Najedź kursorem na czerwone pola błędów lub kliknij pole błędu u góry, aby zobaczyć, jakie są błędy\\n - Użyj asemblera zamiast rekompilacji, aby wprowadzić zmiany\njava.savewitherrors.title=Dotyczące błędów rekompilacji\njava.decompiler=Dekompilator\njava.targetversion=Docelowa wersja kompilacji\njava.targetversion.auto=Dopasuj do wersji pliku klasy\njava.targetdownsampleversion=Obniż wersję docelową\njava.targetdownsampleversion.disabled=Wyłączone\njava.targetdebug=Kompiluj z informacjami debugowania\njava.info=Informacje o klasie\njava.info.version=Wersja klasy\njava.info.sourcefile=Nazwa pliku źródłowego\n\n## Pasek wyszukiwania\nfind.replace=Zamień\nfind.replaceall=Zamień wszystkie\nfind.regexinvalid=Nieprawidłowe wyrażenie regularne\nfind.regexreplace=Tekst zastępczy\n\n## Pola i metody\nfieldsandmethods.title=Pola i metody\nfieldsandmethods.showoutlinedsynths=Pokaż elementy syntetyczne (wygenerowane przez kompilator)\nfieldsandmethods.showoutlinedvisibility=Filtruj według widoczności elementu\nfieldsandmethods.showoutlinedmembertype=Filtruj według typu elementu\nfieldsandmethods.sortalphabetically=Sortuj alfabetycznie\nfieldsandmethods.sortbyvisibility=Sortuj według widoczności\nfieldsandmethods.filter.prompt=Filtruj: Nazwa pola/metody...\n\n## Hierarchia\nhierarchy.title=Dziedziczenie\nhierarchy.children=Potomne\nhierarchy.parents=Nadrzędne\n\n## Logowanie\nlogging.title=Logowanie\n\n## Asembler\nassembler.title=Asembler\nassembler.analysis.title=Analiza\nassembler.analysis.stack=Stos\nassembler.analysis.variables=Zmienne\nassembler.analysis.type=Typ\nassembler.analysis.value=Wartość\nassembler.playground.title=Java do Bytecode\nassembler.playground.comment=// Napisz tutaj kod Java, aby automatycznie przekonwertować go na bytecode\\n// Możesz uzyskać dostęp do pól/metod bieżącej klasy,\\n// oraz parametrów/zmiennych bieżącej metody.\nassembler.variables.title=Zadeklarowane zmienne\nassembler.variables.name=Nazwa zmiennej\nassembler.variables.type=Typ\nassembler.variables.usage=Użycia\nassembler.variables.value=Wartość\nassembler.variables.empty=<Wymaga co najmniej jednokrotnej kompilacji>\nassembler.suggestions.none=Brak sugestii\n\n## Komentarze\ncomments.search.prompt=Szukaj komentarza...\n\n## Wyszukiwanie\nsearch.run=Szukaj\nsearch.results=Wyniki\nsearch.text=Zawartość tekstu\nsearch.textmode=Tryb dopasowania tekstu\nsearch.number=Wartość liczbowa\nsearch.numbermode=Tryb dopasowania liczby\nsearch.refowner=Właściciel elementu\nsearch.refname=Nazwa elementu\nsearch.refdesc=Deskryptor typu elementu\n\n## Pomoc\nhelp.system=System\nhelp.system.sub=Informacje o systemie operacyjnym\nhelp.java=Java\nhelp.java.sub=Informacje o JVM\nhelp.javafx=JavaFX\nhelp.javafx.sub=Informacje o interfejsie użytkownika JavaFX\nhelp.recaf=Recaf\nhelp.recaf.sub=Informacje o Recafie\nhelp.copy=Kopiuj informacje do schowka\nhelp.opendir=Otwórz katalog Recafa\n\n## Generator mapowania\nmapgen=Generator mapowania\nmapgen.genimpl=Konwencja nazewnictwa\nmapgen.filter.name=Nazwa\nmapgen.filter.class-name=Nazwa klasy\nmapgen.filter.owner-name=Nazwa właściciela\nmapgen.filter.field-name=Nazwa pola\nmapgen.filter.method-name=Nazwa metody\nmapgen.filters=Filtry\nmapgen.filters.add=Dodaj filtr\nmapgen.filters.edit=Edytuj zaznaczone\nmapgen.filters.editdone=Gotowe\nmapgen.filters.delete=Usuń zaznaczone\nmapgen.filters.type=Typ filtra\nmapgen.filter.modifiers.tooltip=Modyfikatory są oddzielone spacjami\nmapgen.filter.excludealreadymapped=Wyklucz już zmapowane\nmapgen.filter.excludemodifier=Wyklucz modyfikatory\nmapgen.filter.excludeclasses=Wyklucz klasy\nmapgen.filter.excludename=Wyklucz nazwy\nmapgen.filter.excludeclass=Wyklucz w klasach\nmapgen.filter.excludefield=Wyklucz w polach\nmapgen.filter.excludemethod=Wyklucz w metodach\nmapgen.filter.includemodifier=Uwzględnij modyfikatory\nmapgen.filter.includeclass=Uwzględnij w klasach\nmapgen.filter.includefield=Uwzględnij w polach\nmapgen.filter.includemethod=Uwzględnij w metodach\nmapgen.filter.includewhitespacenames=Uwzględnij spacje\nmapgen.filter.includenonasciinames=Uwzględnij znaki spoza ASCII\nmapgen.filter.includekeywords=Uwzględnij słowa kluczowe\nmapgen.filter.includelong=Uwzględnij długie nazwy\nmapgen.filter.includename=Uwzględnij nazwy\nmapgen.filter.includeclasses=Uwzględnij klasy\nmapgen.title.newfilter=Nowy filtr\nmapgen.header.newfilter=Wprowadź zawartość filtra\nmapgen.preview.empty=Statystyki wygenerowanych mapowań pojawią się tutaj\\n\\n\\n\nmapgen.configure=Konfiguruj\nmapgen.configure.nothing=Nic do skonfigurowania\nmapgen.generate=Generuj\nmapgen.apply=Zastosuj\n\n## Widok mapowania\nmapprog=Postęp mapowania\nmapprog.metric.size=Rozmiar pliku klasy\nmapprog.metric.membercount=Pola i metody klasy\n\n# Drzewo\ntree.classes=Klasy\ntree.files=Pliki\ntree.defaultpackage=(Pakiet domyślny)\ntree.defaultdirectory=(Katalog główny)\ntree.prompt=Przeciągnij tutaj swoje pliki\ntree.hidelibs=Ukryj biblioteki\ntree.phantoms=Wygenerowane fantomy\n\n# Usługi\nservice=Wszystkie usługi\nservice.analysis=Analiza\nservice.analysis.comments-config=Komentarze\nservice.analysis.comments-config.enable-display=Wyświetlaj komentarze w dekompilacji\nservice.analysis.comments-config.word-wrapping-limit=Limit zawijania słów\nservice.analysis.graph-calls-config=Graf wywołań\nservice.analysis.graph-inheritance-config=Graf dziedziczenia\nservice.analysis.jphantom-generator-config=JPhantom\nservice.analysis.jphantom-generator-config.generate-workspace-phantoms=Generuj i dołącz fantomy do obszarów roboczych\nservice.analysis.search-config=Wyszukiwanie\nservice.analysis.entry-points=Punkty wejścia\nservice.analysis.entry-points.none=Nie znaleziono wpisów\nservice.analysis.anti-decompile=Antydekompilacja\nservice.analysis.anti-decompile.illegal-name=Nieprawidłowe nazwy\nservice.analysis.anti-decompile.label-patch=Popraw %d klas\nservice.assembler=Asembler\nservice.assembler.assembler-pipeline.general-config=Ogólne\nservice.assembler.assembler-pipeline.general-config.disassembly-ast-parse-delay=Opóźnienie parsowania AST\nservice.assembler.assembler-pipeline.general-config.disassembly-indent=Wcięcia\nservice.assembler.dalvik-assembler-config=Dalvik\nservice.assembler.dalvik-assembler-config.value-analysis=Włącz analizę wartości\nservice.assembler.dalvik-assembler-config.simulate-jvm-calls=Symuluj typowe wywołania JVM\nservice.assembler.jvm-assembler-config=JVM\nservice.assembler.jvm-assembler-config.value-analysis=Włącz analizę wartości\nservice.assembler.jvm-assembler-config.simulate-jvm-calls=Symuluj typowe wywołania JVM\nservice.compile=Kompilacja\nservice.compile.java-compiler-config=Javac\nservice.compile.java-compiler-config.generate-phantoms=Generuj brakujące klasy\nservice.compile.java-compiler-config.default-emit-debug=Domyślnie dołączaj debugowanie\nservice.compile.java-compiler-config.default-compile-target-version=Domyślna docelowa wersja klasy\nservice.compile.java-compiler-config.default-downsample-target-version=Domyślna obniżona wersja klasy\nservice.debug=Dołącz/Debuguj\nservice.debug.attach-config=Konfiguracja dołączania\nservice.debug.attach-config.attach-jmx-bean-agent=Dołącz agenta JMX bean\nservice.debug.attach-config.passive-scanning=Stan skanowania pasywnego\nservice.config-manager-config=Menedżer konfiguracji\nservice.decompile=Dekompilacja\nservice.decompile.decompilers-config=Menedżer dekompilacji\nservice.decompile.decompilers-config.pref-android-decompiler=Preferowany dekompilator Androida\nservice.decompile.decompilers-config.pref-jvm-decompiler=Preferowany dekompilator Javy\nservice.decompile.decompilers-config.cache-decompilations=Buforuj dekompilacje\nservice.decompile.decompilers-config.filter-annotations-duplicate=Filtruj zduplikowane adnotacje\nservice.decompile.decompilers-config.filter-annotations-illegal=Filtruj nieprawidłowe adnotacje\nservice.decompile.decompilers-config.filter-annotations-long=Filtruj długie adnotacje\nservice.decompile.decompilers-config.filter-annotations-long-limit=Limit długości adnotacji\nservice.decompile.decompilers-config.filter-hollow=Filtruj zawartość klasy (wydrążona)\nservice.decompile.decompilers-config.filter-illegal-signatures=Filtruj nieprawidłowe sygnatury\nservice.decompile.decompilers-config.filter-names-ascii=Filtruj nazwy spoza ASCII\nservice.decompile.decompilers-config.filter-strip-debug=Filtruj dane debugowania (zmienne, generyki)\nservice.decompile.impl=Implementacje\nservice.decompile.impl.decompiler-cfr-config=CFR\nservice.decompile.impl.decompiler-cfr-config.aexagg=Próbuj bardziej agresywnie rozszerzać i łączyć wyjątki\nservice.decompile.impl.decompiler-cfr-config.aexagg2=Próbuj bardziej agresywnie rozszerzać i łączyć wyjątki (może zmienić semantykę)\nservice.decompile.impl.decompiler-cfr-config.aggressivedocopy=Klonuj kod z niemożliwych skoków do pętli z testem 'first'\nservice.decompile.impl.decompiler-cfr-config.aggressivedoextension=Zwiń niemożliwe skoki do pętli do-while z testem 'first'\nservice.decompile.impl.decompiler-cfr-config.aggressiveduff=Zwiń przełączniki w stylu urządzenia duff z dodatkową kontrolą.\nservice.decompile.impl.decompiler-cfr-config.aggressivesizethreshold=Liczba rozkazów, przy której uruchamiane są agresywne redukcje\nservice.decompile.impl.decompiler-cfr-config.allowmalformedswitch=Zezwalaj na potencjalnie nieprawidłowe instrukcje switch\nservice.decompile.impl.decompiler-cfr-config.antiobf=Cofnij różne zaciemnienia\nservice.decompile.impl.decompiler-cfr-config.arrayiter=Przekształć iterację opartą na tablicach\nservice.decompile.impl.decompiler-cfr-config.collectioniter=Przekształć iterację opartą na kolekcjach\nservice.decompile.impl.decompiler-cfr-config.commentmonitors=Zastąp monitory komentarzami - przydatne, jeśli jesteśmy całkowicie zdezorientowani\nservice.decompile.impl.decompiler-cfr-config.comments=Wypisz komentarze opisujące stan dekompilatora, flagi fallback itp.\nservice.decompile.impl.decompiler-cfr-config.constobf=Cofnij zaciemnianie stałych\nservice.decompile.impl.decompiler-cfr-config.decodeenumswitch=Przekształć switch na enum\nservice.decompile.impl.decompiler-cfr-config.decodefinally=Przekształć instrukcje finally\nservice.decompile.impl.decompiler-cfr-config.decodelambdas=Odtwórz funkcje lambda\nservice.decompile.impl.decompiler-cfr-config.decodestringswitch=Przekształć switch na String\nservice.decompile.impl.decompiler-cfr-config.eclipse=Włącz transformacje, aby lepiej obsługiwać kod Eclipse\nservice.decompile.impl.decompiler-cfr-config.elidescala=Usuń rzeczy, które nie są pomocne w wyjściu scala (serialVersionUID, @ScalaSignature)\nservice.decompile.impl.decompiler-cfr-config.forbidanonymousclasses=Nie zezwalaj na anonimowe klasy.\nservice.decompile.impl.decompiler-cfr-config.forbidmethodscopedclasses=Nie zezwalaj na klasy o zasięgu metody.\nservice.decompile.impl.decompiler-cfr-config.forceclassfilever=Wymuś wersję pliku klasy (a tym samym Java), jako którą klasy są dekompilowane.\nservice.decompile.impl.decompiler-cfr-config.forcecondpropagate=Przenieś wyniki deterministycznych skoków z powrotem przez niektóre przypisania stałych\nservice.decompile.impl.decompiler-cfr-config.forceexceptionprune=Usuń zagnieżdżone programy obsługi wyjątków, jeśli nie zmieniają semantyki\nservice.decompile.impl.decompiler-cfr-config.forcereturningifs=Przenieś return do miejsca skoku\nservice.decompile.impl.decompiler-cfr-config.forcetopsort=Wymuś sortowanie bloków podstawowych. Zwykle przydatne tylko w przypadku zaciemnienia.\nservice.decompile.impl.decompiler-cfr-config.forcetopsortaggress=Wymuś dodatkowe agresywne opcje sortowania\nservice.decompile.impl.decompiler-cfr-config.forcetopsortnopull=Wymuś, aby sortowanie nie wyciągało bloków try\nservice.decompile.impl.decompiler-cfr-config.forloopaggcapture=Pozwól pętlom for na agresywne przenoszenie mutacji do sekcji aktualizacji, nawet jeśli nie wydają się być związane z predykatem\nservice.decompile.impl.decompiler-cfr-config.hidebridgemethods=Ukryj metody pomostowe\nservice.decompile.impl.decompiler-cfr-config.hidelangimports=Ukryj importy z java.lang.\nservice.decompile.impl.decompiler-cfr-config.hidelongstrings=Ukryj bardzo długie ciągi znaków - przydatne, jeśli zaciemniacze umieścili fałszywy kod w ciągach znaków\nservice.decompile.impl.decompiler-cfr-config.hideutf=Ukryj znaki UTF8 - umieść je w cudzysłowie zamiast pokazywać surowe znaki\nservice.decompile.impl.decompiler-cfr-config.ignoreexceptions=Upuść informacje o wyjątku, jeśli całkowicie utkniesz (UWAGA: zmienia semantykę, niebezpieczne!)\nservice.decompile.impl.decompiler-cfr-config.ignoreexceptionsalways=Upuść informacje o wyjątku (UWAGA: zmienia semantykę, niebezpieczne!)\nservice.decompile.impl.decompiler-cfr-config.innerclasses=Dekompiluj klasy wewnętrzne\nservice.decompile.impl.decompiler-cfr-config.instanceofpattern=Przekształć dopasowania wzorców instanceof\nservice.decompile.impl.decompiler-cfr-config.j14classobj=Odwróć konstrukcję obiektu klasy Java 1.4\nservice.decompile.impl.decompiler-cfr-config.labelledblocks=Zezwalaj na emitowanie kodu, który używa oznaczonych bloków (obsługa dziwnych skoków do przodu)\nservice.decompile.impl.decompiler-cfr-config.lenient=Bądź trochę bardziej pobłażliwy w sytuacjach, w których normalnie wyrzucilibyśmy wyjątek\nservice.decompile.impl.decompiler-cfr-config.liftconstructorinit=Podnieś kod inicjalizacji wspólny dla wszystkich konstruktorów do inicjalizacji elementów\nservice.decompile.impl.decompiler-cfr-config.obfattr=Cofnij zaciemnianie atrybutów\nservice.decompile.impl.decompiler-cfr-config.obfcontrol=Cofnij zaciemnianie przepływu sterowania\nservice.decompile.impl.decompiler-cfr-config.override=Generuj adnotacje @Override (jeśli metoda implementuje metodę interfejsu lub przesłania metodę klasy bazowej)\nservice.decompile.impl.decompiler-cfr-config.previewfeatures=Dekompiluj funkcje podglądu, jeśli klasa została skompilowana z 'javac --enable-preview'\nservice.decompile.impl.decompiler-cfr-config.pullcodecase=Agresywnie wciągaj kod do instrukcji case\nservice.decompile.impl.decompiler-cfr-config.recordtypes=Przekształć typy rekordów\nservice.decompile.impl.decompiler-cfr-config.recover=Pozwól na ustawienie coraz bardziej agresywnych opcji, jeśli dekompilacja się nie powiedzie\nservice.decompile.impl.decompiler-cfr-config.recovertypeclash=Podziel okresy istnienia, w których analiza spowodowała konflikt typów\nservice.decompile.impl.decompiler-cfr-config.recovertypehints=Odzyskaj wskazówki dotyczące typów dla iteratorów z pierwszego przejścia\nservice.decompile.impl.decompiler-cfr-config.reducecondscope=Zmniejsz zakres warunków, ewentualnie generując więcej anonimowych bloków\nservice.decompile.impl.decompiler-cfr-config.relinkconst=Połącz ponownie stałe - jeśli istnieje wbudowane odwołanie do pola, spróbuj je usunąć.\nservice.decompile.impl.decompiler-cfr-config.relinkconststring=Połącz ponownie stałe ciągi znaków - jeśli istnieje lokalne odwołanie do ciągu znaków, który pasuje do static final, użyj static final.\nservice.decompile.impl.decompiler-cfr-config.removebadgenerics=Ukryj generyki, w których ewidentnie się pomyliliśmy i wróć do niegenerycznych\nservice.decompile.impl.decompiler-cfr-config.removeboilerplate=Usuń sztampowe funkcje - sztampowy konstruktor, deserializacja lambda itp.\nservice.decompile.impl.decompiler-cfr-config.removedeadconditionals=Usuń kod, którego nie można wykonać.\nservice.decompile.impl.decompiler-cfr-config.removedeadmethods=Usuń bezsensowne metody - domyślny konstruktor itp.\nservice.decompile.impl.decompiler-cfr-config.removeinnerclasssynthetics=Usuń (jeśli to możliwe) niejawne odwołania do klas zewnętrznych w klasach wewnętrznych\nservice.decompile.impl.decompiler-cfr-config.renamedupmembers=Zmień nazwę niejednoznacznych/duplikatów pól.\nservice.decompile.impl.decompiler-cfr-config.renameenumidents=Zmień nazwę identyfikatorów ENUM, które nie pasują do ich \"oczekiwanych\" nazw ciągów.\nservice.decompile.impl.decompiler-cfr-config.renameillegalidents=Zmień nazwę identyfikatorów, które nie są prawidłowymi identyfikatorami Java.\nservice.decompile.impl.decompiler-cfr-config.renamesmallmembers=Zmień nazwę małych elementów. Uwaga - spowoduje to przerwanie dostępu opartego na odbiciu, więc nie jest automatycznie włączone.\nservice.decompile.impl.decompiler-cfr-config.sealed=Dekompiluj konstrukcje 'sealed'\nservice.decompile.impl.decompiler-cfr-config.showinferrable=Udekoruj metody jawnymi typami, jeśli nie są one sugerowane przez argumenty\nservice.decompile.impl.decompiler-cfr-config.showversion=Pokaż używaną wersję CFR w nagłówku (przydatne do wyłączenia podczas testowania regresji)\nservice.decompile.impl.decompiler-cfr-config.skipbatchinnerclasses=Podczas przetwarzania wielu plików pomiń klasy wewnętrzne, ponieważ i tak będą przetwarzane jako część klas zewnętrznych.\nservice.decompile.impl.decompiler-cfr-config.staticinitreturn=Spróbuj usunąć return ze statycznej inicjalizacji\nservice.decompile.impl.decompiler-cfr-config.stringbuffer=Konwertuj new StringBuffer().append.append.append na string + string + string\nservice.decompile.impl.decompiler-cfr-config.stringbuilder=Konwertuj new StringBuilder().append.append.append na string + string + string\nservice.decompile.impl.decompiler-cfr-config.stringconcat=Konwertuj użycia StringConcatFactor na string + string + string\nservice.decompile.impl.decompiler-cfr-config.sugarasserts=Przekształć wywołania assert\nservice.decompile.impl.decompiler-cfr-config.sugarboxing=W miarę możliwości usuń bezsensowne opakowania boxing\nservice.decompile.impl.decompiler-cfr-config.sugarenums=Przekształć enumy\nservice.decompile.impl.decompiler-cfr-config.sugarretrolambda=W miarę możliwości przekształć użycia retro lambda\nservice.decompile.impl.decompiler-cfr-config.switchexpression=Przekształć wyrażenie switch\nservice.decompile.impl.decompiler-cfr-config.tidymonitors=Usuń kod pomocniczy dla monitorów - np. bloki catch tylko po to, aby wyjść z monitora\nservice.decompile.impl.decompiler-cfr-config.tryresources=Rekonstruuj try-with-resources\nservice.decompile.impl.decompiler-cfr-config.usenametable=Użyj tabeli nazw zmiennych lokalnych, jeśli istnieje\nservice.decompile.impl.decompiler-cfr-config.usesignatures=Używaj sygnatur oprócz deskryptorów (gdy nie są one ewidentnie niepoprawne)\nservice.decompile.impl.decompiler-cfr-config.version=Pokaż aktualną wersję CFR\nservice.decompile.impl.decompiler-procyon-config=Procyon\nservice.decompile.impl.decompiler-procyon-config.alwaysGenerateExceptionVariableForCatchBlocks=Zawsze generuj blok catch\nservice.decompile.impl.decompiler-procyon-config.arePreviewFeaturesEnabled=Włącz funkcje podglądu języka\nservice.decompile.impl.decompiler-vineflower-config=Vineflower\nservice.decompile.impl.decompiler-vineflower-config.logging-level=Poziom logowania\nservice.decompile.impl.decompiler-vineflower-config.remove-bridge=Usuń metody pomostowe\nservice.decompile.impl.decompiler-vineflower-config.remove-synthetic=Usuń metody i pola syntetyczne\nservice.decompile.impl.decompiler-vineflower-config.decompile-inner=Dekompiluj klasy wewnętrzne\nservice.decompile.impl.decompiler-vineflower-config.decompile-java4=Dekompiluj odwołania do klas Java 4\nservice.decompile.impl.decompiler-vineflower-config.decompile-assert=Dekompiluj asercje\nservice.decompile.impl.decompiler-vineflower-config.hide-empty-super=Ukryj puste super()\nservice.decompile.impl.decompiler-vineflower-config.hide-default-constructor=Ukryj domyślny konstruktor\nservice.decompile.impl.decompiler-vineflower-config.decompile-generics=Dekompiluj generyki\nservice.decompile.impl.decompiler-vineflower-config.incorporate-returns=Brak wyjątków w return\nservice.decompile.impl.decompiler-vineflower-config.ensure-synchronized-monitors=Upewnij się, że zakresy synchronizowane są kompletne\nservice.decompile.impl.decompiler-vineflower-config.decompile-enums=Dekompiluj wyliczenia\nservice.decompile.impl.decompiler-vineflower-config.decompile-preview=Dekompiluj funkcje podglądu\nservice.decompile.impl.decompiler-vineflower-config.remove-getclass=Usuń odwołanie getClass()\nservice.decompile.impl.decompiler-vineflower-config.keep-literals=Zachowaj literały bez zmian\nservice.decompile.impl.decompiler-vineflower-config.boolean-as-int=Reprezentuj boolean jako 0/1\nservice.decompile.impl.decompiler-vineflower-config.ascii-strings=Znaki ASCII w ciągach\nservice.decompile.impl.decompiler-vineflower-config.synthetic-not-set=Syntetyczne nie ustawione\nservice.decompile.impl.decompiler-vineflower-config.undefined-as-object=Traktuj niezdefiniowany typ parametru jako obiekt\nservice.decompile.impl.decompiler-vineflower-config.use-lvt-names=Używaj nazw LVT\nservice.decompile.impl.decompiler-vineflower-config.use-method-parameters=Używaj parametrów metody\nservice.decompile.impl.decompiler-vineflower-config.remove-empty-try-catch=Usuń puste bloki try-catch\nservice.decompile.impl.decompiler-vineflower-config.decompile-finally=Dekompiluj bloki finally\nservice.decompile.impl.decompiler-vineflower-config.lambda-to-anonymous-class=Dekompiluj lambdy jako klasy anonimowe\nservice.decompile.impl.decompiler-vineflower-config.bytecode-source-mapping=Mapowanie bytecode na źródło\nservice.decompile.impl.decompiler-vineflower-config.__dump_original_lines__=Zrzuć linie kodu\nservice.decompile.impl.decompiler-vineflower-config.ignore-invalid-bytecode=Ignoruj nieprawidłowy bytecode\nservice.decompile.impl.decompiler-vineflower-config.verify-anonymous-classes=Weryfikuj klasy anonimowe\nservice.decompile.impl.decompiler-vineflower-config.ternary-constant-simplification=Uproszczenie stałych trójskładnikowych\nservice.decompile.impl.decompiler-vineflower-config.pattern-matching=Dopasowywanie wzorców\nservice.decompile.impl.decompiler-vineflower-config.try-loop-fix=Naprawianie pętli try\nservice.decompile.impl.decompiler-vineflower-config.ternary-in-if=Trójskładnikowe w warunkach if (eksperymentalne)\nservice.decompile.impl.decompiler-vineflower-config.decompile-switch-expressions=Dekompiluj wyrażenia switch\nservice.decompile.impl.decompiler-vineflower-config.show-hidden-statements=Pokaż ukryte instrukcje (debugowanie)\nservice.decompile.impl.decompiler-vineflower-config.override-annotation=Adnotacja Override\nservice.decompile.impl.decompiler-vineflower-config.simplify-stack=Uproszczenie stosu w drugim przebiegu\nservice.decompile.impl.decompiler-vineflower-config.verify-merges=Weryfikuj scalanie zmiennych (eksperymentalne)\nservice.decompile.impl.decompiler-vineflower-config.include-classpath=Uwzględnij całą ścieżkę klasy\nservice.decompile.impl.decompiler-vineflower-config.include-runtime=Uwzględnij środowisko wykonawcze Java\nservice.decompile.impl.decompiler-vineflower-config.explicit-generics=Jawne argumenty generyczne\nservice.decompile.impl.decompiler-vineflower-config.inline-simple-lambdas=Wstaw proste lambdy\nservice.decompile.impl.decompiler-vineflower-config.rename-members=Poziom logowania\nservice.decompile.impl.decompiler-vineflower-config.mpm=Maksymalna liczba przetwarzanych metod\nservice.decompile.impl.decompiler-vineflower-config.ren=Zmień nazwy jednostek / elementów\nservice.decompile.impl.decompiler-vineflower-config.user-renamer-class=Klasa zmiany nazw użytkownika\nservice.decompile.impl.decompiler-vineflower-config.indent-string=Ciąg wcięcia\nservice.decompile.impl.decompiler-vineflower-config.pll=Preferowana długość linii\nservice.decompile.impl.decompiler-vineflower-config.banner=Klasa zmiany nazw użytkownika\nservice.decompile.impl.decompiler-vineflower-config.error-message=Komunikat o błędzie\nservice.decompile.impl.decompiler-vineflower-config.thread-count=Liczba wątków\nservice.decompile.impl.decompiler-vineflower-config.skip-extra-files=Pomiń dodatkowe pliki\nservice.decompile.impl.decompiler-vineflower-config.warn-inconsistent-inner-attributes=Ostrzegaj o niespójnych atrybutach wewnętrznych\nservice.decompile.impl.decompiler-vineflower-config.dump-bytecode-on-error=Zrzuć bytecode w przypadku błędu\nservice.decompile.impl.decompiler-vineflower-config.dump-exception-on-error=Zrzuć wyjątki w przypadku błędu\nservice.decompile.impl.decompiler-vineflower-config.decompiler-comments=Komentarze dekompilatora\nservice.decompile.impl.decompiler-vineflower-config.sourcefile-comments=Komentarze do pliku źródłowego\nservice.decompile.impl.decompiler-vineflower-config.decompile-complex-constant-dynamic=Dekompiluj złożone wyrażenia stało-dynamiczne\nservice.decompile.impl.decompiler-vineflower-config.force-jsr-inline=Wymuś wstawienie JSR\nservice.io=We/Wy\nservice.io.directories-config=Katalogi\nservice.io.export-config=Eksportowanie\nservice.io.export-config.bundle-supporting-resources=Dołącz zasoby pomocnicze do wyjścia\nservice.io.export-config.compression=Strategia kompresji dla zawartości wyjścia\nservice.io.export-config.create-zip-dir-entries=Utwórz wpisy katalogów ZIP w wyjściu\nservice.io.export-config.warn-no-changes=Ostrzegaj o eksporcie bez wprowadzonych zmian\nservice.io.gson-provider-config=Json\nservice.io.gson-provider-config.pretty-print=Ładne drukowanie\nservice.io.info-importer-config=Importowanie zawartości\nservice.io.info-importer-config.skip-class-asm-validation=Pomiń poprawki i walidację klasy\nservice.io.recent-workspaces-config=Ostatnie obszary robocze\nservice.io.recent-workspaces-config.last-workspace-export-path=Ostatnia ścieżka eksportu obszaru roboczego\nservice.io.recent-workspaces-config.last-workspace-open-path=Ostatnia ścieżka otwierania obszaru roboczego\nservice.io.recent-workspaces-config.max-recent-workspaces=Maksymalna liczba ostatnich ścieżek\nservice.io.recent-workspaces-config.recent-workspaces=Ostatni obszar roboczy\nservice.io.recent-workspaces-config.last-class-export-path=Ostatnia ścieżka eksportu klasy\nservice.io.resource-importer-config=Importowanie archiwum\nservice.io.resource-importer-config.zip-strategy=Strategia parsowania ZIP\nservice.io.resource-importer-config.skip-revisited-cen-to-local-links=Pomiń duplikaty wpisów CEN-to-LFH ze strategią JVM\nservice.mapping=Mapowanie\nservice.mapping.mapping-aggregator-config=Agregacja mapowania\nservice.mapping.mapping-formats-config=Formaty mapowania\nservice.mapping.mapping-generator-config=Generator mapowania\nservice.mapping.name-gen-provider=Generatory nazw\nservice.mapping.name-gen-provider.alphabet=Alfabet\nservice.mapping.name-gen-provider.alphabet.alphabet=Znaki alfabetu\nservice.mapping.name-gen-provider.alphabet.length=Minimalna długość\nservice.plugin=Wtyczki\nservice.plugin.plugin-manager-config=Menedżer wtyczek\nservice.plugin.plugin-manager-config.scan-on-start=Ładuj przy uruchomieniu\nservice.plugin.script-manager-config=Menedżer skryptów\nservice.plugin.script-manager-config.file-watching=Pasywnie skanuj katalog skryptów w poszukiwaniu zmian\nservice.ui=Interfejs użytkownika\nservice.ui.bind-config=Skróty klawiszowe\nservice.ui.bind-config.bundle=Pakiet mapy skrótów klawiszowych\nservice.ui.class-editing-config=Edycja klasy\nservice.ui.class-editing-config.default-android-editor=Domyślny edytor dla klas Androida\nservice.ui.class-editing-config.default-jvm-editor=Domyślny edytor dla klas JVM\nservice.ui.decompile-pane-config=Panel dekompilacji\nservice.ui.decompile-pane-config.timeout-seconds=Limit czasu dekompilatora (sekundy)\nservice.ui.decompile-pane-config.mapping-acceleration=Przyspiesz operacje remapowania\nservice.ui.member-format-config=Format pola i metody\nservice.ui.member-format-config.name-type-display=Wyświetlanie nazwy i typu\nservice.ui.text-format-config=Format tekstu\nservice.ui.text-format-config.escape=Włącz escape'y tekstu\nservice.ui.text-format-config.max-length=Maksymalna długość wyświetlanego tekstu\nservice.ui.text-format-config.shorten=Włącz skracanie tekstu\nservice.ui.file-type-association-config=Powiązania typów plików\nservice.ui.file-type-association-config.extensions-to-langs=Mapa rozszerzenia do języka\nservice.ui.window-manager-config=Menedżer okien\nservice.ui.workspace-explorer-config=Eksplorator obszaru roboczego\nservice.ui.workspace-explorer-config.drag-drop-action=Zachowanie przeciągnij i upuść\nservice.ui.workspace-explorer-config.max-tree-dir-depth=Maksymalna głębokość drzewa\nservice.ui.language-config=Język\nservice.ui.language-config.current=Aktualny język\n\n# Tłumaczenia dopasowań\nnumber.match.equal=wartość == n\nnumber.match.not=wartość != n\nnumber.match.gt=wartość > n\nnumber.match.gte=wartość >= n\nnumber.match.lt=wartość < n\nnumber.match.lte=wartość <= n\nnumber.match.gt-lt=min < wartość < max\nnumber.match.gte-lt=min <= wartość < max\nnumber.match.gt-lte=min < wartość <= max\nnumber.match.gte-lte=min < wartość <= max\nnumber.match.any-of=numbers.contains(wartość)\nstring.match.contains=str.contains(wartość)\nstring.match.contains-ic=str.containsIgnoreCase(wartość)\nstring.match.ends=str.endsWith(wartość)\nstring.match.ends-ic=str.endsWithIgnoreCase(wartość)\nstring.match.equal=str.equals(wartość)\nstring.match.equal-ic=str.equalsIgnoreCase(wartość)\nstring.match.regex-full=str.matches(wartość)\nstring.match.regex-partial=str.matchesPartially(wartość)\nstring.match.starts=str.startsWith(wartość)\nstring.match.starts-ic=str.startsWithIgnoreCase(wartość)\n\n# Różne\nmisc.acknowledge=Potwierdź\nmisc.all=Wszystkie\nmisc.none=Brak\nmisc.ignored=Ignorowane\nmisc.enabled=Włączone\nmisc.disabled=Wyłączone\nmisc.download=Pobierz\nmisc.clear=Wyczyść\nmisc.export=Eksportuj\nmisc.casesensitive=Rozróżnianie wielkości liter\nmisc.path=Ścieżka\nmisc.regex=Wyrażenie regularne\nmisc.member.field=Pole\nmisc.member.method=Metoda\nmisc.member.field-n-method=Pole i metoda\nmisc.member.inner-class=Klasa wewnętrzna\nmisc.member.inner-interface=Interfejs wewnętrzny\nmisc.member.inner-enum=Wyliczenie wewnętrzne\nmisc.member.inner-annotation=Adnotacja wewnętrzna\nmisc.accessflag.visibility.public=Publiczny\nmisc.accessflag.visibility.protected=Chroniony\nmisc.accessflag.visibility.private=Prywatny\nmisc.accessflag.visibility.package=Pakiet\nmisc.direction.up=W górę\nmisc.direction.down=W dół\nmisc.direction.left=W lewo\nmisc.direction.right=W prawo\nmisc.direction.top=Góra\nmisc.direction.bottom=Dół\nmisc.position.top=Góra\nmisc.position.bottom=Dół\nmisc.position.left=Lewo\nmisc.position.right=Prawo\nmisc.position.center=Środek\nmisc.position.middle=Środe"
  },
  {
    "path": "recaf-ui/src/main/resources/translations/ru_RU.lang",
    "content": "## Language name\nlang.name=Русский\n\n##### General items\n## Main and context menus\nmenu.analysis=Анализ\nmenu.analysis.summary=Просмотр сводки\nmenu.analysis.deobfuscation=Деобфускация\nmenu.analysis.list-comments=Просмотр комментариев\nmenu.analysis.comment=Комментарий\nmenu.association.override=Переопределить язык\nmenu.association.none=Ассоциации не настроены\nmenu.config=Настройки\nmenu.config.edit=Редактировать\nmenu.config.export=Экспорт\nmenu.config.import=Импорт\nmenu.file=Файл\nmenu.file.attach=Подключиться к удалённому\nmenu.file.addtoworkspace=Добавить в рабочее пространство\nmenu.file.decompileall=Декомпилировать все классы\nmenu.file.decompileall.path=Путь вывода:\nmenu.file.openworkspace=Открыть рабочее пространство\nmenu.file.openurl=Открыть по URL\nmenu.file.exportapp=Экспорт приложения\nmenu.file.exportworkspace=Экспорт конфигурации рабочего пространства\nmenu.file.modifications=Просмотр изменений\nmenu.file.recent=Недавние\nmenu.file.close=Закрыть\nmenu.file.quit=Выход\nmenu.goto.class=Перейти к классу\nmenu.goto.field=Перейти к полю\nmenu.goto.method=Перейти к методу\nmenu.goto.instruction=Перейти к инструкции\nmenu.goto.file=Перейти к файлу\nmenu.goto.label=Перейти к метке\nmenu.edit=Редактировать\nmenu.edit.add.field=Добавить поле\nmenu.edit.add.method=Добавить метод\nmenu.edit.add.annotation=Добавить аннотацию\nmenu.edit.override.method=Переопределить метод\nmenu.edit.remove.field=Удалить поля\nmenu.edit.remove.method=Удалить методы\nmenu.edit.remove.annotation=Удалить аннотации\nmenu.edit.assemble.class=Редактировать класс в ассемблере\nmenu.edit.assemble.field=Редактировать поле в ассемблере\nmenu.edit.assemble.method=Редактировать метод в ассемблере\nmenu.edit.remove=Удалить\nmenu.edit.copy=Копировать\nmenu.edit.delete=Удалить\nmenu.edit.noop=Сделать no-op\nmenu.edit.removevars=Очистить информацию о переменных\nmenu.edit.changeversion=Изменить версии классов\nmenu.edit.changeversion.up=Обновить\nmenu.edit.changeversion.down=Понизить\nmenu.edit.newclass=Новый класс\nmenu.export.class=Экспорт класса\nmenu.export.file=Экспорт файла\nmenu.export.classes=Экспорт классов\nmenu.export.files=Экспорт файлов\nmenu.export.package=Экспорт пакета\nmenu.export.directory=Экспорт директории\nmenu.help=Справка\nmenu.help.discord=Discord\nmenu.help.docs=Онлайн документация пользователя\nmenu.help.docsdev=Онлайн документация разработчика\nmenu.help.github=Github\nmenu.help.issues=Трекер проблем\nmenu.help.sysinfo=Информация о системе\nmenu.help.tutorial=Обучение\nmenu.refactor=Рефакторинг\nmenu.refactor.move=Переместить\nmenu.refactor.rename=Переименовать\nmenu.search=Поиск\nmenu.search.string=Строки\nmenu.search.number=Числа\nmenu.search.class.member-declarations=Объявления членов\nmenu.search.class.member-references=Ссылки на членов\nmenu.search.class.type-references=Ссылки на типы\nmenu.search.class.instruction=Дизассемблирование инструкций\nmenu.search.method-overrides=Переопределения методов\nmenu.search.method-references=Ссылки на методы\nmenu.search.field-references=Ссылки на поля\nmenu.search.noresults=Нет результатов\nmenu.mappings=Маппинги\nmenu.mappings.apply=Применить\nmenu.mappings.apply-advanced=Расширенное применение\nmenu.mappings.export=Экспорт\nmenu.mappings.export.unsupported=%s (Не поддерживается)\nmenu.mappings.generate=Сгенерировать\nmenu.mappings.view=Текущие маппинги\nmenu.scripting=Скрипты\nmenu.scripting.list=Скрипты\nmenu.scripting.none-found=Скрипты не найдены\nmenu.scripting.manage=Управление скриптами\nmenu.scripting.new=Новый скрипт\nmenu.scripting.edit=Редактировать\nmenu.scripting.browse=Просмотр скриптов\nmenu.scripting.save=Сохранить скрипт\nmenu.scripting.execute=Выполнить\nmenu.scripting.editor=Редактор скриптов\nmenu.scripting.author=Автор\nmenu.scripting.version=Версия\nmenu.view=Вид\nmenu.view.hierarchy=Иерархия классов\nmenu.view.hierarchy.children=Дочерние\nmenu.view.hierarchy.parents=Родительские\nmenu.view.methodcfg=Граф потока управления\nmenu.view.methodcallgraph=Граф вызовов\nmenu.view.methodcallgraph.calls=Вызовы\nmenu.view.methodcallgraph.callers=Вызывающие\nmenu.view.methodcallgraph.focus=Сфокусироваться на методе\nmenu.tab.close=Закрыть\nmenu.tab.closeothers=Закрыть остальные\nmenu.tab.closeall=Закрыть все\nmenu.tab.copypath=Копировать путь\nmenu.image.resetscale=Сбросить масштаб\nmenu.image.center=Центрировать изображение\nmenu.hex.copyas=Копировать как...\nmenu.mode=Изменить вид\nmenu.mode.class.auto=Автоматически\nmenu.mode.class.decompile=Декомпилировать\nmenu.mode.class.low-level=Низкоуровневый\nmenu.mode.file.auto=Автоматически\nmenu.mode.file.text=Текст\nmenu.mode.file.hex=Hex\nmenu.mode.file.image=Изображение\nmenu.mode.file.audio=Аудио\nmenu.mode.file.video=Видео\nmenu.mode.file.pe=Windows PE\nmenu.mode.file.elf=ELF\nmenu.mode.diff.decompile=Декомпилировать\nmenu.mode.diff.disassemble=Дизассемблировать\nmenu.vm=Виртуализировать\nmenu.vm.optimize=Оптимизировать\nmenu.vm.run=Запустить\nmenu.plugin=Плагины\nmenu.plugin.manage=Управление плагинами\nmenu.plugin.installed=Установленные\nmenu.plugin.remote=Удалённые\nmenu.plugin.browse=Просмотр плагинов\nmenu.plugin.enabled=Включено\nmenu.plugin.uninstall=Удалить\nmenu.plugin.uninstall.warning=Вы уверены, что хотите удалить этот плагин?\n\n##### Keybinds\nbind.inputprompt.initial=<ожидание>\nbind.inputprompt.finish=<ENTER для завершения>\nbind.editor.find=Найти\nbind.editor.goto=Перейти\nbind.editor.rename=Переименовать\nbind.editor.replace=Заменить\nbind.editor.save=Сохранить\nbind.editor.undo=Отменить\nbind.editor.closetab=Закрыть текущую вкладку\nbind.quicknav=Быстрая навигация\nbind.workspace.export=Экспорт рабочего пространства\n\n##### Dialog texts\ndialog.cancel=Отмена\ndialog.close=Закрыть\ndialog.confirm=Подтвердить\ndialog.finish=Завершить\ndialog.next=Далее\ndialog.previous=Назад\ndialog.dismiss=Отклонить\ndialog.configure=Настроить\ndialog.warning=Предупреждение\ndialog.restart=Для изменения этой опции конфигурации рекомендуется перезапуск.\\nВы уверены, что хотите применить?\ndialog.unknownextension=Неизвестное расширение файла. Хотите настроить ассоциацию языка?\n\n## Search\ndialog.search.type=Имя типа\ndialog.search.member-owner=Тип владельца члена\ndialog.search.member-name=Имя члена\ndialog.search.member-descriptor=Дескриптор члена\n\n## File chooser\ndialog.title.primary=Основной ресурс\ndialog.title.supporting=Вспомогательные ресурсы\ndialog.title.nochanges=Экспорт без изменений?\ndialog.file.primary=Основной\ndialog.file.open=Открыть\ndialog.file.open.directory=Директории\ndialog.file.open.file=Файлы\ndialog.file.export=Экспорт\ndialog.file.save=Сохранить\ndialog.file.nothing=Ничего не выбрано\ndialog.file.nochanges=Вы хотите экспортировать приложение, даже если не было внесено изменений?\ndialog.filefilter.any=Любой тип\ndialog.filefilter.mapping=Маппинги\ndialog.filefilter.input=Приложения\ndialog.filefilter.workspace=Рабочие пространства\n\n## File drop items\ndialog.title.create-workspace=Создать рабочее пространство\ndialog.title.update-workspace=Обработать ввод рабочего пространства\ndialog.title.close-workspace=Закрыть рабочее пространство?\ndialog.option.create-workspace=Создать новое рабочее пространство\ndialog.option.update-workspace=Добавить в рабочее пространство\n\n## Select class/file\ndialog.title.select-class=Выбрать класс\ndialog.title.select-file=Выбрать файл\n\n## Create class/file\ndialog.title.create-class=Создать класс\ndialog.header.create-class-error=Имя класса уже существует.\\nПожалуйста, выберите другое имя.\n\n\n## Copy class/file\ndialog.title.copy-class=Копировать класс\ndialog.title.copy-directory=Копировать директорию\ndialog.title.copy-package=Копировать пакет\ndialog.title.copy-field=Копировать поле\ndialog.title.copy-file=Копировать файл\ndialog.title.copy-method=Копировать метод\ndialog.header.copy-class=Укажите новое имя для копируемого класса.\ndialog.header.copy-directory=Укажите новое имя для копируемой директории.\ndialog.header.copy-package=Укажите новое имя для копируемого пакета.\ndialog.header.copy-field=Укажите новое имя для копируемого поля.\ndialog.header.copy-field-error=Имя поля уже существует.\\nПожалуйста, выберите другое имя.\ndialog.header.copy-file=Укажите новое имя для копируемого файла.\ndialog.header.copy-method=Укажите новое имя для копируемого метода.\ndialog.header.copy-method-error=Имя метода уже существует.\\nПожалуйста, выберите другое имя.\n\n## Delete class/file\ndialog.title.delete-class=Удалить класс\ndialog.title.delete-directory=Удалить директорию\ndialog.title.delete-field=Удалить поле\ndialog.title.delete-file=Удалить файл\ndialog.title.delete-method=Удалить метод\ndialog.title.delete-package=Удалить пакет\ndialog.title.delete-resource=Удалить ресурс\ndialog.header.delete-class=Вы уверены, что хотите удалить: %s?\ndialog.header.delete-directory=Вы уверены, что хотите удалить: %s?\ndialog.header.delete-field=Вы уверены, что хотите удалить: %s?\ndialog.header.delete-file=Вы уверены, что хотите удалить: %s?\ndialog.header.delete-method=Вы уверены, что хотите удалить: %s?\ndialog.header.delete-package=Вы уверены, что хотите удалить: %s?\ndialog.header.delete-resource=Вы уверены, что хотите удалить: %s?\n\n## Rename class/file\ndialog.title.rename-class=Переименовать класс\ndialog.title.rename-class-warning=Предупреждение\ndialog.title.rename-directory=Переименовать директорию\ndialog.title.rename-field=Переименовать поле\ndialog.title.rename-file=Переименовать файл\ndialog.title.rename-file-warning=Предупреждение\ndialog.title.rename-method=Переименовать метод\ndialog.title.rename-package=Переименовать пакет\ndialog.header.rename-class=Укажите новое имя для класса.\ndialog.header.rename-class-error=Имя класса уже существует.\\nПожалуйста, выберите другое имя.\ndialog.header.rename-package=Укажите новое имя для пакета.\ndialog.header.rename-package-error=Имя пакета уже существует.\\nПожалуйста, выберите другое имя.\ndialog.header.rename-package-warning=Имя пакета уже существует.\\nЭто может перезаписать некоторые классы.\ndialog.header.rename-directory=Укажите новое имя для директории.\ndialog.header.rename-directory-error=Имя директории уже существует.\\nПожалуйста, выберите другое имя.\ndialog.header.rename-directory-warning=Имя директории уже существует.\\nЭто может перезаписать некоторые файлы.\ndialog.header.rename-field=Укажите новое имя для поля.\ndialog.header.rename-field-error=Имя поля уже существует.\\nПожалуйста, выберите другое имя.\ndialog.header.rename-file=Укажите новое имя для файла.\ndialog.header.rename-file-error=Имя файла уже существует.\\nПожалуйста, выберите другое имя.\ndialog.header.rename-method=Укажите новое имя для метода.\ndialog.header.rename-method-error=Имя метода уже существует.\\nПожалуйста, выберите другое имя.\n\n## Move class/file\ndialog.title.move-class=Выберите пакет назначения\ndialog.title.move-directory=Выберите директорию назначения (родительскую)\ndialog.title.move-file=Выберите директорию назначения\ndialog.title.move-package=Выберите пакет назначения (родительский)\ndialog.header.move-class=Переместить класс в новый пакет.\ndialog.header.move-directory=Переместить директорию в новую родительскую директорию.\ndialog.header.move-file=Переместить файл в новую директорию.\ndialog.header.move-package=Переместить пакет в новый родительский пакет.\n\n## Add members\ndialog.title.add-field=Добавить поле\ndialog.title.add-method=Добавить метод\ndialog.title.override-method=Переопределить метод\ndialog.input.name=Имя\ndialog.input.desc=Дескриптор\ndialog.warn.illegal-name=Недопустимое имя\ndialog.warn.illegal-desc=Недопустимый формат дескриптора\ndialog.warn.field-conflict=Имя поля уже существует.\\nПожалуйста, выберите другое имя.\ndialog.warn.method-conflict=Имя метода уже существует.\\nПожалуйста, выберите другое имя.\n\n## VM actions\ndialog.title.vm-invoke-args=Виртуализировать вызов метода\ndialog.title.vm-peephole-invoke-args=Виртуализированная локальная оптимизация\ndialog.vm.execute=Выполнить\ndialog.vm.optimize=Оптимизировать\ndialog.vm.create-dummy=Использовать заглушку\ndialog.vm.create-null=Использовать null\n\n## Hex dialogs\ndialog.hex.title.insertcount=Вставить\ndialog.hex.header.insertcount=Сколько байт вставить?\n\n# Base Converter dialog\ndialog.conv.title.literal=Числовой литерал\ndialog.conv.title.expression=Числовое выражение\n\n## Quick nav\ndialog.quicknav=Быстрая навигация\ndialog.quicknav.tab.classes=Классы\ndialog.quicknav.tab.members=Члены\ndialog.quicknav.tab.files=Файлы\ndialog.quicknav.tab.text=Текст\ndialog.quicknav.tab.commented=Комментарии\n\n## Error dialog\ndialog.error.exportclass.title=Не удалось экспортировать класс\ndialog.error.exportclass.header=Произошла ошибка при записи в место назначения\ndialog.error.exportclass.content=Ошибка:\ndialog.error.exportfile.title=Не удалось экспортировать файл\ndialog.error.exportfile.header=Произошла ошибка при записи в место назначения\ndialog.error.exportfile.content=Ошибка:\ndialog.error.exportworkspace.title=Не удалось экспортировать рабочее пространство\ndialog.error.exportworkspace.header=Произошла ошибка при записи в место назначения\ndialog.error.exportworkspace.content=Ошибка:\ndialog.error.loadworkspace.title=Не удалось загрузить рабочее пространство\ndialog.error.loadworkspace.header=Произошла ошибка при чтении из выбранных файлов\ndialog.error.loadworkspace.content=Ошибка:\ndialog.error.loadsupport.title=Не удалось загрузить вспомогательные ресурсы\ndialog.error.loadsupport.header=Произошла ошибка при чтении из выбранных файлов\ndialog.error.loadsupport.content=Ошибка:\ndialog.error.attach.title=Не удалось подключиться к JVM\ndialog.error.attach.header=Произошла ошибка при подключении к удалённой JVM\ndialog.error.attach.content=Ошибка:\n\n##### Panels\n## Welcome\nwelcome.title=Добро пожаловать\nwelcome.links=Ссылки\nwelcome.links.home=Recaf: Главная\nwelcome.links.docs-user=Документация пользователя\nwelcome.links.docs-dev=Документация разработчика\nwelcome.links.github=Github: Исходный код + Трекер ошибок/функций\nwelcome.links.discord=Discord\nwelcome.links.jvms=Спецификация виртуальной машины Java\nwelcome.links.jvms.class=Формат файла класса\nwelcome.links.jvms.instructions=Набор инструкций JVM\nwelcome.dnd=Вы можете перетащить файлы на этот экран, чтобы открыть их.\nwelcome.tutorial=Вы не завершили обучение. Нажмите здесь, чтобы начать!\nwelcome.dayssince=дней с момента сборки\nwelcome.norecent=Нет недавних рабочих пространств\n\n\n## Workspace\nworkspace.title=Рабочее пространство\nworkspace.filter-prompt=Фильтр: Имя класса/файла...\nworkspace.info=Информация\nworkspace.info-progress=Анализ содержимого рабочего пространства...\n\n## Attach\nattach.unsupported=Не удалось инициализировать подключение\nattach.unsupported.detail=Агенту подключения не удалось самораспаковаться.\nattach.no-vms=Не найдено подключаемых JVM\nattach.no-vms.detail=В настоящее время нет доступных виртуальных машин Java для подключения.\nattach.problem.disable-attach=Если вы пытаетесь подключиться к процессу Java, использующему -XX:+DisableAttachMechanism, пожалуйста, см.:\nattach.problem.java-21=Если вы пытаетесь подключиться к процессу Java 21+, пожалуйста, см.:\nattach.connect=Подключиться\nattach.tab.properties=Свойства\nattach.tab.classloading=Классы\nattach.tab.compilation=Компиляция\nattach.tab.system=Система\nattach.tab.runtime=Среда выполнения\nattach.tab.thread=Потоки\n\n## Changes view\nmodifications.none=Нет истории изменений для элемента\nmodifications.title=Изменения\n\n## Java area\njava.decompiling=Декомпиляция класса...\njava.unparsable=SourceSolver не смог интерпретировать исходный код, контекстные действия доступны только во вкладке 'Поля и методы'\njava.parse-state.error=Ошибка парсера\njava.parse-state.error-details=Контекстные действия по правому клику недоступны, так как парсинг не удался.\\nВы можете использовать вкладку 'Поля и методы' в это время.\njava.parse-state.initial=Парсинг в процессе...\njava.parse-state.initial-details=Контекстные действия по правому клику недоступны до завершения парсинга.\\nВы можете использовать вкладку 'Поля и методы' в это время.\njava.parse-state.new-progress=Повторный парсинг...\njava.parse-state.new-progress-details=Были внесены изменения, поэтому выполняется новый парсинг.\\nСтарая модель будет использоваться, пока строится новая.\\nВы можете использовать вкладку 'Поля и методы' как альтернативу.\njava.parse-state.none=Нет содержимого для парсинга\njava.decompile-failure=Не удалось декомпилировать класс. Некоторые варианты:\\n- Переключить декомпиляторы\\n- Открыть класс в ассемблере или другом виде\\n- Попробовать деобфусцировать класс и повторить попытку\njava.decompile-failure.brief=Не удалось декомпилировать класс\njava.savewitherrors=Похоже, это первый раз, когда ваши изменения привели к ошибкам.\\nОбычно это результат того, что декомпилированный код не является семантически корректным Java.\\nВы должны исправить эти ошибки перед сохранением изменений.\\n\\nНекоторые предложения:\\n - Измените декомпиляторы в 'настройках' или внизу справа (i)\\n - Наведите курсор на красные блоки ошибок или нажмите на блок ошибок вверху, чтобы увидеть ошибки\\n - Используйте ассемблер вместо перекомпиляции для внесения изменений\njava.savewitherrors.title=Относительно ошибок перекомпиляции\njava.decompiler=Декомпилятор\njava.targetversion=Версия цели компиляции\njava.targetversion.notice.down=Понижение версии добавит заглушки классов для эмуляции отсутствующих API\njava.targetversion.auto=Соответствовать версии файла класса\njava.targetdownsampleversion=Версия цели понижения\njava.targetdownsampleversion.disabled=Отключено\njava.targetdebug=Компилировать с отладочной информацией\njava.info=Информация о классе\njava.info.version=Версия класса\njava.info.sourcefile=Имя исходного файла\n\n## Search bar\nfind.replace=Заменить\nfind.replaceall=Заменить все\nfind.regexinvalid=Недопустимое регулярное выражение\nfind.regexreplace=Текст замены\n\n## Fields and methods\nfieldsandmethods.title=Поля и методы\nfieldsandmethods.empty=Класс не имеет членов\nfieldsandmethods.showoutlinedsynths=Показать синтетические (сгенерированные компилятором) члены\nfieldsandmethods.showoutlinedvisibility=Фильтр по видимости члена\nfieldsandmethods.showoutlinedmembertype=Фильтр по типу члена\nfieldsandmethods.nametypemode=Режим отображения имени/типа\nfieldsandmethods.sortalphabetically=Сортировать по алфавиту\nfieldsandmethods.sortbyvisibility=Сортировать по видимости\nfieldsandmethods.filter.prompt=Фильтр: Имя поля/метода...\n\n## Hierarchy\nhierarchy.title=Наследование\nhierarchy.children=Дочерние\nhierarchy.parents=Родительские\n\n## Kotlin Metadata\nkotlinmetadata.title=@Metadata\nkotlinmetadata.orderwarning=Важно: Элементы не отображаются в том же порядке, что и определены в файле класса\n\n## Logging\nlogging.title=Журналирование\n\n## Assembler\nassembler.problem.0=Нет проблем\nassembler.problem.1=1 проблема\nassembler.problem.N=N проблем\nassembler.title=Ассемблер\nassembler.analysis.title=Анализ\nassembler.analysis.stack=Стек\nassembler.analysis.variables=Переменные\nassembler.analysis.type=Тип\nassembler.analysis.value=Значение\nassembler.playground.title=Java в байткод\nassembler.playground.comment=// Напишите здесь код Java, чтобы автоматически преобразовать его в байткод\\n// Вы можете обращаться к полям/методам текущего класса,\\n// и к параметрам/переменным текущего метода.\nassembler.snippets.title=Фрагменты\nassembler.variables.title=Объявленные переменные\nassembler.variables.name=Имя переменной\nassembler.variables.type=Тип\nassembler.variables.usage=Использования\nassembler.variables.value=Значение\nassembler.variables.empty=<Требуется компиляция хотя бы один раз>\nassembler.variables.read-before-write=Прочитано до записи\nassembler.suggestions.none=Нет предложений\n\n## Comments\ncomments.search.prompt=Поиск комментария...\n\n## Search\nsearch.run=Поиск\nsearch.results=Результаты\nsearch.text=Текстовое содержимое\nsearch.textmode=Режим совпадения текста\nsearch.number=Числовое значение\nsearch.numbermode=Режим совпадения числа\nsearch.refowner=Владелец члена\nsearch.refname=Имя члена\nsearch.refdesc=Дескриптор типа члена\n\n## Help\nhelp.system=Система\nhelp.system.sub=Информация об ОС\nhelp.java=Java\nhelp.java.sub=Информация о JVM\nhelp.javafx=JavaFX\nhelp.javafx.sub=Информация о JavaFX UI\nhelp.recaf=Recaf\nhelp.recaf.sub=Информация о Recaf\nhelp.copy=Копировать информацию в буфер обмена\nhelp.opendir=Открыть директорию Recaf\n\n## Deobfuscation\ndeobf=Деобфускация\ndeobf.selection.title=Выбрать трансформеры\ndeobf.order.title=Порядок трансформеров\ndeobf.order.hint=Выберите трансформеры слева\\nИзмените их порядок, перетаскивая сюда\ndeobf.order.pre=Отсутствуют рекомендуемые предшественники\ndeobf.order.suc=Отсутствуют рекомендуемые последователи\ndeobf.max-passes=Максимум проходов трансформации\ndeobf.preview.title=Предпросмотр\ndeobf.preview.pick=Выбрать класс для предпросмотра\ndeobf.preview.toggle-mode=Переключить режим предпросмотра\ndeobf.preview.noselection=// Класс не выбран, выберите класс для предпросмотра\\n// трансформации до и после состояний\ndeobf.tree.generic=Общие\ndeobf.tree.generic.anticrasher=Anti-Crasher\ndeobf.tree.generic.optimize=Оптимизация\ndeobf.tree.generic.restoration=Восстановление\ndeobf.tree.specific=Специфичные\ndeobf.apply=Трансформировать рабочее пространство\n\n## Mapping application\nmapapply=Применить маппинги\nmapapply.pick.file=Выбрать файл маппингов\nmapapply.pick.dir=Выбрать директорию маппингов\nmapapply.settings.unique=Предполагать уникальные ключи, игнорировать наследование\n\n## Mapping generator\nmapgen=Генератор маппингов\nmapgen.genimpl=Соглашение об именовании\nmapgen.filter.name=Имя\nmapgen.filter.class-name=Имя класса\nmapgen.filter.owner-name=Имя владельца\nmapgen.filter.field-name=Имя поля\nmapgen.filter.method-name=Имя метода\nmapgen.filter.variable-name=Имя переменной\nmapgen.filters=Фильтры\nmapgen.filters.add=Добавить фильтр\nmapgen.filters.edit=Редактировать выбранное\nmapgen.filters.delete=Удалить выбранное\nmapgen.filters.type=Тип фильтра\nmapgen.filter.modifiers.tooltip=Модификаторы разделяются пробелами\nmapgen.filter.excludealreadymapped=Исключить уже замаппленные\nmapgen.filter.excludemodifier=Исключить модификаторы\nmapgen.filter.excludeclasses=Исключить классы\nmapgen.filter.excludename=Исключить имена\nmapgen.filter.excludeclass=Исключить на классах\nmapgen.filter.excludefield=Исключить на полях\nmapgen.filter.excludemethod=Исключить на методах\nmapgen.filter.includemodifier=Включить модификаторы\nmapgen.filter.includeclass=Включить на классах\nmapgen.filter.includefield=Включить на полях\nmapgen.filter.includemethod=Включить на методах\nmapgen.filter.includevariable=Включить на переменных\nmapgen.filter.includewhitespacenames=Включить пробелы\nmapgen.filter.includenonasciinames=Включить не-ASCII\nmapgen.filter.includekeywords=Включить ключевые слова\nmapgen.filter.includenonjavaidentifiers=Включить не-Java идентификаторы\nmapgen.filter.includelong=Включить длинные имена\nmapgen.filter.includename=Включить имена\nmapgen.filter.includeclasses=Включить классы\nmapgen.title.newfilter=Новый фильтр\nmapgen.header.newfilter=Введите содержимое фильтра\nmapgen.preview.empty=Здесь появятся статистики сгенерированных маппингов\\n\\n\\n\nmapgen.configure=Настроить\nmapgen.configure.nothing=Нечего настраивать\nmapgen.generate=Сгенерировать\nmapgen.apply=Применить\n\n## Mapping view\nmapprog=Прогресс маппинга\nmapprog.metric.size=Размер файла класса\nmapprog.metric.membercount=Поля и методы класса\n\n##### Tree\ntree.classes=Классы\ntree.files=Файлы\ntree.defaultpackage=(Пакет по умолчанию)\ntree.defaultdirectory=(Корневая директория)\ntree.prompt=Перетащите ваши файлы сюда\ntree.hidelibs=Скрыть библиотеки\ntree.phantoms=Сгенерированные фантомы\ntree.embedded-resources=Встроенные\n\n##### Services\nservice=Все сервисы\nservice.analysis=Анализ\nservice.analysis.comments-config=Комментарии\nservice.analysis.comments-config.enable-display=Отображать комментарии при декомпиляции\nservice.analysis.comments-config.word-wrapping-limit=Лимит переноса слов\nservice.analysis.info-summary-config=Суммаризация рабочего пространства\nservice.analysis.info-summary-config.summarize-on-open=Суммаризировать содержимое рабочего пространства при открытии\nservice.analysis.graph-calls-config=Граф вызовов\nservice.analysis.graph-inheritance-config=Граф наследования\nservice.analysis.jphantom-generator-config=JPhantom\nservice.analysis.jphantom-generator-config.generate-workspace-phantoms=Генерировать и добавлять фантомы в рабочие пространства\nservice.analysis.search-config=Поиск\nservice.analysis.entry-points=Точки входа\nservice.analysis.entry-points.none=Точки входа не найдены\nservice.analysis.anti-decompile=Анти-декомпиляция\nservice.analysis.anti-decompile.illegal-attr=Недопустимые атрибуты\nservice.analysis.anti-decompile.illegal-name=Недопустимые имена\nservice.analysis.anti-decompile.label-patch=Исправить %d затронутых классов\nservice.analysis.signature-info=Информация о сигнатуре\nservice.assembler=Ассемблер\nservice.assembler.assembler-pipeline.general-config=Общие\nservice.assembler.assembler-pipeline.general-config.disassembly-ast-parse-delay=Задержка парсинга AST\nservice.assembler.assembler-pipeline.general-config.disassembly-indent=Отступ\nservice.assembler.assembler-pipeline.general-config.disassembly-whole-floating=Стандартная нотация чисел с плавающей точкой\nservice.assembler.dalvik-assembler-config=Dalvik\nservice.assembler.dalvik-assembler-config.value-analysis=Включить анализ значений\nservice.assembler.dalvik-assembler-config.simulate-jvm-calls=Имитировать общие вызовы JVM\nservice.assembler.jvm-assembler-config=JVM\nservice.assembler.jvm-assembler-config.value-analysis=Включить анализ значений\nservice.assembler.jvm-assembler-config.simulate-jvm-calls=Имитировать общие вызовы JVM\nservice.assembler.jvm-assembler-config.try-range-comments=Выводить комментарии диапазонов try-catch\nservice.assembler.flow-lines-config=Линии потока управления\nservice.assembler.flow-lines-config.connection-mode=Режим линий\nservice.assembler.flow-lines-config.render-mode=Режим отрисовки\nservice.compile=Компиляция\nservice.compile.java-compiler-config=Javac\nservice.compile.java-compiler-config.generate-phantoms=Генерировать отсутствующие классы\nservice.compile.java-compiler-config.default-emit-debug=По умолчанию включать отладку\nservice.compile.java-compiler-config.default-compile-target-version=Версия цели компиляции по умолчанию\nservice.compile.java-compiler-config.default-downsample-target-version=Версия цели понижения по умолчанию\nservice.debug=Подключение/Отладка\nservice.debug.attach-config=Конфигурация подключения\nservice.debug.attach-config.attach-jmx-bean-agent=Подключить агент JMX bean\nservice.debug.attach-config.passive-scanning=Состояние пассивного сканирования\nservice.config-manager-config=Менеджер конфигурации\nservice.decompile=Декомпиляция\nservice.decompile.decompilers-config=Менеджер декомпиляции\nservice.decompile.decompilers-config.pref-android-decompiler=Предпочитаемый декомпилятор Android\nservice.decompile.decompilers-config.pref-jvm-decompiler=Предпочитаемый декомпилятор Java\nservice.decompile.decompilers-config.cache-decompilations=Кэшировать декомпиляции\nservice.decompile.decompilers-config.filter-annotations-duplicate=Фильтровать дублирующиеся аннотации\nservice.decompile.decompilers-config.filter-annotations-illegal=Фильтровать недопустимые аннотации\nservice.decompile.decompilers-config.filter-annotations-long=Фильтровать длинные аннотации\nservice.decompile.decompilers-config.filter-annotations-long-limit=Лимит длинных аннотаций\nservice.decompile.decompilers-config.filter-exceptions-long=Фильтровать длинные исключения\nservice.decompile.decompilers-config.filter-exceptions-long-limit=Лимит длинных исключений\nservice.decompile.decompilers-config.filter-hollow=Фильтровать содержимое класса (пустое)\nservice.decompile.decompilers-config.filter-illegal-signatures=Фильтровать недопустимые сигнатуры\nservice.decompile.decompilers-config.filter-synthetics=Фильтровать синтетические флаги\nservice.decompile.decompilers-config.filter-names-ascii=Фильтровать не-ASCII имена\nservice.decompile.decompilers-config.filter-strip-debug=Фильтровать отладочные данные (переменные, дженерики)\nservice.decompile.impl=Реализации\nservice.decompile.impl.decompiler-cfr-config=CFR\nservice.decompile.impl.decompiler-cfr-config.aexagg=Пытаться расширять и объединять исключения более агрессивно\nservice.decompile.impl.decompiler-cfr-config.aexagg2=Пытаться расширять и объединять исключения более агрессивно (может изменить семантику)\nservice.decompile.impl.decompiler-cfr-config.aggressivedocopy=Клонировать код из невозможных переходов в циклы с тестом 'first'\nservice.decompile.impl.decompiler-cfr-config.aggressivedoextension=Сворачивать невозможные переходы в циклы do с тестом 'first'\nservice.decompile.impl.decompiler-cfr-config.aggressiveduff=Сворачивать переключатели в стиле duff device с дополнительным управлением\nservice.decompile.impl.decompiler-cfr-config.aggressivesizethreshold=Количество опкодов, при котором запускаются агрессивные сокращения\nservice.decompile.impl.decompiler-cfr-config.allowmalformedswitch=Разрешать потенциально некорректные операторы switch\nservice.decompile.impl.decompiler-cfr-config.antiobf=Отменить различные обфускации\nservice.decompile.impl.decompiler-cfr-config.arrayiter=Пересоздать итерацию на основе массивов\nservice.decompile.impl.decompiler-cfr-config.collectioniter=Пересоздать итерацию на основе коллекций\nservice.decompile.impl.decompiler-cfr-config.commentmonitors=Заменить мониторы комментариями - полезно, если мы полностью запутались\nservice.decompile.impl.decompiler-cfr-config.comments=Выводить комментарии, описывающие статус декомпилятора, флаги отката и т.д.\nservice.decompile.impl.decompiler-cfr-config.constobf=Отменить обфускацию констант\nservice.decompile.impl.decompiler-cfr-config.decodeenumswitch=Пересоздать switch по enum\nservice.decompile.impl.decompiler-cfr-config.decodefinally=Пересоздать операторы finally\nservice.decompile.impl.decompiler-cfr-config.decodelambdas=Пересобрать лямбда-функции\nservice.decompile.impl.decompiler-cfr-config.decodestringswitch=Пересоздать switch по String\nservice.decompile.impl.decompiler-cfr-config.eclipse=Включить трансформации для лучшей обработки кода Eclipse\nservice.decompile.impl.decompiler-cfr-config.elidescala=Пропускать вещи, которые не полезны в выводе scala (serialVersionUID, @ScalaSignature)\nservice.decompile.impl.decompiler-cfr-config.forbidanonymousclasses=Не разрешать анонимные классы\nservice.decompile.impl.decompiler-cfr-config.forbidmethodscopedclasses=Не разрешать классы с областью видимости метода\nservice.decompile.impl.decompiler-cfr-config.forceclassfilever=Принудительно задать версию файла класса (и, следовательно, java), под которую декомпилируются файлы классов\nservice.decompile.impl.decompiler-cfr-config.forcecondpropagate=Протянуть результаты детерминированных переходов назад через некоторые присваивания констант\nservice.decompile.impl.decompiler-cfr-config.forceexceptionprune=Удалить вложенные обработчики исключений, если они не меняют семантику\nservice.decompile.impl.decompiler-cfr-config.forcereturningifs=Переместить return к месту перехода\nservice.decompile.impl.decompiler-cfr-config.forcetopsort=Принудительная сортировка базовых блоков. Обычно полезно только при наличии обфускации\nservice.decompile.impl.decompiler-cfr-config.forcetopsortaggress=Принудительные дополнительные агрессивные опции topsort\nservice.decompile.impl.decompiler-cfr-config.forcetopsortnopull=Принудительно не позволять topsort вытягивать блоки try\nservice.decompile.impl.decompiler-cfr-config.forloopaggcapture=Разрешить циклам for агрессивно переносить мутации в секцию обновления, даже если они не кажутся связанными с предикатом\nservice.decompile.impl.decompiler-cfr-config.hidebridgemethods=Скрывать мостовые методы\nservice.decompile.impl.decompiler-cfr-config.hidelangimports=Скрывать импорты из java.lang\nservice.decompile.impl.decompiler-cfr-config.hidelongstrings=Скрывать очень длинные строки - полезно, если обфускаторы поместили фальшивый код в строки\nservice.decompile.impl.decompiler-cfr-config.hideutf=Скрывать UTF8 символы - цитировать их вместо показа сырых символов\nservice.decompile.impl.decompiler-cfr-config.ignoreexceptions=Отбросить информацию об исключениях, если полностью застряли (ПРЕДУПРЕЖДЕНИЕ: меняет семантику, опасно!)\nservice.decompile.impl.decompiler-cfr-config.ignoreexceptionsalways=Отбросить информацию об исключениях (ПРЕДУПРЕЖДЕНИЕ: меняет семантику, опасно!)\nservice.decompile.impl.decompiler-cfr-config.innerclasses=Декомпилировать внутренние классы\nservice.decompile.impl.decompiler-cfr-config.instanceofpattern=Пересоздать совпадения instanceof pattern\nservice.decompile.impl.decompiler-cfr-config.j14classobj=Обратить построение объекта класса java 1.4\nservice.decompile.impl.decompiler-cfr-config.labelledblocks=Разрешить код, использующий помеченные блоки (обработка необычных переходов вперёд)\nservice.decompile.impl.decompiler-cfr-config.lenient=Быть немного более снисходительным в ситуациях, где обычно выбрасывается исключение\nservice.decompile.impl.decompiler-cfr-config.liftconstructorinit=Поднять код инициализации, общий для всех конструкторов, в инициализацию членов\nservice.decompile.impl.decompiler-cfr-config.obfattr=Отменить обфускацию атрибутов\nservice.decompile.impl.decompiler-cfr-config.obfcontrol=Отменить обфускацию потока управления\nservice.decompile.impl.decompiler-cfr-config.override=Генерировать аннотации @Override (если метод виден как реализующий метод интерфейса или переопределяющий метод базового класса)\nservice.decompile.impl.decompiler-cfr-config.previewfeatures=Декомпилировать preview функции, если класс был скомпилирован с 'javac --enable-preview'\nservice.decompile.impl.decompiler-cfr-config.pullcodecase=Агрессивно втягивать код в операторы case\nservice.decompile.impl.decompiler-cfr-config.recordtypes=Пересоздать типы record\nservice.decompile.impl.decompiler-cfr-config.recover=Разрешить установку всё более агрессивных опций, если декомпиляция не удалась\nservice.decompile.impl.decompiler-cfr-config.recovertypeclash=Разделить время жизни там, где анализ вызвал конфликт типов\nservice.decompile.impl.decompiler-cfr-config.recovertypehints=Восстановить подсказки типов для итераторов из первого прохода\nservice.decompile.impl.decompiler-cfr-config.reducecondscope=Уменьшить область видимости условных операторов, возможно генерируя больше анонимных блоков\nservice.decompile.impl.decompiler-cfr-config.relinkconst=Пересвязать константы - если есть встроенная ссылка на поле, попытаться де-инлайнить\nservice.decompile.impl.decompiler-cfr-config.relinkconststring=Пересвязать строковые константы - если есть локальная ссылка на строку, совпадающую со static final, использовать static final\nservice.decompile.impl.decompiler-cfr-config.removebadgenerics=Скрывать дженерики там, где мы явно ошиблись, и откатиться к не-дженерикам\nservice.decompile.impl.decompiler-cfr-config.removeboilerplate=Удалить шаблонные функции - шаблоны конструкторов, десериализацию лямбд и т.д.\nservice.decompile.impl.decompiler-cfr-config.removedeadconditionals=Удалить код, который не может быть выполнен\nservice.decompile.impl.decompiler-cfr-config.removedeadmethods=Удалить бессмысленные методы - конструктор по умолчанию и т.д.\nservice.decompile.impl.decompiler-cfr-config.removeinnerclasssynthetics=Удалить (где возможно) неявные ссылки на внешний класс во внутренних классах\nservice.decompile.impl.decompiler-cfr-config.renamedupmembers=Переименовать неоднозначные/дублирующиеся поля\nservice.decompile.impl.decompiler-cfr-config.renameenumidents=Переименовать идентификаторы ENUM, которые не соответствуют их 'ожидаемым' строковым именам\nservice.decompile.impl.decompiler-cfr-config.renameillegalidents=Переименовать идентификаторы, которые не являются допустимыми java идентификаторами\nservice.decompile.impl.decompiler-cfr-config.renamesmallmembers=Переименовать маленькие члены. Примечание - это СЛОМАЕТ доступ на основе рефлексии, поэтому не включено автоматически\nservice.decompile.impl.decompiler-cfr-config.sealed=Декомпилировать конструкции 'sealed'\nservice.decompile.impl.decompiler-cfr-config.showinferrable=Украсить методы явными типами, если они не подразумеваются аргументами\nservice.decompile.impl.decompiler-cfr-config.showversion=Показать используемую версию CFR в заголовке (удобно отключить при регрессионном тестировании)\nservice.decompile.impl.decompiler-cfr-config.skipbatchinnerclasses=При обработке многих файлов пропускать внутренние классы, так как они всё равно будут обработаны как часть внешних классов\nservice.decompile.impl.decompiler-cfr-config.staticinitreturn=Попытаться удалить return из static init\nservice.decompile.impl.decompiler-cfr-config.stringbuffer=Преобразовать new StringBuffer().append.append.append в string + string + string\nservice.decompile.impl.decompiler-cfr-config.stringbuilder=Преобразовать new StringBuilder().append.append.append в string + string + string\nservice.decompile.impl.decompiler-cfr-config.stringconcat=Преобразовать использования StringConcatFactor в string + string + string\nservice.decompile.impl.decompiler-cfr-config.sugarasserts=Пересоздать вызовы assert\nservice.decompile.impl.decompiler-cfr-config.sugarboxing=Где возможно, удалить бессмысленные обёртки боксинга\nservice.decompile.impl.decompiler-cfr-config.sugarenums=Пересоздать enums\nservice.decompile.impl.decompiler-cfr-config.sugarretrolambda=Где возможно, пересоздать использования retro lambda\nservice.decompile.impl.decompiler-cfr-config.switchexpression=Пересоздать switch expression\nservice.decompile.impl.decompiler-cfr-config.tidymonitors=Удалить поддерживающий код для мониторов - например, блоки catch только для выхода из монитора\nservice.decompile.impl.decompiler-cfr-config.tryresources=Восстановить try-with-resources\nservice.decompile.impl.decompiler-cfr-config.usenametable=Использовать таблицу имён локальных переменных, если присутствует\nservice.decompile.impl.decompiler-cfr-config.usesignatures=Использовать сигнатуры в дополнение к дескрипторам (когда они не явно неверны)\nservice.decompile.impl.decompiler-cfr-config.version=Показать текущую версию CFR\nservice.decompile.impl.decompiler-procyon-config=Procyon\nservice.decompile.impl.decompiler-procyon-config.alwaysGenerateExceptionVariableForCatchBlocks=Всегда генерировать блок catch\nservice.decompile.impl.decompiler-procyon-config.arePreviewFeaturesEnabled=Включить preview функции языка\nservice.decompile.impl.decompiler-procyon-config.bytecodeOutputOptions=Опции вывода байткода\nservice.decompile.impl.decompiler-procyon-config.disableForEachTransforms=Отключить трансформации forEach(...)\nservice.decompile.impl.decompiler-procyon-config.excludeNestedTypes=Исключить вложенные типы\nservice.decompile.impl.decompiler-procyon-config.flattenSwitchBlocks=Сглаживать блоки switch\nservice.decompile.impl.decompiler-procyon-config.forceExplicitImports=Принудительно явные импорты\nservice.decompile.impl.decompiler-procyon-config.forceExplicitTypeArguments=Принудительно явные аргументы типов\nservice.decompile.impl.decompiler-procyon-config.forceFullyQualifiedReferences=Принудительно полностью квалифицированные ссылки\nservice.decompile.impl.decompiler-procyon-config.forcedCompilerTarget=Версия целевого языка\nservice.decompile.impl.decompiler-procyon-config.includeErrorDiagnostics=Включать диагностику ошибок\nservice.decompile.impl.decompiler-procyon-config.includeLineNumbersInBytecode=Показать номера строк отладки (байткод)\nservice.decompile.impl.decompiler-procyon-config.isUnicodeOutputEnabled=Включить unicode в выводе\nservice.decompile.impl.decompiler-procyon-config.languageTarget=Целевой язык\nservice.decompile.impl.decompiler-procyon-config.mergeVariables=Объединять переменные, когда возможно\nservice.decompile.impl.decompiler-procyon-config.retainPointlessSwitches=Сохранять бесполезные switch\nservice.decompile.impl.decompiler-procyon-config.retainRedundantCasts=Сохранять бесполезные приведения\nservice.decompile.impl.decompiler-procyon-config.showDebugLineNumbers=Показать номера строк отладки\nservice.decompile.impl.decompiler-procyon-config.showSyntheticMembers=Показать синтетические члены\nservice.decompile.impl.decompiler-procyon-config.simplifyMemberReferences=Упрощать ссылки на члены\nservice.decompile.impl.decompiler-procyon-config.textBlockLineMinimum=Минимум строк текстового блока\nservice.decompile.impl.decompiler-vineflower-config=Vineflower\nservice.decompile.impl.decompiler-vineflower-config.logging-level=Уровень логирования\nservice.decompile.impl.decompiler-vineflower-config.remove-bridge=Удалить мостовые методы\nservice.decompile.impl.decompiler-vineflower-config.remove-synthetic=Удалить синтетические методы и поля\nservice.decompile.impl.decompiler-vineflower-config.decompile-inner=Декомпилировать внутренние классы\nservice.decompile.impl.decompiler-vineflower-config.decompile-java4=Декомпилировать ссылки на классы Java 4\nservice.decompile.impl.decompiler-vineflower-config.decompile-assert=Декомпилировать утверждения\nservice.decompile.impl.decompiler-vineflower-config.hide-empty-super=Скрывать пустой super()\nservice.decompile.impl.decompiler-vineflower-config.hide-default-constructor=Скрывать конструктор по умолчанию\nservice.decompile.impl.decompiler-vineflower-config.decompile-generics=Декомпилировать дженерики\nservice.decompile.impl.decompiler-vineflower-config.incorporate-returns=Включать return в блоки try-catch\nservice.decompile.impl.decompiler-vineflower-config.ensure-synchronized-monitors=Обеспечить полноту синхронизированных диапазонов\nservice.decompile.impl.decompiler-vineflower-config.decompile-enums=Декомпилировать enums\nservice.decompile.impl.decompiler-vineflower-config.decompile-preview=Декомпилировать Preview функции\nservice.decompile.impl.decompiler-vineflower-config.remove-getclass=Удалить ссылку getClass()\nservice.decompile.impl.decompiler-vineflower-config.keep-literals=Оставлять литералы как есть\nservice.decompile.impl.decompiler-vineflower-config.boolean-as-int=Представлять boolean как 0/1\nservice.decompile.impl.decompiler-vineflower-config.ascii-strings=ASCII символы строк\nservice.decompile.impl.decompiler-vineflower-config.synthetic-not-set=Синтетический не установлен\nservice.decompile.impl.decompiler-vineflower-config.undefined-as-object=Обрабатывать неопределённый тип параметра как Object\nservice.decompile.impl.decompiler-vineflower-config.use-lvt-names=Использовать имена LVT\nservice.decompile.impl.decompiler-vineflower-config.use-method-parameters=Использовать параметры метода\nservice.decompile.impl.decompiler-vineflower-config.remove-empty-try-catch=Удалить пустые блоки try-catch\nservice.decompile.impl.decompiler-vineflower-config.decompile-finally=Декомпилировать Finally\nservice.decompile.impl.decompiler-vineflower-config.lambda-to-anonymous-class=Декомпилировать лямбды как анонимные классы\nservice.decompile.impl.decompiler-vineflower-config.bytecode-source-mapping=Маппинг байткода в исходный код\nservice.decompile.impl.decompiler-vineflower-config.__dump_original_lines__=Вывести строки кода\nservice.decompile.impl.decompiler-vineflower-config.ignore-invalid-bytecode=Игнорировать недопустимый байткод\nservice.decompile.impl.decompiler-vineflower-config.verify-anonymous-classes=Проверять анонимные классы\nservice.decompile.impl.decompiler-vineflower-config.ternary-constant-simplification=Упрощение тернарных констант\nservice.decompile.impl.decompiler-vineflower-config.pattern-matching=Сопоставление с образцом\nservice.decompile.impl.decompiler-vineflower-config.try-loop-fix=Исправление Try-Loop\nservice.decompile.impl.decompiler-vineflower-config.ternary-in-if=[Экспериментально] Тернарный оператор в условиях If\nservice.decompile.impl.decompiler-vineflower-config.decompile-switch-expressions=Декомпилировать switch expressions\nservice.decompile.impl.decompiler-vineflower-config.show-hidden-statements=[Отладка] Показать скрытые операторы\nservice.decompile.impl.decompiler-vineflower-config.override-annotation=Аннотация Override\nservice.decompile.impl.decompiler-vineflower-config.simplify-stack=Упрощение стека второго прохода\nservice.decompile.impl.decompiler-vineflower-config.verify-merges=[Экспериментально] Проверять объединения переменных\nservice.decompile.impl.decompiler-vineflower-config.include-classpath=Включить весь classpath\nservice.decompile.impl.decompiler-vineflower-config.include-runtime=Включить Java Runtime\nservice.decompile.impl.decompiler-vineflower-config.explicit-generics=Явные аргументы дженериков\nservice.decompile.impl.decompiler-vineflower-config.inline-simple-lambdas=Инлайнить простые лямбды\nservice.decompile.impl.decompiler-vineflower-config.log-level=Уровень логирования\nservice.decompile.impl.decompiler-vineflower-config.max-time-per-method=[УСТАРЕЛО] Максимальное время обработки метода\nservice.decompile.impl.decompiler-vineflower-config.rename-members=Переименовать члены\nservice.decompile.impl.decompiler-vineflower-config.user-renamer-class=Класс переименования пользователя\nservice.decompile.impl.decompiler-vineflower-config.new-line-separator=[УСТАРЕЛО] Разделитель новой строки\nservice.decompile.impl.decompiler-vineflower-config.indent-string=Строка отступа\nservice.decompile.impl.decompiler-vineflower-config.preferred-line-length=Предпочитаемая длина строки\nservice.decompile.impl.decompiler-vineflower-config.banner=Баннер\nservice.decompile.impl.decompiler-vineflower-config.error-message=Сообщение об ошибке\nservice.decompile.impl.decompiler-vineflower-config.thread-count=Количество потоков\nservice.decompile.impl.decompiler-vineflower-config.skip-extra-files=Пропускать дополнительные файлы\nservice.decompile.impl.decompiler-vineflower-config.warn-inconsistent-inner-attributes=Предупреждать о несогласованных внутренних атрибутах\nservice.decompile.impl.decompiler-vineflower-config.dump-bytecode-on-error=Вывести байткод при ошибке\nservice.decompile.impl.decompiler-vineflower-config.dump-exception-on-error=Вывести исключения при ошибке\nservice.decompile.impl.decompiler-vineflower-config.decompiler-comments=Комментарии декомпилятора\nservice.decompile.impl.decompiler-vineflower-config.sourcefile-comments=Комментарии SourceFile\nservice.decompile.impl.decompiler-vineflower-config.decompile-complex-constant-dynamic=Декомпилировать сложные выражения constant-dynamic\nservice.decompile.impl.decompiler-vineflower-config.force-jsr-inline=Принудительно инлайнить JSR\nservice.decompile.impl.decompiler-vineflower-config.dump-text-tokens=Вывести текстовые токены\nservice.decompile.impl.decompiler-vineflower-config.remove-imports=Удалить импорты\nservice.decompile.impl.decompiler-vineflower-config.mark-corresponding-synthetics=Пометить соответствующие синтетические\nservice.io=Ввод-вывод\nservice.io.directories-config=Директории\nservice.io.export-config=Экспорт\nservice.io.export-config.bundle-supporting-resources=Включать вспомогательные ресурсы в вывод\nservice.io.export-config.compression=Стратегия сжатия содержимого вывода\nservice.io.export-config.create-zip-dir-entries=Создавать записи 'директории' ZIP в выводе\nservice.io.export-config.warn-no-changes=Предупреждать при экспорте без внесённых изменений\nservice.io.gson-provider-config=Json\nservice.io.gson-provider-config.pretty-print=Красивый вывод\nservice.io.info-importer-config=Импорт содержимого\nservice.io.info-importer-config.class-patch-mode=Режим патча класса\nservice.io.recent-workspaces-config=Недавние рабочие пространства\nservice.io.recent-workspaces-config.last-workspace-export-path=Последний путь экспорта рабочего пространства\nservice.io.recent-workspaces-config.last-workspace-open-path=Последний путь открытия рабочего пространства\nservice.io.recent-workspaces-config.max-recent-workspaces=Максимальная запись недавних путей\nservice.io.recent-workspaces-config.recent-workspaces=Недавнее рабочее пространство\nservice.io.recent-workspaces-config.last-class-export-path=Последний путь экспорта класса\nservice.io.resource-importer-config=Импорт архивов\nservice.io.resource-importer-config.zip-strategy=Стратегия парсинга ZIP\nservice.io.resource-importer-config.allow-basic-base-offset-zero-check=По умолчанию проверять 0 как начало zip со стратегией JVM\nservice.io.resource-importer-config.skip-revisited-cen-to-local-links=Пропускать дублирующиеся записи CEN-to-LFH со стратегией JVM\nservice.io.resource-importer-config.ignore-file-lengths=Игнорировать заявленные длины файлов со стратегией Naive/Standard\nservice.io.resource-importer-config.adapt-standard-cen-file-names=Принять имена файлов CEN со стратегией Standard\nservice.io.resource-importer-config.max-embedded-zip-depth=Максимальная глубина обхода встроенных zip\nservice.io.resource-importer-config.parallelize=Включить многопоточное чтение ввода\nservice.mapping=Маппинг\nservice.mapping.mapping-aggregator-config=Агрегация маппингов\nservice.mapping.mapping-formats-config=Форматы маппингов\nservice.mapping.mapping-generator-config=Генератор маппингов\nservice.mapping.name-gen-provider=Генераторы имён\nservice.mapping.name-gen-provider.alphabet=Алфавит\nservice.mapping.name-gen-provider.alphabet.alphabet=Символы алфавита\nservice.mapping.name-gen-provider.alphabet.length=Минимальная длина\nservice.plugin=Плагины\nservice.plugin.plugin-manager-config=Менеджер плагинов\nservice.plugin.plugin-manager-config.scan-on-start=Загружать при запуске\nservice.plugin.script-manager-config=Менеджер скриптов\nservice.plugin.script-manager-config.file-watching=Пассивно сканировать директорию скриптов на изменения\nservice.transform=Трансформация\nservice.transform.transformation-applier-config=Применение трансформаций\nservice.transform.transformation-applier-config.parallelize=Включить многопоточное применение трансформеров\nservice.ui=Пользовательский интерфейс\nservice.ui.bind-config=Привязки\nservice.ui.bind-config.bundle=Пакет карты привязок\nservice.ui.class-editing-config=Редактирование классов\nservice.ui.class-editing-config.default-android-editor=Редактор по умолчанию для классов Android\nservice.ui.class-editing-config.default-jvm-editor=Редактор по умолчанию для классов JVM\nservice.ui.decompile-pane-config=Панель декомпиляции\nservice.ui.decompile-pane-config.timeout-seconds=Таймаут декомпилятора (секунды)\nservice.ui.decompile-pane-config.mapping-acceleration=Ускорить операции ремаппинга\nservice.ui.hex-config=Hex редактор\nservice.ui.hex-config.row-length=Столбцы\nservice.ui.hex-config.row-split-interval=Интервал разделения столбцов\nservice.ui.hex-config.show-address=Показать адрес\nservice.ui.hex-config.show-ascii=Показать ASCII\nservice.ui.member-format-config=Формат полей и методов\nservice.ui.member-format-config.name-type-display=Отображение имени и типа\nservice.ui.tab-completion-config=Автодополнение табуляцией\nservice.ui.text-format-config=Формат текста\nservice.ui.tab-completion-config.enabled-in-assembler=Включено в ассемблере\nservice.ui.tab-completion-config.max-completion-length=Максимальная длина дополнения\nservice.ui.tab-completion-config.max-completion-rows=Отображаемые строки дополнения\nservice.ui.tab-completion-config.popup-position=Предпочитаемая позиция всплывающего окна дополнения относительно курсора\nservice.ui.text-format-config.escape=Включить экранирование текста\nservice.ui.text-format-config.max-length=Максимальная длина отображения текста\nservice.ui.text-format-config.shorten=Включить сокращение текста\nservice.ui.file-type-syntax-association-config=Ассоциации типов файлов\nservice.ui.file-type-syntax-association-config.extensions-to-langs=Карта расширений на языки\nservice.ui.snippets-config=Фрагменты\nservice.ui.window-manager-config=Менеджер окон\nservice.ui.window-scale-config=Масштабирование окна\nservice.ui.window-scale-config.scale=Масштаб\nservice.ui.workspace-explorer-config=Обозреватель рабочего пространства\nservice.ui.workspace-explorer-config.drag-drop-action=Поведение перетаскивания\nservice.ui.workspace-explorer-config.max-tree-dir-depth=Максимальная глубина дерева директорий\nservice.ui.language-config=Язык\nservice.ui.language-config.current=Текущий язык\n\n### Matcher translations\nnumber.match.equal=значение == n\nnumber.match.not=значение != n\nnumber.match.gt=значение > n\nnumber.match.gte=значение >= n\nnumber.match.lt=значение < n\nnumber.match.lte=значение <= n\nnumber.match.gt-lt=мин < значение < макс\nnumber.match.gte-lt=мин <= значение < макс\nnumber.match.gt-lte=мин < значение <= макс\nnumber.match.gte-lte=мин <= значение <= макс\nnumber.match.any-of=числа.contains(значение)\nstring.match.anything=Всё\nstring.match.zilch=Ничего\nstring.match.contains=строка.contains(значение)\nstring.match.contains-ic=строка.containsIgnoreCase(значение)\nstring.match.ends=строка.endsWith(значение)\nstring.match.ends-ic=строка.endsWithIgnoreCase(значение)\nstring.match.equal=строка.equals(значение)\nstring.match.equal-ic=строка.equalsIgnoreCase(значение)\nstring.match.regex-full=строка.matches(значение)\nstring.match.regex-partial=строка.matchesPartially(значение)\nstring.match.starts=строка.startsWith(значение)\nstring.match.starts-ic=строка.startsWithIgnoreCase(значение)\n\n### Misc stuff\nmisc.acknowledge=Принять\nmisc.all=Все\nmisc.none=Нет\nmisc.done=Готово\nmisc.ignored=Игнорировано\nmisc.enabled=Включено\nmisc.disabled=Отключено\nmisc.download=Скачать\nmisc.download.invalid-url=Недопустимый URL\nmisc.before=До\nmisc.after=После\nmisc.load=Загрузить\nmisc.clear=Очистить\nmisc.export=Экспорт\nmisc.remove=Удалить\nmisc.removed=Удалено\nmisc.casesensitive=Учитывать регистр\nmisc.path=Путь\nmisc.regex=Регулярное выражение\nmisc.member.field=Поле\nmisc.member.method=Метод\nmisc.member.field-n-method=Поле и метод\nmisc.member.inner-class=Внутренний класс\nmisc.member.inner-interface=Внутренний интерфейс\nmisc.member.inner-enum=Внутренний enum\nmisc.member.inner-annotation=Внутренняя аннотация\nmisc.accessflag.visibility.public=Публичный\nmisc.accessflag.visibility.protected=Защищённый\nmisc.accessflag.visibility.private=Приватный\nmisc.accessflag.visibility.package=Пакетный\nmisc.direction.up=Вверх\nmisc.direction.down=Вниз\nmisc.direction.left=Влево\nmisc.direction.right=Вправо\nmisc.direction.top=Верх\nmisc.direction.bottom=Низ\nmisc.position.top=Верх\nmisc.position.bottom=Низ\nmisc.position.left=Слева\nmisc.position.right=Справа\nmisc.position.center=Центр\nmisc.position.middle=Середина\n\n### Tutorial messages\nservice.ui.tutorial-config=Обучение\ntutorial.1.class=Добро пожаловать в обучение Recaf!\\n\\nСледуйте комментариям в этом классе (и других), чтобы узнать о функциях Recaf.\ntutorial.1.field=Сейчас вы смотрите на декомпилированный код. Исходного кода нет, но для простого класса, как этот, декомпилятор должен дать вам почти идеальное представление о том, как выглядел бы исходный код.\\n\\nRecaf имеет несколько встроенных декомпиляторов:\\n\\n - CFR (по умолчанию)\\n - Procyon\\n - Vineflower (форк FernFlower, декомпилятора, встроенного в IntelliJ)\\n\\nВы можете переключаться между этими декомпиляторами, нажав кнопку шестерёнки внизу справа.\ntutorial.1.main=Если вы смотрите на обычный Java код (без обфускации, без сторонних языков типа Kotlin/Groovy/и т.д.), то вы можете редактировать этот декомпилированный код и нажать [Control + S], чтобы внести изменения в класс. Recaf не будет автоматически заменять файл на вашей локальной машине при этом, изменения остаются только в Recaf при успешном сохранении. Чтобы сохранить любые изменения, вам нужно перейти в меню 'Файл' и выбрать 'Экспорт приложения'.\ntutorial.1.run=Этот метод берёт поле 'String message', определённое выше, и выводит его.\\n\\nПопробуйте изменить строку, присвоенную полю, на что-то другое, кроме 'Hello World', и сохраните изменения.\\n\\nВы должны увидеть, как границы этой области мигают зелёным при успехе. Как только это произойдёт, следующая глава откроется автоматически.\ntutorial.2.class=Этот класс был изменён, чтобы скрыть секретный метод. Некоторые методы могут быть скрыты, если они помечены как автоматически сгенерированные компилятором. Это довольно распространено в коде, использующем лямбда-выражения. Кроме того, обфускаторы также могут помечать вещи как автоматически сгенерированные, чтобы скрыть их.\ntutorial.2.field=В этом классе есть больше, чем просто это поле.\\n\\nВверху справа есть вкладка 'Поля и методы'. Нажмите на неё, чтобы просмотреть объявленные поля и методы этого класса. Вы должны найти там скрытый метод.\\nПопробуйте дважды кликнуть на него!\ntutorial.2.method=Рядом с вкладкой 'Поля и методы' находится вкладка 'Наследование'. По умолчанию она показывает, какие классы расширяют или реализуют текущий класс. Внизу панели есть кнопка, которая показывает, какой контент отображается. Вы можете нажать на неё, чтобы переключаться между показом родительских и дочерних классов. Эта панель покажет вам полное дерево наследования, так что если у вас 'A extends B, B extends C, C extends D', вы можете увидеть всю цепочку в панели.\\n\\nПопробуйте найти следующую главу в панели 'Наследование'.\\nПравый клик на ней и выберите 'Перейти к классу'.\ntutorial.3.class=Этот класс также был изменён, чтобы скрыть секретное значение. Вы можете найти его, правым кликом на вкладке этого класса (вверху, она должна быть подчёркнута синим) и 'Изменить вид' на 'Низкоуровневый'. Этот низкоуровневый вид показывает больше 'низкоуровневых' деталей о файле класса. Он не будет абстрагировать детали, как другие функции Recaf. Посмотрите вокруг в поисках секретного ключа, и когда найдёте его, вернитесь и введите его в поле 'answer' ниже.\ntutorial.3.field=Поместите секретный текст сюда и сохраните через [Control + S].\\n\\nЕсли ваш ответ неверен, это перезапишет содержимое класса и сотрёт ключ. О нет!\\n\\nНе волнуйтесь, вы можете вернуться к более старым версиям классов и файлов через [Control + U]. Это вернёт файл к последнему состоянию, которое было до сохранения, то есть секрет вернётся на своё исходное место. Это не то же самое, что [Control + Z], который является обычной отменой текста.\ntutorial.3.method=Если вам нужна подсказка, секрет не ссылается ни на один исполняемый код. Где бы была определена такая константа?\ntutorial.4.class=На этот раз нет секретов. Всё, что вам нужно сделать, это решить эту математическую задачу.\\n\\nКакое итоговое значение передаётся в метод 'consume'?\ntutorial.4.field=Поместите целочисленное значение, которое передаётся в 'consume', сюда.\ntutorial.4.method=Это выглядит как много работы для ручного выполнения. Не волнуйтесь, Recaf делает это очень легко!\\n\\nВ байткод-ассемблере Recaf есть утилиты, которые могут помочь. Чтобы открыть ассемблер, правый клик на имени какого-либо объявленного класса, поля или метода, затем выберите 'Редактировать > Редактировать в ассемблере'. Вы также можете правым кликом на записях во вкладке 'Поля и методы' вверху справа.\\n\\nДля этого примера правый клик на имени этого метода (где написано \"main\").\\nЗатем выберите 'Редактировать > Редактировать в ассемблере'.\\n\\nАссемблер покажет вам инструкции этого метода вместе с любой другой релевантной метаданной. Внизу ассемблера есть некоторые инструменты, которые можно открыть, кликнув на них. Откройте инструмент 'Анализ' и затем кликните на разные строки кода в методе. Вы должны довольно быстро выяснить значение, которое передаётся в 'consume'.\ntutorial.5.class=Этот класс обфусцирован! Если вы попытаетесь сохранить используя [Control + S] с этим декомпилированным кодом, вы увидите, как редактор мигает красным, и ошибки появятся слева как наложения поверх номеров строк.\\n\\nВы не можете сохранить, когда присутствуют ошибки.\\n\\nВ обфусцированном коде вы не должны использовать [Control + S] на декомпилированном коде.\\nВместо этого используйте ассемблер и вносите изменения там. Сохранение в ассемблере позволяет вам вносить изменения, которые обычно не разрешены в исходном коде Java. Плюс, это гарантирует, что вы меняете только то, что действительно намереваетесь. Когда вы сохраняете декомпилированный код, вы можете непреднамеренно сохранить изменения, сделанные декомпилятором, которые не правильно представляют, как работает логика приложения.\ntutorial.5.main=В этой главе вам нужно открыть ассемблер на обфусцированном методе.\\n\\nИспользуйте вкладку 'Поля и методы' и правый клик на обфусцированном методе, затем откройте ассемблер.\\n\\nОбфусцированный метод в настоящее время реализован неправильно. Правильная реализация может быть найдена в комментарии ниже в следующем методе.\\n\\nСкопируйте код из того комментария.\\n\\nВ ассемблере для обфусцированного метода нажмите 'Java в байткод'. Слева редактор, который принимает исходный код Java. Справа редактор, который покажет вам эквивалентный байткод Java. Вставьте код, который вы скопировали, в левую сторону (заменив существующий код там и удалив '*' перед каждой строкой).\\n\\nПри правильном выполнении вы должны увидеть справа метод, содержащий байткод, сгенерированный из компиляции кода слева. Вы можете скопировать инструкции из этого сгенерированного метода и вставить их в ассемблер выше. Чтобы было ясно, вы копируете только инструкции, а не весь сгенерированный метод. Если вы правильно скопировали логику расшифровки и сохранили изменения в ассемблере, следующая глава будет раскрыта.\ntutorial.5.decrypt=int key = Thread.currentThread().getStackTrace()[1].getMethodName().hashCode();\\nchar[] chars = text.toCharArray();\\nfor (int i = 0; i < chars.length; i++) chars[i] ^= key;\\nreturn String.valueOf(chars);\ntutorial.5.finished=Правый клик на типе возврата этого метода \"Chapter6\" и выберите 'Перейти к классу'.\\n\\nВы можете альтернативно использовать [Control + Click] на ссылках, чтобы избежать необходимости проходить через контекстное меню каждый раз.\ntutorial.6.class=Когда вы правым кликом на классе, поле или методе, вы можете увидеть, какой другой код ссылается на выбранную вами вещь.\\n\\n - Классы: Поиск > Ссылки на типы/члены\\n - Поля: Ссылки на поля\\n - Методы: Ссылки на методы\\n\\nВы можете найти эти опции поиска в меню 'Поиск' в строке меню вверху UI, вместе с другими вещами, такими как поиск строк и числовых литералов.\ntutorial.6.method=Попробуйте найти, что ссылается на этот метод.\ntutorial.7.class=Поздравляем, это конец обучения!\\n\\nНажмите [Control + S] ещё раз в последний раз, чтобы отметить обучение как завершённое.\\n\\nПозже будет добавлено больше в обучение, но это должно покрыть все самые важные функции для вашего обычного случая использования. В то же время, не стесняйтесь исследовать другие функции самостоятельно!"
  },
  {
    "path": "recaf-ui/src/main/resources/translations/sv_SE.lang",
    "content": "## Language name\nlang.name=English\n\n##### General items\n## Main and context menus\nmenu.association.override=Överskrid Språk\nmenu.association.none=Inga tilldelningar konfigurerade\nmenu.config=Konfiguration\nmenu.config.edit=Redigera\nmenu.config.export=Exportera\nmenu.config.import=Importera\nmenu.file=Fil\nmenu.file.attach=Anslut till fjärr\nmenu.file.addtoworkspace=Lägg till i arbetsmiljö\nmenu.file.openworkspace=Öppna arbetsmiljö\nmenu.file.exportapp=Exportera applikation\nmenu.file.exportworkspace=Exportera arbetsmiljö konfiguration\nmenu.file.modifications=Visa ändringar\nmenu.file.recent=Senaste\nmenu.file.close=Stäng\nmenu.file.quit=Avsluta\nmenu.goto.class=Gå till klass\nmenu.goto.field=Gå till fält\nmenu.goto.method=Gå till metod\nmenu.goto.instruction=Gå till instruktion\nmenu.goto.file=Gå till fil\nmenu.goto.label=Gå till etikett\nmenu.edit=Redigera\nmenu.edit.add.field=Lägg till fält\nmenu.edit.add.method=Lägg till metod\nmenu.edit.add.annotation=Lägg till annotering\nmenu.edit.remove.field=Ta bort fält\nmenu.edit.remove.method=Ta bort metod\nmenu.edit.remove.annotation=Ta bort annotering\nmenu.edit.assemble.class=Redigera klass\nmenu.edit.assemble.field=Redigera fält\nmenu.edit.assemble.method=Redigera metod\nmenu.edit.remove=Ta bort\nmenu.edit.copy=Kopiera\nmenu.edit.delete=Radera\nmenu.help=Hjälp\nmenu.help.discord=Discord\nmenu.help.docs=Online-Dokumentation för användare\nmenu.help.docsdev=Online-Dokumentation för utvecklare\nmenu.help.github=Github\nmenu.help.issues=Rapportera problem\nmenu.help.sysinfo=System information\nmenu.refactor=Refaktorisera\nmenu.refactor.move=Flytta\nmenu.refactor.rename=Byt namn\nmenu.search=Sök\nmenu.search.string=String\nmenu.search.number=Nummer\nmenu.search.class.member-references=Member references\nmenu.search.class.type-references=Type references\nmenu.search.method-overrides=Metodöverskridningar\nmenu.search.method-references=Metod referenser\nmenu.search.field-references=Fält referenser\nmenu.search.noresults=Inga resultat\nmenu.mappings=Mappings\nmenu.mappings.apply=Tillämpa\nmenu.mappings.export=Exportera\nmenu.mappings.export.unsupported=%s (Ej stödd)\nmenu.mappings.generate=Generera\nmenu.mappings.view=Nuvarande mappings\nmenu.scripting=Skriptning\nmenu.scripting.list=Skript\nmenu.scripting.none-found=Inga skript hittades\nmenu.scripting.manage=Hantera skript\nmenu.scripting.new=Nytt skript\nmenu.scripting.edit=Redigera\nmenu.scripting.browse=Bläddra i skript\nmenu.scripting.save=Spara skript\nmenu.scripting.execute=Utför\nmenu.scripting.editor=Skriptredigerare\nmenu.scripting.author=Skapare\nmenu.scripting.version=Version\nmenu.view=Visa\nmenu.view.hierarchy=Klass hierarki\nmenu.view.hierarchy.children=Underklasser\nmenu.view.hierarchy.parents=Föräldraklasser\nmenu.view.methodcfg=kontrollflödesgraf\nmenu.tab.close=Stäng\nmenu.tab.closeothers=Stäng andra\nmenu.tab.closeall=Stäng alla\nmenu.tab.copypath=Kopiera sökväg\nmenu.image.resetscale=Återställ zoom\nmenu.image.center=Centrera bild\nmenu.hex.copyas=Kopiera som...\nmenu.mode=Ändra vy\nmenu.mode.class.auto=Automatisk\nmenu.mode.class.decompile=Dekompilera\nmenu.mode.file.auto=Automatisk\nmenu.mode.file.text=Text\nmenu.mode.file.hex=Hex\nmenu.mode.diff.decompile=Dekompilera\nmenu.mode.diff.disassemble=Disassemblera\nmenu.vm=Virtualisera\nmenu.vm.optimize=Optimera\nmenu.vm.run=Kör\nmenu.plugin=Plugins\nmenu.plugin.manage=Hantera plugins\nmenu.plugin.installed=Installerade\nmenu.plugin.remote=Fjärr\nmenu.plugin.browse=Bläddra bland plugins\nmenu.plugin.enabled=Aktiverade\nmenu.plugin.uninstall=Avinstallera\nmenu.plugin.uninstall.warning=Är du säker på att du vill ta bort det här pluginet?\n\n##### Dialog texts\ndialog.cancel=Avbryt\ndialog.close=Stäng\ndialog.confirm=Bekräfta\ndialog.finish=Slutför\ndialog.next=Nästa\ndialog.previous=Föregående\ndialog.dismiss=Avvisa\ndialog.configure=Konfigurera\ndialog.warning=Varning\ndialog.restart=För att ändra den här konfigurationsinställningen rekommenderas en omstart.\\nÄr du säker på att du vill tillämpa ändringen?\ndialog.unknownextension=Okänd filändelse. Vill du konfigurera en språkförknippning?\n\n## File chooser\ndialog.title.primary=Primär resurs\ndialog.title.supporting=Stödjande resurser\ndialog.file.open=Öppna\ndialog.file.open.directory=Kataloger\ndialog.file.open.file=Filer\ndialog.file.export=Exportera\ndialog.file.save=Spara\ndialog.file.nothing=Inget valt\ndialog.filefilter.any=Alla typer\ndialog.filefilter.mapping=Mappings\ndialog.filefilter.input=Applikationer\ndialog.filefilter.workspace=Arbetsmiljöer\n\n## File drop items\ndialog.title.create-workspace=Skapa arbetsmiljö\ndialog.title.update-workspace=Hantera arbetsmiljöinmatning\ndialog.title.close-workspace=Stäng arbetsmiljön?\ndialog.option.create-workspace=Skapa ny arbetsmiljö\ndialog.option.update-workspace=Lägg till i arbetsmiljön\n\n## Copy class/file\ndialog.title.copy-class=Kopiera klass\ndialog.title.copy-directory=Kopiera katalog\ndialog.title.copy-field=Kopiera fält\ndialog.title.copy-file=Kopiera fil\ndialog.title.copy-method=Kopiera metod\ndialog.header.copy-class=Ange ett nytt namn för den kopierade klassen.\ndialog.header.copy-directory=Ange ett nytt namn för den kopierade katalogen.\ndialog.header.copy-field=Ange ett nytt namn för det kopierade fältet.\ndialog.header.copy-file=Ange ett nytt namn för den kopierade filen.\ndialog.header.copy-method=Ange ett nytt namn för den kopierade metoden.\n\n## Delete class/file\ndialog.title.delete-class=Radera klass\ndialog.title.delete-directory=Radera katalog\ndialog.title.delete-field=Radera fält\ndialog.title.delete-file=Radera fil\ndialog.title.delete-method=Radera metod\ndialog.title.delete-package=Radera paket\ndialog.title.delete-resource=Radera resurs\ndialog.header.delete-class=Är du säker på att du vill radera: %s?\ndialog.header.delete-directory=Är du säker på att du vill radera: %s?\ndialog.header.delete-field=Är du säker på att du vill radera: %s?\ndialog.header.delete-file=Är du säker på att du vill radera: %s?\ndialog.header.delete-method=Är du säker på att du vill radera: %s?\ndialog.header.delete-package=Är du säker på att du vill radera: %s?\ndialog.header.delete-resource=Är du säker på att du vill radera: %s?\n\n## Rename class/file\ndialog.title.rename-class=Byt namn på klass\ndialog.title.rename-class-warning=Varning\ndialog.title.rename-directory=Byt namn på katalog\ndialog.title.rename-field=Byt namn på fält\ndialog.title.rename-file=Byt namn på fil\ndialog.title.rename-file-warning=Varning\ndialog.title.rename-method=Byt namn på metod\ndialog.title.rename-package=Byt namn på paket\ndialog.header.rename-class=Ange ett nytt namn för klassen.\ndialog.header.rename-class-error=Klassnamnet finns redan.\\nVänligen välj ett annat namn.\ndialog.header.rename-directory=Ange ett nytt namn för katalogen.\ndialog.header.rename-field=Ange ett nytt namn för fältet.\ndialog.header.rename-field-error=Fältnamnet finns redan.\\nVänligen välj ett annat namn.\ndialog.header.rename-file=Ange ett nytt namn för filen.\ndialog.header.rename-method=Ange ett nytt namn för metoden.\ndialog.header.rename-method-error=Metodnamnet finns redan.\\nVänligen välj ett annat namn.\ndialog.header.rename-package=Ange ett nytt namn för paketet.\n\n## Move class/file\ndialog.title.move-class=Välj ett destinationspaket\ndialog.title.move-directory=Välj en destinations (överordnad) katalog\ndialog.title.move-file=Välj en destinationskatalog\ndialog.title.move-package=Välj ett destinations (överordnat) paket\ndialog.header.move-class=För över klassen till ett nytt paket.\ndialog.header.move-directory=För över katalogen till en ny föräldrakatalog.\ndialog.header.move-file=För över filen till en ny katalog.\ndialog.header.move-package=För över paketet till ett nytt föräldrapaket.\n\n## VM actions\ndialog.title.vm-invoke-args=Virtualisera metodanrop\ndialog.title.vm-peephole-invoke-args=Virtualiserad peephole-optimering\ndialog.vm.execute=Utför\ndialog.vm.optimize=Optimera\ndialog.vm.create-dummy=Använd dummy\ndialog.vm.create-null=Använd null\n\n## Hex dialogs\ndialog.hex.title.insertcount=Infoga\ndialog.hex.header.insertcount=Hur många byte vill du infoga?\n\n# Base Converter dialog\ndialog.conv.title.literal=Nummer Literal\ndialog.conv.title.expression=Nummer uttryck\n\n## Quick nav\ndialog.quicknav=Snabbnavigering\n\n## Error dialog\ndialog.error.exportworkspace.title=Misslyckades med att exportera arbetsmiljö\ndialog.error.exportworkspace.header=Ett fel inträffade när det skrevs till destinationen\ndialog.error.exportworkspace.content=Felmeddelandet var:\ndialog.error.loadworkspace.title=Misslyckades med att ladda arbetsmiljö\ndialog.error.loadworkspace.header=Ett fel inträffade när det lästes från de valda filerna\ndialog.error.loadworkspace.content=Felmeddelandet var:\ndialog.error.loadsupport.title=Misslyckades med att ladda stödjande resurser\ndialog.error.loadsupport.header=Ett fel inträffade när det lästes från de valda filerna\ndialog.error.loadsupport.content=Felmeddelandet var:\ndialog.error.attach.title=Misslyckades med att ansluta till JVM\ndialog.error.attach.header=Ett fel inträffade när anslutningen till den avlägsna JVM:en upprättades\ndialog.error.attach.content=Felmeddelandet var:\n\n## Wizard\nwizard.chooseaction=Välj en åtgärd\nwizard.selectprimary=Välj primär resurs\nwizard.currentworkspace=Aktuell arbetsmiljö\n\n\n##### Panels\n## Welcome\nwelcome.title=Välkommen\n\n## Workspace\nworkspace.title=Arbetsmiljö\nworkspace.filter-prompt=Filter: Klass-/filnamn...\nworkspace.info=Information\n\n## Attach\nattach.unsupported=Anslutning misslyckades med att initialisera\nattach.unsupported.detail=Anslutningsagenten misslyckades med självextrahering.\nattach.connect=Anslut\nattach.tab.properties=Egenskaper\nattach.tab.classloading=Klasser\nattach.tab.compilation=Kompilering\nattach.tab.system=System\nattach.tab.runtime=Körning\nattach.tab.thread=Trådar\n\n## Changes view\nmodifications.none=Ingen ändringshistorik för objektet\nmodifications.title=Ändringar\n\n## Java area\njava.decompiling=Dekompilera av klass...\njava.unparsable=JavaParser misslyckades med att tolka källkoden, kontextåtgärder är endast tillgängliga i fliken 'Fält och Metoder'\njava.savewitherrors=Det verkar som att detta är första gången du gör ändringar som resulterade i fel.\\nDessa fel uppstår vanligtvis när den dekompilerade koden inte är semantiskt korrekt Java.\\nDu måste åtgärda dessa fel innan ändringarna kan sparas.\\n\\nNågra förslag:\\n - Ändra dekompileraren\\n - Hovra över de röda felrutorna eller klicka på felrutan högst upp för att se vad felen är\\n - Använd assembler istället för rekompilering för att göra ändringar\njava.savewitherrors.title=Angående rekompileringsfel\njava.decompiler=Dekompilerare\njava.targetversion=Kompilera för målversion\njava.targetversion.auto=Matcha klass fil version\njava.targetdownsampleversion=Nedsamplingsmålversion\njava.targetdownsampleversion.disabled=Inaktiverad\njava.targetdebug=Kompilera med felsökningsinformation\njava.info=Klassinformation\njava.info.version=Klassversion\njava.info.sourcefile=Källfilsnamn\n\n## Search bar\nfind.replace=Ersätt\nfind.replaceall=Ersätt alla\nfind.regexreplace=Text att ersätta med\n\n## Fields and methods\nfieldsandmethods.title=Fält och metoder\nfieldsandmethods.showoutlinedsynths=Visa syntetiska (kompileringsgenererade) medlemmar\nfieldsandmethods.showoutlinedvisibility=Filtrera efter medlems synlighet\nfieldsandmethods.showoutlinedmembertype=Filtrera efter medlemstyp\nfieldsandmethods.sortalphabetically=Sortera alfabetiskt\nfieldsandmethods.sortbyvisibility=Sortera efter synlighet\nfieldsandmethods.filter.prompt=Filter: Fält/metodnamn...\n\n## Hierarchy\nhierarchy.title=Hierarki\nhierarchy.children=Tillägg\nhierarchy.parents=Föräldrar\n\n## Logging\nlogging.title=Loggning\n\n## Assembler\nassembler.title=Assembler\nassembler.analysis.title=Variabler & Stack\nassembler.analysis.stack=Stack\nassembler.analysis.name-column=Namn\nassembler.analysis.value-column=Värde\nassembler.playground.title=Java till Bytecode\nassembler.playground.comment=Du kan skriva vanlig Java här och låta den översättas till bytekod\nassembler.variables.title=Variabler\nassembler.variables.empty=<Kräver kompilering minst en gång>\nassembler.suggestions.none=Inga förslag\n\n## Search\nsearch.run=Sök\nsearch.results=Resultat\nsearch.text=Textinnehåll\nsearch.textmode=Textmatchläge\nsearch.number=Nummervärde\nsearch.numbermode=Nummermatchläge\nsearch.refowner=Medlemsägare\nsearch.refname=Medlemsnamn\nsearch.refdesc=Medlemstypbeskrivning\n\n## Help\nhelp.system=System\nhelp.system.sub=Information om operativsystemet\nhelp.java=Java\nhelp.java.sub=Information om JVM (Java virtuell maskin)\nhelp.javafx=JavaFX\nhelp.javafx.sub=Information om JavaFX-gränssnittet\nhelp.recaf=Recaf\nhelp.recaf.sub=Information om Recaf\nhelp.copy=Kopiera information till urklipp\nhelp.opendir=Öppna Recaf-katalogen\n\n## Mapping generator\nmapgen=Mapping-generator\nmapgen.genimpl=Namngivningskonvention\nmapgen.filter.name=Namn\nmapgen.filters=Filter\nmapgen.filters.add=Lägg till filter\nmapgen.filters.edit=Redigera valt\nmapgen.filters.editdone=Klar\nmapgen.filters.delete=Radera valt\nmapgen.filters.type=Filtertyp\nmapgen.filter.modifiers.tooltip=Modifierare separeras med mellanslag\nmapgen.filter.excludealreadymapped=Uteslut redan kartlagda\nmapgen.filter.excludemodifier=Uteslut modifierare\nmapgen.filter.excludeclasses=Uteslut klasser\nmapgen.filter.excludename=Uteslut namn\nmapgen.filter.excludeclass=Uteslut på klasser\nmapgen.filter.excludefield=Uteslut på fält\nmapgen.filter.excludemethod=Uteslut på metoder\nmapgen.filter.includemodifier=Inkludera modifierare\nmapgen.filter.includeclass=Inkludera på klasser\nmapgen.filter.includefield=Inkludera på fält\nmapgen.filter.includemethod=Inkludera på metoder\nmapgen.filter.includewhitespacenames=Inkludera mellanslagsnamn\nmapgen.filter.includenonasciinames=Inkludera icke-ascii-namn\nmapgen.filter.includekeywords=Inkludera nyckelord\nmapgen.filter.includename=Inkludera namn\nmapgen.filter.includeclasses=Inkludera klasser\nmapgen.title.newfilter=Nytt filter\nmapgen.header.newfilter=Ange filterinnehåll\nmapgen.preview.empty=Statistik över genererade kartläggningar visas här\\n\\n\\n\nmapgen.generate=Generera\nmapgen.apply=Använd\n\n## Mapping view\nmapprog=Mapping framsteg\nmapprog.metric.size=Klass filstorlek\nmapprog.metric.membercount=Klassfält och metoder\n\n##### Tree\ntree.classes=Klasser\ntree.files=Filer\ntree.defaultpackage=(Standardpaket)\ntree.defaultdirectory=(Rotkatalog)\ntree.prompt=Dra dina filer hit\ntree.hidelibs=Dölj bibliotek\n\n##### Services\nservice=Alla tjänster\nservice.analysis=Analys\nservice.analysis.graph-calls-config=Samtalsgraf\nservice.analysis.graph-inheritance-config=Hierarkigraf\nservice.analysis.search-config=Sök\nservice.analysis.entry-points=Ingångspunkter\nservice.analysis.entry-points.none=Inga poster hittades\nservice.compile=Kompilering\nservice.compile.java-compiler-config=Javac\nservice.compile.java-compiler-config.generate-phantoms=Generera saknade klasser\nservice.compile.java-compiler-config.default-emit-debug=Standard för att inkludera felsökning\nservice.compile.java-compiler-config.default-compile-target-version=Standard målversionsklass\nservice.compile.java-compiler-config.default-downsample-target-version=Standard målversionsklass för nedsampling\nservice.debug=Anslutning/Felsökning\nservice.debug.attach-config=Anslutningskonfiguration\nservice.debug.attach-config.attach-jmx-bean-agent=Anslut JMX bean-agent\nservice.debug.attach-config.passive-scanning=Passiv skanningstillstånd\nservice.config-manager-config=Konfigurationshanterare\nservice.decompile=Avkompilering\nservice.decompile.decompiler-cfr-config=CFR\nservice.decompile.decompiler-cfr-config.aexagg=Försök att utöka och sammanfoga exceptions mer aggressivt\nservice.decompile.decompiler-cfr-config.aexagg2=Försök att utöka och sammanfoga exceptions mer aggressivt (kan ändra semantiken)\nservice.decompile.decompiler-cfr-config.aggressivedocopy=Klona kod från omöjliga hopp in i loopar med 'första' test\nservice.decompile.decompiler-cfr-config.aggressivedoextension=Vik omöjliga hopp i do-loopar med 'första' test\nservice.decompile.decompiler-cfr-config.aggressiveduff=Vik Duff-liknande omkopplare med ytterligare kontroll.\nservice.decompile.decompiler-cfr-config.aggressivesizethreshold=Opcode-räkning vid vilken aggressiva minskningar utlöses\nservice.decompile.decompiler-cfr-config.allowmalformedswitch=Tillåt potentiellt felaktiga switch-satser\nservice.decompile.decompiler-cfr-config.antiobf=Ångra olika obfuskeringar\nservice.decompile.decompiler-cfr-config.arrayiter=Återskapa iteration baserad på array\nservice.decompile.decompiler-cfr-config.collectioniter=Återskapa iteration baserad på samling\nservice.decompile.decompiler-cfr-config.commentmonitors=Ersätt monitorer med kommentarer - användbart om vi är helt förvirrade\nservice.decompile.decompiler-cfr-config.comments=Utmatning av kommentarer som beskriver dekompilerarens status, återfallflaggor etc.\nservice.decompile.decompiler-cfr-config.constobf=Ångra konstant obfuscering\nservice.decompile.decompiler-cfr-config.decodeenumswitch=Återskapa switch för enum\nservice.decompile.decompiler-cfr-config.decodefinally=Återskapa finally-satser\nservice.decompile.decompiler-cfr-config.decodelambdas=Återskapa lambda-funktioner\nservice.decompile.decompiler-cfr-config.decodestringswitch=Återskapa switch för string\nservice.decompile.decompiler-cfr-config.eclipse=Aktivera transformationer för att hantera Eclipse-kod bättre\nservice.decompile.decompiler-cfr-config.elidescala=Ta bort saker som inte är användbara i Scala-utmatning (serialVersionUID, @ScalaSignature)\nservice.decompile.decompiler-cfr-config.forbidanonymousclasses=Tillåt inte anonyma klasser.\nservice.decompile.decompiler-cfr-config.forbidmethodscopedclasses=Tillåt inte klasser som är begränsade till metoder.\nservice.decompile.decompiler-cfr-config.forceclassfilever=Anger versionen av klassfilen (och därmed Java) som klassfiler dekompileras som.\nservice.decompile.decompiler-cfr-config.forcecondpropagate=Drar tillbaka resultat av deterministiska hopp genom vissa konstanta tilldelningar\nservice.decompile.decompiler-cfr-config.forceexceptionprune=Ta bort inbäddade exceptions om de inte ändrar semantiken\nservice.decompile.decompiler-cfr-config.forcereturningifs=Flytta returnering till hoppunkten\nservice.decompile.decompiler-cfr-config.forcetopsort=Uppmana grundläggande blocksortering. Vanligtvis endast användbart när obfuskering är närvarande.\nservice.decompile.decompiler-cfr-config.forcetopsortaggress=Tvångsaktivera extra aggressiva topsort-alternativ\nservice.decompile.decompiler-cfr-config.forcetopsortnopull=Tvångsaktivera topsort att inte flytta 'try' block\nservice.decompile.decompiler-cfr-config.forloopaggcapture=Tillåt for-loopar att aggressivt rulla mutationer i uppdateringssektionen, även om de inte verkar vara involverade i predikatet\nservice.decompile.decompiler-cfr-config.hidebridgemethods=Dölj bridge-metoder\nservice.decompile.decompiler-cfr-config.hidelangimports=Dölj import från java.lang.\ndecompiler-cfr-config.hidelongstrings=Dölj mycket långa strings - användbart om förvrängare har placerat falsk kod i strings\nservice.decompile.decompiler-cfr-config.hideutf=Dölj UTF8-tecken - citera dem istället för att visa de råa tecknen\nservice.decompile.decompiler-cfr-config.ignoreexceptions=Ignorera exception-information om helt fast (VARNING: ändrar semantiken, farligt!)\nservice.decompile.decompiler-cfr-config.ignoreexceptionsalways=Ignorera exception-information (VARNING: ändrar semantiken, farligt!)\nservice.decompile.decompiler-cfr-config.innerclasses=Dekompilera inre klasser\nservice.decompile.decompiler-cfr-config.instanceofpattern=Återskapa instanceof mönstervarianter\nservice.decompile.decompiler-cfr-config.j14classobj=Omvänd Java 1.4-klassobjektskonstruktion\nservice.decompile.decompiler-cfr-config.labelledblocks=Tillåt att generera kod som använder märkta block (hantering av udda forward gotos)\nservice.decompile.decompiler-cfr-config.lenient=Var lite mer eftergivande i situationer där vi normalt skulle kasta en exception\nservice.decompile.decompiler-cfr-config.liftconstructorinit=Flytta initialiseringskod som är gemensam för alla konstruktorer till medlemsinitialisering\nservice.decompile.decompiler-cfr-config.obfattr=Ångra attribut obfuskering\nservice.decompile.decompiler-cfr-config.obfcontrol=Ångra control-flow obfuskering\nservice.decompile.decompiler-cfr-config.override=Generera @Override-annoteringar (om en metod anses implementera en gränssnittsmetod eller återställa en basklassmetod)\nservice.decompile.decompiler-cfr-config.previewfeatures=Dekompilera förhandsgranskningsfunktioner om klassen kompilerades med 'javac --enable-preview'\nservice.decompile.decompiler-cfr-config.pullcodecase=Flytta kod till case-satser aggressivt\nservice.decompile.decompiler-cfr-config.recordtypes=Återskapa record-typer\nservice.decompile.decompiler-cfr-config.recover=Tillåt fler och mer aggressiva alternativ att ställas in om dekompileringen misslyckas\nservice.decompile.decompiler-cfr-config.recovertypeclash=Dela upp livslängder där analysen orsakade typkonflikt\nservice.decompile.decompiler-cfr-config.recovertypehints=Återställ typledtrådar för iteratorer från första genomgången\nservice.decompile.decompiler-cfr-config.reducecondscope=Minska omfånget av villkorssatser, vilket kan generera fler anonyma block\nservice.decompile.decompiler-cfr-config.relinkconst=Återlänka konstanter - om det finns en inlinad referens till ett fält, försök att de-inlinera.\nservice.decompile.decompiler-cfr-config.relinkconststring=Återlänka konstanta strings - om det finns en lokal referens till en string som matchar en static final, använd den static final.\nservice.decompile.decompiler-cfr-config.removebadgenerics=Dölj generiska typer där det uppenbarligen är fel och återgå till icke-generiska\nservice.decompile.decompiler-cfr-config.removeboilerplate=Ta bort boilerplate-funktioner - konstrukton boilerplate, lambda-deserialisering etc.\nservice.decompile.decompiler-cfr-config.removedeadconditionals=Ta bort kod som inte kan köras.\nservice.decompile.decompiler-cfr-config.removedeadmethods=Ta bort meningslösa metoder - standardkonstruktorer osv.\nservice.decompile.decompiler-cfr-config.removeinnerclasssynthetics=Ta bort (om möjligt) implicita yttre klassreferenser i inre klasser\nservice.decompile.decompiler-cfr-config.renamedupmembers=Byt namn på otydliga/dubbla fält.\nservice.decompile.decompiler-cfr-config.renameenumidents=Byt namn på ENUM-identifierare som inte matchar deras 'förväntade' strängnamn.\nservice.decompile.decompiler-cfr-config.renameillegalidents=Byt namn på identifierare som inte är giltiga Java-identifierare.\nservice.decompile.decompiler-cfr-config.renamesmallmembers=Byt namn på små medlemmar. Observera att detta KOMMER att bryta reflektionbaserad åtkomst, så det är inte automatiskt aktiverat.\nservice.decompile.decompiler-cfr-config.sealed=Dekompilera 'sealed'-konstruktioner\nservice.decompile.decompiler-cfr-config.showinferrable=Lägg till explicita typer i metoder om de inte kan härledas från argumenten\nservice.decompile.decompiler-cfr-config.showversion=Visa använd CFR-version i rubriken (praktiskt att stänga av vid regressionsprovning)\nservice.decompile.decompiler-cfr-config.skipbatchinnerclasses=När du bearbetar många filer, hoppa över inre klasser, eftersom de ändå kommer att bearbetas som en del av yttre klasser.\nservice.decompile.decompiler-cfr-config.staticinitreturn=Försök ta bort returnering från statisk initiering\nservice.decompile.decompiler-cfr-config.stringbuffer=Konvertera new StringBuffer().append.append.append till string + string + string\nservice.decompile.decompiler-cfr-config.stringbuilder=Konvertera new StringBuilder().append.append.append till string + string + string\nservice.decompile.decompiler-cfr-config.stringconcat=Konvertera användningar av StringConcatFactor till string + string + string\nservice.decompile.decompiler-cfr-config.sugarasserts=Re-sugar assert-anrop\nservice.decompile.decompiler-cfr-config.sugarboxing=I den mån det är möjligt, ta bort meningslösa boxningsomslag\nservice.decompile.decompiler-cfr-config.sugarenums=Re-sugar enums\nservice.decompile.decompiler-cfr-config.sugarretrolambda=I den mån det är möjligt, återskapa användningar av retro lambda\nservice.decompile.decompiler-cfr-config.switchexpression=Re-sugar switch-uttryck\nservice.decompile.decompiler-cfr-config.tidymonitors=Ta bort supportkod för monitorer - t.ex. catch-block som bara används för att avsluta en monitor\nservice.decompile.decompiler-cfr-config.tryresources=Återskapa try-with-resources\nservice.decompile.decompiler-cfr-config.usenametable=Använd lokal variabelnamnstabell om den finns\nservice.decompile.decompiler-cfr-config.usesignatures=Använd signaturer utöver beskrivare (när de inte är uppenbart felaktiga)\nservice.decompile.decompiler-cfr-config.version=Visa aktuell CFR-version\nservice.decompile.decompiler-procyon-config=Procyon\nservice.decompile.decompiler-procyon-config.alwaysGenerateExceptionVariableForCatchBlocks=Generera alltid exceptionsvariabler för catch-block\nservice.decompile.decompiler-procyon-config.arePreviewFeaturesEnabled=Aktivera förhandsgranskningsfunktioner för språket\nservice.decompile.decompiler-procyon-config.bytecodeOutputOptions=Bytecode-utdataalternativ\nservice.decompile.decompiler-procyon-config.disableForEachTransforms=Inaktivera forEach(...) omvandlingar\nservice.decompile.decompiler-procyon-config.excludeNestedTypes=Exkludera nästlade typer\nservice.decompile.decompiler-procyon-config.flattenSwitchBlocks=Sammanfoga switch-block\nservice.decompile.decompiler-procyon-config.forceExplicitImports=Tvinga fram explicita importeringar\nservice.decompile.decompiler-procyon-config.forceExplicitTypeArguments=Tvinga fram explicita typargument\nservice.decompile.decompiler-procyon-config.forceFullyQualifiedReferences=Tvinga fram fullständigt kvalificerade referenser\nservice.decompile.decompiler-procyon-config.forcedCompilerTarget=Målspråkversion\nservice.decompile.decompiler-procyon-config.includeErrorDiagnostics=Inkludera fel diagnostik\nservice.decompile.decompiler-procyon-config.includeLineNumbersInBytecode=Visa debuggningsradnummer (bytecode)\nservice.decompile.decompiler-procyon-config.isUnicodeOutputEnabled=Aktivera unicode i utdata\nservice.decompile.decompiler-procyon-config.mergeVariables=Slå samman variabler när det är möjligt\nservice.decompile.decompiler-procyon-config.retainPointlessSwitches=Behåll meningslösa switch-satser\nservice.decompile.decompiler-procyon-config.retainRedundantCasts=Behåll onödiga typomvandlingar\nservice.decompile.decompiler-procyon-config.showDebugLineNumbers=Visa debuggningsradnummer\nservice.decompile.decompiler-procyon-config.showSyntheticMembers=Visa syntetiska medlemmar\nservice.decompile.decompiler-procyon-config.simplifyMemberReferences=Förenkla medlemsreferenser\nservice.decompile.decompiler-procyon-config.textBlockLineMinimum=Textblock minimilinjer\nservice.decompile.decompilers-config=Dekompileringshanterare\nservice.decompile.decompilers-config.pref-android-decompiler=Föredragen Android-dekompilerare\nservice.decompile.decompilers-config.pref-jvm-decompiler=Föredragen Java-dekompilerare\nservice.io=IO\nservice.io.directories-config=Kataloger\nservice.io.export-config=Export\nservice.io.export-config=Inkludera stödresurser i utdata\nservice.io.export-config.compression=Komprimeringsstrategi för innehållet i utdata\nservice.io.export-config.create-zip-dir-entries=Skapa ZIP 'katalog'-poster i utdata\nservice.io.recent-workspaces-config=Senaste arbetsmiljöer\nservice.io.recent-workspaces-config.last-workspace-export-path=Senaste arbetsmiljöns exportväg\nservice.io.recent-workspaces-config.last-workspace-open-path=Senaste arbetsmiljöns öppningsväg\nservice.io.recent-workspaces-config.max-recent-workspaces=Maximalt antal sparade senaste sökvägar\nservice.io.recent-workspaces-config.recent-workspaces=Senaste arbetsmiljö\nservice.io.resource-importer-config=Import av resurser\nservice.io.resource-importer-config.zip-strategy=ZIP-analysstrategi\nservice.mapping=Mapping\nservice.mapping.mapping-aggregator-config=Mappingaggregering\nservice.mapping.mapping-formats-config=Mappingformat\nservice.mapping.mapping-generator-config=Mappinggenerator\nservice.plugin=Plugin\nservice.plugin.plugin-manager-config=Plugin hanterare\nservice.plugin.script-manager-config=Skript hanterare\nservice.plugin.script-manager-config.file-watching=Aktivt skanna skriptkatalogen för ändringar\nservice.ui=Användargränssnitt\nservice.ui.bind-config=Bindningar\nservice.ui.bind-config.bundle=Inkluderad bindnings-map\nservice.ui.class-editing-config=Klassredigering\nservice.ui.class-editing-config.default-android-editor=Standardredigerare för Android-klasser\nservice.ui.class-editing-config.default-jvm-editor=Standardredigerare för JVM-klasser\nservice.ui.decompile-pane-config=Dekompileringspanel\nservice.ui.decompile-pane-config.timeout-seconds=Timeout för dekompilerare (sekunder)\nservice.ui.decompile-pane-config.mapping-acceleration=Accelerera om-mappningsoperationer\nservice.ui.text-format-config=Textformat\nservice.ui.text-format-config.escape=Aktivera text-escapes\nservice.ui.text-format-config.max-length=Maximal textvisningslängd\nservice.ui.text-format-config.shorten=Aktivera textförkortning\nservice.ui.file-type-association-config=Filtypassociationer\nservice.ui.file-type-association-config.extensions-to-langs=Filtillägg till språk-mappning\nservice.ui.window-manager-config=Fönsterhanterare\nservice.ui.workspace-explorer-config=Arbetsutrymmesutforskare\nservice.ui.workspace-explorer-config.drag-drop-action=Dragg- och släpp-beteende\n\n##### Config\n# conf.display=Appearance\n# conf.display.general=General\n# conf.display.general.flashopentabs=Flash open tab on re-focus\n# conf.display.general.language=Language\n# conf.display.tree=Tree display settings\n# conf.display.tree.maxtreedirectorydepth=Max tree directory depth\n# conf.display.tree.maxtreetextlength=Max item text length\n# conf.display.workspace=Workspace behavior\n# conf.display.workspace.showfilterbuttons=Show advanced filter buttons\n# conf.display.workspace.onfiledrop=On file drop action\n# conf.display.workspace.onfiledrop.choose=Let me choose\n# conf.display.workspace.onfiledrop.createnew=Create new workspace\n# conf.display.workspace.onfiledrop.addlibrary=Add as library\n# conf.display.workspace.promptcloseworkspace=Prompt when closing workspace\n# conf.display.workspace.promptdeleteitem=Prompt when deleting items\n# conf.display.workspace.showselectionnavbar=Show selection nav-bar\n# conf.display.text=Text\n# conf.display.text.fontsize=Font size\n#\n# conf.editor=Editor Components\n# conf.editor.general=General\n# conf.editor.general.classmode=Default class view\n# conf.editor.general.filemode=Default file view\n# conf.editor.general.errorindicatorpos=Error indicator position\n# conf.editor.assoc=File extension associations\n# conf.editor.assoc.fileextassociations=Associations\n# conf.editor.text=Text editor\n# conf.editor.text.showbracketfolds=Show bracket folds\n# conf.editor.hex=Hex editor\n# conf.editor.hex.classhints=Show class file hints\n# conf.editor.hex.hexcolumns=Number of columns\n# conf.editor.hex.highlightcurrent=Highlight current selection\n# conf.editor.diff=Difference Editor\n# conf.editor.diff.diff-view-mode=Class difference view mode\n#\n# conf.binding=Keybinding\n# conf.binding.navigation=Navigation\n# conf.binding.navigation.closetab=Close tab\n# conf.binding.navigation.find=Find\n# conf.binding.navigation.fullscreen=Fullscreen\n# conf.binding.navigation.quicknav=Quick navigation\n# conf.binding.edit=Editing\n# conf.binding.edit.save=Save changes\n# conf.binding.edit.undo=Undo change\n# conf.binding.inputprompt.initial=<waiting>\n# conf.binding.inputprompt.finish=<ENTER to finish>\n# conf.binding.code=Code interactions\n# conf.binding.code.gotodef=Goto definition\n# conf.binding.code.rename=Rename selected\n# conf.binding.code.searchref=Search references\n# conf.binding.code.suggest=Show suggestions\n# conf.binding.appearance=Appearance\n# conf.binding.appearance.fontsize.up=Increase font size\n# conf.binding.appearance.fontsize.down=Decrease font size\n#\n# conf.assembler=Assembler\n# conf.assembler.build=Building\n# conf.assembler.build.delay=Delay before updating bytecode\n# conf.assembler.format=Format\n# conf.assembler.format.prefix=Use dot prefix for keywords\n# conf.assembler.parse=Parsing\n# conf.assembler.parse.recover=Attempt error recovery\n# conf.assembler.validation=Validation\n# conf.assembler.validation.ast=Enable AST validation\n# conf.assembler.validation.bytecode=Enable bytecode analysis\n# conf.assembler.debug=Debugging\n# conf.assembler.debug.ast-debug=Show AST debug info\n#\n# conf.compiler=Compiler\n# conf.compiler.general=General\n# conf.compiler.general.impl=Compiler implementation\n# conf.compiler.general.phantoms=Generate missing classes\n# conf.compiler.debug=Debug\n# conf.compiler.debug.vars=Include debug symbols\n# conf.compiler.debug.lines=Include line numbers\n# conf.compiler.debug.sourcefile=Include source file\n#\n# conf.decompiler.filter=Filtering\n# conf.decompiler.filter.escapeunicode=Escape unicode\n# conf.decompiler.filter.generics=Strip type arguments\n# conf.decompiler.filter.vars=Strip variable info\n# conf.decompiler.filter.synthetics=Strip synthetic flags\n# conf.decompiler.classfile=Classfile\n# conf.decompiler.classfile.maxouterdepth=Max outer class breadcrumbs depth\\n(-1: disable check, 0: disable breadcrumbs)\n#\n# conf.plugin=Plugins\n# conf.plugin.general=General\n# conf.plugin.general.enabled=Enabled\n# conf.plugin.general.remote=Remote\n# conf.plugin.general.remotetime=Cache timestamp\n#\n# conf.ssvm=SSVM\n# conf.ssvm.access=File IO access\n# conf.ssvm.access.read=Allow read\n# conf.ssvm.access.write=Allow write\n# conf.ssvm.access.read.warn=Enabling this will allow SSVM actions to read files on your system.\\nBe careful when enabling this option.\n# conf.ssvm.access.write.warn=Enabling this will allow SSVM actions to write files to your system.\\nBe VERY careful when enabling this option.\n# conf.ssvm.general=General\n# conf.ssvm.general.active=Initialize SSVM when loading workspaces\n# conf.ssvm.remote=Remote VM\n# conf.ssvm.remote.active=Active\n# conf.ssvm.remote.path=Path to VM executable\n# conf.undefined=Undefined\n\nmisc.acknowledge=Bekräfta\nmisc.all=Alla\nmisc.none=Ingen\nmisc.enabled=Aktiverad\nmisc.disabled=Inaktiverad\nmisc.clear=Rensa\nmisc.export=Exportera\nmisc.casesensitive=Skiftlägeskänslig\nmisc.regex=Regex\nmisc.member.field=Fält\nmisc.member.method=Metod\nmisc.member.field-n-method=Fält och metod\nmisc.member.inner-class=Inre klass\nmisc.member.inner-interface=Inre gränssnitt\nmisc.member.inner-enum=Inre uppräkning\nmisc.member.inner-annotation=Inre annotering\nmisc.accessflag.visibility.public=Publik\nmisc.accessflag.visibility.protected=Skyddad\nmisc.accessflag.visibility.private=Privat\nmisc.accessflag.visibility.package=Paket\nmisc.direction.up=Uppåt\nmisc.direction.down=Nedåt\nmisc.direction.left=Vänster\nmisc.direction.right=Höger\nmisc.direction.top=Topp\nmisc.direction.bottom=Botten\nmisc.position.top=Topp\nmisc.position.bottom=Botten\nmisc.position.left=Vänster\nmisc.position.right=Höger\nmisc.position.center=Mitten\nmisc.position.middle=Mellan\nmisc.text.equals=Equals\nmisc.text.contains=Innehåller\nmisc.text.startswith=Börjar med\nmisc.text.endswith=Slutar med\nmisc.text.regex=Regex\n"
  },
  {
    "path": "recaf-ui/src/main/resources/translations/zh_CN.lang",
    "content": "## Language name\nlang.name=简体中文\n\n##### General items\n## Main and context menus\nmenu.analysis=分析\nmenu.analysis.summary=查看摘要\nmenu.analysis.deobfuscation=反混淆\nmenu.analysis.list-comments=查看注释\nmenu.analysis.comment=注释\nmenu.association.override=覆盖语言\nmenu.association.none=未配置关联\nmenu.config=配置\nmenu.config.edit=修改\nmenu.config.export=导出\nmenu.config.import=导入\nmenu.file=文件\nmenu.file.attach=远程附加\nmenu.file.addtoworkspace=添加到工作空间\nmenu.file.decompileall=反编译所有类\nmenu.file.decompileall.path=导出路径:\nmenu.file.openworkspace=打开工作空间\nmenu.file.openurl=从URL打开\nmenu.file.exportapp=导出应用\nmenu.file.exportworkspace=导出工作空间配置\nmenu.file.modifications=查看修改\nmenu.file.recent=最近\nmenu.file.close=关闭\nmenu.file.quit=退出\nmenu.goto.class=到类\nmenu.goto.field=到字段\nmenu.goto.method=到方法\nmenu.goto.instruction=到指令\nmenu.goto.file=到文件\nmenu.goto.label=到标签\nmenu.edit=修改\nmenu.edit.add.field=添加字段\nmenu.edit.add.method=添加方法\nmenu.edit.add.annotation=添加注解\nmenu.edit.override.method=重写方法\nmenu.edit.remove.field=移除字段\nmenu.edit.remove.method=移除方法\nmenu.edit.remove.annotation=移除注解\nmenu.edit.assemble.class=在汇编中修改类\nmenu.edit.assemble.field=在汇编中修改字段\nmenu.edit.assemble.method=在汇编中修改方法\nmenu.edit.remove=移除\nmenu.edit.copy=复制\nmenu.edit.delete=删除\nmenu.edit.noop=无操作\nmenu.edit.removevars=裁剪变量信息\nmenu.edit.changeversion=改变类版本\nmenu.edit.changeversion.up=升级\nmenu.edit.changeversion.down=降级\nmenu.edit.newclass=新建类\nmenu.export.class=导出类\nmenu.export.file=导出文件\nmenu.export.classes=导出类\nmenu.export.files=导出文件\nmenu.export.package=导出包\nmenu.export.directory=导出目录\nmenu.help=帮助\nmenu.help.discord=Discord\nmenu.help.docs=在线用户文档\nmenu.help.docsdev=在线开发者文档\nmenu.help.github=Github\nmenu.help.issues=问题跟踪器\nmenu.help.sysinfo=系统信息\nmenu.help.tutorial=教程\nmenu.refactor=重构\nmenu.refactor.move=移动\nmenu.refactor.rename=重命名\nmenu.search=搜索\nmenu.search.string=字符串\nmenu.search.number=数字\nmenu.search.class.member-declarations=成员声明\nmenu.search.class.member-references=成员引用\nmenu.search.class.type-references=类型引用\nmenu.search.class.instruction=指令反汇编\nmenu.search.method-overrides=方法覆盖\nmenu.search.method-references=方法引用\nmenu.search.field-references=字段引用\nmenu.search.noresults=无结果\nmenu.mappings=映射\nmenu.mappings.apply=应用\nmenu.mappings.apply-advanced=高级应用\nmenu.mappings.export=导出\nmenu.mappings.export.unsupported=%s (不支持)\nmenu.mappings.generate=生成\nmenu.mappings.view=当前映射\nmenu.scripting=脚本\nmenu.scripting.list=脚本\nmenu.scripting.none-found=未找到脚本\nmenu.scripting.manage=管理脚本\nmenu.scripting.new=新建脚本\nmenu.scripting.edit=编辑\nmenu.scripting.browse=浏览脚本\nmenu.scripting.save=保存脚本\nmenu.scripting.execute=执行\nmenu.scripting.editor=脚本编辑器\nmenu.scripting.author=作者\nmenu.scripting.version=版本\nmenu.view=视图\nmenu.view.hierarchy=类结构\nmenu.view.hierarchy.children=子类\nmenu.view.hierarchy.parents=父类\nmenu.view.methodcfg=控制流图\nmenu.view.methodcallgraph=调用图\nmenu.view.methodcallgraph.calls=调用\nmenu.view.methodcallgraph.callers=调用者\nmenu.view.methodcallgraph.focus=聚焦方法\nmenu.tab.close=关闭\nmenu.tab.closeothers=关闭其他\nmenu.tab.closeall=关闭所有\nmenu.tab.copypath=复制路径\nmenu.image.resetscale=重置缩放\nmenu.image.center=图片居中\nmenu.hex.copyas=复制为...\nmenu.mode=切换视图\nmenu.mode.class.auto=自动\nmenu.mode.class.decompile=反编译\nmenu.mode.class.low-level=低级别\nmenu.mode.file.auto=自动\nmenu.mode.file.text=文本\nmenu.mode.file.hex=十六进制\nmenu.mode.file.image=图像\nmenu.mode.file.audio=音频\nmenu.mode.file.video=视频\nmenu.mode.file.pe=Windows PE\nmenu.mode.file.elf=ELF\nmenu.mode.diff.decompile=反编译\nmenu.mode.diff.disassemble=反汇编\nmenu.vm=虚拟化\nmenu.vm.optimize=优化\nmenu.vm.run=运行\nmenu.plugin=插件\nmenu.plugin.manage=管理插件\nmenu.plugin.installed=已安装\nmenu.plugin.remote=远程\nmenu.plugin.browse=浏览插件\nmenu.plugin.enabled=启用\nmenu.plugin.uninstall=卸载\nmenu.plugin.uninstall.warning=你确定要删除该插件吗?\n\n##### Keybinds\nbind.inputprompt.initial=<等待中>\nbind.inputprompt.finish=<按回车键完成>\nbind.editor.find=查找\nbind.editor.goto=跳转\nbind.editor.rename=重命名\nbind.editor.replace=替换\nbind.editor.save=保存\nbind.editor.undo=撤销\nbind.editor.closetab=关闭当前标签\nbind.quicknav=快速导航\nbind.workspace.export=导出工作空间\n\n##### Dialog texts\ndialog.cancel=取消\ndialog.close=关闭\ndialog.confirm=确认\ndialog.finish=完成\ndialog.next=下一个\ndialog.previous=上一个\ndialog.dismiss=忽略\ndialog.configure=配置\ndialog.warning=警告\ndialog.restart=修改该配置需要你重启.\\n你确定要应用吗?\ndialog.unknownextension=未知的文件扩展名. 是否要配置语言关联?\n\n## Search\ndialog.search.type=类型名称\ndialog.search.member-owner=成员所有者类型\ndialog.search.member-name=成员名称\ndialog.search.member-descriptor=成员描述符\n\n## File chooser\ndialog.title.primary=主要资源\ndialog.title.supporting=辅助资源\ndialog.title.nochanges=导出时不做更改?\ndialog.file.primary=主要\ndialog.file.open=打开\ndialog.file.open.directory=目录\ndialog.file.open.file=文件\ndialog.file.export=导出\ndialog.file.save=保存\ndialog.file.nothing=无任何选中\ndialog.file.nochanges=即使未作更改,也要导出应用程序吗?\ndialog.filefilter.any=任意类型\ndialog.filefilter.mapping=映射\ndialog.filefilter.input=应用\ndialog.filefilter.workspace=工作空间\n\n## File drop items\ndialog.title.create-workspace=创建工作空间\ndialog.title.update-workspace=处理工作空间输入\ndialog.title.close-workspace=关闭工作空间?\ndialog.option.create-workspace=创建新的工作空间\ndialog.option.update-workspace=添加到工作空间\n\n## Select class/file\ndialog.title.select-class=选择类\ndialog.title.select-file=选择文件\n\n## Create class/file\ndialog.title.create-class=创建类\ndialog.header.create-class-error=类名称已存在.\\n请选择一个不同的名称.\n\n\n## Copy class/file\ndialog.title.copy-class=复制类\ndialog.title.copy-directory=复制目录\ndialog.title.copy-package=复制包\ndialog.title.copy-field=复制字段\ndialog.title.copy-file=复制文件\ndialog.title.copy-method=复制方法\ndialog.header.copy-class=为复制的类提供一个新名称.\ndialog.header.copy-directory=为复制的目录提供一个新名称.\ndialog.header.copy-package=为复制的包提供一个新名称.\ndialog.header.copy-field=为复制的字段提供一个新名称.\ndialog.header.copy-field-error=字段名称已存在.\\n请选择一个不同的名称.\ndialog.header.copy-file=为复制的文件提供一个新名称.\ndialog.header.copy-method=为复制的方法提供一个新名称.\ndialog.header.copy-method-error=方法名已存在.\\n请选择一个不同的名称.\n\n## Delete class/file\ndialog.title.delete-class=删除类\ndialog.title.delete-directory=删除目录\ndialog.title.delete-field=删除字段\ndialog.title.delete-file=删除文件\ndialog.title.delete-method=删除方法\ndialog.title.delete-package=删除包\ndialog.title.delete-resource=删除资源\ndialog.header.delete-class=你确定要删除: %s吗?\ndialog.header.delete-directory=你确定要删除: %s吗?\ndialog.header.delete-field=你确定要删除: %s吗?\ndialog.header.delete-file=你确定要删除: %s吗?\ndialog.header.delete-method=你确定要删除: %s吗?\ndialog.header.delete-package=你确定要删除: %s吗?\ndialog.header.delete-resource=你确定要删除: %s吗?\n\n## Rename class/file\ndialog.title.rename-class=重命名类\ndialog.title.rename-class-warning=警告\ndialog.title.rename-directory=重命名目录\ndialog.title.rename-field=重命名字段\ndialog.title.rename-file=重命名文件\ndialog.title.rename-file-warning=警告\ndialog.title.rename-method=重命名方法\ndialog.title.rename-package=重命名包\ndialog.header.rename-class=为类提供一个新名称.\ndialog.header.rename-class-error=类名称已存在.\\n请选择一个不同的名称.\ndialog.header.rename-package=为包提供一个新名称.\ndialog.header.rename-package-error=包名称已存在.\\n请选择一个不同的名称.\ndialog.header.rename-package-warning=包名称已存在.\\n这可能会覆盖某些类.\ndialog.header.rename-directory=为目录提供一个新名称.\ndialog.header.rename-directory-error=目录名称已存在.\\n请选择一个不同的名称.\ndialog.header.rename-directory-warning=目录名称已存在.\\n这可能会覆盖某些文件.\ndialog.header.rename-field=为字段提供一个新名称.\ndialog.header.rename-field-error=字段名称已存在.\\n请选择一个不同的名称.\ndialog.header.rename-file=为文件提供一个新名称.\ndialog.header.rename-file-error=文件名称已存在.\\n请选择一个不同的名称.\ndialog.header.rename-method=为方法提供一个新名称.\ndialog.header.rename-method-error=方法名称已存在.\\n请选择一个不同的名称.\n\n## Move class/file\ndialog.title.move-class=选择目标包\ndialog.title.move-directory=选择目标(父)目录\ndialog.title.move-file=选择目标目录\ndialog.title.move-package=选择目标(父)包\ndialog.header.move-class=将类移动到新包中.\ndialog.header.move-directory=将目录移动到新的父目录.\ndialog.header.move-file=将文件移动到新目录.\ndialog.header.move-package=将包移动到新的父包中.\n\n## Add members\ndialog.title.add-field=添加字段\ndialog.title.add-method=添加方法\ndialog.title.override-method=重写方法\ndialog.input.name=名称\ndialog.input.desc=描述符\ndialog.warn.illegal-name=非法名称\ndialog.warn.illegal-desc=非法描述符格式\ndialog.warn.field-conflict=字段名称已存在.\\n请选择一个不同的名称.\ndialog.warn.method-conflict=方法名称已存在.\\n请选择一个不同的名称.\n\n## VM actions\ndialog.title.vm-invoke-args=虚拟化方法调用\ndialog.title.vm-peephole-invoke-args=虚拟化窥孔优化\ndialog.vm.execute=执行\ndialog.vm.optimize=优化\ndialog.vm.create-dummy=使用虚拟对象\ndialog.vm.create-null=使用null\n\n## Hex dialogs\ndialog.hex.title.insertcount=插入\ndialog.hex.header.insertcount=要插入多少字节?\n\n# Base Converter dialog\ndialog.conv.title.literal=数字字面量\ndialog.conv.title.expression=数字表达式\n\n## Quick nav\ndialog.quicknav=快速导航\ndialog.quicknav.tab.classes=类\ndialog.quicknav.tab.members=成员\ndialog.quicknav.tab.files=文件\ndialog.quicknav.tab.text=文本\ndialog.quicknav.tab.commented=注释\n\n## Error dialog\ndialog.error.exportclass.title=导出类失败\ndialog.error.exportclass.header=写入目标时发生错误\ndialog.error.exportclass.content=错误为:\ndialog.error.exportfile.title=导出文件失败\ndialog.error.exportfile.header=写入目标时发生错误\ndialog.error.exportfile.content=错误为:\ndialog.error.exportworkspace.title=导出工作区失败\ndialog.error.exportworkspace.header=写入目标时发生错误\ndialog.error.exportworkspace.content=错误为:\ndialog.error.loadworkspace.title=加载工作区失败\ndialog.error.loadworkspace.header=从选定文件读取时发生错误\ndialog.error.loadworkspace.content=错误为:\ndialog.error.loadsupport.title=加载支持资源失败\ndialog.error.loadsupport.header=从选定文件读取时发生错误\ndialog.error.loadsupport.content=错误为:\ndialog.error.attach.title=附加到JVM失败\ndialog.error.attach.header=连接到远程JVM时发生错误\ndialog.error.attach.content=错误为:\n\n##### Panels\n## Welcome\nwelcome.title=欢迎\nwelcome.links=链接\nwelcome.links.home=Recaf:主页\nwelcome.links.docs-user=用户文档\nwelcome.links.docs-dev=开发者文档\nwelcome.links.github=Github:源码+缺陷/功能追踪\nwelcome.links.discord=Discord\nwelcome.links.jvms=Java 虚拟机规范\nwelcome.links.jvms.class=类文件格式\nwelcome.links.jvms.instructions=JVM指令集\nwelcome.dnd=你可以在这个界面拖拽文件打开.\nwelcome.tutorial=你还没完成教程.点击这里开始!\nwelcome.dayssince=距离构建的天数\nwelcome.norecent=没有最近工作区\n\n\n## Workspace\nworkspace.title=工作区\nworkspace.filter-prompt=过滤:类/文件名...\nworkspace.info=信息\nworkspace.info-progress=分析工作区内容...\n\n## Attach\nattach.unsupported=附加初始化失败\nattach.unsupported.detail=附加代理自解压失败.\nattach.no-vms=未找到可附加的JVM\nattach.no-vms.detail=当前没有可用于附加的Java虚拟机.\nattach.problem.disable-attach=如果您尝试连接使用-XX:+DisableAttachMechanism的Java进程,请参阅:\nattach.problem.java-21=如果您尝试连接Java 21+进程,请参阅:\nattach.connect=连接\nattach.tab.properties=属性\nattach.tab.classloading=类\nattach.tab.compilation=编译\nattach.tab.system=系统\nattach.tab.runtime=运行时\nattach.tab.thread=线程\n\n## Changes view\nmodifications.none=项目没有修改历史\nmodifications.title=修改\n\n## Java area\njava.decompiling=正在反编译类...\njava.unparsable=SourceSolver无法解释源代码,上下文操作仅在'字段和方法'侧边栏可用\njava.parse-state.error=解析器错误\njava.parse-state.error-details=由于解析失败,右键上下文操作不可用.\\n您可以暂时使用'字段和方法'侧边栏.\njava.parse-state.initial=解析进行中...\njava.parse-state.initial-details=在解析完成之前,右键上下文操作不可用.\\n您可以暂时使用'字段和方法'侧边栏.\njava.parse-state.new-progress=重新解析中...\njava.parse-state.new-progress-details=已进行更改,正在进行新的解析.\\n在构建新模型时将使用旧模型.\\n您也可以使用'字段和方法'侧边栏.\njava.parse-state.none=没有要解析的内容\njava.decompile-failure=类反编译失败.一些选项:\\n- 切换反编译器\\n- 在汇编器或其他视图中打开类\\n- 尝试反混淆类并再次尝试\njava.decompile-failure.brief=类反编译失败\njava.savewitherrors=看起来这是您第一次进行导致错误的更改.\\n这些通常是由于反编译代码在语义上不是正确的Java.\\n您必须在保存更改之前解决这些错误.\\n\\n一些建议:\\n - 在'配置'或右下角(i)中更改反编译器\\n - 将鼠标悬停在红色错误框上,或点击顶部的错误框查看错误内容\\n - 使用汇编器而不是重新编译来进行更改\njava.savewitherrors.title=关于重新编译错误\njava.decompiler=反编译器\njava.targetversion=编译目标版本\njava.targetversion.notice.down=降级会添加存根类来模拟缺失的API.\njava.targetversion.auto=匹配类文件版本\njava.targetdownsampleversion=降低目标版本\njava.targetdownsampleversion.disabled=已禁用\njava.targetdebug=使用调试信息编译\njava.info=类信息\njava.info.version=类版本\njava.info.sourcefile=源文件名\n\n## Search bar\nfind.replace=替换\nfind.replaceall=全部替换\nfind.regexinvalid=无效的正则表达式\nfind.regexreplace=替换文本\n\n## Fields and methods\nfieldsandmethods.title=字段和方法\nfieldsandmethods.empty=类没有成员\nfieldsandmethods.showoutlinedsynths=显示合成(编译器生成)成员\nfieldsandmethods.showoutlinedvisibility=按成员可见性过滤\nfieldsandmethods.showoutlinedmembertype=按成员类型过滤\nfieldsandmethods.nametypemode=名称/类型显示模式\nfieldsandmethods.sortalphabetically=按字典序排序\nfieldsandmethods.sortbyvisibility=按可见性排序\nfieldsandmethods.filter.prompt=过滤:字段/方法名...\n\n## Hierarchy\nhierarchy.title=继承\nhierarchy.children=子类\nhierarchy.parents=父类\n\n## Kotlin Metadata\nkotlinmetadata.title=@Metadata\nkotlinmetadata.orderwarning=重要提示:项目不会按照类文件中定义的相同顺序显示\n\n## Logging\nlogging.title=日志\n\n## Assembler\nassembler.problem.0=没有问题\nassembler.problem.1=1个问题\nassembler.problem.N=N个问题\nassembler.title=汇编器\nassembler.analysis.title=分析\nassembler.analysis.stack=栈\nassembler.analysis.variables=变量\nassembler.analysis.type=类型\nassembler.analysis.value=值\nassembler.playground.title=Java到字节码\nassembler.playground.comment=// 在这里编写一些Java代码,自动转换为字节码\\n// 您可以访问当前类的字段/方法,\\n// 以及当前方法的参数/变量.\nassembler.snippets.title=代码片段\nassembler.variables.title=已声明变量\nassembler.variables.name=变量名\nassembler.variables.type=类型\nassembler.variables.usage=使用次数\nassembler.variables.value=值\nassembler.variables.empty=<需要至少编译一次>\nassembler.variables.read-before-write=在写入前读取\nassembler.suggestions.none=没有建议\n\n## Comments\ncomments.search.prompt=搜索注释...\n\n## Search\nsearch.run=搜索\nsearch.results=结果\nsearch.text=文本内容\nsearch.textmode=文本匹配模式\nsearch.number=数值\nsearch.numbermode=数值匹配模式\nsearch.refowner=成员所有者\nsearch.refname=成员名称\nsearch.refdesc=成员类型描述符\n\n## Help\nhelp.system=系统\nhelp.system.sub=关于操作系统的信息\nhelp.java=Java\nhelp.java.sub=关于JVM的信息\nhelp.javafx=JavaFX\nhelp.javafx.sub=关于JavaFX界面的信息\nhelp.recaf=Recaf\nhelp.recaf.sub=关于Recaf的信息\nhelp.copy=复制信息到剪贴板\nhelp.opendir=打开Recaf目录\n\n## Deobfuscation\ndeobf=反混淆\ndeobf.selection.title=选择转换器\ndeobf.order.title=排序转换器\ndeobf.order.hint=在左侧选择转换器\\n通过拖动来改变它们的顺序\ndeobf.order.pre=缺少推荐的前置转换器\ndeobf.order.suc=缺少推荐的后置转换器\ndeobf.max-passes=最大转换次数\ndeobf.preview.title=预览\ndeobf.preview.pick=选择预览类\ndeobf.preview.toggle-mode=切换预览模式\ndeobf.preview.noselection=// 未选择类,请选择一个类来预览\\n// 转换前后的状态\ndeobf.tree.generic=通用\ndeobf.tree.generic.anticrasher=防崩溃器\ndeobf.tree.generic.optimize=优化\ndeobf.tree.generic.restoration=恢复\ndeobf.tree.specific=特定\ndeobf.tree.plugin-provided=插件\ndeobf.apply=转换工作区\n\n## Mapping application\nmapapply=应用映射\nmapapply.pick.file=选择映射文件\nmapapply.pick.dir=选择映射目录\nmapapply.settings.unique=假设唯一键,忽略继承\n\n## Mapping generator\nmapgen=映射生成器\nmapgen.genimpl=命名约定\nmapgen.filter.name=名称\nmapgen.filter.class-name=类名\nmapgen.filter.owner-name=所有者名称\nmapgen.filter.field-name=字段名\nmapgen.filter.method-name=方法名\nmapgen.filter.variable-name=变量名\nmapgen.filters=过滤器\nmapgen.filters.add=添加过滤器\nmapgen.filters.edit=编辑选中项\nmapgen.filters.delete=删除选中项\nmapgen.filters.type=过滤器类型\nmapgen.filter.modifiers.tooltip=修饰符以空格分隔\nmapgen.filter.excludealreadymapped=排除已映射的\nmapgen.filter.excludemodifier=排除修饰符\nmapgen.filter.excludeclasses=排除类\nmapgen.filter.excludename=排除名称\nmapgen.filter.excludeclass=排除类\nmapgen.filter.excludefield=排除字段\nmapgen.filter.excludemethod=排除方法\nmapgen.filter.includemodifier=包含修饰符\nmapgen.filter.includeclass=包含类\nmapgen.filter.includefield=包含字段\nmapgen.filter.includemethod=包含方法\nmapgen.filter.includevariable=包含变量\nmapgen.filter.includewhitespacenames=包含空白\nmapgen.filter.includenonasciinames=包含非ASCII字符\nmapgen.filter.includekeywords=包含关键字\nmapgen.filter.includenonjavaidentifiers=包含非Java标识符\nmapgen.filter.includelong=包含长名称\nmapgen.filter.includename=包含名称\nmapgen.filter.includeclasses=包含类\nmapgen.title.newfilter=新过滤器\nmapgen.header.newfilter=输入过滤器内容\nmapgen.preview.empty=生成的映射统计信息将显示在这里\\n\\n\\n\nmapgen.configure=配置\nmapgen.configure.nothing=没有可配置的内容\nmapgen.generate=生成\nmapgen.apply=应用\n\n## Mapping view\nmapprog=映射进度\nmapprog.metric.size=类文件大小\nmapprog.metric.membercount=类字段和方法\n\n##### Tree\ntree.classes=类\ntree.files=文件\ntree.defaultpackage=(默认包)\ntree.defaultdirectory=(根目录)\ntree.prompt=将文件拖到这里\ntree.hidelibs=隐藏库\ntree.phantoms=生成的幻类\ntree.embedded-resources=嵌入资源\n\n##### Services\nservice=所有服务\nservice.analysis=分析\nservice.analysis.comments-config=注释\nservice.analysis.comments-config.enable-display=在反编译中显示注释\nservice.analysis.comments-config.word-wrapping-limit=自动换行限制\nservice.analysis.info-summary-config=工作区摘要\nservice.analysis.info-summary-config.summarize-on-open=打开时汇总工作区内容\nservice.analysis.graph-calls-config=调用图\nservice.analysis.graph-inheritance-config=继承图\nservice.analysis.jphantom-generator-config=JPhantom\nservice.analysis.jphantom-generator-config.generate-workspace-phantoms=生成并添加幻类到工作区\nservice.analysis.search-config=搜索\nservice.analysis.entry-points=入口点\nservice.analysis.entry-points.none=未找到入口\nservice.analysis.hashing=散列\nservice.analysis.hashing.type=类型\nservice.analysis.hashing.value=哈希\nservice.analysis.anti-decompile=反反编译\nservice.analysis.anti-decompile.illegal-attr=非法属性\nservice.analysis.anti-decompile.illegal-name=非法名称\nservice.analysis.anti-decompile.label-patch=修补 %d 个受影响的类\nservice.analysis.signature-info=签名信息\nservice.assembler=汇编器\nservice.assembler.assembler-pipeline.general-config=通用\nservice.assembler.assembler-pipeline.general-config.disassembly-ast-parse-delay=AST解析延迟\nservice.assembler.assembler-pipeline.general-config.disassembly-indent=缩进\nservice.assembler.assembler-pipeline.general-config.disassembly-whole-floating=标准浮点表示法\nservice.assembler.dalvik-assembler-config=Dalvik\nservice.assembler.dalvik-assembler-config.value-analysis=启用值分析\nservice.assembler.dalvik-assembler-config.simulate-jvm-calls=模拟常见JVM调用\nservice.assembler.jvm-assembler-config=JVM\nservice.assembler.jvm-assembler-config.value-analysis=启用值分析\nservice.assembler.jvm-assembler-config.simulate-jvm-calls=模拟常见JVM调用\nservice.assembler.jvm-assembler-config.try-range-comments=生成try-catch范围注释\nservice.assembler.flow-lines-config=控制流线\nservice.assembler.flow-lines-config.connection-mode=线条模式\nservice.assembler.flow-lines-config.render-mode=渲染模式\nservice.compile=编译\nservice.compile.java-compiler-config=Javac\nservice.compile.java-compiler-config.generate-phantoms=生成缺失的类\nservice.compile.java-compiler-config.default-emit-debug=默认包含调试信息\nservice.compile.java-compiler-config.default-compile-target-version=默认类版本目标\nservice.compile.java-compiler-config.default-downsample-target-version=默认降低类版本目标\nservice.debug=附加/调试\nservice.debug.attach-config=附加配置\nservice.debug.attach-config.attach-jmx-bean-agent=附加JMX bean代理\nservice.debug.attach-config.passive-scanning=被动扫描状态\nservice.config-manager-config=配置管理器\nservice.decompile=反编译\nservice.decompile.decompilers-config=反编译管理器\nservice.decompile.decompilers-config.pref-android-decompiler=首选Android反编译器\nservice.decompile.decompilers-config.pref-jvm-decompiler=首选Java反编译器\nservice.decompile.decompilers-config.cache-decompilations=缓存反编译结果\nservice.decompile.decompilers-config.filter-annotations-duplicate=过滤重复注解\nservice.decompile.decompilers-config.filter-annotations-illegal=过滤非法注解\nservice.decompile.decompilers-config.filter-annotations-long=过滤长注解\nservice.decompile.decompilers-config.filter-annotations-long-limit=长注解限制\nservice.decompile.decompilers-config.filter-exceptions-long=过滤长异常\nservice.decompile.decompilers-config.filter-exceptions-long-limit=长异常限制\nservice.decompile.decompilers-config.filter-hollow=过滤类内容(空心)\nservice.decompile.decompilers-config.filter-illegal-signatures=过滤非法签名\nservice.decompile.decompilers-config.filter-synthetics=过滤合成标志\nservice.decompile.decompilers-config.filter-names-ascii=过滤非ASCII名称\nservice.decompile.decompilers-config.filter-strip-debug=过滤调试数据(变量,泛型)\nservice.decompile.impl=实现\nservice.decompile.impl.decompiler-cfr-config=CFR\nservice.decompile.impl.decompiler-cfr-config.aexagg=尝试更积极地扩展和合并异常\nservice.decompile.impl.decompiler-cfr-config.aexagg2=尝试更积极地扩展和合并异常(可能改变语义)\nservice.decompile.impl.decompiler-cfr-config.aggressivedocopy=将不可能的跳转中的代码克隆到带有\"first\"测试的循环中\nservice.decompile.impl.decompiler-cfr-config.aggressivedoextension=将不可能的跳转折叠到带有\"first\"测试的do循环中\nservice.decompile.impl.decompiler-cfr-config.aggressiveduff=折叠带有额外控制的duff设备样式开关\nservice.decompile.impl.decompiler-cfr-config.aggressivesizethreshold=触发积极减少的操作码计数\nservice.decompile.impl.decompiler-cfr-config.allowmalformedswitch=允许潜在的格式错误的switch语句\nservice.decompile.impl.decompiler-cfr-config.antiobf=撤销各种混淆\nservice.decompile.impl.decompiler-cfr-config.arrayiter=重新糖化基于数组的迭代\nservice.decompile.impl.decompiler-cfr-config.collectioniter=重新糖化基于集合的迭代\nservice.decompile.impl.decompiler-cfr-config.commentmonitors=用注释替换监视器 - 当完全混淆时有用\nservice.decompile.impl.decompiler-cfr-config.comments=输出描述反编译器状态、回退标志等的注释\nservice.decompile.impl.decompiler-cfr-config.constobf=撤销常量混淆\nservice.decompile.impl.decompiler-cfr-config.decodeenumswitch=重新糖化枚举上的switch\nservice.decompile.impl.decompiler-cfr-config.decodefinally=重新糖化finally语句\nservice.decompile.impl.decompiler-cfr-config.decodelambdas=重建lambda函数\nservice.decompile.impl.decompiler-cfr-config.decodestringswitch=重新糖化String上的switch\nservice.decompile.impl.decompiler-cfr-config.eclipse=启用转换以更好地处理Eclipse代码\nservice.decompile.impl.decompiler-cfr-config.elidescala=省略在scala输出中无用的内容(serialVersionUID, @ScalaSignature)\nservice.decompile.impl.decompiler-cfr-config.forbidanonymousclasses=不允许匿名类\nservice.decompile.impl.decompiler-cfr-config.forbidmethodscopedclasses=不允许方法作用域类\nservice.decompile.impl.decompiler-cfr-config.forceclassfilever=强制指定反编译的类文件版本(以及java版本)\nservice.decompile.impl.decompiler-cfr-config.forcecondpropagate=通过一些常量赋值回溯确定性跳转的结果\nservice.decompile.impl.decompiler-cfr-config.forceexceptionprune=如果不改变语义,则移除嵌套的异常处理程序\nservice.decompile.impl.decompiler-cfr-config.forcereturningifs=将返回移至跳转点\nservice.decompile.impl.decompiler-cfr-config.forcetopsort=强制基本块排序.通常只在存在混淆时有用\nservice.decompile.impl.decompiler-cfr-config.forcetopsortaggress=强制额外的积极拓扑排序选项\nservice.decompile.impl.decompiler-cfr-config.forcetopsortnopull=强制拓扑排序不拉取try块\nservice.decompile.impl.decompiler-cfr-config.forloopaggcapture=允许for循环积极地将变异滚动到更新部分,即使它们似乎与谓词无关\nservice.decompile.impl.decompiler-cfr-config.hidebridgemethods=隐藏桥接方法\nservice.decompile.impl.decompiler-cfr-config.hidelangimports=隐藏来自java.lang的导入\nservice.decompile.impl.decompiler-cfr-config.hidelongstrings=隐藏非常长的字符串 - 当混淆器在字符串中放置假代码时有用\nservice.decompile.impl.decompiler-cfr-config.hideutf=隐藏UTF8字符 - 引用它们而不是显示原始字符\nservice.decompile.impl.decompiler-cfr-config.ignoreexceptions=如果完全卡住,则丢弃异常信息(警告:改变语义,危险!)\nservice.decompile.impl.decompiler-cfr-config.ignoreexceptionsalways=丢弃异常信息(警告:改变语义,危险!)\nservice.decompile.impl.decompiler-cfr-config.innerclasses=反编译内部类\nservice.decompile.impl.decompiler-cfr-config.instanceofpattern=重新糖化instanceof模式匹配\nservice.decompile.impl.decompiler-cfr-config.j14classobj=反转java 1.4类对象构造\nservice.decompile.impl.decompiler-cfr-config.labelledblocks=允许生成使用标记块的代码(处理奇怪的前向跳转)\nservice.decompile.impl.decompiler-cfr-config.lenient=在通常会抛出异常的情况下更宽容一些\nservice.decompile.impl.decompiler-cfr-config.liftconstructorinit=将所有构造函数共有的初始化代码提升到成员初始化\nservice.decompile.impl.decompiler-cfr-config.obfattr=撤销属性混淆\nservice.decompile.impl.decompiler-cfr-config.obfcontrol=撤销控制流混淆\nservice.decompile.impl.decompiler-cfr-config.override=生成@Override注解(如果方法被视为实现接口方法或覆盖基类方法)\nservice.decompile.impl.decompiler-cfr-config.previewfeatures=如果类是用'javac --enable-preview'编译的,则反编译预览功能\nservice.decompile.impl.decompiler-cfr-config.pullcodecase=积极地将代码拉入case语句\nservice.decompile.impl.decompiler-cfr-config.recordtypes=重新糖化记录类型\nservice.decompile.impl.decompiler-cfr-config.recover=如果反编译失败,允许设置更多和更积极的选项\nservice.decompile.impl.decompiler-cfr-config.recovertypeclash=在分析导致类型冲突的地方拆分生命周期\nservice.decompile.impl.decompiler-cfr-config.recovertypehints=从第一遍恢复迭代器的类型提示\nservice.decompile.impl.decompiler-cfr-config.reducecondscope=减少条件的作用域,可能生成更多匿名块\nservice.decompile.impl.decompiler-cfr-config.relinkconst=重新链接常量 - 如果有对字段的内联引用,尝试取消内联\nservice.decompile.impl.decompiler-cfr-config.relinkconststring=重新链接常量字符串 - 如果有与静态final匹配的字符串的本地引用,使用静态final\nservice.decompile.impl.decompiler-cfr-config.removebadgenerics=隐藏明显错误的泛型,回退到非泛型\nservice.decompile.impl.decompiler-cfr-config.removeboilerplate=移除样板函数 - 构造函数样板,lambda反序列化等\nservice.decompile.impl.decompiler-cfr-config.removedeadconditionals=移除无法执行的代码\nservice.decompile.impl.decompiler-cfr-config.removedeadmethods=移除无意义的方法 - 默认构造函数等\nservice.decompile.impl.decompiler-cfr-config.removeinnerclasssynthetics=移除(在可能的情况下)内部类中隐式的外部类引用\nservice.decompile.impl.decompiler-cfr-config.renamedupmembers=重命名模糊/重复的字段\nservice.decompile.impl.decompiler-cfr-config.renameenumidents=重命名与其\"预期\"字符串名称不匹配的枚举标识符\nservice.decompile.impl.decompiler-cfr-config.renameillegalidents=重命名不是有效Java标识符的标识符\nservice.decompile.impl.decompiler-cfr-config.renamesmallmembers=重命名小成员.注意 - 这将破坏基于反射的访问,因此不会自动启用\nservice.decompile.impl.decompiler-cfr-config.sealed=反编译'sealed'构造\nservice.decompile.impl.decompiler-cfr-config.showinferrable=如果参数未暗示,则用显式类型装饰方法\nservice.decompile.impl.decompiler-cfr-config.showversion=在头部显示使用的CFR版本(在回归测试时关闭很方便)\nservice.decompile.impl.decompiler-cfr-config.skipbatchinnerclasses=处理多个文件时,跳过内部类,因为它们无论如何都会作为外部类的一部分处理\nservice.decompile.impl.decompiler-cfr-config.staticinitreturn=尝试从静态初始化中移除return\nservice.decompile.impl.decompiler-cfr-config.stringbuffer=将new StringBuffer().append.append.append转换为string + string + string\nservice.decompile.impl.decompiler-cfr-config.stringbuilder=将new StringBuilder().append.append.append转换为string + string + string\nservice.decompile.impl.decompiler-cfr-config.stringconcat=将StringConcatFactor的用法转换为string + string + string\nservice.decompile.impl.decompiler-cfr-config.sugarasserts=重新糖化assert调用\nservice.decompile.impl.decompiler-cfr-config.sugarboxing=在可能的情况下,移除无意义的装箱包装器\nservice.decompile.impl.decompiler-cfr-config.sugarenums=重新糖化枚举\nservice.decompile.impl.decompiler-cfr-config.sugarretrolambda=在可能的情况下,重新糖化retro lambda的使用\nservice.decompile.impl.decompiler-cfr-config.switchexpression=重新糖化switch表达式\nservice.decompile.impl.decompiler-cfr-config.tidymonitors=移除监视器的支持代码 - 例如,仅用于退出监视器的catch块\nservice.decompile.impl.decompiler-cfr-config.tryresources=重构try-with-resources\nservice.decompile.impl.decompiler-cfr-config.usenametable=如果存在,使用本地变量名表\nservice.decompile.impl.decompiler-cfr-config.usesignatures=除了描述符外,还使用签名(当它们不明显不正确时)\nservice.decompile.impl.decompiler-cfr-config.version=显示当前CFR版本\nservice.decompile.impl.decompiler-procyon-config=Procyon\nservice.decompile.impl.decompiler-procyon-config.alwaysGenerateExceptionVariableForCatchBlocks=始终生成catch块\nservice.decompile.impl.decompiler-procyon-config.arePreviewFeaturesEnabled=启用语言预览功能\nservice.decompile.impl.decompiler-procyon-config.bytecodeOutputOptions=字节码输出选项\nservice.decompile.impl.decompiler-procyon-config.disableForEachTransforms=禁用forEach(...)转换\nservice.decompile.impl.decompiler-procyon-config.excludeNestedTypes=排除嵌套类型\nservice.decompile.impl.decompiler-procyon-config.flattenSwitchBlocks=扁平化switch块\nservice.decompile.impl.decompiler-procyon-config.forceExplicitImports=强制显式导入\nservice.decompile.impl.decompiler-procyon-config.forceExplicitTypeArguments=强制显式类型参数\nservice.decompile.impl.decompiler-procyon-config.forceFullyQualifiedReferences=强制完全限定引用\nservice.decompile.impl.decompiler-procyon-config.forcedCompilerTarget=目标语言版本\nservice.decompile.impl.decompiler-procyon-config.includeErrorDiagnostics=包含错误诊断\nservice.decompile.impl.decompiler-procyon-config.includeLineNumbersInBytecode=显示调试行号(字节码)\nservice.decompile.impl.decompiler-procyon-config.isUnicodeOutputEnabled=在输出中启用Unicode\nservice.decompile.impl.decompiler-procyon-config.languageTarget=语言目标\nservice.decompile.impl.decompiler-procyon-config.mergeVariables=在可能的情况下合并变量\nservice.decompile.impl.decompiler-procyon-config.retainPointlessSwitches=保留无用的switch语句\nservice.decompile.impl.decompiler-procyon-config.retainRedundantCasts=保留无用的类型转换\nservice.decompile.impl.decompiler-procyon-config.showDebugLineNumbers=显示调试行号\nservice.decompile.impl.decompiler-procyon-config.showSyntheticMembers=显示合成成员\nservice.decompile.impl.decompiler-procyon-config.simplifyMemberReferences=简化成员引用\nservice.decompile.impl.decompiler-procyon-config.textBlockLineMinimum=文本块最小行数\nservice.decompile.impl.decompiler-vineflower-config=Vineflower\nservice.decompile.impl.decompiler-vineflower-config.logging-level=日志级别\nservice.decompile.impl.decompiler-vineflower-config.remove-bridge=移除桥接方法\nservice.decompile.impl.decompiler-vineflower-config.remove-synthetic=移除合成方法和字段\nservice.decompile.impl.decompiler-vineflower-config.decompile-inner=反编译内部类\nservice.decompile.impl.decompiler-vineflower-config.decompile-java4=反编译Java 4类引用\nservice.decompile.impl.decompiler-vineflower-config.decompile-assert=反编译断言\nservice.decompile.impl.decompiler-vineflower-config.hide-empty-super=隐藏空的super()\nservice.decompile.impl.decompiler-vineflower-config.hide-default-constructor=隐藏默认构造函数\nservice.decompile.impl.decompiler-vineflower-config.decompile-generics=反编译泛型\nservice.decompile.impl.decompiler-vineflower-config.incorporate-returns=在try-catch块中合并返回语句\nservice.decompile.impl.decompiler-vineflower-config.ensure-synchronized-monitors=确保同步范围完整\nservice.decompile.impl.decompiler-vineflower-config.decompile-enums=反编译枚举\nservice.decompile.impl.decompiler-vineflower-config.decompile-preview=反编译预览功能\nservice.decompile.impl.decompiler-vineflower-config.remove-getclass=移除getClass()引用\nservice.decompile.impl.decompiler-vineflower-config.keep-literals=保持字面量原样\nservice.decompile.impl.decompiler-vineflower-config.boolean-as-int=将布尔值表示为0/1\nservice.decompile.impl.decompiler-vineflower-config.ascii-strings=ASCII字符串字符\nservice.decompile.impl.decompiler-vineflower-config.synthetic-not-set=合成标志未设置\nservice.decompile.impl.decompiler-vineflower-config.undefined-as-object=将未定义的参数类型视为Object\nservice.decompile.impl.decompiler-vineflower-config.use-lvt-names=使用局部变量表名称\nservice.decompile.impl.decompiler-vineflower-config.use-method-parameters=使用方法参数\nservice.decompile.impl.decompiler-vineflower-config.remove-empty-try-catch=移除空的try-catch块\nservice.decompile.impl.decompiler-vineflower-config.decompile-finally=反编译finally\nservice.decompile.impl.decompiler-vineflower-config.lambda-to-anonymous-class=将Lambda反编译为匿名类\nservice.decompile.impl.decompiler-vineflower-config.bytecode-source-mapping=字节码到源代码映射\nservice.decompile.impl.decompiler-vineflower-config.__dump_original_lines__=导出代码行\nservice.decompile.impl.decompiler-vineflower-config.ignore-invalid-bytecode=忽略无效字节码\nservice.decompile.impl.decompiler-vineflower-config.verify-anonymous-classes=验证匿名类\nservice.decompile.impl.decompiler-vineflower-config.ternary-constant-simplification=三元常量简化\nservice.decompile.impl.decompiler-vineflower-config.pattern-matching=模式匹配\nservice.decompile.impl.decompiler-vineflower-config.try-loop-fix=Try-Loop修复\nservice.decompile.impl.decompiler-vineflower-config.ternary-in-if=[实验性] If条件中的三元表达式\nservice.decompile.impl.decompiler-vineflower-config.decompile-switch-expressions=反编译Switch表达式\nservice.decompile.impl.decompiler-vineflower-config.show-hidden-statements=[调试] 显示隐藏语句\nservice.decompile.impl.decompiler-vineflower-config.override-annotation=Override注解\nservice.decompile.impl.decompiler-vineflower-config.simplify-stack=二次栈简化\nservice.decompile.impl.decompiler-vineflower-config.verify-merges=[实验性] 验证变量合并\nservice.decompile.impl.decompiler-vineflower-config.include-classpath=包含整个类路径\nservice.decompile.impl.decompiler-vineflower-config.include-runtime=包含Java运行时\nservice.decompile.impl.decompiler-vineflower-config.explicit-generics=显式泛型参数\nservice.decompile.impl.decompiler-vineflower-config.inline-simple-lambdas=内联简单Lambda\nservice.decompile.impl.decompiler-vineflower-config.log-level=日志级别\nservice.decompile.impl.decompiler-vineflower-config.max-time-per-method=[已弃用] 处理方法的最大时间\nservice.decompile.impl.decompiler-vineflower-config.rename-members=重命名成员\nservice.decompile.impl.decompiler-vineflower-config.user-renamer-class=用户重命名类\nservice.decompile.impl.decompiler-vineflower-config.new-line-separator=[已弃用] 换行符分隔符\nservice.decompile.impl.decompiler-vineflower-config.indent-string=缩进字符串\nservice.decompile.impl.decompiler-vineflower-config.preferred-line-length=首选行长度\nservice.decompile.impl.decompiler-vineflower-config.banner=横幅\nservice.decompile.impl.decompiler-vineflower-config.error-message=错误消息\nservice.decompile.impl.decompiler-vineflower-config.thread-count=线程数\nservice.decompile.impl.decompiler-vineflower-config.skip-extra-files=跳过额外文件\nservice.decompile.impl.decompiler-vineflower-config.warn-inconsistent-inner-attributes=警告不一致的内部属性\nservice.decompile.impl.decompiler-vineflower-config.dump-bytecode-on-error=错误时转储字节码\nservice.decompile.impl.decompiler-vineflower-config.dump-exception-on-error=错误时转储异常\nservice.decompile.impl.decompiler-vineflower-config.decompiler-comments=反编译器注释\nservice.decompile.impl.decompiler-vineflower-config.sourcefile-comments=源文件注释\nservice.decompile.impl.decompiler-vineflower-config.decompile-complex-constant-dynamic=反编译复杂的常量动态表达式\nservice.decompile.impl.decompiler-vineflower-config.force-jsr-inline=强制JSR内联\nservice.decompile.impl.decompiler-vineflower-config.dump-text-tokens=转储文本标记\nservice.decompile.impl.decompiler-vineflower-config.remove-imports=移除导入\nservice.decompile.impl.decompiler-vineflower-config.mark-corresponding-synthetics=标记对应的合成项\nservice.io=IO\nservice.io.directories-config=目录\nservice.io.export-config=导出\nservice.io.export-config.bundle-supporting-resources=将支持资源打包到输出中\nservice.io.export-config.compression=输出内容的压缩策略\nservice.io.export-config.create-zip-dir-entries=在输出中创建ZIP'目录'条目\nservice.io.export-config.warn-no-changes=在未做任何更改的情况下导出时发出警告\nservice.io.gson-provider-config=Json\nservice.io.gson-provider-config.pretty-print=美化打印\nservice.io.info-importer-config=内容导入\nservice.io.info-importer-config.class-patch-mode=类补丁模式\nservice.io.recent-workspaces-config=最近的工作区\nservice.io.recent-workspaces-config.last-workspace-export-path=最后的工作区导出路径\nservice.io.recent-workspaces-config.last-workspace-open-path=最后的工作区打开路径\nservice.io.recent-workspaces-config.max-recent-workspaces=最近路径的最大记录数\nservice.io.recent-workspaces-config.recent-workspaces=最近的工作区\nservice.io.recent-workspaces-config.last-class-export-path=最后的类导出路径\nservice.io.resource-importer-config=归档导入\nservice.io.resource-importer-config.zip-strategy=ZIP解析策略\nservice.io.resource-importer-config.allow-basic-base-offset-zero-check=使用JVM策略时默认检查0作为zip开始\nservice.io.resource-importer-config.skip-revisited-cen-to-local-links=使用JVM策略时跳过重复的CEN到LFH条目\nservice.io.resource-importer-config.ignore-file-lengths=使用Naive/Standard策略时忽略报告的文件长度\nservice.io.resource-importer-config.adapt-standard-cen-file-names=使用Standard策略时采用CEN文件名\nservice.io.resource-importer-config.max-embedded-zip-depth=嵌入式zip遍历的最大深度\nservice.io.resource-importer-config.parallelize=启用多核输入读取\nservice.mapping=映射\nservice.mapping.mapping-aggregator-config=映射聚合\nservice.mapping.mapping-formats-config=映射格式\nservice.mapping.mapping-generator-config=映射生成器\nservice.mapping.name-gen-provider=名称生成器\nservice.mapping.name-gen-provider.alphabet=字母表\nservice.mapping.name-gen-provider.alphabet.alphabet=字母表字符\nservice.mapping.name-gen-provider.alphabet.length=最小长度\nservice.plugin=插件\nservice.plugin.plugin-manager-config=插件管理器\nservice.plugin.plugin-manager-config.scan-on-start=启动时加载\nservice.plugin.script-manager-config=脚本管理器\nservice.plugin.script-manager-config.file-watching=被动扫描脚本目录的变化\nservice.transform=转换\nservice.transform.transformation-applier-config=转换应用\nservice.transform.transformation-applier-config.parallelize=启用多核转换应用\nservice.ui=用户界面\nservice.ui.bind-config=绑定\nservice.ui.bind-config.bundle=绑定映射包\nservice.ui.class-editing-config=类编辑\nservice.ui.class-editing-config.default-android-editor=Android类的默认编辑器\nservice.ui.class-editing-config.default-jvm-editor=JVM类的默认编辑器\nservice.ui.decompile-pane-config=反编译面板\nservice.ui.decompile-pane-config.timeout-seconds=反编译器超时(秒)\nservice.ui.decompile-pane-config.mapping-acceleration=加速重映射操作\nservice.ui.hex-config=十六进制编辑器\nservice.ui.hex-config.row-length=列数\nservice.ui.hex-config.row-split-interval=列分割间隔\nservice.ui.hex-config.show-address=显示地址\nservice.ui.hex-config.show-ascii=显示ASCII\nservice.ui.member-format-config=字段和方法格式\nservice.ui.member-format-config.name-type-display=名称和类型显示\nservice.ui.tab-completion-config=Tab补全\nservice.ui.text-format-config=文本格式\nservice.ui.tab-completion-config.enabled-in-assembler=在汇编器中启用\nservice.ui.tab-completion-config.max-completion-length=最大补全长度\nservice.ui.tab-completion-config.max-completion-rows=显示的补全行数\nservice.ui.tab-completion-config.popup-position=相对于光标的首选补全弹出位置\nservice.ui.text-format-config.escape=启用文本转义\nservice.ui.text-format-config.max-length=最大文本显示长度\nservice.ui.text-format-config.shorten=启用文本缩短\nservice.ui.file-type-syntax-association-config=文件类型关联\nservice.ui.file-type-syntax-association-config.extensions-to-langs=扩展名到语言映射\nservice.ui.snippets-config=代码片段\nservice.ui.window-manager-config=窗口管理器\nservice.ui.window-scale-config=窗口缩放\nservice.ui.window-scale-config.scale=缩放\nservice.ui.workspace-explorer-config=工作区浏览器\nservice.ui.workspace-explorer-config.drag-drop-action=拖放行为\nservice.ui.workspace-explorer-config.max-tree-dir-depth=最大树深度\nservice.ui.language-config=语言\nservice.ui.language-config.current=当前语言\n\n### Matcher translations\nnumber.match.equal=value == n\nnumber.match.not=value != n\nnumber.match.gt=value > n\nnumber.match.gte=value >= n\nnumber.match.lt=value < n\nnumber.match.lte=value <= n\nnumber.match.gt-lt=min < value < max\nnumber.match.gte-lt=min <= value < max\nnumber.match.gt-lte=min < value <= max\nnumber.match.gte-lte=min < value <= max\nnumber.match.any-of=numbers.contains(value)\nstring.match.anything=Anything\nstring.match.zilch=Nothing\nstring.match.contains=str.contains(value)\nstring.match.contains-ic=str.containsIgnoreCase(value)\nstring.match.ends=str.endsWith(value)\nstring.match.ends-ic=str.endsWithIgnoreCase(value)\nstring.match.equal=str.equals(value)\nstring.match.equal-ic=str.equalsIgnoreCase(value)\nstring.match.regex-full=str.matches(value)\nstring.match.regex-partial=str.matchesPartially(value)\nstring.match.starts=str.startsWith(value)\nstring.match.starts-ic=str.startsWithIgnoreCase(value)\n\n### Misc stuff\nmisc.acknowledge=确认\nmisc.all=全部\nmisc.none=无\nmisc.done=完成\nmisc.ignored=已忽略\nmisc.enabled=已启用\nmisc.disabled=已禁用\nmisc.download=下载\nmisc.download.invalid-url=无效URL\nmisc.before=之前\nmisc.after=之后\nmisc.load=加载\nmisc.clear=清除\nmisc.export=导出\nmisc.remove=移除\nmisc.removed=已移除\nmisc.casesensitive=区分大小写\nmisc.path=路径\nmisc.regex=正则表达式\nmisc.member.field=字段\nmisc.member.method=方法\nmisc.member.field-n-method=字段和方法\nmisc.member.inner-class=内部类\nmisc.member.inner-interface=内部接口\nmisc.member.inner-enum=内部枚举\nmisc.member.inner-annotation=内部注解\nmisc.accessflag.visibility.public=公共\nmisc.accessflag.visibility.protected=受保护\nmisc.accessflag.visibility.private=私有\nmisc.accessflag.visibility.package=包级\nmisc.direction.up=向上\nmisc.direction.down=向下\nmisc.direction.left=向左\nmisc.direction.right=向右\nmisc.direction.top=顶部\nmisc.direction.bottom=底部\nmisc.position.top=顶部\nmisc.position.bottom=底部\nmisc.position.left=左侧\nmisc.position.right=右侧\nmisc.position.center=中心\nmisc.position.middle=中间\n\n### Tutorial messages\nservice.ui.tutorial-config=教程\ntutorial.1.class=欢迎来到Recaf教程!\\n\\n请跟随当前类(及其他类)的注释了解Recaf的功能.\ntutorial.1.field=目前你看到的是反编译代码.虽然没有\"源代码\",但对于这样简单的类,反编译器应该能给你一个几乎完美的源代码样貌.\\n\\nRecaf内置了多个反编译器:\\n\\n - CFR（默认版本）\\n - Procyon\\n - Vineflower（FernFlower的一个分支,IntelliJ中捆绑的反编译器）\\n\\n你可以通过点击右下角的齿轮⚙按钮切换这些反编译器.\ntutorial.1.main=如果你看到的是纯Java代码(没有混淆,没有像KotlinGroovy等第三方语言),你可以编辑这些反编译代码,然后按[Control+S]对类做修改.\\n当你这样做时,Recaf不会自动替换本地机器上的文件,只有成功保存后,Recaf内部才会有更改.要持久保存你所做的任何更改,你需要进入\"文件\"菜单,选择\"导出应用程序\".\ntutorial.1.run=该方法将上述定义的\"字符串消息\"字段打印出来.\\n\\n尝试将分配给字段的字符串改为\"Hello World\"以外的内容,并保存你的更改.\\n\\n成功后你应该会看到该区域的边界闪烁绿色.一旦发生这种情况,下一章节就会自动打开.\ntutorial.2.class=该类被修改以隐藏一个秘密方法.如果编译器标记为自动生成的方法,某些方法可以被隐藏.这在使用lambda表达式的代码中很常见.此外,混淆器还可以标记为自动生成内容以隐藏内容.\ntutorial.2.field=这个类的内容不止这个字段.\\n\\n右上角有一个\"字段和方法\"标签.点击它查看该类的声明字段和方法.你应该能找到隐藏的方法.\\n试着双击它看看!\ntutorial.2.method=在\"字段和方法\"标签页旁边是\"继承\"标签.默认情况下,它会显示哪些类扩展或实现当前的类.面板底部有一个按钮,告诉你当前显示的内容类型.你可以点击它切换显示父类和子类.\\n这个面板会显示完整的继承树,如果你有\"A继承B,B继承C,C继承D\",你就能在面板中看到整个链条.\\n\\n试着在\"继承\"面板里找下一章.\\n右键点击它,选择\"到类\".\ntutorial.3.class=该类也被修改以隐藏一个秘密值.你可以右键点击该类的标签(上面,应该有蓝色下划线)然后\"切换视图\"为\"低级别\"来找到它.\\n这个低级视图显示了关于类文件的更多\"底层\"细节.它不会像Recaf的其他功能那样抽象化细节.四处寻找秘密密钥,找到后回来在下面的\"answer\"栏输入.\ntutorial.3.field=把秘密文本放在这里,然后通过[Control + S]保存.\\n\\n如果你的答案错了,这会覆盖该类的内容并清除密钥.哦不!\\n\\n\\n不过别担心,你可以通过[Control + U]恢复到旧版本的类和文件.这会将文件恢复到保存前的最后状态,意味着秘密会回到它最初的隐藏位置.\\n这和[Control + Z]不同,后者只是普通的文本撤销.\ntutorial.3.method=如果你需要提示,秘密不会被任何可执行代码引用.这样的常数该在哪里定义?\ntutorial.4.class=这次没有秘密.你只需要解决这个数学问题.\\n\\n传递给\"consume\"方法的最终值是什么?\ntutorial.4.field=把传递给\"consume\"的整数值放在这里.\ntutorial.4.method=这看起来手工作起来工作量很大.别担心,Recaf 让这变得非常简单!\\n\\n Recaf 的字节码汇编器里有一些工具可以帮助你.要打开汇编器,右键点击某个声明的类、字段或方法的名称,然后选择\"用汇编器编辑>编辑\".你也可以在右上角的\"字段和方法\"标签中右键点击条目.\\n\\n在这个例子中,右键点击该方法的名称（\"main\"）.\\n然后选择\"修改>在汇编中修改方法\".\\n\\n汇编器会显示该方法的指令以及其他相关元数据.汇编器底部有一些工具,可以通过点击它们打开.打开\"分析\"工具,然后点击方法中的不同代码行.你应该能很快推算出传递到\"consume\"的数值.\ntutorial.5.class=这个类是被混淆过的!如果你尝试用[Control + S]保存这个反编译的代码,你会看到编辑器闪红灯,错误会在左侧以叠加形式出现在行号上.\\n\\n当存在错误时无法保存.\\n\\n在混淆代码中,反编译代码不应使用[Control + S].\\n而是应该用汇编器在那里做修改.在汇编器中保存可以让你做Java源代码中通常不允许的更改.而且,这也确保你只改变了真正想改变的内容.当你保存反编译代码时,可能会无意中保存反编译器所做的更改,这些更改并未正确表示应用逻辑的工作方式.\ntutorial.5.main=在本章节中,你需要在混淆方法上打开汇编器.\\n\\n使用\"字段和方法\"标签,右键点击混淆方法,然后打开汇编器.\\n\\n混淆方法目前尚未正确实现.正确的实现可以在下一个方法的注释中找到.\\n\\n复制该注释中的代码.\\n\\n在混淆方法的汇编器中点击\"Java到字节码\".左侧是一个编辑器,负责接收Java源代码.右侧有一个编辑器,会显示等效的Java字节码.把你复制的代码粘贴到左侧(替换原有代码,去掉每行前的*').\\n\\n正确操作后,右侧会显示一个包含编译代码生成字节码的方法.你可以复制这个生成方法里的指令,粘贴到上面的汇编器里.需要说明的是,你只是复制了指令,而不是整个生成的方法.如果你正确复制了解密逻辑并将更改保存在汇编器中,下一章节将会被揭示.\ntutorial.5.decrypt=int key = Thread.currentThread().getStackTrace()[1].getMethodName().hashCode();\\nchar[] chars = text.toCharArray();\\nfor (int i = 0; i < chars.length; i++) chars[i] ^= key;\\nreturn String.valueOf(chars);\ntutorial.5.finished=右键点击该方法的返回类型\"Chapter6\",然后选择\"前往类\".\\n\\n你也可以在引用上使用[Control+点击],这样就不用每次都通过上下文菜单了.\ntutorial.6.class=当你右键点击某个类、字段或方法时,可以使用查看哪些代码引用你所选的内容.\\n\\n - 类：搜索 > 类型/成员引用\\n - 字段：字段引用\\n - 方法：方法引用\\n\\n你可以在界面顶部菜单栏的\"搜索\"菜单中找到这些搜索选项,还有字符串和数字文字等搜索功能.\ntutorial.6.method=试着搜索一下有哪些引用了这种方法.\ntutorial.7.class=恭喜,教程到此结束!\\n\\n\\n最后按[Control+S]标记教程已完成.\\n\\n教程后续会添加更多内容,但这应该涵盖了你普通使用场景中所有最重要的功能.与此同时,你可以自己探索其他功能!"
  },
  {
    "path": "recaf-ui/src/test/java/software/coley/recaf/services/script/ScriptManagerTest.java",
    "content": "package software.coley.recaf.services.script;\n\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport software.coley.observables.ObservableBoolean;\nimport software.coley.observables.ObservableCollection;\nimport software.coley.recaf.util.IOUtil;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n/**\n * Tests for {@link ScriptManager}\n */\nclass ScriptManagerTest {\n\tstatic ScriptManager scriptManager;\n\tstatic ObservableBoolean fileWatching;\n\tstatic Path scriptDir;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\t// Use our temporary dir for scripts\n\t\tscriptDir = Files.createTempDirectory(\"recaf-scripts\");\n\n\t\t// Manage file watching ourselves\n\t\tfileWatching = new ObservableBoolean(false);\n\n\t\tScriptManagerConfig config = mock(ScriptManagerConfig.class);\n\t\twhen(config.getScriptsDirectory()).thenReturn(scriptDir);\n\t\twhen(config.getFileWatching()).thenReturn(fileWatching);\n\t\tscriptManager = new ScriptManager(config);\n\t}\n\n\t@AfterAll\n\tstatic void cleanup() throws IOException {\n\t\tfileWatching.setValue(false);\n\t\tIOUtil.cleanDirectory(scriptDir);\n\t}\n\n\t@Test\n\tvoid testScriptDirectoryScanning() throws InterruptedException {\n\t\tObservableCollection<ScriptFile, List<ScriptFile>> scriptFiles = scriptManager.getScriptFiles();\n\n\t\t// Nothing by default\n\t\tassertEquals(0, scriptFiles.size());\n\n\t\t// Enable watching, wait a bit for thread to start\n\t\tfileWatching.setValue(true);\n\t\tThread.sleep(50);\n\n\t\t// Add a listener that will assert our desired script is loaded\n\t\tString script = \"\"\"\n\t\t\t\t// ==Metadata==\n\t\t\t\t// @name Hello world\n\t\t\t\t// @description Says hello to the world\n\t\t\t\t// @version 1.0.0\n\t\t\t\t// @author Author\n\t\t\t\t// ==/Metadata==\n\t\t\t\t    \n\t\t\t\tSystem.out.println(\"Hello world\");\n\t\t\t\t\"\"\";\n\t\tCompletableFuture<Void> future = new CompletableFuture<>();\n\t\tscriptFiles.addChangeListener((ob, old, cur) -> {\n\t\t\tassertEquals(1, scriptFiles.size(), \"Script file was not loaded\");\n\t\t\tScriptFile scriptFile = scriptFiles.iterator().next();\n\t\t\tassertEquals(\"Hello world\", scriptFile.name());\n\t\t\tassertEquals(\"Says hello to the world\", scriptFile.description());\n\t\t\tassertEquals(\"1.0.0\", scriptFile.version());\n\t\t\tassertEquals(\"Author\", scriptFile.author());\n\n\t\t\t// Done\n\t\t\tfuture.complete(null);\n\t\t});\n\n\t\t// Add the script to the directory.\n\t\t// Will complete when assertions are passed.\n\t\ttry {\n\t\t\tFiles.writeString(scriptDir.resolve(\"test.bsh\"), script);\n\t\t\tfuture.get(2, TimeUnit.SECONDS); // Really this is only set for this long because CI can be slow\n\t\t} catch (Exception ex) {\n\t\t\tfail(ex);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/test/java/software/coley/recaf/ui/BaseFxTest.java",
    "content": "package software.coley.recaf.ui;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport software.coley.recaf.util.TestEnvironment;\n\n/**\n * Ensures child classes are marked as test env via {@link TestEnvironment#isTestEnv()}.\n *\n * @author Matt Coley\n */\npublic class BaseFxTest {\n\t@BeforeAll\n\tstatic void setup() {\n\t\tTestEnvironment.initTestEnv();\n\t}\n}\n"
  },
  {
    "path": "recaf-ui/src/test/java/software/coley/recaf/ui/control/richtext/bracket/SelectedBracketTrackingTest.java",
    "content": "package software.coley.recaf.ui.control.richtext.bracket;\n\nimport org.fxmisc.richtext.CodeArea;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Answers;\nimport software.coley.recaf.ui.BaseFxTest;\nimport software.coley.recaf.ui.control.richtext.Editor;\nimport software.coley.recaf.util.IntRange;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n/**\n * Tests for {@link SelectedBracketTracking}.\n */\nclass SelectedBracketTrackingTest extends BaseFxTest {\n\t@Nested\n\tclass Simple {\n\t\t@Test\n\t\tvoid testImmediateAdjacent() {\n\t\t\tSelectedBracketTracking tracking = setup(\" {} \");\n\t\t\tIntRange target = new IntRange(1, 2);\n\n\t\t\t// Caret not adjacent to bracket\n\t\t\tassertNull(tracking.scanAround(0));\n\n\t\t\t// { to right\n\t\t\tassertEquals(target, tracking.scanAround(1));\n\n\t\t\t// { to right\n\t\t\tassertEquals(target, tracking.scanAround(2));\n\n\t\t\t// } to left\n\t\t\tassertEquals(target, tracking.scanAround(3));\n\n\t\t\t// Caret not adjacent to bracket\n\t\t\tassertNull(tracking.scanAround(4));\n\t\t}\n\n\t\t@Test\n\t\tvoid testWithContent() {\n\t\t\tSelectedBracketTracking tracking = setup(\" { ... } \");\n\t\t\tIntRange target = new IntRange(1, 7);\n\n\t\t\t// Caret not adjacent to bracket\n\t\t\tassertNull(tracking.scanAround(0));\n\n\t\t\t// { to right\n\t\t\tassertEquals(target, tracking.scanAround(1));\n\n\t\t\t// { to left\n\t\t\tassertEquals(target, tracking.scanAround(2));\n\n\t\t\t// Caret not adjacent to bracket\n\t\t\tassertNull(tracking.scanAround(3));\n\t\t\tassertNull(tracking.scanAround(4));\n\t\t\tassertNull(tracking.scanAround(5));\n\t\t\tassertNull(tracking.scanAround(6));\n\n\t\t\t// } to right\n\t\t\tassertEquals(target, tracking.scanAround(7));\n\n\t\t\t// } to left\n\t\t\tassertEquals(target, tracking.scanAround(8));\n\n\t\t\t// Caret not adjacent to bracket\n\t\t\tassertNull(tracking.scanAround(9));\n\t\t}\n\n\t\t@Test\n\t\tvoid testAtEdges() {\n\t\t\tSelectedBracketTracking tracking = setup(\"{...}\");\n\t\t\tIntRange target = new IntRange(0, 4);\n\n\t\t\t// { to right\n\t\t\tassertEquals(target, tracking.scanAround(0));\n\n\t\t\t// { to left\n\t\t\tassertEquals(target, tracking.scanAround(1));\n\n\t\t\t// Caret not adjacent to bracket\n\t\t\tassertNull(tracking.scanAround(2));\n\t\t\tassertNull(tracking.scanAround(3));\n\n\t\t\t// { to right\n\t\t\tassertEquals(target, tracking.scanAround(4));\n\n\t\t\t// } to left\n\t\t\tassertEquals(target, tracking.scanAround(5));\n\t\t}\n\t}\n\n\t@Nested\n\tclass Balance {\n\t\t@Test\n\t\tvoid testTwoLevels() {\n\t\t\tString text = \"\"\"\n\t\t\t\t\t{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\t\t\tSelectedBracketTracking tracking = setup(text);\n\t\t\tIntRange outer = new IntRange(0, 8);\n\t\t\tIntRange inner = new IntRange(3, 6);\n\n\t\t\t// First open bracket\n\t\t\tIntRange fromOuterStart0 = tracking.scanAround(0);\n\t\t\tassertEquals(outer, fromOuterStart0);\n\t\t\tIntRange fromOuterStart1 = tracking.scanAround(1);\n\t\t\tassertEquals(outer, fromOuterStart1);\n\n\t\t\t// No adjacent brackets\n\t\t\tassertNull(tracking.scanAround(2));\n\n\t\t\t// Last open bracket\n\t\t\tIntRange fromInnerStart3 = tracking.scanAround(3);\n\t\t\tassertEquals(inner, fromInnerStart3);\n\t\t\tIntRange fromInnerStart4 = tracking.scanAround(4);\n\t\t\tassertEquals(inner, fromInnerStart4);\n\n\t\t\t// No adjacent brackets\n\t\t\tassertNull(tracking.scanAround(5));\n\n\t\t\t// First close bracket\n\t\t\tIntRange fromInnerEnd6 = tracking.scanAround(6);\n\t\t\tassertEquals(inner, fromInnerEnd6);\n\t\t\tIntRange fromInnerEnd7 = tracking.scanAround(7);\n\t\t\tassertEquals(inner, fromInnerEnd7);\n\n\t\t\t// Last close bracket (there is no unmatched region here, as there is no '\\t' unlike before)\n\t\t\tIntRange fromOuterEnd8 = tracking.scanAround(8);\n\t\t\tassertEquals(outer, fromOuterEnd8);\n\t\t\tIntRange fromOuterEnd9 = tracking.scanAround(9);\n\t\t\tassertEquals(outer, fromOuterEnd9);\n\t\t}\n\t}\n\n\tprivate SelectedBracketTracking setup(String text) {\n\t\t// Setup UI mocks\n\t\tCodeArea codeArea = mock(CodeArea.class, Answers.RETURNS_MOCKS);\n\t\tEditor editor = mock(Editor.class, Answers.RETURNS_MOCKS);\n\t\twhen(editor.getCodeArea()).thenReturn(codeArea);\n\t\twhen(codeArea.getText()).thenReturn(text);\n\t\twhen(codeArea.getLength()).thenReturn(text.length());\n\n\t\t// Create bracket impl\n\t\tSelectedBracketTracking tracking = new SelectedBracketTracking();\n\t\ttracking.install(editor);\n\t\treturn tracking;\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/test/java/software/coley/recaf/ui/control/richtext/problem/ProblemTrackingTest.java",
    "content": "package software.coley.recaf.ui.control.richtext.problem;\n\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.ui.control.richtext.inheritance.InheritanceTracking;\n\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static software.coley.recaf.ui.control.richtext.problem.ProblemLevel.ERROR;\nimport static software.coley.recaf.ui.control.richtext.problem.ProblemLevel.WARN;\nimport static software.coley.recaf.ui.control.richtext.problem.ProblemPhase.*;\n\n/**\n * Tests for {@link ProblemTracking}.\n * <br>\n * Since the base class is shared with {@link InheritanceTracking} as well, its largely covering that too.\n */\nclass ProblemTrackingTest {\n\t@Test\n\tvoid removeByPhase() {\n\t\tProblemTracking tracking = new ProblemTracking();\n\t\ttracking.addItem(new Problem(1, 0, 0, ERROR, LINT, \"message-err-lint\"));\n\t\ttracking.addItem(new Problem(2, 0, 0, WARN, LINT, \"message-warn-lint\"));\n\t\ttracking.addItem(new Problem(3, 0, 0, ERROR, BUILD, \"message-err-build\"));\n\t\ttracking.addItem(new Problem(4, 0, 0, WARN, BUILD, \"message-warn-build\"));\n\t\ttracking.addItem(new Problem(5, 0, 0, ERROR, POST_PROCESS, \"message-err-proc\"));\n\t\ttracking.addItem(new Problem(6, 0, 0, WARN, POST_PROCESS, \"message-warn-proc\"));\n\n\t\t// Base truth\n\t\tassertEquals(6, tracking.getItems().size());\n\n\t\t// Remove 2 BUILD problems\n\t\tassertTrue(tracking.removeByPhase(BUILD));\n\t\tassertEquals(4, tracking.getItems().size());\n\n\t\t// Duplicate calls yield no change\n\t\tassertFalse(tracking.removeByPhase(BUILD));\n\t\tassertEquals(4, tracking.getItems().size());\n\t}\n\n\t@Test\n\tvoid removeByInstance() {\n\t\tProblemTracking tracking = new ProblemTracking();\n\t\tProblem problem = new Problem(0, 0, 0, ERROR, LINT, \"message\");\n\t\tProblem copy = new Problem(0, 0, 0, ERROR, LINT, \"message\");\n\t\ttracking.addItem(problem);\n\n\t\t// Base truth\n\t\tassertEquals(1, tracking.getItems().size());\n\n\t\t// Remove by instance must be THE instance, equality does not cut it\n\t\tassertFalse(tracking.removeByInstance(copy));\n\t\tassertTrue(tracking.removeByInstance(problem));\n\n\t\t// Should be empty now\n\t\tassertEquals(0, tracking.getItems().size());\n\t}\n\n\t@Test\n\tvoid removeByLine() {\n\t\tProblemTracking tracking = new ProblemTracking();\n\t\ttracking.addItem(new Problem(0, 0, 0, ERROR, LINT, \"message\"));\n\t\ttracking.addItem(new Problem(1, 0, 0, ERROR, LINT, \"message\"));\n\n\t\tassertEquals(2, tracking.getItems().size());\n\t\tassertTrue(tracking.removeByLine(1));\n\t\tassertEquals(1, tracking.getItems().size());\n\t}\n\n\t@Test\n\tvoid onLinesRemoved() {\n\t\tProblem problem0 = new Problem(0, 0, 0, ERROR, LINT, \"message\");\n\t\tProblem problem10 = new Problem(10, 0, 0, ERROR, LINT, \"message\");\n\t\tTestProblemTracking tracking = new TestProblemTracking();\n\t\ttracking.addItem(problem0);\n\t\ttracking.addItem(problem10);\n\n\t\t// Initial state\n\t\tassertSame(problem0, tracking.getFirstItemOnLine(0), \"Invalid initial state\");\n\t\tassertSame(problem10, tracking.getFirstItemOnLine(10), \"Invalid initial state\");\n\n\t\t// Remove line 3\n\t\t//  - End range is exclusive so this is just removing one line\n\t\ttracking.onLinesRemoved(3, 4);\n\n\t\t// Validate line0 problem not moved, problem10 moved to line 9\n\t\tassertEquals(2, tracking.getAllItems().size());\n\t\tassertSame(problem0, tracking.getFirstItemOnLine(0), \"Line 0 should not have moved\");\n\t\tassertNull(tracking.getFirstItemOnLine(10), \"Line 10 should have moved\");\n\t\tProblem problem9 = tracking.getFirstItemOnLine(9);\n\t\tassertNotEquals(problem10, problem9, \"Moved problem should be different reference + have different line number\");\n\t\tassertEquals(9, problem9.line(), \"Line 10 problem should have moved to line 9\");\n\t}\n\n\t@Test\n\tvoid onLinesInserted() {\n\t\tProblem problem0 = new Problem(0, 0, 0, ERROR, LINT, \"message\");\n\t\tProblem problem10 = new Problem(10, 0, 0, ERROR, LINT, \"message\");\n\t\tTestProblemTracking tracking = new TestProblemTracking();\n\t\ttracking.addItem(problem0);\n\t\ttracking.addItem(problem10);\n\n\t\t// Initial state\n\t\tassertSame(problem0, tracking.getFirstItemOnLine(0), \"Invalid initial state\");\n\t\tassertSame(problem10, tracking.getFirstItemOnLine(10), \"Invalid initial state\");\n\n\t\t// Insert new line at line 3\n\t\t//  - End range is inclusive\n\t\ttracking.onLinesInserted(3, 3);\n\n\t\t// Validate line0 problem not moved, problem10 moved to line 11\n\t\tassertEquals(2, tracking.getAllItems().size());\n\t\tassertSame(problem0, tracking.getFirstItemOnLine(0), \"Line 0 should not have moved\");\n\t\tassertNull(tracking.getFirstItemOnLine(10), \"Line 10 should have moved\");\n\t\tProblem problem11 = tracking.getFirstItemOnLine(11);\n\t\tassertNotEquals(problem10, problem11, \"Moved problem should be different reference + have different line number\");\n\t\tassertEquals(11, problem11.line(), \"Line 10 problem should have moved to line 11\");\n\t}\n\n\t@Test\n\tvoid multipleOnLine() {\n\t\tProblemTracking tracking = new ProblemTracking();\n\t\ttracking.addItem(new Problem(10, 1, 0, ERROR, LINT, \"foo\"));\n\t\ttracking.addItem(new Problem(10, 0, 0, ERROR, LINT, \"fizz\"));\n\t\ttracking.addItem(new Problem(10, 1, 0, ERROR, LINT, \"buzz\"));\n\n\t\t// 3 total\n\t\tassertEquals(3, tracking.getAllItems().size());\n\t\tassertEquals(3, tracking.getProblemsByLevel(ERROR).size());\n\t\tassertEquals(3, tracking.getProblemsByPhase(LINT).size());\n\n\t\t// 1 map entry since they all are on a single line\n\t\tassertEquals(1, tracking.getItems().size());\n\n\t\t// 3 on line 10\n\t\tList<Problem> problemsOnLine = tracking.getItemsOnLine(10);\n\t\tassertEquals(3, problemsOnLine.size());\n\t}\n\n\t/**\n\t * Exists to retain visibility of protected methods for testing.\n\t */\n\tprivate static class TestProblemTracking extends ProblemTracking {\n\t\tprotected void onLinesInserted(int startLine, int endLine) {\n\t\t\tsuper.onLinesInserted(startLine, endLine);\n\t\t}\n\n\t\tprotected void onLinesRemoved(int startLine, int endLine) {\n\t\t\tsuper.onLinesRemoved(startLine, endLine);\n\t\t}\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/test/java/software/coley/recaf/ui/control/richtext/syntax/RegexSyntaxHighlighterTest.java",
    "content": "package software.coley.recaf.ui.control.richtext.syntax;\n\nimport org.fxmisc.richtext.model.PlainTextChange;\nimport org.fxmisc.richtext.model.StyleSpans;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport software.coley.recaf.util.IntRange;\nimport software.coley.recaf.util.StringUtil;\n\nimport java.util.Collection;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static software.coley.recaf.ui.control.richtext.syntax.RegexLanguages.getJavaLanguage;\n\n/**\n * Tests for {@link RegexSyntaxHighlighter}.\n */\nclass RegexSyntaxHighlighterTest {\n\tprivate static final String JAVADOC_PATTERN = \"/\\\\*\\\\*[\\\\w\\\\W]+\\\\*/\";\n\tprivate static final RegexSyntaxHighlighter HIGHLIGHTER_JAVA = new RegexSyntaxHighlighter(getJavaLanguage());\n\tprivate static final String TEXT_JAVA = \"\"\"\n\t\t\t{\n\t\t\t/**\n\t\t\t* one\n\t\t\t* @param foo\n\t\t\t*/\n\t\t\tvoid one(String foo);\n\t\t\t\t\t\t\t\n\t\t\t/**\n\t\t\t* two\n\t\t\t* @param bar\n\t\t\t*/\n\t\t\tvoid two(String bar);\n\t\t\t\t\t\t\t\n\t\t\t/**\n\t\t\t* three\n\t\t\t* @return it does not\n\t\t\t*/\n\t\t\tvoid three();\n\t\t\t}\n\t\t\t\"\"\";\n\tprivate static final StyleSpans<Collection<String>> styleSpans =\n\t\t\tHIGHLIGHTER_JAVA.createStyleSpans(TEXT_JAVA, 0, TEXT_JAVA.length());\n\n\t@Test\n\tvoid testFlatten() {\n\t\tList<IntRange> flattened = SyntaxUtil.flatten(styleSpans);\n\t\tList<String> flattenedSubstrings = flattened.stream()\n\t\t\t\t.map(range -> range.sectionOfText(TEXT_JAVA))\n\t\t\t\t.toList();\n\n\t\t// Should be 13 ranges from flattened spans:\n\t\t// 01. {\n\t\t// 02. block comment\n\t\t// 03. whitespace\n\t\t// 04. void\n\t\t// 05. one(String foo);\n\t\t// 06. block comment\n\t\t// 07. whitespace\n\t\t// 08. void\n\t\t// 09. two(String bar);\n\t\t// 10. block comment\n\t\t// 11. whitespace\n\t\t// 12. void\n\t\t// 13. three(); }\n\t\tassertEquals(13, flattened.size());\n\t\tassertTrue(flattened.size() < styleSpans.getSpanCount(), \"Expected less # than base span count\");\n\t\tassertEquals(\"{\", flattenedSubstrings.get(0).trim());\n\t\tassertTrue(flattenedSubstrings.get(1).matches(JAVADOC_PATTERN));\n\t\tassertTrue(flattenedSubstrings.get(2).isBlank());\n\t\tassertEquals(\"void\", flattenedSubstrings.get(3).trim());\n\t\tassertEquals(\"one(String foo);\", flattenedSubstrings.get(4).trim());\n\t\tassertTrue(flattenedSubstrings.get(5).matches(JAVADOC_PATTERN));\n\t\tassertTrue(flattenedSubstrings.get(6).isBlank());\n\t\tassertEquals(\"void\", flattenedSubstrings.get(7).trim());\n\t\tassertEquals(\"two(String bar);\", flattenedSubstrings.get(8).trim());\n\t\tassertTrue(flattenedSubstrings.get(9).matches(JAVADOC_PATTERN));\n\t\tassertTrue(flattenedSubstrings.get(10).isBlank());\n\t\tassertEquals(\"void\", flattenedSubstrings.get(11).trim());\n\t\tassertEquals(\"three();\\n}\", flattenedSubstrings.get(12).trim());\n\t}\n\n\t/**\n\t * Change for inserting a space in a javadoc comment.\n\t * Should only refresh the range of the javadoc comment, nothing before or after.\n\t */\n\t@Nested\n\tclass Simple_RangeForRestyle {\n\t\t@Test\n\t\tvoid testBlock1() {\n\t\t\t// Change for inserting a space\n\t\t\tint blockComment1 = TEXT_JAVA.indexOf(\"* one\");\n\t\t\tint blockComment1Open = TEXT_JAVA.lastIndexOf(\"/**\", blockComment1);\n\t\t\tint blockComment1Close = TEXT_JAVA.indexOf(\"*/\", blockComment1) + 2;\n\t\t\tPlainTextChange change = new PlainTextChange(blockComment1 + 1, \"\", \" \");\n\t\t\tString modifiedText = apply(change);\n\n\t\t\t// The restyled range should just be the block comment.\n\t\t\t// The change does not break the match, so we do not need to expand.\n\t\t\tIntRange restyledRange = SyntaxUtil.getRangeForRestyle(modifiedText, styleSpans, HIGHLIGHTER_JAVA, change);\n\t\t\tString matchedText = restyledRange.sectionOfText(modifiedText);\n\t\t\tassertTrue(matchedText.matches(JAVADOC_PATTERN));\n\t\t\tassertEquals(blockComment1Open, restyledRange.start());\n\t\t\tassertEquals(blockComment1Close + change.getNetLength(), restyledRange.end());\n\t\t}\n\n\t\t@Test\n\t\tvoid testBlock2() {\n\t\t\t// Change for inserting a space\n\t\t\tint blockComment2 = TEXT_JAVA.indexOf(\"* two\");\n\t\t\tint blockComment2Open = TEXT_JAVA.lastIndexOf(\"/**\", blockComment2);\n\t\t\tint blockComment2Close = TEXT_JAVA.indexOf(\"*/\", blockComment2) + 2;\n\t\t\tPlainTextChange change = new PlainTextChange(blockComment2 + 1, \"\", \" \");\n\t\t\tString modifiedText = apply(change);\n\n\t\t\t// The restyled range should just be the block comment.\n\t\t\t// The change does not break the match, so we do not need to expand.\n\t\t\tIntRange restyledRange = SyntaxUtil.getRangeForRestyle(modifiedText, styleSpans, HIGHLIGHTER_JAVA, change);\n\t\t\tString matchedText = restyledRange.sectionOfText(modifiedText);\n\t\t\tassertTrue(matchedText.matches(JAVADOC_PATTERN));\n\t\t\tassertEquals(blockComment2Open, restyledRange.start());\n\t\t\tassertEquals(blockComment2Close + change.getNetLength(), restyledRange.end());\n\t\t}\n\n\t\t@Test\n\t\tvoid testBlock3() {\n\t\t\t// Change for inserting a space\n\t\t\tint blockComment3 = TEXT_JAVA.indexOf(\"* three\");\n\t\t\tint blockComment3Open = TEXT_JAVA.lastIndexOf(\"/**\", blockComment3);\n\t\t\tint blockComment3Close = TEXT_JAVA.indexOf(\"*/\", blockComment3) + 2;\n\t\t\tPlainTextChange change = new PlainTextChange(blockComment3 + 1, \"\", \" \");\n\t\t\tString modifiedText = apply(change);\n\n\t\t\t// The restyled range should just be the block comment.\n\t\t\t// The change does not break the match, so we do not need to expand.\n\t\t\tIntRange restyledRange = SyntaxUtil.getRangeForRestyle(modifiedText, styleSpans, HIGHLIGHTER_JAVA, change);\n\t\t\tString matchedText = restyledRange.sectionOfText(modifiedText);\n\t\t\tassertTrue(matchedText.matches(JAVADOC_PATTERN));\n\t\t\tassertEquals(blockComment3Open, restyledRange.start());\n\t\t\tassertEquals(blockComment3Close + change.getNetLength(), restyledRange.end());\n\t\t}\n\t}\n\n\t/**\n\t * Change for removing '/' from block comments.\n\t * Should refresh towards the open direction.\n\t */\n\t@Nested\n\tclass BreakOpenBlockComment_RangeForRestyle {\n\t\t@Test\n\t\tvoid testBlock1Start() {\n\t\t\t// Change for removing '/'\n\t\t\tint blockComment1 = TEXT_JAVA.indexOf(\"* one\");\n\t\t\tint blockComment1Open = TEXT_JAVA.lastIndexOf(\"/**\", blockComment1);\n\t\t\tint blockComment1Close = TEXT_JAVA.indexOf(\"*/\", blockComment1) + 2;\n\t\t\tPlainTextChange change = new PlainTextChange(blockComment1Open, \"/\", \"\");\n\t\t\tString modifiedText = apply(change);\n\n\t\t\t// The comment is broken, and doesn't flow into any opening '/**' prior.\n\t\t\t// Currently, this will expand backwards to the start since the change is on the edge of these\n\t\t\t// two flattened ranges (0th and 1st).\n\t\t\tIntRange restyledRange = SyntaxUtil.getRangeForRestyle(modifiedText, styleSpans, HIGHLIGHTER_JAVA, change);\n\t\t\tString matchedText = restyledRange.sectionOfText(modifiedText);\n\t\t\tassertFalse(matchedText.matches(JAVADOC_PATTERN));\n\t\t\tassertEquals(0, restyledRange.start());\n\t\t\tassertEquals(blockComment1Close, restyledRange.end());\n\t\t}\n\n\t\t@Test\n\t\tvoid testBlock1End() {\n\t\t\t// Change for removing '/'\n\t\t\tint blockComment1 = TEXT_JAVA.indexOf(\"* one\");\n\t\t\tint blockComment1Open = TEXT_JAVA.lastIndexOf(\"/**\", blockComment1);\n\t\t\tint blockComment1Close = TEXT_JAVA.indexOf(\"*/\", blockComment1) + 2;\n\t\t\tint blockComment2Close = TEXT_JAVA.indexOf(\"*/\", blockComment1Close + 1) + 2;\n\t\t\tPlainTextChange change = new PlainTextChange(blockComment1Close - 1, \"/\", \"\");\n\t\t\tString modifiedText = apply(change);\n\n\t\t\t// The restyled range should expand into block 2, since the end of block 1 was removed.\n\t\t\tIntRange restyledRange = SyntaxUtil.getRangeForRestyle(modifiedText, styleSpans, HIGHLIGHTER_JAVA, change);\n\t\t\tString matchedText = restyledRange.sectionOfText(modifiedText);\n\t\t\tassertTrue(matchedText.matches(JAVADOC_PATTERN));\n\t\t\tassertEquals(blockComment1Open, restyledRange.start());\n\t\t\tassertEquals(blockComment2Close + change.getNetLength(), restyledRange.end());\n\t\t}\n\n\t\t@Test\n\t\tvoid testBlock2Start() {\n\t\t\t// Change for removing '/'\n\t\t\tint blockComment2 = TEXT_JAVA.indexOf(\"* two\");\n\t\t\tint blockComment2Open = TEXT_JAVA.lastIndexOf(\"/**\", blockComment2);\n\t\t\tPlainTextChange change = new PlainTextChange(blockComment2Open, \"/\", \"\");\n\t\t\tString modifiedText = apply(change);\n\n\t\t\t// The restyled range should expand up to the first method declaration, since it is the prior adjacent flattened range\n\t\t\tIntRange restyledRange = SyntaxUtil.getRangeForRestyle(modifiedText, styleSpans, HIGHLIGHTER_JAVA, change);\n\t\t\tString matchedText = restyledRange.sectionOfText(modifiedText);\n\t\t\tassertFalse(matchedText.matches(JAVADOC_PATTERN));\n\t\t\tassertTrue(matchedText.startsWith(\"void one(\"));\n\t\t}\n\n\t\t@Test\n\t\tvoid testBlock2End() {\n\t\t\t// Change for removing '/'\n\t\t\tint blockComment2 = TEXT_JAVA.indexOf(\"* two\");\n\t\t\tint blockComment2Open = TEXT_JAVA.lastIndexOf(\"/**\", blockComment2);\n\t\t\tint blockComment2Close = TEXT_JAVA.indexOf(\"*/\", blockComment2) + 2;\n\t\t\tint blockComment3Close = TEXT_JAVA.indexOf(\"*/\", blockComment2Close + 1) + 2;\n\t\t\tPlainTextChange change = new PlainTextChange(blockComment2Close - 1, \"/\", \"\");\n\t\t\tString modifiedText = apply(change);\n\n\t\t\t// The restyled range should expand into block 3, since the end of block 2 was removed.\n\t\t\tIntRange restyledRange = SyntaxUtil.getRangeForRestyle(modifiedText, styleSpans, HIGHLIGHTER_JAVA, change);\n\t\t\tString matchedText = restyledRange.sectionOfText(modifiedText);\n\t\t\tassertTrue(matchedText.matches(JAVADOC_PATTERN));\n\t\t\tassertEquals(blockComment2Open, restyledRange.start());\n\t\t\tassertEquals(blockComment3Close + change.getNetLength(), restyledRange.end());\n\t\t}\n\n\t\t@Test\n\t\tvoid testBlock3Start() {\n\t\t\t// Change for removing '/'\n\t\t\tint blockComment3 = TEXT_JAVA.indexOf(\"* three\");\n\t\t\tint blockComment3Open = TEXT_JAVA.lastIndexOf(\"/**\", blockComment3);\n\t\t\tPlainTextChange change = new PlainTextChange(blockComment3Open, \"/\", \"\");\n\t\t\tString modifiedText = apply(change);\n\n\t\t\t// The restyled range should expand up to the second method declaration, since it is the prior adjacent flattened range\n\t\t\tIntRange restyledRange = SyntaxUtil.getRangeForRestyle(modifiedText, styleSpans, HIGHLIGHTER_JAVA, change);\n\t\t\tString matchedText = restyledRange.sectionOfText(modifiedText);\n\t\t\tassertFalse(matchedText.matches(JAVADOC_PATTERN));\n\t\t\tassertTrue(matchedText.startsWith(\"void two(\"));\n\t\t}\n\n\t\t@Test\n\t\tvoid testBlock3End() {\n\t\t\t// Change for removing '/'\n\t\t\tint blockComment3 = TEXT_JAVA.indexOf(\"* three\");\n\t\t\tint blockComment3Open = TEXT_JAVA.lastIndexOf(\"/**\", blockComment3);\n\t\t\tint blockComment3Close = TEXT_JAVA.indexOf(\"*/\", blockComment3) + 2;\n\t\t\tPlainTextChange change = new PlainTextChange(blockComment3Close - 1, \"/\", \"\");\n\t\t\tString modifiedText = apply(change);\n\n\t\t\t// The comment is broken, and doesn't flow into any closing '*/' later.\n\t\t\t// Thus, it should only need to restyle what was previously the block comment.\n\t\t\tIntRange restyledRange = SyntaxUtil.getRangeForRestyle(modifiedText, styleSpans, HIGHLIGHTER_JAVA, change);\n\t\t\tString matchedText = restyledRange.sectionOfText(modifiedText);\n\t\t\tassertFalse(matchedText.matches(JAVADOC_PATTERN)); // nothing to close\n\t\t\tassertEquals(blockComment3Open, restyledRange.start());\n\t\t\tassertEquals(blockComment3Close, restyledRange.end()); // Not adding + net like others since target is different\n\t\t}\n\t}\n\n\t/**\n\t * Change for inserting '/' to a broken block comment.\n\t * Should restyle the newly formed block comment range <i>(and some adjacent text, not ideal but not terrible)</i>.\n\t */\n\t@Nested\n\tclass CreateBlockComment_RangeForRestyle {\n\t\t@Test\n\t\tvoid testAtStart() {\n\t\t\tString textOpenMissing = \"\"\"\n\t\t\t\t\t{\n\t\t\t\t\t**\n\t\t\t\t\t* @return foo\n\t\t\t\t\t*/\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\n\t\t\t// Change for inserting '/' at start\n\t\t\tPlainTextChange change = new PlainTextChange(textOpenMissing.indexOf('*'), \"\", \"/\");\n\t\t\tString modifiedText = apply(textOpenMissing, change);\n\n\t\t\t// Initial spans\n\t\t\tStyleSpans<Collection<String>> spans = HIGHLIGHTER_JAVA.createStyleSpans(textOpenMissing, 0, textOpenMissing.length());\n\n\t\t\t// Expecting full match of block comment, plus prior unmatched text (range is adjacent)\n\t\t\tIntRange restyledRange = SyntaxUtil.getRangeForRestyle(modifiedText, spans, HIGHLIGHTER_JAVA, change);\n\t\t\tString matchedText = restyledRange.sectionOfText(modifiedText);\n\t\t\tassertTrue(matchedText.substring(2).matches(JAVADOC_PATTERN));\n\t\t\tassertEquals(0, restyledRange.start());\n\t\t\tassertEquals(modifiedText.lastIndexOf('/') + 1, restyledRange.end());\n\t\t}\n\n\t\t@Test\n\t\tvoid testAtEnd() {\n\t\t\tString textCloseMissing = \"\"\"\n\t\t\t\t\t{\n\t\t\t\t\t/**\n\t\t\t\t\t* @return foo\n\t\t\t\t\t*\n\t\t\t\t\t}\n\t\t\t\t\t\"\"\";\n\n\t\t\t// Change for inserting '/' at start\n\t\t\tPlainTextChange change = new PlainTextChange(textCloseMissing.lastIndexOf('*') + 1, \"\", \"/\");\n\t\t\tString modifiedText = apply(textCloseMissing, change);\n\n\t\t\t// Initial spans\n\t\t\tStyleSpans<Collection<String>> spans = HIGHLIGHTER_JAVA.createStyleSpans(textCloseMissing, 0, textCloseMissing.length());\n\n\t\t\t// Expecting full match of block comment, plus following unmatched text (range is adjacent)\n\t\t\tIntRange restyledRange = SyntaxUtil.getRangeForRestyle(modifiedText, spans, HIGHLIGHTER_JAVA, change);\n\t\t\tString matchedText = restyledRange.sectionOfText(modifiedText);\n\t\t\tassertTrue(matchedText.substring(0, matchedText.length() - 2).matches(JAVADOC_PATTERN));\n\t\t\tassertEquals(modifiedText.indexOf('/'), restyledRange.start());\n\t\t\tassertEquals(modifiedText.length() - 1, restyledRange.end());\n\t\t}\n\t}\n\n\tprivate static String apply(PlainTextChange change) {\n\t\treturn apply(TEXT_JAVA, change);\n\t}\n\n\tprivate static String apply(String text, PlainTextChange change) {\n\t\ttext = StringUtil.remove(text, change.getPosition(), change.getRemoved().length());\n\t\ttext = StringUtil.insert(text, change.getPosition(), change.getInserted());\n\t\treturn text;\n\t}\n}"
  },
  {
    "path": "recaf-ui/src/test/java/software/coley/recaf/ui/control/tree/WorkspaceTreeNodeTest.java",
    "content": "package software.coley.recaf.ui.control.tree;\n\nimport jakarta.annotation.Nonnull;\nimport javafx.collections.ObservableList;\nimport javafx.scene.control.TreeItem;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport software.coley.collections.Unchecked;\nimport software.coley.recaf.info.BasicFileInfo;\nimport software.coley.recaf.info.FileInfo;\nimport software.coley.recaf.info.JarFileInfo;\nimport software.coley.recaf.info.JvmClassInfo;\nimport software.coley.recaf.info.Named;\nimport software.coley.recaf.info.StubClassInfo;\nimport software.coley.recaf.info.StubFileInfo;\nimport software.coley.recaf.info.properties.BasicPropertyContainer;\nimport software.coley.recaf.path.BundlePathNode;\nimport software.coley.recaf.path.ClassPathNode;\nimport software.coley.recaf.path.DirectoryPathNode;\nimport software.coley.recaf.path.FilePathNode;\nimport software.coley.recaf.path.PathNode;\nimport software.coley.recaf.path.PathNodes;\nimport software.coley.recaf.path.ResourcePathNode;\nimport software.coley.recaf.path.WorkspacePathNode;\nimport software.coley.recaf.services.text.TextFormatConfig;\nimport software.coley.recaf.services.workspace.io.BasicClassPatcher;\nimport software.coley.recaf.services.workspace.io.BasicInfoImporter;\nimport software.coley.recaf.services.workspace.io.BasicResourceImporter;\nimport software.coley.recaf.services.workspace.io.InfoImporterConfig;\nimport software.coley.recaf.services.workspace.io.ResourceImporter;\nimport software.coley.recaf.services.workspace.io.ResourceImporterConfig;\nimport software.coley.recaf.test.TestClassUtils;\nimport software.coley.recaf.test.dummy.AccessibleFields;\nimport software.coley.recaf.test.dummy.HelloWorld;\nimport software.coley.recaf.test.dummy.StringConsumer;\nimport software.coley.recaf.test.dummy.VariedModifierFields;\nimport software.coley.recaf.ui.config.WorkspaceExplorerConfig;\nimport software.coley.recaf.util.ZipCreationUtils;\nimport software.coley.recaf.util.io.ByteSource;\nimport software.coley.recaf.util.io.ByteSources;\nimport software.coley.recaf.workspace.model.BasicWorkspace;\nimport software.coley.recaf.workspace.model.Workspace;\nimport software.coley.recaf.workspace.model.bundle.BasicFileBundle;\nimport software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.JvmClassBundle;\nimport software.coley.recaf.workspace.model.bundle.VersionedJvmClassBundle;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceFileResourceBuilder;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResource;\nimport software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static software.coley.recaf.test.TestClassUtils.*;\nimport static software.coley.recaf.ui.control.tree.WorkspaceTreeNode.getOrInsertIntoTree;\n\n/**\n * Tests for {@link WorkspaceTreeNode}.\n */\nclass WorkspaceTreeNodeTest {\n\tstatic ResourceImporter importer;\n\t// Workspace model for testing - Used by multiple tests, but some will make their own.\n\tstatic Workspace workspace;\n\tstatic WorkspaceResource primaryResource;\n\tstatic WorkspaceResource resourceWithEmbedded;\n\tstatic WorkspaceFileResource embeddedResource;\n\tstatic String embeddedResourcePath = \"embedded.jar\";\n\tstatic JvmClassBundle primaryJvmBundle;\n\tstatic JvmClassInfo classA;\n\tstatic JvmClassInfo classB;\n\tstatic JvmClassInfo classC;\n\tstatic JvmClassInfo classD;\n\t// Normal paths\n\tstatic ClassPathNode p1a;\n\tstatic ClassPathNode p1b;\n\tstatic ClassPathNode p1c;\n\tstatic ClassPathNode p1d;\n\tstatic DirectoryPathNode p2;\n\tstatic DirectoryPathNode p2b;\n\tstatic BundlePathNode p3c;\n\tstatic BundlePathNode p3f;\n\tstatic ResourcePathNode p4;\n\tstatic WorkspacePathNode p5;\n\t// Default package paths\n\tstatic FilePathNode default1;\n\tstatic DirectoryPathNode default2;\n\t// Obfuscated zero width paths\n\tstatic FilePathNode z1;\n\tstatic DirectoryPathNode z2;\n\n\t@BeforeAll\n\tstatic void setup() throws IOException {\n\t\timporter = new BasicResourceImporter(\n\t\t\t\tnew BasicInfoImporter(new InfoImporterConfig(), new TextFormatConfig(), new BasicClassPatcher()),\n\t\t\t\tnew ResourceImporterConfig()\n\t\t);\n\n\t\tBasicFileBundle fileBundle = new BasicFileBundle();\n\n\t\tprimaryJvmBundle = fromClasses(\n\t\t\t\tclassA = fromRuntimeClass(AccessibleFields.class),\n\t\t\t\tclassB = fromRuntimeClass(HelloWorld.class),\n\t\t\t\tclassC = fromRuntimeClass(StringConsumer.class),\n\t\t\t\tclassD = fromRuntimeClass(VariedModifierFields.class));\n\t\tprimaryResource = new WorkspaceResourceBuilder()\n\t\t\t\t.withJvmClassBundle(primaryJvmBundle)\n\t\t\t\t.withFileBundle(fileBundle)\n\t\t\t\t.build();\n\n\t\tworkspace = new BasicWorkspace(primaryResource);\n\n\t\tString packageName = Objects.requireNonNull(classA.getPackageName());\n\t\tString parentPackageName = packageName.substring(0, packageName.lastIndexOf('/'));\n\n\t\tp5 = PathNodes.workspacePath(workspace);\n\t\tp4 = p5.child(primaryResource);\n\t\tp3c = p4.child(primaryJvmBundle);\n\t\tp3f = p4.child(fileBundle);\n\t\tp2 = p3c.child(packageName);\n\t\tp2b = p3c.child(parentPackageName);\n\t\tp1a = p2.child(classA);\n\t\tp1b = p2.child(classB);\n\t\tp1c = p2.child(classC);\n\t\tp1d = p2.child(classD);\n\n\t\t// Content available in the default package/root directory.\n\t\tdefault2 = p3f.child(null);\n\t\tdefault1 = default2.child(new BasicFileInfo(\"root.txt\", new byte[0], new BasicPropertyContainer()));\n\n\t\t// The path will visually look like (root)//zero.txt in the workspace tree.\n\t\t// This is not ideal, but there's not really any great alternatives either.\n\t\tz2 = p3f.child(\"//\");\n\t\tz1 = z2.child(new BasicFileInfo(\"///zero.txt\", new byte[0], new BasicPropertyContainer()));\n\n\t\t// Embedded resource containing just 'root.txt'\n\t\tembeddedResource = new WorkspaceFileResourceBuilder(new BasicJvmClassBundle(), fromFiles(default1.getValue()))\n\t\t\t\t.withFileInfo(new StubFileInfo(\"embedded.jar\")).build();\n\t\tresourceWithEmbedded = new WorkspaceResourceBuilder(new BasicJvmClassBundle(), new BasicFileBundle())\n\t\t\t\t.withEmbeddedResources(Map.of(embeddedResourcePath, embeddedResource))\n\t\t\t\t.build();\n\t}\n\n\t@Test\n\tvoid testPathCreationOfFileInEmbeddedResource() {\n\t\tWorkspace workspace = new BasicWorkspace(resourceWithEmbedded);\n\t\tWorkspacePathNode workspacePath = PathNodes.workspacePath(workspace);\n\t\tFilePathNode embeddedFilePath = workspacePath.child(resourceWithEmbedded)\n\t\t\t\t.embeddedChildContainer()\n\t\t\t\t.child(embeddedResource)\n\t\t\t\t.child(embeddedResource.getFileBundle())\n\t\t\t\t.child(null)\n\t\t\t\t.child(default1.getValue());\n\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(workspacePath);\n\t\troot.getOrCreateNodeByPath(embeddedFilePath);\n\n\t\t// workspace\n\t\tWorkspaceTreeNode child = root.getFirstChild();\n\t\tassertNotNull(child, \"Workspace did not have child\");\n\n\t\t// workspace > resourceWithEmbedded\n\t\tchild = child.getFirstChild();\n\t\tassertNotNull(child, \"Primary resource did not have child\");\n\n\t\t// workspace > resourceWithEmbedded > embedded-container\n\t\tchild = child.getFirstChild();\n\t\tassertNotNull(child, \"Embedded container did not have child\");\n\n\t\t// workspace > resourceWithEmbedded > embedded-container > embeddedResource\n\t\tchild = child.getFirstChild();\n\t\tassertNotNull(child, \"Embedded resource did not have child\");\n\n\t\t// workspace > resourceWithEmbedded > embedded-container > embeddedResource > bundle\n\t\tchild = child.getFirstChild();\n\t\tassertNotNull(child, \"Embedded bundle did not have child\");\n\n\t\t// workspace > resourceWithEmbedded > embedded-container > embeddedResource > bundle > directory\n\t\tchild = child.getFirstChild();\n\t\tassertNotNull(child, \"Embedded directory did not have child\");\n\n\t\t// workspace > resourceWithEmbedded > embedded-container > embeddedResource > bundle > directory > file\n\t\tObject createdPathFile = child.getValue().getValue();\n\t\tFileInfo file = default1.getValue();\n\t\tassertEquals(file, createdPathFile, \"File at end of path not the same\");\n\t}\n\n\t@Test\n\tvoid testPackageDoesNotPreventRemovalOfPackageWithSamePrefix() {\n\t\tDirectoryPathNode firstDir = new DirectoryPathNode(\"co/fizz\");\n\t\tDirectoryPathNode secondDir = new DirectoryPathNode(\"com/foo\");\n\t\tClassPathNode firstClass = firstDir.child(createEmptyClass(firstDir.getValue() + \"/Buzz\"));\n\t\tClassPathNode secondClass = secondDir.child(createEmptyClass(secondDir.getValue() + \"/Bar\"));\n\n\t\t// Create the tree:\n\t\t//  root:\n\t\t//   co/\n\t\t//    fizz/\n\t\t//     Buzz\n\t\t//   com/\n\t\t//    foo/\n\t\t//     Bar\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(p5);\n\t\troot.getOrCreateNodeByPath(firstClass);\n\t\troot.getOrCreateNodeByPath(secondClass);\n\n\t\t// Removal of 'com/foo/Bar' should not be blocked by existence of 'co/fizz/Buzz'\n\t\t// This ensures our package parent checks do not regress.\n\t\tassertTrue(root.removeNodeByPath(secondClass));\n\t}\n\n\t@Test\n\tvoid testGetAndRemoveWithDifferentNodesOfEqualValue() {\n\t\tClassPathNode[] array = new ClassPathNode[]{p1a, p1b, p1c, p1d};\n\n\t\t// Create copies of the path nodes which we will use for the rest of the test.\n\t\tvar p5 = PathNodes.workspacePath(workspace);\n\t\tvar p4 = p5.child(primaryResource);\n\t\tvar p3c = p4.child(primaryJvmBundle);\n\t\tvar p2 = p3c.child(classA.getPackageName());\n\t\tvar p1a = p2.child(classA);\n\t\tvar p1b = p2.child(classB);\n\t\tvar p1c = p2.child(classC);\n\t\tvar p1d = p2.child(classD);\n\n\t\t// Test for each class path in the array.\n\t\tfor (ClassPathNode classPath : array) {\n\t\t\t// Create a tree model which has each class (using the cloned path nodes). Looks like:\n\t\t\t//  workspace\n\t\t\t//   bundle\n\t\t\t//    dir\n\t\t\t//     classA\n\t\t\t//     classB\n\t\t\t//     classC\n\t\t\t//     classD\n\t\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(p5);\n\t\t\troot.getOrCreateNodeByPath(p1a);\n\t\t\troot.getOrCreateNodeByPath(p1b);\n\t\t\troot.getOrCreateNodeByPath(p1c);\n\t\t\troot.getOrCreateNodeByPath(p1d);\n\n\t\t\t// Try doing operations with the ORIGINAL path node reference.\n\t\t\tassertNotNull(root.getNodeByPath(classPath), \"Could not get info\");\n\t\t\tassertTrue(root.removeNodeByPath(classPath));\n\t\t\tassertNull(root.getNodeByPath(classPath), \"Info not removed\");\n\t\t}\n\t}\n\n\t@Test\n\tvoid nameCaseSensitivity() {\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(p5);\n\t\tWorkspaceTreeNode bundle = root.getOrCreateNodeByPath(p3c);\n\n\t\t// Add:\n\t\t// - a\n\t\t// - b\n\t\t// - c (missing)\n\t\t// - d\n\t\t// - e\n\t\tWorkspaceTreeNode a = root.getOrCreateNodeByPath(p3c.child(\"a\"));\n\t\tWorkspaceTreeNode b = root.getOrCreateNodeByPath(p3c.child(\"b\"));\n\t\tWorkspaceTreeNode d = root.getOrCreateNodeByPath(p3c.child(\"d\"));\n\t\tWorkspaceTreeNode e = root.getOrCreateNodeByPath(p3c.child(\"e\"));\n\t\tassertNotNull(a);\n\t\tassertNotNull(b);\n\t\tassertNotNull(d);\n\t\tassertNotNull(e);\n\n\t\t// Insert the missing \"c\" case\n\t\tWorkspaceTreeNode c = root.getOrCreateNodeByPath(p3c.child(\"c\"));\n\t\tassertNotNull(c);\n\n\t\t// Assert sorted order\n\t\tList<TreeItem<PathNode<?>>> children = bundle.getSourceChildren();\n\t\tassertArrayEquals(new Object[]{a, b, c, d, e}, children.toArray());\n\n\t\t// Insert an upper-case \"C\" case, it should be before the lower \"c\"\n\t\tWorkspaceTreeNode cu = root.getOrCreateNodeByPath(p3c.child(\"C\"));\n\t\tassertNotNull(cu);\n\t\tassertArrayEquals(new Object[]{a, b, cu, c, d, e}, children.toArray());\n\t}\n\n\t@Test\n\tvoid nameOverloadSensitivity() {\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(p5);\n\t\tWorkspaceTreeNode bundle = root.getOrCreateNodeByPath(p3c);\n\n\t\t// Adding classes with different capitalization should yield different nodes.\n\t\t// Adding the same capitalization should yield the same node.\n\t\tWorkspaceTreeNode a1 = root.getOrCreateNodeByPath(p3c.child(\"aaa\"));\n\t\tWorkspaceTreeNode a1_alt = root.getOrCreateNodeByPath(p3c.child(\"aaa\"));\n\t\tWorkspaceTreeNode a2 = root.getOrCreateNodeByPath(p3c.child(\"aaA\"));\n\t\tWorkspaceTreeNode a3 = root.getOrCreateNodeByPath(p3c.child(\"aAa\"));\n\t\tWorkspaceTreeNode a4 = root.getOrCreateNodeByPath(p3c.child(\"Aaa\"));\n\t\tWorkspaceTreeNode a5 = root.getOrCreateNodeByPath(p3c.child(\"AAA\"));\n\n\t\t// Same node check\n\t\tassertSame(a1, a1_alt, \"Same path yielded different node references\");\n\n\t\t// Different node check\n\t\tList.of(a2, a3, a4, a5).forEach(n -> {\n\t\t\tassertNotNull(n, \"Expected node to be created\");\n\t\t\tassertNotSame(a1, n, \"Different path yielded same node reference\");\n\t\t});\n\t}\n\n\t/**\n\t * Similar to {@link #nameOverloadSensitivity()} and {@link #emptyDirDoesNotNamedPathSorting()} but also covers a case\n\t * with old code in {@link WorkspaceRootTreeNode} where we pre-sorted items before insertion and then ignored the\n\t * binary search's returned index to instead always append to the end of the list. This meant that items with\n\t * different names could wind up in the wrong order if the pre-sort was wrong.\n\t * <p>\n\t * The pre-sorting was wrong specifically in the following case:\n\t * <ul>\n\t *     <li>com/example/treemap</li>\n\t *     <li>com/example/treeview</li>\n\t *     <li>com/example/tree</li>\n\t * </ul>\n\t * The pre-sort would put \"tree\" after \"treemap\" and \"treeview\", but the binary search would find that \"tree\"\n\t * should be before both of them. So why was the pre-sort wrong? Because the named path comparator was getting confused\n\t * when comparing full class names in the packages. If you look at the index 16 of the string\n\t * (where the '/' would be after \"tree\") you have the comparator comparing \"/\" vs \"m\" or \"v\".\n\t * <p>\n\t * This has since been fixed in the named path comparator, and we no longer pre-sort before insertion.\n\t * However, this test is still useful to ensure that the sorting logic does not regress.\n\t */\n\t@Test\n\tvoid nameOrdering() {\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(p5);\n\t\tFunction<String, ClassPathNode> func = dir -> p3c.child(dir).child(new StubClassInfo(dir + \"/Clazz\"));\n\t\tString n0dir = \"\";\n\t\tString n1dir = \"a\";\n\t\tString n2dir = \"aa\";\n\t\tString n3dir = \"aaa\";\n\t\tClassPathNode n0 = func.apply(n0dir);\n\t\tClassPathNode n1 = func.apply(n1dir);\n\t\tClassPathNode n1s = func.apply(n1dir + \"/a\");\n\t\tClassPathNode n2 = func.apply(n2dir);\n\t\tClassPathNode n2s = func.apply(n2dir + \"/a\");\n\t\tClassPathNode n3 = func.apply(n3dir);\n\t\tClassPathNode n3s = func.apply(n3dir + \"/a\");\n\t\tpermutations(List.of(n0, n1, n1s, n2, n2s, n3, n3s)).forEach(ordered -> {\n\t\t\t// Clear children from prior permutation run.\n\t\t\troot.getSourceChildren().clear();\n\n\t\t\t// Insert in this order.\n\t\t\tfor (ClassPathNode path : ordered) {\n\t\t\t\tWorkspaceTreeNode node = WorkspaceTreeNode.getOrInsertIntoTree(root, path);\n\t\t\t\tassertNotNull(node, \"Node not created for path: \" + path.getValue().getName());\n\t\t\t}\n\n\t\t\t// Assert sorted order.\n\t\t\t// Regardless of the permuted insertion order, the final tree order should always be the same.\n\t\t\tWorkspaceTreeNode bundle = root.getOrCreateNodeByPath(p3c);\n\t\t\tObservableList<TreeItem<PathNode<?>>> children = bundle.getSourceChildren();\n\t\t\tassertEquals(4, children.size(), \"Expected 4 directory children\");\n\t\t\tassertEquals(n0dir, children.get(0).getValue().getValue());\n\t\t\tassertEquals(n1dir, children.get(1).getValue().getValue());\n\t\t\tassertEquals(n2dir, children.get(2).getValue().getValue());\n\t\t\tassertEquals(n3dir, children.get(3).getValue().getValue());\n\t\t});\n\t}\n\n\t/**\n\t * While not an issue of name overloading in adjacent tree nodes, this test covers a case/regression where we\n\t * saw multiple classes with the same name in different versioned paths not being all shown.\n\t * This scenario has the effect of making all {@link VersionedJvmClassBundle} have map equality too which was\n\t * another issue with an older version of the path node path containment/equality logic.\n\t */\n\t@Test\n\tvoid multipleVersionedPaths() throws Exception {\n\t\tString classPath = HelloWorld.class.getName().replace(\".\", \"/\");\n\t\tString classPackage = classPath.substring(0, classPath.lastIndexOf('/'));\n\t\tbyte[] classBytes = TestClassUtils.fromRuntimeClass(HelloWorld.class).getBytecode();\n\n\t\t// Create JAR with 'META-INF/versions/<dummyversion>/<dummypackage>/HelloWorld.class' for multiple versions.\n\t\tbyte[] zipBytes = ZipCreationUtils.builder()\n\t\t\t\t.add(classPath + \".class\", classBytes)\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"9/\" + classPath + \".class\", classBytes)\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"11/\" + classPath + \".class\", classBytes)\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"16/\" + classPath + \".class\", classBytes)\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"21/\" + classPath + \".class\", classBytes)\n\t\t\t\t.add(JarFileInfo.MULTI_RELEASE_PREFIX + \"25/\" + classPath + \".class\", classBytes)\n\t\t\t\t.bytes();\n\t\tByteSource zipSource = ByteSources.wrap(zipBytes);\n\n\t\t// Build the workspace and validate the versioned bundles exist.\n\t\tWorkspaceResource resource = importer.importResource(zipSource);\n\t\tBasicWorkspace workspace = new BasicWorkspace(resource);\n\t\tassertEquals(5, resource.getVersionedJvmClassBundles().size(), \"Expected 5 versioned bundles: 9, 11, 16, 21, 25\");\n\n\t\t// Build the tree model.\n\t\tWorkspacePathNode rootPath = PathNodes.workspacePath(workspace);\n\t\tWorkspaceRootTreeNode root = new WorkspaceRootTreeNode(new WorkspaceExplorerConfig(), rootPath);\n\t\troot.build();\n\n\t\t// Iterate over all versions, validate the path to the versioned class exists and that no duplicate tree paths exist.\n\t\tfinal int baseline = -1;\n\t\tint[] versions = new int[]{baseline, 9, 11, 16, 21, 25};\n\t\tSet<BundlePathNode> visitedNodes = Collections.newSetFromMap(new IdentityHashMap<>());\n\t\tfor (int version : versions) {\n\t\t\tString versionName = version == baseline ? \"baseline\" : String.valueOf(version);\n\t\t\tClassPathNode path = version == baseline ?\n\t\t\t\t\tworkspace.findJvmClass(classPath) :\n\t\t\t\t\tworkspace.findVersionedJvmClass(classPath, version);\n\t\t\tassertNotNull(path, \"Could not find class path for version: \" + versionName);\n\t\t\tWorkspaceTreeNode classNode = root.getNodeByPath(path);\n\t\t\tassertNotNull(classNode, \"Could not find tree node for class path for version: \" + versionName);\n\t\t\tBundlePathNode bundleNode = path.getParent().getParent();\n\t\t\tassertTrue(visitedNodes.add(bundleNode), \"Duplicate bundle node found for version: \" + versionName);\n\t\t}\n\t\tassertEquals(6, visitedNodes.size(), \"Expected 6 unique bundle nodes for versions: baseline, 9, 11, 16, 21, 25\");\n\t}\n\n\t/**\n\t * The named path sorter was getting confused when checking \"does 'a' have 'b' as a parent-directory\" if either\n\t * String was empty, which is the case for classes in the default package. This led to inconsistent return value\n\t * from the {@link Comparator#compare(Object, Object)} implementation which broke the binary search used in\n\t * {@link WorkspaceTreeNode}.\n\t *\n\t * @see Named#STRING_PATH_COMPARATOR\n\t */\n\t@Test\n\tvoid emptyDirDoesNotNamedPathSorting() {\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(p5);\n\n\t\t// No matter what insertion order, the tree should sort correctly.\n\t\tClassPathNode childModule = p3c.child(\"\").child(new StubClassInfo(\"module-info\"));\n\t\tClassPathNode childFoo = p3c.child(\"example\").child(new StubClassInfo(\"example/Foo\"));\n\t\tClassPathNode childBar = p3c.child(\"example\").child(new StubClassInfo(\"example/Bar\"));\n\t\tpermutations(List.of(childModule, childFoo, childBar)).forEach(ordered -> {\n\t\t\t// Clear children from prior permutation run.\n\t\t\troot.getSourceChildren().clear();\n\n\t\t\t// Insert in this order.\n\t\t\tWorkspaceTreeNode bundle = root.getOrCreateNodeByPath(p3c);\n\t\t\tfor (ClassPathNode path : ordered) {\n\t\t\t\tWorkspaceTreeNode node = WorkspaceTreeNode.getOrInsertIntoTree(root, path);\n\t\t\t\tassertNotNull(node, \"Node not created for path: \" + path.getValue().getName());\n\t\t\t}\n\n\t\t\t// Fetch now that tree is built.\n\t\t\tWorkspaceTreeNode ex1 = WorkspaceTreeNode.getOrInsertIntoTree(root, childFoo);\n\t\t\tWorkspaceTreeNode ex2 = WorkspaceTreeNode.getOrInsertIntoTree(root, childBar);\n\t\t\tWorkspaceTreeNode exM = WorkspaceTreeNode.getOrInsertIntoTree(root, childModule);\n\n\t\t\t// The tree should have both example paths have the same parent \"example\" node.\n\t\t\t// The bundle should see two children, the empty directory, and the \"example\" directory.\n\t\t\tList<TreeItem<PathNode<?>>> children = bundle.getSourceChildren();\n\t\t\tassertNotEquals(exM.getParent(), ex1.getParent()); // default-package vs example package\n\t\t\tassertSame(ex1.getParent(), ex2.getParent()); // both in example package\n\t\t\tassertEquals(2, children.size()); // example/ and default-package/\n\t\t});\n\t}\n\n\t@Test\n\tvoid defaultPackage() {\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(p5);\n\t\troot.getOrCreateNodeByPath(default1);\n\n\t\t// Remove the info\n\t\tassertNotNull(root.getNodeByPath(default1), \"Could not get info\");\n\t\tassertTrue(root.removeNodeByPath(default1));\n\t\tassertNull(root.getNodeByPath(default1), \"Info not removed\");\n\n\t\t// The directory should be removed since the tree was linear\n\t\tassertNull(root.getNodeByPath(default2), \"Could not get directory of info\");\n\t\tassertFalse(root.removeNodeByPath(default2));\n\t\tassertNull(root.getNodeByPath(default2), \"Directory of info not removed\");\n\n\t\t// There is no more content in the bundle, so it should be gone too\n\t\tassertNull(root.getNodeByPath(p3f), \"Could not get file bundle\");\n\t}\n\n\t@Test\n\tvoid zeroWidthDir() {\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(p5);\n\t\troot.getOrCreateNodeByPath(z1);\n\n\t\t// Remove the info\n\t\tassertNotNull(root.getNodeByPath(z1), \"Could not get info\");\n\t\tassertTrue(root.removeNodeByPath(z1));\n\t\tassertNull(root.getNodeByPath(z1), \"Info not removed\");\n\n\t\t// The directory should be removed since the tree was linear\n\t\tassertNull(root.getNodeByPath(z2), \"Could not get directory of info\");\n\t\tassertFalse(root.removeNodeByPath(z2));\n\t\tassertNull(root.getNodeByPath(z2), \"Directory of info not removed\");\n\n\t\t// There is no more content in the bundle, so it should be gone too\n\t\tassertNull(root.getNodeByPath(p3f), \"Could not get file bundle\");\n\t}\n\n\t@Test\n\tvoid removeOneOfTwoChildrenDoesNotPruneWholeTree() {\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(p5);\n\t\troot.getOrCreateNodeByPath(p1a);\n\t\troot.getOrCreateNodeByPath(p1b);\n\n\t\t// Remove the info\n\t\tassertNotNull(root.getNodeByPath(p1a), \"Could not get info\");\n\t\tassertTrue(root.removeNodeByPath(p1a));\n\t\tassertNull(root.getNodeByPath(p1a), \"Info not removed\");\n\n\t\t// The package should be not be removed since the tree still has one class remaining\n\t\tassertNotNull(root.getNodeByPath(p2), \"Could not get package of info\");\n\t\tassertTrue(root.removeNodeByPath(p1b), \"Info not removed\");\n\n\t\t// Now the package should be removed\n\t\tassertNull(root.getNodeByPath(p2), \"Package of info not removed\");\n\t\tassertNull(root.getNodeByPath(p2b), \"Parent of that package should not have been removed\");\n\n\t\t// There is no more content in the bundle, so it should be gone too\n\t\tassertNull(root.getNodeByPath(p3c), \"Could not get jvm class bundle\");\n\t\tassertFalse(root.removeNodeByPath(p3c));\n\t\tassertNull(root.getNodeByPath(p3c), \"Jvm class bundle not removed\");\n\t\tassertNull(root.getNodeByPath(p2b), \"Child of jvm class bundle still accessible after bundle removal\");\n\t}\n\n\t@Test\n\tvoid removeNodeByPath() {\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(p5);\n\t\troot.getOrCreateNodeByPath(p1a);\n\n\t\t// Remove the info\n\t\tassertNotNull(root.getNodeByPath(p1a), \"Could not get info\");\n\t\tassertTrue(root.removeNodeByPath(p1a));\n\t\tassertNull(root.getNodeByPath(p1a), \"Info not removed\");\n\n\t\t// The package should be removed since the tree was linear\n\t\tassertNull(root.getNodeByPath(p2), \"Could not get package of info\");\n\t\tassertFalse(root.removeNodeByPath(p2));\n\t\tassertNull(root.getNodeByPath(p2), \"Package of info not removed\");\n\t\tassertNull(root.getNodeByPath(p2b), \"Parent of that package should not have been removed\");\n\n\t\t// There is no more content in the bundle, so it should be gone too\n\t\tassertNull(root.getNodeByPath(p3c), \"Could not get jvm class bundle\");\n\t\tassertFalse(root.removeNodeByPath(p3c));\n\t\tassertNull(root.getNodeByPath(p3c), \"Jvm class bundle not removed\");\n\t\tassertNull(root.getNodeByPath(p2b), \"Child of jvm class bundle still accessible after bundle removal\");\n\t}\n\n\t@Test\n\tvoid getNodeByPath() {\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(p5);\n\t\troot.getOrCreateNodeByPath(p1a);\n\n\t\t// Get each node, should exist.\n\t\tassertNotNull(root.getNodeByPath(p1a), \"Could not get info\");\n\t\tassertNotNull(root.getNodeByPath(p2), \"Could not get package of info\");\n\t\tassertNotNull(root.getNodeByPath(p2b), \"Could not get parent package of info\");\n\t\tassertNotNull(root.getNodeByPath(p3c), \"Could not get bundle\");\n\t\tassertNotNull(root.getNodeByPath(p4), \"Could not get resource\");\n\t}\n\n\t@Test\n\tvoid getOrCreateNodeByPath() {\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(p5);\n\n\t\t// Get or create the deepest path should create all other parent paths.\n\t\tWorkspaceTreeNode result = root.getOrCreateNodeByPath(p1a);\n\t\tassertNotNull(result, \"No result of get or create operation\");\n\t\tassertEquals(result, root.getNodeByPath(p1a), \"Could not get info\");\n\t\tassertNotNull(root.getNodeByPath(p2), \"Could not get package of info\");\n\t\tassertNotNull(root.getNodeByPath(p2b), \"Could not get parent package of info\");\n\t\tassertNotNull(root.getNodeByPath(p3c), \"Could not get bundle\");\n\t\tassertNotNull(root.getNodeByPath(p4), \"Could not get resource\");\n\n\t\t// Try inserting with just the info missing.\n\t\tassertTrue(root.removeNodeByPath(p1a));\n\t\tresult = root.getOrCreateNodeByPath(p1a);\n\t\tassertEquals(result, root.getNodeByPath(p1a), \"Could not get info\");\n\n\t\t// Try inserting with parent package missing.\n\t\tassertTrue(root.removeNodeByPath(p2b));\n\t\tresult = root.getOrCreateNodeByPath(p1a);\n\t\tassertEquals(result, root.getNodeByPath(p1a), \"Could not get info\");\n\n\t\t// If we do repeated calls, the reference should always be the same since it acts as a getter.\n\t\tassertSame(result, root.getOrCreateNodeByPath(p1a));\n\t\tassertSame(root.getOrCreateNodeByPath(p3c), root.getOrCreateNodeByPath(p3c));\n\t}\n\n\t@Test\n\t@SuppressWarnings(\"deprecation\")\n\tvoid matches() {\n\t\tWorkspaceTreeNode root = new WorkspaceTreeNode(p5);\n\t\troot.getOrCreateNodeByPath(p1a);\n\n\t\t// Get child-most item following the \"top\" of the tree.\n\t\t// Should yield the class info node.\n\t\tWorkspaceTreeNode child = root;\n\t\twhile (!child.getChildren().isEmpty())\n\t\t\tchild = (WorkspaceTreeNode) child.getChildren().getFirst();\n\n\t\t// Validate it is the node for the class info.\n\t\tassertTrue(child.matches(p1a));\n\n\t\t// Now do the same, but following the \"source\" tree.\n\t\twhile (!child.getSourceChildren().isEmpty())\n\t\t\tchild = (WorkspaceTreeNode) child.getSourceChildren().getFirst();\n\n\t\t// Validate it is the node for the class info.\n\t\tassertTrue(child.matches(p1a));\n\t}\n\n\t@Nonnull\n\tprivate static <T> Stream<List<T>> permutations(@Nonnull Collection<T> input) {\n\t\tif (input.size() == 1)\n\t\t\treturn Stream.of(new ArrayList<>(input));\n\t\treturn input.stream()\n\t\t\t\t.flatMap(first -> permutations(input.stream()\n\t\t\t\t\t\t.filter(a -> !a.equals(first))\n\t\t\t\t\t\t.toList())\n\t\t\t\t\t\t.map(ArrayList::new)\n\t\t\t\t\t\t.peek(l -> l.addFirst(first)));\n\t}\n\n\t@Nested\n\t@SuppressWarnings(\"deprecation\") // Intentionally testing filtered API which is marked deprecated to discourage use.\n\tclass Filtered {\n\t\t@Test\n\t\tvoid insertWhileFilteredStillUpdatesChildren() {\n\t\t\t// Create workspace root, but apply a filter so that nothing is shown\n\t\t\tWorkspaceTreeNode workspaceNode = new WorkspaceTreeNode(p5);\n\t\t\tworkspaceNode.predicateProperty().set(p -> false);\n\n\t\t\t// Insert the class, which should generate all paths between the class and the workspace node.\n\t\t\tWorkspaceTreeNode classNode = getOrInsertIntoTree(workspaceNode, p1a);\n\t\t\tassertNotNull(classNode, \"Class not yielded by original assert\");\n\n\t\t\t// Validate the filtered view is still empty, but the unfiltered model has children\n\t\t\tWorkspaceTreeNode node = workspaceNode;\n\t\t\tfor (int d = 0; d < 5; d++) {\n\t\t\t\tassertTrue(node.getChildren().isEmpty(), \"Filtered children list is not empty: depth=\" + d);\n\t\t\t\tassertFalse(node.getSourceChildren().isEmpty(), \"Unfiltered children list was empty: depth=\" + d);\n\t\t\t\tnode = Unchecked.cast(workspaceNode.getSourceChildren().getFirst());\n\t\t\t}\n\t\t}\n\n\t\t@Test\n\t\tvoid removeWhileFilteredStillUpdatesChildren() {\n\t\t\t// Create workspace root and insert the class, which should generate all paths between the class and the workspace node.\n\t\t\tWorkspaceTreeNode workspaceNode = new WorkspaceTreeNode(p5);\n\t\t\tWorkspaceTreeNode classNode = getOrInsertIntoTree(workspaceNode, p1a);\n\t\t\tassertNotNull(classNode, \"Class not yielded by original assert\");\n\t\t\tassertNotNull(workspaceNode.getNodeByPath(p1a), \"Could not get class after setup\");\n\n\t\t\t// Apply a filter so that nothing is shown.\n\t\t\t// We should still be able to access items via path-lookup though.\n\t\t\tworkspaceNode.predicateProperty().set(p -> false);\n\t\t\tassertNotNull(workspaceNode.getNodeByPath(p1a), \"Could not get class after filtering\");\n\n\t\t\t// Validate the filtered view is still empty, but the unfiltered model has children\n\t\t\tassertTrue(workspaceNode.removeNodeByPath(p1a), \"Class could not be removed\");\n\t\t\tassertNull(workspaceNode.getNodeByPath(p1a), \"Class accessible after removed\");\n\t\t}\n\n\t\t@Test\n\t\tvoid removeWhileFilteredDoesNotEliminateOtherClasses() {\n\t\t\t// Create workspace root and insert the class, which should generate all paths between the class and the workspace node.\n\t\t\tWorkspaceTreeNode workspaceNode = new WorkspaceTreeNode(p5);\n\t\t\tWorkspaceTreeNode classNodeA = getOrInsertIntoTree(workspaceNode, p1a);\n\t\t\tWorkspaceTreeNode classNodeB = getOrInsertIntoTree(workspaceNode, p1b);\n\t\t\tWorkspaceTreeNode classNodeC = getOrInsertIntoTree(workspaceNode, p1c);\n\t\t\tassertNotNull(classNodeA, \"Class not yielded by original assert\");\n\t\t\tassertNotNull(classNodeB, \"Class not yielded by original assert\");\n\t\t\tassertNotNull(classNodeC, \"Class not yielded by original assert\");\n\t\t\tassertNotNull(workspaceNode.getNodeByPath(p1a), \"Could not get class after setup\");\n\t\t\tassertNotNull(workspaceNode.getNodeByPath(p1b), \"Could not get class after setup\");\n\t\t\tassertNotNull(workspaceNode.getNodeByPath(p1c), \"Could not get class after setup\");\n\n\t\t\t// Apply a filter so that nothing is shown.\n\t\t\tworkspaceNode.predicateProperty().set(p -> false);\n\n\t\t\t// Remove 'Class A' and verify 'Class B' and 'Class C' are still accessible\n\t\t\tassertTrue(workspaceNode.removeNodeByPath(p1a), \"Could not remove class after filtering\");\n\t\t\tassertNull(workspaceNode.getNodeByPath(p1a), \"Class A accessible after removed\");\n\t\t\tassertNotNull(workspaceNode.getNodeByPath(p1b), \"Class B not accessible after adjacent leaf removed\");\n\t\t\tassertNotNull(workspaceNode.getNodeByPath(p1c), \"Class C not accessible after adjacent leaf removed\");\n\t\t}\n\t}\n\n\t@Nested\n\tclass Insertion {\n\t\t@Test\n\t\tvoid insertClassGeneratesIntermediatesToWorkspaceNode() {\n\t\t\tWorkspaceTreeNode workspaceNode = new WorkspaceTreeNode(p5);\n\n\t\t\t// Insert the class, which should generate all paths between the class and the workspace node.\n\t\t\tWorkspaceTreeNode classNode = getOrInsertIntoTree(workspaceNode, p1a);\n\t\t\tassertNotNull(classNode, \"Class not yielded by original assert\");\n\n\t\t\t// Assert all package entries exist for: software.coley.recaf.test.dummy\n\t\t\tWorkspaceTreeNode packageDummy = classNode.getSourceParentNode();\n\t\t\tassertNotNull(packageDummy, \"Missing node for package: 'software.coley.recaf.test.dummy'\");\n\t\t\tWorkspaceTreeNode packageTest = packageDummy.getSourceParentNode();\n\t\t\tassertNotNull(packageTest, \"Missing node for package: 'software.coley.recaf.test'\");\n\t\t\tWorkspaceTreeNode packageRecaf = packageTest.getSourceParentNode();\n\t\t\tassertNotNull(packageRecaf, \"Missing node for package: 'software.coley.recaf'\");\n\t\t\tWorkspaceTreeNode packageColey = packageRecaf.getSourceParentNode();\n\t\t\tassertNotNull(packageColey, \"Missing node for package: 'software.coley'\");\n\t\t\tWorkspaceTreeNode packageSoftware = packageColey.getSourceParentNode();\n\t\t\tassertNotNull(packageSoftware, \"Missing node for package: 'software'\");\n\n\t\t\t// Bundle parent\n\t\t\tWorkspaceTreeNode bundleNode = packageSoftware.getSourceParentNode();\n\t\t\tassertNotNull(bundleNode, \"Missing bundle node\");\n\t\t\tassertTrue(bundleNode.getValue() instanceof BundlePathNode);\n\n\t\t\t// Resource parent\n\t\t\tWorkspaceTreeNode resourceNode = bundleNode.getSourceParentNode();\n\t\t\tassertNotNull(resourceNode, \"Missing resource node\");\n\t\t\tassertTrue(resourceNode.getValue() instanceof ResourcePathNode);\n\n\t\t\t// Workspace parent should be the same as the original item we had.\n\t\t\tWorkspaceTreeNode workspaceNode2 = resourceNode.getSourceParentNode();\n\t\t\tassertSame(workspaceNode, workspaceNode2);\n\t\t}\n\n\t\t@Test\n\t\tvoid duplicateInsertYieldsExistingNode() {\n\t\t\t// Create workspace root\n\t\t\tWorkspaceTreeNode workspaceNode = new WorkspaceTreeNode(p5);\n\n\t\t\t// Insert operation\n\t\t\tWorkspaceTreeNode classNode1 = getOrInsertIntoTree(workspaceNode, p1a);\n\t\t\tWorkspaceTreeNode classNode2 = getOrInsertIntoTree(workspaceNode, p1a);\n\t\t\tassertSame(classNode1, classNode2);\n\t\t}\n\n\t\t@Test\n\t\tvoid duplicateInsertYieldsExistingNodeOnRoot() {\n\t\t\t// Create workspace root\n\t\t\tWorkspaceTreeNode workspaceNode = new WorkspaceTreeNode(p5);\n\n\t\t\t// Insert operation, insert root value should yield self.\n\t\t\tWorkspaceTreeNode workspaceNode2 = getOrInsertIntoTree(workspaceNode, p5);\n\t\t\tassertSame(workspaceNode2, workspaceNode);\n\t\t}\n\t}\n}"
  },
  {
    "path": "settings.gradle",
    "content": "rootProject.name = 'Recaf'\n\ninclude 'recaf-core'\ninclude 'recaf-ui'\n\nbuildCache {\n    local {\n        enabled = true\n        directory = new File('build/cache')\n    }\n}"
  },
  {
    "path": "setup/code-style-eclipsej.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<profiles version=\"21\">\n\t<profile kind=\"CodeFormatterProfile\" name=\"recaf\" version=\"21\">\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.tabulation.char\" value=\"mixed\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.indentation.size\" value=\"4\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.tabulation.size\" value=\"4\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.text_block_indentation\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.indent_statements_compare_to_body\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.indent_statements_compare_to_block\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.indent_empty_lines\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.align_type_members_on_columns\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.align_with_spaces\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines\" value=\"1\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_type_declaration\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_method_declaration\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_enum_constant\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_record_declaration\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_record_constructor\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_block\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_block_in_case\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_switch\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_array_initializer\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.brace_position_for_lambda_body\" value=\"end_of_line\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration\" value=\"preserve_positions\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation\" value=\"preserve_positions\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration\" value=\"preserve_positions\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration\" value=\"preserve_positions\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation\" value=\"preserve_positions\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration\" value=\"preserve_positions\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement\" value=\"preserve_positions\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment\" value=\"preserve_positions\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement\" value=\"preserve_positions\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause\" value=\"preserve_positions\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause\" value=\"preserve_positions\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_ellipsis\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_ellipsis\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_semicolon\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_unary_operator\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_unary_operator\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_not_operator\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_additive_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_additive_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_shift_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_shift_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_relational_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_relational_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_logical_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_logical_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve\" value=\"1\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_package\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_after_package\" value=\"1\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_imports\" value=\"1\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_after_imports\" value=\"1\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations\" value=\"1\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk\" value=\"1\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_member_type\" value=\"1\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_field\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method\" value=\"1\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.blank_lines_before_method\" value=\"1\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_label\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.compact_else_if\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation\" value=\"do not insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line\" value=\"one_line_preserve\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line\" value=\"one_line_preserve\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line\" value=\"one_line_preserve\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_code_block_on_one_line\" value=\"one_line_preserve\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_method_body_on_one_line\" value=\"one_line_preserve\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line\" value=\"one_line_preserve\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line\" value=\"one_line_preserve\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line\" value=\"one_line_preserve\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line\" value=\"one_line_preserve\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line\" value=\"one_line_preserve\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line\" value=\"one_line_preserve\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line\" value=\"one_line_preserve\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.lineSplit\" value=\"120\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.continuation_indentation\" value=\"2\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer\" value=\"2\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.join_wrapped_lines\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_multiple_fields\" value=\"16\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration\" value=\"2\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_method_declaration\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration\" value=\"2\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_enum_constants\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_record_components\" value=\"18\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_additive_operator\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_additive_operator\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_string_concatenation\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_string_concatenation\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_shift_operator\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_shift_operator\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_relational_operator\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_relational_operator\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_logical_operator\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_logical_operator\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_conditional_expression\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_conditional_operator\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_assignment\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_assignment_operator\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header\" value=\"2\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_compact_if\" value=\"16\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_compact_loops\" value=\"16\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_resources_in_try\" value=\"2\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch\" value=\"18\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_assertion_message\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_type_arguments\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_type_parameters\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type\" value=\"49\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field\" value=\"49\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method\" value=\"49\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_type_annotations\" value=\"49\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.alignment_for_module_statements\" value=\"0\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.line_length\" value=\"120\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.format_javadoc_comments\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.format_block_comments\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.format_line_comments\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.format_header\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.join_lines_in_comments\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.format_html\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.format_source_code\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter\" value=\"insert\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.indent_parameter_description\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.indent_tag_description\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.indent_root_tags\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment\" value=\"false\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.use_on_off_tags\" value=\"true\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.enabling_tag\" value=\"@formatter:on\"/>\n\t\t<setting id=\"org.eclipse.jdt.core.formatter.disabling_tag\" value=\"@formatter:off\"/>\n\t</profile>\n</profiles>"
  },
  {
    "path": "setup/code-style-intellij.xml",
    "content": "<code_scheme name=\"recaf\" version=\"173\">\n  <JavaCodeStyleSettings>\n    <option name=\"CLASS_NAMES_IN_JAVADOC\" value=\"3\" />\n    <option name=\"CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND\" value=\"999\" />\n    <option name=\"PACKAGES_TO_USE_IMPORT_ON_DEMAND\">\n      <value />\n    </option>\n    <option name=\"JD_ADD_BLANK_AFTER_PARM_COMMENTS\" value=\"true\" />\n    <option name=\"JD_ADD_BLANK_AFTER_RETURN\" value=\"true\" />\n    <option name=\"JD_DO_NOT_WRAP_ONE_LINE_COMMENTS\" value=\"true\" />\n    <option name=\"JD_PRESERVE_LINE_FEEDS\" value=\"true\" />\n    <option name=\"JD_PARAM_DESCRIPTION_ON_NEW_LINE\" value=\"true\" />\n    <REPEAT_ANNOTATIONS>\n      <ANNO name=\"jakarta.annotation.Nonnull\" />\n      <ANNO name=\"jakarta.annotation.Nullable\" />\n      <ANNO name=\"javax.annotation.Nonnull\" />\n      <ANNO name=\"javax.annotation.Nullable\" />\n    </REPEAT_ANNOTATIONS>\n  </JavaCodeStyleSettings>\n  <JetCodeStyleSettings>\n    <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n  </JetCodeStyleSettings>\n  <codeStyleSettings language=\"JAVA\">\n    <option name=\"KEEP_BLANK_LINES_IN_DECLARATIONS\" value=\"1\" />\n    <option name=\"KEEP_BLANK_LINES_IN_CODE\" value=\"1\" />\n    <option name=\"KEEP_BLANK_LINES_BETWEEN_PACKAGE_DECLARATION_AND_HEADER\" value=\"1\" />\n    <option name=\"KEEP_BLANK_LINES_BEFORE_RBRACE\" value=\"1\" />\n    <option name=\"KEEP_SIMPLE_BLOCKS_IN_ONE_LINE\" value=\"true\" />\n    <option name=\"KEEP_SIMPLE_METHODS_IN_ONE_LINE\" value=\"true\" />\n    <option name=\"KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE\" value=\"true\" />\n    <option name=\"KEEP_SIMPLE_CLASSES_IN_ONE_LINE\" value=\"true\" />\n    <indentOptions>\n      <option name=\"USE_TAB_CHARACTER\" value=\"true\" />\n      <option name=\"SMART_TABS\" value=\"true\" />\n    </indentOptions>\n    <arrangement>\n      <groups>\n        <group>\n          <type>GETTERS_AND_SETTERS</type>\n          <order>KEEP</order>\n        </group>\n        <group>\n          <type>OVERRIDDEN_METHODS</type>\n          <order>KEEP</order>\n        </group>\n      </groups>\n    </arrangement>\n  </codeStyleSettings>\n  <codeStyleSettings language=\"kotlin\">\n    <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n  </codeStyleSettings>\n</code_scheme>"
  }
]