Skip to content

Commit

Permalink
issues/133: part 3 - introduction of ClassFileSelector (#192)
Browse files Browse the repository at this point in the history
issues/133: part 3 - introduction of ClassFileSelector
  • Loading branch information
fmck3516 authored Nov 14, 2024
1 parent 0f23165 commit 03216a8
Show file tree
Hide file tree
Showing 36 changed files with 677 additions and 430 deletions.
136 changes: 136 additions & 0 deletions skippy-core/src/main/java/io/skippy/core/ClassFileSelector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2023-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
*
* https://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.skippy.core;

import java.util.List;

/**
* Strategy to select {@link ClassFile}s from a {@link ClassFileContainer} based on
*
* <ul>
* <li>class name and </li>
* <li>class path.</li>
* </ul>
*
* Why is this needed? The same class name can occur multiple times within a project:
*
* <pre>
* src/main/java/com.example.Foo
* src/test/java/com.example.Foo
* </pre>
*
* The above example will result in multiple entries for the same class name in test-impact-analysis.java:
*
* <pre>
* {
* "classes": {
* "1": {
* "name": "com.example.Foo",
* "path": "com/example/Foo.class",
* "outputFolder": "build/classes/java/integrationTest",
* ...
* }
* "1": {
* "name": "com.example.Foo",
* "path": "com/example/Foo.class",
* "outputFolder": "build/classes/java/test",
* ...
* }
* ]
* }
* </pre>
*
* A {@link ClassFileSelector} can try to map a class name to a single {@link ClassFile} by taking
* the class path into account.
*
* <br /><br />
*
* Let's consider the classpath to be as follows:
*
* <pre>
* build/classes/java/test
* build/classes/java/main
* build/resources/main
* </pre>
*
* In the above classpath, com.example.Foo can be mapped to
*
* <pre>
* {
* "name": "com.example.Foo",
* "path": "com/example/Foo.class",
* "outputFolder": "build/classes/java/test",
* ...
* }
* </pre>
*
* Now, let's consider the following classpath:
*
* <pre>
* build/classes/java/integrationTest
* build/classes/java/main
* build/resources/main
* </pre>
*
* In this example, com.example.Foo can be mapped to
*
* <pre>
* {
* "name": "com.example.Foo",
* "path": "com/example/Foo.class",
* "outputFolder": "build/classes/java/integrationTest",
* ...
* }
* </pre>
*
* If no exact match is possible, then multiple {@link ClassFile}s can be returned. This is safe, but it might result in
* unnecessary {@link Prediction#EXECUTE} predictions.
*
* <br /><br />
*
* Custom implementations must have a public no-args constructor.
* They can be registered using Skippy's build plugins.
*
* <br /><br />
*
* Gradle example:
* <pre>
* skippy {
* ...
* classFileSelector = 'com.example.CustomClassFileSelector'
* }
* </pre>
*
* @author Florian McKee
*/
public interface ClassFileSelector {

/**
* Select {@link ClassFile}s from the {@code classFileContainer} based on
*
* <ul>
* <li>{@code className} and </li>
* <li>{@code classPath}.</li>
* </ul>
* @param className a class name (e.g., com.example.Foo)
* @param classFileContainer a {@link ClassFileContainer}
* @param classPath a list of folders within the project's output folders that contribute to the classpath (e.g. build/classes/java/main).
*
* @return all matching {@link ClassFile}s
*/
List<ClassFile> select(String className, ClassFileContainer classFileContainer, List<String> classPath);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.skippy.core;

import java.nio.file.Path;
import java.util.List;

import static java.util.Collections.emptyList;

/**
* Default {@link ClassFileSelector} implementation.
*
* @author Florian McKee
*/
public final class DefaultClassFileSelector implements ClassFileSelector {

/**
* Comment to make the JavaDoc task happy.
*/
public DefaultClassFileSelector() {
}

@Override
public List<ClassFile> select(String className, ClassFileContainer classFileContainer, List<String> classPath) {

// try to match by class name only
var classFiles = classFileContainer.getIdsByClassName(className).stream().map(classFileContainer::getById).toList();

// no match? return nothing
if (classFiles.size() == 0) {
return emptyList();
}

// exactly one match? return the match
if (classFiles.size() == 1) {
return List.of(classFiles.get(0));
}

// iterate through the classpath folders
for (var classPathEntry : classPath) {

// check for a match for the given folder
var matchingClassFiles = classFiles.stream().filter(classFile -> classFile.getOutputFolder().equals(Path.of(classPathEntry))).toList();

if (matchingClassFiles.size() == 1) {
return List.of(matchingClassFiles.get(0));
}
}

// no exact match found: return all candidates (this is safe, but it will result in unnecessary execute predictions)
return classFiles;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import java.util.Optional;

/**
* {@link PredictionModifier} that defaults to {@link Prediction#EXECUTE} if a test (or one of it's superclasses or
* {@link PredictionModifier} that defaults to {@link Prediction#EXECUTE} if a test (or one of its superclasses or
* interfaces it implements is annotated with @{@link AlwaysRun}.
*
* @author Florian McKee
Expand All @@ -30,7 +30,7 @@ public DefaultPredictionModifier() {
}

@Override
public PredictionWithReason passThruOrModify(Class<?> test, ParametersFromBuildPlugin parametersFromBuildPlugin, PredictionWithReason prediction) {
public PredictionWithReason passThruOrModify(Class<?> test, PredictionWithReason prediction) {
if (isAnnotatedWithAlwaysRun(test)) {
return new PredictionWithReason(
Prediction.ALWAYS_EXECUTE,
Expand Down
12 changes: 11 additions & 1 deletion skippy-core/src/main/java/io/skippy/core/HashUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ static String hashWith32Digits(byte[] data) {
return fullHash(data);
}

/**
* Generates an 8-character hexadecimal hash of the input.
*
* @param data the input
* @return ab 8-character hexadecimal hash of the input
*/
static String hashWith8Digits(byte[] data) {
return fullHash(data).substring(24, 32);
}

/**
* Generates a 8-digit hexadecimal hash for the {@code classfile} that is agnostic of debug information.
*
Expand All @@ -65,7 +75,7 @@ static String hashWith32Digits(byte[] data) {
* @return a 8-digit hexadecimal hash of the {@code classfile} that is agnostic of debug information
*/
static String debugAgnosticHash(Path classFile) {
return fullHash(getBytecodeWithoutDebugInformation(classFile)).substring(24, 32);
return hashWith8Digits(getBytecodeWithoutDebugInformation(classFile));
}

private static String fullHash(byte[] data) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
* <li>etc.</li>
* </ul>
*
* Custom implementations must have a public no-args constructor.
* They can be registered using Skippy's build plugins.
*
* <br /><br />
* Custom implementations must be registered using Skippy's build plugins.
* <br /><br />
*
* Gradle example:
* <pre>
* skippy {
Expand All @@ -45,10 +47,9 @@ public interface PredictionModifier {
* Returns a modified or unmodified prediction made by Skippy.
*
* @param test a class object representing a test
* @param parametersFromBuildPlugin parameters that have been passed from Skippy's build plugin
* @param prediction the prediction made by Skippy.
* @return the modified or unmodified prediction
*/
PredictionWithReason passThruOrModify(Class<?> test, ParametersFromBuildPlugin parametersFromBuildPlugin, PredictionWithReason prediction);
PredictionWithReason passThruOrModify(Class<?> test, PredictionWithReason prediction);

}
Loading

0 comments on commit 03216a8

Please sign in to comment.