001/*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2013 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 */
020package org.sonar.api.batch.fs.internal;
021
022import com.google.common.base.Function;
023import com.google.common.base.Preconditions;
024import com.google.common.base.Predicate;
025import com.google.common.collect.Iterables;
026import com.google.common.collect.Lists;
027import com.google.common.collect.Maps;
028import com.google.common.collect.Sets;
029import org.sonar.api.batch.fs.FilePredicate;
030import org.sonar.api.batch.fs.FilePredicates;
031import org.sonar.api.batch.fs.FileSystem;
032import org.sonar.api.batch.fs.InputFile;
033
034import javax.annotation.CheckForNull;
035import javax.annotation.Nullable;
036import java.io.File;
037import java.nio.charset.Charset;
038import java.util.Collections;
039import java.util.List;
040import java.util.Map;
041import java.util.NoSuchElementException;
042import java.util.SortedSet;
043
044/**
045 * @since 4.2
046 */
047public 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}