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