From c43bfb5c294731df764932234772504a2a79e2d9 Mon Sep 17 00:00:00 2001 From: Justin Van Dort Date: Tue, 4 Feb 2025 18:56:21 -0500 Subject: [PATCH] Support class versions Add the class version to JavaClass so rules can verify the class version --- .../archunit/core/domain/JavaClass.java | 9 +++ .../core/domain/JavaClassVersion.java | 61 +++++++++++++++++++ .../core/importer/DomainBuilders.java | 11 ++++ .../core/importer/JavaClassProcessor.java | 4 ++ 4 files changed, 85 insertions(+) create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassVersion.java diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index e4480a4da..1de303690 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -77,6 +77,7 @@ public final class JavaClass private final Optional source; private final SourceCodeLocation sourceCodeLocation; private final JavaClassDescriptor descriptor; + private final JavaClassVersion version; private JavaPackage javaPackage; private final boolean isInterface; private final boolean isEnum; @@ -133,6 +134,7 @@ public final class JavaClass JavaClass(JavaClassBuilder builder) { source = checkNotNull(builder.getSource()); descriptor = checkNotNull(builder.getDescriptor()); + version = checkNotNull(builder.getVersion()); isInterface = builder.isInterface(); isEnum = builder.isEnum(); isAnnotation = builder.isAnnotation(); @@ -189,6 +191,13 @@ public String getSimpleName() { return descriptor.getSimpleClassName(); } + /** + * @return The version of this {@link JavaClass}. + */ + public JavaClassVersion getJavaVersion() { + return version; + } + @PublicAPI(usage = ACCESS) public JavaPackage getPackage() { return javaPackage; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassVersion.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassVersion.java new file mode 100644 index 000000000..737163909 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassVersion.java @@ -0,0 +1,61 @@ +package com.tngtech.archunit.core.domain; + +/** + * Represents the version of a JVM class file. + */ +public class JavaClassVersion { + + private final int major; + private final int minor; + + /** + * Create a new instance of {@link JavaClassVersion}. + * + * @param major The major version of the class file. + * @param minor The minor version of the class file. + */ + public JavaClassVersion(int major, int minor) { + this.major = major; + this.minor = minor; + } + + /** + * Get the Java version corresponding to the major version of the class file. + * + * @return The Java version + */ + public int getJavaVersion() { + return major - 44; + } + + /** + * Get the major version of the class file. + * + * @return The major version + */ + public int getBytecodeMajorVersion() { + return major; + } + + /** + * Get the minor version of the class file. + * + * @return The minor version + */ + public int getBytecodeMinorVersion() { + return minor; + } + + /** + * Create a new instance of {@link JavaClassVersion} from the version as provided by ASM. + * + * @return A representation of the class file version + */ + public static JavaClassVersion of(int asmVersion) { + int major = asmVersion & 0xFF; + int minor = asmVersion >> 16; + + return new JavaClassVersion(major, minor); + } + +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index d1b9f509e..0d2b6b4f3 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -47,6 +47,7 @@ import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClassDescriptor; +import com.tngtech.archunit.core.domain.JavaClassVersion; import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaConstructor; import com.tngtech.archunit.core.domain.JavaConstructorCall; @@ -392,6 +393,7 @@ public static final class JavaClassBuilder { private Optional sourceDescriptor = Optional.empty(); private Optional sourceFileName = Optional.empty(); private JavaClassDescriptor descriptor; + private JavaClassVersion version; private boolean isInterface; private boolean isEnum; private boolean isAnnotation; @@ -423,6 +425,11 @@ JavaClassBuilder withDescriptor(JavaClassDescriptor descriptor) { return this; } + JavaClassBuilder withVersion(JavaClassVersion version) { + this.version = version; + return this; + } + JavaClassBuilder withInterface(boolean isInterface) { this.isInterface = isInterface; return this; @@ -475,6 +482,10 @@ public JavaClassDescriptor getDescriptor() { return descriptor; } + public JavaClassVersion getVersion() { + return version; + } + public boolean isInterface() { return isInterface; } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java index 982107985..27a3de15e 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java @@ -42,6 +42,7 @@ import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClassDescriptor; +import com.tngtech.archunit.core.domain.JavaClassVersion; import com.tngtech.archunit.core.domain.JavaEnumConstant; import com.tngtech.archunit.core.domain.JavaField; import com.tngtech.archunit.core.domain.JavaModifier; @@ -107,6 +108,8 @@ public void visit(int version, int access, String name, String signature, String return; } + JavaClassVersion classVersion = JavaClassVersion.of(version); + List interfaceNames = createInterfaceNames(interfaces); LOG.trace("Found interfaces {} on class '{}'", interfaceNames, name); boolean opCodeForInterfaceIsPresent = (access & Opcodes.ACC_INTERFACE) != 0; @@ -117,6 +120,7 @@ public void visit(int version, int access, String name, String signature, String LOG.trace("Found superclass {} on class '{}'", superclassName.orElse(null), name); javaClassBuilder = new DomainBuilders.JavaClassBuilder() + .withVersion(classVersion) .withSourceDescriptor(sourceDescriptor) .withDescriptor(descriptor) .withInterface(opCodeForInterfaceIsPresent)