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    *      http://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  
34      static {
35          final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
36          MethodHandle getModule = null;
37          MethodHandle getPackageName = null;
38          MethodHandle isExported = null;
39          try {
40              final Class<?> modulec = ClassTool.class.getClassLoader().loadClass("java.lang.Module");
41              if (modulec != null) {
42                  getModule = LOOKUP.findVirtual(Class.class, "getModule", MethodType.methodType(modulec));
43                  if (getModule != null) {
44                      getPackageName = LOOKUP.findVirtual(Class.class, "getPackageName", MethodType.methodType(String.class));
45                      if (getPackageName != null) {
46                          isExported = LOOKUP.findVirtual(modulec, "isExported", MethodType.methodType(boolean.class, String.class));
47                      }
48                  }
49              }
50          } catch (final Exception e) {
51              // ignore all
52          }
53          GET_MODULE = getModule;
54          GET_PKGNAME = getPackageName;
55          IS_EXPORTED = isExported;
56      }
57  
58      /**
59       * Gets the package name of a class (class.getPackage() may return null).
60       *
61       * @param clz the class
62       * @return the class package name
63       */
64      static String getPackageName(final Class<?> clz) {
65          String pkgName = "";
66          if (clz != null) {
67              // use native if we can
68              if (GET_PKGNAME != null) {
69                  try {
70                      return (String) GET_PKGNAME.invoke(clz);
71                  } catch (final Throwable xany) {
72                      return "";
73                  }
74              }
75              // remove array
76              Class<?> clazz = clz;
77              while (clazz.isArray()) {
78                  clazz = clazz.getComponentType();
79              }
80              // mimic getPackageName()
81              if (clazz.isPrimitive()) {
82                  return "java.lang";
83              }
84              // remove enclosing
85              Class<?> walk = clazz.getEnclosingClass();
86              while (walk != null) {
87                  clazz = walk;
88                  walk = walk.getEnclosingClass();
89              }
90              final Package pkg = clazz.getPackage();
91              // pkg may be null for unobvious reasons
92              if (pkg == null) {
93                  final String name = clazz.getName();
94                  final int dot = name.lastIndexOf('.');
95                  if (dot > 0) {
96                      pkgName = name.substring(0, dot);
97                  }
98              } else {
99                  pkgName = pkg.getName();
100             }
101         }
102         return pkgName;
103     }
104 
105     /**
106      * Checks whether a class is exported by its module (Java 9+).
107      * The code performs the following sequence through reflection (since the same jar can run
108      * on a Java8 or Java9+ runtime and the module features does not exist on 8).
109      * <code>
110      * Module module = declarator.getModule();
111      * return module.isExported(declarator.getPackageName());
112      * </code>
113      * This is required since some classes and methods may not be exported thus not callable through
114      * reflection.
115      *
116      * @param declarator the class
117      * @return true if class is exported or no module support exists
118      */
119     static boolean isExported(final Class<?> declarator) {
120         if (IS_EXPORTED != null) {
121             try {
122                 final Object module = GET_MODULE.invoke(declarator);
123                 if (module != null) {
124                     final String pkgName = (String) GET_PKGNAME.invoke(declarator);
125                     return (Boolean) IS_EXPORTED.invoke(module, pkgName);
126                 }
127             } catch (final Throwable e) {
128                 // ignore
129             }
130         }
131         return true;
132     }
133 
134 }