diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d31e75cd..4cf925cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,6 +40,22 @@ jobs: git push --delete origin ${GITHUB_REF#refs/tags/} exit 1 # Fail the workflow + - name: Resolve version + id: ver + run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" + + # Stage release assets: a stable-named codeanalyzer.jar (what the installer fetches), the + # Neo4j schema contract (platform-independent, version-locked to this build), and the + # cargo-dist-style install script. + - name: Stage release assets (jar + Neo4j schema + installer) + run: | + mkdir -p release-assets + cp build/libs/codeanalyzer-${{ steps.ver.outputs.version }}.jar release-assets/codeanalyzer.jar + cp build/libs/codeanalyzer-${{ steps.ver.outputs.version }}.jar "release-assets/codeanalyzer-${{ steps.ver.outputs.version }}.jar" + java -jar build/libs/codeanalyzer-${{ steps.ver.outputs.version }}.jar --emit schema > release-assets/schema.neo4j.json + cp packaging/install/codeanalyzer-installer.sh release-assets/codeanalyzer-installer.sh + ls -lh release-assets + - name: Build Changelog id: gen_changelog uses: mikepenz/release-changelog-builder-action@v5 @@ -49,10 +65,44 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # cargo-dist-style release notes: install one-liner + downloads, then the generated changelog. + - name: Compose release notes + id: notes + run: | + { + echo "## Install" + echo + echo '```sh' + echo "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/${GITHUB_REPOSITORY}/releases/download/v${{ steps.ver.outputs.version }}/codeanalyzer-installer.sh | sh" + echo '```' + echo + echo "Or run the JAR directly (requires Java 11+):" + echo + echo '```sh' + echo "java -jar codeanalyzer.jar -i /path/to/project -a 2 --emit neo4j -o ./out # writes out/graph.cypher" + echo '```' + echo + echo "## Downloads" + echo + echo "| Asset | Description |" + echo "| --- | --- |" + echo "| \`codeanalyzer.jar\` | Self-contained analyzer (run with \`java -jar\`) |" + echo "| \`codeanalyzer-installer.sh\` | Installer that fetches the jar and adds a \`codeanalyzer\` launcher |" + echo "| \`schema.neo4j.json\` | Neo4j graph schema contract (node labels, relationships, DDL) |" + echo + echo "---" + echo + echo "${{ steps.gen_changelog.outputs.changelog }}" + } > release-notes.md + - name: Publish Release uses: softprops/action-gh-release@v1 with: - files: build/libs/*.jar - body: ${{ steps.gen_changelog.outputs.changelog }} + files: | + release-assets/codeanalyzer.jar + release-assets/codeanalyzer-${{ steps.ver.outputs.version }}.jar + release-assets/schema.neo4j.json + release-assets/codeanalyzer-installer.sh + body_path: release-notes.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index bf70d632..e51c7fde 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,24 @@ Native WALA implementation of source code analysis tool for Enterprise Java Applications. +`codeanalyzer` extracts a comprehensive **symbol table** and **call graph** from Java applications +and emits them either as the canonical `analysis.json`, or as a **Neo4j property graph** +(`--emit neo4j`) — a `graph.cypher` snapshot or a live, incremental push over Bolt. See +[§4. Neo4j graph output](#4-neo4j-graph-output). + +## Quick install + +Grab the latest release jar and a `codeanalyzer` launcher (requires a Java 11+ runtime): + +```sh +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/codellm-devkit/codeanalyzer-java/releases/latest/download/codeanalyzer-installer.sh | sh +# or with wget: +wget -qO- https://github.com/codellm-devkit/codeanalyzer-java/releases/latest/download/codeanalyzer-installer.sh | sh +``` + +Overrides: `CODEANALYZER_INSTALL_DIR` (default `~/.local/bin`), `CODEANALYZER_VERSION` (default `latest`). +Prefer to build from source? See [§2. Building `codeanalyzer`](#2-building-codeanalyzer). + ## 1. Prerequisites Before you begin, ensure you have met the following requirements: @@ -68,30 +86,42 @@ Run the Gradle wrapper script to build the project. This will compile the projec ### 2.3. Using `codeanalyzer` -The jar will be built at `build/libs/codeanalyzer-1.0.jar`. It may be used as follows: +The jar will be built at `build/libs/codeanalyzer-.jar`. It may be used as follows: ```help -Usage: java -jar /path/to/codeanalyzer.jar [-hvV] [--no-build] [-a=] [-b=] +Usage: codeanalyzer [-hvV] [--no-build] [--no-clean-dependencies] + [-a=] [-b=] [-f=] [-i=] [-o=] [-s=] -Convert java binary into a comprehensive system dependency graph. - -i, --input= Path to the project root directory. - -s, --source-analysis= - Analyze a single string of java source code instead - the project. - -o, --output= Destination directory to save the output graphs. By - default, the SDG formatted as a JSON will be - printed to the console. - -b, --build-cmd= Custom build command. Defaults to auto build. - --no-build Do not build your application. Use this option if - you have already built your application. - -a, --analysis-level= - Level of analysis to perform. Options: 1 (for just - symbol table) or 2 (for call graph). Default: 1 - -v, --verbose Print logs to console. - -h, --help Show this help message and exit. - -V, --version Print version information and exit. - -t, --target-files For each file user wants to perform source analysis on top of existing analysis.json - + [--emit=] [--app-name=] + [--neo4j-uri=] [--neo4j-user=] + [--neo4j-password=] [--neo4j-database=] + [-t=]... +Analyze java application. + -i, --input= Path to the project root directory. + -s, --source-analysis= Analyze a single string of java source code instead + of the project. + -o, --output= Destination directory to save the output graphs. By + default, the analysis JSON is printed to the console. + -b, --build-cmd= Custom build command. Defaults to auto build. + --no-build Do not build your application (use if already built). + -a, --analysis-level= Level of analysis: 1 (symbol table) or 2 (call graph). + Default: 1. Level 2 adds J_CALLS edges to the graph. + -t, --target-files=... Restrict analysis to specific files (incremental). + --emit= Output target: json (analysis.json, default) | + neo4j (graph.cypher or live Bolt push) | + schema (the Neo4j schema.neo4j.json contract). + --app-name= Logical application name for the graph :JApplication + anchor (default: input dir name). + --neo4j-uri= Push the graph to a live Neo4j over Bolt (incremental); + omit to write graph.cypher. Falls back to the + NEO4J_URI environment variable. + --neo4j-user= Neo4j username (env: NEO4J_USERNAME, default: neo4j). + --neo4j-password= Neo4j password (env: NEO4J_PASSWORD, default: neo4j). + --neo4j-database= Neo4j database name (env: NEO4J_DATABASE, default: + server default). + -v, --verbose Print logs to console. + -h, --help Show this help message and exit. + -V, --version Print version information and exit. ``` @@ -157,6 +187,66 @@ There is a sample application in `src/test/resources/sample_apps/daytrader8/bina This will produce print the SDG on the console. Explore other flags to save the output to a JSON. +## 4. Neo4j graph output + +`codeanalyzer` can project the analysis IR into a [Neo4j](https://neo4j.com/) property graph instead +of `analysis.json`. The graph is a **lossless** projection of the IR: compilation units, types, +callables, fields, parameters, call sites, variables, enum constants, record components, +initialization blocks, CRUD operations/queries, comments, annotations and packages are all +first-class nodes and relationships, and (at `-a 2`) it adds `J_CALLS` edges from the call graph. +Every field of the Lombok entity model is represented (scalars as node properties — maps such as a +field's per-variable initializers are kept as a `*_json` property since Neo4j has no map type; +comments are `:JComment` nodes in addition to the convenience `docstring` property). + +The full contract (node labels, their keys and typed properties, relationship types and endpoints, +plus the constraint/index DDL) lives in [`schema.neo4j.json`](./schema.neo4j.json) and is visualized +in [`neo4j-schema.drawio`](./neo4j-schema.drawio). All node labels are `J`-prefixed and relationship +types `J_`-prefixed (e.g. `:JType`, `:JCallable`, `J_CALLS`) so a Java graph can share a Neo4j +database with the Python (`Py*`/`PY_*`) and TypeScript (`TS*`/`TS_*`) backends without colliding. +`SCHEMA_VERSION` is stamped onto the `:JApplication` node of every emitted graph. + +### 4.1. Cypher snapshot (no database required) + +```sh +codeanalyzer -i /path/to/project -a 2 --emit neo4j -o ./out +# → writes ./out/graph.cypher (a self-contained, re-runnable script) +cypher-shell -u neo4j -p < ./out/graph.cypher +``` + +The snapshot is **not** incremental: it constraints, scopes-wipes this application's prior subgraph, +then `UNWIND … MERGE`-loads the full truth. + +### 4.2. Live incremental push over Bolt + +```sh +codeanalyzer -i /path/to/project -a 2 --emit neo4j \ + --neo4j-uri bolt://localhost:7687 --neo4j-user neo4j --neo4j-password +``` + +The Bolt writer reads the database's current state and updates **only what changed**: it diffs each +compilation unit's `content_hash`, replaces just the changed units' subgraphs (idempotent +`MERGE` upserts), and — on a full run — prunes units whose source file vanished. Combine with +`--target-files` for a targeted, partial re-push (orphan pruning is then skipped). + +### 4.3. Schema contract + +```sh +codeanalyzer --emit schema -o ./out # → ./out/schema.neo4j.json (no project analysis needed) +codeanalyzer --emit schema # → prints the contract to stdout +``` + +### 4.4. Verifying the writers + +A no-container conformance test (`Neo4jSchemaConformanceTest`) asserts the projector never emits a +label/relationship/property the catalog doesn't declare, and that `schema.neo4j.json` is current. A +Testcontainers-backed integration test (`Neo4jBoltWriterTest`) spins up a real Neo4j and exercises +the Bolt writer (full push, idempotent re-push, orphan pruning). The container suite is **opt-in** +(it needs Docker/Podman) and runs only when `RUN_CONTAINER_TESTS` is set: + +```sh +RUN_CONTAINER_TESTS=1 ./gradlew test +``` + ## FAQ 1. After making a few code changes, my native binary gives random exceptions. But, my code works perfectly with `java -jar`. diff --git a/build.gradle b/build.gradle index b2ffabf8..2189bfe3 100644 --- a/build.gradle +++ b/build.gradle @@ -124,9 +124,17 @@ dependencies { implementation('com.github.javaparser:javaparser-symbol-solver-core:3.26.3') implementation('com.github.javaparser:javaparser-core:3.26.3') + // Neo4j Bolt driver for `--emit neo4j --neo4j-uri ...` (live incremental graph push). + // Bundled into the fat jar so `java -jar` supports live push out of the box. It is reached ONLY + // reflectively, via the BoltSink seam (see Neo4jEmitter#loadBoltSink), so the GraalVM native image + // prunes BoltWriter and never compiles the driver + Netty into the binary — the native build stays + // small and Netty-metadata-free, and `--neo4j-uri` there falls back to a graph.cypher snapshot. + implementation('org.neo4j.driver:neo4j-java-driver:4.4.12') + // TestContainers testImplementation 'org.testcontainers:testcontainers:1.20.6' testImplementation 'org.testcontainers:junit-jupiter:1.20.6' + testImplementation 'org.testcontainers:neo4j:1.20.6' // JUnit 5 testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1' diff --git a/gradle.properties b/gradle.properties index df44ad55..6c470b7c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=2.3.8 +version=2.4.0 diff --git a/neo4j-schema.drawio b/neo4j-schema.drawio new file mode 100644 index 00000000..2d0e6fc9 --- /dev/null +++ b/neo4j-schema.drawio @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/install/codeanalyzer-installer.sh b/packaging/install/codeanalyzer-installer.sh new file mode 100755 index 00000000..ed057ec0 --- /dev/null +++ b/packaging/install/codeanalyzer-installer.sh @@ -0,0 +1,62 @@ +#!/bin/sh +# codeanalyzer installer — downloads the prebuilt codeanalyzer-java fat JAR for the requested +# release from the GitHub Release and installs a `codeanalyzer` launcher on your PATH. Mirrors the +# cargo-dist installer pattern (see codeanalyzer-typescript's cants-installer.sh). +# +# Usage: +# curl --proto '=https' --tlsv1.2 -LsSf https://github.com/codellm-devkit/codeanalyzer-java/releases/latest/download/codeanalyzer-installer.sh | sh +# # or with wget: +# wget -qO- https://github.com/codellm-devkit/codeanalyzer-java/releases/latest/download/codeanalyzer-installer.sh | sh +# +# Environment overrides: +# CODEANALYZER_INSTALL_DIR install location (default: ~/.local/bin) +# CODEANALYZER_VERSION release tag, e.g. v2.4.0 (default: latest) +set -eu + +REPO="codellm-devkit/codeanalyzer-java" +INSTALL_DIR="${CODEANALYZER_INSTALL_DIR:-$HOME/.local/bin}" +VERSION="${CODEANALYZER_VERSION:-latest}" + +# codeanalyzer-java ships as a single self-contained fat JAR; it needs a Java 11+ runtime. +if ! command -v java >/dev/null 2>&1; then + echo "codeanalyzer: a Java 11+ runtime is required but 'java' was not found on PATH." >&2 + echo "codeanalyzer: install a JDK (e.g. 'sdk install java 17.0.10-sem') and re-run." >&2 + exit 1 +fi + +asset="codeanalyzer.jar" +if [ "$VERSION" = "latest" ]; then + url="https://github.com/$REPO/releases/latest/download/$asset" +else + url="https://github.com/$REPO/releases/download/$VERSION/$asset" +fi + +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +echo "codeanalyzer: downloading $asset ($VERSION)..." +if command -v curl >/dev/null 2>&1; then + curl --proto '=https' --tlsv1.2 -fLsS "$url" -o "$tmp/codeanalyzer.jar" +elif command -v wget >/dev/null 2>&1; then + wget -q "$url" -O "$tmp/codeanalyzer.jar" +else + echo "codeanalyzer: need curl or wget to download" >&2 + exit 1 +fi + +mkdir -p "$INSTALL_DIR" +mv "$tmp/codeanalyzer.jar" "$INSTALL_DIR/codeanalyzer.jar" + +# Write a tiny launcher so users can call `codeanalyzer ...` directly. +cat > "$INSTALL_DIR/codeanalyzer" < symbolTable; projectRootPom = projectRootPom == null ? input : projectRootPom; @@ -212,6 +252,24 @@ private static void analyze() throws Exception { // Cleanup library dependencies directory BuildProject.cleanLibraryDependencies(); + // Neo4j graph output: project the IR to a graph and either push it over Bolt or write a + // graph.cypher snapshot. The call graph (level 2) is included as CALLS edges when present. + if ("neo4j".equalsIgnoreCase(emit)) { + JsonArray callGraph = combinedJsonObject.has("call_graph") + ? combinedJsonObject.getAsJsonArray("call_graph") + : null; + // Connection options resolve with precedence: CLI flag > NEO4J_* env var > default. + String uri = firstNonEmpty(neo4jUri, System.getenv("NEO4J_URI")); + BoltConfig bolt = uri == null + ? null + : new BoltConfig(uri, + firstNonEmpty(neo4jUser, System.getenv("NEO4J_USERNAME"), "neo4j"), + firstNonEmpty(neo4jPassword, System.getenv("NEO4J_PASSWORD"), "neo4j"), + firstNonEmpty(neo4jDatabase, System.getenv("NEO4J_DATABASE"))); + Neo4jEmitter.emit(symbolTable, callGraph, appName, input, output, targetFiles != null, bolt); + return; + } + // Convert the JavaCompilationUnit to JSON and add to consolidated json object String symbolTableJSONString = gson.toJson(symbolTable); JsonElement symbolTableJSON = gson.fromJson(symbolTableJSONString, JsonElement.class); diff --git a/src/main/java/com/ibm/cldk/neo4j/BoltConfig.java b/src/main/java/com/ibm/cldk/neo4j/BoltConfig.java new file mode 100644 index 00000000..b0c27551 --- /dev/null +++ b/src/main/java/com/ibm/cldk/neo4j/BoltConfig.java @@ -0,0 +1,33 @@ +/* +Copyright IBM Corporation 2023, 2024 + +Licensed under the Apache Public License 2.0, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.ibm.cldk.neo4j; + +/** + * Bolt connection configuration. Deliberately driver-free (no {@code org.neo4j.driver} import) and + * separate from {@link BoltSink}/{@code BoltWriter} so the core (CLI, {@link Neo4jEmitter}) can carry + * connection options without statically referencing the Neo4j driver — which is what lets the GraalVM + * native image prune the driver + Netty entirely. See {@link BoltSink}. + */ +public final class BoltConfig { + public final String uri; + public final String user; + public final String password; + public final String database; + + public BoltConfig(String uri, String user, String password, String database) { + this.uri = uri; + this.user = user; + this.password = password; + this.database = database; + } +} diff --git a/src/main/java/com/ibm/cldk/neo4j/BoltSink.java b/src/main/java/com/ibm/cldk/neo4j/BoltSink.java new file mode 100644 index 00000000..d0bffb70 --- /dev/null +++ b/src/main/java/com/ibm/cldk/neo4j/BoltSink.java @@ -0,0 +1,31 @@ +/* +Copyright IBM Corporation 2023, 2024 + +Licensed under the Apache Public License 2.0, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.ibm.cldk.neo4j; + +/** + * The driver-free seam between the core and the live Bolt writer. {@code BoltWriter} is the only + * class that imports {@code org.neo4j.driver.*}; everything else (the CLI, {@link Neo4jEmitter}) + * talks to it through this interface and never names {@code BoltWriter} statically. + * + *

{@link Neo4jEmitter} obtains the implementation reflectively (by a non-constant class name), so: + *

    + *
  • Fat jar — the Neo4j driver is bundled, reflection succeeds, live push works.
  • + *
  • GraalVM native image — because nothing references {@code BoltWriter} statically and the + * reflective name is not a foldable constant, native-image's reachability analysis prunes + * {@code BoltWriter}, so the Neo4j driver and Netty are never compiled into the binary. At + * runtime {@code --neo4j-uri} then degrades gracefully to a {@code graph.cypher} snapshot.
  • + *
+ */ +public interface BoltSink { + void write(GraphRows rows, BoltConfig cfg, boolean fullRun); +} diff --git a/src/main/java/com/ibm/cldk/neo4j/BoltWriter.java b/src/main/java/com/ibm/cldk/neo4j/BoltWriter.java new file mode 100644 index 00000000..55322e39 --- /dev/null +++ b/src/main/java/com/ibm/cldk/neo4j/BoltWriter.java @@ -0,0 +1,231 @@ +/* +Copyright IBM Corporation 2023, 2024 + +Licensed under the Apache Public License 2.0, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.ibm.cldk.neo4j; + +import com.ibm.cldk.neo4j.GraphRows.EdgeRow; +import com.ibm.cldk.neo4j.GraphRows.NodeRow; +import com.ibm.cldk.utils.Log; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.Session; +import org.neo4j.driver.SessionConfig; +import org.neo4j.driver.Values; + +/** + * The incremental writer: push {@link GraphRows} into a live Neo4j over Bolt. Unlike the snapshot + * writer, this one reads the DB's current state and updates only what changed. + * + *

Algorithm (the compilation-unit subgraph is the unit of idempotent replacement): + *

    + *
  1. ensure constraints + indexes.
  2. + *
  3. diff each unit's {@code content_hash} against the DB → the set of changed units.
  4. + *
  5. per changed unit, in a transaction: delete the edges it owned (edges out of its nodes), + * detach-delete the declarations it no longer emits, then upsert its current nodes.
  6. + *
  7. upsert edges owned by changed units (+ the shared edges).
  8. + *
  9. on a FULL run only, prune units whose source file vanished.
  10. + *
+ * + *

Nodes are MERGE-upserted, never blindly deleted, so a declaration another (unchanged) unit + * still references survives and its incoming edges stay valid. {@code :JPackage}/{@code :JAnnotation} + * are shared (no {@code _module}) and are MERGE-only. + */ +public final class BoltWriter implements BoltSink { + + private static final int BATCH = 1000; + + /** Public no-arg constructor: {@link Neo4jEmitter} instantiates this reflectively via {@link BoltSink}. */ + public BoltWriter() {} + + @Override + public void write(GraphRows rows, BoltConfig cfg, boolean fullRun) { + try (Driver driver = GraphDatabase.driver(cfg.uri, AuthTokens.basic(cfg.user, cfg.password))) { + new Runner(driver, cfg.database).run(rows, fullRun); + } + } + + private static final class Runner { + private final Driver driver; + private final String database; + + Runner(Driver driver, String database) { + this.driver = driver; + this.database = database; + } + + private Session session() { + return database != null + ? driver.session(SessionConfig.forDatabase(database)) + : driver.session(); + } + + void run(GraphRows rows, boolean fullRun) { + // 1. schema (DDL runs in its own autocommit transactions). + try (Session s = session()) { + for (String stmt : Schema.CONSTRAINTS) { + s.run(stmt); + } + for (String stmt : Schema.INDEXES) { + s.run(stmt); + } + } + + // Partition nodes by owning unit; shared nodes have no _module. + Map> byUnit = new LinkedHashMap<>(); + List shared = new ArrayList<>(); + Map unitOf = new HashMap<>(); // node value → owning unit + for (NodeRow n : rows.nodes) { + Object m = n.props.get("_module"); + if (m instanceof String) { + byUnit.computeIfAbsent((String) m, x -> new ArrayList<>()).add(n); + unitOf.put(n.value, (String) m); + } else { + shared.add(n); + } + } + + // 2. diff content_hash. + Map dbHash = new HashMap<>(); + try (Session s = session()) { + s.run("MATCH (c:JCompilationUnit) RETURN c.file_key AS k, c.content_hash AS h").list() + .forEach(rec -> dbHash.put(rec.get("k").asString(null), rec.get("h").asString(null))); + } + Set changed = new HashSet<>(); + for (Map.Entry> e : byUnit.entrySet()) { + String unit = e.getKey(); + String rowHash = hashOf(e.getValue(), unit); + if (!dbHash.containsKey(unit) || rowHash == null || !rowHash.equals(dbHash.get(unit))) { + changed.add(unit); + } + } + Log.info("neo4j(bolt): " + byUnit.size() + " units (" + changed.size() + " changed), " + + shared.size() + " shared nodes, " + rows.edges.size() + " edges"); + + // 3. shared nodes are always upserted (MERGE-only). + upsertNodes(shared); + + // 4. per changed unit: purge owned edges + vanished decls, then upsert its nodes. + for (String unit : changed) { + List nodes = byUnit.get(unit); + List keys = new ArrayList<>(); + for (NodeRow n : nodes) { + keys.add(n.value); + } + try (Session s = session()) { + s.writeTransaction(tx -> { + tx.run("MATCH (x {_module: $m})-[r]->() DELETE r", Values.parameters("m", unit)); + tx.run("MATCH (x {_module: $m}) WHERE NOT coalesce(x.id, x.file_key) IN $keys DETACH DELETE x", + Values.parameters("m", unit, "keys", keys)); + return null; + }); + } + upsertNodes(nodes); + } + + // 5. upsert edges owned by a changed unit (owner = source node's unit) or shared. + List edges = new ArrayList<>(); + for (EdgeRow e : rows.edges) { + String owner = unitOf.get(e.from.value); + if (owner == null || changed.contains(owner)) { + edges.add(e); + } + } + upsertEdges(edges); + + // 6. orphan prune — only safe on a full run. + if (fullRun) { + List present = new ArrayList<>(byUnit.keySet()); + try (Session s = session()) { + long pruned = s.run("MATCH (c:JCompilationUnit) WHERE NOT c.file_key IN $present " + + "OPTIONAL MATCH (c)-" + CypherWriter.DESCENDANTS + "->(x) " + + "DETACH DELETE x, c RETURN count(c) AS pruned", + Values.parameters("present", present)).single().get("pruned").asLong(0); + Log.info("neo4j(bolt): pruned " + pruned + " vanished unit(s)"); + } + } else { + Log.info("neo4j(bolt): targeted run — orphan pruning skipped (deleted files not removed)"); + } + } + + private void upsertNodes(List nodes) { + Map> groups = new LinkedHashMap<>(); + for (NodeRow n : nodes) { + groups.computeIfAbsent(String.join(":", n.labels) + "|" + n.keyProp, x -> new ArrayList<>()).add(n); + } + for (List group : groups.values()) { + NodeRow head = group.get(0); + List extra = head.labels.subList(1, head.labels.size()); + String setLabels = extra.isEmpty() ? "" : ", n:" + String.join(":", extra); + String cypher = "UNWIND $rows AS row MERGE (n:" + head.labels.get(0) + " {" + + head.keyProp + ": row.k}) SET n += row.p" + setLabels; + for (List batch : CypherWriter.chunk(group, BATCH)) { + List> payload = new ArrayList<>(); + for (NodeRow n : batch) { + Map r = new HashMap<>(); + r.put("k", n.value); + r.put("p", n.props); + payload.add(r); + } + try (Session s = session()) { + s.run(cypher, Values.parameters("rows", payload)); + } + } + } + } + + private void upsertEdges(List edges) { + Map> groups = new LinkedHashMap<>(); + for (EdgeRow e : edges) { + String k = e.type + "|" + e.from.label + "." + e.from.keyProp + "|" + e.to.label + "." + e.to.keyProp; + groups.computeIfAbsent(k, x -> new ArrayList<>()).add(e); + } + for (List group : groups.values()) { + EdgeRow head = group.get(0); + String cypher = "UNWIND $rows AS row " + + "MATCH (a:" + head.from.label + " {" + head.from.keyProp + ": row.f}) " + + "MATCH (b:" + head.to.label + " {" + head.to.keyProp + ": row.t}) " + + "MERGE (a)-[r:" + head.type + "]->(b) SET r += row.p"; + for (List batch : CypherWriter.chunk(group, BATCH)) { + List> payload = new ArrayList<>(); + for (EdgeRow e : batch) { + Map r = new HashMap<>(); + r.put("f", e.from.value); + r.put("t", e.to.value); + r.put("p", e.props); + payload.add(r); + } + try (Session s = session()) { + s.run(cypher, Values.parameters("rows", payload)); + } + } + } + } + + private static String hashOf(List nodes, String fileKey) { + for (NodeRow n : nodes) { + if (n.labels.get(0).equals("JCompilationUnit") && n.value.equals(fileKey)) { + Object h = n.props.get("content_hash"); + return h instanceof String ? (String) h : null; + } + } + return null; + } + } +} diff --git a/src/main/java/com/ibm/cldk/neo4j/CypherWriter.java b/src/main/java/com/ibm/cldk/neo4j/CypherWriter.java new file mode 100644 index 00000000..e5cdd2ea --- /dev/null +++ b/src/main/java/com/ibm/cldk/neo4j/CypherWriter.java @@ -0,0 +1,184 @@ +/* +Copyright IBM Corporation 2023, 2024 + +Licensed under the Apache Public License 2.0, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.ibm.cldk.neo4j; + +import com.ibm.cldk.neo4j.GraphRows.EdgeRow; +import com.ibm.cldk.neo4j.GraphRows.NodeRow; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * The snapshot writer: render {@link GraphRows} to a self-contained {@code .cypher} script. Running + * it (e.g. {@code cypher-shell < graph.cypher}) rebuilds this project's subgraph from scratch — + * constraints, a scoped wipe of the prior version, then batched {@code UNWIND … MERGE} for nodes + * and edges. + * + *

This artifact is intentionally NOT incremental: a static script has no view of the live DB, so + * it expresses the full truth. Incremental updates are the {@link BoltWriter}'s job. + */ +public final class CypherWriter { + + private static final int BATCH = 500; + static final String DESCENDANTS = "[:J_DECLARES_TYPE|J_HAS_NESTED_TYPE|J_HAS_CALLABLE|J_HAS_FIELD|J_HAS_PARAMETER" + + "|J_HAS_CALLSITE|J_DECLARES_VAR|J_HAS_ENUM_CONSTANT|J_HAS_RECORD_COMPONENT|J_HAS_INIT_BLOCK" + + "|J_HAS_CRUD_OPERATION|J_HAS_CRUD_QUERY|J_HAS_COMMENT*1..]"; + + private CypherWriter() {} + + public static String renderCypher(GraphRows rows, String appName) { + List out = new ArrayList<>(); + + out.add("// ── constraints & indexes ──"); + for (String stmt : Schema.CONSTRAINTS) { + out.add(stmt + ";"); + } + for (String stmt : Schema.INDEXES) { + out.add(stmt + ";"); + } + + out.add(""); + out.add("// ── wipe this project's prior subgraph (packages/annotations are shared) ──"); + out.add(wipe(appName)); + + out.add(""); + out.add("// ── nodes ──"); + out.addAll(nodeStatements(rows.nodes)); + + out.add(""); + out.add("// ── relationships ──"); + out.addAll(edgeStatements(rows.edges)); + + out.add(""); + return String.join("\n", out); + } + + private static String wipe(String appName) { + return "MATCH (a:JApplication {name: " + cypherValue(appName) + "})\n" + + "OPTIONAL MATCH (a)-[:J_HAS_UNIT]->(c:JCompilationUnit)\n" + + "OPTIONAL MATCH (c)-" + DESCENDANTS + "->(x)\n" + + "DETACH DELETE x, c, a;"; + } + + // ---------------------------------------------------------------------------------------------- + // Nodes — grouped by their full label set + key property, batched into UNWIND lists. + // ---------------------------------------------------------------------------------------------- + + private static List nodeStatements(List nodes) { + Map> groups = new LinkedHashMap<>(); + for (NodeRow n : nodes) { + String k = String.join(":", n.labels) + "|" + n.keyProp; + groups.computeIfAbsent(k, x -> new ArrayList<>()).add(n); + } + + List blocks = new ArrayList<>(); + for (List group : groups.values()) { + NodeRow head = group.get(0); + String mergeLabel = head.labels.get(0); + List extra = head.labels.subList(1, head.labels.size()); + String setLabels = extra.isEmpty() ? "" : ", n:" + String.join(":", extra); + for (List batch : chunk(group, BATCH)) { + List list = new ArrayList<>(); + for (NodeRow n : batch) { + list.add(" {k: " + cypherValue(n.value) + ", p: " + cypherMap(n.props) + "}"); + } + blocks.add("UNWIND [\n" + String.join(",\n", list) + "\n] AS row\n" + + "MERGE (n:" + mergeLabel + " {" + head.keyProp + ": row.k})\n" + + "SET n += row.p" + setLabels + ";"); + } + } + return blocks; + } + + // ---------------------------------------------------------------------------------------------- + // Edges — grouped by (type, endpoint labels + key props), batched. + // ---------------------------------------------------------------------------------------------- + + private static List edgeStatements(List edges) { + Map> groups = new LinkedHashMap<>(); + for (EdgeRow e : edges) { + String k = e.type + "|" + e.from.label + "." + e.from.keyProp + "|" + e.to.label + "." + e.to.keyProp; + groups.computeIfAbsent(k, x -> new ArrayList<>()).add(e); + } + + List blocks = new ArrayList<>(); + for (List group : groups.values()) { + EdgeRow head = group.get(0); + for (List batch : chunk(group, BATCH)) { + List list = new ArrayList<>(); + for (EdgeRow e : batch) { + list.add(" {f: " + cypherValue(e.from.value) + ", t: " + cypherValue(e.to.value) + + ", p: " + cypherMap(e.props) + "}"); + } + blocks.add("UNWIND [\n" + String.join(",\n", list) + "\n] AS row\n" + + "MATCH (a:" + head.from.label + " {" + head.from.keyProp + ": row.f})\n" + + "MATCH (b:" + head.to.label + " {" + head.to.keyProp + ": row.t})\n" + + "MERGE (a)-[r:" + head.type + "]->(b)\n" + + "SET r += row.p;"); + } + } + return blocks; + } + + // ---------------------------------------------------------------------------------------------- + // Cypher literal rendering + // ---------------------------------------------------------------------------------------------- + + @SuppressWarnings("unchecked") + static String cypherValue(Object v) { + if (v == null) { + return "null"; + } + if (v instanceof String) { + return cypherString((String) v); + } + if (v instanceof Boolean) { + return ((Boolean) v) ? "true" : "false"; + } + if (v instanceof Number) { + return v.toString(); + } + if (v instanceof List) { + List list = (List) v; + List parts = new ArrayList<>(); + for (Object x : list) { + parts.add(cypherValue(x)); + } + return "[" + String.join(", ", parts) + "]"; + } + return cypherString(v.toString()); + } + + static String cypherMap(Map props) { + List entries = new ArrayList<>(); + for (Map.Entry e : props.entrySet()) { + entries.add(e.getKey() + ": " + cypherValue(e.getValue())); + } + return "{" + String.join(", ", entries) + "}"; + } + + private static String cypherString(String s) { + String escaped = s.replace("\\", "\\\\").replace("'", "\\'") + .replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t"); + return "'" + escaped + "'"; + } + + static List> chunk(List items, int size) { + List> out = new ArrayList<>(); + for (int i = 0; i < items.size(); i += size) { + out.add(new ArrayList<>(items.subList(i, Math.min(i + size, items.size())))); + } + return out; + } +} diff --git a/src/main/java/com/ibm/cldk/neo4j/GraphProjector.java b/src/main/java/com/ibm/cldk/neo4j/GraphProjector.java new file mode 100644 index 00000000..3851d233 --- /dev/null +++ b/src/main/java/com/ibm/cldk/neo4j/GraphProjector.java @@ -0,0 +1,643 @@ +/* +Copyright IBM Corporation 2023, 2024 + +Licensed under the Apache Public License 2.0, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.ibm.cldk.neo4j; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.ibm.cldk.entities.CRUDOperation; +import com.ibm.cldk.entities.CRUDQuery; +import com.ibm.cldk.entities.CallSite; +import com.ibm.cldk.entities.Callable; +import com.ibm.cldk.entities.Comment; +import com.ibm.cldk.entities.EnumConstant; +import com.ibm.cldk.entities.Field; +import com.ibm.cldk.entities.Import; +import com.ibm.cldk.entities.InitializationBlock; +import com.ibm.cldk.entities.JavaCompilationUnit; +import com.ibm.cldk.entities.ParameterInCallable; +import com.ibm.cldk.entities.RecordComponent; +import com.ibm.cldk.entities.Type; +import com.ibm.cldk.entities.VariableDeclaration; +import com.ibm.cldk.neo4j.GraphRows.NodeRef; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * {@code project()} — the pure projection from the canonical codeanalyzer symbol table (and the + * level-2 call graph) to graph rows. It walks the same containment tree the SDG builder walks, but + * instead of collecting call edges it emits nodes + edges. No I/O: the writers + * ({@link CypherWriter} snapshot / {@link BoltWriter} incremental) consume the returned + * {@link GraphRows}. + * + *

Modelling decisions (mirrors the codeanalyzer-typescript Neo4j projection; all node labels are + * {@code J}-prefixed and relationship types {@code J_}-prefixed so a Java graph can share a Neo4j + * database with the Python/TypeScript backends without colliding): + *

    + *
  • {@code JType} and {@code JCallable} carry a shared {@code :JSymbol} label (the global-identity + * / MERGE key); their {@code id} is the FQN (types) or {@code #} (callables).
  • + *
  • call sites, fields, parameters, variables, enum constants, record components, initialization + * blocks, CRUD operations/queries and comments are all first-class nodes (the graph is a + * lossless projection of the IR); annotations are shared {@code :JAnnotation} nodes (like TS + * decorators) in addition to the {@code annotations} string array on each owner.
  • + *
  • entrypoints are a marker label ({@code :JEntrypoint}) on the owning callable/type.
  • + *
  • every project-owned node carries an internal {@code _module} provenance prop, so the + * incremental writer can delete exactly what a re-analyzed compilation unit previously emitted.
  • + *
+ */ +public final class GraphProjector { + + private static final Gson GSON = new Gson(); + + private GraphProjector() {} + + /** + * @param symbolTable file path → {@link JavaCompilationUnit} (the {@code symbol_table} map). + * @param callGraph the {@code call_graph} array (level 2), or {@code null} at level 1. + * @param appName logical application name for the {@code :JApplication} anchor. + */ + public static GraphRows project(Map symbolTable, JsonArray callGraph, String appName) { + RowBuilder b = new RowBuilder(); + + NodeRef app = b.node(Collections.singletonList("JApplication"), "name", appName, + map("schema_version", SchemaCatalog.SCHEMA_VERSION, "name", appName)); + + for (Map.Entry e : symbolTable.entrySet()) { + String fileKey = e.getKey(); + JavaCompilationUnit cu = e.getValue(); + NodeRef cuRef = b.node(Collections.singletonList("JCompilationUnit"), "file_key", fileKey, + compilationUnitProps(cu, fileKey)); + b.edge("J_HAS_UNIT", app, cuRef); + projectCompilationUnit(b, fileKey, cuRef, cu); + } + + if (callGraph != null) { + projectCallGraph(b, callGraph); + } + + return b.finish(); + } + + // ---------------------------------------------------------------------------------------------- + // Compilation unit body + // ---------------------------------------------------------------------------------------------- + + private static void projectCompilationUnit(RowBuilder b, String fileKey, NodeRef cuRef, JavaCompilationUnit cu) { + Map types = cu.getTypeDeclarations(); + if (types == null) { + types = Collections.emptyMap(); + } + Set typeKeys = types.keySet(); + + // Per-type nodes; nested types hang off their parent (HAS_NESTED_TYPE), top-level off the unit. + Map typeRefs = new java.util.HashMap<>(); + for (Map.Entry te : types.entrySet()) { + typeRefs.put(te.getKey(), b.node(symbolLabels("JType", te.getValue().isEntrypointClass()), "id", + te.getKey(), typeProps(te.getValue(), te.getKey(), fileKey))); + } + + for (Map.Entry te : types.entrySet()) { + String fqn = te.getKey(); + Type type = te.getValue(); + NodeRef typeRef = typeRefs.get(fqn); + + String parent = type.getParentType(); + if (parent != null && typeKeys.contains(parent)) { + b.edge("J_HAS_NESTED_TYPE", typeRefs.get(parent), typeRef); + } else { + b.edge("J_DECLARES_TYPE", cuRef, typeRef); + } + + projectTypeBody(b, fileKey, fqn, typeRef, type); + } + + // Imports: resolve to a known Type (gated) or to a Package node. + if (cu.getImports() != null) { + for (Import im : cu.getImports()) { + projectImport(b, cuRef, im, typeKeys); + } + } + + projectComments(b, cuRef, cu.getComments(), fileKey); + } + + private static void projectImport(RowBuilder b, NodeRef cuRef, Import im, Set typeKeys) { + String path = im.getPath(); + if (path == null || path.isEmpty()) { + return; + } + Map props = map("is_static", im.isStatic(), "is_wildcard", im.isWildcard()); + if (!im.isWildcard() && typeKeys.contains(path)) { + b.edgeToSymbol("J_IMPORTS", cuRef, path, props); + return; + } + // Otherwise model the imported package: the path's package portion (strip the trailing class). + String pkg = im.isWildcard() ? path : packageOf(path); + if (pkg != null && !pkg.isEmpty()) { + NodeRef pkgRef = b.node(Collections.singletonList("JPackage"), "name", pkg, map("name", pkg)); + b.edge("J_IMPORTS", cuRef, pkgRef, props); + } + } + + // ---------------------------------------------------------------------------------------------- + // Type body + // ---------------------------------------------------------------------------------------------- + + private static void projectTypeBody(RowBuilder b, String fileKey, String fqn, NodeRef typeRef, Type type) { + for (String s : safe(type.getAnnotations())) { + annotate(b, typeRef, s); + } + for (String s : safe(type.getExtendsList())) { + b.edgeToSymbol("J_EXTENDS", typeRef, s); + } + for (String s : safe(type.getImplementsList())) { + b.edgeToSymbol("J_IMPLEMENTS", typeRef, s); + } + + if (type.getCallableDeclarations() != null) { + for (Map.Entry ce : type.getCallableDeclarations().entrySet()) { + projectCallable(b, fileKey, fqn, typeRef, ce.getValue(), ce.getKey()); + } + } + for (Field f : safe(type.getFieldDeclarations())) { + projectField(b, fileKey, fqn, typeRef, f); + } + for (EnumConstant ec : safe(type.getEnumConstants())) { + projectEnumConstant(b, fileKey, fqn, typeRef, ec); + } + for (RecordComponent rc : safe(type.getRecordComponents())) { + projectRecordComponent(b, fileKey, fqn, typeRef, rc); + } + List initBlocks = type.getInitializationBlocks(); + if (initBlocks != null) { + for (int i = 0; i < initBlocks.size(); i++) { + projectInitializationBlock(b, fileKey, fqn, typeRef, initBlocks.get(i), i); + } + } + projectComments(b, typeRef, type.getComments(), fileKey); + } + + private static void projectCallable(RowBuilder b, String fileKey, String ownerFqn, NodeRef owner, + Callable c, String mapKey) { + String signature = c.getSignature() != null ? c.getSignature() : mapKey; + String id = ownerFqn + "#" + signature; + NodeRef ref = b.node(symbolLabels("JCallable", c.isEntrypoint()), "id", id, callableProps(c, id, signature, fileKey)); + b.edge("J_HAS_CALLABLE", owner, ref); + + for (String s : safe(c.getAnnotations())) { + annotate(b, ref, s); + } + + List params = c.getParameters(); + if (params != null) { + for (int i = 0; i < params.size(); i++) { + projectParameter(b, fileKey, id, ref, params.get(i), i); + } + } + for (CallSite cs : safe(c.getCallSites())) { + projectCallSite(b, fileKey, id, ref, cs); + } + for (VariableDeclaration v : safe(c.getVariableDeclarations())) { + projectVariable(b, fileKey, id, ref, v); + } + List crudOps = c.getCrudOperations(); + if (crudOps != null) { + for (int i = 0; i < crudOps.size(); i++) { + projectCrudOperation(b, fileKey, id, ref, crudOps.get(i), i); + } + } + List crudQueries = c.getCrudQueries(); + if (crudQueries != null) { + for (int i = 0; i < crudQueries.size(); i++) { + projectCrudQuery(b, fileKey, id, ref, crudQueries.get(i), i); + } + } + projectComments(b, ref, c.getComments(), fileKey); + } + + private static void projectParameter(RowBuilder b, String fileKey, String callableId, NodeRef owner, + ParameterInCallable p, int index) { + String id = callableId + "#param#" + index; + NodeRef ref = b.node(Collections.singletonList("JParameter"), "id", id, RowBuilder.prune( + map("id", id, "name", p.getName(), "type", p.getType(), + "annotations", strList(p.getAnnotations()), "modifiers", strList(p.getModifiers()), + "start_line", asLong(p.getStartLine()), "end_line", asLong(p.getEndLine()), + "start_column", asLong(p.getStartColumn()), "end_column", asLong(p.getEndColumn()), + "_module", fileKey))); + b.edge("J_HAS_PARAMETER", owner, ref); + } + + private static void projectCallSite(RowBuilder b, String fileKey, String callableId, NodeRef owner, CallSite s) { + String id = callableId + "@" + s.getStartLine() + ":" + s.getStartColumn() + + "-" + s.getEndLine() + ":" + s.getEndColumn(); + NodeRef ref = b.node(Collections.singletonList("JCallSite"), "id", id, RowBuilder.prune( + map("id", id, "method_name", s.getMethodName(), "receiver_expr", s.getReceiverExpr(), + "receiver_type", s.getReceiverType(), "return_type", s.getReturnType(), + "callee_signature", s.getCalleeSignature(), "argument_types", strList(s.getArgumentTypes()), + "argument_expr", strList(s.getArgumentExpr()), + "is_static_call", s.isStaticCall(), "is_constructor_call", s.isConstructorCall(), + "is_public", s.isPublic(), "is_private", s.isPrivate(), "is_protected", s.isProtected(), + "is_unspecified", s.isUnspecified(), + "start_line", asLong(s.getStartLine()), "start_column", asLong(s.getStartColumn()), + "end_line", asLong(s.getEndLine()), "end_column", asLong(s.getEndColumn()), + "docstring", docstringOf(s.getComment()), "_module", fileKey))); + b.edge("J_HAS_CALLSITE", owner, ref); + if (s.getCalleeSignature() != null && !s.getCalleeSignature().isEmpty()) { + // Gated: kept only if the callee was emitted as a JCallable node. + b.edgeToSymbol("J_RESOLVES_TO", ref, s.getCalleeSignature()); + } + projectComment(b, ref, s.getComment(), fileKey); + projectCrudOperation(b, fileKey, id, ref, s.getCrudOperation(), 0); + projectCrudQuery(b, fileKey, id, ref, s.getCrudQuery(), 0); + } + + private static void projectVariable(RowBuilder b, String fileKey, String callableId, NodeRef owner, + VariableDeclaration v) { + String id = callableId + "#var#" + v.getName() + "@" + v.getStartLine(); + NodeRef ref = b.node(Collections.singletonList("JVariable"), "id", id, RowBuilder.prune( + map("id", id, "name", v.getName(), "type", v.getType(), "initializer", v.getInitializer(), + "start_line", asLong(v.getStartLine()), "end_line", asLong(v.getEndLine()), + "start_column", asLong(v.getStartColumn()), "end_column", asLong(v.getEndColumn()), + "docstring", docstringOf(v.getComment()), "_module", fileKey))); + b.edge("J_DECLARES_VAR", owner, ref); + projectComment(b, ref, v.getComment(), fileKey); + } + + private static void projectField(RowBuilder b, String fileKey, String ownerFqn, NodeRef owner, Field f) { + String id = ownerFqn + "#field#" + f.getName(); + NodeRef ref = b.node(Collections.singletonList("JField"), "id", id, RowBuilder.prune( + map("id", id, "name", f.getName(), "type", f.getType(), + "modifiers", strList(f.getModifiers()), "annotations", strList(f.getAnnotations()), + "variables", strList(f.getVariables()), + "variable_initializers_json", variableInitializersJson(f.getVariableInitializers()), + "start_line", asLong(f.getStartLine()), "end_line", asLong(f.getEndLine()), + "docstring", docstringOf(f.getComment()), "_module", fileKey))); + b.edge("J_HAS_FIELD", owner, ref); + for (String s : safe(f.getAnnotations())) { + annotate(b, ref, s); + } + projectComment(b, ref, f.getComment(), fileKey); + } + + private static void projectEnumConstant(RowBuilder b, String fileKey, String ownerFqn, NodeRef owner, + EnumConstant ec) { + String id = ownerFqn + "#enum#" + ec.getName(); + NodeRef ref = b.node(Collections.singletonList("JEnumConstant"), "id", id, RowBuilder.prune( + map("id", id, "name", ec.getName(), "arguments", strList(ec.getArguments()), "_module", fileKey))); + b.edge("J_HAS_ENUM_CONSTANT", owner, ref); + } + + private static void projectRecordComponent(RowBuilder b, String fileKey, String ownerFqn, NodeRef owner, + RecordComponent rc) { + String id = ownerFqn + "#rec#" + rc.getName(); + NodeRef ref = b.node(Collections.singletonList("JRecordComponent"), "id", id, RowBuilder.prune( + map("id", id, "name", rc.getName(), "type", rc.getType(), + "modifiers", strList(rc.getModifiers()), "annotations", strList(rc.getAnnotations()), + "default_value", rc.getDefaultValue() == null ? null : String.valueOf(rc.getDefaultValue()), + "is_var_args", rc.isVarArgs(), + "docstring", docstringOf(rc.getComment()), "_module", fileKey))); + b.edge("J_HAS_RECORD_COMPONENT", owner, ref); + projectComment(b, ref, rc.getComment(), fileKey); + } + + private static void annotate(RowBuilder b, NodeRef on, String annotation) { + if (annotation == null || annotation.isEmpty()) { + return; + } + NodeRef ann = b.node(Collections.singletonList("JAnnotation"), "name", annotation, map("name", annotation)); + b.edge("J_ANNOTATED_BY", on, ann); + } + + // ---------------------------------------------------------------------------------------------- + // Initialization blocks, CRUD, comments (first-class nodes — full IR fidelity) + // ---------------------------------------------------------------------------------------------- + + private static void projectInitializationBlock(RowBuilder b, String fileKey, String ownerFqn, NodeRef owner, + InitializationBlock ib, int index) { + if (ib == null) { + return; + } + String id = ownerFqn + "#init#" + index; + NodeRef ref = b.node(Collections.singletonList("JInitializationBlock"), "id", id, RowBuilder.prune( + map("id", id, "file_path", ib.getFilePath(), "code", ib.getCode(), + "annotations", strList(ib.getAnnotations()), "thrown_exceptions", strList(ib.getThrownExceptions()), + "referenced_types", strList(ib.getReferencedTypes()), "accessed_fields", strList(ib.getAccessedFields()), + "is_static", ib.isStatic(), "cyclomatic_complexity", asLong(ib.getCyclomaticComplexity()), + "start_line", asLong(ib.getStartLine()), "end_line", asLong(ib.getEndLine()), + "docstring", docstringOf(ib.getComments()), "_module", fileKey))); + b.edge("J_HAS_INIT_BLOCK", owner, ref); + projectComments(b, ref, ib.getComments(), fileKey); + for (CallSite cs : safe(ib.getCallSites())) { + projectCallSite(b, fileKey, id, ref, cs); + } + for (VariableDeclaration v : safe(ib.getVariableDeclarations())) { + projectVariable(b, fileKey, id, ref, v); + } + } + + private static void projectCrudOperation(RowBuilder b, String fileKey, String ownerId, NodeRef owner, + CRUDOperation op, int index) { + if (op == null) { + return; + } + String id = ownerId + "#crudop#" + index; + NodeRef ref = b.node(Collections.singletonList("JCrudOperation"), "id", id, RowBuilder.prune( + map("id", id, "line_number", asLong(op.getLineNumber()), + "operation_type", enumName(op.getOperationType()), + "target_table", op.getTargetTable(), "involved_columns", strList(op.getInvolvedColumns()), + "condition", op.getCondition(), "joined_tables", strList(op.getJoinedTables()), + "_module", fileKey))); + b.edge("J_HAS_CRUD_OPERATION", owner, ref); + } + + private static void projectCrudQuery(RowBuilder b, String fileKey, String ownerId, NodeRef owner, + CRUDQuery q, int index) { + if (q == null) { + return; + } + String id = ownerId + "#crudq#" + index; + NodeRef ref = b.node(Collections.singletonList("JCrudQuery"), "id", id, RowBuilder.prune( + map("id", id, "line_number", asLong(q.getLineNumber()), + "query_type", enumName(q.getQueryType()), + "query_arguments", strList(q.getQueryArguments()), "_module", fileKey))); + b.edge("J_HAS_CRUD_QUERY", owner, ref); + } + + private static void projectComments(RowBuilder b, NodeRef owner, List comments, String fileKey) { + for (Comment c : safe(comments)) { + projectComment(b, owner, c, fileKey); + } + } + + private static void projectComment(RowBuilder b, NodeRef owner, Comment c, String fileKey) { + if (c == null || c.getContent() == null) { + return; + } + String id = owner.value + "#comment@" + c.getStartLine() + ":" + c.getStartColumn() + + "-" + c.getEndLine() + ":" + c.getEndColumn(); + NodeRef ref = b.node(Collections.singletonList("JComment"), "id", id, RowBuilder.prune( + map("id", id, "content", c.getContent(), "is_javadoc", c.isJavadoc(), + "start_line", asLong(c.getStartLine()), "start_column", asLong(c.getStartColumn()), + "end_line", asLong(c.getEndLine()), "end_column", asLong(c.getEndColumn()), + "_module", fileKey))); + b.edge("J_HAS_COMMENT", owner, ref); + } + + // ---------------------------------------------------------------------------------------------- + // Call graph (level 2) + // ---------------------------------------------------------------------------------------------- + + private static void projectCallGraph(RowBuilder b, JsonArray callGraph) { + for (JsonElement el : callGraph) { + if (!el.isJsonObject()) { + continue; + } + JsonObject edge = el.getAsJsonObject(); + String from = vertexId(edge.getAsJsonObject("source")); + String to = vertexId(edge.getAsJsonObject("target")); + if (from == null || to == null) { + continue; + } + Map props = RowBuilder.prune(map( + "type", str(edge, "type"), + "weight", asLong(parseIntOrNull(str(edge, "weight"))), + "source_kind", str(edge, "source_kind"), + "destination_kind", str(edge, "destination_kind"))); + // Gated against the symbol table: kept only if both callables were emitted as nodes. + b.edgeIfBothResolved("J_CALLS", + new NodeRef("JSymbol", "id", from), new NodeRef("JSymbol", "id", to), props); + } + } + + private static String vertexId(JsonObject vertex) { + if (vertex == null) { + return null; + } + String typeDecl = str(vertex, "type_declaration"); + String signature = str(vertex, "signature"); + if (typeDecl == null || signature == null) { + return null; + } + return typeDecl + "#" + signature; + } + + // ---------------------------------------------------------------------------------------------- + // Property flattening + // ---------------------------------------------------------------------------------------------- + + private static Map compilationUnitProps(JavaCompilationUnit cu, String fileKey) { + int commentCount = cu.getComments() == null ? 0 : cu.getComments().size(); + return RowBuilder.prune(map( + "file_key", fileKey, + "file_path", cu.getFilePath(), + "package_name", cu.getPackageName(), + "content_hash", contentHash(cu), + "comment_count", (long) commentCount, + "is_modified", cu.isModified(), + "_module", fileKey)); + } + + private static Map typeProps(Type t, String fqn, String fileKey) { + Map p = map( + "id", fqn, + "name", simpleName(fqn), + "fqn", fqn, + "kind", typeKind(t), + "modifiers", strList(t.getModifiers()), + "annotations", strList(t.getAnnotations()), + "extends_list", strList(t.getExtendsList()), + "implements_list", strList(t.getImplementsList()), + "nested_type_declarations", strList(t.getNestedTypeDeclarations()), + "is_interface", t.isInterface(), + "is_nested_type", t.isNestedType(), + "is_inner_class", t.isInnerClass(), + "is_local_class", t.isLocalClass(), + "is_entrypoint_class", t.isEntrypointClass(), + "parent_type", t.getParentType(), + "docstring", docstringOf(t.getComments()), + "_module", fileKey); + return RowBuilder.prune(p); + } + + private static Map callableProps(Callable c, String id, String signature, String fileKey) { + Map p = map( + "id", id, + "name", callableName(signature), + "signature", signature, + "declaration", c.getDeclaration(), + "return_type", c.getReturnType(), + "modifiers", strList(c.getModifiers()), + "annotations", strList(c.getAnnotations()), + "thrown_exceptions", strList(c.getThrownExceptions()), + "parameter_types", parameterTypes(c), + "referenced_types", strList(c.getReferencedTypes()), + "accessed_fields", strList(c.getAccessedFields()), + "code", c.getCode(), + "code_start_line", asLong(c.getCodeStartLine()), + "start_line", asLong(c.getStartLine()), + "end_line", asLong(c.getEndLine()), + "cyclomatic_complexity", asLong(c.getCyclomaticComplexity()), + "is_constructor", c.isConstructor(), + "is_implicit", c.isImplicit(), + "is_entrypoint", c.isEntrypoint(), + "docstring", docstringOf(c.getComments()), + "_module", fileKey); + return RowBuilder.prune(p); + } + + // ---------------------------------------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------------------------------------- + + private static List symbolLabels(String specific, boolean entrypoint) { + if (entrypoint) { + return Arrays.asList("JSymbol", specific, "JEntrypoint"); + } + return Arrays.asList("JSymbol", specific); + } + + private static String typeKind(Type t) { + if (t.isInterface()) { + return "interface"; + } + if (t.isEnumDeclaration()) { + return "enum"; + } + if (t.isAnnotationDeclaration()) { + return "annotation"; + } + if (t.isRecordDeclaration()) { + return "record"; + } + return "class"; + } + + private static List parameterTypes(Callable c) { + if (c.getParameters() == null) { + return null; + } + return c.getParameters().stream().map(ParameterInCallable::getType).collect(Collectors.toList()); + } + + private static String docstringOf(List comments) { + if (comments == null) { + return null; + } + String joined = comments.stream().filter(Comment::isJavadoc).map(Comment::getContent) + .collect(Collectors.joining("\n")); + return joined.isEmpty() ? null : joined; + } + + private static String docstringOf(Comment comment) { + if (comment == null || !comment.isJavadoc()) { + return null; + } + return comment.getContent(); + } + + private static String simpleName(String fqn) { + int i = fqn.lastIndexOf('.'); + return i < 0 ? fqn : fqn.substring(i + 1); + } + + private static String callableName(String signature) { + int paren = signature.indexOf('('); + String head = paren < 0 ? signature : signature.substring(0, paren); + int dot = head.lastIndexOf('.'); + return dot < 0 ? head : head.substring(dot + 1); + } + + private static String packageOf(String fqImport) { + int i = fqImport.lastIndexOf('.'); + return i < 0 ? "" : fqImport.substring(0, i); + } + + private static String contentHash(JavaCompilationUnit cu) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] digest = md.digest(GSON.toJson(cu).getBytes(StandardCharsets.UTF_8)); + StringBuilder sb = new StringBuilder(digest.length * 2); + for (byte x : digest) { + sb.append(Character.forDigit((x >> 4) & 0xF, 16)).append(Character.forDigit(x & 0xF, 16)); + } + return sb.toString(); + } catch (Exception e) { + return null; + } + } + + private static List strList(List in) { + if (in == null || in.isEmpty()) { + return null; + } + return new ArrayList<>(in); + } + + private static List safe(List in) { + return in == null ? Collections.emptyList() : in; + } + + private static Long asLong(Integer i) { + return i == null ? null : i.longValue(); + } + + private static Long asLong(int i) { + return (long) i; + } + + /** Enum → its {@code name()} (via toString), or null. Avoids importing the CRUD enum types. */ + private static String enumName(Object e) { + return e == null ? null : e.toString(); + } + + /** Serialize a field's per-variable initializer map to a JSON string (Neo4j has no map type), or null. */ + private static String variableInitializersJson(Map initializers) { + if (initializers == null || initializers.isEmpty()) { + return null; + } + return GSON.toJson(initializers); + } + + private static String str(JsonObject o, String key) { + JsonElement el = o.get(key); + return el == null || el.isJsonNull() ? null : el.getAsString(); + } + + private static Integer parseIntOrNull(String s) { + if (s == null) { + return null; + } + try { + return Integer.valueOf(s.trim()); + } catch (NumberFormatException e) { + return null; + } + } + + /** Build an ordered props map from alternating key/value varargs. */ + private static Map map(Object... kv) { + Map m = RowBuilder.props(); + for (int i = 0; i + 1 < kv.length; i += 2) { + m.put((String) kv[i], kv[i + 1]); + } + return m; + } +} diff --git a/src/main/java/com/ibm/cldk/neo4j/GraphRows.java b/src/main/java/com/ibm/cldk/neo4j/GraphRows.java new file mode 100644 index 00000000..be1f796c --- /dev/null +++ b/src/main/java/com/ibm/cldk/neo4j/GraphRows.java @@ -0,0 +1,81 @@ +/* +Copyright IBM Corporation 2023, 2024 + +Licensed under the Apache Public License 2.0, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.ibm.cldk.neo4j; + +import java.util.List; +import java.util.Map; + +/** + * The output-agnostic intermediate between {@link GraphProjector} and the two writers + * ({@link CypherWriter} snapshot / {@link BoltWriter} incremental). Pure data — no I/O, no driver. + * A {@code GraphRows} is a deterministic, deduped bag of nodes and edges that both writers consume + * identically. + * + *

Property values are restricted to Neo4j-legal shapes: primitives ({@link String}, + * {@link Long}/{@link Integer}, {@link Boolean}) and homogeneous {@link List}s of primitives. + * {@code null} values are pruned (in Neo4j a null property simply means absence). + */ +public final class GraphRows { + + public final List nodes; + public final List edges; + + public GraphRows(List nodes, List edges) { + this.nodes = nodes; + this.edges = edges; + } + + /** How an edge addresses one of its endpoints: the label + key property to MATCH on, and value. */ + public static final class NodeRef { + /** The label carrying the uniqueness constraint (e.g. "JSymbol", "JCompilationUnit"). */ + public final String label; + /** "id" | "file_key" | "name". */ + public final String keyProp; + public final String value; + + public NodeRef(String label, String keyProp, String value) { + this.label = label; + this.keyProp = keyProp; + this.value = value; + } + } + + public static final class NodeRow { + /** labels[0] is the constrained MERGE label; the rest are SET as extra labels. */ + public final List labels; + public final String keyProp; + public final String value; + public final Map props; + + public NodeRow(List labels, String keyProp, String value, Map props) { + this.labels = labels; + this.keyProp = keyProp; + this.value = value; + this.props = props; + } + } + + public static final class EdgeRow { + public final String type; + public final NodeRef from; + public final NodeRef to; + public final Map props; + + public EdgeRow(String type, NodeRef from, NodeRef to, Map props) { + this.type = type; + this.from = from; + this.to = to; + this.props = props; + } + } +} diff --git a/src/main/java/com/ibm/cldk/neo4j/Neo4jEmitter.java b/src/main/java/com/ibm/cldk/neo4j/Neo4jEmitter.java new file mode 100644 index 00000000..c022baf7 --- /dev/null +++ b/src/main/java/com/ibm/cldk/neo4j/Neo4jEmitter.java @@ -0,0 +1,120 @@ +/* +Copyright IBM Corporation 2023, 2024 + +Licensed under the Apache Public License 2.0, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.ibm.cldk.neo4j; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.ibm.cldk.entities.JavaCompilationUnit; +import com.ibm.cldk.utils.Log; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; + +/** + * The Neo4j output facade. Two targets, mirroring codeanalyzer-typescript: + *

    + *
  • {@code neo4j}: project the IR to a graph. With {@code --neo4j-uri}, push incrementally to a + * live DB over Bolt; otherwise write a self-contained {@code graph.cypher} snapshot.
  • + *
  • {@code schema}: emit the Neo4j schema contract ({@code schema.neo4j.json}) — a static artifact + * derived from the in-repo catalog, independent of any analyzed project.
  • + *
+ */ +public final class Neo4jEmitter { + + private Neo4jEmitter() {} + + /** Emit the machine-readable schema contract. {@code output == null} prints to stdout. */ + public static void emitSchema(String output) throws IOException { + String doc = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create() + .toJson(SchemaCatalog.buildSchemaDocument()) + "\n"; + if (output == null) { + System.out.print(doc); + return; + } + Files.createDirectories(Paths.get(output)); + try (FileWriter w = new FileWriter(new File(output, "schema.neo4j.json"))) { + w.write(doc); + Log.done("Neo4j schema contract saved at " + output + File.separator + "schema.neo4j.json"); + } + } + + /** + * Project + emit the Neo4j graph. + * + * @param symbolTable the {@code symbol_table} map. + * @param callGraph the {@code call_graph} array (level 2), or {@code null}. + * @param appName logical application name (null ⇒ derived from input dir). + * @param input the analyzed project root (used to derive appName + the cypher output dir). + * @param output output directory (null ⇒ cwd for the snapshot). + * @param targetFiles non-null when an incremental/targeted run was requested. + * @param bolt non-null ⇒ push to a live DB over Bolt; null ⇒ write graph.cypher. + */ + public static void emit(Map symbolTable, JsonArray callGraph, String appName, + String input, String output, boolean targetedRun, BoltConfig bolt) throws IOException { + String name = appName != null ? appName : deriveAppName(input); + GraphRows rows = GraphProjector.project(symbolTable, callGraph, name); + + if (bolt != null) { + BoltSink sink = loadBoltSink(); + if (sink != null) { + Log.info("Pushing graph to Neo4j at " + bolt.uri); + sink.write(rows, bolt, !targetedRun); // full run ⇒ orphan pruning is safe + Log.done("Neo4j graph push complete (" + rows.nodes.size() + " nodes, " + + rows.edges.size() + " edges)"); + return; + } + // No Neo4j driver on the runtime (e.g. the GraalVM native image, which deliberately omits + // it). Degrade gracefully to a graph.cypher snapshot instead of failing the run. + Log.warn("Live Bolt push (--neo4j-uri) is unavailable in this binary — the Neo4j driver is " + + "not bundled in the native image. Writing graph.cypher instead; load it with " + + "`cypher-shell < graph.cypher`, or run the fat jar (java -jar codeanalyzer.jar) " + + "for a live incremental push."); + } + + String dir = output != null ? output : System.getProperty("user.dir"); + Files.createDirectories(Paths.get(dir)); + try (FileWriter w = new FileWriter(new File(dir, "graph.cypher"))) { + w.write(CypherWriter.renderCypher(rows, name)); + Log.done("Neo4j graph.cypher saved at " + dir + File.separator + "graph.cypher"); + } + } + + /** + * Load the live Bolt writer reflectively, or {@code null} if its Neo4j driver isn't on the + * runtime. The class name is assembled at runtime (not a compile-time constant) on purpose: it + * stops GraalVM native-image from folding this {@code Class.forName} and dragging {@code BoltWriter} + * — and with it the Neo4j driver + Netty — into the image. So the native binary has no driver and + * returns {@code null} here; the bundled fat jar resolves the class and returns a working sink. + */ + private static BoltSink loadBoltSink() { + try { + String className = String.join(".", "com", "ibm", "cldk", "neo4j", "Bolt".concat("Writer")); + return Class.forName(className).asSubclass(BoltSink.class).getDeclaredConstructor().newInstance(); + } catch (Throwable t) { + return null; + } + } + + private static String deriveAppName(String input) { + if (input == null) { + return "application"; + } + Path p = Paths.get(input).toAbsolutePath().normalize(); + Path fileName = p.getFileName(); + return fileName != null ? fileName.toString() : "application"; + } +} diff --git a/src/main/java/com/ibm/cldk/neo4j/RowBuilder.java b/src/main/java/com/ibm/cldk/neo4j/RowBuilder.java new file mode 100644 index 00000000..29b30da1 --- /dev/null +++ b/src/main/java/com/ibm/cldk/neo4j/RowBuilder.java @@ -0,0 +1,138 @@ +/* +Copyright IBM Corporation 2023, 2024 + +Licensed under the Apache Public License 2.0, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.ibm.cldk.neo4j; + +import com.ibm.cldk.neo4j.GraphRows.EdgeRow; +import com.ibm.cldk.neo4j.GraphRows.NodeRef; +import com.ibm.cldk.neo4j.GraphRows.NodeRow; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Accumulates nodes/edges with {@code MERGE} semantics in memory, so the same node touched many + * times (a hot annotation, a canonical package) collapses to one row, and cross-reference edges to + * a target that never materialized are dropped (the "edge-only-when-resolved" rule). + * + *

This is the in-memory analog of {@code MERGE (n:Label {key}) SET n += props}: re-seeing the + * same (mergeLabel, value) merges props (last write wins) and unions labels. + */ +public final class RowBuilder { + + /** key: {@code labels[0] + " " + value}. */ + private final Map nodes = new LinkedHashMap<>(); + private final List edges = new ArrayList<>(); + /** Edges gated against node existence at {@link #finish()}. */ + private final List deferred = new ArrayList<>(); + /** Every node value seen, for resolved-gating. */ + private final Set keys = new HashSet<>(); + + /** Convenience: a new mutable props map. */ + public static Map props() { + return new LinkedHashMap<>(); + } + + /** + * Drop {@code null} entries — in Neo4j a null property means "absent", so we never store one. + * Empty collections are also dropped (Neo4j cannot store an empty typed list cleanly). + */ + public static Map prune(Map in) { + Map out = new LinkedHashMap<>(); + for (Map.Entry e : in.entrySet()) { + Object v = e.getValue(); + if (v == null) { + continue; + } + if (v instanceof List && ((List) v).isEmpty()) { + continue; + } + out.put(e.getKey(), v); + } + return out; + } + + /** + * Upsert a node. Re-seeing the same {@code (labels[0], value)} merges props (last write wins) + * and unions labels. + */ + public NodeRef node(List labels, String keyProp, String value, Map props) { + String id = labels.get(0) + " " + value; + NodeRow existing = nodes.get(id); + if (existing != null) { + existing.props.putAll(props); + for (String l : labels) { + if (!existing.labels.contains(l)) { + existing.labels.add(l); + } + } + } else { + nodes.put(id, new NodeRow(new ArrayList<>(labels), keyProp, value, new LinkedHashMap<>(props))); + } + keys.add(value); + return new NodeRef(labels.get(0), keyProp, value); + } + + /** An edge whose endpoints are known to exist (both ends emitted as nodes this run). */ + public void edge(String type, NodeRef from, NodeRef to, Map props) { + edges.add(new EdgeRow(type, from, to, props)); + } + + public void edge(String type, NodeRef from, NodeRef to) { + edges.add(new EdgeRow(type, from, to, RowBuilder.props())); + } + + /** + * An edge to a {@code :JSymbol} target that may be external/library code not present in the + * graph. Deferred and kept only if the target id was actually emitted as a node — so J_EXTENDS / + * J_IMPLEMENTS / J_RESOLVES_TO / J_CALLS never dangle. + */ + public void edgeToSymbol(String type, NodeRef from, String targetId, Map props) { + deferred.add(new EdgeRow(type, from, new NodeRef("JSymbol", "id", targetId), props)); + } + + public void edgeToSymbol(String type, NodeRef from, String targetId) { + edgeToSymbol(type, from, targetId, RowBuilder.props()); + } + + /** An edge kept only if BOTH endpoints were emitted as nodes (used for CALLS). */ + public void edgeIfBothResolved(String type, NodeRef from, NodeRef to, Map props) { + deferred.add(new EdgeRow(type, from, to, props)); + } + + public GraphRows finish() { + for (EdgeRow e : deferred) { + if (keys.contains(e.from.value) && keys.contains(e.to.value)) { + edges.add(e); + } + } + // Dedupe edges the way Neo4j's MERGE would: one relationship per + // (type, source, target), last-write-wins on props (mirrors `MERGE (a)-[r]->(b) SET r += p`). + Map uniqueEdges = new LinkedHashMap<>(); + for (EdgeRow e : edges) { + uniqueEdges.put(e.type + "|" + e.from.label + ":" + e.from.value + + "|" + e.to.label + ":" + e.to.value, e); + } + + List nodeList = new ArrayList<>(nodes.values()); + nodeList.sort((a, b) -> + (a.labels.get(0) + " " + a.value).compareTo(b.labels.get(0) + " " + b.value)); + List edgeList = new ArrayList<>(uniqueEdges.values()); + edgeList.sort((a, b) -> + (a.type + " " + a.from.value + " " + a.to.value) + .compareTo(b.type + " " + b.from.value + " " + b.to.value)); + return new GraphRows(nodeList, edgeList); + } +} diff --git a/src/main/java/com/ibm/cldk/neo4j/Schema.java b/src/main/java/com/ibm/cldk/neo4j/Schema.java new file mode 100644 index 00000000..5e260ccb --- /dev/null +++ b/src/main/java/com/ibm/cldk/neo4j/Schema.java @@ -0,0 +1,49 @@ +/* +Copyright IBM Corporation 2023, 2024 + +Licensed under the Apache Public License 2.0, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.ibm.cldk.neo4j; + +import java.util.Arrays; +import java.util.List; + +/** + * The Cypher DDL — uniqueness constraints and indexes — shared by both writers. Run BEFORE any + * load so MERGE uses an index seek (not a label scan) and the identity invariant is enforced by the + * database. Every statement is idempotent ({@code IF NOT EXISTS}). + */ +public final class Schema { + + private Schema() {} + + public static final List CONSTRAINTS = Arrays.asList( + "CREATE CONSTRAINT j_symbol_id IF NOT EXISTS FOR (s:JSymbol) REQUIRE s.id IS UNIQUE", + "CREATE CONSTRAINT j_application_name IF NOT EXISTS FOR (a:JApplication) REQUIRE a.name IS UNIQUE", + "CREATE CONSTRAINT j_compilation_unit_key IF NOT EXISTS FOR (c:JCompilationUnit) REQUIRE c.file_key IS UNIQUE", + "CREATE CONSTRAINT j_package_name IF NOT EXISTS FOR (p:JPackage) REQUIRE p.name IS UNIQUE", + "CREATE CONSTRAINT j_annotation_name IF NOT EXISTS FOR (an:JAnnotation) REQUIRE an.name IS UNIQUE", + "CREATE CONSTRAINT j_callsite_id IF NOT EXISTS FOR (cs:JCallSite) REQUIRE cs.id IS UNIQUE", + "CREATE CONSTRAINT j_field_id IF NOT EXISTS FOR (f:JField) REQUIRE f.id IS UNIQUE", + "CREATE CONSTRAINT j_parameter_id IF NOT EXISTS FOR (p:JParameter) REQUIRE p.id IS UNIQUE", + "CREATE CONSTRAINT j_variable_id IF NOT EXISTS FOR (v:JVariable) REQUIRE v.id IS UNIQUE", + "CREATE CONSTRAINT j_enum_constant_id IF NOT EXISTS FOR (e:JEnumConstant) REQUIRE e.id IS UNIQUE", + "CREATE CONSTRAINT j_record_component_id IF NOT EXISTS FOR (r:JRecordComponent) REQUIRE r.id IS UNIQUE", + "CREATE CONSTRAINT j_init_block_id IF NOT EXISTS FOR (ib:JInitializationBlock) REQUIRE ib.id IS UNIQUE", + "CREATE CONSTRAINT j_crud_operation_id IF NOT EXISTS FOR (co:JCrudOperation) REQUIRE co.id IS UNIQUE", + "CREATE CONSTRAINT j_crud_query_id IF NOT EXISTS FOR (cq:JCrudQuery) REQUIRE cq.id IS UNIQUE", + "CREATE CONSTRAINT j_comment_id IF NOT EXISTS FOR (cm:JComment) REQUIRE cm.id IS UNIQUE"); + + public static final List INDEXES = Arrays.asList( + "CREATE INDEX j_callable_name IF NOT EXISTS FOR (c:JCallable) ON (c.name)", + "CREATE INDEX j_type_name IF NOT EXISTS FOR (t:JType) ON (t.name)", + "CREATE INDEX j_annotation_name_idx IF NOT EXISTS FOR (an:JAnnotation) ON (an.name)", + "CREATE FULLTEXT INDEX j_code_fts IF NOT EXISTS FOR (c:JCallable) ON EACH [c.code, c.docstring]"); +} diff --git a/src/main/java/com/ibm/cldk/neo4j/SchemaCatalog.java b/src/main/java/com/ibm/cldk/neo4j/SchemaCatalog.java new file mode 100644 index 00000000..668dab1e --- /dev/null +++ b/src/main/java/com/ibm/cldk/neo4j/SchemaCatalog.java @@ -0,0 +1,269 @@ +/* +Copyright IBM Corporation 2023, 2024 + +Licensed under the Apache Public License 2.0, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.ibm.cldk.neo4j; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * The declarative Neo4j schema catalog — the single in-repo source of truth for the graph contract + * (node labels, their keys and typed properties, relationship types and their endpoints). + * {@code --emit schema} serializes this (with the DDL from {@link Schema}) to a machine-readable + * {@code schema.json}, and the conformance test asserts the real projector never produces a label / + * relationship / property that isn't declared here — so this file cannot silently drift from + * {@link GraphProjector}. + * + *

{@code SCHEMA_VERSION} is the contract version: bump MAJOR on a breaking change (renamed/removed + * label, relationship or key), MINOR on an additive change (new label/rel/property). It is stamped + * onto the {@code :JApplication} node of every emitted graph so any consumer can detect a + * producer/consumer mismatch at runtime. + */ +public final class SchemaCatalog { + + private SchemaCatalog() {} + + public static final String SCHEMA_VERSION = "1.0.0"; + + /** Labels layered onto a node in addition to its primary/specific label. */ + public static final List MARKER_LABELS = Arrays.asList("JEntrypoint"); + + public static final class NodeLabel { + public final String label; + public final String mergeLabel; + public final String key; + public final Map properties; + + NodeLabel(String label, String mergeLabel, String key, Map properties) { + this.label = label; + this.mergeLabel = mergeLabel; + this.key = key; + this.properties = properties; + } + } + + public static final class RelType { + public final String type; + public final List from; + public final List to; + public final Map properties; + + RelType(String type, List from, List to, Map properties) { + this.type = type; + this.from = from; + this.to = to; + this.properties = properties; + } + } + + /** Tiny ordered-map builder for property declarations. */ + private static final class P { + private final Map m = new LinkedHashMap<>(); + + P put(String k, String v) { + m.put(k, v); + return this; + } + + Map done() { + return m; + } + } + + private static Map span(P p) { + return p.put("start_line", "integer").put("end_line", "integer").done(); + } + + public static final List NODE_LABELS = buildNodeLabels(); + public static final List REL_TYPES = buildRelTypes(); + + private static List buildNodeLabels() { + List n = new ArrayList<>(); + + n.add(new NodeLabel("JApplication", "JApplication", "name", + new P().put("name", "string").put("schema_version", "string").done())); + + n.add(new NodeLabel("JCompilationUnit", "JCompilationUnit", "file_key", + new P().put("file_key", "string").put("file_path", "string").put("package_name", "string") + .put("content_hash", "string").put("comment_count", "integer").put("is_modified", "boolean") + .put("_module", "string").done())); + + n.add(new NodeLabel("JType", "JSymbol", "id", + new P().put("id", "string").put("name", "string").put("fqn", "string").put("kind", "string") + .put("modifiers", "string[]").put("annotations", "string[]") + .put("extends_list", "string[]").put("implements_list", "string[]") + .put("nested_type_declarations", "string[]") + .put("is_interface", "boolean").put("is_nested_type", "boolean") + .put("is_inner_class", "boolean").put("is_local_class", "boolean") + .put("is_entrypoint_class", "boolean").put("parent_type", "string") + .put("docstring", "string").put("_module", "string").done())); + + n.add(new NodeLabel("JCallable", "JSymbol", "id", + new P().put("id", "string").put("name", "string").put("signature", "string") + .put("file_path", "string") + .put("declaration", "string").put("return_type", "string") + .put("modifiers", "string[]").put("annotations", "string[]") + .put("thrown_exceptions", "string[]").put("parameter_types", "string[]") + .put("referenced_types", "string[]").put("accessed_fields", "string[]") + .put("code", "string").put("code_start_line", "integer") + .put("start_line", "integer").put("end_line", "integer") + .put("cyclomatic_complexity", "integer") + .put("is_constructor", "boolean").put("is_implicit", "boolean") + .put("is_entrypoint", "boolean").put("docstring", "string").put("_module", "string").done())); + + n.add(new NodeLabel("JField", "JField", "id", + new P().put("id", "string").put("name", "string").put("type", "string") + .put("modifiers", "string[]").put("annotations", "string[]").put("variables", "string[]") + .put("variable_initializers_json", "string") + .put("start_line", "integer").put("end_line", "integer") + .put("docstring", "string").put("_module", "string").done())); + + n.add(new NodeLabel("JParameter", "JParameter", "id", + new P().put("id", "string").put("name", "string").put("type", "string") + .put("annotations", "string[]").put("modifiers", "string[]") + .put("start_line", "integer").put("end_line", "integer") + .put("start_column", "integer").put("end_column", "integer") + .put("_module", "string").done())); + + n.add(new NodeLabel("JVariable", "JVariable", "id", + new P().put("id", "string").put("name", "string").put("type", "string") + .put("initializer", "string").put("start_line", "integer").put("end_line", "integer") + .put("start_column", "integer").put("end_column", "integer") + .put("docstring", "string").put("_module", "string").done())); + + n.add(new NodeLabel("JCallSite", "JCallSite", "id", + new P().put("id", "string").put("method_name", "string").put("receiver_expr", "string") + .put("receiver_type", "string").put("return_type", "string") + .put("callee_signature", "string").put("argument_types", "string[]") + .put("argument_expr", "string[]") + .put("is_static_call", "boolean").put("is_constructor_call", "boolean") + .put("is_public", "boolean").put("is_private", "boolean").put("is_protected", "boolean") + .put("is_unspecified", "boolean") + .put("start_line", "integer").put("start_column", "integer") + .put("end_line", "integer").put("end_column", "integer") + .put("docstring", "string").put("_module", "string").done())); + + n.add(new NodeLabel("JEnumConstant", "JEnumConstant", "id", + new P().put("id", "string").put("name", "string").put("arguments", "string[]") + .put("_module", "string").done())); + + n.add(new NodeLabel("JRecordComponent", "JRecordComponent", "id", + new P().put("id", "string").put("name", "string").put("type", "string") + .put("modifiers", "string[]").put("annotations", "string[]") + .put("default_value", "string").put("is_var_args", "boolean") + .put("docstring", "string").put("_module", "string").done())); + + n.add(new NodeLabel("JInitializationBlock", "JInitializationBlock", "id", + new P().put("id", "string").put("file_path", "string").put("code", "string") + .put("annotations", "string[]").put("thrown_exceptions", "string[]") + .put("referenced_types", "string[]").put("accessed_fields", "string[]") + .put("is_static", "boolean").put("cyclomatic_complexity", "integer") + .put("start_line", "integer").put("end_line", "integer") + .put("docstring", "string").put("_module", "string").done())); + + n.add(new NodeLabel("JCrudOperation", "JCrudOperation", "id", + new P().put("id", "string").put("line_number", "integer").put("operation_type", "string") + .put("target_table", "string").put("involved_columns", "string[]") + .put("condition", "string").put("joined_tables", "string[]") + .put("_module", "string").done())); + + n.add(new NodeLabel("JCrudQuery", "JCrudQuery", "id", + new P().put("id", "string").put("line_number", "integer").put("query_type", "string") + .put("query_arguments", "string[]").put("_module", "string").done())); + + n.add(new NodeLabel("JComment", "JComment", "id", + new P().put("id", "string").put("content", "string").put("is_javadoc", "boolean") + .put("start_line", "integer").put("start_column", "integer") + .put("end_line", "integer").put("end_column", "integer").put("_module", "string").done())); + + n.add(new NodeLabel("JPackage", "JPackage", "name", new P().put("name", "string").done())); + + n.add(new NodeLabel("JAnnotation", "JAnnotation", "name", new P().put("name", "string").done())); + + return n; + } + + private static List buildRelTypes() { + List r = new ArrayList<>(); + Map none = new LinkedHashMap<>(); + + r.add(new RelType("J_HAS_UNIT", Arrays.asList("JApplication"), Arrays.asList("JCompilationUnit"), none)); + r.add(new RelType("J_DECLARES_TYPE", Arrays.asList("JCompilationUnit"), Arrays.asList("JType"), none)); + r.add(new RelType("J_HAS_NESTED_TYPE", Arrays.asList("JType"), Arrays.asList("JType"), none)); + r.add(new RelType("J_HAS_CALLABLE", Arrays.asList("JType"), Arrays.asList("JCallable"), none)); + r.add(new RelType("J_HAS_FIELD", Arrays.asList("JType"), Arrays.asList("JField"), none)); + r.add(new RelType("J_HAS_PARAMETER", Arrays.asList("JCallable"), Arrays.asList("JParameter"), none)); + r.add(new RelType("J_HAS_CALLSITE", Arrays.asList("JCallable", "JInitializationBlock"), + Arrays.asList("JCallSite"), none)); + r.add(new RelType("J_DECLARES_VAR", Arrays.asList("JCallable", "JInitializationBlock"), + Arrays.asList("JVariable"), none)); + r.add(new RelType("J_HAS_ENUM_CONSTANT", Arrays.asList("JType"), Arrays.asList("JEnumConstant"), none)); + r.add(new RelType("J_HAS_RECORD_COMPONENT", Arrays.asList("JType"), Arrays.asList("JRecordComponent"), none)); + r.add(new RelType("J_HAS_INIT_BLOCK", Arrays.asList("JType"), Arrays.asList("JInitializationBlock"), none)); + r.add(new RelType("J_EXTENDS", Arrays.asList("JType"), Arrays.asList("JType"), none)); + r.add(new RelType("J_IMPLEMENTS", Arrays.asList("JType"), Arrays.asList("JType"), none)); + r.add(new RelType("J_ANNOTATED_BY", Arrays.asList("JType", "JCallable", "JField"), Arrays.asList("JAnnotation"), none)); + r.add(new RelType("J_IMPORTS", Arrays.asList("JCompilationUnit"), Arrays.asList("JType", "JPackage"), + new P().put("is_static", "boolean").put("is_wildcard", "boolean").done())); + r.add(new RelType("J_RESOLVES_TO", Arrays.asList("JCallSite"), Arrays.asList("JCallable"), none)); + r.add(new RelType("J_CALLS", Arrays.asList("JCallable"), Arrays.asList("JCallable"), + new P().put("type", "string").put("weight", "integer") + .put("source_kind", "string").put("destination_kind", "string").done())); + r.add(new RelType("J_HAS_CRUD_OPERATION", Arrays.asList("JCallable", "JCallSite"), + Arrays.asList("JCrudOperation"), none)); + r.add(new RelType("J_HAS_CRUD_QUERY", Arrays.asList("JCallable", "JCallSite"), + Arrays.asList("JCrudQuery"), none)); + r.add(new RelType("J_HAS_COMMENT", + Arrays.asList("JCompilationUnit", "JType", "JCallable", "JField", "JCallSite", "JVariable", + "JRecordComponent", "JInitializationBlock"), + Arrays.asList("JComment"), none)); + + return r; + } + + /** Build the full machine-readable schema document emitted by {@code --emit schema}. */ + public static Map buildSchemaDocument() { + Map doc = new LinkedHashMap<>(); + doc.put("schema_version", SCHEMA_VERSION); + doc.put("generator", "codeanalyzer-java"); + doc.put("marker_labels", MARKER_LABELS); + + List> nodeLabels = new ArrayList<>(); + for (NodeLabel nl : NODE_LABELS) { + Map m = new LinkedHashMap<>(); + m.put("label", nl.label); + m.put("merge_label", nl.mergeLabel); + m.put("key", nl.key); + m.put("properties", nl.properties); + nodeLabels.add(m); + } + doc.put("node_labels", nodeLabels); + + List> relTypes = new ArrayList<>(); + for (RelType rt : REL_TYPES) { + Map m = new LinkedHashMap<>(); + m.put("type", rt.type); + m.put("from", rt.from); + m.put("to", rt.to); + m.put("properties", rt.properties); + relTypes.add(m); + } + doc.put("relationship_types", relTypes); + + doc.put("constraints", Schema.CONSTRAINTS); + doc.put("indexes", Schema.INDEXES); + return doc; + } +} diff --git a/src/main/resources/META-INF/native-image-config/jni-config.json b/src/main/resources/META-INF/native-image-config/jni-config.json index 8b4e4170..4a0be2d8 100644 --- a/src/main/resources/META-INF/native-image-config/jni-config.json +++ b/src/main/resources/META-INF/native-image-config/jni-config.json @@ -1,6 +1,18 @@ [ +{ + "name":"com.ibm.cldk.CodeAnalyzer", + "methods":[{"name":"main","parameterTypes":["java.lang.String[]"] }] +}, { "name":"java.lang.Boolean", "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.lang.String", + "methods":[{"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }] +}, +{ + "name":"java.lang.System", + "methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }] } ] diff --git a/src/main/resources/META-INF/native-image-config/reflect-config.json b/src/main/resources/META-INF/native-image-config/reflect-config.json index 2c027f53..e5f6b484 100644 --- a/src/main/resources/META-INF/native-image-config/reflect-config.json +++ b/src/main/resources/META-INF/native-image-config/reflect-config.json @@ -1,240 +1,3286 @@ [ -{ - "name":"[B" -}, -{ - "name":"[Ljava.lang.String;" -}, -{ - "name":"[Lsun.security.pkcs.SignerInfo;" -}, -{ - "name":"com.ibm.northstar.CodeAnalyzer", - "allDeclaredFields":true, - "queryAllDeclaredMethods":true, - "queryAllPublicMethods":true -}, -{ - "name":"com.ibm.northstar.entities.Callable", - "allDeclaredFields":true, - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"com.ibm.northstar.entities.ClassOrInterface", - "allDeclaredFields":true, - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"com.ibm.northstar.entities.Field", - "allDeclaredFields":true, - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"com.ibm.northstar.entities.JavaCompilationUnit", - "allDeclaredFields":true, - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"com.ibm.northstar.entities.ParameterInCallable", - "allDeclaredFields":true, - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"com.ibm.northstar.entities.Type", - "allDeclaredFields":true, - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"java.lang.Class", - "methods":[{"name":"getRecordComponents","parameterTypes":[] }, {"name":"isRecord","parameterTypes":[] }] -}, -{ - "name":"java.lang.Object", - "allDeclaredFields":true, - "queryAllDeclaredMethods":true -}, -{ - "name":"java.lang.String" -}, -{ - "name":"java.lang.reflect.RecordComponent", - "methods":[{"name":"getName","parameterTypes":[] }, {"name":"getType","parameterTypes":[] }] -}, -{ - "name":"java.nio.file.Path" -}, -{ - "name":"java.nio.file.Paths", - "methods":[{"name":"get","parameterTypes":["java.lang.String","java.lang.String[]"] }] -}, -{ - "name":"java.security.interfaces.RSAPrivateKey" -}, -{ - "name":"java.security.interfaces.RSAPublicKey" -}, -{ - "name":"java.sql.Connection" -}, -{ - "name":"java.sql.Date" -}, -{ - "name":"java.sql.Driver" -}, -{ - "name":"java.sql.DriverManager", - "methods":[{"name":"getConnection","parameterTypes":["java.lang.String"] }, {"name":"getDriver","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.sql.Time", - "methods":[{"name":"","parameterTypes":["long"] }] -}, -{ - "name":"java.sql.Timestamp", - "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.time.Duration", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.Instant", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.LocalDate", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.LocalDateTime", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.LocalTime", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.MonthDay", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.OffsetDateTime", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.OffsetTime", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.Period", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.Year", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.YearMonth", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.ZoneId", - "methods":[{"name":"of","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.time.ZoneOffset", - "methods":[{"name":"of","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.time.ZonedDateTime", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.util.Date" -}, -{ - "name":"java.util.HashMap", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"java.util.LinkedHashMap", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"java.util.concurrent.atomic.AtomicBoolean", - "fields":[{"name":"value"}] -}, -{ - "name":"javax.security.auth.x500.X500Principal", - "fields":[{"name":"thisX500Name"}], - "methods":[{"name":"","parameterTypes":["sun.security.x509.X500Name"] }] -}, -{ - "name":"picocli.CommandLine$AutoHelpMixin", - "allDeclaredFields":true, - "queryAllDeclaredMethods":true -}, -{ - "name":"sun.security.provider.SHA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.SHA2$SHA256", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.X509Factory", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.rsa.RSAKeyFactory$Legacy", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.rsa.RSASignature$SHA256withRSA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.util.ObjectIdentifier" -}, -{ - "name":"sun.security.x509.AuthorityInfoAccessExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.AuthorityKeyIdentifierExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.BasicConstraintsExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.CRLDistributionPointsExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.CertificateExtensions" -}, -{ - "name":"sun.security.x509.CertificatePoliciesExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.ExtendedKeyUsageExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.KeyUsageExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.SubjectAlternativeNameExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.SubjectKeyIdentifierExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -} + { + "name": "ArrayUtils" + }, + { + "name": "BitMapExtractor" + }, + { + "name": "Boolean" + }, + { + "name": "CharSequence" + }, + { + "name": "IllegalArgumentException" + }, + { + "name": "IndexOutOfBoundsException" + }, + { + "name": "IndexUtils" + }, + { + "name": "Integer" + }, + { + "name": "NullPointerException" + }, + { + "name": "Object" + }, + { + "name": "Predicate" + }, + { + "name": "String" + }, + { + "name": "TestCase" + }, + { + "name": "Thread" + }, + { + "name": "WeakHashtable" + }, + { + "name": "[B" + }, + { + "name": "[Ljava.lang.String;" + }, + { + "name": "[Lsun.security.pkcs.SignerInfo;" + }, + { + "name": "array" + }, + { + "name": "bitMapExtractor" + }, + { + "name": "bitSet" + }, + { + "name": "chars" + }, + { + "name": "collection" + }, + { + "name": "com.github.javaparser.ast.AccessSpecifier", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.AllFieldsConstructor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.ArrayCreationLevel", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.CompilationUnit", + "allDeclaredFields": true, + "methods": [ + { + "name": "getImports", + "parameterTypes": [] + }, + { + "name": "getModule", + "parameterTypes": [] + }, + { + "name": "getPackageDeclaration", + "parameterTypes": [] + }, + { + "name": "getTypes", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.CompilationUnit$Storage", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.DataKey", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.Generated", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.ImportDeclaration", + "methods": [ + { + "name": "getAsterisk", + "parameterTypes": [] + }, + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getStatic", + "parameterTypes": [] + }, + { + "name": "isAsterisk", + "parameterTypes": [] + }, + { + "name": "isStatic", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.Modifier", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.Modifier$Keyword", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.Node", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.Node$BreadthFirstIterator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.Node$DirectChildrenIterator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.Node$ObserverRegistrationMode", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.Node$ParentsVisitor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.Node$Parsedness", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.Node$PostOrderIterator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.Node$PostOrderIterator$Level", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.Node$PreOrderIterator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.Node$TreeTraversal", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.NodeList", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.NodeList$NodeListIterator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.PackageDeclaration", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.AnnotationDeclaration", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.AnnotationMemberDeclaration", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.BodyDeclaration", + "allDeclaredFields": true, + "methods": [ + { + "name": "getAnnotations", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.CallableDeclaration", + "allDeclaredFields": true, + "methods": [ + { + "name": "getModifiers", + "parameterTypes": [] + }, + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getParameters", + "parameterTypes": [] + }, + { + "name": "getReceiverParameter", + "parameterTypes": [] + }, + { + "name": "getThrownExceptions", + "parameterTypes": [] + }, + { + "name": "getTypeParameters", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.CallableDeclaration$Signature", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.ClassOrInterfaceDeclaration", + "allDeclaredFields": true, + "methods": [ + { + "name": "getExtendedTypes", + "parameterTypes": [] + }, + { + "name": "getImplementedTypes", + "parameterTypes": [] + }, + { + "name": "getInterface", + "parameterTypes": [] + }, + { + "name": "getPermittedTypes", + "parameterTypes": [] + }, + { + "name": "getTypeParameters", + "parameterTypes": [] + }, + { + "name": "isInterface", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.CompactConstructorDeclaration", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.ConstructorDeclaration", + "allDeclaredFields": true, + "methods": [ + { + "name": "getBody", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.EnumConstantDeclaration", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.EnumDeclaration", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.FieldDeclaration", + "allDeclaredFields": true, + "methods": [ + { + "name": "getModifiers", + "parameterTypes": [] + }, + { + "name": "getVariables", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.InitializerDeclaration", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.MethodDeclaration", + "allDeclaredFields": true, + "methods": [ + { + "name": "getBody", + "parameterTypes": [] + }, + { + "name": "getType", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.Parameter", + "allDeclaredFields": true, + "methods": [ + { + "name": "getAnnotations", + "parameterTypes": [] + }, + { + "name": "getModifiers", + "parameterTypes": [] + }, + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getType", + "parameterTypes": [] + }, + { + "name": "getVarArgs", + "parameterTypes": [] + }, + { + "name": "getVarArgsAnnotations", + "parameterTypes": [] + }, + { + "name": "isVarArgs", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.ReceiverParameter", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.RecordDeclaration", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.TypeDeclaration", + "allDeclaredFields": true, + "methods": [ + { + "name": "getMembers", + "parameterTypes": [] + }, + { + "name": "getModifiers", + "parameterTypes": [] + }, + { + "name": "getName", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.body.VariableDeclarator", + "methods": [ + { + "name": "getInitializer", + "parameterTypes": [] + }, + { + "name": "getName", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.comments.BlockComment", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.comments.Comment", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.comments.CommentsCollection", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.comments.JavadocComment", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.comments.LineComment", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.AnnotationExpr", + "methods": [ + { + "name": "getName", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.ArrayAccessExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.ArrayCreationExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.ArrayInitializerExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.AssignExpr", + "methods": [ + { + "name": "getOperator", + "parameterTypes": [] + }, + { + "name": "getTarget", + "parameterTypes": [] + }, + { + "name": "getValue", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.AssignExpr$Operator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.BinaryExpr", + "methods": [ + { + "name": "getLeft", + "parameterTypes": [] + }, + { + "name": "getOperator", + "parameterTypes": [] + }, + { + "name": "getRight", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.BinaryExpr$Operator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.BooleanLiteralExpr", + "methods": [ + { + "name": "getValue", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.CastExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.CharLiteralExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.ClassExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.ConditionalExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.DoubleLiteralExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.EnclosedExpr", + "methods": [ + { + "name": "getInner", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.Expression", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.FieldAccessExpr", + "allDeclaredFields": true, + "methods": [ + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getScope", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.InstanceOfExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.IntegerLiteralExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.LambdaExpr", + "allDeclaredFields": true, + "methods": [ + { + "name": "getBody", + "parameterTypes": [] + }, + { + "name": "getEnclosingParameters", + "parameterTypes": [] + }, + { + "name": "getExpressionBody", + "parameterTypes": [] + }, + { + "name": "getParameters", + "parameterTypes": [] + }, + { + "name": "isEnclosingParameters", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.LiteralExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.LiteralStringValueExpr", + "methods": [ + { + "name": "getValue", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.LongLiteralExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.MarkerAnnotationExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.MemberValuePair", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.MethodCallExpr", + "allDeclaredFields": true, + "methods": [ + { + "name": "getArguments", + "parameterTypes": [] + }, + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getScope", + "parameterTypes": [] + }, + { + "name": "getTypeArguments", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.MethodReferenceExpr", + "methods": [ + { + "name": "getIdentifier", + "parameterTypes": [] + }, + { + "name": "getScope", + "parameterTypes": [] + }, + { + "name": "getTypeArguments", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.Name", + "methods": [ + { + "name": "getIdentifier", + "parameterTypes": [] + }, + { + "name": "getQualifier", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.NameExpr", + "methods": [ + { + "name": "getName", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.NormalAnnotationExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.NullLiteralExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.ObjectCreationExpr", + "allDeclaredFields": true, + "methods": [ + { + "name": "getAnonymousClassBody", + "parameterTypes": [] + }, + { + "name": "getArguments", + "parameterTypes": [] + }, + { + "name": "getScope", + "parameterTypes": [] + }, + { + "name": "getType", + "parameterTypes": [] + }, + { + "name": "getTypeArguments", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.PatternExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.RecordPatternExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.SimpleName", + "methods": [ + { + "name": "getIdentifier", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.SingleMemberAnnotationExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.StringLiteralExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.SuperExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.SwitchExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.TextBlockLiteralExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.ThisExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.TypeExpr", + "methods": [ + { + "name": "getType", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.TypePatternExpr", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.UnaryExpr", + "methods": [ + { + "name": "getExpression", + "parameterTypes": [] + }, + { + "name": "getOperator", + "parameterTypes": [] + }, + { + "name": "getPostfix", + "parameterTypes": [] + }, + { + "name": "getPrefix", + "parameterTypes": [] + }, + { + "name": "isPostfix", + "parameterTypes": [] + }, + { + "name": "isPrefix", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.UnaryExpr$Operator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.expr.VariableDeclarationExpr", + "allDeclaredFields": true, + "methods": [ + { + "name": "getAnnotations", + "parameterTypes": [] + }, + { + "name": "getModifiers", + "parameterTypes": [] + }, + { + "name": "getVariables", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.modules.ModuleDeclaration", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.modules.ModuleDirective", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.modules.ModuleExportsDirective", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.modules.ModuleOpensDirective", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.modules.ModuleProvidesDirective", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.modules.ModuleRequiresDirective", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.modules.ModuleUsesDirective", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithAnnotations", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithArguments", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithBlockStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithBody", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithCondition", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithDeclaration", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithExpression", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithExtends", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithIdentifier", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithImplements", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithJavadoc", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithMembers", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithModifiers", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithName", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithOptionalBlockStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithOptionalLabel", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithOptionalScope", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithParameters", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithRange", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithScope", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithSimpleName", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithStatements", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithThrownExceptions", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithTokenRange", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithTraversableScope", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithType", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithTypeArguments", + "methods": [ + { + "name": "isUsingDiamondOperator", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithTypeParameters", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithVariables", + "methods": [ + { + "name": "getMaximumCommonType", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.NodeWithVariables$1Helper", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.SwitchNode", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.modifiers.NodeWithAbstractModifier", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.modifiers.NodeWithAccessModifiers", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.modifiers.NodeWithFinalModifier", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.modifiers.NodeWithPrivateModifier", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.modifiers.NodeWithProtectedModifier", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.modifiers.NodeWithPublicModifier", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.modifiers.NodeWithStaticModifier", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.nodeTypes.modifiers.NodeWithStrictfpModifier", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.observer.AstObserver", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.observer.AstObserver$ListChangeType", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.observer.AstObserverAdapter", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.observer.Observable", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.observer.ObservableProperty", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.observer.ObservableProperty$Type", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.observer.PropagatingAstObserver", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.AssertStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.BlockStmt", + "allDeclaredFields": true, + "methods": [ + { + "name": "getStatements", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.BreakStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.CatchClause", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.ContinueStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.DoStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.EmptyStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt", + "methods": [ + { + "name": "getArguments", + "parameterTypes": [] + }, + { + "name": "getExpression", + "parameterTypes": [] + }, + { + "name": "getThis", + "parameterTypes": [] + }, + { + "name": "getTypeArguments", + "parameterTypes": [] + }, + { + "name": "isThis", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.ExpressionStmt", + "methods": [ + { + "name": "getExpression", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.ForEachStmt", + "methods": [ + { + "name": "getBody", + "parameterTypes": [] + }, + { + "name": "getIterable", + "parameterTypes": [] + }, + { + "name": "getVariable", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.ForStmt", + "allDeclaredFields": true, + "methods": [ + { + "name": "getBody", + "parameterTypes": [] + }, + { + "name": "getCompare", + "parameterTypes": [] + }, + { + "name": "getInitialization", + "parameterTypes": [] + }, + { + "name": "getUpdate", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.IfStmt", + "methods": [ + { + "name": "getCondition", + "parameterTypes": [] + }, + { + "name": "getElseStmt", + "parameterTypes": [] + }, + { + "name": "getThenBlock", + "parameterTypes": [] + }, + { + "name": "getThenStmt", + "parameterTypes": [] + }, + { + "name": "hasThenBlock", + "parameterTypes": [] + }, + { + "name": "isThenBlock", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.LabeledStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.LocalClassDeclarationStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.LocalRecordDeclarationStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.ReturnStmt", + "methods": [ + { + "name": "getExpression", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.Statement", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.SwitchEntry", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.SwitchEntry$Type", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.SwitchStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.SynchronizedStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.ThrowStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.TryStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.UnparsableStmt", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.WhileStmt", + "methods": [ + { + "name": "getBody", + "parameterTypes": [] + }, + { + "name": "getCondition", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.stmt.YieldStmt", + "methods": [ + { + "name": "getExpression", + "parameterTypes": [] + } + ], + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.ArrayType", + "allDeclaredFields": true, + "methods": [ + { + "name": "getComponentType", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.ArrayType$ArrayBracketPair", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.ArrayType$Origin", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.ClassOrInterfaceType", + "allDeclaredFields": true, + "methods": [ + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getScope", + "parameterTypes": [] + }, + { + "name": "getTypeArguments", + "parameterTypes": [] + }, + { + "name": "getUsingDiamondOperator", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.ConvertibleToUsage", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.IntersectionType", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.PrimitiveType", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.PrimitiveType$Primitive", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.ReferenceType", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.Type", + "allDeclaredFields": true, + "methods": [ + { + "name": "getAnnotations", + "parameterTypes": [] + } + ], + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.TypeParameter", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.UnionType", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.UnknownType", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.VarType", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.VoidType", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.type.WildcardType", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.ProblemReporter", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.RecordAsTypeIdentifierNotAllowed", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.ReservedKeywordValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.SimpleValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.SingleNodeTypeValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.TreeVisitorValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.TypedValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.Validators", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.VisitorValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java10PreviewValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java10Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java11PreviewValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java11Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java12PreviewValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java12Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java13PreviewValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java13Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java14PreviewValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java14Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java15PreviewValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java15Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java16PreviewValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java16Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java17PreviewValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java17Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java18Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java19Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java1_0Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java1_1Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java1_2Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java1_3Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java1_4Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java20Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java21Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java5Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java6Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java7Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java8Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.Java9Validator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.UpgradeJavaMessage", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.chunks.CommonValidators", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.chunks.ModifierValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.chunks.NoBinaryIntegerLiteralsValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.chunks.NoUnderscoresInIntegerLiteralsValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.chunks.RecordDeclarationValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.chunks.UnderscoreKeywordValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.language_level_validations.chunks.VarValidator", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.postprocessors.Java10PostProcessor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.postprocessors.Java11PostProcessor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.postprocessors.Java12PostProcessor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.postprocessors.Java13PostProcessor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.postprocessors.Java14PostProcessor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.postprocessors.Java15PostProcessor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.postprocessors.Java16PostProcessor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.postprocessors.Java17PostProcessor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.postprocessors.Java18PostProcessor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.postprocessors.Java19PostProcessor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.postprocessors.Java20PostProcessor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.postprocessors.Java21PostProcessor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.validator.postprocessors.PostProcessors", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.CloneVisitor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.EqualsVisitor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.GenericListVisitorAdapter", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.GenericVisitor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.GenericVisitorAdapter", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.GenericVisitorWithDefaults", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.HashCodeVisitor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.ModifierVisitor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.NoCommentEqualsVisitor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.NoCommentHashCodeVisitor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.NodeFinderVisitor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.ObjectIdentityEqualsVisitor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.ObjectIdentityHashCodeVisitor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.TreeVisitor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.Visitable", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.VoidVisitor", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.VoidVisitorAdapter", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.javaparser.ast.visitor.VoidVisitorWithDefaults", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.ibm.cldk.CodeAnalyzer", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "queryAllPublicMethods": true + }, + { + "name": "com.ibm.cldk.VersionProvider", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.CRUDOperation", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.CRUDQuery", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.CallSite", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.Callable", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.Comment", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.EnumConstant", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.Field", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.Import", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.InitializationBlock", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.JavaCompilationUnit", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.ParameterInCallable", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.RecordComponent", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.Type", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.entities.VariableDeclaration", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.cldk.javaee.utils.enums.CRUDOperationType", + "allDeclaredFields": true + }, + { + "name": "com.ibm.cldk.javaee.utils.enums.CRUDQueryType", + "allDeclaredFields": true + }, + { + "name": "com.ibm.northstar.CodeAnalyzer", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true, + "queryAllPublicMethods": true + }, + { + "name": "com.ibm.northstar.entities.Callable", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.northstar.entities.ClassOrInterface", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.northstar.entities.Field", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.northstar.entities.JavaCompilationUnit", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.northstar.entities.ParameterInCallable", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.ibm.northstar.entities.Type", + "allDeclaredFields": true, + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "consumer" + }, + { + "name": "consumers" + }, + { + "name": "data" + }, + { + "name": "functions" + }, + { + "name": "hashtable" + }, + { + "name": "indices" + }, + { + "name": "java" + }, + { + "name": "java.io.Closeable", + "queryAllPublicMethods": true + }, + { + "name": "java.io.FilterOutputStream", + "queryAllDeclaredMethods": true + }, + { + "name": "java.io.Flushable", + "queryAllPublicMethods": true + }, + { + "name": "java.io.OutputStream", + "queryAllDeclaredMethods": true + }, + { + "name": "java.io.PrintStream", + "queryAllDeclaredMethods": true + }, + { + "name": "java.io.Serializable", + "queryAllPublicMethods": true + }, + { + "name": "java.lang" + }, + { + "name": "java.lang.Appendable", + "queryAllPublicMethods": true + }, + { + "name": "java.lang.ArrayUtils" + }, + { + "name": "java.lang.AutoCloseable", + "queryAllPublicMethods": true + }, + { + "name": "java.lang.BitMapExtractor" + }, + { + "name": "java.lang.Boolean", + "allDeclaredFields": true + }, + { + "name": "java.lang.CharSequence", + "queryAllPublicMethods": true + }, + { + "name": "java.lang.Class", + "methods": [ + { + "name": "getRecordComponents", + "parameterTypes": [] + }, + { + "name": "isRecord", + "parameterTypes": [] + } + ] + }, + { + "name": "java.lang.Cloneable", + "queryAllPublicMethods": true + }, + { + "name": "java.lang.Comparable", + "queryAllPublicMethods": true + }, + { + "name": "java.lang.Exception", + "queryAllDeclaredMethods": true + }, + { + "name": "java.lang.IllegalArgumentException", + "queryAllDeclaredConstructors": true + }, + { + "name": "java.lang.IndexOutOfBoundsException", + "queryAllDeclaredConstructors": true + }, + { + "name": "java.lang.IndexUtils" + }, + { + "name": "java.lang.Integer", + "queryAllDeclaredMethods": true + }, + { + "name": "java.lang.Iterable", + "queryAllPublicMethods": true + }, + { + "name": "java.lang.NullPointerException", + "queryAllDeclaredConstructors": true + }, + { + "name": "java.lang.Number", + "queryAllDeclaredMethods": true + }, + { + "name": "java.lang.Object", + "allDeclaredFields": true, + "allDeclaredClasses": true, + "queryAllDeclaredMethods": true + }, + { + "name": "java.lang.Predicate" + }, + { + "name": "java.lang.Record", + "allDeclaredFields": true, + "allDeclaredClasses": true, + "queryAllDeclaredMethods": true + }, + { + "name": "java.lang.Runnable", + "allDeclaredFields": true, + "allDeclaredClasses": true + }, + { + "name": "java.lang.RuntimeException", + "queryAllDeclaredConstructors": true + }, + { + "name": "java.lang.String", + "queryAllDeclaredMethods": true + }, + { + "name": "java.lang.System", + "allDeclaredFields": true + }, + { + "name": "java.lang.TestCase" + }, + { + "name": "java.lang.Thread", + "allDeclaredFields": true, + "allDeclaredClasses": true + }, + { + "name": "java.lang.Throwable", + "queryAllDeclaredMethods": true + }, + { + "name": "java.lang.WeakHashtable" + }, + { + "name": "java.lang.array" + }, + { + "name": "java.lang.bitMapExtractor" + }, + { + "name": "java.lang.bitSet" + }, + { + "name": "java.lang.car" + }, + { + "name": "java.lang.chars" + }, + { + "name": "java.lang.classUnderTest" + }, + { + "name": "java.lang.collection" + }, + { + "name": "java.lang.constant.Constable", + "queryAllPublicMethods": true + }, + { + "name": "java.lang.constant.ConstantDesc", + "queryAllPublicMethods": true + }, + { + "name": "java.lang.consumer" + }, + { + "name": "java.lang.consumers" + }, + { + "name": "java.lang.data" + }, + { + "name": "java.lang.e" + }, + { + "name": "java.lang.functions" + }, + { + "name": "java.lang.hashtable" + }, + { + "name": "java.lang.indices" + }, + { + "name": "java.lang.java" + }, + { + "name": "java.lang.java.util" + }, + { + "name": "java.lang.java.util.function" + }, + { + "name": "java.lang.java.util.function.Predicate" + }, + { + "name": "java.lang.map" + }, + { + "name": "java.lang.person" + }, + { + "name": "java.lang.predicate" + }, + { + "name": "java.lang.predicates" + }, + { + "name": "java.lang.reflect.RecordComponent", + "methods": [ + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getType", + "parameterTypes": [] + } + ] + }, + { + "name": "java.lang.values" + }, + { + "name": "java.nio.file.Path" + }, + { + "name": "java.nio.file.Paths", + "methods": [ + { + "name": "get", + "parameterTypes": [ + "java.lang.String", + "java.lang.String[]" + ] + } + ] + }, + { + "name": "java.security.SecureRandomParameters" + }, + { + "name": "java.security.interfaces.RSAPrivateKey" + }, + { + "name": "java.security.interfaces.RSAPublicKey" + }, + { + "name": "java.sql.Connection" + }, + { + "name": "java.sql.Date" + }, + { + "name": "java.sql.Driver" + }, + { + "name": "java.sql.DriverManager", + "methods": [ + { + "name": "getConnection", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "getDriver", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "name": "java.sql.Time", + "methods": [ + { + "name": "", + "parameterTypes": [ + "long" + ] + } + ] + }, + { + "name": "java.sql.Timestamp", + "methods": [ + { + "name": "valueOf", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "name": "java.time.Duration", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.Instant", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.LocalDate", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.LocalDateTime", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.LocalTime", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.MonthDay", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.OffsetDateTime", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.OffsetTime", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.Period", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.Year", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.YearMonth", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.ZoneId", + "methods": [ + { + "name": "of", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "name": "java.time.ZoneOffset", + "methods": [ + { + "name": "of", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "name": "java.time.ZonedDateTime", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.util.Arrays", + "queryAllDeclaredMethods": true + }, + { + "name": "java.util.BitSet", + "queryAllDeclaredMethods": true, + "queryAllDeclaredConstructors": true + }, + { + "name": "java.util.Collection", + "queryAllPublicMethods": true + }, + { + "name": "java.util.Date" + }, + { + "name": "java.util.HashMap", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "java.util.LinkedHashMap", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "java.util.Map", + "queryAllPublicMethods": true + }, + { + "name": "java.util.Objects", + "queryAllDeclaredMethods": true + }, + { + "name": "java.util.concurrent.ForkJoinTask", + "fields": [ + { + "name": "aux" + }, + { + "name": "status" + } + ] + }, + { + "name": "java.util.concurrent.atomic.AtomicBoolean", + "fields": [ + { + "name": "value" + } + ] + }, + { + "name": "java.util.function.Consumer" + }, + { + "name": "java.util.function.Function" + }, + { + "name": "java.util.function.IntPredicate", + "queryAllDeclaredMethods": true, + "queryAllPublicMethods": true + }, + { + "name": "java.util.function.LongPredicate", + "allDeclaredFields": true, + "allDeclaredClasses": true + }, + { + "name": "java.util.function.Predicate" + }, + { + "name": "java.util.function.Supplier" + }, + { + "name": "javax.security.auth.x500.X500Principal", + "fields": [ + { + "name": "thisX500Name" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + "sun.security.x509.X500Name" + ] + } + ] + }, + { + "name": "jdk.internal.misc.Unsafe" + }, + { + "name": "junit" + }, + { + "name": "junit.framework" + }, + { + "name": "junit.framework.TestCase" + }, + { + "name": "map" + }, + { + "name": "org.apache" + }, + { + "name": "org.apache.commons" + }, + { + "name": "org.apache.commons.collections4" + }, + { + "name": "org.apache.commons.collections4.Predicate" + }, + { + "name": "org.example" + }, + { + "name": "org.example.App" + }, + { + "name": "org.example.CarRecord" + }, + { + "name": "org.example.Exception" + }, + { + "name": "org.example.PersonRecord" + }, + { + "name": "org.example.RuntimeException" + }, + { + "name": "org.example.String" + }, + { + "name": "org.example.System" + }, + { + "name": "org.example.car" + }, + { + "name": "org.example.classUnderTest" + }, + { + "name": "org.example.e" + }, + { + "name": "org.example.person" + }, + { + "name": "org.junit" + }, + { + "name": "org.junit.jupiter" + }, + { + "name": "org.junit.jupiter.api" + }, + { + "name": "org.junit.jupiter.api.Assertions" + }, + { + "name": "org.junit.jupiter.api.Assertions.classUnderTest" + }, + { + "name": "picocli.CommandLine$AutoHelpMixin", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true + }, + { + "name": "predicate" + }, + { + "name": "predicates" + }, + { + "name": "sun.security.provider.NativePRNG", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "", + "parameterTypes": [ + "java.security.SecureRandomParameters" + ] + } + ] + }, + { + "name": "sun.security.provider.SHA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.SHA2$SHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.X509Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.rsa.RSAKeyFactory$Legacy", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.rsa.RSASignature$SHA256withRSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.util.ObjectIdentifier" + }, + { + "name": "sun.security.x509.AuthorityInfoAccessExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.AuthorityKeyIdentifierExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.BasicConstraintsExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.CRLDistributionPointsExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.CertificateExtensions" + }, + { + "name": "sun.security.x509.CertificatePoliciesExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.ExtendedKeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.KeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.SubjectAlternativeNameExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.SubjectKeyIdentifierExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "values" + } ] diff --git a/src/main/resources/META-INF/native-image-config/resource-config.json b/src/main/resources/META-INF/native-image-config/resource-config.json index 271f2e65..9cc73509 100644 --- a/src/main/resources/META-INF/native-image-config/resource-config.json +++ b/src/main/resources/META-INF/native-image-config/resource-config.json @@ -1,6 +1,8 @@ { "resources":{ "includes":[{ + "pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" + }, { "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt67b/nfkc.nrm\\E" }]}, "bundles":[] diff --git a/src/test/java/com/ibm/cldk/neo4j/Neo4jBoltWriterTest.java b/src/test/java/com/ibm/cldk/neo4j/Neo4jBoltWriterTest.java new file mode 100644 index 00000000..ea928537 --- /dev/null +++ b/src/test/java/com/ibm/cldk/neo4j/Neo4jBoltWriterTest.java @@ -0,0 +1,138 @@ +/* +Copyright IBM Corporation 2023, 2024 + +Licensed under the Apache Public License 2.0, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.ibm.cldk.neo4j; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.github.javaparser.Problem; +import com.ibm.cldk.SymbolTable; +import com.ibm.cldk.entities.JavaCompilationUnit; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.Session; +import org.testcontainers.containers.Neo4jContainer; + +/** + * Integration test for the Neo4j {@link BoltWriter}. Spins up a real Neo4j via Testcontainers, + * projects a sample fixture to graph rows, pushes them, and asserts the graph in the database — + * including the incremental behaviours (idempotent re-push, vanished-declaration cleanup, and + * full-run orphan pruning). + * + *

This suite needs a container runtime (Docker / Podman), so it is OPT-IN: it runs only when the + * {@code RUN_CONTAINER_TESTS} environment variable is set (e.g. {@code RUN_CONTAINER_TESTS=1 ./gradlew + * test}). The no-container schema conformance test always runs. + */ +@EnabledIfEnvironmentVariable(named = "RUN_CONTAINER_TESTS", matches = ".+") +public class Neo4jBoltWriterTest { + + private static final Path FIXTURE = Paths.get("src/test/resources/test-applications/call-graph-test"); + private static final String APP = "call-graph-test"; + private static final String PASSWORD = "testpassword123"; + + @SuppressWarnings("resource") + private static final Neo4jContainer CONTAINER = + new Neo4jContainer<>("neo4j:5").withAdminPassword(PASSWORD); + + private static Driver driver; + private static BoltConfig cfg; + + @BeforeAll + static void startup() { + CONTAINER.start(); + cfg = new BoltConfig(CONTAINER.getBoltUrl(), "neo4j", CONTAINER.getAdminPassword(), null); + driver = GraphDatabase.driver(cfg.uri, AuthTokens.basic(cfg.user, cfg.password)); + } + + @AfterAll + static void teardown() { + if (driver != null) { + driver.close(); + } + CONTAINER.stop(); + } + + private static GraphRows projectFixture() throws Exception { + Pair, Map>> extracted = + SymbolTable.extractAll(FIXTURE); + return GraphProjector.project(extracted.getLeft(), null, APP); + } + + private long num(String cypher) { + try (Session s = driver.session()) { + return s.run(cypher).single().get(0).asLong(); + } + } + + @Test + public void fullPushMaterializesTheWholeGraphAndSchema() throws Exception { + GraphRows rows = projectFixture(); + new BoltWriter().write(rows, cfg, true); + + // Every projected node/edge lands. + assertEquals(rows.nodes.size(), num("MATCH (n) RETURN count(n)")); + assertEquals(rows.edges.size(), num("MATCH ()-[r]->() RETURN count(r)")); + + // Shared :JSymbol label spans the id-keyed declaration kinds (JType + JCallable). + long symbol = num("MATCH (s:JSymbol) RETURN count(s)"); + long kinds = num("MATCH (s:JSymbol) WHERE s:JType OR s:JCallable RETURN count(s)"); + assertTrue(symbol > 0, "expected JSymbol nodes"); + assertEquals(symbol, kinds); + + // Constraints + indexes were created up front. + assertTrue(num("SHOW CONSTRAINTS YIELD name RETURN count(*)") >= 11); + assertTrue(num("SHOW INDEXES YIELD name RETURN count(*)") >= 4); + } + + @Test + public void rePushingIdenticalAnalysisIsIdempotent() throws Exception { + GraphRows rows = projectFixture(); + new BoltWriter().write(rows, cfg, true); + new BoltWriter().write(rows, cfg, true); + assertEquals(rows.nodes.size(), num("MATCH (n) RETURN count(n)")); + assertEquals(rows.edges.size(), num("MATCH ()-[r]->() RETURN count(r)")); + } + + @Test + public void fullRunPrunesAUnitWhoseSourceVanished() throws Exception { + Pair, Map>> extracted = + SymbolTable.extractAll(FIXTURE); + Map symbolTable = extracted.getLeft(); + new BoltWriter().write(GraphProjector.project(symbolTable, null, APP), cfg, true); + + // Drop one compilation unit and re-push as a full run. + String victim = symbolTable.keySet().stream().sorted().findFirst().orElseThrow(IllegalStateException::new); + symbolTable.remove(victim); + GraphRows reduced = GraphProjector.project(symbolTable, null, APP); + new BoltWriter().write(reduced, cfg, true); + + // The victim's unit-scoped nodes are gone; the surviving unit-scoped graph matches. + try (Session s = driver.session()) { + long victimNodes = s.run("MATCH (n {_module: $m}) RETURN count(n)", + org.neo4j.driver.Values.parameters("m", victim)).single().get(0).asLong(); + assertEquals(0L, victimNodes); + } + long unitScoped = reduced.nodes.stream().filter(n -> n.props.containsKey("_module")).count(); + assertEquals(unitScoped, num("MATCH (n) WHERE n._module IS NOT NULL RETURN count(n)")); + } +} diff --git a/src/test/java/com/ibm/cldk/neo4j/Neo4jSchemaConformanceTest.java b/src/test/java/com/ibm/cldk/neo4j/Neo4jSchemaConformanceTest.java new file mode 100644 index 00000000..268b764e --- /dev/null +++ b/src/test/java/com/ibm/cldk/neo4j/Neo4jSchemaConformanceTest.java @@ -0,0 +1,138 @@ +/* +Copyright IBM Corporation 2023, 2024 + +Licensed under the Apache Public License 2.0, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.ibm.cldk.neo4j; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.github.javaparser.Problem; +import com.google.gson.GsonBuilder; +import com.ibm.cldk.SymbolTable; +import com.ibm.cldk.entities.JavaCompilationUnit; +import com.ibm.cldk.neo4j.GraphRows.EdgeRow; +import com.ibm.cldk.neo4j.GraphRows.NodeRow; +import com.ibm.cldk.neo4j.SchemaCatalog.NodeLabel; +import com.ibm.cldk.neo4j.SchemaCatalog.RelType; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Schema conformance test (no container needed). Projects a sample fixture and asserts that the + * real projector only ever produces node labels, relationship types and properties that the catalog + * ({@link SchemaCatalog}) declares. This is the anti-drift guard: if {@link GraphProjector} grows a + * label or property that the catalog doesn't declare, this fails — keeping the published + * {@code schema.json} honest. It also checks the checked-in {@code schema.neo4j.json} is regenerated. + */ +public class Neo4jSchemaConformanceTest { + + private static final Path FIXTURE = Paths.get("src/test/resources/test-applications/call-graph-test"); + + private static GraphRows rows; + + private static final Map BY_LABEL = new HashMap<>(); + private static final Map MERGE_OF = new HashMap<>(); + private static final Map REL_BY_TYPE = new HashMap<>(); + private static final Set MARKERS = new HashSet<>(SchemaCatalog.MARKER_LABELS); + + @BeforeAll + static void project() throws Exception { + for (NodeLabel nl : SchemaCatalog.NODE_LABELS) { + BY_LABEL.put(nl.label, nl); + MERGE_OF.put(nl.label, nl.mergeLabel); + } + for (RelType rt : SchemaCatalog.REL_TYPES) { + REL_BY_TYPE.put(rt.type, rt); + } + Pair, Map>> extracted = + SymbolTable.extractAll(FIXTURE); + rows = GraphProjector.project(extracted.getLeft(), null, "call-graph-test"); + } + + /** The specific (catalog) label for a node row: the non-merge, non-marker label. */ + private static String specificLabel(List labels) { + String merge = labels.get(0); + if (!merge.equals("JSymbol")) { + return merge; + } + for (String l : labels) { + if (!l.equals("JSymbol") && !MARKERS.contains(l)) { + return l; + } + } + return "JSymbol"; + } + + private static Set mergeLabelsFor(List specifics) { + Set out = new HashSet<>(); + for (String s : specifics) { + out.add(MERGE_OF.get(s)); + } + return out; + } + + @Test + public void everyEmittedNodeLabelAndPropertyIsDeclared() { + assertTrue(rows.nodes.size() > 0, "fixture produced no nodes"); + for (NodeRow node : rows.nodes) { + String specific = specificLabel(node.labels); + NodeLabel decl = BY_LABEL.get(specific); + assertNotNull(decl, "undeclared node label: " + String.join(":", node.labels)); + assertEquals(decl.mergeLabel, node.labels.get(0), "wrong merge label for " + specific); + + for (String label : node.labels) { + boolean ok = label.equals(decl.mergeLabel) || label.equals(specific) || MARKERS.contains(label); + assertTrue(ok, "unexpected label '" + label + "' on " + specific); + } + for (String key : node.props.keySet()) { + assertTrue(decl.properties.containsKey(key), "undeclared property '" + specific + "." + key + "'"); + } + } + } + + @Test + public void everyEmittedRelationshipIsDeclared() { + assertTrue(rows.edges.size() > 0, "fixture produced no edges"); + for (EdgeRow edge : rows.edges) { + RelType decl = REL_BY_TYPE.get(edge.type); + assertNotNull(decl, "undeclared relationship type: " + edge.type); + assertTrue(mergeLabelsFor(decl.from).contains(edge.from.label), + "bad source " + edge.from.label + " for " + edge.type); + assertTrue(mergeLabelsFor(decl.to).contains(edge.to.label), + "bad target " + edge.to.label + " for " + edge.type); + for (String key : edge.props.keySet()) { + assertTrue(decl.properties.containsKey(key), "undeclared property on " + edge.type + "." + key); + } + } + } + + @Test + public void checkedInSchemaMatchesCatalog() throws Exception { + Path onDiskPath = Paths.get("schema.neo4j.json"); + assertTrue(Files.exists(onDiskPath), "schema.neo4j.json missing — run `--emit schema`"); + String onDisk = new String(Files.readAllBytes(onDiskPath), StandardCharsets.UTF_8).trim(); + String fresh = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create() + .toJson(SchemaCatalog.buildSchemaDocument()).trim(); + assertEquals(fresh, onDisk, "schema.neo4j.json is stale — regenerate with `--emit schema`"); + } +}