1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.jexl3.internal.introspection;
18
19 import java.lang.invoke.MethodHandle;
20 import java.lang.invoke.MethodHandles;
21 import java.lang.invoke.MethodType;
22
23 /**
24 * Utility for Java9+ backport in Java8 of class and module related methods.
25 */
26 final class ClassTool {
27 /** The Class.getModule() method. */
28 private static final MethodHandle GET_MODULE;
29 /** The Class.getPackageName() method. */
30 private static final MethodHandle GET_PKGNAME;
31 /** The Module.isExported(String packageName) method. */
32 private static final MethodHandle IS_EXPORTED;
33 /** The Module of JEXL itself. */
34 private static final Object JEXL_MODULE;
35
36 static {
37 final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
38 MethodHandle getModule = null;
39 MethodHandle getPackageName = null;
40 MethodHandle isExported = null;
41 Object myModule = null;
42 try {
43 final Class<?> modulec = ClassTool.class.getClassLoader().loadClass("java.lang.Module");
44 if (modulec != null) {
45 getModule = LOOKUP.findVirtual(Class.class, "getModule", MethodType.methodType(modulec));
46 if (getModule != null) {
47 getPackageName = LOOKUP.findVirtual(Class.class, "getPackageName", MethodType.methodType(String.class));
48 if (getPackageName != null) {
49 myModule = getModule.invoke(ClassTool.class);
50 isExported = LOOKUP.findVirtual(modulec, "isExported", MethodType.methodType(boolean.class, String.class, modulec));
51 }
52 }
53 }
54 } catch (final Throwable e) {
55 // ignore all
56 }
57 JEXL_MODULE = myModule;
58 GET_MODULE = getModule;
59 GET_PKGNAME = getPackageName;
60 IS_EXPORTED = isExported;
61 }
62
63 /**
64 * Gets the package name of a class (class.getPackage() may return null).
65 *
66 * @param clz the class
67 * @return the class package name
68 */
69 static String getPackageName(final Class<?> clz) {
70 String pkgName = "";
71 if (clz != null) {
72 // use native if we can
73 if (GET_PKGNAME != null) {
74 try {
75 return (String) GET_PKGNAME.invoke(clz);
76 } catch (final Throwable xany) {
77 return "";
78 }
79 }
80 // remove array
81 Class<?> clazz = clz;
82 while (clazz.isArray()) {
83 clazz = clazz.getComponentType();
84 }
85 // mimic getPackageName()
86 if (clazz.isPrimitive()) {
87 return "java.lang";
88 }
89 // remove enclosing
90 Class<?> walk = clazz.getEnclosingClass();
91 while (walk != null) {
92 clazz = walk;
93 walk = walk.getEnclosingClass();
94 }
95 final Package pkg = clazz.getPackage();
96 // pkg may be null for unobvious reasons
97 if (pkg == null) {
98 final String name = clazz.getName();
99 final int dot = name.lastIndexOf('.');
100 if (dot > 0) {
101 pkgName = name.substring(0, dot);
102 }
103 } else {
104 pkgName = pkg.getName();
105 }
106 }
107 return pkgName;
108 }
109
110 /**
111 * Checks whether a class is exported by its module (Java 9+) to JEXL.
112 * The code performs the following sequence through reflection (since the same jar can run
113 * on a Java8 or Java9+ runtime and the module features does not exist on 8).
114 * {@code
115 * Module jexlModule ClassTool.getClass().getModule();
116 * Module module = declarator.getModule();
117 * return module.isExported(declarator.getPackageName(), jexlModule);
118 * }
119 * This is required since some classes and methods may not be exported thus not callable through
120 * reflection. A package can be non-exported, <em>unconditionally</em> exported (to all reading
121 * modules), or use <em>qualified</em> exports to only export the package to specifically named
122 * modules. This method is only concerned with whether JEXL may reflectively access the
123 * package, so a qualified export naming the JEXL module is the least-privilege access required.
124 * The declarator's module may also use: unqualified exports, qualified {@code opens}, or
125 * unqualified {@code opens}, in increasing order of privilege; the last two allow reflective
126 * access to non-public members and are not recommended.
127 *
128 * @param declarator the class
129 * @return true if class is exported (to JEXL) or no module support exists
130 */
131 static boolean isExported(final Class<?> declarator) {
132 if (IS_EXPORTED != null) {
133 try {
134 final Object module = GET_MODULE.invoke(declarator);
135 if (module != null) {
136 final String pkgName = (String) GET_PKGNAME.invoke(declarator);
137 return (Boolean) IS_EXPORTED.invoke(module, pkgName, JEXL_MODULE);
138 }
139 } catch (final Throwable e) {
140 // ignore
141 }
142 }
143 return true;
144 }
145
146 }