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