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      /** 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 }