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