Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include minimum Java version supported by implementations #68

Merged
merged 1 commit into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ dependencies {
annotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:$jmhVersion")
implementation("com.fasterxml.jackson.core:jackson-databind")

implementation("org.ow2.asm:asm:9.4")

implementation("org.json:json:20230227")

implementation("com.worldturner.medeia:medeia-validator-jackson:1.1.0")
Expand Down
10 changes: 6 additions & 4 deletions docs/_docs/1. implementations.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,21 @@ against the underlying [<i class="fab fa-fw fa-github"/>&nbsp; GitHub Repo](http
"headings": [
"Implementation",
"Short Name",
"Supported Schema Versions",
"Language",
"Licence",
"Jar size",
"Supported Schema Versions",
"Minimum Java Version",
"Jar size",
"Project activity"
],
"data": implData.filter(row => row.shortName !== "Jackson").map(row => [
"<a href='" + row.url + "'>" + row.longName + "</a>",
row.shortName,
row.shortName,
row.supported.join(', '),
row.language,
row.licence,
row.minJavaVersion,
Math.ceil(row.jarSize / 1024) + ' KB',
row.supported.join(', '),
row.inactive ?? 'Active'
])
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import org.creekservice.kafka.test.perf.model.TestModel;
import org.creekservice.kafka.test.perf.testsuite.AdditionalSchemas;
import org.creekservice.kafka.test.perf.testsuite.SchemaSpec;
import org.creekservice.kafka.test.perf.util.JarFile;
import org.creekservice.kafka.test.perf.util.ImplJarFile;

public interface Implementation {

Expand Down Expand Up @@ -100,6 +100,7 @@ class MetaData {
private final URL url;
private final Color color;
private final long jarSize;
private final String minJavaVersion;
private final String inactiveMsg;

/**
Expand Down Expand Up @@ -143,8 +144,9 @@ public MetaData(
}
this.color = requireNonNull(color, "color");
this.jarSize =
JarFile.jarSizeForClass(
ImplJarFile.jarSizeForClass(
requireNonNull(typeFromImplementation, "typeFromImplementation"));
this.minJavaVersion = ImplJarFile.jarMinJavaVersion(typeFromImplementation);
this.inactiveMsg = requireNonNull(inactiveMsg, "inactiveMsg").trim();

if (longName.isBlank()) {
Expand Down Expand Up @@ -201,6 +203,11 @@ public long jarSize() {
return jarSize;
}

@JsonProperty("minJavaVersion")
public String minJavaVersion() {
return minJavaVersion;
}

@JsonProperty("inactive")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public String inactiveMsg() {
Expand Down
190 changes: 190 additions & 0 deletions src/main/java/org/creekservice/kafka/test/perf/util/ImplJarFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Copyright 2023 Creek Contributors (https://github.com/creek-service)
*
* 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 org.creekservice.kafka.test.perf.util;

import static java.util.Objects.requireNonNull;

import com.google.common.annotations.VisibleForTesting;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.CodeSource;
import java.util.Enumeration;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.objectweb.asm.ClassReader;

/** Utility for determining the size of a jar file a class is loaded from. */
public final class ImplJarFile {

/**
* Determine the size of the jar the supplied type is loaded from.
*
* @param type a class loaded from the jar.
* @return the size of the jar, in bytes.
* @throws IllegalArgumentException if the supplied {@code type} isn't loaded from an accessible
* jar.
*/
public static long jarSizeForClass(final Class<?> type) {
return new JarSize(JarTaskBase::sourceCode).size(type);
}

/**
* Determine the minimum supported Java version for the jar the supplied type is loaded from.
*
* @param type a class loaded from the jar.
* @return the minimum supported Java version
* @throws IllegalArgumentException if the supplied {@code type} isn't loaded from an accessible
* jar.
*/
public static String jarMinJavaVersion(final Class<?> type) {
return new JarMinJavaVersion(JarTaskBase::sourceCode).minJavaVersion(type);
}

private ImplJarFile() {}

@VisibleForTesting
static class JarTaskBase {

final Function<Class<?>, CodeSource> sourceAccessor;

JarTaskBase(final Function<Class<?>, CodeSource> locator) {
this.sourceAccessor = requireNonNull(locator, "locator");
}

@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Location supplied by JVM")
Path pathToJar(final Class<?> type) {
final URL location = location(type);

try {
return Paths.get(location.toURI());
} catch (final Exception e) {
throw new IllegalArgumentException(
"Type not loaded from accessible jar file. location: " + location, e);
}
}

private URL location(final Class<?> type) {
final CodeSource codeSource = sourceAccessor.apply(type);
if (codeSource == null) {
throw new IllegalArgumentException("Type not loaded from a jar file: " + type);
}

return codeSource.getLocation();
}

private static CodeSource sourceCode(final Class<?> type) {
return type.getProtectionDomain().getCodeSource();
}
}

@VisibleForTesting
static final class JarSize extends JarTaskBase {

JarSize(final Function<Class<?>, CodeSource> locator) {
super(locator);
}

long size(final Class<?> type) {
final Path path = pathToJar(type);

try {
return Files.size(path);
} catch (final Exception e) {
throw new IllegalArgumentException(
"Failed to read file size. location: " + path, e);
}
}
}

@VisibleForTesting
static final class JarMinJavaVersion extends JarTaskBase {

JarMinJavaVersion(final Function<Class<?>, CodeSource> locator) {
super(locator);
}

String minJavaVersion(final Class<?> type) {
final Path path = pathToJar(type);

try (JarFile jarFile = new JarFile(path.toFile())) {
final Enumeration<JarEntry> entries = jarFile.entries();

while (entries.hasMoreElements()) {
final JarEntry entry = entries.nextElement();

if (entry.getName().endsWith(".class")) {
try (InputStream classInputStream = jarFile.getInputStream(entry)) {
return minJavaVersion(classInputStream);
}
}
}
} catch (IOException e) {
throw new IllegalArgumentException(
"Failed to extract the min Java version the jar supports. Path: " + path,
e);
}

return "Unknown";
}

public static String minJavaVersion(final InputStream classFile) throws IOException {
final ClassReader classReader = new ClassReader(classFile);
final VersionClassVisitor versionClassVisitor = new VersionClassVisitor();
classReader.accept(versionClassVisitor, 0);

final int majorVersion = versionClassVisitor.majorVersion;
if (majorVersion < 0) {
throw new IllegalArgumentException("Failed to determine Java version");
}

final String javaVersion =
majorVersion < 49
? "1." + (majorVersion - 44)
: String.valueOf(majorVersion - 44);

return "Java " + javaVersion;
}

private static class VersionClassVisitor extends org.objectweb.asm.ClassVisitor {

private int majorVersion = -1;

VersionClassVisitor() {
super(org.objectweb.asm.Opcodes.ASM9);
}

@Override
public void visit(
final int version,
final int access,
final String name,
final String signature,
final String superName,
final String[] interfaces) {
if (version > majorVersion) {
majorVersion = version;
}
}
}
}
}

This file was deleted.

Loading