ClassTool.java
/*
* 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
*
* https://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.apache.commons.jexl3.internal.introspection;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
/**
* Utility for Java9+ backport in Java8 of class and module related methods.
*/
final class ClassTool {
/** The Class.getModule() method. */
private static final MethodHandle GET_MODULE;
/** The Class.getPackageName() method. */
private static final MethodHandle GET_PKGNAME;
/** The Module.isExported(String packageName) method. */
private static final MethodHandle IS_EXPORTED;
/** The Module of JEXL itself. */
private static final Object JEXL_MODULE;
static {
final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
MethodHandle getModule = null;
MethodHandle getPackageName = null;
MethodHandle isExported = null;
Object myModule = null;
try {
final Class<?> modulec = ClassTool.class.getClassLoader().loadClass("java.lang.Module");
if (modulec != null) {
getModule = LOOKUP.findVirtual(Class.class, "getModule", MethodType.methodType(modulec));
if (getModule != null) {
getPackageName = LOOKUP.findVirtual(Class.class, "getPackageName", MethodType.methodType(String.class));
if (getPackageName != null) {
myModule = getModule.invoke(ClassTool.class);
isExported = LOOKUP.findVirtual(modulec, "isExported", MethodType.methodType(boolean.class, String.class, modulec));
}
}
}
} catch (final Throwable e) {
// ignore all
}
JEXL_MODULE = myModule;
GET_MODULE = getModule;
GET_PKGNAME = getPackageName;
IS_EXPORTED = isExported;
}
/**
* Gets the package name of a class (class.getPackage() may return null).
*
* @param clz the class
* @return the class package name
*/
static String getPackageName(final Class<?> clz) {
String pkgName = "";
if (clz != null) {
// use native if we can
if (GET_PKGNAME != null) {
try {
return (String) GET_PKGNAME.invoke(clz);
} catch (final Throwable xany) {
return "";
}
}
// remove array
Class<?> clazz = clz;
while (clazz.isArray()) {
clazz = clazz.getComponentType();
}
// mimic getPackageName()
if (clazz.isPrimitive()) {
return "java.lang";
}
// remove enclosing
Class<?> walk = clazz.getEnclosingClass();
while (walk != null) {
clazz = walk;
walk = walk.getEnclosingClass();
}
final Package pkg = clazz.getPackage();
// pkg may be null for unobvious reasons
if (pkg == null) {
final String name = clazz.getName();
final int dot = name.lastIndexOf('.');
if (dot > 0) {
pkgName = name.substring(0, dot);
}
} else {
pkgName = pkg.getName();
}
}
return pkgName;
}
/**
* Checks whether a class is exported by its module (Java 9+) to JEXL.
* The code performs the following sequence through reflection (since the same jar can run
* on a Java8 or Java9+ runtime and the module features does not exist on 8).
* {@code
* Module jexlModule ClassTool.getClass().getModule();
* Module module = declarator.getModule();
* return module.isExported(declarator.getPackageName(), jexlModule);
* }
* This is required since some classes and methods may not be exported thus not callable through
* reflection. A package can be non-exported, <em>unconditionally</em> exported (to all reading
* modules), or use <em>qualified</em> exports to only export the package to specifically named
* modules. This method is only concerned with whether JEXL may reflectively access the
* package, so a qualified export naming the JEXL module is the least-privilege access required.
* The declarator's module may also use: unqualified exports, qualified {@code opens}, or
* unqualified {@code opens}, in increasing order of privilege; the last two allow reflective
* access to non-public members and are not recommended.
*
* @param declarator the class
* @return true if class is exported (to JEXL) or no module support exists
*/
static boolean isExported(final Class<?> declarator) {
if (IS_EXPORTED != null) {
try {
final Object module = GET_MODULE.invoke(declarator);
if (module != null) {
final String pkgName = (String) GET_PKGNAME.invoke(declarator);
return (Boolean) IS_EXPORTED.invoke(module, pkgName, JEXL_MODULE);
}
} catch (final Throwable e) {
// ignore
}
}
return true;
}
}