-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add java bytecode class file version detection
Signed-off-by: Jorge Solórzano <[email protected]>
- Loading branch information
Showing
20 changed files
with
276 additions
and
1 deletion.
There are no files selected for viewing
141 changes: 141 additions & 0 deletions
141
...s-java/src/main/java/org/codehaus/plexus/languages/java/version/JavaClassfileVersion.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package org.codehaus.plexus.languages.java.version; | ||
|
||
import java.io.IOException; | ||
import java.io.UncheckedIOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
|
||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you 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. | ||
*/ | ||
|
||
/** | ||
* Reads the bytecode of a Java class to detect the major, minor and Java | ||
* version that was compiled. | ||
* | ||
* @author Jorge Solórzano | ||
*/ | ||
public final class JavaClassfileVersion { | ||
|
||
private final int major; | ||
private final int minor; | ||
|
||
JavaClassfileVersion(int major, int minor) { | ||
if (major < 45) { | ||
throw new IllegalArgumentException("Java class major version must be 45 or above."); | ||
} | ||
this.major = major; | ||
this.minor = minor; | ||
} | ||
|
||
/** | ||
* Reads the bytecode of a Java class file and returns the | ||
* {@link JavaClassfileVersion}. | ||
* | ||
* @param bytes {@code byte[]} of the Java class file | ||
* @return the {@link JavaClassfileVersion} of the byte array | ||
*/ | ||
public static JavaClassfileVersion of(byte[] bytes) { | ||
return JavaClassfileVersionParser.of(bytes); | ||
} | ||
|
||
/** | ||
* Reads the bytecode of a Java class file and returns the | ||
* {@link JavaClassfileVersion}. | ||
* | ||
* @param path {@link Path} of the Java class file | ||
* @return the {@link JavaClassfileVersion} of the path java class | ||
*/ | ||
public static JavaClassfileVersion of(Path path) { | ||
try { | ||
byte[] readAllBytes = Files.readAllBytes(path); | ||
return of(readAllBytes); | ||
} catch (IOException ex) { | ||
throw new UncheckedIOException(ex); | ||
} | ||
} | ||
|
||
/** | ||
* JavaVersion of the class file version detected. | ||
* | ||
* @return JavaVersion based on the major version of the class file. | ||
*/ | ||
public JavaVersion javaVersion() { | ||
int javaVer = major - 44; | ||
String javaVersion = javaVer < 9 ? "1." + javaVer : Integer.toString(javaVer); | ||
|
||
return JavaVersion.parse(javaVersion); | ||
} | ||
|
||
/** | ||
* Returns the major version of the parsed classfile. | ||
* | ||
* @return the major classfile version | ||
*/ | ||
public int majorVersion() { | ||
return major; | ||
} | ||
|
||
/** | ||
* Returns the minor version of the parsed classfile. | ||
* | ||
* @return the minor classfile version | ||
*/ | ||
public int minorVersion() { | ||
return minor; | ||
} | ||
|
||
/** | ||
* Returns if the classfile use preview features. | ||
* | ||
* @return {@code true} if the classfile use preview features. | ||
*/ | ||
public boolean isPreview() { | ||
return minor == 65535; | ||
} | ||
|
||
/** | ||
* Returns a String representation of the Java class file version, e.g. | ||
* {@code 65.0 (Java 21)}. | ||
* | ||
* @return String representation of the Java class file version | ||
*/ | ||
@Override | ||
public String toString() { | ||
return major + "." + minor + " (Java " + javaVersion() + ")"; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
final int prime = 31; | ||
int result = 1; | ||
result = prime * result + major; | ||
result = prime * result + minor; | ||
return result; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (this == obj) return true; | ||
if (!(obj instanceof JavaClassfileVersion)) return false; | ||
JavaClassfileVersion other = (JavaClassfileVersion) obj; | ||
if (major != other.major) return false; | ||
if (minor != other.minor) return false; | ||
return true; | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
.../src/main/java/org/codehaus/plexus/languages/java/version/JavaClassfileVersionParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package org.codehaus.plexus.languages.java.version; | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.DataInputStream; | ||
import java.io.IOException; | ||
import java.io.UncheckedIOException; | ||
|
||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you 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. | ||
*/ | ||
|
||
/** | ||
* This class is intented to be package-private and consumed by | ||
* {@link JavaClassfileVersion}. | ||
* | ||
* @author Jorge Solórzano | ||
*/ | ||
final class JavaClassfileVersionParser { | ||
|
||
private JavaClassfileVersionParser() {} | ||
|
||
/** | ||
* Reads the bytecode of a Java class file and returns the {@link JavaClassfileVersion}. | ||
* | ||
* @param in {@code byte[]} of the Java class file | ||
* @return the {@link JavaClassfileVersion} of the input stream | ||
*/ | ||
public static JavaClassfileVersion of(byte[] bytes) { | ||
try (final DataInputStream data = new DataInputStream(new ByteArrayInputStream(bytes))) { | ||
if (0xCAFEBABE != data.readInt()) { | ||
throw new IOException("Invalid java class file header"); | ||
} | ||
int minor = data.readUnsignedShort(); | ||
int major = data.readUnsignedShort(); | ||
return new JavaClassfileVersion(major, minor); | ||
} catch (IOException ex) { | ||
throw new UncheckedIOException(ex); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
...s-java/src/test/java/org/codehaus/plexus/languages/java/version/JavaClassVersionTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package org.codehaus.plexus.languages.java.version; | ||
|
||
import java.io.IOException; | ||
import java.io.UncheckedIOException; | ||
import java.nio.file.DirectoryStream; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
import java.util.stream.StreamSupport; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertNotEquals; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
class JavaClassVersionTest { | ||
|
||
@ParameterizedTest | ||
@MethodSource("provideClassFiles") | ||
void testFilesClassVersions(Path filePath) { | ||
String fileName = filePath.getFileName().toString(); | ||
int javaVersion = Integer.valueOf(fileName.substring(fileName.indexOf("-") + 1, fileName.length() - 6)); | ||
JavaClassfileVersion classVersion = JavaClassfileVersion.of(filePath); | ||
assertEquals(javaVersion + 44, classVersion.majorVersion()); | ||
assertEquals(0, classVersion.minorVersion()); | ||
assertEquals(JavaVersion.parse("" + javaVersion), classVersion.javaVersion()); | ||
} | ||
|
||
static Stream<Path> provideClassFiles() { | ||
List<Path> paths; | ||
try (DirectoryStream<Path> directoryStream = | ||
Files.newDirectoryStream(Paths.get("src/test/resources/classfile.version/"), "*-[0-9]?.class")) { | ||
paths = StreamSupport.stream(directoryStream.spliterator(), false) | ||
.filter(Files::isRegularFile) | ||
.collect(Collectors.toList()); | ||
} catch (IOException ex) { | ||
throw new UncheckedIOException(ex); | ||
} | ||
return paths.stream(); | ||
} | ||
|
||
@Test | ||
void testJavaClassPreview() { | ||
Path previewFile = Paths.get("src/test/resources/classfile.version/helloworld-preview.class"); | ||
JavaClassfileVersion previewClass = JavaClassfileVersion.of(previewFile); | ||
assertTrue(previewClass.isPreview()); | ||
assertEquals(20 + 44, previewClass.majorVersion()); | ||
assertEquals(JavaVersion.parse("20"), previewClass.javaVersion()); | ||
} | ||
|
||
@Test | ||
void testJavaClassVersionMajor45orAbove() { | ||
assertThrows( | ||
IllegalArgumentException.class, | ||
() -> new JavaClassfileVersion(44, 0), | ||
"Java class major version must be 45 or above."); | ||
} | ||
|
||
@Test | ||
void equalsContract() { | ||
JavaClassfileVersion javaClassVersion = new JavaClassfileVersion(65, 0); | ||
JavaClassfileVersion previewFeature = new JavaClassfileVersion(65, 65535); | ||
assertNotEquals(javaClassVersion, previewFeature); | ||
assertNotEquals(javaClassVersion.hashCode(), previewFeature.hashCode()); | ||
|
||
JavaClassfileVersion javaClassVersionOther = new JavaClassfileVersion(65, 0); | ||
assertEquals(javaClassVersion, javaClassVersionOther); | ||
assertEquals(javaClassVersion.hashCode(), javaClassVersionOther.hashCode()); | ||
assertEquals(javaClassVersion.javaVersion(), javaClassVersionOther.javaVersion()); | ||
assertEquals(javaClassVersion.javaVersion(), previewFeature.javaVersion()); | ||
} | ||
} |
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-10.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-11.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-12.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-13.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-14.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-15.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-16.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-17.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-18.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-19.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-20.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-21.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-22.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-8.class
Binary file not shown.
Binary file added
BIN
+424 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-9.class
Binary file not shown.
Binary file added
BIN
+489 Bytes
plexus-java/src/test/resources/classfile.version/helloworld-preview.class
Binary file not shown.