diff --git a/pkl-core/src/main/java/org/pkl/core/module/FileResolver.java b/pkl-core/src/main/java/org/pkl/core/module/FileResolver.java index 87c4f2179..3bf008833 100644 --- a/pkl-core/src/main/java/org/pkl/core/module/FileResolver.java +++ b/pkl-core/src/main/java/org/pkl/core/module/FileResolver.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,13 +26,29 @@ import java.util.List; import org.pkl.core.util.IoUtils; +/** Utilities for inspecting file-system paths while resolving module glob imports. */ public final class FileResolver { private FileResolver() {} + /** + * Returns the immediate, non-symbolic-link children of the directory identified by {@code + * baseUri}. + * + *

Returns an empty list if {@code baseUri} does not exist or does not identify a directory. + * + * @throws IOException if the directory cannot be read + */ public static List listElements(URI baseUri) throws IOException { return listElements(IoUtils.pathOf(baseUri)); } + /** + * Returns the immediate, non-symbolic-link children of {@code path}. + * + *

Returns an empty list if {@code path} does not exist or is not a directory. + * + * @throws IOException if the directory cannot be read + */ public static List listElements(Path path) throws IOException { try (var stream = Files.newDirectoryStream(path)) { var ret = new ArrayList(); @@ -49,10 +65,12 @@ public static List listElements(Path path) throws IOException { } } + /** Returns whether the file-system path identified by {@code elementUri} exists. */ public static boolean hasElement(URI elementUri) { return Files.exists(IoUtils.pathOf(elementUri)); } + /** Returns whether {@code path} exists. */ public static boolean hasElement(Path path) { return Files.exists(path); } diff --git a/pkl-core/src/main/java/org/pkl/core/module/ModulePathResolver.java b/pkl-core/src/main/java/org/pkl/core/module/ModulePathResolver.java index 2b8ab8b27..8e4242775 100644 --- a/pkl-core/src/main/java/org/pkl/core/module/ModulePathResolver.java +++ b/pkl-core/src/main/java/org/pkl/core/module/ModulePathResolver.java @@ -57,10 +57,19 @@ public final class ModulePathResolver implements AutoCloseable { private static final ModulePathResolver EMPTY = new ModulePathResolver(Collections.emptyList()); + /** Returns a resolver whose module path contains no entries. */ public static ModulePathResolver empty() { return EMPTY; } + /** + * Creates a resolver for the given module path entries. + * + *

Each entry can be a directory, JAR file, or ZIP file. Entries are searched in iteration + * order; if more than one entry contains the same path, the first entry wins. + * + * @param modulePath the module path entries to search + */ public ModulePathResolver(Iterable modulePath) { this.modulePath = modulePath; } @@ -103,6 +112,13 @@ private Map getFileCache() throws IOException { } } + /** + * Resolves a {@code modulepath:} URI to the path containing its source. + * + * @throws FileNotFoundException if the module path contains no matching file + * @throws IOException if a module path entry cannot be read + * @throws IllegalStateException if this resolver has been closed + */ public Path resolve(URI uri) throws IOException { var modulePath = getModulePath(uri); var result = getFileCache().get(modulePath); @@ -111,6 +127,12 @@ public Path resolve(URI uri) throws IOException { throw new FileNotFoundException(); } + /** + * Returns whether the module path contains the element identified by {@code elementUri}. + * + * @throws UncheckedIOException if a module path entry cannot be read + * @throws IllegalStateException if this resolver has been closed + */ public boolean hasElement(URI elementUri) { var path = elementUri.getPath(); try { @@ -121,6 +143,11 @@ public boolean hasElement(URI elementUri) { } } + /** + * Closes file systems opened for JAR and ZIP entries. + * + *

Calling this method more than once has no effect. + */ @Override public void close() { synchronized (lock) { diff --git a/pkl-core/src/main/java/org/pkl/core/module/PathElement.java b/pkl-core/src/main/java/org/pkl/core/module/PathElement.java index 31dd8d471..568a93bca 100644 --- a/pkl-core/src/main/java/org/pkl/core/module/PathElement.java +++ b/pkl-core/src/main/java/org/pkl/core/module/PathElement.java @@ -24,7 +24,9 @@ import org.jspecify.annotations.Nullable; import org.pkl.core.util.EconomicMaps; +/** Describes a child of a hierarchical module or resource path. */ public class PathElement { + /** Orders files before directories, then orders elements lexicographically by name. */ public static final Comparator comparator = (o1, o2) -> { if (o1.isDirectory && !o2.isDirectory) { @@ -39,19 +41,23 @@ public class PathElement { private final boolean isDirectory; + /** Creates an element whose type cannot be inspected and is therefore treated as a file. */ public static PathElement opaque(String name) { return new PathElement(name, false); } + /** Creates an element with the given name and directory status. */ public PathElement(String name, boolean isDirectory) { this.name = name; this.isDirectory = isDirectory; } + /** Returns this element's name relative to its parent. */ public String getName() { return name; } + /** Returns an equivalent element with {@code name}, or this element if its name is unchanged. */ public PathElement withName(String name) { if (name.equals(this.name)) { return this; @@ -59,6 +65,7 @@ public PathElement withName(String name) { return new PathElement(name, isDirectory); } + /** Returns whether this element represents a directory. */ public boolean isDirectory() { return isDirectory; } @@ -80,13 +87,19 @@ public String toString() { return "PathElement{" + "name='" + name + '\'' + ", isDirectory=" + isDirectory + '}'; } + /** A path element that stores its descendants as a tree. */ public static final class TreePathElement extends PathElement { private final EconomicMap children = EconomicMaps.create(); + /** Creates a tree element with the given name and directory status. */ public TreePathElement(String name, boolean isDirectory) { super(name, isDirectory); } + /** + * Adds {@code child} unless a child named {@code name} already exists, and returns the stored + * child. + */ public TreePathElement putIfAbsent(String name, TreePathElement child) { children.putIfAbsent(name, child); return children.get(name); @@ -113,10 +126,12 @@ public TreePathElement putIfAbsent(String name, TreePathElement child) { return getElement(Path.of(basePath)); } + /** Returns this element's children, keyed by name. */ public EconomicMap getChildren() { return children; } + /** Returns a snapshot of this element's child values. */ public List getChildrenValues() { var ret = new ArrayList(children.size()); for (var elem : EconomicMaps.getValues(children)) { diff --git a/pkl-core/src/main/java/org/pkl/core/module/ProjectDependenciesManager.java b/pkl-core/src/main/java/org/pkl/core/module/ProjectDependenciesManager.java index 3b9e3c2bf..a643ead2d 100644 --- a/pkl-core/src/main/java/org/pkl/core/module/ProjectDependenciesManager.java +++ b/pkl-core/src/main/java/org/pkl/core/module/ProjectDependenciesManager.java @@ -40,9 +40,12 @@ import org.pkl.core.util.IoUtils; import org.pkl.core.util.json.Json.JsonParseException; +/** Resolves a project's declared package dependencies against its lock file. */ public final class ProjectDependenciesManager { + /** The conventional name of a Pkl project file. */ public static final String PKL_PROJECT_FILENAME = "PklProject"; + /** The conventional name of a Pkl project's dependency lock file. */ public static final String PKL_PROJECT_DEPS_FILENAME = "PklProject.deps.json"; private final DeclaredDependencies declaredDependencies; @@ -66,6 +69,13 @@ public final class ProjectDependenciesManager { private final Object lock = new Object(); + /** + * Creates a dependency manager for a project. + * + * @param declaredDependencies the dependencies declared by the project + * @param moduleResolver the resolver used to load the project's dependency lock file + * @param securityManager the security manager used when loading the dependency lock file + */ public ProjectDependenciesManager( DeclaredDependencies declaredDependencies, ModuleResolver moduleResolver, @@ -77,6 +87,7 @@ public ProjectDependenciesManager( this.securityManager = securityManager; } + /** Returns whether {@code uri} is located within this project. */ public boolean hasUri(URI uri) { return projectBaseUri.getScheme().equals(uri.getScheme()) && Objects.equals(projectBaseUri.getAuthority(), uri.getAuthority()) @@ -164,6 +175,7 @@ private Map doBuildResolvedDependenciesForProject( } // `ensureDependenciesInitialized` makes `myDependencies` safe to access + /** Returns the project's direct dependencies, keyed by their declared names. */ @SuppressWarnings({"FieldAccessNotGuarded", "GuardedBy"}) public Map getDependencies() { ensureDependenciesInitialized(); @@ -172,6 +184,7 @@ public Map getDependencies() { } // `ensureDependenciesInitialized` makes `localPackageDependencies` safe to access + /** Returns whether {@code packageUri} identifies a local dependency of this project. */ @SuppressWarnings({"FieldAccessNotGuarded", "GuardedBy"}) public boolean isLocalPackage(PackageUri packageUri) { ensureDependenciesInitialized(); @@ -179,6 +192,7 @@ public boolean isLocalPackage(PackageUri packageUri) { } // `ensureDependenciesInitialized` makes `localPackageDependencies` safe to access + /** Returns the dependencies of the local package identified by {@code packageUri}. */ @SuppressWarnings({"FieldAccessNotGuarded", "GuardedBy"}) public Map getLocalPackageDependencies(PackageUri packageUri) { ensureDependenciesInitialized(); @@ -188,6 +202,13 @@ public Map getLocalPackageDependencies(PackageUri packageUri return dep; } + /** + * Resolves a package's declared dependencies against this project's lock file. + * + * @param packageUri the package whose dependencies are being resolved + * @param dependencyMetadata the package's published dependency metadata + * @return dependencies keyed by their names in the package metadata + */ public Map getResolvedDependenciesForPackage( PackageUri packageUri, DependencyMetadata dependencyMetadata) { synchronized (lock) { @@ -217,10 +238,12 @@ public Map getResolvedDependenciesForPackage( } } + /** Returns this project's declared dependencies. */ public DeclaredDependencies getDeclaredDependencies() { return declaredDependencies; } + /** Returns the locked dependency matching {@code packageUri}. */ public Dependency getResolvedDependency(PackageUri packageUri) { var dep = getProjectDeps().get(CanonicalPackageUri.fromPackageUri(packageUri)); if (dep == null) { @@ -229,14 +252,17 @@ public Dependency getResolvedDependency(PackageUri packageUri) { return dep; } + /** Returns the base URI used to resolve paths within this project. */ public URI getProjectBaseUri() { return projectBaseUri; } + /** Returns the URI of this project's dependency lock file. */ public URI getProjectDepsFileUri() { return IoUtils.resolve(projectBaseUri, PKL_PROJECT_DEPS_FILENAME); } + /** Returns the URI of this project's project file. */ public URI getProjectFileUri() { return declaredDependencies.projectFileUri(); } diff --git a/pkl-core/src/main/java/org/pkl/core/module/ResolvedModuleKey.java b/pkl-core/src/main/java/org/pkl/core/module/ResolvedModuleKey.java index 2228d6458..ddeee95e7 100644 --- a/pkl-core/src/main/java/org/pkl/core/module/ResolvedModuleKey.java +++ b/pkl-core/src/main/java/org/pkl/core/module/ResolvedModuleKey.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ /** SPI for identifying a resolved module and loading its source code. */ public interface ResolvedModuleKey { + /** Returns the unresolved module key from which this key was resolved. */ ModuleKey getOriginal(); /** diff --git a/pkl-core/src/main/java/org/pkl/core/module/ResolvedModuleKeys.java b/pkl-core/src/main/java/org/pkl/core/module/ResolvedModuleKeys.java index 3d2685c69..21607289a 100644 --- a/pkl-core/src/main/java/org/pkl/core/module/ResolvedModuleKeys.java +++ b/pkl-core/src/main/java/org/pkl/core/module/ResolvedModuleKeys.java @@ -34,6 +34,7 @@ private ResolvedModuleKeys() {} * loaded from that file path and cached using the given URI as cache key. * * @param nofollow if true, the file will be opened with {@link LinkOption#NOFOLLOW_LINKS}. + * @return a resolved module key backed by {@code path} */ public static ResolvedModuleKey file(ModuleKey original, URI uri, Path path, boolean nofollow) { return new FileKey(original, uri, path, nofollow); @@ -42,6 +43,8 @@ public static ResolvedModuleKey file(ModuleKey original, URI uri, Path path, boo /** * Creates a resolved module key backed by the given file path. The resulting module will be * loaded from that file path and cached using the given URI as cache key. + * + * @return a resolved module key backed by {@code path} */ public static ResolvedModuleKey file(ModuleKey original, URI uri, Path path) { return new FileKey(original, uri, path, false); @@ -50,6 +53,8 @@ public static ResolvedModuleKey file(ModuleKey original, URI uri, Path path) { /** * Creates a resolved module key backed by the given URL. The resulting module will be loaded from * that URL and cached using the given URI as cache key. + * + * @return a resolved module key backed by {@code url} */ public static ResolvedModuleKey url(ModuleKey original, URI uri, URL url) { return new Url(original, uri, url); @@ -58,6 +63,8 @@ public static ResolvedModuleKey url(ModuleKey original, URI uri, URL url) { /** * Creates a resolved module key backed by the given source code. If {@code cached} is {@code * true}, the resulting module will be cached using the given URI as cache key. + * + * @return a resolved module key backed by {@code sourceText} */ public static ResolvedModuleKey virtual( ModuleKey original, URI uri, String sourceText, boolean cached) { @@ -67,6 +74,8 @@ public static ResolvedModuleKey virtual( /** * Creates a resolved module key that behaves like {@code delegate}, except with {@code original} * as its original module key. + * + * @return a resolved module key backed by {@code delegate} */ public static ResolvedModuleKey delegated(ResolvedModuleKey delegate, ModuleKey original) { return new Delegated(delegate, original); diff --git a/pkl-core/src/main/java/org/pkl/core/module/package-info.java b/pkl-core/src/main/java/org/pkl/core/module/package-info.java index 42eeedb06..b04acad46 100644 --- a/pkl-core/src/main/java/org/pkl/core/module/package-info.java +++ b/pkl-core/src/main/java/org/pkl/core/module/package-info.java @@ -1,3 +1,9 @@ +/** + * APIs for resolving Pkl module URIs and loading module source code. + * + *

Custom module schemes can be implemented with {@link org.pkl.core.module.ModuleKeyFactory}, + * {@link org.pkl.core.module.ModuleKey}, and {@link org.pkl.core.module.ResolvedModuleKey}. + */ @NullMarked package org.pkl.core.module;