diff --git a/AGENTS.md b/AGENTS.md index 7110c7b1f22..84faa50ee6e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,12 +1,70 @@ -# AGENTS.md +# You are a very strong reasoner and planner. Use these critical instructions to structure your plans, thoughts, and responses. -Welcome, AI Agent! Your persistence, curiosity, and craftsmanship make a difference. Take your time, work methodically, validate thoroughly, and iterate. This repository is large and tests can take time — that’s expected and supported. +Before taking any action (either tool calls *or* responses to the user), you must proactively, methodically, and independently plan and reason about: -You need to read the entire AGENTS.md file and follow all instructions exactly. Keep this fresh in your context as you work. +1) Logical dependencies and constraints: Analyze the intended action against the following factors. Resolve conflicts in order of importance: + + 1.1) Policy-based rules, mandatory prerequisites, and constraints. + + 1.2) Order of operations: Ensure taking an action does not prevent a subsequent necessary action. + + 1.2.1) The user may request actions in a random order, but you may need to reorder operations to maximize successful completion of the task. + + 1.3) Other prerequisites (information and/or actions needed). + + 1.4) Explicit user constraints or preferences. + +2) Risk assessment: What are the consequences of taking the action? Will the new state cause any future issues? + + 2.1) For exploratory tasks (like searches), missing *optional* parameters is a LOW risk. + **Prefer calling the tool with the available information over asking the user, unless** your `Rule 1` (Logical Dependencies) reasoning determines that optional information is required for a later step in your plan. + +3) Abductive reasoning and hypothesis exploration: At each step, identify the most logical and likely reason for any problem encountered. + + 3.1) Look beyond immediate or obvious causes. The most likely reason may not be the simplest and may require deeper inference. + + 3.2) Hypotheses may require additional research. Each hypothesis may take multiple steps to test. + + 3.3) Prioritize hypotheses based on likelihood, but do not discard less likely ones prematurely. A low-probability event may still be the root cause. + +4) Outcome evaluation and adaptability: Does the previous observation require any changes to your plan? + + 4.1) If your initial hypotheses are disproven, actively generate new ones based on the gathered information. + +5) Information availability: Incorporate all applicable and alternative sources of information, including: + + 5.1) Using available tools and their capabilities + 5.2) All policies, rules, checklists, and constraints + 5.3) Previous observations and conversation history + 5.4) Information only available by asking the user + +6) Precision and Grounding: Ensure your reasoning is extremely precise and relevant to each exact ongoing situation. + + 6.1) Verify your claims by quoting the exact applicable information (including policies) when referring to them. + +7) Completeness: Ensure that all requirements, constraints, options, and preferences are exhaustively incorporated into your plan. + + 7.1) Resolve conflicts using the order of importance in #1. + + 7.2) Avoid premature conclusions: There may be multiple relevant options for a given situation. + + 7.2.1) To check for whether an option is relevant, reason about all information sources from #5. + + 7.2.2) You may need to consult the user to even know whether something is applicable. Do not assume it is not applicable without checking. + + 7.3) Review applicable sources of information from #5 to confirm which are relevant to the current state. + +8) Persistence and patience: Do not give up unless all the reasoning above is exhausted. + + 8.1) Don't be dissuaded by time taken or user frustration. + + 8.2) This persistence must be intelligent: On *transient* errors (e.g. please try again), you *must* retry **unless an explicit retry limit (e.g., max x tries) has been reached**. If such a limit is hit, you *must* stop. On *other* errors, you must change your strategy or arguments, not repeat the same failed call. + +9) Inhibit your response: only take an action after all the above reasoning is completed. Once you've taken an action, you cannot take it back. --- -## Read‑Me‑Now: Proportional Test‑First Rule (Default) +## Read‑Me‑Now: Proportional Test‑First Rule **Default:** Use **test‑first (TDD)** for any change that alters externally observable behavior. @@ -31,21 +89,17 @@ It is illegal to `-q` when running tests! ## Four Routines: Choose Your Path -**Routine A — Full TDD (Default)** +**Routine A — Full TDD** **Routine B — Change without new tests (Proportional, gated)** **Routine C — Spike/Investigate (No production changes)** **Routine D — ExecPlans: Complex features or significant refactors** ### Decision quickstart -1. **Is ExecPlans required (complex feature, significant refactor or requested by the user)?** +1. **Is ExecPlans required (complex feature, significant refactor, etc. or explicitly requested by the user)?** → **Yes:** **Routine D (ExecPlans)**. Use an ExecPlan (as described in .agent/PLANS.md) from design to implementation. → **No:** continue. -2**Is new externally observable behavior required?** -→ **Yes:** **Routine A (Full TDD)**. Add the smallest failing test first. -→ **No:** continue. - 3**Does a failing test already exist in this repo that pinpoints the issue?** → **Yes:** **Routine B (Bugfix using existing failing test).** → **No:** continue. @@ -54,11 +108,13 @@ It is illegal to `-q` when running tests! → **Yes:** **Routine B (Refactor/micro‑perf/documentation/build).** → **No or unsure:** continue. -5**Is this purely an investigation/design spike with no production code changes?** -→ **Yes:** **Routine C (Spike/Investigate).** -→ **No or unsure:** **Routine A.** +4. **Is new externally observable behavior required?** + → **Yes:** **Routine A (Full TDD)**. Add the smallest failing test first. + → **No:** continue. -**When in doubt, choose Routine A (Full TDD).** Ambiguity is risk; tests are insurance. +5. **Is this purely an investigation/design spike with no production code changes?** + → **Yes:** **Routine C (Spike/Investigate).** + → **No or unsure:** **Routine A.** --- @@ -66,6 +122,10 @@ It is illegal to `-q` when running tests! When writing complex features or significant refactors, use an ExecPlan (as described in PLANS.md) from design to implementation. +## ExecPlans + +When writing complex features or significant refactors, use an ExecPlan (as described in PLANS.md) from design to implementation. + ## PIOSEE Decision Model (Adopted) Use this as a compact, repeatable loop for anything from a one‑line bug fix to a multi‑quarter program. @@ -257,12 +317,12 @@ Plan `-am` is helpful for **compiles**, hazardous for **tests**. * ✅ Use `-am` **only** for compile/verify with tests skipped (e.g. `-Pquick`): - * `mvn -o -Dmaven.repo.local=.m2_repo -pl -am -Pquick install` + * `mvn -o -Dmaven.repo.local=.m2_repo -pl -am -Pquick clean install` * ❌ Do **not** use `-am` with `verify` when tests are enabled. **Two-step pattern (fast + safe)** 1. **Compile deps fast (skip tests):** - `mvn -o -Dmaven.repo.local=.m2_repo -pl -am -Pquick install` + `mvn -o -Dmaven.repo.local=.m2_repo -pl -am -Pquick clean install` 2. **Run tests:** `mvn -o -Dmaven.repo.local=.m2_repo -pl verify | tail -500` @@ -276,9 +336,10 @@ It is illegal to `-q` when running tests! The Maven reactor resolves inter-module dependencies from the configured local Maven repository (here: `.m2_repo`). Running `install` publishes your changed modules there so downstream modules and tests pick up the correct versions. -* Always run `mvn -o -Dmaven.repo.local=.m2_repo -Pquick clean install | tail -200` before you start working. This command typically takes up to 30 seconds. Never use a shorter timeout than 30,000 ms. -* Always run `mvn -o -Dmaven.repo.local=.m2_repo -Pquick clean install | tail -200` before any `verify` or test runs. -* If offline resolution fails due to a missing dependency or plugin, rerun the exact `install` command once without `-o`, then return offline. +* Always run `mvn -T 1C -o -Dmaven.repo.local=.m2_repo -Pquick clean install | tail -200` before you start working. This command typically takes up to 30 seconds. Never use a shorter timeout than 60,000 ms. +* Always run `mvn -T 1C -o -Dmaven.repo.local=.m2_repo -Pquick clean install | tail -200` before any `verify` or test runs. +* If offline resolution fails due to a missing dependency or plugin, run the command without `-o`: `mvn -Dmaven.repo.local=.m2_repo -Pquick clean install | tail -200`, then return offline. +* If it fails for any other reason, run the command without `-T 1C`: `mvn -o -Dmaven.repo.local=.m2_repo -Pquick clean install | tail -200`. * Skipping this step can lead to stale or missing artifacts during tests, producing confusing compilation or linkage errors. * Always use a workspace-local Maven repository: append `-Dmaven.repo.local=.m2_repo` to all Maven commands (install, verify, formatter, etc.). * Always try to run these commands first to see if they run without needing any approvals from the user w.r.t. the sandboxing. @@ -287,8 +348,8 @@ Why this is mandatory - Tests must not use `-am`. Without `-am`, Maven will not build upstream modules when you run tests; it will resolve cross‑module dependencies from the configured local repository (here: `.m2_repo`). - Therefore, tests only see whatever versions were last published to the configured local repo (`.m2_repo`). If you change code in one module and then run tests in another, those tests will not see your changes unless the updated module has been installed to `.m2_repo` first. -- The reliable way to ensure all tests always use the latest code across the entire multi‑module build is to install all modules to the configured local repo (`.m2_repo`) before running any tests: run `mvn -o -Dmaven.repo.local=.m2_repo -Pquick install` at the repository root. -- In tight loops you may also install a specific module and its deps (`-pl -am -Pquick install`) to iterate quickly, but before executing tests anywhere that depend on your changes, run a root‑level `mvn -o -Dmaven.repo.local=.m2_repo -Pquick install` so the latest jars are available to the reactor from `.m2_repo`. +- The reliable way to ensure all tests always use the latest code across the entire multi‑module build is to install all modules to the configured local repo (`.m2_repo`) before running any tests: run `mvn -T 1C -o -Dmaven.repo.local=.m2_repo -Pquick clean install` at the repository root. +- In tight loops you may also install a specific module and its deps (`-pl -am -Pquick clean install`) to iterate quickly, but before executing tests anywhere that depend on your changes, run a root‑level `mvn -T 1C -o -Dmaven.repo.local=.m2_repo -Pquick clean install` so the latest jars are available to the reactor from `.m2_repo`. --- ## Quick Start (First 10 Minutes) @@ -297,7 +358,7 @@ Why this is mandatory * Inspect root `pom.xml` and module tree (see “Maven Module Overview”). * Search fast with ripgrep: `rg -n ""` 2. **Build sanity (fast, skip tests)** - * `mvn -o -Dmaven.repo.local=.m2_repo -Pquick install | tail -200` + * `mvn -T 1C -o -Dmaven.repo.local=.m2_repo -Pquick clean install | tail -200` 3. **Format (Java, imports, XML)** * `mvn -o -Dmaven.repo.local=.m2_repo -q -T 2C formatter:format impsort:sort xml-format:xml-format` 4. **Targeted tests (tight loops)** @@ -313,7 +374,7 @@ It is illegal to `-q` when running tests! --- -## Routine A — Full TDD (Default) +## Routine A — Full TDD > Use for **all behavior‑changing work** and whenever Routine B gates do not all pass. @@ -407,7 +468,7 @@ When writing complex features or significant refactors, use an ExecPlan (as desc * **Plan:** small, verifiable steps; keep one `in_progress`, or follow PLANS.md (ExecPlans) * **Change:** minimal, surgical edits; keep style/structure consistent. * **Format:** `mvn -o -Dmaven.repo.local=.m2_repo -q -T 2C formatter:format impsort:sort xml-format:xml-format` -* **Compile (fast):** `mvn -o -Dmaven.repo.local=.m2_repo -pl -am -Pquick install | tail -500` +* **Compile (fast):** `mvn -o -Dmaven.repo.local=.m2_repo -pl -am -Pquick clean install | tail -500` * **Test:** start smallest (class/method → module). For integration, run module `verify`. * **Triage:** read reports; fix root cause; expand scope only when needed. * **Iterate:** keep momentum; escalate only when blocked or irreversible. @@ -514,7 +575,7 @@ Do **not** modify existing headers’ years. ## Pre‑Commit Checklist * **Format:** `mvn -o -Dmaven.repo.local=.m2_repo -q -T 2C formatter:format impsort:sort xml-format:xml-format` -* **Compile (fast path):** `mvn -o -Dmaven.repo.local=.m2_repo -Pquick install | tail -200` +* **Compile (fast path):** `mvn -T 1C -o -Dmaven.repo.local=.m2_repo -Pquick clean install | tail -200` * **Tests (targeted):** `mvn -o -Dmaven.repo.local=.m2_repo -pl verify | tail -500` (broaden as needed) * **Reports:** zero new failures in Surefire/Failsafe, or explain precisely. * **Evidence:** Routine A — failing pre‑fix + passing post‑fix. @@ -629,7 +690,7 @@ Do **not** modify existing headers’ years. ## Build * **Build without tests (fast path):** - `mvn -o -Dmaven.repo.local=.m2_repo -Pquick install` + `mvn -T 1C -o -Dmaven.repo.local=.m2_repo -Pquick clean install` * **Verify with tests:** Targeted module(s): `mvn -o -Dmaven.repo.local=.m2_repo -pl verify` Entire repo: `mvn -o -Dmaven.repo.local=.m2_repo verify` (use judiciously) @@ -751,7 +812,6 @@ rdf4j: root project │ ├── lmdb: Sail implementation that stores data to disk using LMDB. │ ├── lucene-api: StackableSail API offering full-text search on literals, based on Apache Lucene. │ ├── lucene: StackableSail implementation offering full-text search on literals, based on Apache Lucene. - │ ├── solr: StackableSail implementation offering full-text search on literals, based on Solr. │ ├── elasticsearch: StackableSail implementation offering full-text search on literals, based on Elastic Search. │ ├── elasticsearch-store: Store for utilizing Elasticsearch as a triplestore. │ └── extensible-store: Store that can be extended with a simple user-made backend. @@ -791,7 +851,6 @@ rdf4j: root project ├── model: RDF4J: Model compliance tests ├── sparql: Tests for the SPARQL query language implementation ├── lucene: Compliance Tests for LuceneSail. - ├── solr: Tests for Solr Sail. ├── elasticsearch: Tests for Elasticsearch. └── geosparql: Tests for the GeoSPARQL query language implementation ├── examples: Examples and HowTos for use of RDF4J in Java @@ -803,6 +862,7 @@ rdf4j: root project * Don’t commit or push unless explicitly asked. * Don’t add new dependencies without explicit approval. +* Never revert unrelated working tree changes ### Version Control Conventions diff --git a/PLANS.md b/PLANS.md index 7d8044a9f71..b491c7abe2a 100644 --- a/PLANS.md +++ b/PLANS.md @@ -1,152 +1,153 @@ # Codex Execution Plans (ExecPlans): - + This document describes the requirements for an execution plan ("ExecPlan"), a design document that a coding agent can follow to deliver a working feature or system change. Treat the reader as a complete beginner to this repository: they have only the current working tree and the single ExecPlan file you provide. There is no memory of prior plans and no external context. - + ## How to use ExecPlans and PLANS.md - + When authoring an executable specification (ExecPlan), follow PLANS.md _to the letter_. If it is not in your context, refresh your memory by reading the entire PLANS.md file. Be thorough in reading (and re-reading) source material to produce an accurate specification. When creating a spec, start from the skeleton and flesh it out as you do your research. - + When implementing an executable specification (ExecPlan), do not prompt the user for "next steps"; simply proceed to the next milestone. Keep all sections up to date, add or split entries in the list at every stopping point to affirmatively state the progress made and next steps. Resolve ambiguities autonomously, and commit frequently. - + When discussing an executable specification (ExecPlan), record decisions in a log in the spec for posterity; it should be unambiguously clear why any change to the specification was made. ExecPlans are living documents, and it should always be possible to restart from _only_ the ExecPlan and no other work. - + When researching a design with challenging requirements or significant unknowns, use milestones to implement proof of concepts, "toy implementations", etc., that allow validating whether the user's proposal is feasible. Read the source code of libraries by finding or acquiring them, research deeply, and include prototypes to guide a fuller implementation. - + ## Requirements - + NON-NEGOTIABLE REQUIREMENTS: - + * Every ExecPlan must be fully self-contained. Self-contained means that in its current form it contains all knowledge and instructions needed for a novice to succeed. * Every ExecPlan is a living document. Contributors are required to revise it as progress is made, as discoveries occur, and as design decisions are finalized. Each revision must remain fully self-contained. * Every ExecPlan must enable a complete novice to implement the feature end-to-end without prior knowledge of this repo. * Every ExecPlan must produce a demonstrably working behavior, not merely code changes to "meet a definition". * Every ExecPlan must define every term of art in plain language or do not use it. - + Purpose and intent come first. Begin by explaining, in a few sentences, why the work matters from a user's perspective: what someone can do after this change that they could not do before, and how to see it working. Then guide the reader through the exact steps to achieve that outcome, including what to edit, what to run, and what they should observe. - + The agent executing your plan can list files, read files, search, run the project, and run tests. It does not know any prior context and cannot infer what you meant from earlier milestones. Repeat any assumption you rely on. Do not point to external blogs or docs; if knowledge is required, embed it in the plan itself in your own words. If an ExecPlan builds upon a prior ExecPlan and that file is checked in, incorporate it by reference. If it is not, you must include all relevant context from that plan. - + ## Formatting - + Format and envelope are simple and strict. Each ExecPlan must be one single fenced code block labeled as `md` that begins and ends with triple backticks. Do not nest additional triple-backtick code fences inside; when you need to show commands, transcripts, diffs, or code, present them as indented blocks within that single fence. Use indentation for clarity rather than code fences inside an ExecPlan to avoid prematurely closing the ExecPlan's code fence. Use two newlines after every heading, use # and ## and so on, and correct syntax for ordered and unordered lists. - + When writing an ExecPlan to a Markdown (.md) file where the content of the file *is only* the single ExecPlan, you should omit the triple backticks. - + Write in plain prose. Prefer sentences over lists. Avoid checklists, tables, and long enumerations unless brevity would obscure meaning. Checklists are permitted only in the `Progress` section, where they are mandatory. Narrative sections must remain prose-first. - + ## Guidelines - + Self-containment and plain language are paramount. If you introduce a phrase that is not ordinary English ("daemon", "middleware", "RPC gateway", "filter graph"), define it immediately and remind the reader how it manifests in this repository (for example, by naming the files or commands where it appears). Do not say "as defined previously" or "according to the architecture doc." Include the needed explanation here, even if you repeat yourself. - + Avoid common failure modes. Do not rely on undefined jargon. Do not describe "the letter of a feature" so narrowly that the resulting code compiles but does nothing meaningful. Do not outsource key decisions to the reader. When ambiguity exists, resolve it in the plan itself and explain why you chose that path. Err on the side of over-explaining user-visible effects and under-specifying incidental implementation details. - + Anchor the plan with observable outcomes. State what the user can do after implementation, the commands to run, and the outputs they should see. Acceptance should be phrased as behavior a human can verify ("after starting the server, navigating to [http://localhost:8080/health](http://localhost:8080/health) returns HTTP 200 with body OK") rather than internal attributes ("added a HealthCheck struct"). If a change is internal, explain how its impact can still be demonstrated (for example, by running tests that fail before and pass after, and by showing a scenario that uses the new behavior). - + Specify repository context explicitly. Name files with full repository-relative paths, name functions and modules precisely, and describe where new files should be created. If touching multiple areas, include a short orientation paragraph that explains how those parts fit together so a novice can navigate confidently. When running commands, show the working directory and exact command line. When outcomes depend on environment, state the assumptions and provide alternatives when reasonable. - + Be idempotent and safe. Write the steps so they can be run multiple times without causing damage or drift. If a step can fail halfway, include how to retry or adapt. If a migration or destructive operation is necessary, spell out backups or safe fallbacks. Prefer additive, testable changes that can be validated as you go. - + Validation is not optional. Include instructions to run tests, to start the system if applicable, and to observe it doing something useful. Describe comprehensive testing for any new features or capabilities. Include expected outputs and error messages so a novice can tell success from failure. Where possible, show how to prove that the change is effective beyond compilation (for example, through a small end-to-end scenario, a CLI invocation, or an HTTP request/response transcript). State the exact test commands appropriate to the project’s toolchain and how to interpret their results. - + Capture evidence. When your steps produce terminal output, short diffs, or logs, include them inside the single fenced block as indented examples. Keep them concise and focused on what proves success. If you need to include a patch, prefer file-scoped diffs or small excerpts that a reader can recreate by following your instructions rather than pasting large blobs. - + ## Milestones - + Milestones are narrative, not bureaucracy. If you break the work into milestones, introduce each with a brief paragraph that describes the scope, what will exist at the end of the milestone that did not exist before, the commands to run, and the acceptance you expect to observe. Keep it readable as a story: goal, work, result, proof. Progress and milestones are distinct: milestones tell the story, progress tracks granular work. Both must exist. Never abbreviate a milestone merely for the sake of brevity, do not leave out details that could be crucial to a future implementation. - + Each milestone must be independently verifiable and incrementally implement the overall goal of the execution plan. - + ## Living plans and design decisions - + * ExecPlans are living documents. As you make key design decisions, update the plan to record both the decision and the thinking behind it. Record all decisions in the `Decision Log` section. * ExecPlans must contain and maintain a `Progress` section, a `Surprises & Discoveries` section, a `Decision Log`, and an `Outcomes & Retrospective` section. These are not optional. * When you discover optimizer behavior, performance tradeoffs, unexpected bugs, or inverse/unapply semantics that shaped your approach, capture those observations in the `Surprises & Discoveries` section with short evidence snippets (test output is ideal). * If you change course mid-implementation, document why in the `Decision Log` and reflect the implications in `Progress`. Plans are guides for the next contributor as much as checklists for you. * At completion of a major task or the full plan, write an `Outcomes & Retrospective` entry summarizing what was achieved, what remains, and lessons learned. - + # Prototyping milestones and parallel implementations - + It is acceptable—-and often encouraged—-to include explicit prototyping milestones when they de-risk a larger change. Examples: adding a low-level operator to a dependency to validate feasibility, or exploring two composition orders while measuring optimizer effects. Keep prototypes additive and testable. Clearly label the scope as “prototyping”; describe how to run and observe results; and state the criteria for promoting or discarding the prototype. - + Prefer additive code changes followed by subtractions that keep tests passing. Parallel implementations (e.g., keeping an adapter alongside an older path during migration) are fine when they reduce risk or enable tests to continue passing during a large migration. Describe how to validate both paths and how to retire one safely with tests. When working with multiple new libraries or feature areas, consider creating spikes that evaluate the feasibility of these features _independently_ of one another, proving that the external library performs as expected and implements the features we need in isolation. - + ## Skeleton of a Good ExecPlan - + ```md # - + This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds. - + If PLANS.md file is checked into the repo, reference the path to that file here from the repository root and note that this document must be maintained in accordance with PLANS.md. - + ## Purpose / Big Picture - + Explain in a few sentences what someone gains after this change and how they can see it working. State the user-visible behavior you will enable. - + ## Progress - + Use a list with checkboxes to summarize granular steps. Every stopping point must be documented here, even if it requires splitting a partially completed task into two (“done” vs. “remaining”). This section must always reflect the actual current state of the work. - + - [x] (2025-10-01 13:00Z) Example completed step. - [ ] Example incomplete step. - [ ] Example partially completed step (completed: X; remaining: Y). - + Use timestamps to measure rates of progress. - + ## Surprises & Discoveries - + Document unexpected behaviors, bugs, optimizations, or insights discovered during implementation. Provide concise evidence. - + - Observation: … Evidence: … - + ## Decision Log - + Record every decision made while working on the plan in the format: - + - Decision: … Rationale: … Date/Author: … - + ## Outcomes & Retrospective - + Summarize outcomes, gaps, and lessons learned at major milestones or at completion. Compare the result against the original purpose. - + ## Context and Orientation - + Describe the current state relevant to this task as if the reader knows nothing. Name the key files and modules by full path. Define any non-obvious term you will use. Do not refer to prior plans. - + ## Plan of Work - + Describe, in prose, the sequence of edits and additions. For each edit, name the file and location (function, module) and what to insert or change. Keep it concrete and minimal. - + ## Concrete Steps - + State the exact commands to run and where to run them (working directory). When a command generates output, show a short expected transcript so the reader can compare. This section must be updated as work proceeds. - + ## Validation and Acceptance - + Describe how to start or exercise the system and what to observe. Phrase acceptance as behavior, with specific inputs and outputs. If tests are involved, say "run and expect passed; the new test fails before the change and passes after>". - + ## Idempotence and Recovery - + If steps can be repeated safely, say so. If a step is risky, provide a safe retry or rollback path. Keep the environment clean after completion. - + ## Artifacts and Notes - + Include the most important transcripts, diffs, or snippets as indented examples. Keep them concise and focused on what proves success. - + ## Interfaces and Dependencies - + Be prescriptive. Name the libraries, modules, and services to use and why. Specify the types, traits/interfaces, and function signatures that must exist at the end of the milestone. Prefer stable names and paths such as `crate::module::function` or `package.submodule.Interface`. E.g.: - + In crates/foo/planner.rs, define: - + pub trait Planner { fn plan(&self, observed: &Observed) -> Vec; } ``` - + If you follow the guidance above, a single, stateless agent -- or a human novice -- can read your ExecPlan from top to bottom and produce a working, observable result. That is the bar: SELF-CONTAINED, SELF-SUFFICIENT, NOVICE-GUIDING, OUTCOME-FOCUSED. - + When you revise a plan, you must ensure your changes are comprehensively reflected across all sections, including the living document sections, and you must write a note at the bottom of the plan describing the change and the reason why. ExecPlans must describe not just the what but the why for almost everything. + diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/PropertyShape.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/PropertyShape.java index db324f3c020..9e53b665ff9 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/PropertyShape.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/PropertyShape.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.eclipse.rdf4j.sail.shacl.ast; +import java.util.IdentityHashMap; import java.util.List; import java.util.Objects; import java.util.Set; @@ -344,14 +345,8 @@ public SourceConstraintComponent getConstraintComponent() { } @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - if (!super.equals(o)) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { + if (!(o instanceof PropertyShape)) { return false; } @@ -369,12 +364,18 @@ public boolean equals(Object o) { if (!Objects.equals(group, that.group)) { return false; } - return Objects.equals(path, that.path); + + if (!Objects.equals(path, that.path)) { + return false; + } + + return super.equals(o, kvIdentityHashMap); + } @Override - public int hashCode() { - int result = super.hashCode(); + public int hashCode(IdentityHashMap cycleDetection) { + int result = super.hashCode(cycleDetection); result = 31 * result + (name != null ? name.hashCode() : 0); result = 31 * result + (description != null ? description.hashCode() : 0); result = 31 * result + (defaultValue != null ? defaultValue.hashCode() : 0); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/ShaclShapeParsingException.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/ShaclShapeParsingException.java index 2b867ef5a63..09d9270b827 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/ShaclShapeParsingException.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/ShaclShapeParsingException.java @@ -32,6 +32,10 @@ public ShaclShapeParsingException(String msg, Throwable t, Resource id) { this.id = id; } + public ShaclShapeParsingException(String msg, Throwable t) { + super(msg, t); + } + public ShaclShapeParsingException(Throwable t, Resource id) { super(t.getMessage() + " - Caused by shape with id: " + resourceToString(id), t); this.id = id; diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/Shape.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/Shape.java index 4c3d7db0bb4..df3c54d2f45 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/Shape.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/Shape.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Objects; import java.util.Set; @@ -21,6 +22,7 @@ import java.util.stream.Stream; import org.eclipse.rdf4j.common.annotation.InternalUseOnly; +import org.eclipse.rdf4j.common.exception.RDF4JException; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Literal; import org.eclipse.rdf4j.model.Model; @@ -43,6 +45,7 @@ import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ValidationSettings; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; +import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.AbstractConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.AndConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.ClassConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.ClosedConstraintComponent; @@ -201,7 +204,7 @@ public void toModel(Resource subject, IRI predicate, Model model, Set modelBuilder.subject(getId()); if (deactivated) { - modelBuilder.add(SHACL.DEACTIVATED, deactivated); + modelBuilder.add(SHACL.DEACTIVATED, true); } for (Literal literal : message) { @@ -523,7 +526,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public final boolean equals(Object o) { if (this == o) { return true; } @@ -531,8 +534,24 @@ public boolean equals(Object o) { return false; } + return equals((Shape) o, new IdentityHashMap<>()); + } + + @Override + public boolean equals(ConstraintComponent o, IdentityHashMap comparisonGuard) { + Object alreadyComparing = comparisonGuard.get(this); + if (alreadyComparing == o) { + return true; + } + + if (!(o instanceof Shape)) { + return false; + } + Shape shape = (Shape) o; + comparisonGuard.put(this, shape); + if (produceValidationReports != shape.produceValidationReports) { return false; } @@ -548,20 +567,87 @@ public boolean equals(Object o) { if (severity != shape.severity) { return false; } - if (!Objects.equals(constraintComponents, shape.constraintComponents)) { + + if (constraintComponents.size() != shape.constraintComponents.size()) { return false; } + + boolean[] matchedRightSide = new boolean[shape.constraintComponents.size()]; + + for (int i = 0; i < constraintComponents.size(); i++) { + ConstraintComponent left = constraintComponents.get(i); + + if (left == null) { + continue; + } + + int matchIndex = -1; + + if (!matchedRightSide[i]) { + ConstraintComponent right = shape.constraintComponents.get(i); + if (left.equals(right, comparisonGuard)) { + matchIndex = i; + } + } + + if (matchIndex == -1) { + for (int j = 0; j < shape.constraintComponents.size(); j++) { + if (matchedRightSide[j]) { + continue; + } + ConstraintComponent candidate = shape.constraintComponents.get(j); + if (left.equals(candidate, comparisonGuard)) { + matchIndex = j; + break; + } + } + } + + if (matchIndex == -1) { + return false; + } + + matchedRightSide[matchIndex] = true; + } + return true; } @Override - public int hashCode() { + public final int hashCode() { + return hashCode(new IdentityHashMap<>()); + } + + @Override + public int hashCode(IdentityHashMap cycleDetection) { + if (cycleDetection.put(this, Boolean.TRUE) != null) { + throw new ShaclShapeParsingException("Recursive shape definition detected while computing hashCode", + getId()); + } + int result = (produceValidationReports ? 1 : 0); result = 31 * result + (target != null ? target.hashCode() : 0); result = 31 * result + (deactivated ? 1 : 0); result = 31 * result + (message != null ? message.hashCode() : 0); result = 31 * result + (severity != null ? severity.hashCode() : 0); - result = 31 * result + (constraintComponents != null ? constraintComponents.hashCode() : 0); + + long temp = 0; + + for (ConstraintComponent constraintComponent : constraintComponents) { + int componentHash; + if (constraintComponent instanceof Shape) { + componentHash = constraintComponent.hashCode(cycleDetection); + } else if (constraintComponent instanceof AbstractConstraintComponent) { + componentHash = constraintComponent.hashCode(cycleDetection); + } else { + componentHash = constraintComponent != null ? constraintComponent.hashCode() : 0; + } + temp += componentHash; + } + + result = 31 * result + Long.hashCode(temp); + + cycleDetection.remove(this); return result; } @@ -569,26 +655,42 @@ public static class Factory { public static List getShapes(ShapeSource shapeSource, ParseSettings parseSettings) { - List parsed = parse(shapeSource, parseSettings); - - return getShapes(parsed); + try { + List parsed = parse(shapeSource, parseSettings); + return getShapes(parsed); + } catch (RDF4JException e) { + logger.error(e.getMessage(), e); + throw e; + } catch (Throwable e) { + logger.error("Unexpected error while parsing shapes", e); + throw new ShaclShapeParsingException("Unexpected error while parsing shapes", e); + } } public static List getShapes(List parsed) { - return parsed.stream() - .flatMap(contextWithShapes -> { - List split = split(contextWithShapes.getShape()); - calculateTargetChain(split); - calculateIfProducesValidationResult(split); - return split.stream().map(s -> { - return new ContextWithShape(contextWithShapes.getDataGraph(), - contextWithShapes.getShapeGraph(), s); - }); - }) - .filter(ContextWithShape::hasShape) - .distinct() - .collect(Collectors.toList()); + try { + return parsed.stream() + .flatMap(contextWithShapes -> { + List split = split(contextWithShapes.getShape()); + calculateTargetChain(split); + calculateIfProducesValidationResult(split); + return split.stream().map(s -> { + return new ContextWithShape(contextWithShapes.getDataGraph(), + contextWithShapes.getShapeGraph(), s); + }); + }) + .filter(ContextWithShape::hasShape) + .distinct() + .peek(a -> a.getShape().verifyNotRecursive()) + .collect(Collectors.toList()); + } catch (RDF4JException e) { + logger.error(e.getMessage(), e); + throw e; + } catch (Throwable e) { + logger.error("Unexpected error while parsing shapes", e); + throw new ShaclShapeParsingException("Unexpected error while parsing shapes", e); + } } private static void calculateIfProducesValidationResult(List split) { @@ -750,6 +852,11 @@ public static List getShapesInContext(ShapeSource shapeSource, } + private void verifyNotRecursive() { + // calling hashCode will throw an exception if recursion is detected + int i = hashCode(new IdentityHashMap<>()); + } + @Override public String toString() { Model statements = toModel(new DynamicModel(new LinkedHashModelFactory())); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractConstraintComponent.java index fedfe846162..b6de74b1f30 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractConstraintComponent.java @@ -11,6 +11,8 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; + import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Literal; import org.eclipse.rdf4j.model.Resource; @@ -178,4 +180,21 @@ static PlanNode getAllTargetsIncludingThoseAddedByPath(ConnectionsGroup connecti return allTargets; } + @Override + public final int hashCode() { + return hashCode(new IdentityHashMap<>()); + } + + @Override + public final boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + return equals(((AbstractConstraintComponent) o), new IdentityHashMap<>()); + } + } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractPairwiseConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractPairwiseConstraintComponent.java index daba61bd6c0..eb43ec44531 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractPairwiseConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractPairwiseConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.Optional; import java.util.Set; @@ -268,7 +269,7 @@ public boolean requiresEvaluation(ConnectionsGroup connectionsGroup, Scope scope } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -282,7 +283,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return predicate.hashCode() + "AbstractPairwiseConstraintComponent".hashCode(); } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AndConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AndConstraintComponent.java index bfc71a8e135..3326c1cc1d3 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AndConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AndConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -176,21 +177,31 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { + public boolean equals(ConstraintComponent o, IdentityHashMap guard) { + if (!(o instanceof AndConstraintComponent)) { return false; } AndConstraintComponent that = (AndConstraintComponent) o; - return and.equals(that.and); + if (and.size() != that.and.size()) { + return false; + } + + for (int i = 0; i < and.size(); i++) { + if (!and.get(i).equals(that.and.get(i), guard)) { + return false; + } + } + return true; } @Override - public int hashCode() { - return and.hashCode() + "AndConstraintComponent".hashCode(); + public int hashCode(IdentityHashMap guard) { + int result = "AndConstraintComponent".hashCode(); + for (Shape shape : and) { + result = 31 * result + shape.hashCode(guard); + } + return result; } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClassConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClassConstraintComponent.java index 935c1cf3727..a2aee5506cb 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClassConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClassConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; @@ -23,6 +24,7 @@ import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ValidationSettings; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; @@ -403,7 +405,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -417,7 +419,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return clazz.hashCode() + "ClassConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java index a12cc650c84..025e4fe021f 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java @@ -15,6 +15,7 @@ import java.util.Collections; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Objects; import java.util.Set; @@ -556,7 +557,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -573,7 +574,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { int result = paths != null ? paths.hashCode() : 0; result = 31 * result + (ignoredProperties != null ? ignoredProperties.hashCode() : 0); return result; diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ConstraintComponent.java index e051b20057c..384d91f165b 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import org.eclipse.rdf4j.model.Literal; @@ -19,6 +20,7 @@ import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ValidationSettings; import org.eclipse.rdf4j.sail.shacl.ast.Exportable; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; @@ -74,6 +76,10 @@ SparqlFragment buildSparqlValidNodes_rsx_targetShape(Variable subject, List getDefaultMessage(); + boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap); + + int hashCode(IdentityHashMap identityHashMap); + enum Scope { none, nodeShape, diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DashHasValueInConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DashHasValueInConstraintComponent.java index 2839d3bcd71..2324f71d007 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DashHasValueInConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DashHasValueInConstraintComponent.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.IdentityHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -27,6 +28,7 @@ import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ValidationSettings; import org.eclipse.rdf4j.sail.shacl.ast.ShaclAstLists; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; @@ -224,7 +226,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -238,7 +240,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return hasValueIn.hashCode() + "DashHasValueInConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DatatypeConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DatatypeConstraintComponent.java index b2571243577..62c8447320a 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DatatypeConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DatatypeConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.function.Function; @@ -24,6 +25,7 @@ import org.eclipse.rdf4j.model.vocabulary.RSX; import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.DatatypeFilter; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.FilterPlanNode; @@ -94,7 +96,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -108,7 +110,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return datatype.hashCode() + "DatatypeConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DisjointConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DisjointConstraintComponent.java index ba9d6b2e03c..cf21ee80ed5 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DisjointConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DisjointConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import org.eclipse.rdf4j.model.IRI; @@ -62,7 +63,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -76,7 +77,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return predicate.hashCode() + "DisjointConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/EqualsConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/EqualsConstraintComponent.java index bf7ffedb77e..43cb2b0e5f6 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/EqualsConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/EqualsConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import org.eclipse.rdf4j.model.IRI; @@ -62,7 +63,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -76,7 +77,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return predicate.hashCode() + "EqualsConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/HasValueConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/HasValueConstraintComponent.java index a4f3d66b11c..11cfed73fcf 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/HasValueConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/HasValueConstraintComponent.java @@ -13,6 +13,7 @@ import java.util.Collections; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; @@ -24,6 +25,7 @@ import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ValidationSettings; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; @@ -235,7 +237,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -249,7 +251,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return hasValue.hashCode() + "HasValueConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/InConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/InConstraintComponent.java index df2b549f8ad..0932904aaa5 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/InConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/InConstraintComponent.java @@ -12,6 +12,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; import java.util.Collections; +import java.util.IdentityHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -25,6 +26,7 @@ import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ast.ShaclAstLists; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.FilterPlanNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode; @@ -109,7 +111,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -123,7 +125,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return in.hashCode() + "InConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LanguageInConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LanguageInConstraintComponent.java index a6542ae19aa..ba0e94365ef 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LanguageInConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LanguageInConstraintComponent.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.function.Function; @@ -28,6 +29,7 @@ import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ast.ShaclAstLists; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.FilterPlanNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.LanguageInFilter; @@ -123,7 +125,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -137,7 +139,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return languageIn.hashCode() + "LanguageInConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanConstraintComponent.java index 9dc72f88ced..81bf4afb87f 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import org.eclipse.rdf4j.model.IRI; @@ -62,7 +63,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -76,7 +77,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return predicate.hashCode() + "LessThanConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanOrEqualsConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanOrEqualsConstraintComponent.java index 8b5e39cfecc..7281218907e 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanOrEqualsConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanOrEqualsConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import org.eclipse.rdf4j.model.IRI; @@ -62,7 +63,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -76,7 +77,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return predicate.hashCode() + "LessThanOrEqualsConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxCountConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxCountConstraintComponent.java index f38c04ac44a..3e287815a6f 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxCountConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxCountConstraintComponent.java @@ -16,6 +16,7 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Optional; import java.util.Set; @@ -27,6 +28,7 @@ import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ValidationSettings; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; import org.eclipse.rdf4j.sail.shacl.ast.ValidationApproach; @@ -248,7 +250,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -262,7 +264,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return Long.hashCode(maxCount) + "MaxCountConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxExclusiveConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxExclusiveConstraintComponent.java index 8f1ef0cd175..bc772caad8b 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxExclusiveConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxExclusiveConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.function.Function; @@ -23,6 +24,7 @@ import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.query.algebra.Compare; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.FilterPlanNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.LiteralComparatorFilter; @@ -73,7 +75,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -87,7 +89,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return maxExclusive.hashCode() + "MaxExclusiveConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxInclusiveConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxInclusiveConstraintComponent.java index 2713f231748..44c39f7efeb 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxInclusiveConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxInclusiveConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.function.Function; @@ -23,6 +24,7 @@ import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.query.algebra.Compare; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.FilterPlanNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.LiteralComparatorFilter; @@ -73,7 +75,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -87,7 +89,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return maxInclusive.hashCode() + "MaxInclusiveConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxLengthConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxLengthConstraintComponent.java index 63f3cd979d6..3d34becfa01 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxLengthConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MaxLengthConstraintComponent.java @@ -14,6 +14,7 @@ import static org.eclipse.rdf4j.model.util.Values.literal; import java.math.BigInteger; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.function.Function; @@ -25,6 +26,7 @@ import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.FilterPlanNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.MaxLengthFilter; @@ -76,7 +78,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -90,7 +92,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return Long.hashCode(maxLength) + "MaxLengthConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinCountConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinCountConstraintComponent.java index 7f4f4af9ae6..c8c1e6e7dd6 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinCountConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinCountConstraintComponent.java @@ -16,6 +16,7 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; @@ -26,6 +27,7 @@ import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ValidationSettings; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; import org.eclipse.rdf4j.sail.shacl.ast.ValidationApproach; import org.eclipse.rdf4j.sail.shacl.ast.ValidationQuery; @@ -223,7 +225,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -237,7 +239,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return Long.hashCode(minCount) + "MinCountConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinExclusiveConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinExclusiveConstraintComponent.java index b8448811711..a9e994862fe 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinExclusiveConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinExclusiveConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.function.Function; @@ -23,6 +24,7 @@ import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.query.algebra.Compare; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.FilterPlanNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.LiteralComparatorFilter; @@ -73,7 +75,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -87,7 +89,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return minExclusive.hashCode() + "MinExclusiveConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinInclusiveConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinInclusiveConstraintComponent.java index d9654cc37b8..8777e0b855b 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinInclusiveConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinInclusiveConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.function.Function; @@ -23,6 +24,7 @@ import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.query.algebra.Compare; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.FilterPlanNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.LiteralComparatorFilter; @@ -73,7 +75,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -87,7 +89,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return minInclusive.hashCode() + "MinInclusiveConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinLengthConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinLengthConstraintComponent.java index 792cb8aaf45..086c939bc83 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinLengthConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/MinLengthConstraintComponent.java @@ -14,6 +14,7 @@ import static org.eclipse.rdf4j.model.util.Values.literal; import java.math.BigInteger; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.function.Function; @@ -25,6 +26,7 @@ import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.FilterPlanNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.MinLengthFilter; @@ -76,7 +78,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -90,7 +92,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return Long.hashCode(minLength) + "MinLengthConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/NodeKindConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/NodeKindConstraintComponent.java index e165851bb5b..0836dcd0a7e 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/NodeKindConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/NodeKindConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.function.Function; @@ -22,6 +23,7 @@ import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.FilterPlanNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.NodeKindFilter; @@ -113,7 +115,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -127,7 +129,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return nodeKind.hashCode() + "NodeKindConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/NotConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/NotConstraintComponent.java index 40f793308ac..42a9ded6b42 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/NotConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/NotConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; @@ -212,21 +213,15 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { - if (this == o) { - return true; + public boolean equals(ConstraintComponent that, IdentityHashMap guard) { + if (that instanceof NotConstraintComponent) { + return not.equals(((NotConstraintComponent) that).not, guard); } - if (o == null || getClass() != o.getClass()) { - return false; - } - - NotConstraintComponent that = (NotConstraintComponent) o; - - return not.equals(that.not); + return false; } @Override - public int hashCode() { - return not.hashCode() + "NotConstraintComponent".hashCode(); + public int hashCode(IdentityHashMap guard) { + return not.hashCode(guard) + "NotConstraintComponent".hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/OrConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/OrConstraintComponent.java index 393e7cbd9a4..d132b217b5d 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/OrConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/OrConstraintComponent.java @@ -12,6 +12,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; import java.util.Collections; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -213,21 +214,29 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { + public boolean equals(ConstraintComponent o, IdentityHashMap guard) { + if (!(o instanceof OrConstraintComponent)) { return false; } - OrConstraintComponent that = (OrConstraintComponent) o; - return or.equals(that.or); + if (or.size() != that.or.size()) { + return false; + } + for (int i = 0; i < or.size(); i++) { + if (!or.get(i).equals(that.or.get(i), guard)) { + return false; + } + } + return true; } @Override - public int hashCode() { - return or.hashCode() + "OrConstraintComponent".hashCode(); + public int hashCode(IdentityHashMap guard) { + int result = "OrConstraintComponent".hashCode(); + for (Shape shape : or) { + result = 31 * result + shape.hashCode(guard); + } + return result; } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/PatternConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/PatternConstraintComponent.java index 8536421133f..a6989d55a70 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/PatternConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/PatternConstraintComponent.java @@ -13,6 +13,7 @@ import static org.eclipse.rdf4j.model.util.Values.literal; +import java.util.IdentityHashMap; import java.util.List; import java.util.Objects; import java.util.Set; @@ -26,6 +27,7 @@ import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.FilterPlanNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PatternFilter; @@ -148,7 +150,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -165,7 +167,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { int result = pattern.hashCode(); result = 31 * result + (flags != null ? flags.hashCode() : 0); return result + "PatternConstraintComponent".hashCode(); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/QualifiedMaxCountConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/QualifiedMaxCountConstraintComponent.java index 63b660bf6f5..a3b50d55b8f 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/QualifiedMaxCountConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/QualifiedMaxCountConstraintComponent.java @@ -13,6 +13,7 @@ import static org.eclipse.rdf4j.model.util.Values.literal; import java.math.BigInteger; +import java.util.IdentityHashMap; import java.util.List; import java.util.Objects; import java.util.Set; @@ -288,11 +289,8 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { + public boolean equals(ConstraintComponent o, IdentityHashMap guard) { + if (!(o instanceof QualifiedMaxCountConstraintComponent)) { return false; } @@ -301,15 +299,15 @@ public boolean equals(Object o) { if (qualifiedValueShapesDisjoint != that.qualifiedValueShapesDisjoint) { return false; } - if (!qualifiedValueShape.equals(that.qualifiedValueShape)) { + if (!qualifiedValueShape.equals(that.qualifiedValueShape, guard)) { return false; } return Objects.equals(qualifiedMaxCount, that.qualifiedMaxCount); } @Override - public int hashCode() { - int result = qualifiedValueShape.hashCode(); + public int hashCode(IdentityHashMap guard) { + int result = qualifiedValueShape.hashCode(guard); result = 31 * result + (qualifiedValueShapesDisjoint ? 1 : 0); result = 31 * result + (qualifiedMaxCount != null ? qualifiedMaxCount.hashCode() : 0); return result + "QualifiedMaxCountConstraintComponent".hashCode(); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/QualifiedMinCountConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/QualifiedMinCountConstraintComponent.java index a3ad8f3a70e..b68b5ccbe0e 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/QualifiedMinCountConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/QualifiedMinCountConstraintComponent.java @@ -14,6 +14,7 @@ import static org.eclipse.rdf4j.model.util.Values.literal; import java.math.BigInteger; +import java.util.IdentityHashMap; import java.util.List; import java.util.Objects; import java.util.Set; @@ -287,11 +288,8 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { + public boolean equals(ConstraintComponent o, IdentityHashMap guard) { + if (!(o instanceof QualifiedMinCountConstraintComponent)) { return false; } @@ -300,15 +298,15 @@ public boolean equals(Object o) { if (qualifiedValueShapesDisjoint != that.qualifiedValueShapesDisjoint) { return false; } - if (!qualifiedValueShape.equals(that.qualifiedValueShape)) { + if (!qualifiedValueShape.equals(that.qualifiedValueShape, guard)) { return false; } return Objects.equals(qualifiedMinCount, that.qualifiedMinCount); } @Override - public int hashCode() { - int result = qualifiedValueShape.hashCode(); + public int hashCode(IdentityHashMap guard) { + int result = qualifiedValueShape.hashCode(guard); result = 31 * result + (qualifiedValueShapesDisjoint ? 1 : 0); result = 31 * result + (qualifiedMinCount != null ? qualifiedMinCount.hashCode() : 0); return result + "QualifiedMinCountConstraintComponent".hashCode(); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/SparqlConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/SparqlConstraintComponent.java index 77af99c9376..e4b1aa3ea92 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/SparqlConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/SparqlConstraintComponent.java @@ -12,6 +12,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; import java.util.ArrayList; +import java.util.IdentityHashMap; import java.util.List; import java.util.Objects; import java.util.Set; @@ -254,7 +255,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -280,7 +281,7 @@ public boolean equals(Object o) { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { int result = (produceValidationReports ? 1 : 0); result = 31 * result + select.hashCode(); result = 31 * result + originalSelect.hashCode(); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/UniqueLangConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/UniqueLangConstraintComponent.java index 99858731510..1097cab12d2 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/UniqueLangConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/UniqueLangConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import java.util.Optional; import java.util.Set; @@ -23,6 +24,7 @@ import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ValidationSettings; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; import org.eclipse.rdf4j.sail.shacl.ast.ValidationApproach; import org.eclipse.rdf4j.sail.shacl.ast.ValidationQuery; @@ -225,12 +227,12 @@ public List getDefaultMessage() { } @Override - public int hashCode() { + public int hashCode(IdentityHashMap identityHashMap) { return "UniqueLangConstraintComponent".hashCode(); } @Override - public boolean equals(Object obj) { - return obj instanceof UniqueLangConstraintComponent; + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { + return o instanceof UniqueLangConstraintComponent; } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/VoidConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/VoidConstraintComponent.java index f658cf12b45..8d9c2a3aea4 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/VoidConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/VoidConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; @@ -20,6 +21,7 @@ import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ValidationSettings; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; import org.eclipse.rdf4j.sail.shacl.ast.ValidationApproach; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.EmptyNode; @@ -81,7 +83,7 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { + public boolean equals(ConstraintComponent o, IdentityHashMap kvIdentityHashMap) { if (this == o) { return true; } @@ -89,11 +91,11 @@ public boolean equals(Object o) { return false; } - return super.equals(o); + return true; } @Override - public int hashCode() { - return super.hashCode(); + public int hashCode(IdentityHashMap identityHashMap) { + return this.getClass().hashCode(); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/XoneConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/XoneConstraintComponent.java index f7997c207b1..cb59eb471ea 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/XoneConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/XoneConstraintComponent.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; +import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -99,21 +100,30 @@ public List getDefaultMessage() { } @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { + public boolean equals(ConstraintComponent o, IdentityHashMap guard) { + if (!(o instanceof XoneConstraintComponent)) { return false; } XoneConstraintComponent that = (XoneConstraintComponent) o; - return xone.equals(that.xone); + if (xone.size() != that.xone.size()) { + return false; + } + for (int i = 0; i < xone.size(); i++) { + if (!xone.get(i).equals(that.xone.get(i), guard)) { + return false; + } + } + return true; } @Override - public int hashCode() { - return xone.hashCode() + "XoneConstraintComponent".hashCode(); + public int hashCode(IdentityHashMap guard) { + int result = "XoneConstraintComponent".hashCode(); + for (Shape shape : xone) { + result = 31 * result + shape.hashCode(guard); + } + return result; } } diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/DeepRecursionValidationTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/DeepRecursionValidationTest.java new file mode 100644 index 00000000000..27ffcb5e946 --- /dev/null +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/DeepRecursionValidationTest.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +package org.eclipse.rdf4j.sail.shacl; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.URL; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.rdf4j.common.transaction.IsolationLevels; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.util.Values; +import org.eclipse.rdf4j.model.vocabulary.RDF4J; +import org.eclipse.rdf4j.model.vocabulary.RDFS; +import org.eclipse.rdf4j.repository.RepositoryException; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.rio.RDFFormat; +import org.eclipse.rdf4j.sail.memory.MemoryStore; +import org.eclipse.rdf4j.sail.shacl.ast.ShaclShapeParsingException; +import org.junit.jupiter.api.Test; + +class DeepRecursionValidationTest { + + @Test + void recursiveShapesCanBeRemovedAfterFailedValidation() throws Exception { + ShaclSail shaclSail = new ShaclSail(new MemoryStore()); + shaclSail.setShapesGraphs(Set.of(RDF4J.SHACL_SHAPE_GRAPH)); + SailRepository shaclRepository = new SailRepository(shaclSail); + shaclRepository.init(); + + URL shapes = Objects.requireNonNull( + getClass().getClassLoader().getResource("recursion/deep/shacl.trig")); + + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(IsolationLevels.NONE, ShaclSail.TransactionSettings.ValidationApproach.Disabled); + connection.add(shapes, shapes.toString(), RDFFormat.TRIG, RDF4J.SHACL_SHAPE_GRAPH); + connection.commit(); + } + + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(); + connection.add(RDF4J.SHACL_SHAPE_GRAPH, RDF4J.SHACL_SHAPE_GRAPH, RDF4J.SHACL_SHAPE_GRAPH); + + assertThrows(ShaclShapeParsingException.class, connection::commit); + connection.rollback(); + } + + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(); + connection.clear(RDF4J.SHACL_SHAPE_GRAPH); + connection.commit(); + + assertFalse(connection.hasStatement(null, null, null, false, RDF4J.SHACL_SHAPE_GRAPH)); + } finally { + shaclRepository.shutDown(); + } + } + + @Test + void recursiveShapesCanBeRemovedAfterFailedValidation2() throws Exception { + ShaclSail shaclSail = new ShaclSail(new MemoryStore()); + shaclSail.setShapesGraphs(Set.of(RDF4J.NIL)); + + SailRepository shaclRepository = new SailRepository(shaclSail); + shaclRepository.init(); + + URL shapes = Objects.requireNonNull( + getClass().getClassLoader().getResource("recursion/deep/shacl.trig")); + + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(IsolationLevels.NONE, ShaclSail.TransactionSettings.ValidationApproach.Disabled); + connection.add(shapes, shapes.toString(), RDFFormat.TRIG); + connection.commit(); + } + + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(); + connection.add(Values.bnode(), RDFS.LABEL, Values.literal("This will fail")); + + assertThrows(ShaclShapeParsingException.class, connection::commit); + connection.rollback(); + } + + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(); + connection.clear(new Resource[1]); + connection.commit(); + + assertFalse(connection.hasStatement(null, null, null, false)); + } finally { + shaclRepository.shutDown(); + } + } + + @Test + void deepRecursionAcrossAllConstraintTypesValidates() throws Exception { + SailRepository shaclRepository = Utils.getInitializedShaclRepository( + "recursion/deep/shacl.trig"); + + ShaclSail shaclSail = (ShaclSail) shaclRepository.getSail(); + shaclSail.setShapesGraphs(Set.of(RDF4J.NIL)); + + URL data = Objects.requireNonNull( + getClass().getClassLoader().getResource("recursion/deep/data.trig")); + + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(); + connection.add(data, data.toString(), RDFFormat.TRIG); + + ShaclShapeParsingException exception = assertThrows(ShaclShapeParsingException.class, () -> { + try { + connection.commit(); + } catch (RepositoryException e) { + connection.rollback(); + throw e.getCause(); + } + }); + + assertNotNull(exception.getId()); + + } finally { + shaclRepository.shutDown(); + } + } +} diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/LogicalRecursionValidationTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/LogicalRecursionValidationTest.java new file mode 100644 index 00000000000..feaada5ffee --- /dev/null +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/LogicalRecursionValidationTest.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +package org.eclipse.rdf4j.sail.shacl; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.URL; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.rdf4j.model.vocabulary.RDF4J; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.rio.RDFFormat; +import org.eclipse.rdf4j.sail.shacl.ast.ShaclShapeParsingException; +import org.junit.jupiter.api.Test; + +class LogicalRecursionValidationTest { + + @Test + void recursionInNotIsDetected() throws Exception { + SailRepository shaclRepository = Utils + .getInitializedShaclRepository("recursion/logical/not/shacl.trig"); + + ShaclSail shaclSail = (ShaclSail) shaclRepository.getSail(); + shaclSail.setShapesGraphs(Set.of(RDF4J.NIL)); + + URL data = Objects.requireNonNull( + getClass().getClassLoader().getResource("recursion/logical/not/data.trig")); + + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(); + connection.add(data, data.toString(), RDFFormat.TRIG); + + assertThrows(ShaclShapeParsingException.class, () -> { + try { + connection.commit(); + } catch (Exception e) { + connection.rollback(); + throw e; + } + }); + } + } + + @Test + void recursionInOrIsDetected() throws Exception { + SailRepository shaclRepository = Utils + .getInitializedShaclRepository("recursion/logical/or/shacl.trig"); + + ShaclSail shaclSail = (ShaclSail) shaclRepository.getSail(); + shaclSail.setShapesGraphs(Set.of(RDF4J.NIL)); + + URL data = Objects.requireNonNull( + getClass().getClassLoader().getResource("recursion/logical/or/data.trig")); + + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(); + connection.add(data, data.toString(), RDFFormat.TRIG); + + assertThrows(ShaclShapeParsingException.class, () -> { + try { + connection.commit(); + } catch (Exception e) { + connection.rollback(); + throw e; + } + }); + } + } + + @Test + void recursionInNodeIsDetected() throws Exception { + SailRepository shaclRepository = Utils + .getInitializedShaclRepository("recursion/node/selfNode/shacl.trig"); + + ShaclSail shaclSail = (ShaclSail) shaclRepository.getSail(); + shaclSail.setShapesGraphs(Set.of(RDF4J.NIL)); + + URL data = Objects.requireNonNull( + getClass().getClassLoader().getResource("recursion/node/selfNode/data.trig")); + + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(); + connection.add(data, data.toString(), RDFFormat.TRIG); + + assertThrows(ShaclShapeParsingException.class, () -> { + try { + connection.commit(); + } catch (Exception e) { + connection.rollback(); + throw e; + } + }); + } + } +} diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/PropertyRecursionValidationTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/PropertyRecursionValidationTest.java new file mode 100644 index 00000000000..da95ec879cc --- /dev/null +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/PropertyRecursionValidationTest.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +package org.eclipse.rdf4j.sail.shacl; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.URL; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.rdf4j.model.vocabulary.RDF4J; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.rio.RDFFormat; +import org.eclipse.rdf4j.sail.shacl.ast.ShaclShapeParsingException; +import org.junit.jupiter.api.Test; + +class PropertyRecursionValidationTest { + + @Test + void nodeShapeRecursingThroughPropertyShapeWithOnlyNodeIsDetected() throws Exception { + SailRepository shaclRepository = Utils.getInitializedShaclRepository( + "recursion/property/selfNodeOnly/shacl.trig"); + + ShaclSail shaclSail = (ShaclSail) shaclRepository.getSail(); + shaclSail.setShapesGraphs(Set.of(RDF4J.NIL)); + + URL data = Objects.requireNonNull( + getClass().getClassLoader() + .getResource("recursion/property/selfNodeOnly/data.trig")); + + try (SailRepositoryConnection connection = shaclRepository.getConnection()) { + connection.begin(); + connection.add(data, data.toString(), RDFFormat.TRIG); + + assertThrows(ShaclShapeParsingException.class, () -> { + try { + connection.commit(); + } catch (Exception e) { + connection.rollback(); + throw e; + } + }); + } finally { + shaclRepository.shutDown(); + } + } + +} diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ast/ShapeEqualsTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ast/ShapeEqualsTest.java new file mode 100644 index 00000000000..752e146bb47 --- /dev/null +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ast/ShapeEqualsTest.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +package org.eclipse.rdf4j.sail.shacl.ast; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.rdf4j.model.vocabulary.XSD; +import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.DatatypeConstraintComponent; +import org.junit.jupiter.api.Test; + +public class ShapeEqualsTest { + + @Test + public void shapesWithDuplicateConstraintComponentsAreNotEqualToShapesWithDifferentComponents() { + NodeShape shapeWithDuplicate = new NodeShape(); + + DatatypeConstraintComponent firstConstraint = new DatatypeConstraintComponent(XSD.STRING); + + shapeWithDuplicate.constraintComponents.add(firstConstraint); + shapeWithDuplicate.constraintComponents.add(firstConstraint); + + NodeShape shapeWithDifferentSecondConstraint = new NodeShape(); + + DatatypeConstraintComponent differentConstraint = new DatatypeConstraintComponent(XSD.INT); + + shapeWithDifferentSecondConstraint.constraintComponents.add(firstConstraint); + shapeWithDifferentSecondConstraint.constraintComponents.add(differentConstraint); + + assertTrue(shapeWithDuplicate.equals(shapeWithDuplicate)); + assertTrue(shapeWithDifferentSecondConstraint.equals(shapeWithDifferentSecondConstraint)); + + assertFalse(shapeWithDuplicate.equals(shapeWithDifferentSecondConstraint)); + assertFalse(shapeWithDifferentSecondConstraint.equals(shapeWithDuplicate)); + } + + @Test + public void shapesWithSameComponentsInDifferentOrderAreEqual() { + NodeShape first = new NodeShape(); + NodeShape second = new NodeShape(); + + DatatypeConstraintComponent firstConstraint = new DatatypeConstraintComponent(XSD.STRING); + DatatypeConstraintComponent secondConstraint = new DatatypeConstraintComponent(XSD.INT); + + first.constraintComponents.add(firstConstraint); + first.constraintComponents.add(secondConstraint); + + second.constraintComponents.add(secondConstraint); + second.constraintComponents.add(firstConstraint); + + assertTrue(first.equals(second)); + assertTrue(second.equals(first)); + } +} diff --git a/core/sail/shacl/src/test/resources/recursion/deep/data.trig b/core/sail/shacl/src/test/resources/recursion/deep/data.trig new file mode 100644 index 00000000000..81344fba07d --- /dev/null +++ b/core/sail/shacl/src/test/resources/recursion/deep/data.trig @@ -0,0 +1,28 @@ +@prefix ex: . +@prefix rdf: . + +{ + ex:root a ex:Root ; + ex:next ex:node1 ; + ex:friend ex:friend1 ; + ex:xoneA "A" ; + ex:xoneB "B" ; + ex:notFlag "ban" . + + ex:node1 ex:next ex:node2 . + ex:node2 ex:next ex:node3 . + ex:node3 ex:next ex:node4 . + ex:node4 ex:next ex:node5 . + ex:node5 ex:next ex:node6 . + ex:node6 ex:next ex:node7 . + ex:node7 ex:next ex:node8 . + ex:node8 ex:next ex:node9 . + ex:node9 ex:next ex:node10 . + ex:node10 ex:next ex:node11 . + ex:node11 ex:next ex:node12 . + + ex:node12 ex:value "twelve" . + + ex:friend1 rdf:type ex:Friend . + +} diff --git a/core/sail/shacl/src/test/resources/recursion/deep/shacl.trig b/core/sail/shacl/src/test/resources/recursion/deep/shacl.trig new file mode 100644 index 00000000000..da44678e146 --- /dev/null +++ b/core/sail/shacl/src/test/resources/recursion/deep/shacl.trig @@ -0,0 +1,178 @@ +@prefix ex: . +@prefix sh: . +@prefix xsd: . +@prefix rdf: . +@prefix rdf4j: . + +{ + ex:RootShape a sh:NodeShape ; + sh:targetClass ex:Root ; + sh:property ex:RootNextProperty ; + sh:property ex:RootFriendProperty ; + sh:property ex:RootLoopProperty ; + sh:node ex:SelfCheckShape ; + sh:and ( ex:AndShape ) ; + sh:or ( ex:OrShapeA ex:OrShapeB ) ; + sh:xone ( ex:XoneShapeA ex:XoneShapeB ) ; + sh:not ex:NotShape . + + ex:RootNextProperty a sh:PropertyShape ; + sh:path ex:next ; + sh:minCount 1 ; + sh:node ex:ChainShape1 . + + ex:RootFriendProperty a sh:PropertyShape ; + sh:path ex:friend ; + sh:qualifiedValueShape ex:QualifiedShape ; + sh:qualifiedMinCount 1 . + + ex:RootLoopProperty a sh:PropertyShape ; + sh:path ex:loop ; + sh:minCount 1 ; + sh:node ex:RootShape . + + ex:SelfCheckShape a sh:NodeShape ; + sh:property [ + sh:path ex:selfCheck ; + sh:hasValue "ok" + ] . + + ex:AndShape a sh:NodeShape ; + sh:property [ + sh:path ex:andFlag ; + sh:hasValue "ok" + ] . + + ex:OrShapeA a sh:NodeShape ; + sh:property [ + sh:path ex:orA ; + sh:hasValue "A" + ] . + + ex:OrShapeB a sh:NodeShape ; + sh:property [ + sh:path ex:orB ; + sh:hasValue "B" + ] . + + ex:XoneShapeA a sh:NodeShape ; + sh:property [ + sh:path ex:xoneA ; + sh:hasValue "A" + ] . + + ex:XoneShapeB a sh:NodeShape ; + sh:property [ + sh:path ex:xoneB ; + sh:hasValue "B" + ] . + + ex:NotShape a sh:NodeShape ; + sh:property [ + sh:path ex:notFlag ; + sh:hasValue "ban" + ] . + + ex:QualifiedShape a sh:NodeShape ; + sh:property [ + sh:path ex:friendFlag ; + sh:hasValue "friend" + ] . + + ex:ChainShape1 a sh:NodeShape ; + sh:property ex:ChainShape1Property . + + ex:ChainShape1Property a sh:PropertyShape ; + sh:path ex:next ; + sh:minCount 1 ; + sh:node ex:ChainShape2 . + + ex:ChainShape2 a sh:NodeShape ; + sh:property ex:ChainShape2Property . + + ex:ChainShape2Property a sh:PropertyShape ; + sh:path ex:next ; + sh:minCount 1 ; + sh:node ex:ChainShape3 . + + ex:ChainShape3 a sh:NodeShape ; + sh:property ex:ChainShape3Property . + + ex:ChainShape3Property a sh:PropertyShape ; + sh:path ex:next ; + sh:minCount 1 ; + sh:node ex:ChainShape4 . + + ex:ChainShape4 a sh:NodeShape ; + sh:property ex:ChainShape4Property . + + ex:ChainShape4Property a sh:PropertyShape ; + sh:path ex:next ; + sh:minCount 1 ; + sh:node ex:ChainShape5 . + + ex:ChainShape5 a sh:NodeShape ; + sh:property ex:ChainShape5Property . + + ex:ChainShape5Property a sh:PropertyShape ; + sh:path ex:next ; + sh:minCount 1 ; + sh:node ex:ChainShape6 . + + ex:ChainShape6 a sh:NodeShape ; + sh:property ex:ChainShape6Property . + + ex:ChainShape6Property a sh:PropertyShape ; + sh:path ex:next ; + sh:minCount 1 ; + sh:node ex:ChainShape7 . + + ex:ChainShape7 a sh:NodeShape ; + sh:property ex:ChainShape7Property . + + ex:ChainShape7Property a sh:PropertyShape ; + sh:path ex:next ; + sh:minCount 1 ; + sh:node ex:ChainShape8 . + + ex:ChainShape8 a sh:NodeShape ; + sh:property ex:ChainShape8Property . + + ex:ChainShape8Property a sh:PropertyShape ; + sh:path ex:next ; + sh:minCount 1 ; + sh:node ex:ChainShape9 . + + ex:ChainShape9 a sh:NodeShape ; + sh:property ex:ChainShape9Property . + + ex:ChainShape9Property a sh:PropertyShape ; + sh:path ex:next ; + sh:minCount 1 ; + sh:node ex:ChainShape10 . + + ex:ChainShape10 a sh:NodeShape ; + sh:property ex:ChainShape10Property . + + ex:ChainShape10Property a sh:PropertyShape ; + sh:path ex:next ; + sh:minCount 1 ; + sh:node ex:ChainShape11 . + + ex:ChainShape11 a sh:NodeShape ; + sh:property ex:ChainShape11Property . + + ex:ChainShape11Property a sh:PropertyShape ; + sh:path ex:next ; + sh:minCount 1 ; + sh:node ex:ChainShape12 . + + ex:ChainShape12 a sh:NodeShape ; + sh:property ex:ChainShape12Property . + + ex:ChainShape12Property a sh:PropertyShape ; + sh:path ex:value ; + sh:datatype xsd:integer . + + rdf4j:nil sh:shapesGraph rdf4j:nil . +} diff --git a/core/sail/shacl/src/test/resources/recursion/logical/data.trig b/core/sail/shacl/src/test/resources/recursion/logical/data.trig new file mode 100644 index 00000000000..828c7448b14 --- /dev/null +++ b/core/sail/shacl/src/test/resources/recursion/logical/data.trig @@ -0,0 +1,6 @@ +@prefix ex: . +@prefix rdf: . + +{ + ex:placeholder rdf:type ex:Placeholder . +} diff --git a/core/sail/shacl/src/test/resources/recursion/logical/not/data.trig b/core/sail/shacl/src/test/resources/recursion/logical/not/data.trig new file mode 100644 index 00000000000..918036ec3dc --- /dev/null +++ b/core/sail/shacl/src/test/resources/recursion/logical/not/data.trig @@ -0,0 +1,6 @@ +@prefix ex: . +@prefix rdf: . + +{ + ex:n1 rdf:type ex:Node . +} diff --git a/core/sail/shacl/src/test/resources/recursion/logical/not/shacl.trig b/core/sail/shacl/src/test/resources/recursion/logical/not/shacl.trig new file mode 100644 index 00000000000..fd548b1a592 --- /dev/null +++ b/core/sail/shacl/src/test/resources/recursion/logical/not/shacl.trig @@ -0,0 +1,11 @@ +@prefix ex: . +@prefix sh: . +@prefix rdf4j: . + +{ + ex:RootShape a sh:NodeShape ; + sh:targetClass ex:Node ; + sh:not ex:RootShape . + + rdf4j:nil sh:shapesGraph rdf4j:nil . +} diff --git a/core/sail/shacl/src/test/resources/recursion/logical/or/data.trig b/core/sail/shacl/src/test/resources/recursion/logical/or/data.trig new file mode 100644 index 00000000000..918036ec3dc --- /dev/null +++ b/core/sail/shacl/src/test/resources/recursion/logical/or/data.trig @@ -0,0 +1,6 @@ +@prefix ex: . +@prefix rdf: . + +{ + ex:n1 rdf:type ex:Node . +} diff --git a/core/sail/shacl/src/test/resources/recursion/logical/or/shacl.trig b/core/sail/shacl/src/test/resources/recursion/logical/or/shacl.trig new file mode 100644 index 00000000000..c66869e42c1 --- /dev/null +++ b/core/sail/shacl/src/test/resources/recursion/logical/or/shacl.trig @@ -0,0 +1,14 @@ +@prefix ex: . +@prefix sh: . +@prefix rdf4j: . + +{ + ex:RootShape a sh:NodeShape ; + sh:targetClass ex:Node ; + sh:or ( ex:RootShape ex:OtherShape ) . + + ex:OtherShape a sh:NodeShape ; + sh:property [ sh:path ex:p ; sh:minCount 0 ] . + + rdf4j:nil sh:shapesGraph rdf4j:nil . +} diff --git a/core/sail/shacl/src/test/resources/recursion/logical/shacl.trig b/core/sail/shacl/src/test/resources/recursion/logical/shacl.trig new file mode 100644 index 00000000000..73d15e74d7c --- /dev/null +++ b/core/sail/shacl/src/test/resources/recursion/logical/shacl.trig @@ -0,0 +1,7 @@ +@prefix ex: . +@prefix sh: . + +{ + ex:PlaceholderShape a sh:NodeShape ; + sh:targetClass ex:Placeholder . +} diff --git a/core/sail/shacl/src/test/resources/recursion/node/selfNode/data.trig b/core/sail/shacl/src/test/resources/recursion/node/selfNode/data.trig new file mode 100644 index 00000000000..9977cf1ee67 --- /dev/null +++ b/core/sail/shacl/src/test/resources/recursion/node/selfNode/data.trig @@ -0,0 +1,7 @@ +@prefix ex: . +@prefix rdf: . + +{ + ex:n1 rdf:type ex:Node . +} + diff --git a/core/sail/shacl/src/test/resources/recursion/node/selfNode/shacl.trig b/core/sail/shacl/src/test/resources/recursion/node/selfNode/shacl.trig new file mode 100644 index 00000000000..93d3372b378 --- /dev/null +++ b/core/sail/shacl/src/test/resources/recursion/node/selfNode/shacl.trig @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix sh: . +@prefix rdf4j: . + +{ + ex:RootShape a sh:NodeShape ; + sh:targetClass ex:Node ; + sh:node ex:RootShape . + + rdf4j:nil sh:shapesGraph rdf4j:nil . +} + diff --git a/core/sail/shacl/src/test/resources/recursion/property/selfNodeOnly/data.trig b/core/sail/shacl/src/test/resources/recursion/property/selfNodeOnly/data.trig new file mode 100644 index 00000000000..6bfa177412a --- /dev/null +++ b/core/sail/shacl/src/test/resources/recursion/property/selfNodeOnly/data.trig @@ -0,0 +1,8 @@ +@prefix ex: . +@prefix rdf: . + +{ + ex:n1 rdf:type ex:Node ; + ex:p ex:n1 . +} + diff --git a/core/sail/shacl/src/test/resources/recursion/property/selfNodeOnly/shacl.trig b/core/sail/shacl/src/test/resources/recursion/property/selfNodeOnly/shacl.trig new file mode 100644 index 00000000000..1df7574e95c --- /dev/null +++ b/core/sail/shacl/src/test/resources/recursion/property/selfNodeOnly/shacl.trig @@ -0,0 +1,16 @@ +@prefix ex: . +@prefix sh: . +@prefix rdf4j: . + +{ + ex:RootShape a sh:NodeShape ; + sh:targetClass ex:Node ; + sh:property ex:RecursiveProperty . + + ex:RecursiveProperty a sh:PropertyShape ; + sh:path ex:p ; + sh:node ex:RootShape . + + rdf4j:nil sh:shapesGraph rdf4j:nil . +} +