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.base.Predicate;
025 import com.google.common.collect.Iterables;
026 import com.google.common.collect.Lists;
027 import com.google.common.collect.Maps;
028 import com.google.common.collect.Sets;
029 import org.sonar.api.batch.fs.FilePredicate;
030 import org.sonar.api.batch.fs.FilePredicates;
031 import org.sonar.api.batch.fs.FileSystem;
032 import org.sonar.api.batch.fs.InputFile;
033
034 import javax.annotation.CheckForNull;
035 import javax.annotation.Nullable;
036 import java.io.File;
037 import java.nio.charset.Charset;
038 import java.util.Collections;
039 import java.util.List;
040 import java.util.Map;
041 import java.util.NoSuchElementException;
042 import java.util.SortedSet;
043
044 /**
045 * @since 4.2
046 */
047 public class DefaultFileSystem implements FileSystem {
048
049 private final Cache cache;
050 private final SortedSet<String> languages = Sets.newTreeSet();
051 private File baseDir, workDir;
052 private Charset encoding;
053 private final FilePredicates predicates = new DefaultFilePredicates();
054
055 /**
056 * Only for testing
057 */
058 public DefaultFileSystem() {
059 this.cache = new MapCache();
060 }
061
062 protected DefaultFileSystem(Cache cache) {
063 this.cache = cache;
064 }
065
066 public DefaultFileSystem setBaseDir(File d) {
067 Preconditions.checkNotNull(d, "Base directory can't be null");
068 this.baseDir = d.getAbsoluteFile();
069 return this;
070 }
071
072 @Override
073 public File baseDir() {
074 return baseDir;
075 }
076
077 public DefaultFileSystem setEncoding(@Nullable Charset e) {
078 this.encoding = e;
079 return this;
080 }
081
082 @Override
083 public Charset encoding() {
084 return encoding == null ? Charset.defaultCharset() : encoding;
085 }
086
087 public boolean isDefaultJvmEncoding() {
088 return encoding == null;
089 }
090
091 public DefaultFileSystem setWorkDir(File d) {
092 this.workDir = d.getAbsoluteFile();
093 return this;
094 }
095
096 @Override
097 public File workDir() {
098 return workDir;
099 }
100
101 @Override
102 public InputFile inputFile(FilePredicate predicate) {
103 doPreloadFiles();
104 if (predicate instanceof UniqueIndexPredicate) {
105 return cache.inputFile((UniqueIndexPredicate) predicate);
106 }
107 try {
108 Iterable<InputFile> files = inputFiles(predicate);
109 return Iterables.getOnlyElement(files);
110 } catch (NoSuchElementException e) {
111 // contrary to guava, return null if iterable is empty
112 return null;
113 }
114 }
115
116 @Override
117 public Iterable<InputFile> inputFiles(FilePredicate predicate) {
118 doPreloadFiles();
119 return Iterables.filter(cache.inputFiles(), new GuavaPredicate(predicate));
120 }
121
122 @Override
123 public boolean hasFiles(FilePredicate predicate) {
124 doPreloadFiles();
125 return Iterables.indexOf(cache.inputFiles(), new GuavaPredicate(predicate)) >= 0;
126 }
127
128 @Override
129 public Iterable<File> files(FilePredicate predicate) {
130 doPreloadFiles();
131 return Iterables.transform(inputFiles(predicate), new Function<InputFile, File>() {
132 @Override
133 public File apply(@Nullable InputFile input) {
134 return input == null ? null : input.file();
135 }
136 });
137 }
138
139 /**
140 * Adds InputFile to the list and registers its language, if present.
141 */
142 public DefaultFileSystem add(InputFile inputFile) {
143 cache.add(inputFile);
144 if (inputFile.language() != null) {
145 languages.add(inputFile.language());
146 }
147 return this;
148 }
149
150 /**
151 * Adds a language to the list. To be used only for unit tests that need to use {@link #languages()} without
152 * using {@link #add(org.sonar.api.batch.fs.InputFile)}.
153 */
154 public DefaultFileSystem addLanguages(String language, String... others) {
155 languages.add(language);
156 Collections.addAll(languages, others);
157 return this;
158 }
159
160 @Override
161 public SortedSet<String> languages() {
162 doPreloadFiles();
163 return languages;
164 }
165
166 @Override
167 public FilePredicates predicates() {
168 return predicates;
169 }
170
171 /**
172 * This method is called before each search of files.
173 */
174 protected void doPreloadFiles() {
175 // nothing to do by default
176 }
177
178 public static abstract class Cache {
179 protected abstract Iterable<InputFile> inputFiles();
180
181 @CheckForNull
182 protected abstract InputFile inputFile(UniqueIndexPredicate predicate);
183
184 protected abstract void doAdd(InputFile inputFile);
185
186 protected abstract void doIndex(String indexId, Object value, InputFile inputFile);
187
188 final void add(InputFile inputFile) {
189 doAdd(inputFile);
190 for (FileIndex index : FileIndex.ALL) {
191 doIndex(index.id(), index.valueOf(inputFile), inputFile);
192 }
193 }
194 }
195
196 /**
197 * Used only for testing
198 */
199 private static class MapCache extends Cache {
200 private final List<InputFile> files = Lists.newArrayList();
201 private final Map<String, Map<Object, InputFile>> fileMap = Maps.newHashMap();
202
203 @Override
204 public Iterable<InputFile> inputFiles() {
205 return Lists.newArrayList(files);
206 }
207
208 @Override
209 public InputFile inputFile(UniqueIndexPredicate predicate) {
210 Map<Object, InputFile> byAttr = fileMap.get(predicate.indexId());
211 if (byAttr != null) {
212 return byAttr.get(predicate.value());
213 }
214 return null;
215 }
216
217 @Override
218 protected void doAdd(InputFile inputFile) {
219 files.add(inputFile);
220 }
221
222 @Override
223 protected void doIndex(String indexId, Object value, InputFile inputFile) {
224 Map<Object, InputFile> attrValues = fileMap.get(indexId);
225 if (attrValues == null) {
226 attrValues = Maps.newHashMap();
227 fileMap.put(indexId, attrValues);
228 }
229 attrValues.put(value, inputFile);
230 }
231 }
232
233 private static class GuavaPredicate implements Predicate<InputFile> {
234 private final FilePredicate predicate;
235
236 private GuavaPredicate(FilePredicate predicate) {
237 this.predicate = predicate;
238 }
239
240 @Override
241 public boolean apply(@Nullable InputFile input) {
242 return input != null && predicate.apply(input);
243 }
244 }
245 }