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