JvmKit.java
package space.sunqian.common.base.system;
import space.sunqian.annotations.Nonnull;
import space.sunqian.annotations.Nullable;
import space.sunqian.common.Fs;
import space.sunqian.common.base.exception.UnknownPrimitiveTypeException;
import space.sunqian.common.reflect.TypeKit;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Objects;
/**
* Utilities for JVM
*
* @author sunqian
*/
public class JvmKit {
/**
* Returns a description of current JVM, in format of: {@code [JVM name]:[JVM version]:[Java version]}.
*
* @return a description of current JVM
*/
public static @Nonnull String jvmDescription() {
return SystemKit.getJavaVmName() + ":" + SystemKit.getJavaVmVersion() + ":" + SystemKit.getJavaVersion();
}
/**
* Returns the major version of current Java Runtime Environment version.
* <p>
* If the version {@code <= 1.8}, returns the second version number (such as {@code 6} for {@code 1.6.x}, {@code 8}
* for {@code 1.8.x}). Otherwise, returns the first number (such as {@code 9} for {@code 9.x}, {@code 17} for
* {@code 17-ea.x}).
* <p>
* Returns -1 if obtain failed.
*
* @return the major version of current Java Runtime Environment version
*/
public static int javaMajorVersion() {
return javaMajorVersion(SystemKit.getJavaVersion());
}
/**
* Returns the major version of the specified Java Runtime Environment version.
* <p>
* If the version {@code <= 1.8}, returns the second version number (such as {@code 6} for {@code 1.6.x}, {@code 8}
* for {@code 1.8.x}). Otherwise, returns the first number (such as {@code 9} for {@code 9.x}, {@code 17} for
* {@code 17-ea.x}).
* <p>
* Returns -1 if obtain failed.
*
* @param version the specified Java Runtime Environment version
* @return the major version of the specified Java Runtime Environment version
*/
public static int javaMajorVersion(@Nonnull String version) {
try {
int dot1 = version.indexOf('.');
if (dot1 < 0) {
return versionToNumber(version);
}
int firstNum = versionToNumber(version.substring(0, dot1));
if (firstNum >= 9) {
return firstNum;
}
int dot2 = version.indexOf('.', dot1 + 1);
if (dot2 < 0) {
return versionToNumber(version.substring(dot1 + 1));
}
return versionToNumber(version.substring(dot1 + 1, dot2));
} catch (Exception e) {
return -1;
}
}
private static int versionToNumber(@Nonnull String version) {
int dashDot = version.indexOf('-');
if (dashDot < 0) {
return Integer.parseInt(version);
}
return Integer.parseInt(version.substring(0, dashDot));
}
/**
* Returns the internal name of the given class. The internal name of a class is its fully qualified name, as
* returned by {@link Class#getName()}, where '.' are replaced by '/'.
*
* @param cls the given class
* @return the internal name of the given class.
*/
public static @Nonnull String toInternalName(@Nonnull Class<?> cls) {
return cls.getName().replace('.', '/');
}
/**
* Returns the descriptor of the given type. This method supports {@link Class}, {@link ParameterizedType},
* {@link GenericArrayType} and {@link TypeVariable}.
*
* @param type the given type
* @return the descriptor of the given type
* @throws JvmException if any problem occurs
*/
public static @Nonnull String toDescriptor(@Nonnull Type type) throws JvmException {
if (TypeKit.isTypeVariable(type)) {
TypeVariable<?> tv = (TypeVariable<?>) type;
Type bound = TypeKit.getFirstBound(tv);
return toDescriptor(bound);
}
StringBuilder appender = new StringBuilder();
if (TypeKit.isClass(type)) {
appendDescriptor((Class<?>) type, appender);
} else if (TypeKit.isParameterized(type)) {
Class<?> rawClass = toRawClass((ParameterizedType) type);
appendDescriptor(rawClass, appender);
} else if (TypeKit.isGenericArray(type)) {
appendDescriptor(toRawClass((GenericArrayType) type), appender);
} else {
throw new JvmException("Unknown type: " + type + ".");
}
return appender.toString();
}
/**
* Returns the descriptor of the given method.
*
* @param method the given method
* @return the descriptor of the given method
*/
public static @Nonnull String toDescriptor(@Nonnull Method method) {
Class<?> returnType = method.getReturnType();
Class<?>[] parameters = method.getParameterTypes();
if (Objects.equals(returnType, void.class) && parameters.length == 0) {
return "()V";
}
StringBuilder appender = new StringBuilder();
appender.append("(");
for (Class<?> parameter : parameters) {
appendDescriptor(parameter, appender);
}
appender.append(")");
appendDescriptor(returnType, appender);
return appender.toString();
}
/**
* Returns the descriptor of the given constructor.
*
* @param constructor the given constructor
* @return the descriptor of the given constructor
*/
public static @Nonnull String toDescriptor(@Nonnull Constructor<?> constructor) {
Class<?>[] parameters = constructor.getParameterTypes();
if (parameters.length == 0) {
return "()V";
}
StringBuilder appender = new StringBuilder();
appender.append("(");
for (Class<?> parameter : parameters) {
appendDescriptor(parameter, appender);
}
appender.append(")V");
return appender.toString();
}
private static void appendDescriptor(@Nonnull Class<?> cls, @Nonnull StringBuilder appender) {
Class<?> curCls = cls;
while (curCls.isArray()) {
appender.append('[');
curCls = curCls.getComponentType();
}
if (curCls.isPrimitive()) {
appender.append(toPrimitiveDescriptor(curCls));
} else {
appender.append('L').append(toInternalName(curCls)).append(';');
}
}
private static char toPrimitiveDescriptor(@Nonnull Class<?> cls) {
if (Objects.equals(cls, boolean.class)) {
return 'Z';
}
if (Objects.equals(cls, byte.class)) {
return 'B';
}
if (Objects.equals(cls, short.class)) {
return 'S';
}
if (Objects.equals(cls, char.class)) {
return 'C';
}
if (Objects.equals(cls, int.class)) {
return 'I';
}
if (Objects.equals(cls, long.class)) {
return 'J';
}
if (Objects.equals(cls, float.class)) {
return 'F';
}
if (Objects.equals(cls, double.class)) {
return 'D';
}
if (Objects.equals(cls, void.class)) {
return 'V';
}
throw new UnknownPrimitiveTypeException(cls);
}
/**
* Returns whether the given type need a signature for JVM.
* <p>
* The {@code declaration} specifies where the signature is used for. When for the declaration, {@code true} should
* be passed here. Otherwise, such as signature of a {@link Field}'s type, {@code false}.
*
* @param type the given type
* @param declaration whether the type is declaring
* @return whether the given type need a signature for JVM
*/
public static boolean needSignature(@Nonnull Type type, boolean declaration) {
if (!declaration) {
return !TypeKit.isClass(type);
}
if (!TypeKit.isClass(type)) {
return true;
}
Class<?> cls = (Class<?>) type;
TypeVariable<?>[] tv = cls.getTypeParameters();
if (tv.length > 0) {
return true;
}
@Nullable Type superclass = cls.getGenericSuperclass();
if (superclass != null && !TypeKit.isClass(superclass)) {
return true;
}
Type[] interfaces = cls.getGenericInterfaces();
for (Type anInterface : interfaces) {
if (!TypeKit.isClass(anInterface)) {
return true;
}
}
return false;
}
/**
* Returns whether the given method need a signature for JVM.
*
* @param method the given method
* @return whether the given method need a signature for JVM
*/
public static boolean needSignature(@Nonnull Method method) {
Type returnType = method.getGenericReturnType();
if (needSignature(returnType, false)) {
return true;
}
return needSignature((Executable) method);
}
/**
* Returns whether the given constructor need a signature for JVM.
*
* @param constructor the given constructor
* @return whether the given constructor need a signature for JVM
*/
public static boolean needSignature(@Nonnull Constructor<?> constructor) {
return needSignature((Executable) constructor);
}
private static boolean needSignature(@Nonnull Executable executable) {
Type[] parameters = executable.getGenericParameterTypes();
for (Type parameter : parameters) {
if (needSignature(parameter, false)) {
return true;
}
}
return false;
}
/**
* Returns the signature of the given type.
* <p>
* The {@code declaration} specifies where the signature is used for. When for the declaration, {@code true} should
* be passed here. Otherwise, such as signature of a {@link Field}'s type, {@code false}.
*
* @param type the given type
* @param declaration whether the type is declaring
* @return the signature of the given type
*/
public static @Nullable String toSignature(@Nonnull Type type, boolean declaration) {
if (!needSignature(type, declaration)) {
return null;
}
StringBuilder appender = new StringBuilder();
if (TypeKit.isClass(type)) {
Class<?> cls = (Class<?>) type;
appendSignature(cls.getTypeParameters(), appender);
@Nullable Type superclass = cls.getGenericSuperclass();
if (superclass == null) {
appender.append("Ljava/lang/Object;");
} else {
appendSignature(superclass, appender);
}
Type[] interfaces = cls.getGenericInterfaces();
for (Type anInterface : interfaces) {
appendSignature(anInterface, appender);
}
} else {
appendSignature(type, appender);
}
return appender.toString();
}
/**
* Returns the given type's signature used for the declaration. This method is equivalent to
* ({@link #toSignature(Type, boolean)}): {@code getSignature(type, true)}.
*
* @param type the given type
* @return the given type's signature used for the declaration
*/
public static @Nullable String toSignature(@Nonnull Type type) {
return toSignature(type, true);
}
/**
* Returns the signature of the given field.
*
* @param field the given field
* @return the signature of the given field
*/
public static @Nullable String toSignature(@Nonnull Field field) {
return toSignature(field.getGenericType(), false);
}
/**
* Returns the signature of the given method.
*
* @param method the given method
* @return the signature of the given method
*/
public static @Nullable String toSignature(@Nonnull Method method) {
if (!needSignature(method)) {
return null;
}
StringBuilder appender = new StringBuilder();
appendSignature(method, appender);
Type returnType = method.getGenericReturnType();
appendSignature(returnType, appender);
return appender.toString();
}
/**
* Returns the signature of the given constructor.
*
* @param constructor the given constructor
* @return the signature of the given constructor
*/
public static @Nullable String toSignature(@Nonnull Constructor<?> constructor) {
if (!needSignature(constructor)) {
return null;
}
StringBuilder appender = new StringBuilder();
appendSignature(constructor, appender);
appender.append('V');
return appender.toString();
}
private static void appendSignature(
@Nonnull Executable executable,
@Nonnull StringBuilder appender
) {
appendSignature(executable.getTypeParameters(), appender);
appender.append('(');
for (Type parameter : executable.getGenericParameterTypes()) {
appendSignature(parameter, appender);
}
appender.append(')');
}
private static void appendSignature(
@Nonnull TypeVariable<?> @Nonnull [] typeVariables,
@Nonnull StringBuilder appender
) {
if (typeVariables.length > 0) {
appender.append('<');
for (TypeVariable<?> tv : typeVariables) {
appendTypeVariableDeclaringSignature(tv, appender);
}
appender.append('>');
}
}
private static void appendTypeVariableDeclaringSignature(
@Nonnull TypeVariable<?> type,
@Nonnull StringBuilder appender
) {
appender.append(type.getName());
Type[] bounds = type.getBounds();
for (int i = 0; i < bounds.length; i++) {
Type bound = bounds[i];
appender.append(':');
if (TypeKit.isTypeVariable(bound)) {
appender.append('T');
appender.append(((TypeVariable<?>) bound).getName());
appender.append(';');
continue;
}
if (i == 0) {
if (TypeKit.isParameterized(bound)) {
Class<?> rawClass = toRawClass((ParameterizedType) bound);
if (rawClass.isInterface()) {
appender.append(':');
}
}
if (TypeKit.isClass(bound)) {
Class<?> boundClass = Fs.as(bound);
if (boundClass.isInterface()) {
appender.append(':');
}
}
}
appendSignature(bound, appender);
}
}
private static void appendSignature(@Nonnull Type type, @Nonnull StringBuilder appender) {
if (TypeKit.isClass(type)) {
appendSignature((Class<?>) type, appender);
return;
}
if (TypeKit.isParameterized(type)) {
appendSignature((ParameterizedType) type, appender);
return;
}
if (TypeKit.isWildcard(type)) {
appendSignature((WildcardType) type, appender);
return;
}
if (TypeKit.isTypeVariable(type)) {
appendSignature((TypeVariable<?>) type, appender);
return;
}
if (TypeKit.isGenericArray(type)) {
appendSignature((GenericArrayType) type, appender);
return;
}
throw new JvmException("Unknown type: " + type + ".");
}
private static void appendSignature(@Nonnull Class<?> type, @Nonnull StringBuilder appender) {
appender.append(toDescriptor(type));
}
private static void appendSignature(@Nonnull ParameterizedType type, @Nonnull StringBuilder appender) {
Type owner = type.getOwnerType();
Class<?> rawClass = toRawClass(type);
if (owner != null) {
appendSignature(owner, appender);
// it must end with a ';'
int semicolonIndex = appender.length() - 1;
if (TypeKit.isClass(owner)) {
appender.setCharAt(semicolonIndex, '$');
} else {
appender.setCharAt(semicolonIndex, '.');
}
appender.append(rawClass.getSimpleName());
} else {
// no primitive
appender.append('L');
appender.append(toInternalName(rawClass));
}
appender.append('<');
for (Type actualTypeArgument : type.getActualTypeArguments()) {
appendSignature(actualTypeArgument, appender);
}
appender.append(">;");
}
private static void appendSignature(@Nonnull WildcardType type, @Nonnull StringBuilder appender) {
@Nullable Type lower = TypeKit.getLowerBound(type);
Type[] bounds;
if (lower != null) {
// ? super
appender.append('-');
bounds = type.getLowerBounds();
} else {
// '?' and '? extends' are equivalent
appender.append('+');
bounds = type.getUpperBounds();
}
for (Type bound : bounds) {
appendSignature(bound, appender);
}
}
private static void appendSignature(@Nonnull TypeVariable<?> type, @Nonnull StringBuilder appender) {
appender.append('T').append(type.getTypeName()).append(';');
}
private static void appendSignature(@Nonnull GenericArrayType type, @Nonnull StringBuilder appender) {
@Nonnull Type curType = type;
while (TypeKit.isArray(curType)) {
appender.append('[');
// never null
curType = Objects.requireNonNull(TypeKit.getComponentType(curType));
}
appendSignature(curType, appender);
}
private static @Nonnull Class<?> toRawClass(@Nonnull ParameterizedType type) throws JvmException {
Type rawType = type.getRawType();
if (TypeKit.isClass(rawType)) {
return (Class<?>) rawType;
}
throw new JvmException("Unknown raw type: " + rawType + ".");
}
private static @Nonnull Class<?> toRawClass(@Nonnull GenericArrayType type) throws JvmException {
@Nullable Class<?> arrayClass = TypeKit.toRuntimeClass(type);
if (arrayClass != null) {
return arrayClass;
}
throw new JvmException("Unknown array type: " + type + ".");
}
private JvmKit() {
}
}