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.instrument.classloading;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.net.URL;
022import java.util.Enumeration;
023import java.util.HashMap;
024import java.util.Map;
025
026import org.springframework.lang.Nullable;
027import org.springframework.util.Assert;
028
029/**
030 * Subclass of ShadowingClassLoader that overrides attempts to
031 * locate certain files.
032 *
033 * @author Rod Johnson
034 * @author Adrian Colyer
035 * @since 2.0
036 */
037public class ResourceOverridingShadowingClassLoader extends ShadowingClassLoader {
038
039        private static final Enumeration<URL> EMPTY_URL_ENUMERATION = new Enumeration<URL>() {
040                @Override
041                public boolean hasMoreElements() {
042                        return false;
043                }
044                @Override
045                public URL nextElement() {
046                        throw new UnsupportedOperationException("Should not be called. I am empty.");
047                }
048        };
049
050
051        /**
052         * Key is asked for value: value is actual value.
053         */
054        private Map<String, String> overrides = new HashMap<>();
055
056
057        /**
058         * Create a new ResourceOverridingShadowingClassLoader,
059         * decorating the given ClassLoader.
060         * @param enclosingClassLoader the ClassLoader to decorate
061         */
062        public ResourceOverridingShadowingClassLoader(ClassLoader enclosingClassLoader) {
063                super(enclosingClassLoader);
064        }
065
066
067        /**
068         * Return the resource (if any) at the new path
069         * on an attempt to locate a resource at the old path.
070         * @param oldPath the path requested
071         * @param newPath the actual path to be looked up
072         */
073        public void override(String oldPath, String newPath) {
074                this.overrides.put(oldPath, newPath);
075        }
076
077        /**
078         * Ensure that a resource with the given path is not found.
079         * @param oldPath the path of the resource to hide even if
080         * it exists in the parent ClassLoader
081         */
082        public void suppress(String oldPath) {
083                this.overrides.put(oldPath, null);
084        }
085
086        /**
087         * Copy all overrides from the given ClassLoader.
088         * @param other the other ClassLoader to copy from
089         */
090        public void copyOverrides(ResourceOverridingShadowingClassLoader other) {
091                Assert.notNull(other, "Other ClassLoader must not be null");
092                this.overrides.putAll(other.overrides);
093        }
094
095
096        @Override
097        public URL getResource(String requestedPath) {
098                if (this.overrides.containsKey(requestedPath)) {
099                        String overriddenPath = this.overrides.get(requestedPath);
100                        return (overriddenPath != null ? super.getResource(overriddenPath) : null);
101                }
102                else {
103                        return super.getResource(requestedPath);
104                }
105        }
106
107        @Override
108        @Nullable
109        public InputStream getResourceAsStream(String requestedPath) {
110                if (this.overrides.containsKey(requestedPath)) {
111                        String overriddenPath = this.overrides.get(requestedPath);
112                        return (overriddenPath != null ? super.getResourceAsStream(overriddenPath) : null);
113                }
114                else {
115                        return super.getResourceAsStream(requestedPath);
116                }
117        }
118
119        @Override
120        public Enumeration<URL> getResources(String requestedPath) throws IOException {
121                if (this.overrides.containsKey(requestedPath)) {
122                        String overriddenLocation = this.overrides.get(requestedPath);
123                        return (overriddenLocation != null ?
124                                        super.getResources(overriddenLocation) : EMPTY_URL_ENUMERATION);
125                }
126                else {
127                        return super.getResources(requestedPath);
128                }
129        }
130
131}