001 /*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
019 */
020 package org.sonar.api.batch.fs.internal;
021
022 import com.google.common.base.Preconditions;
023 import org.sonar.api.batch.fs.FilePredicate;
024 import org.sonar.api.batch.fs.FilePredicates;
025 import org.sonar.api.batch.fs.FileSystem;
026 import org.sonar.api.batch.fs.InputDir;
027 import org.sonar.api.batch.fs.InputFile;
028 import org.sonar.api.scan.filesystem.PathResolver;
029 import org.sonar.api.utils.PathUtils;
030
031 import javax.annotation.CheckForNull;
032 import javax.annotation.Nullable;
033
034 import java.io.File;
035 import java.nio.charset.Charset;
036 import java.util.ArrayList;
037 import java.util.Collection;
038 import java.util.Collections;
039 import java.util.HashMap;
040 import java.util.Iterator;
041 import java.util.Map;
042 import java.util.SortedSet;
043 import java.util.TreeSet;
044
045 /**
046 * @since 4.2
047 */
048 public class DefaultFileSystem implements FileSystem {
049
050 private final Cache cache;
051 private final SortedSet<String> languages = new TreeSet<String>();
052 private File baseDir, workDir;
053 private Charset encoding;
054 private final FilePredicates predicates = new DefaultFilePredicates();
055
056 /**
057 * Only for testing
058 */
059 public DefaultFileSystem() {
060 this.cache = new MapCache();
061 }
062
063 protected DefaultFileSystem(Cache cache) {
064 this.cache = cache;
065 }
066
067 public DefaultFileSystem setBaseDir(File d) {
068 Preconditions.checkNotNull(d, "Base directory can't be null");
069 this.baseDir = d.getAbsoluteFile();
070 return this;
071 }
072
073 @Override
074 public File baseDir() {
075 return baseDir;
076 }
077
078 public DefaultFileSystem setEncoding(@Nullable Charset e) {
079 this.encoding = e;
080 return this;
081 }
082
083 @Override
084 public Charset encoding() {
085 return encoding == null ? Charset.defaultCharset() : encoding;
086 }
087
088 public boolean isDefaultJvmEncoding() {
089 return encoding == null;
090 }
091
092 public DefaultFileSystem setWorkDir(File d) {
093 this.workDir = d.getAbsoluteFile();
094 return this;
095 }
096
097 @Override
098 public File workDir() {
099 return workDir;
100 }
101
102 @Override
103 public InputFile inputFile(FilePredicate predicate) {
104 doPreloadFiles();
105 if (predicate instanceof RelativePathPredicate) {
106 return cache.inputFile((RelativePathPredicate) predicate);
107 }
108 Iterable<InputFile> files = inputFiles(predicate);
109 Iterator<InputFile> iterator = files.iterator();
110 if (!iterator.hasNext()) {
111 return null;
112 }
113 InputFile first = iterator.next();
114 if (!iterator.hasNext()) {
115 return first;
116 }
117
118 StringBuilder sb = new StringBuilder();
119 sb.append("expected one element but was: <" + first);
120 for (int i = 0; i < 4 && iterator.hasNext(); i++) {
121 sb.append(", " + iterator.next());
122 }
123 if (iterator.hasNext()) {
124 sb.append(", ...");
125 }
126 sb.append('>');
127
128 throw new IllegalArgumentException(sb.toString());
129
130 }
131
132 @Override
133 public Iterable<InputFile> inputFiles(FilePredicate predicate) {
134 doPreloadFiles();
135 return filter(cache.inputFiles(), predicate);
136 }
137
138 @Override
139 public boolean hasFiles(FilePredicate predicate) {
140 doPreloadFiles();
141 for (InputFile element : cache.inputFiles()) {
142 if (predicate.apply(element)) {
143 return true;
144 }
145 }
146 return false;
147 }
148
149 @Override
150 public Iterable<File> files(FilePredicate predicate) {
151 doPreloadFiles();
152 Collection<File> result = new ArrayList<File>();
153 for (InputFile element : inputFiles(predicate)) {
154 if (predicate.apply(element)) {
155 result.add(element.file());
156 }
157 }
158 return result;
159 }
160
161 @Override
162 public InputDir inputDir(File dir) {
163 doPreloadFiles();
164 String relativePath = PathUtils.sanitize(new PathResolver().relativePath(baseDir, dir));
165 if (relativePath == null) {
166 return null;
167 }
168 return cache.inputDir(relativePath);
169 }
170
171 public static Collection<InputFile> filter(Iterable<InputFile> target, FilePredicate predicate) {
172 Collection<InputFile> result = new ArrayList<InputFile>();
173 for (InputFile element : target) {
174 if (predicate.apply(element)) {
175 result.add(element);
176 }
177 }
178 return result;
179 }
180
181 /**
182 * Adds InputFile to the list and registers its language, if present.
183 */
184 public DefaultFileSystem add(InputFile inputFile) {
185 cache.add(inputFile);
186 if (inputFile.language() != null) {
187 languages.add(inputFile.language());
188 }
189 return this;
190 }
191
192 /**
193 * Adds InputDir to the list.
194 */
195 public DefaultFileSystem add(InputDir inputDir) {
196 cache.add(inputDir);
197 return this;
198 }
199
200 /**
201 * Adds a language to the list. To be used only for unit tests that need to use {@link #languages()} without
202 * using {@link #add(org.sonar.api.batch.fs.InputFile)}.
203 */
204 public DefaultFileSystem addLanguages(String language, String... others) {
205 languages.add(language);
206 Collections.addAll(languages, others);
207 return this;
208 }
209
210 @Override
211 public SortedSet<String> languages() {
212 doPreloadFiles();
213 return languages;
214 }
215
216 @Override
217 public FilePredicates predicates() {
218 return predicates;
219 }
220
221 /**
222 * This method is called before each search of files.
223 */
224 protected void doPreloadFiles() {
225 // nothing to do by default
226 }
227
228 public abstract static class Cache {
229 protected abstract Iterable<InputFile> inputFiles();
230
231 @CheckForNull
232 protected abstract InputFile inputFile(RelativePathPredicate predicate);
233
234 @CheckForNull
235 protected abstract InputDir inputDir(String relativePath);
236
237 protected abstract void doAdd(InputFile inputFile);
238
239 protected abstract void doAdd(InputDir inputDir);
240
241 final void add(InputFile inputFile) {
242 doAdd(inputFile);
243 }
244
245 public void add(InputDir inputDir) {
246 doAdd(inputDir);
247 }
248
249 }
250
251 /**
252 * Used only for testing
253 */
254 private static class MapCache extends Cache {
255 private final Map<String, InputFile> fileMap = new HashMap<String, InputFile>();
256 private final Map<String, InputDir> dirMap = new HashMap<String, InputDir>();
257
258 @Override
259 public Iterable<InputFile> inputFiles() {
260 return new ArrayList<InputFile>(fileMap.values());
261 }
262
263 @Override
264 public InputFile inputFile(RelativePathPredicate predicate) {
265 return fileMap.get(predicate.path());
266 }
267
268 @Override
269 protected InputDir inputDir(String relativePath) {
270 return dirMap.get(relativePath);
271 }
272
273 @Override
274 protected void doAdd(InputFile inputFile) {
275 fileMap.put(inputFile.relativePath(), inputFile);
276 }
277
278 @Override
279 protected void doAdd(InputDir inputDir) {
280 dirMap.put(inputDir.relativePath(), inputDir);
281 }
282 }
283
284 }