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