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
28 /** The Class.getModule() method. */
29 private static final MethodHandle GET_MODULE;
30
31 /** The Class.getPackageName() method. */
32 private static final MethodHandle GET_PKGNAME;
33
34 /** The Module.isExported(String packageName) method. */
35 private static final MethodHandle IS_EXPORTED;
36
37 /** The Module of JEXL itself. */
38 private static final Object JEXL_MODULE;
39
40 static {
41 final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
42 MethodHandle getModule = null;
43 MethodHandle getPackageName = null;
44 MethodHandle isExported = null;
45 Object myModule = null;
46 try {
47 final Class<?> modulec = ClassTool.class.getClassLoader().loadClass("java.lang.Module");
48 if (modulec != null) {
49 getModule = LOOKUP.findVirtual(Class.class, "getModule", MethodType.methodType(modulec));
50 if (getModule != null) {
51 getPackageName = LOOKUP.findVirtual(Class.class, "getPackageName", MethodType.methodType(String.class));
52 if (getPackageName != null) {
53 myModule = getModule.invoke(ClassTool.class);
54 isExported = LOOKUP.findVirtual(modulec, "isExported", MethodType.methodType(boolean.class, String.class, modulec));
55 }
56 }
57 }
58 } catch (final Throwable e) {
59 // ignore all
60 }
61 JEXL_MODULE = myModule;
62 GET_MODULE = getModule;
63 GET_PKGNAME = getPackageName;
64 IS_EXPORTED = isExported;
65 }
66
67 /**
68 * Gets the package name of a class (class.getPackage() may return null).
69 *
70 * @param clz the class
71 * @return the class package name
72 */
73 static String getPackageName(final Class<?> clz) {
74 String pkgName = "";
75 if (clz != null) {
76 // use native if we can
77 if (GET_PKGNAME != null) {
78 try {
79 return (String) GET_PKGNAME.invoke(clz);
80 } catch (final Throwable xany) {
81 return "";
82 }
83 }
84 // remove array
85 Class<?> clazz = clz;
86 while (clazz.isArray()) {
87 clazz = clazz.getComponentType();
88 }
89 // mimic getPackageName()
90 if (clazz.isPrimitive()) {
91 return "java.lang";
92 }
93 // remove enclosing
94 Class<?> walk = clazz.getEnclosingClass();
95 while (walk != null) {
96 clazz = walk;
97 walk = walk.getEnclosingClass();
98 }
99 final Package pkg = clazz.getPackage();
100 // pkg may be null for unobvious reasons
101 if (pkg == null) {
102 final String name = clazz.getName();
103 final int dot = name.lastIndexOf('.');
104 if (dot > 0) {
105 pkgName = name.substring(0, dot);
106 }
107 } else {
108 pkgName = pkg.getName();
109 }
110 }
111 return pkgName;
112 }
113
114 /**
115 * Checks whether a class is exported by its module (Java 9+) to JEXL.
116 * The code performs the following sequence through reflection (since the same jar can run
117 * on a Java8 or Java9+ runtime and the module features does not exist on 8).
118 * {@code
119 * Module jexlModule ClassTool.getClass().getModule();
120 * Module module = declarator.getModule();
121 * return module.isExported(declarator.getPackageName(), jexlModule);
122 * }
123 * This is required since some classes and methods may not be exported thus not callable through
124 * reflection. A package can be non-exported, <em>unconditionally</em> exported (to all reading
125 * modules), or use <em>qualified</em> exports to only export the package to specifically named
126 * modules. This method is only concerned with whether JEXL may reflectively access the
127 * package, so a qualified export naming the JEXL module is the least-privilege access required.
128 * The declarator's module may also use: unqualified exports, qualified {@code opens}, or
129 * unqualified {@code opens}, in increasing order of privilege; the last two allow reflective
130 * access to non-public members and are not recommended.
131 *
132 * @param declarator the class
133 * @return true if class is exported (to JEXL) or no module support exists
134 */
135 static boolean isExported(final Class<?> declarator) {
136 if (IS_EXPORTED != null) {
137 try {
138 final Object module = GET_MODULE.invoke(declarator);
139 if (module != null) {
140 final String pkgName = (String) GET_PKGNAME.invoke(declarator);
141 return (Boolean) IS_EXPORTED.invoke(module, pkgName, JEXL_MODULE);
142 }
143 } catch (final Throwable e) {
144 // ignore
145 }
146 }
147 return true;
148 }
149
150 }