FastDoubleParserShadingIT.java
package tools.jackson.core.it;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* Integration test for [core#1498]: FastDoubleParser classes must be properly
* shaded in the Jackson JAR to prevent {@code NoClassDefFoundError} when
* Jackson's JAR loads before the FastDoubleParser JAR on the classpath.
* <p>
* This test verifies the packaged JAR to ensure that:
* <ul>
* <li>No unshaded {@code ch.randelshofer.fastdoubleparser} classes exist in base package</li>
* <li>Multi-version JAR entries (JDK 17, 21, 22, 23+) have properly shaded paths</li>
* <li>All FastDoubleParser classes are relocated to {@code tools.jackson.core.internal.shaded.fdp}</li>
* </ul>
*
* @see <a href="https://github.com/FasterXML/jackson-core/issues/1498">[core#1498]</a>
*/
public class FastDoubleParserShadingIT
{
private static final String UNSHADED_PACKAGE_PREFIX = "ch/randelshofer/fastdoubleparser/";
private static final String SHADED_PACKAGE_PREFIX = "tools/jackson/core/internal/shaded/fdp/";
/**
* Test that verifies no unshaded FastDoubleParser classes exist in the
* Jackson JAR, including in multi-version JAR directories.
*/
@Test
public void verifyNoUnshadedFDPClasses() throws Exception
{
File jarFile = findJacksonCoreJar();
assertNotNull(jarFile, "Could not locate jackson-core JAR file");
assertTrue(jarFile.exists(), "JAR file does not exist: " + jarFile);
assertTrue(jarFile.isFile(), "Not a file: " + jarFile);
// Parse the JAR to check for unshaded classes
List<String> unshadedClasses = new ArrayList<>();
List<String> shadedClasses = new ArrayList<>();
List<String> multiVersionUnshadedClasses = new ArrayList<>();
List<String> multiVersionShadedClasses = new ArrayList<>();
try (InputStream is = new FileInputStream(jarFile);
JarInputStream jarStream = new JarInputStream(is)) {
JarEntry entry;
while ((entry = jarStream.getNextJarEntry()) != null) {
String entryName = entry.getName();
// Check for unshaded classes in base location
if (entryName.startsWith(UNSHADED_PACKAGE_PREFIX) && entryName.endsWith(".class")) {
unshadedClasses.add(entryName);
}
// Check for unshaded classes in multi-version JAR directories
// Pattern: META-INF/versions/{version}/ch/randelshofer/fastdoubleparser/...
if (entryName.startsWith("META-INF/versions/") &&
entryName.contains("/" + UNSHADED_PACKAGE_PREFIX) &&
entryName.endsWith(".class")) {
multiVersionUnshadedClasses.add(entryName);
}
// Track properly shaded classes for verification
if (entryName.startsWith(SHADED_PACKAGE_PREFIX) && entryName.endsWith(".class")) {
shadedClasses.add(entryName);
}
// Also check multi-version shaded classes
if (entryName.startsWith("META-INF/versions/") &&
entryName.contains("/" + SHADED_PACKAGE_PREFIX) &&
entryName.endsWith(".class")) {
multiVersionShadedClasses.add(entryName);
}
}
}
// Report findings for debugging
System.out.println("=== FastDoubleParser Shading Verification Report ===");
System.out.println("JAR: " + jarFile.getName());
System.out.println("Shaded classes found: " + shadedClasses.size());
System.out.println("Multi-version shaded classes found: " + multiVersionShadedClasses.size());
if (!shadedClasses.isEmpty()) {
System.out.println("\nShaded classes (sample):");
shadedClasses.stream().limit(5).forEach(name -> System.out.println(" ��� " + name));
}
if (!multiVersionShadedClasses.isEmpty()) {
System.out.println("\nMulti-version shaded classes:");
multiVersionShadedClasses.forEach(name -> System.out.println(" ��� " + name));
}
// Verify no unshaded classes exist in base location
if (!unshadedClasses.isEmpty()) {
System.err.println("\n��� FAILED: Found unshaded FastDoubleParser classes in base location:");
unshadedClasses.forEach(name -> System.err.println(" ��� " + name));
fail("Found " + unshadedClasses.size() + " unshaded FastDoubleParser classes in base location. " +
"These should be shaded to '" + SHADED_PACKAGE_PREFIX + "'");
}
// Verify no unshaded classes exist in multi-version directories
if (!multiVersionUnshadedClasses.isEmpty()) {
System.err.println("\n��� FAILED: Found unshaded FastDoubleParser classes in multi-version JAR:");
multiVersionUnshadedClasses.forEach(name -> System.err.println(" ��� " + name));
fail("Found " + multiVersionUnshadedClasses.size() + " unshaded FastDoubleParser classes " +
"in multi-version JAR directories. These should be shaded to '" + SHADED_PACKAGE_PREFIX + "'");
}
// Verify that shaded classes DO exist (sanity check that shading happened)
assertFalse(shadedClasses.isEmpty(),
"No shaded FastDoubleParser classes found. Expected classes under '" +
SHADED_PACKAGE_PREFIX + "'. Shading may have failed.");
// Additional verification: check that expected core classes are present
assertTrue(shadedClasses.stream()
.anyMatch(name -> name.contains("FastIntegerMath") || name.contains("FastDoubleMath")),
"Expected to find core FastDoubleParser classes like FastIntegerMath or FastDoubleMath");
System.out.println("\n��� All FastDoubleParser classes are properly shaded!");
System.out.println("=====================================================");
}
/**
* Finds the jackson-core JAR file in the target directory.
*/
private File findJacksonCoreJar() {
// Look in target directory for the built JAR
File targetDir = new File("target");
if (!targetDir.exists() || !targetDir.isDirectory()) {
return null;
}
// Find JAR files matching jackson-core pattern
File[] jarFiles = targetDir.listFiles((dir, name) ->
name.startsWith("jackson-core-") &&
name.endsWith(".jar") &&
!name.contains("sources") &&
!name.contains("javadoc") &&
!name.contains("tests"));
if (jarFiles == null || jarFiles.length == 0) {
return null;
}
// Return the first matching JAR (there should only be one)
return jarFiles[0];
}
}