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    }