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.Function;
023    import com.google.common.base.Preconditions;
024    import com.google.common.base.Predicate;
025    import com.google.common.collect.Iterables;
026    import com.google.common.collect.Lists;
027    import com.google.common.collect.Maps;
028    import com.google.common.collect.Sets;
029    import org.sonar.api.batch.fs.FilePredicate;
030    import org.sonar.api.batch.fs.FilePredicates;
031    import org.sonar.api.batch.fs.FileSystem;
032    import org.sonar.api.batch.fs.InputFile;
033    
034    import javax.annotation.CheckForNull;
035    import javax.annotation.Nullable;
036    import java.io.File;
037    import java.nio.charset.Charset;
038    import java.util.Collections;
039    import java.util.List;
040    import java.util.Map;
041    import java.util.NoSuchElementException;
042    import java.util.SortedSet;
043    
044    /**
045     * @since 4.2
046     */
047    public 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    }