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.Function;
023 import com.google.common.collect.Iterables;
024 import org.sonar.api.batch.fs.FilePredicate;
025 import org.sonar.api.batch.fs.FilePredicates;
026 import org.sonar.api.batch.fs.FileSystem;
027 import org.sonar.api.batch.fs.InputDir;
028 import org.sonar.api.batch.fs.InputFile;
029 import org.sonar.api.scan.filesystem.PathResolver;
030 import org.sonar.api.utils.PathUtils;
031
032 import javax.annotation.CheckForNull;
033 import javax.annotation.Nullable;
034
035 import java.io.File;
036 import java.nio.charset.Charset;
037 import java.util.ArrayList;
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;
053 private File workDir;
054 private Charset encoding;
055 private final FilePredicates predicates;
056
057 /**
058 * Only for testing
059 */
060 public DefaultFileSystem(File baseDir) {
061 this(baseDir, new MapCache());
062 }
063
064 protected DefaultFileSystem(File baseDir, Cache cache) {
065 // Basedir can be null with views
066 this.baseDir = baseDir != null ? baseDir.getAbsoluteFile() : new File(".");
067 this.cache = cache;
068 this.predicates = new DefaultFilePredicates(baseDir);
069 }
070
071 @Override
072 public File baseDir() {
073 return baseDir;
074 }
075
076 public void setBaseDir(File baseDir) {
077 this.baseDir = baseDir;
078 }
079
080 public DefaultFileSystem setEncoding(@Nullable Charset e) {
081 this.encoding = e;
082 return this;
083 }
084
085 @Override
086 public Charset encoding() {
087 return encoding == null ? Charset.defaultCharset() : encoding;
088 }
089
090 public boolean isDefaultJvmEncoding() {
091 return encoding == null;
092 }
093
094 public DefaultFileSystem setWorkDir(File d) {
095 this.workDir = d.getAbsoluteFile();
096 return this;
097 }
098
099 @Override
100 public File workDir() {
101 return workDir;
102 }
103
104 @Override
105 public InputFile inputFile(FilePredicate predicate) {
106 Iterable<InputFile> files = inputFiles(predicate);
107 Iterator<InputFile> iterator = files.iterator();
108 if (!iterator.hasNext()) {
109 return null;
110 }
111 InputFile first = iterator.next();
112 if (!iterator.hasNext()) {
113 return first;
114 }
115
116 StringBuilder sb = new StringBuilder();
117 sb.append("expected one element but was: <" + first);
118 for (int i = 0; i < 4 && iterator.hasNext(); i++) {
119 sb.append(", " + iterator.next());
120 }
121 if (iterator.hasNext()) {
122 sb.append(", ...");
123 }
124 sb.append('>');
125
126 throw new IllegalArgumentException(sb.toString());
127
128 }
129
130 @Override
131 public Iterable<InputFile> inputFiles(FilePredicate predicate) {
132 doPreloadFiles();
133 return OptimizedFilePredicateAdapter.create(predicate).get(cache);
134 }
135
136 @Override
137 public boolean hasFiles(FilePredicate predicate) {
138 return inputFiles(predicate).iterator().hasNext();
139 }
140
141 @Override
142 public Iterable<File> files(FilePredicate predicate) {
143 doPreloadFiles();
144 return Iterables.transform(inputFiles(predicate), new Function<InputFile, File>() {
145 @Override
146 public File apply(InputFile input) {
147 return input.file();
148 }
149 });
150 }
151
152 @Override
153 public InputDir inputDir(File dir) {
154 doPreloadFiles();
155 String relativePath = PathUtils.sanitize(new PathResolver().relativePath(baseDir, dir));
156 if (relativePath == null) {
157 return null;
158 }
159 return cache.inputDir(relativePath);
160 }
161
162 /**
163 * Adds InputFile to the list and registers its language, if present.
164 */
165 public DefaultFileSystem add(InputFile inputFile) {
166 cache.add(inputFile);
167 if (inputFile.language() != null) {
168 languages.add(inputFile.language());
169 }
170 return this;
171 }
172
173 /**
174 * Adds InputDir to the list.
175 */
176 public DefaultFileSystem add(InputDir inputDir) {
177 cache.add(inputDir);
178 return this;
179 }
180
181 /**
182 * Adds a language to the list. To be used only for unit tests that need to use {@link #languages()} without
183 * using {@link #add(org.sonar.api.batch.fs.InputFile)}.
184 */
185 public DefaultFileSystem addLanguages(String language, String... others) {
186 languages.add(language);
187 Collections.addAll(languages, others);
188 return this;
189 }
190
191 @Override
192 public SortedSet<String> languages() {
193 doPreloadFiles();
194 return languages;
195 }
196
197 @Override
198 public FilePredicates predicates() {
199 return predicates;
200 }
201
202 /**
203 * This method is called before each search of files.
204 */
205 protected void doPreloadFiles() {
206 // nothing to do by default
207 }
208
209 public abstract static class Cache implements Index {
210 @Override
211 public abstract Iterable<InputFile> inputFiles();
212
213 @Override
214 @CheckForNull
215 public abstract InputFile inputFile(String relativePath);
216
217 @Override
218 @CheckForNull
219 public abstract InputDir inputDir(String relativePath);
220
221 protected abstract void doAdd(InputFile inputFile);
222
223 protected abstract void doAdd(InputDir inputDir);
224
225 final void add(InputFile inputFile) {
226 doAdd(inputFile);
227 }
228
229 public void add(InputDir inputDir) {
230 doAdd(inputDir);
231 }
232
233 }
234
235 /**
236 * Used only for testing
237 */
238 private static class MapCache extends Cache {
239 private final Map<String, InputFile> fileMap = new HashMap<String, InputFile>();
240 private final Map<String, InputDir> dirMap = new HashMap<String, InputDir>();
241
242 @Override
243 public Iterable<InputFile> inputFiles() {
244 return new ArrayList<InputFile>(fileMap.values());
245 }
246
247 @Override
248 public InputFile inputFile(String relativePath) {
249 return fileMap.get(relativePath);
250 }
251
252 @Override
253 public InputDir inputDir(String relativePath) {
254 return dirMap.get(relativePath);
255 }
256
257 @Override
258 protected void doAdd(InputFile inputFile) {
259 fileMap.put(inputFile.relativePath(), inputFile);
260 }
261
262 @Override
263 protected void doAdd(InputDir inputDir) {
264 dirMap.put(inputDir.relativePath(), inputDir);
265 }
266 }
267
268 }