001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.expression.spel.support;
018
019import java.util.Collections;
020import java.util.LinkedList;
021import java.util.List;
022
023import org.springframework.expression.EvaluationException;
024import org.springframework.expression.TypeLocator;
025import org.springframework.expression.spel.SpelEvaluationException;
026import org.springframework.expression.spel.SpelMessage;
027import org.springframework.lang.Nullable;
028import org.springframework.util.ClassUtils;
029
030/**
031 * A simple implementation of {@link TypeLocator} that uses the context ClassLoader
032 * (or any ClassLoader set upon it). It supports 'well-known' packages: So if a
033 * type cannot be found, it will try the registered imports to locate it.
034 *
035 * @author Andy Clement
036 * @author Juergen Hoeller
037 * @since 3.0
038 */
039public class StandardTypeLocator implements TypeLocator {
040
041        @Nullable
042        private final ClassLoader classLoader;
043
044        private final List<String> knownPackagePrefixes = new LinkedList<>();
045
046
047        /**
048         * Create a StandardTypeLocator for the default ClassLoader
049         * (typically, the thread context ClassLoader).
050         */
051        public StandardTypeLocator() {
052                this(ClassUtils.getDefaultClassLoader());
053        }
054
055        /**
056         * Create a StandardTypeLocator for the given ClassLoader.
057         * @param classLoader the ClassLoader to delegate to
058         */
059        public StandardTypeLocator(@Nullable ClassLoader classLoader) {
060                this.classLoader = classLoader;
061                // Similar to when writing regular Java code, it only knows about java.lang by default
062                registerImport("java.lang");
063        }
064
065
066        /**
067         * Register a new import prefix that will be used when searching for unqualified types.
068         * Expected format is something like "java.lang".
069         * @param prefix the prefix to register
070         */
071        public void registerImport(String prefix) {
072                this.knownPackagePrefixes.add(prefix);
073        }
074
075        /**
076         * Remove that specified prefix from this locator's list of imports.
077         * @param prefix the prefix to remove
078         */
079        public void removeImport(String prefix) {
080                this.knownPackagePrefixes.remove(prefix);
081        }
082
083        /**
084         * Return a list of all the import prefixes registered with this StandardTypeLocator.
085         * @return a list of registered import prefixes
086         */
087        public List<String> getImportPrefixes() {
088                return Collections.unmodifiableList(this.knownPackagePrefixes);
089        }
090
091
092        /**
093         * Find a (possibly unqualified) type reference - first using the type name as-is,
094         * then trying any registered prefixes if the type name cannot be found.
095         * @param typeName the type to locate
096         * @return the class object for the type
097         * @throws EvaluationException if the type cannot be found
098         */
099        @Override
100        public Class<?> findType(String typeName) throws EvaluationException {
101                String nameToLookup = typeName;
102                try {
103                        return ClassUtils.forName(nameToLookup, this.classLoader);
104                }
105                catch (ClassNotFoundException ey) {
106                        // try any registered prefixes before giving up
107                }
108                for (String prefix : this.knownPackagePrefixes) {
109                        try {
110                                nameToLookup = prefix + '.' + typeName;
111                                return ClassUtils.forName(nameToLookup, this.classLoader);
112                        }
113                        catch (ClassNotFoundException ex) {
114                                // might be a different prefix
115                        }
116                }
117                throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
118        }
119
120}