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;
18  
19  import org.apache.commons.jexl3.JexlContext;
20  import org.apache.commons.jexl3.introspection.JexlUberspect;
21  
22  import java.util.HashMap;
23  import java.util.LinkedHashSet;
24  import java.util.Map;
25  import java.util.Set;
26  import java.util.concurrent.locks.ReadWriteLock;
27  import java.util.concurrent.locks.ReentrantReadWriteLock;
28  
29  /**
30   * Helper resolving a simple class name into a fully-qualified class name (hence FqcnResolver) using
31   * package names as roots of import.
32   * <p>This only keeps names of classes to avoid any class loading/reloading/permissions issue.</p>
33   */
34   class FqcnResolver implements JexlContext.ClassNameResolver {
35      /**
36       * The class loader.
37       */
38      private final JexlUberspect uberspect;
39      /**
40       * A lock for RW concurrent ops.
41       */
42      private final ReadWriteLock lock = new ReentrantReadWriteLock();
43      /**
44       * The set of packages to be used as import roots.
45       */
46      private final Set<String> imports = new LinkedHashSet<>();
47      /**
48       * The map of solved fqcns based on imports keyed on (simple) name,
49       * valued as fully-qualified class name.
50       */
51      private final Map<String, String> fqcns = new HashMap<>();
52      /**
53       * Optional parent solver.
54       */
55      private final FqcnResolver parent;
56  
57      /**
58       * Adds a collection of packages as import root, checks the names are one of a package.
59       * @param names the package names
60       */
61      private void importCheck(final Iterable<String> names) {
62          if (names != null) {
63              names.forEach(this::importCheck);
64          }
65      }
66  
67      /**
68       * Adds a package as import root, checks the name if one of a package.
69       * @param name the package name
70       */
71      private void importCheck(final String name) {
72          // check the package name actually points to a package to avoid clutter
73          if (name != null && Package.getPackage(name) != null) {
74              imports.add(name);
75          }
76      }
77  
78      @Override
79      public String resolveClassName(final String name) {
80          return getQualifiedName(name);
81      }
82  
83      /**
84       * Creates a class name solver.
85       *
86       * @param uber   the optional class loader
87       * @param packages the optional package names
88       */
89      FqcnResolver(final JexlUberspect uber, final Iterable<String> packages) {
90          this.uberspect = uber;
91          this.parent = null;
92          importCheck(packages);
93      }
94  
95      /**
96       * Creates a class name solver.
97       *
98       * @param solver the parent solver
99       * @throws NullPointerException if parent solver is null
100      */
101     FqcnResolver(final FqcnResolver solver) {
102         if (solver == null) {
103             throw new NullPointerException("parent solver can not be null");
104         }
105         this.parent = solver;
106         this.uberspect = solver.uberspect;
107     }
108 
109     /**
110      * Checks is a package is imported by this solver of one of its ascendants.
111      *
112      * @param pkg the package name
113      * @return true if an import exists for this package, false otherwise
114      */
115     boolean isImporting(final String pkg) {
116         if (parent != null && parent.isImporting(pkg)) {
117             return true;
118         }
119         lock.readLock().lock();
120         try {
121             return imports.contains(pkg);
122         } finally {
123             lock.readLock().unlock();
124         }
125     }
126 
127     /**
128      * Imports a list of packages as solving roots.
129      *
130      * @param packages the packages
131      * @return this solver
132      */
133     FqcnResolver importPackages(final Iterable<String> packages) {
134         if (packages != null) {
135             lock.writeLock().lock();
136             try {
137                 if (parent == null) {
138                     importCheck(packages);
139                 } else {
140                     packages.forEach(pkg ->{ if (!parent.isImporting(pkg)) { importCheck(pkg); }});
141                 }
142             } finally {
143                 lock.writeLock().unlock();
144             }
145         }
146         return this;
147     }
148 
149     /**
150      * Gets a fully qualified class name from a simple class name and imports.
151      *
152      * @param name the simple name
153      * @return the fqcn
154      */
155     String getQualifiedName(final String name) {
156         String fqcn;
157         if (parent != null && (fqcn = parent.getQualifiedName(name)) != null) {
158             return  fqcn;
159         }
160         lock.readLock().lock();
161         try {
162             fqcn = fqcns.get(name);
163         } finally {
164             lock.readLock().unlock();
165         }
166         if (fqcn == null) {
167             final ClassLoader loader = uberspect.getClassLoader();
168             for (final String pkg : imports) {
169                 Class<?> clazz;
170                 try {
171                     clazz = loader.loadClass(pkg + "." + name);
172                 } catch (final ClassNotFoundException e) {
173                     // not in this package
174                     continue;
175                 }
176                 // solved it, insert in map and return
177                 if (clazz != null) {
178                     fqcn = clazz.getName();
179                     lock.writeLock().lock();
180                     try {
181                         fqcns.put(name, fqcn);
182                     } finally {
183                         lock.writeLock().unlock();
184                     }
185                     break;
186                 }
187             }
188         }
189         return fqcn;
190     }
191 }