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