All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.ascopes.jct.containers.PackageContainerGroup Maven / Gradle / Ivy

/*
 * Copyright (C) 2022 - 2024, the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.github.ascopes.jct.containers;

import io.github.ascopes.jct.filemanagers.PathFileObject;
import io.github.ascopes.jct.workspaces.PathRoot;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.tools.FileObject;
import javax.tools.JavaFileManager.Location;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import org.jspecify.annotations.Nullable;

/**
 * Base interface representing a group of package-oriented paths.
 *
 * 

Warning: container group APIs are not designed to allow reuse between * compilation runs due the behaviour around providing access to class loaders. See the notes for * {@link #getClassLoader} for more details. * * @author Ashley Scopes * @since 0.0.1 */ public interface PackageContainerGroup extends ContainerGroup { /** * Add a container to this group. * *

The provided container will be closed when this group is closed. * *

Note that this will destroy the {@link #getClassLoader class loader} if one is already * allocated from a previous request. * * @param container the container to add. */ void addPackage(Container container); /** * Add a path to this group. * *

Note that this will destroy the {@link #getClassLoader class loader} if one is already * allocated from a previous request. * *

If the path points to some form of archive (such as a JAR), then this may open that archive * in a new resource internally. If this occurs, then the resource will always be freed by this * class by calling {@link #close}. * *

Any other closable resources passed to this function will not be closed by this * implementation. You must handle the lifecycle of those objects yourself. * * @param path the path to add. */ void addPackage(PathRoot path); /** * Get a class loader for this group of containers. * *

If a class loader has not yet been created, then calling this method is expected to create * a class loader first. * *

This method is primarily provided to allow JCT to load components like annotation * processors from provided class paths dynamically during compilation, but is also suitable for * use by users to load classes compiled as part of test cases into memory to perform further * tests on the results via standard reflection APIs. * *

While not strictly required, it is recommended that any implementations of this class * provide a subclass of {@link java.net.URLClassLoader} to ensure similar behaviour to the * internals within OpenJDK's {@code javac} implementation. * *

Warning: adding additional containers to this group after accessing this * class loader may result in the class loader being destroyed or re-created. This can result in * confusing behaviour where classes may get loaded multiple times. Generally this shouldn't be an * issue since the class loader is only accessed once the files have been added, but this does * mean that container group types should not be reused between compilation runs if possible. Due * to how the JCT API works, this means that you should avoid calling this method prior to * invoking the compiler itself, and likewise should try to avoid adding new packages to * implementations of container groups after the compiler has been invoked. * *

Example of usage with Java: * *


   *   // Checked exception handling has been omitted from this example for
   *   // brevity. See the java.lang.reflect documentation for full details.
   *
   *   ClassLoader cl = containerGroup.getClassLoader();
   *   Class<?> cls = cl.loadClass("org.example.NumberAdder");
   *   Object adder = cls.getDeclaredConstructor().newInstance();
   *   Method addMethod = cls.getMethod("add", int.class, int.class);
   *   int result = (int) addMethod.invoke(adder, 9, 18);
   *
   *   assertThat(result).isEqualTo(27);
   * 
* *

Example of usage with Groovy: * *


   *   // Groovy is a great option if you are writing lots of tests like this,
   *   // since it will avoid much of the boilerplate around using the reflection
   *   // APIs directly due to the ability to dynamically infer the types of
   *   // objects at runtime. You also avoid having to deal with checked exceptions.
   *
   *   def cl = containerGroup.getClassLoader()
   *   def cls = cl.loadClass("org.example.NumberAdder")
   *   def adder = cls.getDeclaredConstructor().newInstance()
   *   def result = adder.add(9, 18)
   *
   *   assertThat(result).isEqualTo(27)
   * 
* *

Example working with resources: * *


   *   // Checked exception handling has been omitted from this example for
   *   // brevity. See the java.lang.reflect documentation for full details.
   *   //
   *   // Consider the .getFile method on the PackageContainerGroup class instead
   *   // to achieve the same outcome with simpler syntax.
   *
   *   ClassLoader cl = containerGroup.getClassLoader();
   *   try (InputStream inputStream = cl.getResourceAsStream("META-INF/spring.factories")) {
   *     ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
   *     inputStream.transferTo(outputStream);
   *     String content = new String(outputStream.toByteArray(), ...);
   *     ...
   *   }
   * 
* * @return a class loader for the contents of this container group. * @see java.lang.ClassLoader * @see java.lang.Class * @see java.lang.reflect.Method * @see java.lang.reflect.Field * @see java.lang.reflect.Constructor * @see java.net.URLClassLoader */ ClassLoader getClassLoader(); /** * Find the first occurrence of a given path to a file in packages or modules. * *

Paths should be relative to the root of this package group. Absolute paths * will be treated as erroneous inputs. * *

Modules are treated as subdirectories where supported. * *

This method accepts multiple strings to prevent users from having to * hard-code OS-specific file path separators that may create flaky tests. For example, * {@code .getFile("foo", "bar", "baz")} is equivalent to {@code .getFile("foo/bar/baz")} on most * systems. * *

Unlike {@link #getClassLoader}, this will allow access to the files * directly without needing to handle class loading exceptions. * *


   *   // Letting JCT infer the correct path separators to use (recommended).
   *   containerGroup.getFile("foo", "bar", "baz.txt")...;
   *
   *   // Using platform-specific separators.
   *   containerGroup.getFile("foo/bar/baz.txt")...;
   * 
* * @param fragments parts of the path. * @return the first occurrence of the path in this group, or null if not found. * @throws IllegalArgumentException if the provided path is absolute. * @throws IllegalArgumentException if no path fragments are provided. * @see java.nio.file.Path * @see java.nio.file.Files */ @Nullable Path getFile(String... fragments); /** * Get a {@link FileObject} that can have content read from it. * *

This will return {@code null} if no file is found matching the criteria. * * @param packageName the package name of the file to read. * @param relativeName the relative name of the file to read. * @return the file object, or null if the file is not found. */ @Nullable PathFileObject getFileForInput(String packageName, String relativeName); /** * Get a {@link FileObject} that can have content written to it for the given file. * *

This will attempt to write to the first writeable path in this group. {@code null} * will be returned if no writeable paths exist in this group. * * @param packageName the name of the package the file is in. * @param relativeName the relative name of the file within the package. * @return the {@link FileObject} to write to, or null if this group has no paths that can be * written to. */ @Nullable PathFileObject getFileForOutput(String packageName, String relativeName); /** * Get a {@link JavaFileObject} that can have content read from it for the given file. * *

This will return {@code null} if no file is found matching the criteria. * * @param className the binary name of the class to read. * @param kind the kind of file to read. * @return the {@link JavaFileObject} to write to, or null if this group has no paths that can be * written to. */ @Nullable PathFileObject getJavaFileForInput(String className, Kind kind); /** * Get a {@link JavaFileObject} that can have content written to it for the given class. * *

This will attempt to write to the first writeable path in this group. {@code null} * will be returned if no writeable paths exist in this group. * * @param className the name of the class. * @param kind the kind of the class file. * @return the {@link JavaFileObject} to write to, or null if this group has no paths that can be * written to. */ @Nullable PathFileObject getJavaFileForOutput(String className, Kind kind); /** * Get the package-oriented location that this group of paths is for. * * @return the package-oriented location. */ @Override Location getLocation(); /** * Get the package containers in this group. * *

Returned packages are presented in the order that they were registered. This is the * resolution order that the compiler will use. * * @return the containers. */ List getPackages(); /** * Try to infer the binary name of a given file object. * * @param fileObject the file object to infer the binary name for. * @return the binary name if known, or null otherwise. */ @Nullable String inferBinaryName(PathFileObject fileObject); /** * Determine if this group has no paths registered. * * @return {@code true} if no paths are registered. {@code false} if paths are registered. */ boolean isEmpty(); /** * List all the file objects that match the given criteria in this group. * *

File objects are returned in an unordered collection, but lookup will be * performed in a deterministic order corresponding to the same order as the containers returned * by {@link #getPackages}. * * @param packageName the package name to look in. * @param kinds the kinds of file to look for. * @param recurse {@code true} to recurse subpackages, {@code false} to only consider the * given package. * @return the file objects that were found. * @throws IOException if the file lookup fails due to an IO error somewhere. */ Set listFileObjects( String packageName, Set kinds, boolean recurse ) throws IOException; /** * List all files recursively in this container group, returning a multimap of each container and * all files within that container. * * @return a multimap of containers mapping to collections of all files in that container. * @throws IOException if the file lookup fails due to an IO error somewhere. * @since 0.6.0 */ Map> listAllFiles() throws IOException; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy