View Javadoc
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 }