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.FileSystem;
031import org.sonar.api.batch.fs.InputFile;
032import org.sonar.api.batch.fs.UniqueIndexPredicate;
033
034import javax.annotation.CheckForNull;
035import javax.annotation.Nullable;
036import java.io.File;
037import java.nio.charset.Charset;
038import java.util.List;
039import java.util.Map;
040import java.util.NoSuchElementException;
041import java.util.SortedSet;
042
043/**
044 * @since 4.2
045 */
046public class DefaultFileSystem implements FileSystem {
047
048  private final Cache cache;
049  private final SortedSet<String> languages = Sets.newTreeSet();
050  private File baseDir, workDir;
051  private Charset encoding;
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 UniqueIndexPredicate) {
103      return cache.inputFile((UniqueIndexPredicate) predicate);
104    }
105    try {
106      Iterable<InputFile> files = inputFiles(predicate);
107      return Iterables.getOnlyElement(files);
108    } catch (NoSuchElementException e) {
109      // contrary to guava, return null if iterable is empty
110      return null;
111    }
112  }
113
114  @Override
115  public Iterable<InputFile> inputFiles(FilePredicate predicate) {
116    doPreloadFiles();
117    return Iterables.filter(cache.inputFiles(), new GuavaPredicate(predicate));
118  }
119
120  @Override
121  public boolean hasFiles(FilePredicate predicate) {
122    doPreloadFiles();
123    return Iterables.indexOf(cache.inputFiles(), new GuavaPredicate(predicate)) >= 0;
124  }
125
126  @Override
127  public Iterable<File> files(FilePredicate predicate) {
128    doPreloadFiles();
129    return Iterables.transform(inputFiles(predicate), new Function<InputFile, File>() {
130      @Override
131      public File apply(@Nullable InputFile input) {
132        return input == null ? null : input.file();
133      }
134    });
135  }
136
137  /**
138   * Adds InputFile to the list and registers its language, if present.
139   */
140  public DefaultFileSystem add(InputFile inputFile) {
141    cache.add(inputFile);
142    if (inputFile.language() != null) {
143      languages.add(inputFile.language());
144    }
145    return this;
146  }
147
148  /**
149   * Adds a language to the list. To be used only for unit tests that need to use {@link #languages()} without
150   * using {@link #add(org.sonar.api.batch.fs.InputFile)}.
151   */
152  public DefaultFileSystem addLanguages(String language, String... others) {
153    languages.add(language);
154    for (String other : others) {
155      languages.add(other);
156    }
157    return this;
158  }
159
160  @Override
161  public SortedSet<String> languages() {
162    doPreloadFiles();
163    return languages;
164  }
165
166  /**
167   * This method is called before each search of files.
168   */
169  protected void doPreloadFiles() {
170    // nothing to do by default
171  }
172
173  public static abstract class Cache {
174    protected abstract Iterable<InputFile> inputFiles();
175
176    @CheckForNull
177    protected abstract InputFile inputFile(UniqueIndexPredicate predicate);
178
179    protected abstract void doAdd(InputFile inputFile);
180
181    protected abstract void doIndex(String indexId, Object value, InputFile inputFile);
182
183    final void add(InputFile inputFile) {
184      doAdd(inputFile);
185      for (FileIndex index : FileIndex.ALL) {
186        doIndex(index.id(), index.valueOf(inputFile), inputFile);
187      }
188    }
189  }
190
191  /**
192   * Used only for testing
193   */
194  private static class MapCache extends Cache {
195    private final List<InputFile> files = Lists.newArrayList();
196    private final Map<String, Map<Object, InputFile>> fileMap = Maps.newHashMap();
197
198    @Override
199    public Iterable<InputFile> inputFiles() {
200      return Lists.newArrayList(files);
201    }
202
203    @Override
204    public InputFile inputFile(UniqueIndexPredicate predicate) {
205      Map<Object, InputFile> byAttr = fileMap.get(predicate.indexId());
206      if (byAttr != null) {
207        return byAttr.get(predicate.value());
208      }
209      return null;
210    }
211
212    @Override
213    protected void doAdd(InputFile inputFile) {
214      files.add(inputFile);
215    }
216
217    @Override
218    protected void doIndex(String indexId, Object value, InputFile inputFile) {
219      Map<Object, InputFile> attrValues = fileMap.get(indexId);
220      if (attrValues == null) {
221        attrValues = Maps.newHashMap();
222        fileMap.put(indexId, attrValues);
223      }
224      attrValues.put(value, inputFile);
225    }
226  }
227
228  private static class GuavaPredicate implements Predicate<InputFile> {
229    private final FilePredicate predicate;
230
231    private GuavaPredicate(FilePredicate predicate) {
232      this.predicate = predicate;
233    }
234
235    @Override
236    public boolean apply(@Nullable InputFile input) {
237      return input != null && predicate.apply(input);
238    }
239  }
240}