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     */
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.apache.commons.io.Charsets;
030    import org.sonar.api.batch.fs.FilePredicate;
031    import org.sonar.api.batch.fs.FileSystem;
032    import org.sonar.api.batch.fs.InputFile;
033    import org.sonar.api.batch.fs.UniqueIndexPredicate;
034    
035    import javax.annotation.CheckForNull;
036    import javax.annotation.Nullable;
037    import java.io.File;
038    import java.nio.charset.Charset;
039    import java.util.*;
040    
041    /**
042     * @since 4.2
043     */
044    public class DefaultFileSystem implements FileSystem {
045    
046      private final Cache cache;
047      private final Set<String> languages = Sets.newTreeSet();
048      private File baseDir, workDir;
049      private Charset encoding = Charsets.UTF_8;
050      private boolean isDefaultJvmEncoding = false;
051    
052      /**
053       * Only for testing
054       */
055      public DefaultFileSystem() {
056        this.cache = new MapCache();
057      }
058    
059      protected DefaultFileSystem(Cache cache) {
060        this.cache = cache;
061      }
062    
063      public DefaultFileSystem setBaseDir(File d) {
064        Preconditions.checkNotNull(d, "Base directory can't be null");
065        this.baseDir = d.getAbsoluteFile();
066        return this;
067      }
068    
069      @Override
070      public File baseDir() {
071        return baseDir;
072      }
073    
074      public DefaultFileSystem setEncoding(Charset e) {
075        this.encoding = e;
076        return this;
077      }
078    
079      @Override
080      public Charset encoding() {
081        return encoding;
082      }
083    
084      public boolean isDefaultJvmEncoding() {
085        return isDefaultJvmEncoding;
086      }
087    
088      public void setIsDefaultJvmEncoding(boolean b) {
089        this.isDefaultJvmEncoding = b;
090      }
091    
092      public DefaultFileSystem setWorkDir(File d) {
093        this.workDir = d.getAbsoluteFile();
094        return this;
095      }
096    
097      @Override
098      public File workDir() {
099        return workDir;
100      }
101    
102      @Override
103      public InputFile inputFile(FilePredicate predicate) {
104        doPreloadFiles();
105        if (predicate instanceof UniqueIndexPredicate) {
106          return cache.inputFile((UniqueIndexPredicate) predicate);
107        }
108        try {
109          Iterable<InputFile> files = inputFiles(predicate);
110          return Iterables.getOnlyElement(files);
111        } catch (NoSuchElementException e) {
112          // contrary to guava, return null if iterable is empty
113          return null;
114        }
115      }
116    
117      @Override
118      public Iterable<InputFile> inputFiles(FilePredicate predicate) {
119        doPreloadFiles();
120        return Iterables.filter(cache.inputFiles(), new GuavaPredicate(predicate));
121      }
122    
123      @Override
124      public boolean hasFiles(FilePredicate predicate) {
125        doPreloadFiles();
126        return Iterables.indexOf(cache.inputFiles(), new GuavaPredicate(predicate)) >= 0;
127      }
128    
129      @Override
130      public Iterable<File> files(FilePredicate predicate) {
131        doPreloadFiles();
132        return Iterables.transform(inputFiles(predicate), new Function<InputFile, File>() {
133          @Override
134          public File apply(@Nullable InputFile input) {
135            return input == null ? null : input.file();
136          }
137        });
138      }
139    
140      public void add(InputFile inputFile) {
141        cache.add(inputFile);
142        if (inputFile.language() != null) {
143          languages.add(inputFile.language());
144        }
145      }
146    
147      @Override
148      public Set<String> languages() {
149        doPreloadFiles();
150        return languages;
151      }
152    
153      /**
154       * This method is called before each search of files.
155       */
156      protected void doPreloadFiles() {
157        // nothing to do by default
158      }
159    
160      public static abstract class Cache {
161        protected abstract Iterable<InputFile> inputFiles();
162    
163        @CheckForNull
164        protected abstract InputFile inputFile(UniqueIndexPredicate predicate);
165    
166        protected abstract void doAdd(InputFile inputFile);
167    
168        protected abstract void doIndex(String indexId, Object value, InputFile inputFile);
169    
170        final void add(InputFile inputFile) {
171          doAdd(inputFile);
172          for (FileIndex index : FileIndex.ALL) {
173            doIndex(index.id(), index.valueOf(inputFile), inputFile);
174          }
175        }
176      }
177    
178      /**
179       * Used only for testing
180       */
181      private static class MapCache extends Cache {
182        private final List<InputFile> files = Lists.newArrayList();
183        private final Map<String, Map<Object, InputFile>> fileMap = Maps.newHashMap();
184    
185        @Override
186        public Iterable<InputFile> inputFiles() {
187          return Lists.newArrayList(files);
188        }
189    
190        @Override
191        public InputFile inputFile(UniqueIndexPredicate predicate) {
192          Map<Object, InputFile> byAttr = fileMap.get(predicate.indexId());
193          if (byAttr != null) {
194            return byAttr.get(predicate.value());
195          }
196          return null;
197        }
198    
199        @Override
200        protected void doAdd(InputFile inputFile) {
201          files.add(inputFile);
202        }
203    
204        @Override
205        protected void doIndex(String indexId, Object value, InputFile inputFile) {
206          Map<Object, InputFile> attrValues = fileMap.get(indexId);
207          if (attrValues == null) {
208            attrValues = Maps.newHashMap();
209            fileMap.put(indexId, attrValues);
210          }
211          attrValues.put(value, inputFile);
212        }
213      }
214    
215      private static class GuavaPredicate implements Predicate<InputFile> {
216        private final FilePredicate predicate;
217    
218        private GuavaPredicate(FilePredicate predicate) {
219          this.predicate = predicate;
220        }
221    
222        @Override
223        public boolean apply(@Nullable InputFile input) {
224          return input != null && predicate.apply(input);
225        }
226      }
227    }