001/*
002 * Copyright 2012-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 *      http://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.boot.devtools.restart.classloader;
018
019import java.io.Serializable;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.LinkedHashMap;
023import java.util.Map;
024import java.util.Map.Entry;
025import java.util.Set;
026
027import javax.management.loading.ClassLoaderRepository;
028
029import org.springframework.util.Assert;
030
031/**
032 * {@link ClassLoaderFileRepository} that maintains a collection of
033 * {@link ClassLoaderFile} items grouped by source folders.
034 *
035 * @author Phillip Webb
036 * @since 1.3.0
037 * @see ClassLoaderFile
038 * @see ClassLoaderRepository
039 */
040public class ClassLoaderFiles implements ClassLoaderFileRepository, Serializable {
041
042        private static final long serialVersionUID = 1;
043
044        private final Map<String, SourceFolder> sourceFolders;
045
046        /**
047         * Create a new {@link ClassLoaderFiles} instance.
048         */
049        public ClassLoaderFiles() {
050                this.sourceFolders = new LinkedHashMap<>();
051        }
052
053        /**
054         * Create a new {@link ClassLoaderFiles} instance.
055         * @param classLoaderFiles the source classloader files.
056         */
057        public ClassLoaderFiles(ClassLoaderFiles classLoaderFiles) {
058                Assert.notNull(classLoaderFiles, "ClassLoaderFiles must not be null");
059                this.sourceFolders = new LinkedHashMap<>(classLoaderFiles.sourceFolders);
060        }
061
062        /**
063         * Add all elements items from the specified {@link ClassLoaderFiles} to this
064         * instance.
065         * @param files the files to add
066         */
067        public void addAll(ClassLoaderFiles files) {
068                Assert.notNull(files, "Files must not be null");
069                for (SourceFolder folder : files.getSourceFolders()) {
070                        for (Map.Entry<String, ClassLoaderFile> entry : folder.getFilesEntrySet()) {
071                                addFile(folder.getName(), entry.getKey(), entry.getValue());
072                        }
073                }
074        }
075
076        /**
077         * Add a single {@link ClassLoaderFile} to the collection.
078         * @param name the name of the file
079         * @param file the file to add
080         */
081        public void addFile(String name, ClassLoaderFile file) {
082                addFile("", name, file);
083        }
084
085        /**
086         * Add a single {@link ClassLoaderFile} to the collection.
087         * @param sourceFolder the source folder of the file
088         * @param name the name of the file
089         * @param file the file to add
090         */
091        public void addFile(String sourceFolder, String name, ClassLoaderFile file) {
092                Assert.notNull(sourceFolder, "SourceFolder must not be null");
093                Assert.notNull(name, "Name must not be null");
094                Assert.notNull(file, "File must not be null");
095                removeAll(name);
096                getOrCreateSourceFolder(sourceFolder).add(name, file);
097        }
098
099        private void removeAll(String name) {
100                for (SourceFolder sourceFolder : this.sourceFolders.values()) {
101                        sourceFolder.remove(name);
102                }
103        }
104
105        /**
106         * Get or create a {@link SourceFolder} with the given name.
107         * @param name the name of the folder
108         * @return an existing or newly added {@link SourceFolder}
109         */
110        protected final SourceFolder getOrCreateSourceFolder(String name) {
111                SourceFolder sourceFolder = this.sourceFolders.get(name);
112                if (sourceFolder == null) {
113                        sourceFolder = new SourceFolder(name);
114                        this.sourceFolders.put(name, sourceFolder);
115                }
116                return sourceFolder;
117        }
118
119        /**
120         * Return all {@link SourceFolder SourceFolders} that have been added to the
121         * collection.
122         * @return a collection of {@link SourceFolder} items
123         */
124        public Collection<SourceFolder> getSourceFolders() {
125                return Collections.unmodifiableCollection(this.sourceFolders.values());
126        }
127
128        /**
129         * Return the size of the collection.
130         * @return the size of the collection
131         */
132        public int size() {
133                int size = 0;
134                for (SourceFolder sourceFolder : this.sourceFolders.values()) {
135                        size += sourceFolder.getFiles().size();
136                }
137                return size;
138        }
139
140        @Override
141        public ClassLoaderFile getFile(String name) {
142                for (SourceFolder sourceFolder : this.sourceFolders.values()) {
143                        ClassLoaderFile file = sourceFolder.get(name);
144                        if (file != null) {
145                                return file;
146                        }
147                }
148                return null;
149        }
150
151        /**
152         * An individual source folder that is being managed by the collection.
153         */
154        public static class SourceFolder implements Serializable {
155
156                private static final long serialVersionUID = 1;
157
158                private final String name;
159
160                private final Map<String, ClassLoaderFile> files = new LinkedHashMap<>();
161
162                SourceFolder(String name) {
163                        this.name = name;
164                }
165
166                public Set<Entry<String, ClassLoaderFile>> getFilesEntrySet() {
167                        return this.files.entrySet();
168                }
169
170                protected final void add(String name, ClassLoaderFile file) {
171                        this.files.put(name, file);
172                }
173
174                protected final void remove(String name) {
175                        this.files.remove(name);
176                }
177
178                protected final ClassLoaderFile get(String name) {
179                        return this.files.get(name);
180                }
181
182                /**
183                 * Return the name of the source folder.
184                 * @return the name of the source folder
185                 */
186                public String getName() {
187                        return this.name;
188                }
189
190                /**
191                 * Return all {@link ClassLoaderFile ClassLoaderFiles} in the collection that are
192                 * contained in this source folder.
193                 * @return the files contained in the source folder
194                 */
195                public Collection<ClassLoaderFile> getFiles() {
196                        return Collections.unmodifiableCollection(this.files.values());
197                }
198
199        }
200
201}