001/*
002 * Copyright 2002-2014 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.util.ClassUtils;
028
029/**
030 * A simple implementation of {@link TypeLocator} that uses the context ClassLoader
031 * (or any ClassLoader set upon it). It supports 'well-known' packages: So if a
032 * type cannot be found, it will try the registered imports to locate it.
033 *
034 * @author Andy Clement
035 * @author Juergen Hoeller
036 * @since 3.0
037 */
038public class StandardTypeLocator implements TypeLocator {
039
040        private final ClassLoader classLoader;
041
042        private final List<String> knownPackagePrefixes = new LinkedList<String>();
043
044
045        /**
046         * Create a StandardTypeLocator for the default ClassLoader
047         * (typically, the thread context ClassLoader).
048         */
049        public StandardTypeLocator() {
050                this(ClassUtils.getDefaultClassLoader());
051        }
052
053        /**
054         * Create a StandardTypeLocator for the given ClassLoader.
055         * @param classLoader the ClassLoader to delegate to
056         */
057        public StandardTypeLocator(ClassLoader classLoader) {
058                this.classLoader = classLoader;
059                // Similar to when writing regular Java code, it only knows about java.lang by default
060                registerImport("java.lang");
061        }
062
063
064        /**
065         * Register a new import prefix that will be used when searching for unqualified types.
066         * Expected format is something like "java.lang".
067         * @param prefix the prefix to register
068         */
069        public void registerImport(String prefix) {
070                this.knownPackagePrefixes.add(prefix);
071        }
072
073        /**
074         * Remove that specified prefix from this locator's list of imports.
075         * @param prefix the prefix to remove
076         */
077        public void removeImport(String prefix) {
078                this.knownPackagePrefixes.remove(prefix);
079        }
080
081        /**
082         * Return a list of all the import prefixes registered with this StandardTypeLocator.
083         * @return a list of registered import prefixes
084         */
085        public List<String> getImportPrefixes() {
086                return Collections.unmodifiableList(this.knownPackagePrefixes);
087        }
088
089
090        /**
091         * Find a (possibly unqualified) type reference - first using the type name as-is,
092         * then trying any registered prefixes if the type name cannot be found.
093         * @param typeName the type to locate
094         * @return the class object for the type
095         * @throws EvaluationException if the type cannot be found
096         */
097        @Override
098        public Class<?> findType(String typeName) throws EvaluationException {
099                String nameToLookup = typeName;
100                try {
101                        return ClassUtils.forName(nameToLookup, this.classLoader);
102                }
103                catch (ClassNotFoundException ey) {
104                        // try any registered prefixes before giving up
105                }
106                for (String prefix : this.knownPackagePrefixes) {
107                        try {
108                                nameToLookup = prefix + '.' + typeName;
109                                return ClassUtils.forName(nameToLookup, this.classLoader);
110                        }
111                        catch (ClassNotFoundException ex) {
112                                // might be a different prefix
113                        }
114                }
115                throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
116        }
117
118}