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