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.io.IOException;
036 import java.nio.charset.Charset;
037 import java.util.ArrayList;
038 import java.util.Collection;
039 import java.util.Collections;
040 import java.util.HashMap;
041 import java.util.Iterator;
042 import java.util.Map;
043 import java.util.SortedSet;
044 import java.util.TreeSet;
045
046 /**
047 * @since 4.2
048 */
049 public class DefaultFileSystem implements FileSystem {
050
051 private final Cache cache;
052 private final SortedSet<String> languages = new TreeSet<String>();
053 private File baseDir, workDir;
054 private Charset encoding;
055 private final FilePredicates predicates = new DefaultFilePredicates();
056
057 /**
058 * Only for testing
059 */
060 public DefaultFileSystem() {
061 this.cache = new MapCache();
062 }
063
064 protected DefaultFileSystem(Cache cache) {
065 this.cache = cache;
066 }
067
068 public DefaultFileSystem setBaseDir(File d) {
069 Preconditions.checkNotNull(d, "Base directory can't be null");
070 this.baseDir = d.getAbsoluteFile();
071 return this;
072 }
073
074 @Override
075 public File baseDir() {
076 return baseDir;
077 }
078
079 public DefaultFileSystem setEncoding(@Nullable Charset e) {
080 this.encoding = e;
081 return this;
082 }
083
084 @Override
085 public Charset encoding() {
086 return encoding == null ? Charset.defaultCharset() : encoding;
087 }
088
089 public boolean isDefaultJvmEncoding() {
090 return encoding == null;
091 }
092
093 public DefaultFileSystem setWorkDir(File d) {
094 this.workDir = d.getAbsoluteFile();
095 return this;
096 }
097
098 @Override
099 public File workDir() {
100 return workDir;
101 }
102
103 @Override
104 public InputFile inputFile(FilePredicate predicate) {
105 doPreloadFiles();
106 if (predicate instanceof RelativePathPredicate) {
107 return cache.inputFile((RelativePathPredicate) predicate);
108 }
109 Iterable<InputFile> files = inputFiles(predicate);
110 Iterator<InputFile> iterator = files.iterator();
111 if (!iterator.hasNext()) {
112 return null;
113 }
114 InputFile first = iterator.next();
115 if (!iterator.hasNext()) {
116 return first;
117 }
118
119 StringBuilder sb = new StringBuilder();
120 sb.append("expected one element but was: <" + first);
121 for (int i = 0; i < 4 && iterator.hasNext(); i++) {
122 sb.append(", " + iterator.next());
123 }
124 if (iterator.hasNext()) {
125 sb.append(", ...");
126 }
127 sb.append('>');
128
129 throw new IllegalArgumentException(sb.toString());
130
131 }
132
133 @Override
134 public Iterable<InputFile> inputFiles(FilePredicate predicate) {
135 doPreloadFiles();
136 return filter(cache.inputFiles(), predicate);
137 }
138
139 @Override
140 public boolean hasFiles(FilePredicate predicate) {
141 doPreloadFiles();
142 for (InputFile element : cache.inputFiles()) {
143 if (predicate.apply(element)) {
144 return true;
145 }
146 }
147 return false;
148 }
149
150 @Override
151 public Iterable<File> files(FilePredicate predicate) {
152 doPreloadFiles();
153 Collection<File> result = new ArrayList<File>();
154 for (InputFile element : inputFiles(predicate)) {
155 if (predicate.apply(element)) {
156 result.add(element.file());
157 }
158 }
159 return result;
160 }
161
162 @Override
163 public InputDir inputDir(File dir) {
164 doPreloadFiles();
165 String relativePath = PathUtils.sanitize(new PathResolver().relativePath(baseDir, dir));
166 if (relativePath == null) {
167 return null;
168 }
169 return cache.inputDir(relativePath);
170 }
171
172 public static Collection<InputFile> filter(Iterable<InputFile> target, FilePredicate predicate) {
173 Collection<InputFile> result = new ArrayList<InputFile>();
174 for (InputFile element : target) {
175 if (predicate.apply(element)) {
176 result.add(element);
177 }
178 }
179 return result;
180 }
181
182 /**
183 * Adds InputFile to the list and registers its language, if present.
184 * Synchronized because PersistIt Exchange is not concurrent
185 */
186 public synchronized DefaultFileSystem add(InputFile inputFile) {
187 cache.add(inputFile);
188 if (inputFile.language() != null) {
189 languages.add(inputFile.language());
190 }
191 return this;
192 }
193
194 /**
195 * Adds InputDir to the list.
196 * Synchronized because PersistIt Exchange is not concurrent
197 */
198 public synchronized DefaultFileSystem add(InputDir inputDir) {
199 cache.add(inputDir);
200 return this;
201 }
202
203 /**
204 * Adds a language to the list. To be used only for unit tests that need to use {@link #languages()} without
205 * using {@link #add(org.sonar.api.batch.fs.InputFile)}.
206 */
207 public DefaultFileSystem addLanguages(String language, String... others) {
208 languages.add(language);
209 Collections.addAll(languages, others);
210 return this;
211 }
212
213 @Override
214 public SortedSet<String> languages() {
215 doPreloadFiles();
216 return languages;
217 }
218
219 @Override
220 public FilePredicates predicates() {
221 return predicates;
222 }
223
224 /**
225 * This method is called before each search of files.
226 */
227 protected void doPreloadFiles() {
228 // nothing to do by default
229 }
230
231 public abstract static class Cache {
232 protected abstract Iterable<InputFile> inputFiles();
233
234 @CheckForNull
235 protected abstract InputFile inputFile(RelativePathPredicate predicate);
236
237 @CheckForNull
238 protected abstract InputDir inputDir(String relativePath);
239
240 protected abstract void doAdd(InputFile inputFile);
241
242 protected abstract void doAdd(InputDir inputDir);
243
244 final void add(InputFile inputFile) {
245 doAdd(inputFile);
246 }
247
248 public void add(InputDir inputDir) {
249 doAdd(inputDir);
250 }
251
252 }
253
254 /**
255 * Used only for testing
256 */
257 private static class MapCache extends Cache {
258 private final Map<String, InputFile> fileMap = new HashMap<String, InputFile>();
259 private final Map<String, InputDir> dirMap = new HashMap<String, InputDir>();
260
261 @Override
262 public Iterable<InputFile> inputFiles() {
263 return new ArrayList<InputFile>(fileMap.values());
264 }
265
266 @Override
267 public InputFile inputFile(RelativePathPredicate predicate) {
268 return fileMap.get(predicate.path());
269 }
270
271 @Override
272 protected InputDir inputDir(String relativePath) {
273 return dirMap.get(relativePath);
274 }
275
276 @Override
277 protected void doAdd(InputFile inputFile) {
278 fileMap.put(inputFile.relativePath(), inputFile);
279 }
280
281 @Override
282 protected void doAdd(InputDir inputDir) {
283 dirMap.put(inputDir.relativePath(), inputDir);
284 }
285 }
286
287 @Override
288 public File resolvePath(String path) {
289 File file = new File(path);
290 if (!file.isAbsolute()) {
291 try {
292 file = new File(baseDir(), path).getCanonicalFile();
293 } catch (IOException e) {
294 throw new IllegalArgumentException("Unable to resolve path '" + path + "'", e);
295 }
296 }
297 return file;
298 }
299 }