Package: groovy.grape.ivy

## [Groovy] Class StrictCachedGrapesResolver

- groovy.grape.ivy.StrictCachedGrapesResolver

-     @CompileStatic
      class StrictCachedGrapesResolver
      extends FileSystemResolver

  org.apache.ivy.plugins.resolver.FileSystemResolver subclass that hardens Grape's `cachedGrapes` resolver against stub-shaped synthetic descriptors.

  Grape's default `cachedGrapes` (a plain `<filesystem>`) synthesises a minimal ivy descriptor when it finds a JAR but no ivy file alongside — `<publications>` only, no `<dependencies>`. The synthetic descriptor is then written back to disk for next time, so the first artifact-only state perpetuates itself. Any module hit via the chain after that returns only the JAR (no transitive deps), causing runtime classloading failures — e.g. when log4j-core's JAR is present but log4j-api is missing, calls into the log4j API raise `NoClassDefFoundError` / `MissingPropertyException`.

  This resolver guards three failure modes:

  1. Sets `descriptor=required` in the constructor — prevents new stub synthesis. If the JAR exists but no ivy descriptor, the resolver returns null and the chain falls through to localm2/ibiblio where the real POM gets parsed.
  2. Overrides findIvyFileRef to reject existing stubs — if an ivy descriptor exists but its companion `ivy-<rev>.xml.original` (the original POM) does not, treats the descriptor as if not present. The `.original` file is Ivy's record that the descriptor was downloaded from a real repo; its absence is the load-bearing signal that we're looking at a synthesised stub rather than a real cached descriptor. This silently self-heals existing stub corruption.
  3. Validates the cached primary JAR's zip integrity. If the JAR exists but JarFile can't open it (e.g. truncated download from a CDN 429 or interrupted process), evicts the JAR + descriptor + `.original` and returns null. The chain falls through to localm2/ibiblio and Ivy re-downloads a fresh copy in the same `@Grab` call — no second grab needed. Catching corruption here, rather than at classloading time, keeps corrupt JARs off the classpath entirely.

  Strict behaviour is unconditional — if this resolver is in use, both checks run. Operators who don't want them can simply not declare this resolver in their `~/.groovy/grapeConfig.xml`, dropping back to the stock Ivy `<filesystem>` resolver. Stub-rejection skips snapshot revisions automatically (Maven snapshot caches use timestamped filenames); integrity validation does not — corruption is corruption.

- - ### Constructor Summary

    Constructors
    Constructor and description
    `StrictCachedGrapesResolver()`

  - ### Methods Summary

    Methods
    Type Params | Return Type | Name and description
    --- | --- | ---
    `` | `ResolvedResource` | `findIvyFileRef(DependencyDescriptor dd, ResolveData data)`
    `` | `static File` | `locatePrimaryJar(File ivyFile, ModuleRevisionId mrid)` Visible for testing: locate the primary JAR for a module revision in the cachedGrapes layout (`<module-dir>/jars/<name>-<rev>.jar`), given the ivy descriptor file.
    `` | `static File` | `resolvedRefAsFile(ResolvedResource ref)` Visible for testing: extract the on-disk File for a resolved ivy resource, or null if the resource is not file-backed.
    `` | `boolean` | `shouldRejectAsCorruptArtifact(ModuleRevisionId mrid, File ivyFile)` Visible for testing: decide whether to treat a cached module as having a corrupt primary JAR (truncated, zero bytes, garbage).
    `` | `boolean` | `shouldRejectAsStub(ModuleRevisionId mrid, File ivyFile)` Visible for testing: decide whether to treat a cached ivy descriptor as a stub and reject it.

- - ### Constructor Detail

    - #### StrictCachedGrapesResolver()

  - ### Method Detail

    - #### @Override
      ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data)

    - #### static File locatePrimaryJar(File ivyFile, ModuleRevisionId mrid)

      Visible for testing: locate the primary JAR for a module revision in the cachedGrapes layout (`<module-dir>/jars/<name>-<rev>.jar`), given the ivy descriptor file. Returns null if any layout assumption fails.

    - #### static File resolvedRefAsFile(ResolvedResource ref)

      Visible for testing: extract the on-disk File for a resolved ivy resource, or null if the resource is not file-backed.

    - #### boolean shouldRejectAsCorruptArtifact(ModuleRevisionId mrid, File ivyFile)

      Visible for testing: decide whether to treat a cached module as having a corrupt primary JAR (truncated, zero bytes, garbage). Returns true iff the JAR is locatable on disk in the cachedGrapes layout and opening it as a JarFile raises ZipException. Unrelated I/O errors (e.g. permissions) return false — we only reject on clear zip-format corruption.

    - #### boolean shouldRejectAsStub(ModuleRevisionId mrid, File ivyFile)

      Visible for testing: decide whether to treat a cached ivy descriptor as a stub and reject it. Returns true iff the revision is not a snapshot, the ivy file's location is known, and no companion `ivy-<rev>.xml.original` file exists alongside it.