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