001/*
002 * Copyright 2002-2018 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.core.io;
018
019import java.io.FileNotFoundException;
020import java.io.IOException;
021import java.io.InputStream;
022import java.net.URL;
023
024import org.springframework.lang.Nullable;
025import org.springframework.util.Assert;
026import org.springframework.util.ClassUtils;
027import org.springframework.util.ObjectUtils;
028import org.springframework.util.StringUtils;
029
030/**
031 * {@link Resource} implementation for class path resources. Uses either a
032 * given {@link ClassLoader} or a given {@link Class} for loading resources.
033 *
034 * <p>Supports resolution as {@code java.io.File} if the class path
035 * resource resides in the file system, but not for resources in a JAR.
036 * Always supports resolution as URL.
037 *
038 * @author Juergen Hoeller
039 * @author Sam Brannen
040 * @since 28.12.2003
041 * @see ClassLoader#getResourceAsStream(String)
042 * @see Class#getResourceAsStream(String)
043 */
044public class ClassPathResource extends AbstractFileResolvingResource {
045
046        private final String path;
047
048        @Nullable
049        private ClassLoader classLoader;
050
051        @Nullable
052        private Class<?> clazz;
053
054
055        /**
056         * Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
057         * A leading slash will be removed, as the ClassLoader resource access
058         * methods will not accept it.
059         * <p>The thread context class loader will be used for
060         * loading the resource.
061         * @param path the absolute path within the class path
062         * @see java.lang.ClassLoader#getResourceAsStream(String)
063         * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
064         */
065        public ClassPathResource(String path) {
066                this(path, (ClassLoader) null);
067        }
068
069        /**
070         * Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
071         * A leading slash will be removed, as the ClassLoader resource access
072         * methods will not accept it.
073         * @param path the absolute path within the classpath
074         * @param classLoader the class loader to load the resource with,
075         * or {@code null} for the thread context class loader
076         * @see ClassLoader#getResourceAsStream(String)
077         */
078        public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
079                Assert.notNull(path, "Path must not be null");
080                String pathToUse = StringUtils.cleanPath(path);
081                if (pathToUse.startsWith("/")) {
082                        pathToUse = pathToUse.substring(1);
083                }
084                this.path = pathToUse;
085                this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
086        }
087
088        /**
089         * Create a new {@code ClassPathResource} for {@code Class} usage.
090         * The path can be relative to the given class, or absolute within
091         * the classpath via a leading slash.
092         * @param path relative or absolute path within the class path
093         * @param clazz the class to load resources with
094         * @see java.lang.Class#getResourceAsStream
095         */
096        public ClassPathResource(String path, @Nullable Class<?> clazz) {
097                Assert.notNull(path, "Path must not be null");
098                this.path = StringUtils.cleanPath(path);
099                this.clazz = clazz;
100        }
101
102        /**
103         * Create a new {@code ClassPathResource} with optional {@code ClassLoader}
104         * and {@code Class}. Only for internal usage.
105         * @param path relative or absolute path within the classpath
106         * @param classLoader the class loader to load the resource with, if any
107         * @param clazz the class to load resources with, if any
108         * @deprecated as of 4.3.13, in favor of selective use of
109         * {@link #ClassPathResource(String, ClassLoader)} vs {@link #ClassPathResource(String, Class)}
110         */
111        @Deprecated
112        protected ClassPathResource(String path, @Nullable ClassLoader classLoader, @Nullable Class<?> clazz) {
113                this.path = StringUtils.cleanPath(path);
114                this.classLoader = classLoader;
115                this.clazz = clazz;
116        }
117
118
119        /**
120         * Return the path for this resource (as resource path within the class path).
121         */
122        public final String getPath() {
123                return this.path;
124        }
125
126        /**
127         * Return the ClassLoader that this resource will be obtained from.
128         */
129        @Nullable
130        public final ClassLoader getClassLoader() {
131                return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
132        }
133
134
135        /**
136         * This implementation checks for the resolution of a resource URL.
137         * @see java.lang.ClassLoader#getResource(String)
138         * @see java.lang.Class#getResource(String)
139         */
140        @Override
141        public boolean exists() {
142                return (resolveURL() != null);
143        }
144
145        /**
146         * Resolves a URL for the underlying class path resource.
147         * @return the resolved URL, or {@code null} if not resolvable
148         */
149        @Nullable
150        protected URL resolveURL() {
151                if (this.clazz != null) {
152                        return this.clazz.getResource(this.path);
153                }
154                else if (this.classLoader != null) {
155                        return this.classLoader.getResource(this.path);
156                }
157                else {
158                        return ClassLoader.getSystemResource(this.path);
159                }
160        }
161
162        /**
163         * This implementation opens an InputStream for the given class path resource.
164         * @see java.lang.ClassLoader#getResourceAsStream(String)
165         * @see java.lang.Class#getResourceAsStream(String)
166         */
167        @Override
168        public InputStream getInputStream() throws IOException {
169                InputStream is;
170                if (this.clazz != null) {
171                        is = this.clazz.getResourceAsStream(this.path);
172                }
173                else if (this.classLoader != null) {
174                        is = this.classLoader.getResourceAsStream(this.path);
175                }
176                else {
177                        is = ClassLoader.getSystemResourceAsStream(this.path);
178                }
179                if (is == null) {
180                        throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
181                }
182                return is;
183        }
184
185        /**
186         * This implementation returns a URL for the underlying class path resource,
187         * if available.
188         * @see java.lang.ClassLoader#getResource(String)
189         * @see java.lang.Class#getResource(String)
190         */
191        @Override
192        public URL getURL() throws IOException {
193                URL url = resolveURL();
194                if (url == null) {
195                        throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
196                }
197                return url;
198        }
199
200        /**
201         * This implementation creates a ClassPathResource, applying the given path
202         * relative to the path of the underlying resource of this descriptor.
203         * @see org.springframework.util.StringUtils#applyRelativePath(String, String)
204         */
205        @Override
206        public Resource createRelative(String relativePath) {
207                String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
208                return (this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) :
209                                new ClassPathResource(pathToUse, this.classLoader));
210        }
211
212        /**
213         * This implementation returns the name of the file that this class path
214         * resource refers to.
215         * @see org.springframework.util.StringUtils#getFilename(String)
216         */
217        @Override
218        @Nullable
219        public String getFilename() {
220                return StringUtils.getFilename(this.path);
221        }
222
223        /**
224         * This implementation returns a description that includes the class path location.
225         */
226        @Override
227        public String getDescription() {
228                StringBuilder builder = new StringBuilder("class path resource [");
229                String pathToUse = this.path;
230                if (this.clazz != null && !pathToUse.startsWith("/")) {
231                        builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
232                        builder.append('/');
233                }
234                if (pathToUse.startsWith("/")) {
235                        pathToUse = pathToUse.substring(1);
236                }
237                builder.append(pathToUse);
238                builder.append(']');
239                return builder.toString();
240        }
241
242
243        /**
244         * This implementation compares the underlying class path locations.
245         */
246        @Override
247        public boolean equals(@Nullable Object other) {
248                if (this == other) {
249                        return true;
250                }
251                if (!(other instanceof ClassPathResource)) {
252                        return false;
253                }
254                ClassPathResource otherRes = (ClassPathResource) other;
255                return (this.path.equals(otherRes.path) &&
256                                ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) &&
257                                ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz));
258        }
259
260        /**
261         * This implementation returns the hash code of the underlying
262         * class path location.
263         */
264        @Override
265        public int hashCode() {
266                return this.path.hashCode();
267        }
268
269}